pax_global_header00006660000000000000000000000064151327323330014514gustar00rootroot0000000000000052 comment=0922a76d2f1707ff3f5a50efd3a0cd9d65944743 liquidsoap-2.4.2/000077500000000000000000000000001513273233300136735ustar00rootroot00000000000000liquidsoap-2.4.2/.codespellignore000066400000000000000000000001611513273233300170500ustar00rootroot00000000000000als ans fo hda mot nd ot ro ser writen WORS # https://github.com/codespell-project/codespell/issues/2508 nwe te liquidsoap-2.4.2/.gitattributes000066400000000000000000000000721513273233300165650ustar00rootroot00000000000000doc/content/build.md binary doc/content/install.md binary liquidsoap-2.4.2/.github/000077500000000000000000000000001513273233300152335ustar00rootroot00000000000000liquidsoap-2.4.2/.github/FUNDING.yml000066400000000000000000000013541513273233300170530ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: ["http://paypal.me/LiquidsoapMedia"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] liquidsoap-2.4.2/.github/ISSUE_TEMPLATE/000077500000000000000000000000001513273233300174165ustar00rootroot00000000000000liquidsoap-2.4.2/.github/ISSUE_TEMPLATE/bug_report.yml000066400000000000000000000253561513273233300223240ustar00rootroot00000000000000name: Bug report description: Document issues or problems encountered in the software to facilitate resolution and improve functionality. labels: - bug body: - type: textarea id: description attributes: label: Description description: A clear and concise description of what the bug is. validations: required: true - type: textarea id: reproduction attributes: label: Steps to reproduce description: A minimal step-by-step instructions to reproduce the bug. validations: required: true - type: textarea id: expected-behavior attributes: label: Expected behavior description: A clear and concise description of what you expected to happen. validations: required: true - type: textarea id: log attributes: label: Log extracts description: | Please provide extracts from your logs. Make sure to set `log.level := 4`. placeholder: | If unavailable just write N/A. validations: required: true - type: textarea id: script attributes: label: Script extracts description: | Please provide extracts from your liquidsoap script. validations: required: true - type: textarea id: version attributes: label: Liquidsoap version description: Output of `liquidsoap --version`. render: text placeholder: | Liquidsoap 2.2.5 Copyright (c) 2003-2026 Savonet team Liquidsoap is open-source software, released under GNU General Public License. See for more information. validations: required: true - type: textarea id: build-config attributes: label: Liquidsoap build config description: Output of `liquidsoap --build-config`. render: text placeholder: | * Liquidsoap version : 2.2.5 * Compilation options - Release build : true - Git SHA : (none) - OCaml version : 4.14.1 - OS type : Unix - Libs versions : alsa=0.3.0 angstrom=0.15.0 ao=0.2.4 asetmap=0.8.1 asn1-combinators=0.2.6 astring=0.8.5 base64=3.5.1 bigarray=[distributed with Ocaml] bigarray-compat=1.1.0 bigstringaf=0.9.1 bjack=0.1.6 bos=0.2.1 bytes=[distributed with OCaml 4.02 or above] ca-certs=v0.2.3 camlimages.all_formats=4.2.6 camlimages.core=5.0.4 camlimages.exif=5.0.4 camlimages.gif=5.0.4 camlimages.jpeg=5.0.4 camlimages.png=5.0.4 camlimages.tiff=5.0.4 camlimages.xpm=5.0.4 camlp-streams camomile.lib=2.0 cohttp=5.3.0 cohttp-lwt=5.3.0 cohttp-lwt-unix=5.3.0 conduit=6.2.0 conduit-lwt=6.2.0 conduit-lwt-unix=6.2.0 cry=1.0.3 cstruct=6.2.0 ctypes=0.21.1 ctypes-foreign=0.21.1 ctypes.stubs=0.21.1 curl=0.9.2 domain-name=0.4.0 domain_shims dssi=0.1.5 dtools=0.4.5 dune-build-info=3.11.1 dune-private-libs.dune-section=3.11.1 dune-site=3.11.1 dune-site.private=3.11.1 duppy=0.9.4 eqaf=0.9 eqaf.bigstring=0.9 eqaf.cstruct=0.9 faad=0.5.2 fdkaac=0.3.3 ffmpeg-av=1.1.10 ffmpeg-avcodec=1.1.10 ffmpeg-avdevice=1.1.10 ffmpeg-avfilter=1.1.10 ffmpeg-avutil=1.1.10 ffmpeg-swresample=1.1.10 ffmpeg-swscale=1.1.10 fileutils=0.6.4 flac=0.5.1 flac.decoder=0.5.1 flac.ogg=0.5.1 fmt=0.9.0 fpath=0.7.3 frei0r=0.1.2 gd=1.0a5 gen=1.1 gmap=0.3.0 hkdf=1.0.4 inotify=2.4.1 integers ipaddr=5.5.0 ipaddr-sexp=5.5.0 ipaddr.unix=5.5.0 irc-client irc-client-unix jemalloc ladspa=0.2.2 lame=0.3.7 lastfm=0.3.4 lilv=0.1.0 liquidsoap-lang=2.2.5 liquidsoap-lang.console=2.2.5 liquidsoap_alsa=f0fdb0e-dirty liquidsoap_ao=f0fdb0e-dirty liquidsoap_bjack=f0fdb0e-dirty liquidsoap_builtins=f0fdb0e-dirty liquidsoap_camlimages=f0fdb0e-dirty liquidsoap_core=f0fdb0e-dirty liquidsoap_dssi=f0fdb0e-dirty liquidsoap_faad=f0fdb0e-dirty liquidsoap_fdkaac=f0fdb0e-dirty liquidsoap_ffmpeg=f0fdb0e-dirty liquidsoap_flac=f0fdb0e-dirty liquidsoap_frei0r=f0fdb0e-dirty liquidsoap_gd=f0fdb0e-dirty liquidsoap_irc=f0fdb0e-dirty liquidsoap_jemalloc=f0fdb0e-dirty liquidsoap_ladspa=f0fdb0e-dirty liquidsoap_lame=f0fdb0e-dirty liquidsoap_lastfm=f0fdb0e-dirty liquidsoap_lilv=f0fdb0e-dirty liquidsoap_lo=f0fdb0e-dirty liquidsoap_mad=f0fdb0e-dirty liquidsoap_mem_usage=f0fdb0e-dirty liquidsoap_memtrace=f0fdb0e-dirty liquidsoap_ogg=f0fdb0e-dirty liquidsoap_ogg_flac=f0fdb0e-dirty liquidsoap_optionals=f0fdb0e-dirty liquidsoap_opus=f0fdb0e-dirty liquidsoap_osc=f0fdb0e-dirty liquidsoap_oss=f0fdb0e-dirty liquidsoap_portaudio=f0fdb0e-dirty liquidsoap_posix_time=f0fdb0e-dirty liquidsoap_prometheus=f0fdb0e-dirty liquidsoap_pulseaudio=f0fdb0e-dirty liquidsoap_runtime=f0fdb0e-dirty liquidsoap_samplerate=f0fdb0e-dirty liquidsoap_sdl=f0fdb0e-dirty liquidsoap_shine=f0fdb0e-dirty liquidsoap_soundtouch=f0fdb0e-dirty liquidsoap_speex=f0fdb0e-dirty liquidsoap_srt=f0fdb0e-dirty liquidsoap_ssl=f0fdb0e-dirty liquidsoap_stereotool=f0fdb0e-dirty liquidsoap_taglib=f0fdb0e-dirty liquidsoap_theora=f0fdb0e-dirty liquidsoap_tls=f0fdb0e-dirty liquidsoap_vorbis=f0fdb0e-dirty liquidsoap_xmlplaylist=f0fdb0e-dirty liquidsoap_yaml=f0fdb0e-dirty lo=0.2.0 logs=0.7.0 logs.fmt=0.7.0 logs.lwt=0.7.0 lwt=5.7.0 lwt.unix=5.7.0 macaddr=5.5.0 mad=0.5.3 magic-mime=1.3.1 mem_usage=0.1.1 memtrace=0.2.3 menhirLib=20230608 metadata=0.3.0 mirage-crypto=0.11.2 mirage-crypto-ec=0.11.2 mirage-crypto-pk=0.11.2 mirage-crypto-rng=0.11.2 mirage-crypto-rng.unix=0.11.2 mm=0.8.5 mm.audio=0.8.5 mm.base=0.8.5 mm.image=0.8.5 mm.midi=0.8.5 mm.video=0.8.5 ocplib-endian ocplib-endian.bigstring ogg=0.7.4 ogg.decoder=0.7.4 opus=0.2.3 opus.decoder=0.2.3 osc osc-unix parsexp=v0.16.0 pbkdf pcre=7.5.0 portaudio=0.2.3 posix-base=5a7f328 posix-socket=5a7f328 posix-socket.constants=5a7f328 posix-socket.stubs=5a7f328 posix-socket.types=5a7f328 posix-time2=5a7f328 posix-time2.constants=5a7f328 posix-time2.stubs=5a7f328 posix-time2.types=5a7f328 posix-types=5a7f328 posix-types.constants=5a7f328 ppx_sexp_conv.runtime-lib=v0.16.0 prometheus=1.2 prometheus-app=1.2 ptime=1.1.0 ptime.clock.os=1.1.0 pulseaudio=0.1.6 re=1.11.0 result=1.5 rresult=0.7.0 samplerate=0.1.7 saturn_lockfree=0.4.1 sedlex=ccd3dea seq=[distributed with OCaml 4.07 or above] sexplib=v0.16.0 sexplib0=v0.16.0 shine=0.2.3 soundtouch=0.1.9 speex=0.4.2 speex.decoder=0.4.2 srt=0.3.1 srt.constants=0.3.1 srt.stubs=0.3.1 srt.stubs.locked=0.3.1 srt.types=0.3.1 ssl=0.7.0 stdlib-shims=0.3.0 stereotool=f0fdb0e-dirty str=[distributed with Ocaml] stringext=1.6.0 taglib=0.3.10 theora=0.4.1 theora.decoder=0.4.1 threads=[distributed with Ocaml] threads.posix=[internal] tls=0.17.1 tsdl=v1.0.0 tsdl-image=0.5 tsdl-ttf=0.6 unix=[distributed with Ocaml] unix-errno=52c6ecb unix-errno.errno_bindings=52c6ecb unix-errno.errno_types=52c6ecb unix-errno.errno_types_detected=52c6ecb unix-errno.unix=52c6ecb uri=4.4.0 uri-sexp=4.4.0 uri.services=4.4.0 vorbis=0.8.1 vorbis.decoder=0.8.1 x509=0.16.5 xmlm=1.4.0 xmlplaylist=0.1.5 yaml=3.1.0 yaml.bindings=3.1.0 yaml.bindings.types=3.1.0 yaml.c=3.1.0 yaml.ffi=3.1.0 yaml.types=3.1.0 zarith=1.13 - architecture : amd64 - host : x86_64-pc-linux-gnu - target : x86_64-pc-linux-gnu - system : linux - ocamlopt_cflags : -O2 -fno-strict-aliasing -fwrapv -pthread -fPIC - native_c_compiler : gcc -O2 -fno-strict-aliasing -fwrapv -pthread -fPIC -D_FILE_OFFSET_BITS=64 - native_c_libraries : -lm * Configured paths - mode : posix - standard library : /usr/share/liquidsoap/libs - scripted binaries : /usr/share/liquidsoap/bin - rundir : /var/run/liquidsoap - logdir : /var/log/liquidsoap - camomile files : /usr/share/liquidsoap/camomile * Supported input formats - MP3 : yes - AAC : yes - Ffmpeg : yes - Flac (native) : yes - Flac (ogg) : yes - Opus : yes - Speex : yes - Theora : yes - Vorbis : yes * Supported output formats - FDK-AAC : yes - Ffmpeg : yes - MP3 : yes - MP3 (fixed-point) : yes - Flac (native) : yes - Flac (ogg) : yes - Opus : yes - Speex : yes - Theora : yes - Vorbis : yes * Tags - Taglib (ID3 tags) : yes - Vorbis : yes * Input / output - ALSA : yes - AO : yes - FFmpeg : yes - GStreamer : no (requires gstreamer) - JACK : yes - OSS : yes - Portaudio : yes - Pulseaudio : yes - SRT : yes * Audio manipulation - FFmpeg : yes - LADSPA : yes - Lilv : yes - Samplerate : yes - SoundTouch : yes - StereoTool : yes * Video manipulation - camlimages : yes - FFmpeg : yes - frei0r : yes - ImageLib : no (requires imagelib) - SDL : yes * MIDI manipulation - DSSI : yes * Visualization - GD : yes - Graphics : no (requires graphics) - SDL : yes * Additional libraries - FFmpeg filters : yes - FFmpeg devices : yes - inotify : yes - irc : yes - jemalloc : yes - lastfm : yes - lo : yes - memtrace : yes - mem_usage : yes - osc : yes - ssl : yes - tls : yes - posix-time2 : yes - windows service : no (requires winsvc) - YAML support : yes - XML playlists : yes * Monitoring - Prometheus : yes validations: required: true - type: dropdown id: installation-method attributes: label: Installation method description: Specify how the software was installed. options: - From official container image - From official packages in the release artifacts - From distribution packages - From OPAM - From source/self-built default: 0 validations: required: true - type: textarea id: additional attributes: label: Additional Info description: | Please provide any additional information, such as logs, system info, similar issues, etc. For example, specify the distribution used for distribution packages, version of OPAM, or details of the self-built process. validations: required: false liquidsoap-2.4.2/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002611513273233300214050ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Question url: https://github.com/savonet/liquidsoap/discussions/new about: Ask a question about using the software. liquidsoap-2.4.2/.github/ISSUE_TEMPLATE/feature_request.yml000066400000000000000000000016671513273233300233560ustar00rootroot00000000000000name: Feature request description: Submit suggestions for new features or improvements to enhance the software's capabilities and user experience. labels: - enhancement body: - type: textarea id: description attributes: label: Description description: A clear and concise description of the feature you are requesting. placeholder: validations: required: true - type: textarea id: preferable-solution attributes: label: Preferable solution description: A clear and concise description of what you want to happen. - type: textarea id: alternatives attributes: label: Alternative solutions description: A clear and concise description of any alternative solutions or features you've considered. - type: textarea id: additional attributes: label: Additional context description: Add any other context or screenshots about the feature request here. liquidsoap-2.4.2/.github/alpine/000077500000000000000000000000001513273233300165035ustar00rootroot00000000000000liquidsoap-2.4.2/.github/alpine/APKBUILD-minimal.in000066400000000000000000000016441513273233300216570ustar00rootroot00000000000000pkgname=@APK_PACKAGE@ pkgver=@APK_VERSION@ pkgrel=@APK_RELEASE@ pkgdesc="Swiss-army knife for multimedia streaming" url="https://github.com/savonet/liquidsoap" arch="all" license="GPL-2.0-only" install="@APK_PACKAGE@.post-install" options="!check textrels" package() { eval "$(opam env)" cd liquidsoap export LIQUIDSOAP_BUILD_TARGET=posix eval "$(opam config env)" export OCAMLPATH=$(cat ../.ocamlpath) dune build @install --release dune install --relocatable --prefix "${pkgdir}/usr" rm -rf "${pkgdir}/usr/share/liquidsoap-lang/libs/extra" rm -rf "$pkgdir/usr/lib" rm -rf "$pkgdir/usr/share/doc" rm -rf "$pkgdir/usr/share/man" mv "$pkgdir/usr/share/liquidsoap-lang" "$pkgdir/usr/share/liquidsoap" rm -rf "$pkgdir/usr/share/liquidsoap/libs/extra" cp -rf "$(opam var share)/camomile" "$pkgdir/usr/share/liquidsoap" } liquidsoap-2.4.2/.github/alpine/APKBUILD.in000066400000000000000000000015161513273233300202310ustar00rootroot00000000000000pkgname=@APK_PACKAGE@ pkgver=@APK_VERSION@ pkgrel=@APK_RELEASE@ pkgdesc="Swiss-army knife for multimedia streaming" url="https://github.com/savonet/liquidsoap" arch="all" license="GPL-2.0-only" install="@APK_PACKAGE@.post-install" options="!check textrels" depends="sdl2 sdl2_image sdl2_ttf" package() { eval "$(opam env)" cd liquidsoap export LIQUIDSOAP_BUILD_TARGET=posix eval "$(opam config env)" export OCAMLPATH=$(cat ../.ocamlpath) dune build @install --release dune install --relocatable --prefix "${pkgdir}/usr" rm -rf "$pkgdir/usr/lib" rm -rf "$pkgdir/usr/share/doc" rm -rf "$pkgdir/usr/share/man" mv "$pkgdir/usr/share/liquidsoap-lang" "$pkgdir/usr/share/liquidsoap" cp -rf "$(opam var share)/camomile" "$pkgdir/usr/share/liquidsoap" } liquidsoap-2.4.2/.github/alpine/liquidsoap.post-install000077500000000000000000000010041513273233300232260ustar00rootroot00000000000000#!/bin/sh addgroup -S liquidsoap 2> /dev/null adduser -S -D -h /var/liquidsoap -s /sbin/nologin -G liquidsoap -g liquidsoap liquidsoap 2> /dev/null addgroup liquidsoap audio 2> /dev/null mkdir -p /var/log/liquidsoap mkdir -p /var/cache/liquidspap echo "Generating cache for the standard library.." LIQ_CACHE_SYSTEM_DIR=/var/cache/liquidsoap liquidsoap --cache-only '()' chown -R liquidsoap:liquidsoap /var/log/liquidsoap chown liquidsoap:liquidsoap /var/cache/liquidsoap chmod -R +r /var/cache/liquidsoap exit 0 liquidsoap-2.4.2/.github/debian/000077500000000000000000000000001513273233300164555ustar00rootroot00000000000000liquidsoap-2.4.2/.github/debian/compat000066400000000000000000000000031513273233300176540ustar00rootroot0000000000000010 liquidsoap-2.4.2/.github/debian/control.in000066400000000000000000000061201513273233300204640ustar00rootroot00000000000000Source: @LIQ_PACKAGE@ Section: sound Priority: optional Maintainer: Romain Beauxis Build-Depends: debhelper (>= 10) Standards-Version: 4.3.0 Homepage: https://liquidsoap.info/ Package: @LIQ_PACKAGE@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, libsdl2-2.0-0, libsdl2-image-2.0-0, libsdl2-ttf-2.0-0, bubblewrap, adduser Recommends: logrotate, ffmpeg Suggests: icecast2, awscli Provides: liquidsoap-snapshot Conflicts: liquidsoap-snapshot Replaces: liquidsoap, liquidsoap-snapshot, liquidsoap-plugin-all, liquidsoap-plugin-alsa [linux-any], liquidsoap-plugin-ao, liquidsoap-plugin-camlimages, liquidsoap-plugin-dssi, liquidsoap-plugin-faad, liquidsoap-plugin-flac, liquidsoap-plugin-frei0r, liquidsoap-plugin-gavl, liquidsoap-plugin-gd, liquidsoap-plugin-graphics, liquidsoap-plugin-gstreamer, liquidsoap-plugin-icecast, liquidsoap-plugin-jack, liquidsoap-plugin-ladspa, liquidsoap-plugin-lame, liquidsoap-plugin-lastfm, liquidsoap-plugin-lo, liquidsoap-plugin-mad, liquidsoap-plugin-ogg, liquidsoap-plugin-opus, liquidsoap-plugin-oss, liquidsoap-plugin-portaudio, liquidsoap-plugin-pulseaudio, liquidsoap-plugin-samplerate, liquidsoap-plugin-schroedinger, liquidsoap-plugin-sdl, liquidsoap-plugin-shine, liquidsoap-plugin-soundtouch, liquidsoap-plugin-speex, liquidsoap-plugin-taglib, liquidsoap-plugin-theora, liquidsoap-plugin-voaacenc, liquidsoap-plugin-vorbis, liquidsoap-plugin-xmlplaylist Breaks: liquidsoap, liquidsoap-plugin-all, liquidsoap-plugin-alsa [linux-any], liquidsoap-plugin-ao, liquidsoap-plugin-camlimages, liquidsoap-plugin-dssi, liquidsoap-plugin-faad, liquidsoap-plugin-flac, liquidsoap-plugin-frei0r, liquidsoap-plugin-gavl, liquidsoap-plugin-gd, liquidsoap-plugin-graphics, liquidsoap-plugin-gstreamer, liquidsoap-plugin-icecast, liquidsoap-plugin-jack, liquidsoap-plugin-ladspa, liquidsoap-plugin-lame, liquidsoap-plugin-lastfm, liquidsoap-plugin-lo, liquidsoap-plugin-mad, liquidsoap-plugin-ogg, liquidsoap-plugin-opus, liquidsoap-plugin-oss, liquidsoap-plugin-portaudio, liquidsoap-plugin-pulseaudio, liquidsoap-plugin-samplerate, liquidsoap-plugin-schroedinger, liquidsoap-plugin-sdl, liquidsoap-plugin-shine, liquidsoap-plugin-soundtouch, liquidsoap-plugin-speex, liquidsoap-plugin-taglib, liquidsoap-plugin-theora, liquidsoap-plugin-voaacenc, liquidsoap-plugin-vorbis, liquidsoap-plugin-xmlplaylist Description: audio and video streaming language Liquidsoap is a powerful tool for building complex audio and video streaming systems, typically targeting internet radios (e.g. icecast streams). . It consists of a simple script language, in which you can create, combine and transform audio sources. Its design makes liquidsoap flexible and easily extensible. . Some of the typical uses are: * dynamically generating playlists depending on the time or other factors * having smooth transitions between songs * adding jingles periodically * applying effects on the sound like volume normalization * reencoding the stream at various qualities * remotely controlling the stream liquidsoap-2.4.2/.github/debian/dirs000066400000000000000000000001211513273233300173330ustar00rootroot00000000000000usr/bin etc/init.d etc/bash_completion.d var/log/liquidsoap var/cache/liquidsoap liquidsoap-2.4.2/.github/debian/install000066400000000000000000000002711513273233300200460ustar00rootroot00000000000000bin/liquidsoap usr/bin share/bash_completion/completions/liquidsoap etc/bash_completion.d share/liquidsoap-lang/libs usr/share/liquidsoap share/liquidsoap/camomile usr/share/liquidsoap liquidsoap-2.4.2/.github/debian/manpages000066400000000000000000000000411513273233300201660ustar00rootroot00000000000000debian/tmp/man/man1/liquidsoap.1 liquidsoap-2.4.2/.github/debian/postinst000066400000000000000000000022151513273233300202630ustar00rootroot00000000000000#!/bin/sh # postinst script for liquidsoap set -e case "$1" in configure) ;; abort-upgrade | abort-remove | abort-deconfigure) exit 0 ;; *) echo "postinst called with unknown argument \`$1'" >&2 exit 1 ;; esac if ! getent group liquidsoap > /dev/null; then addgroup --system liquidsoap fi # Create the new system account id liquidsoap > dev/null 2>&1 || ( adduser --system --disabled-password --disabled-login \ --home /var/cache/liquidsoap --ingroup liquidsoap liquidsoap && usermod --append --groups audio liquidsoap ) # Add again /usr/share/liquidsoap if user was already created if ! test -d /usr/share/liquidsoap; then mkdir /usr/share/liquidsoap fi if ! test -d /var/cache/liquidsoap; then mkdir -p /var/cache/liquidsoap fi echo "Generating cache for the standard library.." LIQ_CACHE_SYSTEM_DIR=/var/cache/liquidsoap liquidsoap --cache-only '()' # Fix directories ownership chown -R liquidsoap:liquidsoap /var/log/liquidsoap chmod -R +r /var/log/liquidsoap chown -R liquidsoap:liquidsoap /var/cache/liquidsoap chmod -R g+w /var/cache/liquidsoap chown -R root:root /usr/share/liquidsoap #DEBHELPER# exit 0 liquidsoap-2.4.2/.github/debian/postrm000066400000000000000000000003351513273233300177250ustar00rootroot00000000000000#!/bin/sh # postrm script for liquidsoap set -e if [ "$1" = "purge" ] && [ -d /usr/share/liquidsoap ]; then if ! rmdir /usr/share/liquidsoap; then echo "leaving /usr/share/liquidsoap in place" fi fi #DEBHELPER# liquidsoap-2.4.2/.github/debian/rules000077500000000000000000000007541513273233300175430ustar00rootroot00000000000000#!/usr/bin/make -f DESTDIR := debian/tmp %: dh $@ override_dh_autoreconf: /bin/true override_dh_auto_configure: /bin/true override_dh_auto_build: /bin/true override_dh_auto_test: /bin/true override_dh_auto_install: export LIQUIDSOAP_BUILD_TARGET=posix dune build @install --release dune install --relocatable --prefix $(DESTDIR) mkdir -p $(DESTDIR)/share/liquidsoap cp -rf `opam var share`/camomile $(DESTDIR)/share/liquidsoap dh_install override_dh_auto_clean: /bin/true liquidsoap-2.4.2/.github/debian/rules-minimal000077500000000000000000000010401513273233300211540ustar00rootroot00000000000000#!/usr/bin/make -f DESTDIR := debian/tmp %: dh $@ override_dh_autoreconf: /bin/true override_dh_auto_configure: /bin/true override_dh_auto_build: /bin/true override_dh_auto_test: /bin/true override_dh_auto_install: export LIQUIDSOAP_BUILD_TARGET=posix dune build @install --release dune install --relocatable --prefix $(DESTDIR) rm -rf $(DESTDIR)/share/liquidsoap-lang/libs/extra mkdir -p $(DESTDIR)/share/liquidsoap cp -rf `opam var share`/camomile $(DESTDIR)/share/liquidsoap dh_install override_dh_auto_clean: /bin/true liquidsoap-2.4.2/.github/debian/source/000077500000000000000000000000001513273233300177555ustar00rootroot00000000000000liquidsoap-2.4.2/.github/debian/source/format000066400000000000000000000000141513273233300211630ustar00rootroot000000000000003.0 (quilt) liquidsoap-2.4.2/.github/docker/000077500000000000000000000000001513273233300165025ustar00rootroot00000000000000liquidsoap-2.4.2/.github/docker/alpine.dockerfile000066400000000000000000000007171513273233300220100ustar00rootroot00000000000000FROM alpine:edge AS downloader ARG APK_FILE COPY $APK_FILE /downloads/liquidsoap.apk FROM alpine:edge RUN --mount=type=bind,from=downloader,source=/downloads,target=/downloads \ set -eux; \ echo 'https://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories; \ apk add --allow-untrusted --no-cache \ /downloads/liquidsoap.apk \ ; USER liquidsoap RUN liquidsoap --cache-stdlib ENTRYPOINT ["/usr/bin/liquidsoap"] liquidsoap-2.4.2/.github/docker/debian.dockerfile000066400000000000000000000032601513273233300217560ustar00rootroot00000000000000FROM debian:13-slim AS downloader ARG DEB_FILE ARG DEB_DEBUG_FILE COPY $DEB_FILE /downloads/liquidsoap.deb COPY $DEB_DEBUG_FILE /downloads/liquidsoap-debug.deb ARG DEB_MULTIMEDIA_KEYRING="https://www.deb-multimedia.org/pool/main/d/deb-multimedia-keyring/deb-multimedia-keyring_2024.9.1_all.deb" ARG DEB_MULTIMEDIA_KEYRING_SHA256SUM="8dc6cbb266c701cfe58bd1d2eb9fe2245a1d6341c7110cfbfe3a5a975dcf97ca" RUN set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ ca-certificates \ wget \ ; \ wget "$DEB_MULTIMEDIA_KEYRING" -O /downloads/deb-multimedia-keyring.deb; \ echo "$DEB_MULTIMEDIA_KEYRING_SHA256SUM /downloads/deb-multimedia-keyring.deb" | sha256sum -c -; FROM debian:13-slim ARG DEBIAN_FRONTEND=noninteractive # For ffmpeg with libfdk-aac RUN --mount=type=bind,from=downloader,source=/downloads,target=/downloads \ set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ /downloads/deb-multimedia-keyring.deb \ ca-certificates \ ; \ cat < /etc/apt/sources.list.d/dmo.sources Types: deb URIs: https://www.deb-multimedia.org Suites: trixie Components: main non-free Signed-By: /usr/share/keyrings/deb-multimedia-keyring.pgp Enabled: yes EOF RUN --mount=type=bind,from=downloader,source=/downloads,target=/downloads \ set -eux; \ apt-get update; \ apt-get install -y --no-install-recommends \ /downloads/liquidsoap.deb \ /downloads/liquidsoap-debug.deb \ ; \ rm -rf \ /var/lib/apt/lists \ /var/lib/dpkg/status-old \ ; USER liquidsoap RUN liquidsoap --cache-stdlib ENTRYPOINT ["/usr/bin/liquidsoap"] liquidsoap-2.4.2/.github/docker/website.dockerfile000066400000000000000000000036131513273233300222000ustar00rootroot00000000000000FROM savonet/liquidsoap-ci:debian_trixie_amd64 MAINTAINER The Savonet Team USER root # The following could also be interesting but compilation of the website takes # forever: amb-plugins caps cmt csladspa fomp lsp-plugins-ladspa lsp-plugins-lv2 RUN apt-get update && apt-get -y dist-upgrade && apt-get -y install openssh-client && apt-get install -y abgate calf-plugins swh-lv2 swh-plugins tap-plugins zam-plugins frei0r-plugins # This is until the next image rebuild: RUN apt-get -y install libcurl4-gnutls-dev USER opam WORKDIR /tmp/liquidsoap-full RUN rm -rf website/savonet.github.io RUN git remote set-url origin https://github.com/savonet/liquidsoap-full.git && \ git fetch --recurse-submodules=no && git checkout origin/master -- Makefile.git && \ git reset --hard && \ git pull && \ git submodule init ocaml-metadata && \ git submodule update ocaml-metadata && \ make public RUN eval "$(opam config env)" && make clean RUN eval "$(opam config env)" && cd ocaml-metadata && opam install -y . fileutils RUN eval "$(opam config env)" && \ git clone https://github.com/savonet/ocaml-posix.git && \ cd ocaml-posix && \ opam install -y . RUN cd liquidsoap && \ mv .git /tmp && \ rm -rf * && \ mv /tmp/.git . && \ git reset --hard RUN make public && make update # TODO: Remove gstreamer from liquidsoap-full RUN cat PACKAGES.default | grep -v gstreamer > PACKAGES RUN eval "$(opam config env)" && \ export PKG_CONFIG_PATH=/usr/share/pkgconfig/pkgconfig && \ touch liquidsoap/configure && \ ./configure --enable-graphics && \ rm liquidsoap/configure && \ export OCAMLPATH="$(cat .ocamlpath)" && \ cd liquidsoap && \ dune build && \ dune build --release src/js WORKDIR /tmp/liquidsoap-full/website RUN eval "$(opam config env)" && opam install -y odoc && make clean && git pull && make dist liquidsoap-2.4.2/.github/opam/000077500000000000000000000000001513273233300161675ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/liquidsoap-windows.opam000066400000000000000000000127721513273233300227200ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: "Swiss-army knife for multimedia streaming" description: """ Liquidsoap is a powerful and flexible language for describing your streams. It offers a rich collection of operators that you can combine at will, giving you more power than you need for creating or transforming streams. But liquidsoap is still very light and easy to use, in the Unix tradition of simple strong components working together. """ maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.2"} "ocaml-windows" {>= "4.14"} "camomile-windows" {>= "2.0.0"} "camomile" {>= "2.0.0"} "dtools-windows" {>= "0.4.5"} "duppy-windows" {>= "0.9.4"} "mm-windows" {>= "0.8.4"} "re-windows" {>= "1.11.0"} "cry-windows" {>= "1.0.1"} "saturn_lockfree-windows" {>= "0.5.0"} "sedlex" {>= "3.2"} "sedlex-windows" {>= "3.2"} "magic-mime-windows" "menhir" "menhirLib-windows" "uri" "uri-windows" "fileutils" "fileutils-windows" "curl-windows" "xml-light-windows" "mem_usage-windows" {>= "0.1.1"} "metadata-windows" {>= "0.3.0"} "dune-site-windows" "dune-build-info-windows" "ppx_string" {build} "ppx_string-windows" {build} "ppx_hash" {build} "ppx_hash-windows" {build} ] depopts: [ "alsa-windows" "ao-windows" "bjack-windows" "camlimages-windows" "dssi-windows" "faad-windows" "fdkaac-windows" "ffmpeg-windows" "flac-windows" "frei0r-windows" "gd-windows" "graphics-windows" "inotify-windows" "irc-client-unix-windows" "ladspa-windows" "lame-windows" "lastfm-windows" "lilv-windows" "lo-windows" "mad-windows" "memtrace-windows" "curl-windows" "ogg-windows" "opus-windows" "osx-secure-transport-windows" "portaudio-windows" "posix-time2-windows" "posix-socket-windows" "pulseaudio-windows" "prometheus-liquidsoap-windows" "samplerate-windows" "shine-windows" "soundtouch-windows" "speex-windows" "srt-windows" "ssl-windows" "theora-windows" "tsdl-windows" "tsdl-image-windows" "tsdl-ttf-windows" "vorbis-windows" "xmlplaylist-windows" ] conflicts: [ "alsa-windows" {< "0.3.0"} "ao-windows" {< "0.2.0"} "bjack-windows" {< "0.1.3"} "dssi-windows" {< "0.1.3"} "faad-windows" {< "0.5.0"} "fdkaac-windows" {< "0.3.1"} "ffmpeg-windows" {< "1.2.5"} "ffmpeg-avutil-windows" {< "1.2.5"} "flac-windows" {< "0.3.0"} "frei0r-windows" {< "0.1.0"} "inotify-windows" {< "1.0"} "ladspa-windows" {< "0.2.0"} "lame-windows" {< "0.3.5"} "lastfm-windows" {< "0.3.0"} "lo-windows" {< "0.2.0"} "liquidsoap-windows" {< "2.2.0"} "mad-windows" {< "0.5.0"} "magic-windows" {< "0.6"} "curl-windows" {< "0.9.2"} "ogg-windows" {< "0.7.4"} "opus-windows" {< "0.2.0"} "portaudio-windows" {< "0.2.0"} "posix-socket-windows" {< "2.1.0"} "pulseaudio-windows" {< "0.1.4"} "samplerate-windows" {< "0.1.5"} "shine-windows" {< "0.2.0"} "soundtouch-windows" {< "0.1.9"} "speex-windows" {< "0.4.0"} "srt-windows" {< "0.3.3"} "ssl-windows" {< "0.5.2"} "sdl-liquidsoap-windows" {< "2"} "tsdl-image-windows" {< "0.3.2"} "theora-windows" {< "0.4.0"} "vorbis-windows" {< "0.8.0"} "xmlplaylist-windows" {< "0.1.3"} ] build: [ [ "env" "LIQUIDSOAP_BUILD_TARGET=standalone" "LIQUIDSOAP_SYS_CONFIG=mingw" "LIQUIDSOAP_ENABLE_BUILD_CONFIG=false" "LIQ_LDFLAGS=-lcurl -lwldap32 -ldl -lnghttp2 -lpsl -lssh2 -lidn2 -lzstd -lunistring -lbrotlicommon -lbrotlidec -lcrypt32 -liconv -lpthread -lz -lbcrypt -lwinmm -lksuser -link /usr/src/mxe/usr/x86_64-w64-mingw32.static/lib/libavutil.a" "dune" "build" "-x" "windows" "-p" "liquidsoap-lang,liquidsoap" "@install" "-j" jobs ] ] post-messages: [ """\ We're sorry that your liquidsoap install failed. Check out our installation instructions at: https://www.liquidsoap.info/doc-%{version}%/install.html#opam for more information.""" {failure} "✨ Congratulations on installing liquidsoap! ✨" {success} """\ We noticed that you did not install any mp3 decoder. This is a feature most users want. You might need to install the mad or ffmpeg package.""" {success & !mad-enabled & !ffmpeg-enabled} """\ We noticed that you did not install any mp3 encoder. This is a feature most users want. You might need to install the lame or shine package.""" {success & !lame-enabled & !shine-enabled & !ffmpeg-enabled} """\ We noticed that you did not install the samplerate package. We strongly recommend this package for audio samplerate conversion.""" {success & !samperate-enabled} """\ We noticed that you did not install the curl package. We strongly recommend this package for http request resolving support.""" {success & !curl-enabled} """\ We noticed that you did not install the cry package that provides icecast output. This is a feature most users want.""" {success & !cry-enabled} """\ We noticed that you did not install any ssl support package. Liquidsoap won't be able to use any HTTPS feature. You might want to install one of ssl or osx-secure-transport package.""" {success & !ssl-enabled & !secure-transport-enabled} ] depexts: ["coreutils"] {os = "macos" & os-distribution = "homebrew"} dev-repo: "git+https://github.com/savonet/liquidsoap.git" url { src: "https://github.com/savonet/liquidsoap/archive/@COMMIT_SHORT@.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/000077500000000000000000000000001513273233300177455ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-av-windows/000077500000000000000000000000001513273233300233055ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-av-windows/ffmpeg-av-windows.1.2.5/000077500000000000000000000000001513273233300273075ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-av-windows/ffmpeg-av-windows.1.2.5/opam000066400000000000000000000016061513273233300301710ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg libraries -- top-level helpers" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "ffmpeg-avutil-windows" {= version} "ffmpeg-avcodec-windows" {= version} ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-av" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avcodec-windows/000077500000000000000000000000001513273233300243035ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avcodec-windows/ffmpeg-avcodec-windows.1.2.5/000077500000000000000000000000001513273233300313035ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avcodec-windows/ffmpeg-avcodec-windows.1.2.5/opam000066400000000000000000000015251513273233300321650ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg avcodec library" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "ffmpeg-avutil-windows" {= version} ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-avcodec" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avdevice-windows/000077500000000000000000000000001513273233300244655ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avdevice-windows/ffmpeg-avdevice-windows.1.2.5/000077500000000000000000000000001513273233300316475ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avdevice-windows/ffmpeg-avdevice-windows.1.2.5/opam000066400000000000000000000015231513273233300325270ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg avdevice library" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "ffmpeg-av-windows" {= version} ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-avdevice" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avfilter-windows/000077500000000000000000000000001513273233300245135ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avfilter-windows/ffmpeg-avfilter-windows.1.2.5/000077500000000000000000000000001513273233300317235ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avfilter-windows/ffmpeg-avfilter-windows.1.2.5/opam000066400000000000000000000015271513273233300326070ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg avfilter library" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "ffmpeg-avutil-windows" {= version} ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-avfilter" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avutil-windows/000077500000000000000000000000001513273233300242035ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avutil-windows/ffmpeg-avutil-windows.1.2.5/000077500000000000000000000000001513273233300311035ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-avutil-windows/ffmpeg-avutil-windows.1.2.5/opam000066400000000000000000000015001513273233300317560ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg avutil libraries" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "base-threads" ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-avutil" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-swresample-windows/000077500000000000000000000000001513273233300250615ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-swresample-windows/ffmpeg-swresample-windows.1.2.5/000077500000000000000000000000001513273233300326375ustar00rootroot00000000000000opam000066400000000000000000000016021513273233300334360ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-swresample-windows/ffmpeg-swresample-windows.1.2.5opam-version: "2.0" synopsis: "Bindings for the ffmpeg swresample library" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "ffmpeg-avutil-windows" {= version} "ffmpeg-avcodec-windows" {= version} ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-swresample" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-swscale-windows/000077500000000000000000000000001513273233300243405ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-swscale-windows/ffmpeg-swscale-windows.1.2.5/000077500000000000000000000000001513273233300313755ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-swscale-windows/ffmpeg-swscale-windows.1.2.5/opam000066400000000000000000000015251513273233300322570ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg swscale library" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "conf-pkg-config" {build} "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "dune-configurator" {build} "ffmpeg-avutil-windows" {= version} ] conflicts: [ "ffmpeg-windows" {< "0.5.0"} ] depexts: [ ["ffmpeg"] {os-distribution = "mxe"} ] build: [ [ "dune" "build" "-p" "ffmpeg-swscale" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/packages/ffmpeg-windows/000077500000000000000000000000001513273233300227015ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-windows/ffmpeg-windows.1.2.5/000077500000000000000000000000001513273233300262775ustar00rootroot00000000000000liquidsoap-2.4.2/.github/opam/packages/ffmpeg-windows/ffmpeg-windows.1.2.5/opam000066400000000000000000000016251513273233300271620ustar00rootroot00000000000000opam-version: "2.0" synopsis: "Bindings for the ffmpeg libraries" maintainer: "Romain Beauxis " authors: "The Savonet Team " license: "LGPL-2.1-only" homepage: "https://github.com/savonet/ocaml-ffmpeg" bug-reports: "https://github.com/savonet/ocaml-ffmpeg/issues" depends: [ "ocaml-windows" {>= "4.08.0"} "dune" {>= "3.6"} "ffmpeg-av-windows" {= version} "ffmpeg-avutil-windows" {= version} "ffmpeg-avcodec-windows" {= version} "ffmpeg-avfilter-windows" {= version} "ffmpeg-avdevice-windows" {= version} "ffmpeg-swscale-windows" {= version} "ffmpeg-swresample-windows" {= version} ] build: [ [ "dune" "build" "-p" "ffmpeg" "-x" "windows" "-j" jobs "@install" ] ] dev-repo: "git+https://github.com/savonet/ocaml-ffmpeg.git" url { src: "https://github.com/savonet/ocaml-ffmpeg/archive/main.tar.gz" } liquidsoap-2.4.2/.github/opam/repo000066400000000000000000000000241513273233300170530ustar00rootroot00000000000000opam-version: "2.0" liquidsoap-2.4.2/.github/renovate.json000066400000000000000000000007711513273233300177560ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:best-practices", "helpers:pinGitHubActionDigestsToSemver", ":automergeMinor" ], "packageRules": [ { "matchDepNames": [ "mknejp/delete-release-assets", "savonet/aws-s3-docker-action", "savonet/latest-tag" ], "matchManagers": ["github-actions"], "enabled": false }, { "matchCategories": ["docker"], "enabled": false } ] } liquidsoap-2.4.2/.github/scripts/000077500000000000000000000000001513273233300167225ustar00rootroot00000000000000liquidsoap-2.4.2/.github/scripts/add-local-opam-packages.sh000077500000000000000000000010321513273233300236030ustar00rootroot00000000000000#!/bin/sh set -e PWD=$(dirname "$0") BASE_DIR=$(cd "${PWD}/../.." && pwd) RELEASE=$GITHUB_SHA eval "$(opam config env)" opam unpin --all -n cd "${BASE_DIR}" mkdir -p "./.github/opam/packages/liquidsoap-windows/liquidsoap-windows.${RELEASE}" cp ./.github/opam/liquidsoap-windows.opam "./.github/opam/packages/liquidsoap-windows/liquidsoap-windows.${RELEASE}/opam" sed -e "s#@COMMIT_SHORT@#$RELEASE#g" -i "./.github/opam/packages/liquidsoap-windows/liquidsoap-windows.${RELEASE}/opam" opam remote add liquidsoap-devel ./.github/opam liquidsoap-2.4.2/.github/scripts/build-apk.sh000077500000000000000000000051671513273233300211420ustar00rootroot00000000000000#!/bin/sh set -e cd /tmp/liquidsoap-full/liquidsoap APK_VERSION=$(opam show -f version ./opam/liquidsoap.opam | cut -d'-' -f 1) COMMIT_SHORT=$(echo "${GITHUB_SHA}" | cut -c-7) export LIQUIDSOAP_BUILD_TARGET=posix export ABUILD_APK_INDEX_OPTS="--allow-untrusted" if [ -n "${IS_ROLLING_RELEASE}" ]; then APK_PACKAGE="liquidsoap-${COMMIT_SHORT}-${ALPINE_TAG}-${ALPINE_ARCH}" elif [ -n "${IS_RELEASE}" ]; then APK_PACKAGE="liquidsoap-${ALPINE_TAG}-${ALPINE_ARCH}" else ALPINE_BRANCH=$(echo "${BRANCH}" | tr '[:upper:]' '[:lower:]' | sed -e 's#[^0-9^a-z^A-Z^.^-]#-#g') APK_PACKAGE="liquidsoap-${ALPINE_BRANCH}-${ALPINE_TAG}-${ALPINE_ARCH}" fi echo "::group:: build ${APK_PACKAGE}.." cd /tmp/liquidsoap-full sed -e "s#@APK_PACKAGE@#${APK_PACKAGE}#" liquidsoap/.github/alpine/APKBUILD.in | sed -e "s#@APK_VERSION@#${APK_VERSION}#" | sed -e "s#@APK_RELEASE@#${APK_RELEASE}#" \ > APKBUILD cp "liquidsoap/.github/alpine/liquidsoap.post-install" "${APK_PACKAGE}.post-install" abuild-keygen -a -n abuild mv /home/opam/packages/tmp/"${ALPINE_ARCH}"/*.apk "${LIQ_TMP_DIR}" echo "::endgroup::" if [ "${ARCH}" = "amd64" ]; then echo "::group:: save build config for ${APK_PACKAGE}.." eval "$(opam config env)" OCAMLPATH=$(cat .ocamlpath) export OCAMLPATH cd liquidsoap && ./liquidsoap --build-config > "${LIQ_TMP_DIR}/${APK_PACKAGE}-${APK_VERSION}-r${APK_RELEASE}.config" echo "::endgroup::" fi rm -rf APKBUILD /home/opam/packages/tmp/"${ALPINE_ARCH}" echo "::group:: building ${APK_PACKAGE}-minimal.." # shellcheck disable=SC2086 opam remove -y --assume-depexts $MINIMAL_EXCLUDE_DEPS eval "$(opam config env)" cd /tmp/liquidsoap-full make clean cp PACKAGES.minimal-build PACKAGES cd liquidsoap ./.github/scripts/build-posix.sh 1 cd /tmp/liquidsoap-full OCAMLPATH=$(cat .ocamlpath) export OCAMLPATH sed -e "s#@APK_PACKAGE@#${APK_PACKAGE}-minimal#" liquidsoap/.github/alpine/APKBUILD-minimal.in | sed -e "s#@APK_VERSION@#${APK_VERSION}#" | sed -e "s#@APK_RELEASE@#${APK_RELEASE}#" \ > APKBUILD cp "liquidsoap/.github/alpine/liquidsoap.post-install" "${APK_PACKAGE}-minimal.post-install" abuild-keygen -a -n abuild mv /home/opam/packages/tmp/"${ALPINE_ARCH}"/*.apk "${LIQ_TMP_DIR}" echo "::endgroup::" if [ "${ARCH}" = "amd64" ]; then echo "::group:: save build config for ${APK_PACKAGE}-minimal.." cd liquidsoap && ./liquidsoap --build-config > "${LIQ_TMP_DIR}/${APK_PACKAGE}-minimal-${APK_VERSION}-r${APK_RELEASE}.config" fi echo "::endgroup::" { echo "basename=${APK_PACKAGE}-${APK_VERSION}-r${APK_RELEASE}.apk" echo "basename-minimal=${APK_PACKAGE}-minimal-${APK_VERSION}-r${APK_RELEASE}.apk" } >> "${GITHUB_OUTPUT}" liquidsoap-2.4.2/.github/scripts/build-deb.sh000077500000000000000000000051471513273233300211170ustar00rootroot00000000000000#!/bin/sh set -e ARCH=$(dpkg --print-architecture) COMMIT_SHORT=$(echo "${GITHUB_SHA}" | cut -c-7) export DEBFULLNAME="The Savonet Team" export DEBEMAIL="savonet-users@lists.sourceforge.net" export LIQUIDSOAP_BUILD_TARGET=posix cd /tmp/liquidsoap-full/liquidsoap eval "$(opam config env)" OCAMLPATH="$(cat ../.ocamlpath)" export OCAMLPATH LIQ_VERSION=$(opam show -f version ./opam/liquidsoap.opam | cut -d'-' -f 1) LIQ_TAG=$(echo "${DOCKER_TAG}" | sed -e 's#_#-#g') if [ -n "${IS_ROLLING_RELEASE}" ]; then LIQ_PACKAGE="liquidsoap-${COMMIT_SHORT}" elif [ -n "${IS_RELEASE}" ]; then LIQ_PACKAGE="liquidsoap" else TAG=$(echo "${BRANCH}" | tr '[:upper:]' '[:lower:]' | sed -e 's#[^0-9^a-z^A-Z^.^-]#-#g') LIQ_PACKAGE="liquidsoap-${TAG}" fi echo "::group:: build ${LIQ_PACKAGE}.." cp -R .github/debian . rm -rf debian/changelog cp -f debian/control.in debian/control sed -e "s#@LIQ_PACKAGE@#${LIQ_PACKAGE}#g" -i debian/control dch --create --distribution unstable --package "${LIQ_PACKAGE}" --newversion "1:${LIQ_VERSION}-${LIQ_TAG}-${DEB_RELEASE}" "Build ${COMMIT_SHORT}" fakeroot debian/rules binary echo "::endgroup::" if [ "${PLATFORM}" = "amd64" ]; then echo "::group:: save build config for ${LIQ_PACKAGE}.." ./liquidsoap --build-config > "${LIQ_TMP_DIR}/${LIQ_PACKAGE}_${LIQ_VERSION}-${LIQ_TAG}-${DEB_RELEASE}.config" mv /tmp/liquidsoap-full/*.deb "${LIQ_TMP_DIR}" fi echo "::endgroup::" echo "::group:: build ${LIQ_PACKAGE}-minimal.." # shellcheck disable=SC2086 opam remove -y --verbose --assume-depexts $MINIMAL_EXCLUDE_DEPS cd /tmp/liquidsoap-full make clean cp PACKAGES.minimal-build PACKAGES rm .ocamlpath cd liquidsoap ./.github/scripts/build-posix.sh 1 OCAMLPATH="$(cat ../.ocamlpath)" export OCAMLPATH rm -rf debian cp -R .github/debian . rm -rf debian/changelog cp -f debian/control.in debian/control sed -e "s#@LIQ_PACKAGE@#${LIQ_PACKAGE}-minimal#g" -i debian/control cp -R debian/rules-minimal debian/rules dch --create --distribution unstable --package "${LIQ_PACKAGE}-minimal" --newversion "1:${LIQ_VERSION}-${LIQ_TAG}-${DEB_RELEASE}" "Build ${COMMIT_SHORT}" fakeroot debian/rules binary echo "::endgroup::" if [ "${PLATFORM}" = "amd64" ]; then echo "::group:: save build config for ${LIQ_PACKAGE}.." ./liquidsoap --build-config > "${LIQ_TMP_DIR}/${LIQ_PACKAGE}-minimal_${LIQ_VERSION}-${LIQ_TAG}-${DEB_RELEASE}.config" echo "::endgroup::" fi mv /tmp/liquidsoap-full/*.deb "${LIQ_TMP_DIR}" { echo "basename=${LIQ_PACKAGE}_${LIQ_VERSION}-${LIQ_TAG}-${DEB_RELEASE}_$ARCH" echo "basename-minimal=${LIQ_PACKAGE}-minimal_${LIQ_VERSION}-${LIQ_TAG}-${DEB_RELEASE}_$ARCH" } >> "${GITHUB_OUTPUT}" liquidsoap-2.4.2/.github/scripts/build-details.sh000077500000000000000000000053151513273233300220070ustar00rootroot00000000000000#!/bin/bash set -e if [ -n "${GITHUB_HEAD_REF}" ]; then BRANCH="${GITHUB_HEAD_REF#refs_heads_}" else BRANCH="${GITHUB_REF}" fi BRANCH="${BRANCH#refs_heads_}" BRANCH="${BRANCH#refs/heads/}" BRANCH="${BRANCH#refs/tags/}" BRANCH="${BRANCH//\//_}" echo "Detected branch: ${BRANCH}" if [ "${IS_FORK}" == "true" ]; then echo "Branch is from a fork" IS_FORK=true fi if [[ "${IS_FORK}" != "true" && ("${BRANCH}" =~ ^rolling-release\-v[0-9]\.[0-9]\.x || "${BRANCH}" =~ ^v[0-9]\.[0-9]\.[0-9]) ]]; then echo "Branch is release branch" IS_RELEASE=true echo "Branch has a docker release" DOCKER_RELEASE=true else echo "Branch is not release branch" IS_RELEASE= echo "Branch does not have a docker release" DOCKER_RELEASE= fi BUILD_OS='["debian_trixie", "debian_forky", "ubuntu_plucky", "ubuntu_noble", "alpine"]' BUILD_PLATFORM='["amd64", "arm64"]' BUILD_INCLUDE='[{"platform": "amd64", "runs-on": "depot-ubuntu-24.04-4", "alpine-arch": "x86_64", "docker-debian-os": "trixie"}, {"platform": "arm64", "runs-on": "depot-ubuntu-24.04-arm-4", "alpine-arch": "aarch64", "docker-debian-os": "trixie"}]' SHA=$(git rev-parse --short HEAD) if [[ "${BRANCH}" =~ "rolling-release-" ]]; then echo "Branch is rolling release" IS_ROLLING_RELEASE=true else IS_ROLLING_RELEASE= fi if [ "${IS_FORK}" != "true" ] && [ "${IS_RELEASE}" != "true" ] && [ "${IS_ROLLING_RELEASE}" != "true" ]; then echo "Save tests traces" SAVE_TRACES=true else echo "Disable tests traces upload" SAVE_TRACES= fi if [ "${IS_RELEASE}" != "true" ] || [ "${IS_ROLLING_RELEASE}" == "true" ]; then echo "Build is a snapshot" IS_SNAPSHOT=true else IS_SNAPSHOT= fi MINIMAL_EXCLUDE_DEPS="alsa ao bjack camlimages dssi faad fdkaac flac frei0r gd graphics irc-client-unix ladspa lame lastfm lilv lo mad magic ogg opus osc-unix portaudio pulseaudio samplerate shine soundtouch speex srt tls theora tsdl sqlite3 vorbis sdl-liquidsoap" echo "Ocaml version to build: 4.14.2, 5.4.0" OCAML_VERSION='["4.14.2", "5.4.0"]' echo "OCaml docker release version: 4.14.2" OCAML_DOCKER_RELEASE_VERSION="4.14.2" { echo "branch=${BRANCH}" echo "is_release=${IS_RELEASE}" echo "build_os=${BUILD_OS}" echo "build_platform=${BUILD_PLATFORM}" echo "build_include=${BUILD_INCLUDE}" echo "docker_release=${DOCKER_RELEASE}" echo "is_rolling_release=${IS_ROLLING_RELEASE}" echo "sha=${SHA}" echo "s3-artifact-basepath=s3://liquidsoap-artifacts/${GITHUB_WORKFLOW}/${GITHUB_RUN_NUMBER}" echo "is_fork=${IS_FORK}" echo "minimal_exclude_deps=${MINIMAL_EXCLUDE_DEPS}" echo "save_traces=${SAVE_TRACES}" echo "is_snapshot=${IS_SNAPSHOT}" echo "ocaml_version=${OCAML_VERSION}" echo "ocaml_docker_release_version=${OCAML_DOCKER_RELEASE_VERSION}" } >> "${GITHUB_OUTPUT}" liquidsoap-2.4.2/.github/scripts/build-doc.sh000077500000000000000000000003161513273233300211230ustar00rootroot00000000000000#!/bin/sh set -e cd /tmp/liquidsoap-full/liquidsoap eval "$(opam config env)" OCAMLPATH="$(cat ../.ocamlpath)" export OCAMLPATH dune build @doc dune build --profile release ./src/js/interactive_js.bc.js liquidsoap-2.4.2/.github/scripts/build-posix.sh000077500000000000000000000033261513273233300215240ustar00rootroot00000000000000#!/bin/sh set -e CPU_CORES="$1" export CPU_CORES eval "$(opam config env)" echo "::group::Preparing bindings" cd /tmp/liquidsoap-full git remote set-url origin https://github.com/savonet/liquidsoap-full.git git fetch --recurse-submodules=no && git checkout origin/master -- Makefile.git git reset --hard git pull git pull make clean make public make update echo "::endgroup::" echo "::group::Checking out CI commit" cd /tmp/liquidsoap-full/liquidsoap git fetch origin "$GITHUB_SHA" git checkout "$GITHUB_SHA" mv .github /tmp rm -rf ./* mv /tmp/.github . git reset --hard echo "::endgroup::" echo "::group::Setting up specific dependencies" opam update opam pin -y add re 1.13.2 opam upgrade -y posix-socket cd /tmp/liquidsoap-full/liquidsoap ./.github/scripts/checkout-deps.sh # TMP cd /tmp/liquidsoap-full/ocaml-ffmpeg && git checkout v1.2.8 cd /tmp/liquidsoap-full export PKG_CONFIG_PATH=/usr/share/pkgconfig/pkgconfig echo "::endgroup::" echo "::group::Cleaning up cache" rm -rf /var/cache/liquidsoap/* "$HOME"/.cache/liquidsoap/* echo "::endgroup::" echo "::group::Compiling" cd /tmp/liquidsoap-full test -f PACKAGES || cp PACKAGES.default PACKAGES # Workaround touch liquidsoap/configure ./configure --prefix=/usr \ --includedir="\${prefix}/include" \ --mandir="\${prefix}/share/man" \ --infodir="\${prefix}/share/info" \ --sysconfdir=/etc \ --localstatedir=/var \ --with-camomile-data-dir=/usr/share/liquidsoap/camomile \ CFLAGS=-g # Workaround rm liquidsoap/configure OCAMLPATH="$(cat .ocamlpath)" export OCAMLPATH cd /tmp/liquidsoap-full/liquidsoap dune build --profile=release echo "::endgroup::" echo "::group::Print build config" dune exec -- liquidsoap --build-config echo "::endgroup::" liquidsoap-2.4.2/.github/scripts/build-website.sh000077500000000000000000000006761513273233300220310ustar00rootroot00000000000000#!/bin/sh set -e PWD=$(dirname "$0") BASE_DIR=$(cd "${PWD}/../.." && pwd) DOCKER_IMAGE=savonet/liquidsoap-github-actions-website docker build --no-cache --tag "${DOCKER_IMAGE}" --file "${BASE_DIR}/.github/docker/website.dockerfile" . id="$(docker create "${DOCKER_IMAGE}")" docker cp "$id:/tmp/liquidsoap-full/website/html" html/ docker cp "$id:/tmp/liquidsoap-full/website/content/doc-dev/reference.md" html/reference.md docker rm -v "$id" liquidsoap-2.4.2/.github/scripts/build-win32.sh000077500000000000000000000042541513273233300213250ustar00rootroot00000000000000#!/bin/sh set -e export PKG_CONFIG_PATH=/usr/src/mxe/usr/x86_64-w64-mingw32.static/lib/pkgconfig SYSTEM="$1" BRANCH="$2" CPU_CORES="$3" IS_ROLLING_RELEASE="$4" IS_RELEASE="$5" GITHUB_SHA="$6" OPAM_PREFIX="$(opam var prefix)" VERSION="$(opam show -f version ./opam/liquidsoap.opam | cut -d'-' -f 1)" PWD="$(dirname "$0")" BASE_DIR="$(cd "${PWD}/../.." && pwd)" COMMIT_SHORT="$(echo "${GITHUB_SHA}" | cut -c-7)" if [ -n "${IS_ROLLING_RELEASE}" ]; then TAG="${COMMIT_SHORT}-" elif [ -n "${IS_RELEASE}" ]; then TAG="" else TAG="${BRANCH}-" fi if [ "${SYSTEM}" = "x64" ]; then HOST="x86_64-w64-mingw32.static" BUILD="${TAG}${VERSION}-win64" PKG_CONFIG_PATH="/usr/src/mxe/usr/x86_64-w64-mingw32.static/lib/pkgconfig/" else # shellcheck disable=SC2034 HOST="i686-w64-mingw32.static" BUILD="${TAG}${VERSION}-win32" # shellcheck disable=SC2034 PKG_CONFIG_PATH="/usr/src/mxe/usr/i686-w64-mingw32.static/lib/pkgconfig/" fi export OPAMSOLVERTIMEOUT=480 export OPAMJOBS="$CPU_CORES" export CC="" echo "::group::Installing deps" eval "$(opam config env)" opam repository set-url windows https://github.com/ocaml-cross/opam-cross-windows.git opam update opam install -y posix-socket.3.0.0 srt-windows.0.3.4 prometheus-app-windows cohttp-lwt-unix-windows ffmpeg-avutil-windows.1.2.5 echo "::endgroup::" echo "::group::Install liquidsoap-windows" opam install -y liquidsoap-windows echo "::endgroup::" echo "::group::Save build config" wine "${OPAM_PREFIX}/windows-sysroot/bin/liquidsoap" --build-config >> "/tmp/${GITHUB_RUN_NUMBER}/win32/dist/liquidsoap-$BUILD.config" echo "Build config:" cat "/tmp/${GITHUB_RUN_NUMBER}/win32/dist/liquidsoap-$BUILD.config" echo "::endgroup::" echo "::group::Bundling executable" cd ~ cp -R "${BASE_DIR}/.github/win32" "liquidsoap-$BUILD" cp -R "${BASE_DIR}/src/libs" "liquidsoap-$BUILD" cd "liquidsoap-$BUILD" cp "${OPAM_PREFIX}"/windows-sysroot/bin/liquidsoap ./liquidsoap.exe cp -R "$(ocamlfind -toolchain windows ocamlc -where)/../../share/camomile" . cd .. zip -r "liquidsoap-$BUILD.zip" "liquidsoap-$BUILD" mv "liquidsoap-$BUILD.zip" "/tmp/${GITHUB_RUN_NUMBER}/win32/dist" echo "basename=liquidsoap-${BUILD}" >> "${GITHUB_OUTPUT}" echo "::endgroup::" liquidsoap-2.4.2/.github/scripts/checkout-deps.sh000077500000000000000000000007631513273233300220250ustar00rootroot00000000000000#!/bin/sh CWD="$(dirname "$0")" BASEDIR="$(cd "$CWD/../../.." && pwd)" for i in $(git log --reverse --pretty=format:%B origin/main..HEAD | grep '^DEPS=' | cut -d'=' -f 2 | tr ":" "\n"); do MODULE=$(echo "$i" | cut -d'#' -f 1) COMMIT=$(echo "$i" | cut -d'#' -f 2) echo "Checking out dep $MODULE on commit $COMMIT" cd "${BASEDIR}/${MODULE}" && git fetch origin "$COMMIT" && git reset --hard && git checkout "$COMMIT" && git submodule init && git submodule update done liquidsoap-2.4.2/.github/scripts/export-metrics.sh000077500000000000000000000006211513273233300222450ustar00rootroot00000000000000#!/bin/sh BRANCH=$1 METRICS_DIR=$2 TIME="$(date +%s)" METRICS_FILE="${METRICS_DIR}/${TIME}.yaml" git config --global --add safe.directory '*' cd /tmp/liquidsoap-full/liquidsoap || exit 1 mkdir -p "${METRICS_DIR}" touch /tmp/metrics.yaml cat /tmp/metrics.yaml > "${METRICS_FILE}" { echo "- commit: $(git rev-parse HEAD)" echo " branch: ${BRANCH}" echo " time: ${TIME}" } >> "${METRICS_FILE}" liquidsoap-2.4.2/.github/scripts/push-docker.sh000077500000000000000000000060141513273233300215060ustar00rootroot00000000000000#!/bin/sh set -e rm -rf ~/.docker/config.json mkdir -p ~/.docker echo "{ \"experimental\": \"enabled\" }" > ~/.docker/config.json COMMIT_SHORT=$(echo "${GITHUB_SHA}" | cut -c-7)$(echo "${GITHUB_SHA}" | cut -d'-' -f 2 -s | while read -r i; do echo "-$i"; done) echo "TAG: ${TAG}" echo "OCAML_DOCKER_RELEASE_VERSION: ${OCAML_DOCKER_RELEASE_VERSION}" echo "amd64 image: savonet/liquidsoap-ci-build:${TAG}_amd64-${OCAML_DOCKER_RELEASE_VERSION}" echo "arm64 image: savonet/liquidsoap-ci-build:${TAG}_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker login -u "$USER" -p "$PASSWORD" docker manifest create "savonet/liquidsoap:${TAG}" --amend "savonet/liquidsoap-ci-build:${TAG}_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "savonet/liquidsoap-ci-build:${TAG}_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "savonet/liquidsoap:${TAG}" docker manifest create "savonet/liquidsoap:${COMMIT_SHORT}" --amend "savonet/liquidsoap-ci-build:${TAG}_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "savonet/liquidsoap-ci-build:${TAG}_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "savonet/liquidsoap:${COMMIT_SHORT}" docker manifest create "savonet/liquidsoap-alpine:${TAG}" --amend "savonet/liquidsoap-ci-build:${TAG}_alpine_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "savonet/liquidsoap-ci-build:${TAG}_alpine_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "savonet/liquidsoap-alpine:${TAG}" docker manifest create "savonet/liquidsoap-alpine:${COMMIT_SHORT}" --amend "savonet/liquidsoap-ci-build:${TAG}_alpine_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "savonet/liquidsoap-ci-build:${TAG}_alpine_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "savonet/liquidsoap-alpine:${COMMIT_SHORT}" docker login ghcr.io -u "$GHCR_USER" -p "$GHCR_PASSWORD" docker manifest create "ghcr.io/savonet/liquidsoap:${TAG}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "ghcr.io/savonet/liquidsoap:${TAG}" docker manifest create "ghcr.io/savonet/liquidsoap:${COMMIT_SHORT}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "ghcr.io/savonet/liquidsoap:${COMMIT_SHORT}" docker manifest create "ghcr.io/savonet/liquidsoap-alpine:${TAG}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_alpine_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_alpine_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "ghcr.io/savonet/liquidsoap-alpine:${TAG}" docker manifest create "ghcr.io/savonet/liquidsoap-alpine:${COMMIT_SHORT}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_alpine_amd64-${OCAML_DOCKER_RELEASE_VERSION}" --amend "ghcr.io/savonet/liquidsoap-ci-build:${TAG}_alpine_arm64-${OCAML_DOCKER_RELEASE_VERSION}" docker manifest push "ghcr.io/savonet/liquidsoap-alpine:${COMMIT_SHORT}" liquidsoap-2.4.2/.github/scripts/stats-posix.sh000077500000000000000000000013611513273233300215600ustar00rootroot00000000000000#!/bin/sh set -e cd /tmp/liquidsoap-full/liquidsoap eval "$(opam config env)" OCAMLPATH="$(cat ../.ocamlpath)" export OCAMLPATH printf "Memory usage before loading all libraries: " dune exec --display=quiet -- src/bin/liquidsoap.exe --no-stdlib --check 'runtime.gc.full_major() print(runtime.memory.prettify_bytes(runtime.memory().process_private_memory))' printf "Memory usage after loading all libraries: " dune exec --display=quiet -- src/bin/liquidsoap.exe --check 'runtime.gc.full_major() print(runtime.memory().pretty.process_private_memory)' printf "Number of core functions: " dune exec --display=quiet -- src/bin/liquidsoap.exe --no-stdlib --list-functions | wc -l echo printf "Number of functions: " ./liquidsoap --list-functions | wc -l liquidsoap-2.4.2/.github/scripts/test-posix.sh000077500000000000000000000004311513273233300213760ustar00rootroot00000000000000#!/bin/sh set -e TARGET=$1 #export OPAMJOBS="$CPU_CORES" cd /tmp/liquidsoap-full/liquidsoap eval "$(opam config env)" OCAMLPATH="$(cat ../.ocamlpath)" export OCAMLPATH export CLICOLOR_FORCE=1 dune build -j 4 "${TARGET}" --error-reporting=twice --display=quiet --auto-promote liquidsoap-2.4.2/.github/stale.yml000066400000000000000000000016131513273233300170670ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 180 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - enhancement - documentation - usage - bug # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: > This issue was closed for lack of activity. If you believe that it is still relevant, please confirm that it applies to the latest released version of liquidsoap and re-open the ticket. Thanks! liquidsoap-2.4.2/.github/win32/000077500000000000000000000000001513273233300161755ustar00rootroot00000000000000liquidsoap-2.4.2/.github/win32/log/000077500000000000000000000000001513273233300167565ustar00rootroot00000000000000liquidsoap-2.4.2/.github/win32/log/.keep000066400000000000000000000000001513273233300176710ustar00rootroot00000000000000liquidsoap-2.4.2/.github/win32/run/000077500000000000000000000000001513273233300170015ustar00rootroot00000000000000liquidsoap-2.4.2/.github/win32/run/.keep000066400000000000000000000000001513273233300177140ustar00rootroot00000000000000liquidsoap-2.4.2/.github/win32/test.liq000066400000000000000000000011601513273233300176610ustar00rootroot00000000000000log.stdout.set(true) log.file.set(false) # Examples # A playlist source with a smart crossfade #s = smart_crossfade(playlist("mp3")) # A HTTP input s = input.http("http://wwoz-sc.streamguys1.com/wwoz-hi.mp3") # An output to a local file, encoding # in ogg/vorbis+ogg/theora #output.file(%ogg(%theora,%vorbis), # fallible=true, # "z:\tmp\output.ogv", # s) # An icecast output in AAC+ format #output.icecast(%aacplus(bitrate=32), # fallible=true, # mount="test", # s) # An output to the local soundcard output.ao(self_sync=false, fallible=true, s) liquidsoap-2.4.2/.github/workflows/000077500000000000000000000000001513273233300172705ustar00rootroot00000000000000liquidsoap-2.4.2/.github/workflows/build-no-depopts.yml000066400000000000000000000033641513273233300232060ustar00rootroot00000000000000name: Build without optional dependencies on: workflow_call: inputs: sha: required: true type: string minimal_exclude_deps: required: true type: string jobs: build_no_depopts: runs-on: depot-ubuntu-24.04-4 container: image: savonet/liquidsoap-ci:debian_trixie@sha256:dc7cb6d629091da7cc938e7e3d2d88b3830894c85d0700065c73cceeca5cf5ab options: --user opam env: HOME: /home/opam steps: - name: Get number of CPU cores uses: savonet/github-actions-cpu-cores-docker@f72bcfaa219a2f60deaf8b26d0707b1d9c67d274 # v1 id: cpu_cores - name: Checkout code run: | cd /tmp/liquidsoap-full/liquidsoap git remote set-url origin https://github.com/savonet/liquidsoap.git git fetch origin ${{ inputs.sha }} git checkout ${{ inputs.sha }} - name: Build run: | echo "::group::Preparing build" cd /tmp/liquidsoap-full git remote set-url origin https://github.com/savonet/liquidsoap-full.git git fetch --recurse-submodules=no git checkout origin/master -- Makefile.git make public git reset --hard git submodule foreach 'git reset --hard' git pull cp PACKAGES.minimal PACKAGES opam update opam pin -yn . opam info -f "depopts:" liquidsoap | grep -v osx-secure-transport | xargs opam remove -y inotify ffmpeg-avutil cohttp-lwt-unix prometheus-app ${{ inputs.minimal_exclude_deps }} echo "::endgroup::" opam install -y mem_usage cd liquidsoap ./.github/scripts/build-posix.sh "${{ steps.cpu_cores.outputs.count }}" env: LIQ_BUILD_MIN: true liquidsoap-2.4.2/.github/workflows/build-opam.yml000066400000000000000000000031031513273233300220410ustar00rootroot00000000000000name: Build opam on: workflow_call: jobs: build_opam: runs-on: depot-ubuntu-24.04-4 strategy: fail-fast: false matrix: ocaml-compiler: - 4.14.x - 5.x steps: - name: Checkout latest code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Update packages run: | sudo apt-get update - name: Setup OCaml uses: ocaml/setup-ocaml@dec6499fef64fc5d7ed43d43a87251b7b1c306f5 # v3.4.8 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} opam-pin: false opam-depext: false - name: Add local packages run: | ./.github/scripts/add-local-opam-packages.sh - name: Install liquidsoap run: | opam install --cli=2.1 --confirm-level=unsafe-yes . - name: Install ocamlformat if: matrix.ocaml-compiler == '4.14.x' run: | opam install ocamlformat=0.28.1 - name: Set PY env variable. if: matrix.ocaml-compiler == '4.14.x' run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - name: Restore pre-commit cache if: matrix.ocaml-compiler == '4.14.x' uses: actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2 with: path: ~/.cache/pre-commit key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - name: Run pre-commit if: matrix.ocaml-compiler == '4.14.x' uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 liquidsoap-2.4.2/.github/workflows/build-posix.yml000066400000000000000000000115661513273233300222630ustar00rootroot00000000000000name: Build POSIX on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string is_fork: required: true type: string is_rolling_release: required: true type: string is_release: required: true type: string is_snapshot: required: true type: string build_os: required: true type: string build_platform: required: true type: string build_include: required: true type: string ocaml_version: required: true type: string minimal_exclude_deps: required: true type: string jobs: build_posix: runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: os: ${{ fromJson(inputs.build_os) }} ocaml_version: ${{ fromJson(inputs.ocaml_version) }} platform: ${{ fromJson(inputs.build_platform) }} include: ${{ fromJson(inputs.build_include) }} exclude: - os: alpine ocaml_version: 5.2.0-ox container: image: savonet/liquidsoap-ci:${{ matrix.os }}-${{ matrix.ocaml_version }} options: --user root --privileged -v ${{ github.workspace }}/${{ github.run_number }}:/tmp/${{ github.run_number }} env: HOME: /home/opam IS_SNAPSHOT: ${{ inputs.is_snapshot == 'true' }} steps: - name: Get number of CPU cores uses: savonet/github-actions-cpu-cores-docker@f72bcfaa219a2f60deaf8b26d0707b1d9c67d274 # v1 id: cpu_cores - name: Checkout code run: | cd /tmp/liquidsoap-full/liquidsoap rm -rf doc/content/build.md doc/content/install.md sudo -u opam -E git remote set-url origin https://github.com/savonet/liquidsoap.git sudo -u opam -E git fetch origin ${{ inputs.sha }} sudo -u opam -E git checkout ${{ inputs.sha }} - name: Build run: | cd /tmp/liquidsoap-full/liquidsoap export CPU_CORES=${{ steps.cpu_cores.outputs.count }} sudo -u opam -E ./.github/scripts/add-local-opam-packages.sh sudo -u opam -E ./.github/scripts/build-posix.sh "${{ steps.cpu_cores.outputs.count }}" - name: Build debian package if: contains(matrix.os, 'debian') || contains(matrix.os, 'ubuntu') id: build_deb env: GITHUB_SHA: ${{ inputs.sha }} BRANCH: ${{ inputs.branch }} DOCKER_TAG: ${{ matrix.os }}-ocaml${{ matrix.ocaml_version }} LIQ_TMP_DIR: /tmp/${{ github.run_number }}/${{ matrix.os }}_${{ matrix.platform }}/debian PLATFORM: ${{ matrix.platform }} IS_ROLLING_RELEASE: ${{ inputs.is_rolling_release }} IS_RELEASE: ${{ inputs.is_release }} MINIMAL_EXCLUDE_DEPS: ${{ inputs.minimal_exclude_deps }} DEB_RELEASE: 1 run: | mkdir -p "${LIQ_TMP_DIR}" chown -R opam "${LIQ_TMP_DIR}" chown -R opam "${GITHUB_OUTPUT}" cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E ./.github/scripts/build-deb.sh - name: Upload debian packages artifacts if: (contains(matrix.os, 'debian') || contains(matrix.os, 'ubuntu')) uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ steps.build_deb.outputs.basename }} path: ${{ github.workspace }}/${{ github.run_number }}/${{ matrix.os }}_${{ matrix.platform }}/debian if-no-files-found: error - name: Build alpine package if: matrix.os == 'alpine' id: build_apk env: BRANCH: ${{ inputs.branch }} ALPINE_TAG: ocaml${{ matrix.ocaml_version }} ARCH: ${{ matrix.platform }} LIQ_TMP_DIR: /tmp/${{ github.run_number }}/${{ matrix.os }}_${{ matrix.platform }}/alpine ALPINE_ARCH: ${{ matrix.alpine-arch }} IS_ROLLING_RELEASE: ${{ inputs.is_rolling_release }} IS_RELEASE: ${{ inputs.is_release }} MINIMAL_EXCLUDE_DEPS: ${{ inputs.minimal_exclude_deps }} APK_RELEASE: 0 run: | cd /tmp/liquidsoap-full/liquidsoap apk add alpine-sdk adduser opam abuild mkdir -p "${LIQ_TMP_DIR}" chown -R opam "${LIQ_TMP_DIR}" chown -R opam "${GITHUB_OUTPUT}" sudo -u opam -E ./.github/scripts/build-apk.sh - name: Upload alpine packages artifacts if: matrix.os == 'alpine' uses: savonet/artifact-upload-docker-action@main with: name: alpine-${{ matrix.ocaml_version }}-${{ matrix.platform }}-packages path: /github/workspace/${{ github.run_number }}/${{ matrix.os }}_${{ matrix.platform }}/alpine - name: Cleanup if: ${{ always() }} run: | rm -rf /tmp/${{ github.run_number }}/${{ matrix.os }}_${{ matrix.platform }} liquidsoap-2.4.2/.github/workflows/build-win32.yml000066400000000000000000000053741513273233300220630ustar00rootroot00000000000000name: Build Windows on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string is_rolling_release: required: true type: string is_release: required: true type: string is_snapshot: required: true type: string jobs: build_win32: runs-on: depot-ubuntu-24.04-4 strategy: fail-fast: false matrix: system: [x64] container: image: savonet/liquidsoap-win32-${{ matrix.system }} options: --user root -v ${{ github.workspace }}/${{ github.run_number }}:/tmp/${{ github.run_number }} env: OPAM_DEPS: ao-windows,lastfm-windows,camomile-windows,cry-windows,dtools-windows,duppy-windows,ffmpeg-windows,ffmpeg-avutil-windows,mm-windows,re-windows,portaudio-windows,samplerate-windows,sedlex-windows,ssl-windows,srt-windows,winsvc-windows,mem_usage-windows,prometheus-app-windows,cohttp-lwt-unix-windows IS_SNAPSHOT: ${{ inputs.is_snapshot == 'true' }} TOOLPREF64: /usr/src/mxe/usr/bin/x86_64-w64-mingw32.static- steps: - name: Get number of CPU cores uses: savonet/github-actions-cpu-cores-docker@f72bcfaa219a2f60deaf8b26d0707b1d9c67d274 # v1 id: cpu_cores - name: Checkout code run: | mkdir -p /tmp/${{ github.run_number }}/win32/liquidsoap cd /tmp/${{ github.run_number }}/win32/liquidsoap git init git remote add origin https://github.com/${{ github.repository }}.git git fetch origin ${{ inputs.sha }} git checkout ${{ inputs.sha }} chown -R opam /tmp/${{ github.run_number }}/win32 - name: Add local packages run: | cd /tmp/${{ github.run_number }}/win32/liquidsoap/ gosu opam:root ./.github/scripts/add-local-opam-packages.sh - name: Build windows binary run: | mkdir -p /tmp/${{ github.run_number }}/win32/dist chown -R opam /tmp/${{ github.run_number }}/win32/dist chown -R opam "${GITHUB_OUTPUT}" cd /tmp/${{ github.run_number }}/win32/liquidsoap gosu opam:root ./.github/scripts/build-win32.sh ${{ matrix.system }} ${{ inputs.branch }} "${{ steps.cpu_cores.outputs.count }}" "${{ inputs.is_rolling_release }}" "${{ inputs.is_release }}" ${{ inputs.sha }} id: build - name: Upload artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ steps.build.outputs.basename }} path: ${{ github.workspace }}/${{ github.run_number }}/win32/dist if-no-files-found: error - name: Cleanup if: ${{ always() }} run: | rm -rf /tmp/${{ github.run_number }}/win32 liquidsoap-2.4.2/.github/workflows/ci.yml000066400000000000000000000131731513273233300204130ustar00rootroot00000000000000name: CI on: merge_group: pull_request: push: branches: - main tags: - rolling-release-* - v[0-9]+.[0-9]+.[0-9]+ - v[0-9]+.[0-9]+.[0-9]+-* paths: - ".github/**" - "**/*.ml*" - "**/*.liq" - "**/src/js/*" - "**/dune" - "**/dune.inc" - "doc/**" - "dune-project" - "scripts/**" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build_details: runs-on: depot-ubuntu-24.04-4 outputs: branch: ${{ steps.build_details.outputs.branch }} sha: ${{ steps.build_details.outputs.sha }} is_release: ${{ steps.build_details.outputs.is_release }} is_rolling_release: ${{ steps.build_details.outputs.is_rolling_release }} is_fork: ${{ steps.build_details.outputs.is_fork }} publish_docker_image: ${{ steps.build_details.outputs.is_fork != 'true' && github.event_name != 'merge_group' }} build_os: ${{ steps.build_details.outputs.build_os }} build_platform: ${{ steps.build_details.outputs.build_platform }} build_include: ${{ steps.build_details.outputs.build_include }} docker_release: ${{ steps.build_details.outputs.docker_release }} minimal_exclude_deps: ${{ steps.build_details.outputs.minimal_exclude_deps }} save_traces: ${{ steps.build_details.outputs.save_traces }} is_snapshot: ${{ steps.build_details.outputs.is_snapshot }} ocaml_version: ${{ steps.build_details.outputs.ocaml_version }} ocaml_docker_release_version: ${{ steps.build_details.outputs.ocaml_docker_release_version }} steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Get build details env: IS_FORK: ${{ github.event.pull_request.head.repo.fork == true }} run: .github/scripts/build-details.sh id: build_details build_no_depopts: uses: ./.github/workflows/build-no-depopts.yml needs: build_details with: sha: ${{ github.sha }} minimal_exclude_deps: ${{ needs.build_details.outputs.minimal_exclude_deps }} build_js: uses: ./.github/workflows/js.yml needs: build_details with: sha: ${{ github.sha }} branch: ${{ needs.build_details.outputs.branch }} parsers: uses: ./.github/workflows/parsers.yml build_opam: uses: ./.github/workflows/build-opam.yml run_tests: uses: ./.github/workflows/tests.yml needs: build_details with: sha: ${{ github.sha }} branch: ${{ needs.build_details.outputs.branch }} is_fork: ${{ needs.build_details.outputs.is_fork }} save_traces: ${{ needs.build_details.outputs.save_traces }} build_posix: uses: ./.github/workflows/build-posix.yml needs: build_details with: sha: ${{ github.sha }} branch: ${{ needs.build_details.outputs.branch }} is_fork: ${{ needs.build_details.outputs.is_fork }} is_rolling_release: ${{ needs.build_details.outputs.is_rolling_release }} is_release: ${{ needs.build_details.outputs.is_release }} is_snapshot: ${{ needs.build_details.outputs.is_snapshot }} build_os: ${{ needs.build_details.outputs.build_os }} build_platform: ${{ needs.build_details.outputs.build_platform }} build_include: ${{ needs.build_details.outputs.build_include }} ocaml_version: ${{ needs.build_details.outputs.ocaml_version }} minimal_exclude_deps: ${{ needs.build_details.outputs.minimal_exclude_deps }} build_win32: uses: ./.github/workflows/build-win32.yml needs: build_details with: sha: ${{ github.sha }} branch: ${{ needs.build_details.outputs.branch }} is_rolling_release: ${{ needs.build_details.outputs.is_rolling_release }} is_release: ${{ needs.build_details.outputs.is_release }} is_snapshot: ${{ needs.build_details.outputs.is_snapshot }} update_doc: uses: ./.github/workflows/doc.yml needs: [build_details, build_js] if: github.event_name != 'pull_request' && github.repository_owner == 'savonet' && needs.build_details.outputs.branch == 'main' with: sha: ${{ github.sha }} branch: ${{ needs.build_details.outputs.branch }} secrets: WEBSITE_TOKEN: ${{ secrets.WEBSITE_TOKEN }} update_release: uses: ./.github/workflows/release.yml needs: [ build_details, build_no_depopts, build_js, run_tests, build_posix, build_win32, ] if: needs.build_details.outputs.is_release == 'true' with: sha: ${{ needs.build_details.outputs.sha }} branch: ${{ needs.build_details.outputs.branch }} is_rolling_release: ${{ needs.build_details.outputs.is_rolling_release }} secrets: LIQUIDSOAP_RELEASE_ASSETS_TOKEN: ${{ secrets.LIQUIDSOAP_RELEASE_ASSETS_TOKEN }} build_docker: uses: ./.github/workflows/docker.yml needs: [build_details, build_posix, run_tests] with: sha: ${{ needs.build_details.outputs.sha }} branch: ${{ needs.build_details.outputs.branch }} is_fork: ${{ needs.build_details.outputs.is_fork }} publish_docker_image: ${{ needs.build_details.outputs.publish_docker_image }} docker_release: ${{ needs.build_details.outputs.docker_release }} build_platform: ${{ needs.build_details.outputs.build_platform }} build_include: ${{ needs.build_details.outputs.build_include }} ocaml_version: ${{ needs.build_details.outputs.ocaml_version }} ocaml_docker_release_version: ${{ needs.build_details.outputs.ocaml_docker_release_version }} secrets: DOCKERHUB_USER: ${{ secrets.DOCKERHUB_USER }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} liquidsoap-2.4.2/.github/workflows/doc.yml000066400000000000000000000057601513273233300205700ustar00rootroot00000000000000name: Documentation on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string secrets: WEBSITE_TOKEN: required: true jobs: update_doc: runs-on: depot-ubuntu-24.04-4 container: image: savonet/liquidsoap-ci:debian_trixie@sha256:dc7cb6d629091da7cc938e7e3d2d88b3830894c85d0700065c73cceeca5cf5ab options: --user root -v ${{ github.workspace }}/${{ github.run_number }}:/tmp/${{ github.run_number }} env: HOME: /home/opam steps: - name: Get number of CPU cores uses: savonet/github-actions-cpu-cores-docker@f72bcfaa219a2f60deaf8b26d0707b1d9c67d274 # v1 id: cpu_cores - name: Download JS artifacts uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: liquidsoap-js-${{ inputs.branch }} path: /tmp/${{ github.run_number }}/js-artifacts - name: Checkout code run: | cd /tmp/liquidsoap-full/liquidsoap rm -rf doc/content/build.md doc/content/install.md _build/default/src/js sudo -u opam -E git remote set-url origin https://github.com/savonet/liquidsoap.git sudo -u opam -E git fetch origin ${{ inputs.sha }} sudo -u opam -E git checkout ${{ inputs.sha }} - name: Build doc env: CPU_CORES: ${{ steps.cpu_cores.outputs.count }} run: | cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E ./.github/scripts/add-local-opam-packages.sh sudo -u opam -E ./.github/scripts/build-posix.sh "${{ steps.cpu_cores.outputs.count }}" sudo -u opam -E ./.github/scripts/build-doc.sh mkdir -p /tmp/${{ github.run_number }}/odoc cp -rf _build/default/_doc/_html/* /tmp/${{ github.run_number }}/odoc # Copy JS artifacts after build to avoid dune overwriting them mkdir -p _build/default/src/js cp -f /tmp/${{ github.run_number }}/js-artifacts/* _build/default/src/js/ cd /tmp/liquidsoap-full/website make dist mkdir -p /tmp/${{ github.run_number }}/html cp -rf html/* /tmp/${{ github.run_number }}/html - name: Push doc content if: success() && github.repository_owner == 'savonet' uses: crazy-max/ghaction-github-pages@df5cc2bfa78282ded844b354faee141f06b41865 # v4.2.0 with: repo: savonet/savonet.github.io target_branch: master build_dir: ${{ github.run_number }}/html fqdn: www.liquidsoap.info env: GH_PAT: ${{ secrets.WEBSITE_TOKEN }} - name: Push odoc content if: success() && github.repository_owner == 'savonet' uses: crazy-max/ghaction-github-pages@df5cc2bfa78282ded844b354faee141f06b41865 # v4.2.0 with: repo: savonet/liquidsoap target_branch: gh_pages build_dir: ${{ github.run_number }}/odoc env: GH_PAT: ${{ secrets.WEBSITE_TOKEN }} liquidsoap-2.4.2/.github/workflows/docker.yml000066400000000000000000000310261513273233300212640ustar00rootroot00000000000000name: Docker on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string is_fork: required: true type: string publish_docker_image: required: true type: string docker_release: required: true type: string build_platform: required: true type: string build_include: required: true type: string ocaml_version: required: true type: string ocaml_docker_release_version: required: true type: string secrets: DOCKERHUB_USER: required: true DOCKERHUB_PASSWORD: required: true jobs: build_docker: runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: platform: ${{ fromJson(inputs.build_platform) }} ocaml_version: ${{ fromJson(inputs.ocaml_version) }} include: ${{ fromJson(inputs.build_include) }} steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Download all artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: path: artifacts/${{ inputs.sha }} - name: Get debian package run: | echo "deb-file=$(find artifacts/${{ inputs.sha }} -type f | grep ${{ matrix.docker-debian-os }} | grep ${{ matrix.ocaml_version }} | grep -v minimal | grep '${{ matrix.platform }}\.deb$' | grep -v dbgsym | grep deb)" >> "${GITHUB_OUTPUT}" id: debian_package - name: Get debian debug package run: | echo "deb-file=$(find artifacts/${{ inputs.sha }} -type f | grep ${{ matrix.docker-debian-os }} | grep ${{ matrix.ocaml_version }} | grep -v minimal | grep '${{ matrix.platform }}\.deb$' | grep dbgsym | grep deb)" >> "${GITHUB_OUTPUT}" id: debian_debug_package - name: Login to Docker Hub if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Login to GitHub Container Registry if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push docker image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 env: DOCKER_BUILD_RECORD_UPLOAD: false with: build-args: | "DEB_FILE=${{ steps.debian_package.outputs.deb-file }}" "DEB_DEBUG_FILE=${{ steps.debian_debug_package.outputs.deb-file }}" context: . file: .github/docker/debian.dockerfile tags: | "savonet/liquidsoap-ci-build:${{ inputs.branch }}_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" "ghcr.io/savonet/liquidsoap-ci-build:${{ inputs.branch }}_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" push: ${{ inputs.publish_docker_image }} provenance: false sbom: false build_docker_alpine: runs-on: ${{ matrix.runs-on }} if: inputs.is_fork != 'true' strategy: fail-fast: false matrix: platform: ${{ fromJson(inputs.build_platform) }} ocaml_version: ${{ fromJson(inputs.ocaml_version) }} include: ${{ fromJson(inputs.build_include) }} exclude: - ocaml_version: 5.2.0-ox steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download all artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: path: artifacts/${{ inputs.sha }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Get alpine package run: | echo "apk-file=$(find artifacts/${{ inputs.sha }} -type f | grep ${{ matrix.ocaml_version }} | grep -v minimal | grep 'apk$' | grep -v dbg | grep ${{ matrix.alpine-arch }})" >> "${GITHUB_OUTPUT}" id: alpine_package - name: Login to Docker Hub if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Login to GitHub Container Registry if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push docker image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 env: DOCKER_BUILD_RECORD_UPLOAD: false with: build-args: | "APK_FILE=${{ steps.alpine_package.outputs.apk-file }}" context: . file: .github/docker/alpine.dockerfile tags: | "savonet/liquidsoap-ci-build:${{ inputs.branch }}_alpine_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" "ghcr.io/savonet/liquidsoap-ci-build:${{ inputs.branch }}_alpine_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" push: ${{ inputs.publish_docker_image }} provenance: false sbom: false build_docker_minimal: runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: platform: ${{ fromJson(inputs.build_platform) }} ocaml_version: ${{ fromJson(inputs.ocaml_version) }} include: ${{ fromJson(inputs.build_include) }} steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Download all artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: path: artifacts/${{ inputs.sha }} - name: Get debian package run: | echo "deb-file=$(find artifacts/${{ inputs.sha }} -type f | grep ${{ matrix.docker-debian-os }} | grep ${{ matrix.ocaml_version }} | grep minimal | grep '${{ matrix.platform }}\.deb$' | grep -v dbgsym | grep deb)" >> "${GITHUB_OUTPUT}" id: debian_package - name: Get debian debug package run: | echo "deb-file=$(find artifacts/${{ inputs.sha }} -type f | grep ${{ matrix.docker-debian-os }} | grep ${{ matrix.ocaml_version }} | grep minimal | grep '${{ matrix.platform }}\.deb$' | grep dbgsym | grep deb)" >> "${GITHUB_OUTPUT}" id: debian_debug_package - name: Login to Docker Hub if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Login to GitHub Container Registry if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push docker image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 env: DOCKER_BUILD_RECORD_UPLOAD: false with: build-args: | "DEB_FILE=${{ steps.debian_package.outputs.deb-file }}" "DEB_DEBUG_FILE=${{ steps.debian_debug_package.outputs.deb-file }}" context: . file: .github/docker/debian.dockerfile tags: | "savonet/liquidsoap-ci-build:${{ inputs.branch }}-minimal_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" "ghcr.io/savonet/liquidsoap-ci-build:${{ inputs.branch }}-minimal_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" push: ${{ inputs.publish_docker_image }} provenance: false sbom: false build_docker_alpine_minimal: runs-on: ${{ matrix.runs-on }} if: inputs.is_fork != 'true' strategy: fail-fast: false matrix: platform: ${{ fromJson(inputs.build_platform) }} ocaml_version: ${{ fromJson(inputs.ocaml_version) }} include: ${{ fromJson(inputs.build_include) }} exclude: - ocaml_version: 5.2.0-ox steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download all artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: path: artifacts/${{ inputs.sha }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Get alpine package run: | echo "apk-file=$(find artifacts/${{ inputs.sha }} -type f | grep minimal | grep ${{ matrix.ocaml_version }} | grep 'apk$' | grep -v dbg | grep ${{ matrix.alpine-arch }})" >> "${GITHUB_OUTPUT}" id: alpine_package - name: Login to Docker Hub if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASSWORD }} - name: Login to GitHub Container Registry if: inputs.publish_docker_image == 'true' uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push docker image uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 env: DOCKER_BUILD_RECORD_UPLOAD: false with: build-args: | "APK_FILE=${{ steps.alpine_package.outputs.apk-file }}" context: . file: .github/docker/alpine.dockerfile tags: | "savonet/liquidsoap-ci-build:${{ inputs.branch }}-minimal_alpine_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" "ghcr.io/savonet/liquidsoap-ci-build:${{ inputs.branch }}-minimal_alpine_${{ matrix.platform }}-ocaml${{ matrix.ocaml_version }}" push: ${{ inputs.publish_docker_image }} provenance: false sbom: false build_docker_release: runs-on: depot-ubuntu-24.04-4 needs: [build_docker, build_docker_alpine] if: inputs.docker_release == 'true' steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Push consolidated manifest env: TAG: ${{ inputs.branch }} USER: ${{ secrets.DOCKERHUB_USER }} PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} GHCR_USER: ${{ github.actor }} GHCR_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUB_SHA: ${{ inputs.sha }} OCAML_DOCKER_RELEASE_VERSION: ocaml${{ inputs.ocaml_docker_release_version }} run: .github/scripts/push-docker.sh build_docker_release_minimal: runs-on: depot-ubuntu-24.04-4 needs: [build_docker_minimal, build_docker_alpine_minimal] if: inputs.docker_release == 'true' steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Push consolidated manifest env: TAG: ${{ inputs.branch }}-minimal USER: ${{ secrets.DOCKERHUB_USER }} PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }} GHCR_USER: ${{ github.actor }} GHCR_PASSWORD: ${{ secrets.GITHUB_TOKEN }} GITHUB_SHA: ${{ inputs.sha }} OCAML_DOCKER_RELEASE_VERSION: ocaml${{ inputs.ocaml_docker_release_version }} run: .github/scripts/push-docker.sh ${{ inputs.branch }}-minimal ${{ secrets.DOCKERHUB_USER }} ${{ secrets.DOCKERHUB_PASSWORD }} ${{ github.actor }} ${{ secrets.GITHUB_TOKEN }} ${{ inputs.sha }}-minimal liquidsoap-2.4.2/.github/workflows/js.yml000066400000000000000000000032061513273233300204300ustar00rootroot00000000000000name: JS Build on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string jobs: build_js: runs-on: depot-ubuntu-24.04-4 container: image: savonet/liquidsoap-ci:debian_trixie@sha256:dc7cb6d629091da7cc938e7e3d2d88b3830894c85d0700065c73cceeca5cf5ab options: --user root env: HOME: /home/opam steps: - name: Checkout code run: | cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E git remote set-url origin https://github.com/savonet/liquidsoap.git sudo -u opam -E git fetch origin ${{ inputs.sha }} sudo -u opam -E git checkout ${{ inputs.sha }} mv .git /tmp rm -rf ./* mv /tmp/.git . sudo -u opam -E git reset --hard - name: Install npm run: | apt-get update apt-get install -y npm - name: Build JS run: | cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E opam update sudo -u opam -E opam upgrade -y sudo -u opam -E opam exec -- dune build --profile release src/js --verbose - name: Upload JS artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: liquidsoap-js-${{ inputs.branch }} path: | /tmp/liquidsoap-full/liquidsoap/_build/default/src/js/interactive_js.bc.js /tmp/liquidsoap-full/liquidsoap/_build/default/src/js/index.html /tmp/liquidsoap-full/liquidsoap/_build/default/src/js/playground.bundle.js if-no-files-found: error liquidsoap-2.4.2/.github/workflows/parsers.yml000066400000000000000000000025011513273233300214700ustar00rootroot00000000000000name: Parser validation on: workflow_call: jobs: tree_sitter_parse: runs-on: depot-ubuntu-24.04-4 steps: - name: Checkout latest code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup node uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: 22 - name: Parse using tree-sitter run: | git clone https://github.com/savonet/tree-sitter-liquidsoap.git cd tree-sitter-liquidsoap npm install npm exec tree-sitter -- parse -q -s ../../**/*.liq lezer_parse: runs-on: depot-ubuntu-24.04-4 steps: - name: Checkout latest code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Setup node uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 with: node-version: latest - name: Parse using liquidsoap-lezer-print-tree run: | # This one has unicode variable name that isn't supported yet. rm -rf src/libs/list.liq git clone https://github.com/savonet/codemirror-lang-liquidsoap.git cd codemirror-lang-liquidsoap npm install npm exec liquidsoap-lezer-print-tree -- -q ../../**/*.liq liquidsoap-2.4.2/.github/workflows/release.yml000066400000000000000000000141321513273233300214340ustar00rootroot00000000000000name: Release on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string is_rolling_release: required: true type: string secrets: LIQUIDSOAP_RELEASE_ASSETS_TOKEN: required: true jobs: update_release: runs-on: depot-ubuntu-24.04-4 steps: - name: Checkout code uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - name: Download all artifact uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: path: artifacts/${{ inputs.sha }} - name: List assets to upload id: release_assets run: | echo "release_assets<> $GITHUB_OUTPUT find artifacts/${{ inputs.sha }} -type f | egrep '\.apk$|\.deb$|\.config|\.zip$' | sort -u >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Generate release notes id: release_notes run: | echo "release_notes<> $GITHUB_OUTPUT if [ ${{ inputs.is_rolling_release }} = "true" ]; then cat doc/content/rolling-release.md >> $GITHUB_OUTPUT fi echo "" >> $GITHUB_OUTPUT echo "" >> $GITHUB_OUTPUT cat CHANGES.md | sed -e "/---/,\$d" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Generate release assets notes id: release_assets_notes run: | echo "release_notes<> $GITHUB_OUTPUT cat doc/content/release-assets.md >> $GITHUB_OUTPUT echo "" >> $GITHUB_OUTPUT echo "" >> $GITHUB_OUTPUT cat CHANGES.md | sed -e "/---/,\$d" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Delete old release assets uses: mknejp/delete-release-assets@v1 with: token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ inputs.branch }} assets: "*" fail-if-no-release: false fail-if-no-assets: false - name: Upload assets to main repo release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: token: ${{ secrets.GITHUB_TOKEN }} tag_name: ${{ inputs.branch }} files: ${{ steps.release_assets.outputs.release_assets }} prerelease: ${{ inputs.is_rolling_release }} body: ${{ steps.release_notes.outputs.release_notes }} draft: ${{ inputs.is_rolling_release != 'true' }} - name: Manage release assets repo limit id: manage_release_assets uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ secrets.LIQUIDSOAP_RELEASE_ASSETS_TOKEN }} script: | const owner = 'savonet'; const repo = 'liquidsoap-release-assets'; const tag = '${{ inputs.branch }}'; const maxAssets = 1000; const newFiles = `${{ steps.release_assets.outputs.release_assets }}`.trim().split('\n').filter(f => f.length > 0); const newFilesCount = newFiles.length; core.info(`New files to upload: ${newFilesCount}`); let release; try { const { data } = await github.rest.repos.getReleaseByTag({ owner, repo, tag }); release = data; } catch (error) { if (error.status === 404) { core.info(`No existing release found for tag ${tag}, nothing to clean up`); core.setOutput('assets_to_delete', ''); return; } throw error; } const assets = []; let page = 1; while (true) { const { data } = await github.rest.repos.listReleaseAssets({ owner, repo, release_id: release.id, per_page: 100, page }); if (data.length === 0) break; assets.push(...data); page++; } core.info(`Found ${assets.length} existing assets`); const maxOldAssets = maxAssets - newFilesCount; core.info(`Maximum old assets to keep: ${maxOldAssets}`); if (assets.length <= maxOldAssets) { core.info(`Current asset count (${assets.length}) is within limit, no deletion needed`); core.setOutput('assets_to_delete', ''); return; } assets.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); const assetsToDeleteCount = assets.length - maxOldAssets; core.info(`Assets to delete: ${assetsToDeleteCount}`); const assetsToDelete = assets.slice(0, assetsToDeleteCount); const assetNamesToDelete = assetsToDelete.map(a => a.name); core.info(`Deleting assets: ${assetNamesToDelete.join(', ')}`); core.setOutput('assets_to_delete', assetNamesToDelete.join('\n')); - name: Delete old release assets from release repo if: steps.manage_release_assets.outputs.assets_to_delete != '' uses: mknejp/delete-release-assets@v1 with: token: ${{ secrets.LIQUIDSOAP_RELEASE_ASSETS_TOKEN }} tag: ${{ inputs.branch }} assets: ${{ steps.manage_release_assets.outputs.assets_to_delete }} repository: savonet/liquidsoap-release-assets fail-if-no-release: false fail-if-no-assets: false - name: Upload assets to release repo uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: token: ${{ secrets.LIQUIDSOAP_RELEASE_ASSETS_TOKEN }} tag_name: ${{ inputs.branch }} files: ${{ steps.release_assets.outputs.release_assets }} repository: savonet/liquidsoap-release-assets prerelease: ${{ inputs.is_rolling_release }} body: ${{ steps.release_assets_notes.outputs.release_notes }} draft: ${{ inputs.is_rolling_release != 'true' }} liquidsoap-2.4.2/.github/workflows/tests.yml000066400000000000000000000131651513273233300211630ustar00rootroot00000000000000name: Tests on: workflow_call: inputs: sha: required: true type: string branch: required: true type: string is_fork: required: true type: string save_traces: required: true type: string jobs: run_tests: runs-on: depot-ubuntu-24.04-4 container: image: savonet/liquidsoap-ci:debian_trixie@sha256:dc7cb6d629091da7cc938e7e3d2d88b3830894c85d0700065c73cceeca5cf5ab options: --user root --privileged --ulimit core=-1 --security-opt seccomp=unconfined -v ${{ github.workspace }}/${{ github.run_number }}:/tmp/${{ github.run_number }} strategy: fail-fast: false matrix: target: ["@citest", "@doctest", "@mediatest"] env: HOME: /home/opam steps: - name: Get number of CPU cores uses: savonet/github-actions-cpu-cores-docker@f72bcfaa219a2f60deaf8b26d0707b1d9c67d274 # v1 id: cpu_cores - name: Enable core dump run: | ulimit -c unlimited mkdir -p /tmp/${{ github.run_number }}/core chown -R opam /tmp/${{ github.run_number }}/core echo /tmp/${{ github.run_number }}/core/core.%h.%e.%t > /proc/sys/kernel/core_pattern - name: Checkout code run: | cd /tmp/liquidsoap-full/liquidsoap rm -rf doc/content/build.md doc/content/install.md sudo -u opam -E git remote set-url origin https://github.com/savonet/liquidsoap.git sudo -u opam -E git fetch origin ${{ inputs.sha }} sudo -u opam -E git checkout ${{ inputs.sha }} - name: Build env: CPU_CORES: ${{ steps.cpu_cores.outputs.count }} run: | cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E ./.github/scripts/add-local-opam-packages.sh sudo -u opam -E ./.github/scripts/build-posix.sh "${{ steps.cpu_cores.outputs.count }}" cp /tmp/liquidsoap-full/liquidsoap/_build/default/src/bin/liquidsoap.exe /tmp/${{ github.run_number }}/core/liquidsoap - name: Compute stats run: | cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E ./.github/scripts/stats-posix.sh - name: Install additional packages for @doc if: matrix.target == '@doctest' run: | apt-get -y update apt-get -y install frei0r-plugins sudo -u opam -E opam install odoc - name: Install additional packages for @citest if: matrix.target == '@citest' run: | apt-get -y update apt-get -y install gnuplot - name: Run tests env: CPU_CORES: ${{ steps.cpu_cores.outputs.count }} run: | cd /tmp/liquidsoap-full/liquidsoap sudo -u opam -E ./.github/scripts/test-posix.sh ${{ matrix.target }} - name: Finalize metrics run: | cd /tmp/liquidsoap-full/liquidsoap mkdir -p "/tmp/${{ github.run_number }}/metrics" chown -R opam "/tmp/${{ github.run_number }}/metrics" sudo -u opam -E ./.github/scripts/export-metrics.sh "${{ inputs.branch }}" "/tmp/${{ github.run_number }}/metrics" - name: Upload metrics artifact uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: inputs.is_fork != 'true' && matrix.target == '@citest' with: name: metrics path: ${{ github.workspace }}/${{ github.run_number }}/metrics - name: Upload metrics if: inputs.is_fork != 'true' && matrix.target == '@citest' uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: metrics publish_dir: ${{ github.workspace }}/${{ github.run_number }}/metrics keep_files: true - name: Save traces if: (success() || failure()) && inputs.save_traces == 'true' run: | mkdir -p /tmp/${{ github.run_number }}/traces/${{ matrix.target }} cd /tmp/liquidsoap-full/liquidsoap find _build/default/tests | grep '\.trace$' | while read i; do mv "$i" /tmp/${{ github.run_number }}/traces/${{ matrix.target }}; done - name: Upload traces if: (success() || failure()) && inputs.save_traces == 'true' uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: traces-${{ matrix.target }} path: ${{ github.workspace }}/${{ github.run_number }}/traces/${{ matrix.target }} - name: Prepare test artifacts if: (success() || failure()) && matrix.target == '@citest' run: | mkdir -p /tmp/${{ github.run_number }}/test-artifacts/tests/streams cd /tmp/liquidsoap-full/liquidsoap cp -f tests/streams/*.txt /tmp/${{ github.run_number }}/test-artifacts/tests/streams cp -f tests/streams/*.png /tmp/${{ github.run_number }}/test-artifacts/tests/streams - name: Upload test artifacts uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: (success() || failure()) && inputs.is_fork != 'true' && matrix.target == '@citest' with: name: test-artifacts path: ${{ github.workspace }}/${{ github.run_number }}/test-artifacts - name: Export potential core dumps uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: failure() with: name: core-dump-${{ matrix.target }} path: ${{ github.workspace }}/${{ github.run_number }}/core - name: Cleanup if: ${{ always() }} run: | rm -rf /tmp/${{ github.run_number }}/core liquidsoap-2.4.2/.gitignore000066400000000000000000000005401513273233300156620ustar00rootroot00000000000000*.sw* *~ _build/ liquidsoap.config *.install tests/streams/ssl.cert tests/streams/ssl.key .DS_Store package.json package-lock.json node_modules/ pnpm-lock.yaml package-lock.json src/tooling/prettier-plugin-liquidsoap/dist/liquidsoap.js src/tooling/prettier-plugin-liquidsoap/dist/web.mjs src/tooling/prettier-plugin-liquidsoap/dist/node.js *.conflicts liquidsoap-2.4.2/.ocamlformat000066400000000000000000000003371513273233300162030ustar00rootroot00000000000000version=0.28.1 profile = conventional break-separators = after space-around-lists = false doc-comments = before match-indent = 2 match-indent-nested = always parens-ite exp-grouping = preserve module-item-spacing = compact liquidsoap-2.4.2/.pre-commit-config.yaml000066400000000000000000000034141513273233300201560ustar00rootroot00000000000000--- # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks # # Run the following command to set up the pre-commit git hook scripts: # $ pre-commit install repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-symlinks - id: destroyed-symlinks - id: check-json - id: check-yaml - id: check-xml - id: check-merge-conflict - id: end-of-file-fixer exclude: dune.inc - id: mixed-line-ending exclude: dune.inc - id: trailing-whitespace exclude: dune.inc - repo: https://github.com/savonet/pre-commit-liquidsoap rev: 1ed3209389725a96248d56996208536af94e693b hooks: - id: liquidsoap-prettier - id: prettier - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: - id: codespell args: [-w, --ignore-words=.codespellignore] exclude: ^(doc/orig/fosdem2020|tests/language/cue_test.liq) - repo: local hooks: - id: shfmt name: shfmt language: docker_image entry: mvdan/shfmt -i 2 -ci -sr -kp -w types: [shell] - id: shellcheck name: shellcheck language: docker_image entry: koalaman/shellcheck --color=always types: [shell] - id: dunefmt name: dunefmt language: system entry: opam exec dune -- build @fmt --auto-promote files: \.(ml|mli|dune|dune-project)$ pass_filenames: false - id: dunegen name: dunegen language: system entry: opam exec dune -- build @gendune --auto-promote pass_filenames: false always_run: true liquidsoap-2.4.2/CHANGES000077700000000000000000000000001513273233300162532CHANGES.mdustar00rootroot00000000000000liquidsoap-2.4.2/CHANGES.md000066400000000000000000003604461513273233300153020ustar00rootroot00000000000000# 2.4.2 (2025-01-17) ## New: - Added pretty-print of clocks and source graphs! Use via `--describe-` in the command line or `clocks.dump` and `clocks.dump_sources` on telnet/server! (#4837, #4836) ## Changed: - Batch calls to `thread.when` to preserve performances, warn when using `thread.when` too intensively (#4832) - Added pretty-print of telnet `help` list of commands. This might break compatibility with automated telnet readers! (Remember: telnet is a user-facing API, not designed for machine consumption!). ## Fixed: - Fixed sources leaks in `source.dynamic` (#4835) - Optimized `Queues` implementation: fast, atomic access, slow, mutex-protected mutation (#4831) --- # 2.4.1 (2025-01-10) ## New: - Added support for explicit metadata to pass to the output when creating a `%ffmpeg` encoder (#4667) - Added start/stop telnet commands for outputs. - Added `settings.syslog.level` to set syslog level (#4686) - Added `active` parameter to `stereotool` and `output.stereotool` (#4749) - Added `file.mime.extension` to get the file extension's associated with a MIME/content-type as registered in `settings.http.mime.extnames` ## Changed: - Do not normalize sources in `mix` by default. - Added `json.value` to make it possible to mix different value types when returning json values (#4712) - Allow referring to pulseaudio devices by name (#4732) - `output.shoutcast`: make `dj` argument a getter. - Exclude metadata listed in `settings.encoder.metadata.cover` from automatic metadata recoding. (#4690) ## Fixed: - Fix `input.harbor` initial metadata when switching with fades (#4736) - Fix audio artifact in crossfade transitions (#4739) - Make sure that sources re-used in `source.dynamic` are never inadvertently cleaned up (#4713) - Fix sources not being properly collected (#4670) - Raise a proper error on parse error raised during `%include` parsing. - Don't print metadata coverart string (#4730) - Make sure `fetch` always fetches a new request in `request.dynamic` (#4745) - Fixed start/stop logic in `output.harbor` (#4666) - Fixed `fade.in`/`fade.out` logic w.r.t. override metadata (#4759) - Fixed `video.cover` --- # 2.4.0 (2025-09-01) ## New: ### Language: - Allow destructing function arguments using the same patterns as for variable assignment (#4562) - Enhanced labeled arguments syntax (#4526) - Add warning when erasing top-level variables (#4518) - `null` can now be used directly without having to call `null()`. `null(value)` calls are still valid and can be used to create non-null values with nullable types. Calls to `null()` are marked as deprecated (#4516) - Added `liquidsoap.script.path` that contains the path to the current script's file, if available. ### Core: - BREAKING: Most callbacks have been moved to source methods and are now executed asynchronously by default. See migration notes for all the details. - BREAKING: Added new file-to-file external `decoder.add` API. This makes it much easier and safer to write external decoders (#4531) - Deprecated `insert_metadata`, added default `insert_metadata` method on every source (#4541) - Added client certificate support to `http.transport.tls` (#4589, @DelilahHoare) ### Utilities: - Added `cron.parse`, `cron.{add,remove}` and a cron thread to allow registration of cron-like asynchronous tasks (#4579) - Added LUFS-based per-track loudness correction (#4545) ## Changed: ### Language: - BREAKING: Error methods have been removed by default. Use `error.methods` to get them! (#4537) - Make sure that `let { foo = gni } = v` assigns a value to `gni` but not to `foo` (#4561) ### Core: - Turned initial memory compaction on by default. This has show to greatly reduce initial memory consumption. - Improved source and clock naming (#4497) - Support for `ImageLib` has been removed. The library is not maintained anymore and causes issues with dangling external processes (#4595) ### Utilities: - Deprecated `replaygain` operator, introduced unified `normalize_track_gain` which works with both ReplayGain and LUFS (#4545) ## Fixed: - Fixed issues with autocue `start_next`. This brings autocue's behavior inline with its expectation but can change existing script's output (#4605) - Fix error when loading script path having non-ascii characters in them (#4343) - Fix concurrent inline encoders (#4638) - Fix error with interactive variables (#4592, @myeungdev) - Prevent concurrent reload and request fetch in playlists (#4316) - Don't mark source as ready until their clock has started. (#4496) - Fixed mutex deadlock caused by aggressive inlining (#4540) - Fixed segfault when using SRT on windows (#4538) - Fixed memleak in ffmpeg inline encoder (#4501) - Fixed `tmp:` protocol logic (#4544) - Fixed dangling file descriptors with `file.*.stream` (#4639, reported by @limebar) --- # 2.3.3 (2025-05-16) New: - `input.srt`: add `ipv6only` to allow to bind only to ipv6 addresses. Set it to `true` when `bind_address` is ipv6. - Allow HLS segment names to contain sub-directories. - Implicitly convert ffmpeg raw audio data, making it much more practical to write scripts using the raw ffmpeg format (#4478) Changed: - Made `defer` more user-friendly by operating directly on generic `source(audio=pcm('a))` sources. Renamed old `defer` to `defer.pcm_s16` - `dtools`, `duppy` and `xmlplaylist` have been moved into the liquidsoap code base and will no longer be developed or required as stand-alone packages (#12582) - Changed default value of `metadata.map` `strip` argument to `true` and `insert_missing` to `false` Added `settings.metadata.map.strip` and `settings.metadata.map.insert_missing` configuration keys to revert to previous defaults. (#4447) Fixed: - Do not send empty metadata to shoutcast servers (#4408) - Automatically close file descriptor opened via scripted values with a log message warning of file descriptor leaks (#4481) - Fixed segfault in new `lufs` C code introduced with release `2.3.2` (#4490) --- # 2.3.2 (2025-04-01) 🃏 New: - Added support for multiple metadata fields in ogg and flac metadata - Added support for track-level REM ALBUM in cue file parsing (#4381) Changed: - Added `"pic"` to list of excluded metadata for automatic charset conversion. - Added `settings.charset.max_string_length` setting to prevent automatic charset conversions of strings over that length. Fixed: - Optimized CPU usage (#4369, #4370) - Fixed empty initial HLS segment (#4401) - Fixed support for `duration` metadata in image decoder (#4397) - Fixed cue-out bug in cue file parsing (#4381) - Bring back parse error location. (#4362) - Fixed SRT encoding when restarting a stream with reverse data flow (#4399) - Make sure that audioscrobbler `on_track`/`on_end` operations are sent to a asynchronous task queue. - Fixed resources accumulation leading to catchup when using `crossfade` (#4419, #4410) - Fixed source reselection logic issue that was causing crashes when using `switch` and `fallback` operators (#4420) - Fixed self-sync logic with pulse audio outputs (#4429) - Fixed script caching on windows. --- # 2.3.1 (2025-02-05) New: - Added support for address resolution preference in SRT (#4317) - Added global address resolution settings for SRT and Icecast (#4317) - Added support for parsing and rendering XML natively (#4252) - Added support for `WAVE_FORMAT_EXTENSIBLE` to the internal wav dexcoder. - Added optional `buffer_size` parameter to `input.alsa` and `output.alsa` (#4243) - Reimplemented audioscrobbler support natively using the more recent protocol (#4250) - Added boolean getter to disable/enable normalization (#4308) Changed: - Make alsa I/O work with buffer size different than liquidsoap internal frame (#4236) - Reimplemented CUE file parser in native liquidsoap script, added support for multiple files and EAC non-compliant extension (#1373, #4330) - Make `"song"` metadata mapping to `"title"` metadata in `input.harbor` disabled when either `"artist"` or `"title"` is also passed. Add a configuration key to disable this mechanism. (#4235, #2676) - `output.icecast` now re-sends the last metadata when connecting to the remote server unless explicitly disabled using the `send_last_metadata_on_connect` option (#3906) - Add full explicit support for `ipv4` vs. `ipv6` resolution in SRT inputs and outputs, add global `settings.srt.prefer_address` and `settings.icecast.prefer_address` (#4317) - Added generic SRT socket get/set API. Added new socket options, including `latency` and `ipv6only`. Fixed: - Fixed request resolution loop when enabling both `autocue` and `replaygain` metadata resolvers (#4245, fixed in #4246) - Fixed `flac` encoding segfault (#4286, #4274) - Fixed source `last_metadata` not being properly updated (#4262) - Convert all ICY (icecast) metadata from `input.http` to `utf8`. - Fixed `inotify` unwatching due to GC cleanup (#4275) - Fixed `delay` initial conditions (#4281) --- # 2.3.0 (2024-11-27) New: - Rewrote the streaming API to work with immutable frame content. This should greatly impact impredictable side-effect of the previous models w.r.t. track marks, content sharing and more. This also impacts multiple operators behavior. Mostly, things should be roughly the same with differences around behaviors related to track marks (`source.on_track` and etc). (#3577) - Added script caching layer for faster script startup time. See: https://www.liquidsoap.info/blog/2024-06-13-a-faster-liquidsoap/ for details (#3924, #3949, #3959 and #3977) - Rewrote the clock/streaming loop layer. This prepares our streaming system to support multicore when the OCaml compiler is mature enough to allow it. Clocks are now attached to sources via their `clock` methods. Returned value is a stripped down `clock` variable. Users can use the `clock` function to retrieve the full methods, e.g. `s = sine(); c = clock(s.clock)`. This value has advanced functions for clock control such as `start`/`stop`, `ticks` and `self_sync` to check for `self-sync`. (#3781) - Allow frames duration shorter than one video frames, typically values under `0.04s`. Smaller frames means less latency and memory consumption at the expense of a higher CPU usage (#3607) - Change default frame duration to `0.02s` (#4033) - Optimized runtime (#3927, #3928, #3919) - Added NDI output support (#4181) - Added `finally` to execute code regardless of whether or not an exception is raised (see: #3895 for more details). - Added support for Spinitron submission API (#4158) - Removed gstreamer support. Gstreamer's architecture was never a good fit for us and created a huge maintenance and debugging burden and it had been marked as deprecated for a while. Most, if not all of its features should be available using `ffmpeg`. (#4036) - Removed `taglib` support. It is superseded by the internal `ocaml-metadata` module and taglib, with its dependency on the C++ runtime library, has been causing issues with binary builds portability and crashes with the (not yet supported) OCaml 5 compiler. (#4087) - Added `video.canvas` to make it possible to position video elements independently of the rendered video size ([#3656](https://github.com/savonet/liquidsoap/pull/3656), [blog post](https://www.liquidsoap.info/blog/2024-02-10-video-canvas-and-ai/)) - Added cover manager from an original code by @vitoyucepi (#3651) - Added non-interleaved API to `%ffmpeg` encoder, enabled by default when only one stream is encoded. - Allow trailing commas in record definition (#3300). - Added `metadata.getter.source.float` (#3356). - BREAKING: Added `duration` and `ticks` to metadata available when computing HLS segment names (#4135) - Added optional `main_playlist_writer` to `output.file.hls` and derivated operator (#3484) - Added `is_nan`, `is_infinite`, `ceil`, `floor`, `sign` and `round` (#3407) - Added `%track.drop` to the `%ffmpeg` encoder to allow partial encoding of a source's available tracks (#3480) - Added `let { foo? } = ...` pattern matching (#3481) - Added `metadata.replaygain` method to extract unified replay gain value from metadata (#3438). - Added `metadata.parse.amplify` to manually parse amplify override metadata. - Added `compute` parameter to `file.replaygain` to control gain calculation (#3438). - Added `compute` parameter to `enable_replaygain_metadata` to control replay gain calculation (#3438). - Added `copy:` protocol (#3506) - Added `file.touch`. - Added support for sqlite databases (#3575). - Added `string.of_int` and `string.spaces`. - Added `list.assoc.nullable`. - Added `source.cue` (#3620). - Added `string.chars` (#4111) - Added atomic file write operations. - Added new `macos_say` speech synthesis protocol. Make it the default implementation for the `say:` protocol on `macos`. - Added `settings.request.timeout` to set the request timeout globally. Changed: - Reimplemented `request.once`, `single` and more using `source.dynamic`. Removed experiment flag on `source.dynamic`. The operator is considered stable enough to define advanced sources but the user should be careful when using it. - Mute SDL startup messages (#2913). - `int` can optionally raises an error when passing `nan` or `infinity`, `int(infinity)` now returns `max_int` and `int(-infinity)` returns `min_int`. (#3407) - Made default font a setting (#3507) - Changed internal metadata format to be immutable (#3297). - Removed `source.dump` and `source.drop` in favor of safer `request.dump` and `request.drop`. `source.{dump, drop}` can still be implemented manually when needed and with the proper knowledge of what's going on. - Allow a getter for the offset of `on_offset` and dropped the metadata mechanism for updating it (#3355). - `string.length` and `string.sub` now default to `utf8` encoding (#4109) - Disable output paging when `TERM` environment variable is not set. - Allow running as `root` user inside `docker` container by default (#3406). - Run `check_next` before playlist's requests resolutions (#3625) - Set `force` to `true` by default in `file.copy` to make operator behave as expected. - BREAKING: Float comparison now follows the expected specs, in particular: `nan == x` is always `false` and `nan != x` is always `true`. Use `float.is_nan` to test if a float is `nan`. - BREAKING: `replaygain` no longer takes `ebu_r128` parameter (#3438). - BREAKING: assume `replaygain_track_gain` always stores volume in _dB_ (#3438). - BREAKING: protocols can now check for nested static uri. Typically, this means that requests for an uri of the form: `annotate:key="value",...:/path/to/file.mp3` is now considered infallible if `/path/to/file.mp3` can be decoded. - Added `parents` option of `file.mkdir` (#3600, #3601). - Added `forced_major_collections` record field to the result of `runtime.gc.stat()` and `runtime.gc.quick_stat()` (#3783). - Changed the port for the built-in Prometheus exporter to `9599` (#3801). - Set `segments_overheader` in HLS outputs to disable segments cleanup altogether. - Added support for caching LV2 and LADSPA plugins (#3959). - Pulseaudio input and output now restart on pulseaudio errors (#4174). Fixed: - Fixed type generalization on values returned from function applications. Most notably, this should help with HTTP endpoint registration (#3303, fixed in #4030) --- # 2.2.5 (2024-05-01) (Mayday!) New: - Added `enable_autocue_metadata` and `autocue:` protocol to automatically compute cue points and crossfade parameters (#3753, #3811, @RM-FM and @Moonbase59) - Added various ffmpeg timestamps when exporting ffmpeg metadata from filters. - Added `db_levels` method to `blank.*` sources (#3790) - Added `excluded_metadata_resolvers` to `request.create` to make it possible to selectively disable specific metadata resolvers when resolving requests. - Normalized expected API from `autocue`, allow multiple implementation and adapt `cross`/`crossfade` to work with it out of the box with workaround for short tracks. - Added private and swapped memory reporting when compiled with `mem_usage`. - Added priorities to metadata deocoders, allows finer-control of metadata overriding. (#3887) Changed: - Allow to disable `http.*` url normalization. Add warning when url normalization changes the url (#3789) - Add `namespace` and optional source IDs to `mix`. Fixed: - Prevent request metadata from overriding root metadata (#3813) - Fixed `source.drop` and `source.dump` clock initialization. - Fixed bogus report of non-monotonous PTS content when using raw ffmpeg content. - Fixed streaming errors when disconnecting `input.harbor`. - Fixed issues with rendered id3v2 frame that contain binary data (#3817) - Fixed memory leaks with SRT listen socket polling callbacks. - Fixed `%ffmpeg` copy muxer logic with some audio/video streams (#3840) - Fixed `duration` metadata calculation in the presence of `cue_in`/`cue_out` metadata. --- # 2.2.4 (2024-02-04) New: - Added support for `id3v2` metadata in `output.*.hls` when using `%mp3`, `%shine` or `%fdkaac` encoders (#3604) - Added option to set preferred address class (`ipv4`, `ipv6` or system default) when resolving hostnames in http transports and `output.icecast` - Added `self_sync` option to `input.srt` to accommodate for streams in file mode (#3684) - Added `curve` parameter to fade functions and `liq_fade_{in,skip,out}_curve` metadata override (#3691) - Added `delay` parameter to fade functions to make it possible to add delay before fade happens. Add `liq_fade_{in,skip,out}_delay` metadata override. - Added `single_track` option to allow `sequence` to play each source until they are unavailable while keeping track marks. Changed: - `cue_cut` operator has been removed. Cueing mechanisms have been moved to underlying file-based sources. See migration notes for more details. Fixed: - Fix pop/click at the end of fade out/in (#3318) - Fix audio/video synchronization issues when decoding live streams using ffmpeg. - Fix issues with TLS connecting clients not being properly timed out (#3598) - Make sure reconnection errors are router through the regulat `on_error` callback in `output.icecast` (#3635) - Fixed discontinuity count after a restart in HLS outputs. - Fixed file header logic when reopening in `output.file` (#3675) - Fixed memory leaks when using dynamically created sources (`input.harbor`, `input.ffmpeg`, SRT sources and `request.dynamic`) - Fixed invalid array fill in `add` (#3678) - Fixed deadlock when connecting to a non-SSL icecast using the TLS transport (#3681) - Fixed crash when closing external process (#3685) --- # 2.2.2 (2023-11-02) New: - Added `string.escape.html` (#3418, @ghostnumber7) - Add support for getters in arguments of `blank.detect` (#3452). - Allow float in source content type annotation so that it possible to write: `source(audio=pcm(5.1))` Changed: - Trim urls in `input.ffmpeg` by default. Disable using `trim_url=false` (#3424) - Automatically add HLS-specific ffmpeg parameters to `%ffmpeg` encoder (#3483) - BREAKING: default `on_fail` removed on `playlist` (#3479) Fixed: - Allow `channel_layout` argument in ffmpeg encoder to set the number of channels. - Improved support for unitary minus, fix runtime call of optional methods (#3498) - Fixed `map.metadata` mutating existing metadata. - Fixed reloading loop in playlists with invalid files (#3479) - Fixed main HLS playlist codecs when using `mpegts` (#3483) - Fixed pop/clicks in crossfade and source with caching (#3318) - Fixed pop/clicks when resampling using `libsamplerate` (#3429) - Fixed gstreamer compilation. Remember that gstreamer features are DEPRECATED! (#3459) - Fixed html character escaping in `interactive.harbor` (#3418, @ghostnumber7) - Fixed icecast not reconnecting after erroring out while closing connection in some circumstances (#3427) - Fixed parse-only mode (#3423) - Fixed ffmpeg decoding failing on files with unknown codecs. - Fixed a crash due to `wait_until` timestamp being in the past when using `posix-time2` - Make sure that temporary files are always cleaned up in HLS outputs (#3493) --- # 2.2.1 (2023-09-05) Changed: - BREAKING: on HLS outputs, `on_file_change` events are now `"created"`, `"updated"` and `"deleted"`, to better reflect the new atomic file operations (#3284) - Added `compact` argument to the `http.response.json` function. `http.response.json` will produce minified JSON by default. Added a newline symbol to the end of the JSON data produced by `http.response.json`. (#3299) - Bumped internal ogg decoder to make sure that it is used over the ffmpeg decoder whenever possible. FFmpeg has issues with metadata in chained streams which needs to be fixed upstream. Unfortunately, `input.http` can only use the ffmpeg decoder at the moment. - Cleanup `output.file` encoding and file handling logic (#3328) - Added `ratio` to `source.{dump,drop}` to make it possible to control its CPU peaks. - Enhanced clock error reporting (#3317) Fixed: - Fixed slow memory leak in muxer operator (#3372, #3181, #3334) - Fixed discontinuity logic error in HLS outputs after a restart. - Fixed HTTP response status in `output.harbor` (#3255) - Make sure main HLS playlist is regenerated after being unlinked (#3275) - Fixed hard crash on icecast disconnection errors. - Fix `output.harbor` encoder header when encoding with `%ogg`, `%vorbis` and etc. (#3276) - Fixed quality argument parsing in ffmpeg encoders (#3267) - Make all HLS file write atomic (#3284) - Allow seek and cue operators to work with muxed sources using a single underlying source (#3252) - Fixed export of cover art metadata (#3279) - Remove use of `stereo:` protocol in `say:` protocol: this is now handled automatically by the decoder and generates latency via high CPU usage peak. - Fixed `output.file` reopening with flac encoding (#3328) --- # 2.2.0 (2023-07-21) New: - Added support for less memory hungry audio formats, namely `pcm_s16` and `pcm_f32` (#3008) - Added support for native osc library (#2426, #2480). - SRT: added support for passphrase, pbkeylen, streamid, added native type for srt sockets with methods, moved stats to socket methods, added `socket()` method on srt input/outputs (#2556) - HLS: Added support for ID3 in-stream metadata (#3154) and custom tags (#2898). - Added support for FLAC metadata (#2952) - Added support for YAML parsing and rendering (#2855) - Added support for the proprietary shared stereotool library (#2953) - Added TLS support via `ocaml-tls` (#3074) - Added `video.align`. - Added `string.index`. - Added support for ffmpeg decoder parameters to allow decoding of raw PCM stream and file (#3066) - Added support for unit interactive variables: those call a handler when their value is set. - Added support for id3v2 `v2.2.0` frames and pictures. - Added `track.audio.defer` to be used to buffer large amount of audio data (#3136) - Added `runtime.locale.force` to force the system's locale (#3231) - Added support for customizable, optimized `jemalloc` memory allocator (#3170) - Added `source.drop` to animate a source as fast as possible.. - Added in house replaygain computation: - `source.replaygain.compute` to compute replaygain of a source - `file.replaygain` to compute the replaygain of a file - Added support for ImageLib to decode images. - Added support for completion in emacs based on company (#2652). - Added syntactic sugar for record spread: `let {foo, gni, ..y} = x` and `y = { foo = 123, gni = "aabb", ...x}` (#2737) - Added `file.{copy, move}` (#2771) - Detect functions defining multiple arguments with the same label (#2823). - Added `null.map`. - References of type `'a` are now objects of type `(()->'a).{set : ('a) -> unit}`. This means that you should use `x()` instead of `!x` in order to get the value of a reference. Setting a reference can be done both by `x.set(v)` and `x := v`, which is still supported as a notation (#2881). - Added `ref.make` and `ref.map`. - Added `video.board`, `video.graph`, `video.info` (#2886). - Added the `pico2wave` protocol in order to perform speech synthesis using [Pico TTS](https://github.com/naggety/picotts) (#2934). - Added `settings.protocol.gtts.lang` to be able to select `gtts`' language, added `settings.protocol.gtts.options` to be able to add any other option (#3182) - Added `settings.protocol.pico2wave.lang` to be able to select `pico2wav` language (#3182) - Added `"metadata_url"` to the default list of exported metadata (#2946) - Added log colors! - Added `list.filter_map` and `list.flatten`. - Added `medialib` in order to store metadata of files in a folder and query them (#3115). - Added `--unsafe` option (#3113). This makes the startup much faster but disables some guarantees (and might even make the script crash...). - Added `string.split.first` (#3146). - Added `string.getter.single` (#3125). Changed: - Switched to `dune` for building the binary and libraries. - Changed `cry` to be a required dependency. - Changed default character encoding in `output.harbor`, `output.icecast` `output.shoutcast` to `UTF-8` (#2704) - BREAKING: all `timeout` settings and parameters are now `float` values and in seconds (#2809) - BREAKING: in `output.{shoutcast,icecast}`: - Old `icy_metadata` renamed to `send_icy_metadata` and changed to a nullable `bool`. `null` means guess. - New `icy_metadata` now returns a list of metadata to send with ICY updates. - Added `icy_song` argument to generate default `"song"` metadata for ICY updates. Defaults to ` - ` when available, otherwise `artist` or `title` if available, otherwise `null`, meaning don't add the metadata. - Cleanup, removed parameters that were irrelevant to each operator, i.e. `icy_id` in `output.icecast` and etc. - Make `mount` mandatory and `name` nullable. Use `mount` as `name` when `name` is `null`. - `reopen_on_error` and `reopen_on_metadata` in `output.file` and related operators are now callbacks to allow dynamic handling. - Added `reopen` method to `output.file`. - Added support for a Javascript build an interpreter. - Removed support for `%define` variables, superseded by support for actual variables in encoders. - Cancel pending append when skipping current track on `append` source. - Errors now report proper stack trace via their `trace` method, making it possible to programmatically point to file, line and character offsets of each step in the error call trace (#2712) - Reimplemented `harbor` http handler API to be more flexible. Added a new node/express-like registration and middleware API (#2599). - Switched default persistence for cross and fade-related overrides to follow documented behavior. By default, `"liq_fade_out"`, `"liq_fade_skip"`, `"liq_fade_in"`, `"liq_cross_duration"` and `"liq_fade_type"` now all reset on new tracks. Use `persist_overrides` to revert to previous behavior (`persist_override` for `cross`/`crossfade`) (#2488). - Allow running as root by default when docker container can be detected using the presence of a `/.dockerenv` file. - `id3v2` argument of `%mp3` encoder changed to `"none"` or version number to allow to choose the metadata version. `true` is still accepted and defaults to version `3`. Switched to our internal implementation so that it does not require `taglib` anymore. - Moved HLS outputs stream info as optional methods on their respective encoder. - Changed `self_sync` in `input.ffmpeg` to be a boolean getter, changed `self_sync` in `input.http` to be a nullable boolean getter. Set `self_sync` to `true` in `input.http` when an icecast or shoutcast server can be detected. - Add `sorted` option to `file.ls`. - Add `buffer_length` method to `input.external.rawaudio` and `input.external.wav` (#2612). - Added full `OCaml` backtrace as `trace` to runtime errors returned from OCaml code. - Removed confusing `let json.stringify` in favor of `json.stringify()`. - Font, font size and colors are now getters for text operators (`video.text`, `video.add_text`, etc.) (#2623). - Add `on_cycle` option to `video.add_text` to register a handler when cycling (#2621). - Renamed `{get,set}env` into `environment.{get,set}` - Renamed `add_decoder`, `add_oblivious_decoder` and `add_metadata_resolver` into, respectively, `decoder.add`, `decoder.oblivious.add`, `decoder.metadata.add` - Deprecated `get_mime`, added `file.mime.libmagic` and `file.mime.cli`, made `file.mime` try `file.mime.libmagic` if present and `file.mime.cli` otherwise, changed returned value when no mime was found to `null`. - Return a nullable float in `request.duration`. - Removed `--list-plugins-json` and `--list-plugins-xml` options. - Added `--list-functions-json` option. - Removed built-in use of `strftime` conversions in output filenames, replaced by an explicit call to `time.string` (#2593) - Added nullable default to `{int,float,bool}_of_string` conversion functions, raise an exception if conversion fails and no default is given. - Deprecated `string_of` in favor of `string` (#2700). - Deprecated `string_of_float` in favor of `string.float` (#2700). - Added `settings.protocol.youtube_dl.timeout` to specify timeout when using `youtube-dl` protocol (#2827). Use `yt-dlp` as default binary for the protocol. - The `sleeper` operator is now scripted (#2899). - Reworked remote request file extension resolution (#2947) - REMOVED `osx-secure-transport`. Doubt it was ever used, API deprecated upstream (#3067) - Renamed `rectangle` to `add_rectangle`, and similarly for `line`. Fixed: - The randomization function `list.shuffle` used in `playlist` was incorrect and could lead to incorrectly randomized playlists (#2507, #2500). - Fixed srt output in listener mode to allow more than one listener at a time and prevent listening socket from being re-created on listener disconnection (#2556) - Fixed race condition when switching `input.ffmpeg`-based urls (#2956) - Fixed deadlock in `%external` encoder (#3029) - Fixed crash in encoders due to concurrent access (#3064) - Fixed long-term connection issues with SSL (#3067) --- # 2.1.4 (2022-03-01) New: - Added `buffer_length` method to `buffer` operator. - Always display error backtrace when a fatal exception is raised in the streaming loop. - Added `umask()` to get the current `umask` and `umask.set(...)` to set the current `umask` (#2840) Changed: - Add break when restarting the external process in `input.external.{rawaudio,rawvideo}` (#2860, #2872) - Removed `disconnect` method on `input.harbor`. This method was doing the same as the `stop` method. Added `shutdown` method to properly shutdown the source even when not connected to an output. - Made process a string getter in `input.external.{rawaudio,rawvideo}` (#2877) Fixed: - Fixed parameter type for `stats_interval` in SRT I/O. - Fixed type generalization on variable and pattern bindings (#2782) - Fixed memory leak in http requests (#2935) - Make sure that exception raised in `request.dynamic` never crash the process (#2897) - Fixed `filename` getter being called multiple time in `output.file` (#2842) - Fixed default directory permissions in `output.*.hls` operators (#2930) - Space trim in interactive variables set on telnet (#2785) - Fixed internal streaming logic in `max_duration` and `crossfade`. - Make sure that there's at most one metadata at any given frame position (#2786) - Fixed `metadata.json.parse` always returns an empty list (#2816). - Fixed `icy_id` being ignored in `output.shoutcast` (#2819) - Fixed shutdown livelock with some ffmpeg inline encoder, decoder and filter operators. - Fixed input polling stop (#2769) - Fixed parsed error report in `%include` directives (#2775) - Fixed crash in external processes when received a `Unix.EINTR` event (#2861) - Fixed crash in `string.interpolate` (#2883) - Cleaned up srt support. --- # 2.1.3 (2022-11-04) New: - Added `time.string`. - Added `error.on_error` to report any error raised during the script's execution. Enhanced reported error positions (#2712) - Added `device_id` and `latency` options to `input.portaudio` and `output.portaudio` to be able to choose the requested device. Use `liquidsoap --list-portaudio-devices` to see the list of devices (#2733) - Added `disconnect` method to `input.harbor`, making it possible to disconnect a source client programmatically, including when a new client is trying to connect. Changed: - Send data in-memory in `http.{post,put}.file` when input data is already in memory. This allows to use plain `Content-Length` instead of `chunked` transfer encoding in these case, though `libcurl` seems to always prefer `chunked` encoding for `put` requests. - Better error message when an encoder is not available on windows (#2665) - Create output directory in HLS outputs when it does not exist using newly introduced `perms` permission argument (#2725) - Removed `restart_on_error` argument on `output.url` and added `restart_delay` which implements a delayed restart. Added `on_error` argument to be notified of errors (#2731) - Changed default `encoding` parameter in `string.{quote, escape}` to be `null`. Fallback to `"ascii"` encoding when no encoding is specified and `"utf8"` fails. This prevents unexpected script failures but might not be backward-compatible if you used a custom `escape_char` or `special_char` function (#2738) Fixed: - Enhanced methods typing support (#2659) - Add support for `song` metadata (mapped to `title`) and `url` (mapped to `metadata_url`) in `input.harbor` (#2676) - Fixed `blank.*` operator types. - Fixed request metadata escaping (#2732) - Fixed `input.external.rawadudio` mono input (#2742) - Fixed `http` response body on redirect (#2758) --- # 2.1.2 (2022-09-26) New: - Added `string.char`, `string.getter.flush` and `string.getter.concat`. - Added `http.multipart_form_data` and `http.{post,put}.file`. Changed: - Allow sub-second values in `sleep()` (#2610) - Allowed many new format for `taglib` (#2605) - Add `settings.ffmpeg.content.copy.relaxed_compatibility_check.set` settings to allow relaxed compatibility check for ffmpeg copy content, making it possible to encode streams with various audio samplerate or video size when the container supports it. Fixed: - Stop error loop when opening a listening ssl socket with non-existent certificate. (#2590) - Youtube HLS upload for live streams. - Fixed `data:...` uri scheme to conform to RFC 2397 (#2491) - Fixed multiple issues related to empty `ogg/opus` metadata (#2605) - Ensure that `video.add_text` fails when the source does (#2609) - Fixed metadata parsing in `server.insert_metadata` (#2619) - Fixed `extract_replaygain` path (#2624, @parnikkapore) - Fixed crash when terminating the process (#2585) - Fixed channels conversion when using `input.rawaudio` (#2602) Internal Change: - `ref()` implementation switched to OCaml's `Atomic` to prevent race conditions, `thread.mutexify` and `mutexify` functions removed. (#2603) --- # 2.1.1 (2022-08-28) New: - Added `process.quote.command` to generate complex quoted command strings suitable for use with `process.run` and os-independent. Changed: - Renamed `playlist.remaining` into `playlist.remaining_files` (#2524) - Added `id` argument to `replaygain` operator (#2537). - Made `ocurl` dependency required, added `uri` as required dependency (#2551) Fixed: - Fixed missing ffmpeg features on windows build. - Fixed sync issues with `ffmpeg.encode.*` inline encoders (#2584) - Fixed `http.get` issues when `user-agent` was not set (#2517) - Fixed order of `playlist.next` returned requests. - Fixed infinite loop when reloading a failed playlist (#2576) - Fixed http requests with urls containing spaces (#2551) - Fixed `on_connect` type for `srt` inputs and outputs. - Fixed parsing issues with functions/variables definitions beginning with `rec` or `replaces` (#2560) - Fixed infinite parse error loop (#2527) - Fixed empty initial `mp4` HLS segment. - Prevent initial start for autostart and fallible sources. --- # 2.1.0 (2022-07-15) New: - Added support for variables in encoders (#1858) - Added support for regular expressions (#1881) - Added generalized support for value extraction patterns (#1970) - Added support for string getter for `http.{post,put}` operations (#1984) - Added `output.youtube.live.hls` - Rewrote out internal JSON parser/renderer (#2011). **Breaking change** values that cannot be represented as `JSON` will now raise `error.json` when converted to `JSON`. `infinite` and `nan` floats can be exported using the `json5` export format. - Added socket API (#2014). - Added support for ffmpeg bitstream filters (#2387) - Added `liquidsoap.version.at_least`. - Added `video.rectangle`, `video.persistence`. - Added `video.vumeter`. - Added `video.slideshow`. - Added `video.add_text.camlimages` (#2202). - Added `video.text.*` and re-implemented `video.add_text.*` from those (#2226). - Added `irc.channel` operator to retrieve the contents of an IRC channel (#2210). - Added new in-house parsing of metadata for some image and video formats (#2236). - Added `file.download` - Added new options for `%ffmpeg` copy encoder: `ignore_keyframes` and `wait_for_keyframe` (#2382) Changed: - Removed support for partial application, which should avoid some type errors, improve performance and simplifies the code related to the reduction (#2204). - Video dimensions (width and height) can now be specified per stream in the type and are then used instead of the default ones. For instance, you can now write ``` s = (single("file.mp4") : source(video(width=300,height=200))) ``` in order to force the decoding of a file to be performed at the 300×200 resolution (#2212). - Video images are now _canvas_, which means that they do not directly contain the images, but are constituted of multiple images placed at various positions. This should make much more efficient operations such as making videos from multiple ones, adding a logo, etc. (#2207) - `output.youtube.live` renamed `output.youtube.live.rtmp`, remove `bitrate` and `quality` arguments and added a single encoder argument to allow stream copy and more. - `source.on_metadata` and `source.on_track` now return a source as this was the case in previous versions, and associated handlers are triggered only when the returned source is pulled (#2103). - Made `streams_info` parameter of `output.file.hls` a record (#2173). - Disable scrolling by default in `video.add_text`. You can re-enable it by using `video.add_text(speed=70, ...)`. - Added "example" sections to operators documentation, we now need to populate those (#2227). - Default implementation of `video.testsrc` is now builtin, previous implementation can be found under `video.testsrc.ffmpeg`. - Images can now generate blank audio if needed, no need to add `mux_audio(audio=blank(),image)` anymore (#2230). - Removed deprecated `timeout` argument in `http.*` operators. - Deprecated `request.ready` in favor of `request.resolved`. Fixed: - Fixed typo in `status` command of the `mix` operator. - Fixed performances issues with `input.ffmpeg` and `input.http` (#2475) - Fixed `list.shuffle` which was used to randomize playlists in `playlist` operator (#2507, #2500). --- # 2.0.7 (2022-07-15) Fixed: - Fixed memory leaks with opus bindings. - Make sure decoding buffer and samplerate converter are only created once. (#2475) - Make sure first metadata is always sent in icecast/shoutcast output (#2506) --- # 2.0.6 (2022-06-20) New: - Added `video/mp4` to list of recognized mime types for request resolutions. Changed: - Log errors when using `process.read` (##2420, @martinkirch) Fixed: - Memory leak when executing `process.run` (#2424) - Delay harbor server endpoint registration until application has started (#1589) - Print user-readable encoder parameter error report. - Fixed m3u metadata parsing when artist has a comma in their name (#2449) - Cleanup failed request in `playlist` operator. - Make sure requests are always cleaned up, making `request.destroy` calls optionals. # 2.0.5 (24-05-2022) New: - Extended m3u EXTINF parser to support empty duration and annotations. Changed: - Brought back `mix` operator (#2401) Fixed: - Allow crossfade duration override of `0.` - Buffer synchronization issues. - Drop methods from ffmpeg filter input source types to avoid unnecessary conflicts. - Fix evaluation of abstract values with methods. - Prevent some sources from being consumed when not active, namely ffmpeg inline encoders, `soundtouch`, `resample` and all the muxing operators. - Raise runtime exceptions in `string.replace` failures with useful message. (#2408) - Prevent `request.dynamic` from raising exceptions when checking if the source is ready (#2381) --- # 2.0.4 (23-04-2022) New: - Added `settings.video.add_text` to enforce consistent choice of `video.add_text` implementation (#2302) Changed: - Make sure source shutdown can only be called on sources that can actually be shutdown: - Remove generic `source.shutdown` - Keep `s.shutdown()` method only on sources that are active. Refs: #2259 - Optimized memory usage when accessing frame content (#2266) - Optimized memory usage when accessing ground terms. - Allow crossfade duration getter to override duration at the end of each track if duration isn't set via metadata. - Make sure crossfade metadata are not duplicated (#2153) - Renamed `map_metadata` into `metadata.map`, deprecated `map_metadata`. - Deprecatdd `list.mem_assoc` - Enhanced remaining time when using `add` (#2255) - Added `timeout_ms` to `http.*` to provide time in milliseconds, deprecated `timeout` argument. - Connect `output.icecast` when data is available instead of when operator starts to avoid useless connections when underlying source fails immediately. Fixed: - Prevent infinite loops when crossfade duration is negative (#2287) - Prevent mutex deadlock when recursively locking mutexes (#2274) - Mark method `add()` as internal in `request.queue`, fix method `length()` (#2274) - Fixed `retry_delay` being ignored in some cases in `request.dynamic`. - Prevent race condition in external process handler. - Fixed A/V sync when streaming encoded data via ffmpeg encoder (#2159) - Prevent stopped/iddle sources from being restarted when resetting `clock(s)` after too much latency (#2278) - Fixed registration of `video.add_text.ffmpeg` as possible implementation for `video.add_text` (#2302) - Fixed `http.*` calls preventing liquidsoap from shutting down. - Fixed `http` protocol not returning an error when timing out (#2242) - Reworked ffmpeg filters feeding mechanism. - Fixed inconsistencies in `playlist.parser` (#2257) - Fixed inconsistent reselect in `rotate` (#2300) - Fixed special characters escaping in `video.add_text.ffmpeg` (#2324) - Fixed `input.rawaudio` and `input.rawvideo` when handling non-stereo content. # 2.0.3 (11-02-2022) New: - Added support for memory debugging using `memtrace` - Added `time.{zone,zone.set,make}` (#2178) - Added `runtime.gc` module, rename `garbage_collect` as `runtime.gc.full_major` with deprecated compatibility wrapper, added `runtime.gc.stat`, `runtime.gc.quick_stat`, `runtime.gc.print_stat` and `runtime.gc.{get,set}`. - Added `runtime.sys.word_size` - Added optional support for `runtime.mem_usage` - Added `runtime.memory` wrapper to get info about the system and process' memory usage. - Added `configure.camomile_dir` to export expected location of camomile directory when packaging liquidsoap. - Added `liquidsoap.chroot.make` to copy all files required for a liquidsoap install. Changed: - Bumped `input.harbor` default buffer to `12.` to make it possible to use it with `crossfade` transitions without changing default values (#2156) - `year` method as returned in `time.local` and `time.utc` now returns the actual year instead of years since 1900 (#2178) - `mday`, `mon`, `wday` and `yday` methods as returned in `time.local` and `time.utc` have been renamed to, resp., `day`, `month`, `week_day` and `year_day` (#2178) - `month` method as returned in `time.local` and `time.utc` now returns the month as a number between `1` and `12` (#2178) - `week_day` method as returned in `time.local` and `time.utc` now returns the week day as a number between `1` and `7` (#2178) - `year_day` method as returned in `time.local` and `time.utc` now returns the week day as a number between `1` and `366` (#2178) - Added option to choose if `input.rtmp` should behave as a server or a client (#2197) - Allow dynamic text change in `video.add_text.ffmpeg` (#2189) - Removed `thread_name` argument from `thread.on_error` callbacks. Fixed: - Make sure metadata are replayed when switching to a source for the first time in switches/fallback (#2138) - Bring back `video.add_text.sdl` (#2187) - Fixed `thread.on_error` implementation (#2171) - Fixed `ffmpeg` video scaling to make sure it always is proportional (#2211) # 2.0.2 (28-12-2021) New: - Show code excerpts on errors (#2086) - Added `on_get_ready` callback to sources, to be executed after a source's has initialized. - Added `flush_and_skip` telnet command to `request.dynamic` to empty the request's queue before skipping the current track, forcing a full reload. - Added `last_metadata` method on sources to return the last metadata produced by the source. Fixed: - Fixed ffmpeg copy encoder crash when switching between streams. - Fixed unbound buffer in muxing operators (#2054) - Return correct positions when parsing strings (#2095) - Deadlock when shutting down with `input.rtmp` (#2089) - Add timeout to srt operations (#2082) - Fixed `request.queue` `queue` telnet command returning nothing (#2088) - Fixed single quotes being escaped in json stringify. (#2120) - Fixed frame caching issues when no initial break was present in the memoized frame. (#2109. AzuraCast/AzuraCast#4825) - Fixed `replay_metadata` not replaying metadata from active sources (#2109) # 2.0.1 (27-11-2021) New: - Added `time.predicate` to parse time predicates at runtime. - Added support for ffmpeg filter commands, unify `video.add_text.ffmpeg` with other operators, make it the default when available. (#2050) Changed: - Removed `encode_metadata` option in `input.file.hls` as it does nothing with the main encoder for HLS format, `%ffmpeg` (#2023) - Converted `output.icecast` optional parameters to `nullable`. Fixes: - Fixed switch-based sources not respecting track boundaries when using default transitions one track only per selected source. (#1999) - Fixed playlist annotation. (#2005) - Raise a proper runtime exception when `string.escape` fails. (#2010) - Account for internal caching in `request.dynamic.list`'s `queue` and `set_queue` methods. - Keep buffering for crossfade when new source has track mark but is still ready. - Added missing output `start`/`stop` commands. - Fixed `perms`, `dir_perms` and `append` not bring honored when delegating file output to the encoder. - Fixed base directory not being created when delegating file output to the encoder (#2069). - Use `process.quote` in process calls (#2031) # 2.0.0 (03-10-2021) New: - Add support for errors with `error.*` and `try ... catch` (#1242). - Add support for optional values with `null.*` (#1242). - Add support for `x ? y : z` syntax (#1266). - Added support for list spread and deconstruction syntax (#1269). - Add support for generic JSON objects, map `(string, 'a)` lists to regular lists, add support for json5 floats (`NaN`, `Infinity`), return `null` for those otherwise, rename `json_of` into `json.stringify` and `of_json` into `json.parse` with deprecation (#1824) - Added support for video encoding and decoding using `ffmpeg` (#1038). - Added support for hardware-accelerated video encoding using `ffmpeg` (#1380) - Added support for ffmpeg filters (#1038). - Added video support to `output.hls` (#1391). - Added mp4 support to `output.hls` (#1391). - Added `output.url` for encoders that support handling data output (currently only `%ffmpeg`) (#1038). - Added `output.file.dash.ffmpeg`. - Added LV2 support (#906). - Added `string.nth` (#970). - Added `string.binary.to_int` (#970). - Added `string.hex_of_int`. - Added `file.ls` (#1011). - Added native id3v2 tag parser, as well as associated function `file.mp3.metadata`, `file.mp3.parse_apic` and `file.cover` (#987). - Use a pager to display long help results (#1017). - Added new functions for lists: `lists.exists`, `list.for_all`, `list.init`, `list.ind`, `list.index`, `list.last`, `list.shuffle`. - Added `request.id`. - Added a profiler for the language. It can be enabled with `profiler.enable` and the results are obtained with `profiler.stats.string` (#1027). - Added `gtts` protocol to use Google TTS (#1034). - Added `liquidsoap.executable` to get the path of the currently running Liquidsoap. - Added `source.dump`. - Added `source.elapsed` and `source.duration` - Added `synth` protocol (#1014). - Added listener and caller mode for `input.srt` and `output.srt` (#1377) - Added support for `srt.enforced_encryption` setting. - Added support for prometheus reporting (#1000) - Add `validate` parameter to `register`, which allows to validate a value before setting it (#1046, @CyberDomovoy) - Add `string.null_terminated` (#960). - Removed `string.utf8.escape` in favor or a unifited, utf8-aware `string.escape`. - Add `string.unescape`. - Add `file.metadata` (#1058). - Add `predicate.activates`, `predicate.changes`, `predicate.first`, `predicate.once`, `predicate.signal` (#1075). - Add `playlist.list.reloadable` and `playlist.list` (#1133). - Make it possible to disable buffer overrun logs. - Add `accelerate` operator (#1144). - Add `video.resize`. - Add `getter.int_of_float` and `getter.float_of_int`. - Add `source.dump` (#1036). - Add `stereo` and `synth` protocols (#1036). - Add `video.add_text.ffmpeg`. - Added support for `file:///path/to/file` and `file:/path/to/file`protocols. - Added configure option to specify internal library install path (#1211). - Add support for records and methods (#1197). - Rename `unsafe.single.infallible` to `single.infallible`. - Add `list.indexed`. - Added optional support for high-resolution time and latency control on POSIX systems (#1050). - Added syntax for `for` and `while` loops (#1252). - Added a bunch of source-related methods (#1379). - Added `min` and `max` functions. - Added `lufs` to compute the LUFS loundness (#1497). - Added `interactive.harbor` in order to expose interactive variables over harbor (#1495). - Added `interactive.persistent` (as well as `interactive.save` and `interactive.load`) to make interactive variables persistent (#1495). - Added `server.harbor` (#1502). - Added `metronome`. - Added `playlist.files`. - Added `getter.is_constant`. - Added `assert`. - Added `source.available`. - Added `request.once`. - Added `file.getter`. - A better `normalize` function (with more reasonable parameters, more customisable, and written in Liquidsoap) is now provided. The old one is renamed `normalize.old`. - New and better `compress` function. The previous one was renamed `compress.old` (#868, #869). - Added `stereo.width`. - Added `file.mkdir`. - Added support for harbor's connected address in auth function and as a method (#1364). - Added `time.up`. - Added `video.cover`. - Added `video.still_frame`. - Added `request.status`. - Added `playlog` to record how long ago a song was last played (#333 and #1530). - Added `clock.log_delay` to configure how often clock catchup error messages should be printed. - Added `input.rtmp` (#1640). - Added `%ifversion` and `%else` preprocessing commands (#1682). - Added `dtmf` and `dtmf.detect` to generate and detect DTMF tones (#1796). - Added `sine.detect` to detect sines (#1796). - Added `on_air_timestamp` to request's metadata to get the request's `on_air` time as a Unix timestamp (#1871) Changed: - Implemented per-frame clock synchronization mechanism, should allow for more advanced flexibility when working with source synchronization while keeping the default safe behavior. (#1012) - Remove `active_source` type, make all output return `unit` type. (#1671) - Switch to YUV420 as internal image format, much more efficient (#848). - Use bigarrays for audio buffers (#950). - Re-implemented switch-derived operators (`fallback`, `rotate`, `random`) as scripted operators, removed `track_sensitive` argument from `rotate` and `random` as it does not have a sound meaning for them. - Added optional exit `code` to `shutdown`. - Renamed `verb` argument info `method` in `output.icecast`. - Simplified `add` behavior, also fixing an clock issue (#668). - Switch to more efficient callback API for decoders (#979). - Use system pagesize for buffer allocation (#915). - Use new Strings module in order to avoid concatenations (#984). - Native Liquidsoap implementation of list functions (#920). - Added `fallible` option to `single` operator. - Allow `input.ffmpeg` to control its own clock or delegate to CPU clock (#1628) - Reimplement `input.http` using `ffmpeg`, deprecate `input.https` in favor of unified `input.http` (#1628) - Changed `input.http` and `input.ffmpeg` `url` parameter into a string getter - Changed `request.queue` into a Liquidsoap implementation (#1013). - Removed `request.equeue`, such a feature could be re-implemented in Liquidsoap, see `request.queue`. - The `playlist` operator is now fully implemented in Liquidsoap (#1015). - Removed `playlist.once`, its behavior can be achieved by passing `"never"` to the `reload_mode` argument of `playlist` (#1015). - Removed `playlist.merged`: it is not that useful and can be achieved easily with `merge_tracks` on a `playlist` (#1015). - Deprecated `playlist.safe` (#1015). - Renamed `add_timeout` to `thread.run.recurrent`, added `thread.run` variant, renamed `exec_at` to `thread.when` and renamed `mutexify` to `thread.mutexify` (#1019). - Changed the weights of `add` to float (#1022). - Renamed `which` to `file.which`. - Change `blank()` duration semantics to mean forever only on negative values. - Get rid of numbering of universal variables (#1037). - Renamed `base64.decode`/`base64.encode` to `string.base64.decode`/`string.base64.encode`. - Vumeter is now implemented in Liquidsoap (#1103). - Change `input.http` and `input.https` `url` parameter into a string getter (#1084). - Added `path.home.unrelate`. - Use getters for arguments of `video.add_image` (#1176). - Add `x`, `y`, `width` and `height` argument to `image`, unify with `video.add_image`. - Generalize `audio_to_stereo` to video frames and those without audio. - Allow crossfading for video (#1132, #1135). - Use getters for parameters of synthesizer sources (#1036). - Renamed `empty` to `fail`. - Restored `request.dynamic` (#1213). - Requests are not typed anymore: their type is fixed at resolution time. - Deprecated `request.create.raw`, you should use `request.create` instead. - Reference setting and access are now handled as normal builtins instead of in the kernel. - Use records as return type of `http.*`, `https.*`, `rms`, `peak` and `request.queue` (#1234). - Indices of groups returned by `string.extract` are now integers instead of strings (#1240). - Generalize the `l[k]` notation so that the key `k` can be of any type (on which we know how to compare). - `ref` is not a keyword anymore: this means that `ref x` is not accepted anymore, you need to write `ref(x)` (#1254). - Renamed `file.unlink` to `file.remove`. - Deprecated `get_process_output`, `get_process_lines`, `test_process` and `system` in favor of `process.run`, `process.read`, `process.read.lines` and `process.test`. - Renamed `http_codes` to `http.codes` and put first member as integer. - Renamed `http.response` to `http.response` and `http.response.stream` to `http.response.stream`. - `localtime` and `gmtime` now return a record. - Deprecated `{eat,strip,skip,on}_blank` in favor of `blank.{eat,strip,skip,detect}`. - `http{,s}.{get,post,push}` now perform redirections if needed, which can be disabled with the `redirect` parameter (#1319). - Deprecated `gettimeofday` in favor or `time`, renamed `localtime` to `time.local` and `gmtime` to `time.utc`, and the argument of these two last functions is now optional (#1320). - Dropped optional `gavl` video converter in favor of `ffmpeg`. - Remove `persist` argument in `output.*.hls` and use nullable value for `persist_at`. - Deprecated source server commands in favor or direct call to source methods. Added wrappers for some of the old commands (#1379). - Deprecated catch-all `input` and `output` in favor or setting your desired input or output explicitly. - Implement `interactive.*` on script side (#1493). - `file.write` does not return a boolean anymore, exceptions are used for exceptional cases (#1500). - `source.dynamic` now takes a nullable argument. - Renamed `on_end` to `source.on_end`. - Changed the name of the arguments of `fallback.skip`. - Normalize ReplayGain handling: - we now use the standard `replaygain_track_gain` metadata - renamed the protocol from `replay_gain` to `replaygain` - added the `replaygain` operator to perform amplification - `normalize` now handles all channels uniformly. - First-order filter `filter.rc` now takes the cutoff frequency instead of the time constant as argument. - `file.watch` now returns unit with `unwatch` method. - Changed the interface for `bpm`: the bpm can now be retrieved using a method of the returned source instead of having a callback. - Removed `server.read*` and `server.write*`. Fixed: - Set `cloexec` on all relevant Unix calls (#1192). - Fix implementation of recursive functions (#934). - Make `blank()` source unavailable past is expected duration (#668). - Remove `video.add_text.gstreamer` shade in background (#1190). - Improve the quality of `video.add_text.gd` (#1188). - Exit with non-zero code on errors. - Fixed parsing of http URI arguments with `=` in them (#1340). - Fixed fade-out in crossfades when crossfade duration is the same as fade-out duration (#1351). - Fixed osc server not working when daemonized (#1365). - Fixed glitchy audio when using `input.harbor` (#1944) - Fixed `"tracknumber"` and `"year"` returning `0` in taglib (#1901) Removed: - LiGuidsoap, the old Liquidsoap GUI. 🪦 # 1.4.4 (27-02-2021) New: - Added `process.quote` to quote process' arguments (#1215) Changed: - Fetch mime type using curl first when available. - Make override metadata name case-sensitive in `amplify` (#1323) - Harnessed playlist file resolver to better support some combination of protocols and file resolution (#1362) Fixed: - Remote file resolution when passing URLs with spaces (#1410) - Fixed empty `{http,https}` body (#1417) - Fixed `input.harbor` shoutcast client connection (#1353) - Fixed exception reporting when output fails to start (#1372) - Fixed `random` track selection (#1468) - Fixed playlist request leak when using `reload="watch"` with `inotify` on a folder (#1451) - Deadlock when LO server thread crashes (#1409) # 1.4.3 (14-09-2020) Fixed: - Fixed exponential memory usage in clock unification algorithm (#1272). # 1.4.2 (03-05-2020) New: - Added `retry_delay` argument to `request.dynamic` (#1169). - Renamed `request.dynamic` to `request.dynamic.list` and updated its callback function type to return an array of requests, making possible to return multiple requests at once but, more importantly, to return `[]` when no next requests are available. (#1169) Changed: - Set `audio/flac` as mime for flac (#1143). - Deprecated `request.dynamic`. Fixed: - Fixed errors when installing bash-completion files (#1095) - Fixed failures in `extract-replaygain` script (#1125) - Do not crash when loading playlists using `~/path/to/..` paths. - Set `set_default_verify_paths` for SSL (#450) - Use 443 as default port for https (#1127) - Fix implementation of `rotate` (#1129). - Register audio/opus mime type for ogg decoding (#1089) - Re-encode name, genre and description in `output.icecast` using the given encoding (#1092) - Accept 24 bits per sample in %flac encoder (#1073). - Fix rare stack overflow during clock unification (#1108). - Prevent metadata inserted via `insert_metadata` from being visible to underlying sources (#1115) - Fix `cross()` fallability. - Fix decoder remaining time when decoding is done (#1159) - Fixed crash when cleaning up `output.hls` - Fix `get_process_lines` regexp logic (#1151) # 1.4.2 (03-05-2020) New: - Added `retry_delay` argument to `request.dynamic` (#1169). - Renamed `request.dynamic` to `request.dynamic.list` and updated its callback function type to return an array of requests, making possible to return multiple requests at once but, more importantly, to return `[]` when no next requests are available. (#1169) Changed: - Set `audio/flac` as mime for flac (#1143). - Deprecated `request.dynamic`. Fixed: - Fixed errors when installing bash-completion files (#1095) - Fixed failures in `extract-replaygain` script (#1125) - Do not crash when loading playlists using `~/path/to/..` paths. - Set `set_default_verify_paths` for SSL (#450) - Use 443 as default port for https (#1127) - Fix implementation of `rotate` (#1129). - Register audio/opus mime type for ogg decoding (#1089) - Re-encode name, genre and description in `output.icecast` using the given encoding (#1092) - Accept 24 bits per sample in %flac encoder (#1073). - Fix rare stack overflow during clock unification (#1108). - Prevent metadata inserted via `insert_metadata` from being visible to underlying sources (#1115) - Fix `cross()` fallability. - Fix decoder remaining time when decoding is done (#1159) - Fixed crash when cleaning up `output.hls` - Fix `get_process_lines` regexp logic (#1151) # 1.4.1 (18-02-2020) Fixed: - Fixed `fade.final` and `fade.initial` (#1009) # 1.4.0 (29-09-2019) New: - UTF8 parsing! - Added support for tuples: `x = (1,"aa",false)` (#838) - Added support for deconstructing tuples: `let (z,t,_) = x` (#838) - Added `input.{file,harbor}.hls` to read HLS stream (#59, #295, #296). - Added `output.hls` to natively stream in HLS (#758). - Added `%ffmpeg` native encoder, only for audio encoding for now (#952) - Added ffmpeg-based stream decoder, limited to mime type `application/ffmpeg` for now. - Added `(to_){string,float,int,bool}_getter` operators to handle getters in script side. - Made `p` parameter in `smooth_add` a `float` getter (#601) - Added `source.time` to get a source's clock time. - Added `max_duration` to limit a source's duration. - Added `file.temp_dir` to create temporary directories. - Added `file.{unlink,rmdir}` to remove, resp., file and directories. - Added `file.write` to write content to a file. - Added `file.read` to read contents of a file without loading all of it in memory. - Added `youtube-pl:<ID>` protocol to resolve and parse youtube playlists (or any playlist supported by `youtube-dl`) (#761) - Added `protocol.aws.endpoint` setting for the `s3://` protocol, thanks to @RecursiveGreen. (#778) - Added support for sandboxing `run_process` calls. (#785) - Added `harbor.{http,https}.static` to serve static path. - Added `log.{critical,severe,important,info,warning,debug}`. Use aliases in code as well (#800, #801, #802) - Added `sleep` function. - Added `mkavailable` function. - Added `fade.skip` function. (#804) - Added `video.external.testsrc` function. - Added `video.frame.*` and `audio.samplerate`. - Added `input.external.ffmpeg` and `output.external.ffmpeg`. - Added `output.youtube.live.ffmpeg`. - Added `output.file.hls.ffmpeg`. - Added `reopen` telnet command in `output.external`. - Added `on_frame` (#886). - Enabled external decoders in windows (#742) - Added support for bash completion. - Added `video.add_text.native`. - Added `configure.bindir` - Added `for` and `while` loop functions. - Added `list.case`. - Added `metadata.getter` and `metadata.getter.float`. - Added `string.contains`. - Added `request.uri`. - Added `{input,output}.srt` (#898) - Added `path.remove_extension`. - Added SSL read/write timeout options, use it for incoming socket connections (#932) - Added ffmpeg resampler (#947). - Added `lsl` and `lsr`. Changed: - Depends on OCaml >= 4.08.0 - Changed return type of `http.*` and `run_process` to use tuples (#838) - Better error reporting with coloring and uniform format. (#790) - Improved reporting of file, line and character during parsing errors. - Remove dynamic plugin build option. - Made `on_end` delay a float getter. - Reimplemented `fade.{in,initial,out,final}` as scripted operators. (#664) - Removed `cross`/`crossfade` operators, superseded by `smart_cross`/`smart_crossfade` - Rename `smart_cross`/`smart_crossfade` operators as `cross`/`crossfade` - Default behavior of `crossfade` is old (simple) crossfade. Use `smart=true` to enable old `smart_crossfade` behavior. - Rename `file.duration` as `request.duration` - Removed duplicate `is_directory` - Rename `{basename,dirname}` as `path.{is_directory,basename,dirname}` - Empty playlists return by scripted resolvers is now considered a failure to resolve. - Rewrite `smooth_add` to use new `mkcross` functions. - Reimplemented `open_process_full` to get a hand on `pid` and finer-grained closing workflow (#703) - Added `transition_length` to `switch`-based operators to limit transition lengths and allow garbage collection of transition sources. - SDL renders text in UTF-8. (#712) - Made `x` and `y` parameters in `video.add_text` `float` getters. (#730) - Reimplemented `extract-replaygain` using `ffmpeg`, added an optional replay gain option to the `ffmpeg2wav` protocol. Thanks to @Yamakaky for contributing on this. (#749) - The `ratio` parameter of `compress` and `limit` is a float getter. (#745) - Removed `rewrite_metadata` which had been deprecated for a while now. - Allow string getter for `harbor` HTTP responses. - Renamed `get_clock_status` to `clock.status` and `log_clocks` to `clock.log`. - Renamed `rms_window` parameter of `compress` to `window`. (#796) - Added `chop` operator. - Keep master tracks' boundaries in `mux_*` functions. (#795) - Added `new_track` optional argument to callback in `insert_metadata`. - Use getters for weights of `rotate`. (#808) - Added `conservative`, `length` and `default_duration` params to `playlist.{reloadable,once,merge}` (#818) - Renamed `input.external` into `input.external.rawaudio`, added `input.external.wav`. - Renamed `gstreamer.hls` to `output.file.hls.gstreamer`. - Raise an error when using a format (e.g. `%vorbis`, `%mp3`, ..) that is not enabled. (#857) - Set default encoders and ladspa plugins samplerate and channels to configured internal `"frame.audio.samplerate"` and `"frame.audio.channels"`. (#870) - Handle unary minus in the preprocessor instead of the parser in order to avoid duplicating the parser. (#860) - Add `filter` option to `playlist.once`. - Added a `replay_delay` option to the `pipe` operator to replay metadata and breaks after a delay instead of restart the piping process. (#885) - Add `buffer_length` telnet command to `input.harbor`. - Bumped default `length` parameter for request-based sources (`playlist`, `request.dynamic`, ..) to `40.` to assure that there always is at least one request ready to play when the current one ends. - Added support for cue in/out and fade in/out/type metadata support in `ffmpeg2wav` protocol. Rename protocol to `ffmpeg`. (#909) - `list.assoc` and `list.assoc.remove` require an ordered type as first component. - Renamed `quote` to `string.quote`, removed `process.quote` in favor or `string.quote` (#1635) - Added `phase_inversion={true/false}` to `%opus` encoder (#937) - Fixed encoders forcing frame rate and audio channels too early (#933) - Change filename to a string getter in file-based outputs. (#198) - Changed `audio.converter.samplerate.preferred` option to `audio.converter.samplerate.converters` to give a list of possible converters. Fixed: - Lack of documentation for `cross`/`crossfade` (#743) - Fixed before metadata being lost during crossfade not in conservative mode. - Correct types and default values for `random.int` (#767). - Allow changing pipeline in gstreamer functions. (#762) - Script deadlock after a long time, most likely related to old crossfade transitions (#755) - AVI export fixed. (#789) - `%external` does not stop processes anymore on each metadata. (#789) - Fixed exit getting stuck when using `input.jack` (#769) - Stop lo server on shutdown. (#820) - Fixed external process stop not detected on second and further calls (#833) - Add `seek` in operators where implementation is clear (#853) - Do not enter buffering mode between tracks in `buffer` (#836) - Fixed file descriptor leak in external processes (#865) - Fixed encoded output creating empty files from failing sources (#876) - Fixed `cue_cut` not working when used before `cross`/`crossfade` (#874) - Fixed audio glitches when seeking within a MP3 file. - Fixed `insert_metadata` logic when insert new track and metadata (#903) - Fixed `replay-gain` script default location. - Fixed audio glitches at the end of crossfade transitions. - Specify that `list.remove` removes only the first occurrence and avoid reversing the list (#922). - File descriptor leak when using openssl-based operators. - Fixed SSL read taking too long to timeout (#932) - Fixed output starting when underlying source is not available (#393) - Fixed `string.escape` also quoting its string. # 1.3.7 (09-04-2019) Changed: - Reimplemented `open_process_full` to get a hand on `pid` and finer-grained closing workflow (#703) - Better log message when request download times out (#708) - Drop `log.level` for `ffmpeg` messages to `5` Fixed: - Timeout when executing external processes (#691, #736, #726, #708) - Set buffering only when frame is partial in time_wrap.ml. Makes it work with crossfade transitions (#695) - Changed `Icy-MetaData:1` to `Icy-MetaData: 1` in HTTP source headers. Fixes some shoutcast implementations (#727) - Fixed deadlock in `input.http` source status command (#367) # 1.3.6 (23-01-2019) Fixed: - Fixed `smart_crossfade` transitions skipping data after track marks. (#683, #652) - Fixed `input.pulseaudio` parameters. - Fixed crash when copying frame content (#684) # 1.3.5 (25-12-2018) New: - Added a bunch of base mathematics primitive, `exp`, `log`, `cos`, `sine`, ... - Added `"extinf_duration"` to parsed `#EXTINF` metadata. Fixed: - Fixed inotify watch semantics (#677) - Enhanced `#EXTINF` parsing in ambiguous cases (#625) - Fixed `output.youtube.live` (#630) - Make sure server writes are synchronous (#643) - Fixed crash when loading some frei0r plugins (#435) - Fixed compilation with `osx-secure-transport` - Fixed invalid opus stream generated when no data was ever encoded (#180) # 1.3.4 (10-09-2018) New: - Added `FFMPEG` decoder using the new `ocaml-ffmpeg` API. Thanks for @gndl for the hard work there. - Added `"init.allow_root"` setting to allow running liquidsoap as root. - Added `on_track` callback for playlists. Can be used to force a reload. - Added `server.condition`, `server.wait`, `server.broadcast` and `server.signal`. Used to control server command execution. - Added `server.write`, `server.read{chars,line}` to write interactive server commands in conjunction with the above functions. (#544, #568) - Added `output.youtube.live` as a wrapper around `output.gstreamer.audio_video` to stream live to Youtube (#498) - Added metadata extraction to `ffmpeg2wav` protocol (#623). Changed: - Depends on OCaml >= 4.03.0 - Depends on camomile > 1.0.0 - Use `http{s}.head` when available to fetch remote file's mime type. (win32 port) - Better log messages for root exit and buffer override. - Switch default log to stdout. Set to file when `log.file.path` is set (#612) - Disabled Gstreamer stream decoder. - Removed asynchronous mode for `output.gstreamer.audio_video` - Reworked `smartcross` internal logic (#596) - Enabled `replaygain` on `m4a` files, thanks to @gilou (#604) - Added `encoding` parameter to `output.shoutcast` to allow alternative string encoding for metadata updates (#411) - Deprecated `rewrite_metadata` Fixed: - Decouple dyntools compilation. - Support for OCaml >= 4.06 - File descriptor leak in `output.icecast` (#548) - Fixed URL regexp for `input.https` (#593) - Multiple gstreamer fixes: - File decoder with video. - Memory leaks (#516, #511, #434, #318) - Process freeze (#608, 278) - Duppy crash on exit (#160) - Fixed audio glitches when using the `pipe` operator (#614) - Deadlock in external decoder. (#611) # 1.3.3 (14-10-2017) New: - Added `on_change` to `register` - Added IPv6 support for `input.harbor`. (#491) - Added `time`, `localtime` and `gmtime` to help with time-predicates (#481) - Added `on_start` to execute callback when liquidsoap starts. - Added `enable_external_ffmpeg_decoder` to enable ffmpeg-base external decoder. - Added `"decoder.external.{ffmpeg,ffprobe,flac,metaflac,faad,mpcdec}.path"` configuration settings. Changed: - Renamed secure transport harbor key paths to: `harbor.secure_transport.*` - Renamed secure transport I/O to: `{input,output}.harbor.secure_transport`. - Added `.wma` to `gstreamer` file decoder file extensions (#483) Fixed: - Fixed memory leak in `output.icecast` connection method. (#490) - Fixed `mutexify` - Make sure that metadata are always passed in increasing position order in `map_metadata` (#469) # 1.3.2 (02-09-2017) Changed: - Removed `kick` telnet/server command, duplicate of `stop`. - Support `replaygain` for mp3 files, thanks to @d4h3r0 (#460) - Implement `input.harbor.ssl` using SecureTransport for OSX. Fixed: - Fix scheduler loop causing high CPU usage when using Process_handler without some of the default callbacks. (#475) - Revert `wait_for` implementation to pre-`1.3.0`, using a custom `select` loop (#453) - Handle mime-type arguments in `input.harbor` streams. (#456) - Tell ocaml to use the same C compiler at build and link time. Fixes build on FreeBSD when using C++-based bindings such as taglib. (#465) - Accept any capitalization of HTTP(S) as regular HTTP URL (#464) - Fix compilation with osx-secure-transport enabled. - Fix deadlock calling logging functions from within `Gc.finalise` (#609) # 1.3.1 (28-05-2017) New: - Allow any tags allowed in `"encoder.encoder.export"` settings in vorbis streams (#418) - Allow `"audio/mp3"` mime-type for mp3 in file resolution protocol. (#451) Fixed: - Fixed `run_process`, `get_process_lines`, `get_process_output` when compiling with OCaml <= 4.03 (#437, #439) - Calls to `wait_for` while the scheduler isn't running (#442) - Revert default handling of environment in `run_process`, `get_process_lines`, `get_process_output` to passing calling process' environment by default. # 1.3.0 (27-04-2017) New: - Added support for recursive functions (#406) - Add peak and peak.stereo operators (#364) - Change `track_sensitive` parameter to a boolean getter (fixed value or anonymous function). - Add SSL support to the various harbor operators, either via openssl or OSX's SecureTransport. - Add optional "dj" and "next" metadata for Shoutcast v2, wrap "dj" value in a callback in output.shoutcast (#370, #388) - Allow partial parsing of JSON objects in `of_json`. - Generalize list.assoc to allow default values. Legacy code must be updated: `list.assoc(k,l)` -> `list.assoc(default="",k,l)` - Generalize list.hd to allow default values. Legacy code must be updated: `list.hd(l)` -> `list.hd(default="",l)` - Allow to pass a default to list.nth. Legacy code must be updated: `list.nth(l,pos)` -> `list.nth(default=<..>,l,pos)` - Added `on_offset` to execute a callback at a given offset within a source's tracks. - Added mutexify to protect a function from being called concurrently. - Added request.log to get log data associated with a request - Added `overlap_sources` to rotate between sources with overlapping tracks. - Added `replay_metadata` to `input.harbor()` - Added `\<char code>` syntax for strings (#368) - Added string.sub - Added `run_process` to run a process with optional environment and return (`stdout`,`stderr`,`exit_status`) - Added `add_playlist_parser` to register new playlist parsers - Added optional static parameter to `protocol.add` - Added file.temp to create fresh temporary filename - Added process: protocol - Reimplemented curl-based fetch process using process: - Added s3:// protocol to fetch files from AWS S3 using the AWS CLI. - Added polly: protocol to enable speech synthesis using AWS polly. Generated files are mono so make sure you use `audio_to_stereo()`. - Added youtube-dl: protocol to resolved requests using youtube-dl - Added `which()` to find an executable within the $PATH - Added `register()` to allow to register new configuration settings Changed: - Reverted default value for `--error_as_warnings` option, renamed to `--strict`. - Moved say: protocol registration to utils.liq. - Moved `get_process_lines` and `get_process_output` to utils.liq, added optional env parameter - Set `conservative=true` by default in `cross()` and `smartcross()` Deprecated (can be removed in any future version): - Dynamic plugins compilation, deprecated in favor of opam rebuild mechanism. Removed: - aac and aacplus encoders, removed in favor of fdk-aac. - dirac/schroedinger video encoder: obsolete, abandoned upstream. - `force_mpeg` option in taglib metadata decoder. Has not been used for years and allows to decouple taglib code from the mad decoder. Bugfixes: - Fix negative seek (#390) - Prevent flows metadata updata from stalling sources (#377) - Add revdns setting for telnet, set all revdns default to false (#372) - Fix icy metadata in output.harbor (#358) - Fix missing first line of headers from icy clients in `input.harbor` (#380) - Fix timestamp in some logged output (#395) - Fix crash in external (download) protocol. - Fix `fade.{in,out}` metadata handling for new fade duration and type. - Compute normalization regardless of child sources ready status in `add()` to avoid unexpected change of volume. # 1.2.1 (01-07-2016) New: - Support for https (SSL/TLS) icecast connections. - Added `http.{put,head,delete}`, `https.{get,post,head,put,delete}`. - Added `input.https`. - Added `list.mapi`. - Added `rotate.sequence`. - New `pipe()` operator to pipe audio data through an external program. - Switched to curl for request resolution/fetch. Bugfixes: - Fix metadata update for shoutcast v2 when sid <> 1 (#320). - Fix connection to `input.harbor` using the shoutcast v1 protocol (#337). # 1.2.0 (12-01-2016) New: - Websocket server (#90): this means that you can stream to harbor directly from your browser! - Add support for AIFF format (#112). - Add `url.split_args` to split the argument of an url (#123). - Add `buffer.adaptative` to cope with small network delays (#131). - Add sleeper operator to simulate network delays and test robustness (#131). - Add `stereo.left` and `stereo.right` to extract channels from a stereo stream. - Add restart command to restart liquidsoap (#135). - Add `file.contents` to read the contents of a file. - Add `filter.rc` for first-order RC filters. Enhancements: - Add support for sending OSC data (`osc.send_*`). - Native support for (some) AVI files (#256) which enables support for external video encoders (#233). - Improve rms operator (#105) to have per channel rms (#102), the ability to dynamically set window duration (#103) and multiple monitors (#104). - Icecast streaming can now use HTTP1.1 chunked encoding (#82, #107). - Add support for multiple shoutcast extensions (#216). - Fade type can be overridden by metadata in `fade.in` / `fade.out` (#64). - Allow LADSPA plugins with arbitrary number of channels (#191). - Rename shine encoder from `%mp3.fxp` to `%shine`. - fdkaac: dynamic plugin (#79), set afterburner parameter, use MPEG4 by default (#83). - Improved subtyping on lists (#125, #126). - Add native simple JSON decoder. - Better code: do not abusively use assertions (#137), issue more warnings and fix them (#162). Bugfixes: - Correctly close connection in http.get / http.post (#72). - Remove `input.lastfm` which has been broken for a while. - Lots of small bugfixes. # 1.1.1 (08-05-2013) New: - Add support for FDK-AAC, which seems to be the best AAC(+) encoder around for now. Replacement candidate for VO-AAC and AACPLUS - Add %ifencoder to check whether Liquidsoap was compiled with support for a particular encoding format. - There is now an emacs mode in scripts/liquidsoap-mode.el. - Liquidsoap can be used as a Windows service. Enhancements: - Handle more OSC types (`float`, `float_pair`, `bool`, `string`, `string_pair`) and added `osc.on_*.` - Better infrastructure for decoding images. `add_image` can now handle most image file types. - Add `random.int` as well as `min_int` and `max_int` to standard library. - Add `playlist.merge` to play a whole playlist as one track. - Add `gstreamer.hls` to play http live streams (HLS). - Add `say.program` to specify text-to-speech program in scripts. - Add "random" transition type to `video.fade.*` in order to select a random transition each time. - Add max parameter to drop data on buffer overrun with `input.gstreamer.*`. - Add `bytes_per_page` parameter to ogg encoders. - Add support for DTX in speex and opus, as well as VAD for speex. - Localize some more parsing errors in files. Bugfixes: - Avoid deadlocks in harbor. - Correctly flush lame encoder. - Correct sequence operator when there is only one source. - Handle relative URLs in http playlists. - portaudio is now an active source. - Avoid jack I/O lowering the volume. # 1.1.0 (04-03-2013) ** This version brings some new features as well as correcting bugs. ** New: - Add support for GStreamer decoding, processing and encoding (%gstreamer format, v4l webcam input is now implemented using GStreamer). - Add support for opus decoding and encoding. - Add support for the shine encoder, which can efficiently work on architectures without FPU. - Add support for automatically computing the duration of tracks in the "duration" metadata [LS-641]. It can be enabled with `set("request.metadata_decoders.duration",true)` - Add support for frei0r video effects. - Allow `%define`'d variables in encoding formats [LS-634], e.g. ``` %define BITRATE 24 %define STEREO true output.file(%mp3(bitrate = BITRATE, stereo = STEREO),"bla.mp3",s) ``` Enhancements: - Taglib now reads all metadatas (even non-standard ones). - Add a mode to automatically reload a playlist when the file was changed [LS-363,LS-523]. For instance, `s = playlist("~/Music",reload_mode="watch")`. Also, add `file.watch` to call a callback when a file is changed, with inotify support when present. - Add support for FFMpeg as video converter, which you can use with `set("video.converter.preferred", "ffmpeg")` - Add `back_time` argument to blank operators [LS-609]. - Add a metadata to override fade.final duration. - MIME is computed at most once when extracting replaygain. - Default samplerate converter is now "fast". - BPM detection (bpm) now uses a callback. - Add `clock.unify` to unify clocks of all sources from a list. - Add `source_url` metadata to `input.http` streams. - Improved error message when theora format is not supported. - Add list.filter function. - `video.add_image` can now take any image format as input. - Add `mux_stereo`. - Support for external decoders in streams. - Move bugtracker to https://github.com/savonet/liquidsoap/issues Bugfixes: - Configure is now compatible with OCaml >= 4.0 and removed support for OCaml < 3.11 [LS-625]. - Fix random memory access / memory leak when decoding AAC/MP4 files [LS-647]. - Correct resampling of wav files. - Use the length for data indicated in header for wav files. - `Argv.(0)` now returns the script name [LS-605]. - Liquidsoap should now operate fine when compiled with -noassert [LS-578]. - Better handling of inexistent MIDI channels. - Video decoder now correctly handles videos from Icecast. - Avoid visu.volume freezing Liquidsoap on shutdown. - Fix a memory leak when decoding both audio and video in ogg [LS-636]. - More efficient handling of video converters, also fixes some crashes [LS-623]. - Have the soundtouch operator preserve tags [LS-621]. - Fix remaining time estimation in cross and `smart_cross`. - Avoid deadlocks in harbor and `input.http`. - Remove leftover files in configure [LS-567]. - Handle wav files with padded fmt headers. - Handle end-of-stream when seeking mp3 with mad. # 1.0.1 (04-07-2012) ** This version brings bug fixes and minor enhancements over 1.0.0. ** Fixes: - correct type for the "flush" parameter in `output.external()` thanks to Romaric Petion for pointing it out - fix bug where MP3 encoder would discard initial ID3v2 rendering - fix bug where `smart_cross()` would stop before the end of tracks, thanks to Edward Kimber for raising the issue - load libraries in --interactive [LS-618] - update examples, notably the installed radio.liq thanks to Emery Hemingway for noticing the problem - generalize the types of `input.http()` and `input.harbor()` to allow variable content kinds, and also allow video for harbor [LS-601] - `request.equeue()` now allows to remove requests from the primary queue - fix compilation of lame dynamic plugin. New: - new values for metadata fields does not override old one anymore; use setting `request.metadata_decoders.override` to restore old behavior - `stereo_mode` and `internal_quality` parameters for %mp3 encoder - enable mad decoder for MP1 and MP2 in addition to MP3, create aliased configuration keys "decoder.file_extensions/mime_types.mad" - support for CUE sheet playlists and metadata in M3U - setting "decoder.taglib.force_mpeg" to force taglib to consider files as MPEG - scripting builtins `getenv()`, `setenv()` and `environment()` - scripting builtin `source.fallible()` - harbor is now verb-oriented, supporting GET, POST, PUT, HEAD, DELETE, OPTIONS - load DSSI plugins from environment variables and using `dssi.register()` - also display the type of the whole expression when -i is passed - generalized custom path support for facilitating standalone distributions - and as usual, various improvements in the code, log and error messages, etc. # 1.0.0 (08-10-2011) Finally, the 1.0.0 release! It brings several important fixes, but also some nice novelties. The most outstanding difference concerns `output.icecast()`: its restart and `restart_delay` parameters are gone, replaced by a new `on_stop` handler which is called on every error (failed connection or disconnection) and returns the new restart delay. The `on_error` handler receives a string describing the error which enables user-friendly reporting, adaptative delays, etc. Note that `on_error` defaults to `fun(_)->3`. which is equivalent to having restart=true, restart_delay=3. in previous versions, NOT the same as the former restart=false default. As a result, liquidsoap won't fail to startup if an initial connection attempt fails. Fixes: - LS-532,527: avoid freeze after errors in streaming threads or source initialization routines - LS-542: race condition in `playlist*()` breaking randomness - LS-489: double expiration lead to illegal queue length and freeze of request-based sources - Avoid multiple simultaneous reloading in `playlist*()`, thanks to Fabio Costa for his help on this one! - Pass charset information to icecast server to avoid encoding bugs - LS-555: timeout for icecast connection attempts - LS-559: permanent stop after disconnection on Ogg streams - LS-565: efficient and crash-free error handling in `input.http/harbor()` when the input stream has an invalid number of channels - LS-431: proper handling of duration in `blank()` avoids abusive empty tracks - LS-556: rework conversion operators, optimizations used to be unsafe & broken - LS-574: silent MIDI synthesis operators - LS-396: `drop*()`'s types reflect that they don't support variable arities - LS-442: allow comments not terminated by newline at end of file New: - `on_error` handler in `output.icecast()`, see above - New msg param in %mp3 for marking frame headers, defaults to version string - `output.file()`: new `on_close` parameter, may be used to write exact duration - %mp3.vbr/abr for variable bitrate MP3, %mp3 is now a synonym of %mp3.cbr - MP3 encoders now support ID3v2 tags - `input.http()`: new "status" command - LS-556: `mux_mono()` for adding a single audio channel into a stream - `video.add_text()` using libgd (gd4o) for environments without X Dependency on graphics can be disabled (to work around erreneous detection) - script language: add infix operator mod (patch by Fabio Costa) - `delay()` now has an "initial" parameter - LS-557: "server.timeout" setting can now be disabled by setting it to -1 - LS-532: `source.init()` for selective init with a way to handle errors, plus settings "clock.allow_streaming_errors" and "init.force_stat" (or --force-start on the command line) for easing dynamic uses of liquidsoap Enhancements: - Panic crash to avoid frozen liquidsoap after duppy crashes - Text-to-speech: festival and sox are now only runtime dependencies - LS-475,516: better support for dynamic URL change in `input.http()` - LS-484: display user-friendly error messages in interactive mode - LS-308: use seconds internally in request sources, avoid overflow and display more user-friendly debug messages - Cleanup `visu.volume()` and `video.vis_volume()` - LS-573: replace " " by "\_" in identifiers to make them valid in the server - Script syntax: unary minus now usable without parenthesis after semicolon - Two generic queues by default, to avoid deadlocks in advanced situations - Documentation, build & install system, etc. # 1.0.0 beta3 (05-08-2011) - Feature: Added `of_json` to parse json data. Depends on json-wheel. - Feature: Added file.exists and is_directory. - Feature: Added timeout options for: telnet, harbor (server), input.icecast - Enhancement: finer-grained timeout detection for `input.harbor` and `input.http` - Fix: deadlock when disconnecting harbor users through server/telnet command. - Fix: dynlink detection in native mode with old versions of ocaml - Fix: deadlock when an exception is raised during startup while the clock is owned by a source (e.g. `input.alsa`). See LS-527 for more details. # 1.0.0 beta2.1 (07-07-2011) - Fix: `playlist.safe()` was unusable in beta2, as a side effect of removing duplicate "timeout" parameter in `playlist()`. - Minor enhancements to documentation, settings and reference. # 1.0.0 beta2 (04-07-2011) This release introduces lots of fixes and cleanup, but also some new features. Major novelties: support for fast seeking and cue points, FLAC and improved AAC+ support, introduction of the liquidsoap yellowpages "flows", plugin support and improved messages for scripting errors Compatibility warning: `insert_metadata` has changed, and `clock.assign_new()` should be used instead of `clock()` to avoid some of the new static checks Decoders: - New support for seeking and fast computation of durations in most formats - New decoders: FLAC (native & Ogg) and images using Camlimages - Fixes in Ogg decoding: LS-515 (loss of data) and LS-537 (segfault). - Fix LS-337: periodical failures when decoding AAC(+) streams - AAC(+): use new ocaml-faad with builtin mp4ff, easier to build - New detection mechanism mixing extensions and MIME types (when available), with corresponding settings `decoder.file_extensions.<format>` and `decoder.mime_types.<format>`. - Decoder order can be user-defined thanks to new settings "decoder.file_decoders", "decoder.stream_decoders" and "decoder.metadata_decoders". - Indicate which decoder is used in the "decoder" metadata - More helpful log for various errors - Fix segfault with SdlImage image decoder Encoders: - New FLAC encoders %flac (native) and %ogg(%flac) - New AAC+ 2.0 and vo-aacenc - New settings to theora: keyframes make files much smaller! - New settings for WAV encoding: headerless, samplesize. - Fix segfaults with ocaml-aacplus - Enhancement LS-441: filter metadata before encoding, based on the "encoder.metadata.export" setting. - Rework infrastructure of encoded outputs to fit all formats, outputs and styles of metadata handling, file reopening (#386) Harbor: - New: `output.harbor()` which acts as a mini icecast server, accepting listeners directly. Encoding is shared among users, and is only performed when needed. - New: ability to register HTTP GET/POST handlers to create simpler web services, using `harbor.http.register/remove()`. - Make all settings local: port, user and password can be set independently for each `input.harbor()` source - New: "metadata_charset" and "icy_metadata_charset" in `input.harbor()` - Fix: race condition possibly leading to abusive "source taken" (LS-500) Icecast: - Add support for streaming native flac, only works when streaming to `input.harbor()`, not supported by actual Icecast servers - Fix bugs in ICY protocol support (header parsing, user name) - Use ICY metadata updates when streaming AAC(+) - New: "encoding" parameter for `output.icecast()`, used for recoding metadata Defaults to "latin1" with shoutcast servers - New: `icy.update_metadata()` function for manual updates - Enhance default "song" metadata, avoiding " - " when unnecessary (#467) Input/output: - New experimental `input.v4l/v4l2()` for webcams - New experimental `input/output.udp()` for unchecked UDP streaming, available with most formats (at your own risk) - Restore `output.pipe.external()`, now called `output.external()` - New parameters for most outputs and inputs (start, on_start, on_stop, fallible); cleanup and uniformize implementations (LS-365) - New ALSA settings alsa.alsa_buffer, alsa.buffer_length and alsa.periods Setting periods=0 allows to not attempt to set the number periods, which is impossible on some devices - New preference order in `input/output.preferred()`: pulseaudio, portaudio, oss, alsa, ao, dummy Operators: - New: support for cue points with `cue_cut()` - Change `insert.metadata()` which is now more script friendly, returning an insertion function rather than register a server command. The old functionality is available as `server.insert_metadata()`. - New: `rms()` operator for getting RMS of a stream, and `server.rms()` which makes this information available as a server command. - New: `track_sensitive` mode for blank detection operators - New: `playlist.reloadable()` for playing a list once, with a command for restarting it. - Remove `id.*()` which can be replaced by type annotations Scripting API: - New: OSC support through `osc.bool()`, `osc.float()` and `osc.float_pair()` - New: JSON export `json_of()` - New: `http.get()` and `http.post()` - New: `url.encode/decode()`, `base64.encode/decode()` - New: `string.recode()` for charset conversions using camomile - New: `notify_metadata()` and `osd_metadata()`, suitable for use with `on_track()` and `on_metadata()` - New: `request.metadata()` for getting a request's metadata - New: `string.length()` - Enhance `log_clocks()` with parameter for delaying startup - Enhance `get_clock_status()` with "uptime" reference time Server interface: - Print the playlist's URI when calling `<playlist>.uri` without an argument. - Enhance `<queue>.ignore` now works also in the primary queue - New command for changing the URL of an `input.http()`, ref #466. The command is `<id>.url` and it needs a restart (`<id>.stop`, then start) to take effect. - Fixed double registration of server commands which resulted in broken "help" command (LS-521) Script language: - Option "-i" doesn't show types for pervasives anymore - Pretty printing of types (LS-474) with indentation, also used in the documentation - Enhanced type errors (LS-459): no more traces, only the relevant part of types is displayed, plus a few special friendly messages. - Enhanced static checks (LS-123) thanks to the introduction of the `active_source` subtype, for unused variables and ignored values This should avoid common errors, help troubleshooting If needed, advanced users can work around errors using --check-lib and --errors-as-warnings General: - Do not attempt to install "daemon" files if user/group have not been set, unless forced using "make INSTALL_DAEMON= install" - Fix several core "source protocol" bugs, causing assert failures and other crashes: LS-460 (source becomes not ready without operator knowing) #403 (information about being ready is not precise enough) - Fix incorrect image accesses (LS-430) by introducing a safer VFrame API Applies to most video operators ( `video.fade()`, `video.text()`, effects...) - Cleanup resource (de)allocation, which is becoming critical with dynamic reconfigurations (e.g., dynamic output creation, `source.dynamic()`) Enforce that server commands are always deallocated (LS-495) Attempt to stop sources when initialization fails, so they cleanup as much as possible (LS-503) Avoid deadlocks upon crashes in IoRing-based operators Share code for stoppable feeding threads, use it in `input.harbor()` Avoid useless initialization of SDL systems - Dynamic loading of lame and aacplus libraries, making it possible to ship them as separate binary packages. This is particularly useless for non-free libraries and for those that depend on X (e.g., SDL) which is often undesirable on servers - Support for separate compilation of most optional features as plugins Use `--enable-<feature>-dynamic-plugin` in ./configure and --dynamic-plugins-dir in liquidsoap - Better Win32 support, more version checks, separate compilation of C files and compliance with Debian's OCaml standards - Externalize many audio and video functions in the new ocaml-mm library Factorize and optimize various conversions - Rewrite harbor and server code using the new Duppy monad, introducing camlp4 into the build system - New regression tests (make test) - More user-friendly exception printing - Avoid problems preventing backtrace printing Miscellaneous: - Update liguidsoap, make microphone input optional (LS-496) - Do not crash upon charset-recoding failures [LS-473] - Fix in `source.dynamic()`: missing source re-selection [LS-354] - Avoid deadlock on startup in daemon mode [LS-229] - Fixes in LADSPA and SDL causing early freezing of Frame parameters. - Fullscreen mode for `output.sdl()` - Fix: SIGPIPE used to cause crashes (LS-53,LS-287) - Fix: `video.volume()` could crash upon some end-of-track situationos - Fix: properly escape filenames in external file duration methods - Rework timeout management for various sockets (notably icecast & harbor) Set nodelay, remove `TCP_*TIMEOUT` [LS-508,LS-509] # 1.0.0 beta1 (06-09-2010) This beta version introduces two major new features: heterogeneous stream types and clocks. New: - Different sources can carry different types of content. - Encoding formats have been introduced to help infer stream content types. This brings static checking for bounds in encoding parameters. - Introduce conversions between stream contents (mono, stereo, drop audio, video, etc) and muxing. - Allow explicit type annotations in scripts. - Introduce clocks, cleanly allowing for the coexistence of different time flows, and avoiding inconsistencies that can result from it. Soundcard I/O and cross-based operators notably make use of it. - Remove root.sync, replaced by attaching a source s to clock(sync=false,s). - Enable dynamic source creation and `source.shutdown()`. - Extend and adapt MIDI and video operators. - Introduce purely metadata streams (audio=video=midi=0 channels) and metadata stream decoder. - Support WAV streams in `input.http/harbor()`. - Introduce static image decoder using SDL image. - Remove bound of request identifies (RID). - Experimental: `source.dynamic()` for advanced dangerous hacking. - Make path relative to script in `%include "PATH"`, and introduce `%include <...>` where path is relative to liquidsoap library directory. - Add `channels_matrix` parameter to `output.ao()`. - Add `on_(dis)connect` hooks in `input.harbor()`. Cleanup and fixes: - Lots of cleanup and fixes as with all major code rewriting. - Optimize video and stabilize it a little bit... still not perfect. - Rewrite stream and file decoding API, as well as file format detection. - Enhance shutdown and error message reporting, notably for icecast and request-based sources. - Avoid quasi-infinite loop in failed request resolving. # 0.9.3 (04-09-2010) This release is a bugfix of the latest snapshot (0.9.2). It will be the last bugfix before 1.0. Bugs fixed: - Add "audio/mpegurl" to the list of mime-type for basic playlist parsing. - Decode arguments passed to `input.harbor`. - Use Camomile framework to try to recode arguments and user/password passed to `input.harbor`. - Support Theora 1.1 API via ocaml-theora 0.2.0. - Fixed `input.lastfm`. - Fixed SDL output. - Allow magic file type detection to follow symlinks. # 0.9.2 (29-10-2009) This release is a SNAPSHOT of upcoming features. It also contains several important bugfixes. As a snapshot, it contains experimental or unpolished features, and also breaks compatibility with previous versions. You should in particular notice the two "New" items below: - `random(strict=true)` is now called `rotate()`; - request sources (`playlists`, `request.*`) have a new queuing behavior, check the doc (request-sources.html) or revert to conservative=true. Bugs fixed: - Ogg encoder now muxes pages according to their ending time. - Support "ogg/audio" and "ogg/video" mime types for HTTP ogg streams. - Changed external encoder's "restart_encoder" to the more relevant "restart_on_new_track". Added optional "restart_after_delay" to restart the encoder after some delay. Also completely rewrite stop mechanism. Stop operation now waits for the encoding process to finish, allowing proper restart_on_new_track like for ogg encoded data. - Factorized buffered I/O code. Also added a blocking API, which avoids "no available frame" and "reader not ready" messages and audio glitches. It enforces that `root.sync` be deactivated for these sources, such that synchronization is done by the source. (#203) - Factorized file decoding code. - Fixed reversed order when parsing playlists using `playlist.parse()`. - Avoid bad crashes when resources lack, e.g. no more memory. - Tighten and enforce the inter-source protocol. - All outputs: fix the autostart server command. With the former code, a modification of the autostart parameter was only taken into account one start/stop cycle later. - `on_blank()`: fix a bug that prevented the first call to on_noise. - Fixed estimated remaining samples on ogg files, fixes issues with operators relying on this value, in particular `crossfade()` and request-based sources when operating in non-conservative mode. - Fixed socket descriptor leak in `input.http`. (#318) - Fixed deadlock at init when an exception was raised at wake_up phase. (#319) - Fix `delay()` which could sometimes incorrectly declare itself ready, and thus try to get some data from its unavailable input source. - Bug in queue duration estimation led to infinite feeding of the queue, until all request IDs are taken. - Several fixes enforcing clean (non-deadlocking) sleep/shutdown. New: - The operator `rotate()` replaces random(strict=true), and `random()` does not have a strict parameter anymore. - Switch to new behaviour in request-based sources. Use conservative=true to get to the old behaviour. - `on_blank()`: provide an `on_noise` handler. - `playlist*()`: add server commands for reloading the playlist and changing its URI. - Fallible outputs: all outputs now have a fallible mode, in which they accept fallible sources, and automatically start/stop when the child fails to stream. - EXPERIMENTAL ogg/dirac encoding support. - `output.*.aacplus()`: native outputs encoding in AAC+ using ocaml-aacplus. - Switched to a custom implementation of the various shout protocols. It notably allows arbitrary content-type settings which enables AAC+ streaming. Enabled wrappers for external encoders for AAC+ format aacplusenc (#220 and #136). Also added custom IRC, AIM and ICQ headers to shoutcast wrappers (#192). - Harbor now supports Shoutcast/ICY headers properly. This allows in particular the use of any supported data format for the source client. - Harbor sources now also consider "song" field when updating metadata. - Added built-in support for m4a audio files and metadata. Made external support optional through enable_faad. - Added optional resampling for output.external operators (#273). - Added optional host and port parameters for audioscrobbling submissions. Allows to use alternate systems, such as libre.fm of jamendo's compatibility API. Added nowplaying submission, and various wrappers, including a full submission process (now playing at beginning, submit some times before the end). - New variables in the script language: `liquidsoap.version` and `configure.{libdir,pidfile,logdir}`. - Added built-in support for replay_gain, through the replay_gain protocol (enabled by default) and the replay gain metadata resolver (to be enabled using `enable_replaygain_metadata()`). (#103 & #317) - Reverse DNS operations can be disabled using settings keys `server.telnet.reverse_dns` and `harbor.reverse_dns`. Experimental: - MIDI: file decoding, synthesis, virtual keyboard. Removed: - RTP input and output. - Removed decoders using ocaml-natty. Slow, unmaintained and superseded by the mplayer decoder. # 0.9.1 (18-06-2009) Bugs fixed: - Fixed request task not ending properly for request-driven sources (playlist, request.queue, request.equeue). Fixes a problem reported when reloading an empty playlist multiple times. (#269) - Fixed math.h usage in rgb_c.c. - Fixed append operator. (#284) - Fixed OSS compilation for non-linux systems. - Disconnect idle harbor sources after timeout has expired. Thanks to Roman Savrulin for reporting and fixing ! - Taglib metadata resolver is only used on files decoded by the MP3 decoder. New: - Get a node's striping status when stripping blank with `strip_blank` (#260). - `on_connect` function for `input.harbor` now receives the list of headers given by the connected source (#266). - Added `on_end` operator, to execute a handler when a track ends. - Added estimated remaining time in the queue length for request-driven sources (`request.{equeue,queue}`, `playlist`). This allows these sources to prepare less files in advance. In particular, primary queue may only contain the file currently played. Default behaviour has been set to the old behaviour, and a conservative option has been added to switch to the new behaviour. New behavivour will be the default for the next release. fixes #169, references #146 # 0.9.0 (01-03-2009) Bugs fixed: - Fixed byte swapping function. - Fixed unix server socket failure (#160). - Fixed mp3 audio glitches when decoding files with picture id3v2 tags using ocaml-mad (#162). - Fixed liquidsoap crash on weird telnet and harbor input (#164). - Fixed `request.queue()` not considering initial queue on wake-up (#196). - Fixed source leak in `append()`. - Fixed `after_output` propagation in the transitions of switches (#208). - Fixed compilation for ocaml 3.11 (#216). - Fixed (again) Vorbis mono output (#211). - Avoid crashing on broken symlinks when parsing directories (#154). - Use random temporary file in liGuidsoap. - Fixed liGuidsoap to use the (not so) new metadata field initial_uri. - Fix bugs in the life cycle (sleep/wake_up) of queued request sources, which made `say_metadata` unfunctional. - Remove buggy unbuffered `{input,output}.jack`. (#231) - Fixed audio information sent to icecast. (#226) - Fixed global reset starting inactive sources. (#227) - Fixed shoutcast source initial answer in harbor. (#254) - Fixed frame and metadata duplication in cross operators. (#257) - Fixed several sources (outputs, external streams) that were not going to sleep correctly. Changes: - Warning: `interactive_float()` is now `interactive.float()`. New: - Compatible with OCaml 3.09. - Faster shutdown. - Rewrote ogg decoding, for files and streams. - Support for ogg skeletons, currently only used for theora. - Cleanup icecast class hierarchy and restart mechanism, especially with respect to the encoder. - Support for breaks and metadata in generators. As a result, `input.http()` and `input.harbor()` now fully support them. See new_track_on_metadata parameters there. - Switch operators (fallback,random and switch) can now replay the metadata of a source that has been left in the middle of a track. - New `force_mime` parameter for `input.http()`. - New `insert_missing` parameter for `append()`. - Multi-line strings in liq scripts, with a caml-like syntax. - Slight modification of the scripting syntax, handling unary minus. - Added `user_agent` parameter for `input.http` and `input.lastfm` - Added speex support for files and HTTP streams. - Added EXPERIMENTAL support for AU, AIFF and NSV mp3 audio files using ocaml-natty. - Added "prefix" parameter to the playlist operators. Allows to prefix each uri in order to force resolution through a specific protocol, like replaygain: for instance. (#166) - Support for external processes as audio encoder: - Added output.icecast.lame to output to icecast using the lame binary. - Added output.icecast.flac to output to icecast using the flac binary. - Full generic support awaits some changes in libshout. - Support for external processes as audio stream decoder: - Added `input.mplayer` to stream data coming from mplayer. - Support for external processes as audio file decoder: - Added support for flac audio files using the flac binary. - Added support for m4a files using the faad binary. - Added optional support for mplayer, enabled with `enable_mplayer()`. - Support for external metadata decoders. - Support for generic authentication function in harbor, also available for ICY (shoutcast) clients. - Added optional support for the samplerate library, and dynamic configuration of resampling routine. - Added `lag()` operator, which delays a stream by a constant time. - Initial support for PulseAudio. - Added experimental support for `{input,output}.marshal`, allowing raw communication between various liquidsoap instances. - Added optional alternatives to store buffered audio data: - raw: in memory, s16le format - disk: on disk, several big files - disk_manyfiles: on disk, a lot of small files See documentation for more details. - Added EXPERIMENTAL video support: - Support for ogg/theora file input. - Support for ogg/theora file and icecast output - Support for SDL output. - Optional support for ocaml-gavl as video converter. - Support for video in _some_ existing operators, including switches, `add()`, metadata/track manipulations. - Added operators: `video.fade.*`, `video.fill`, `video.greyscale`, `video.image`, `video.invert`, `video.lomo`, `video.noise`, `video.opacity`, `video.opacity.blur`, `video.rotate`, `video.scale`, `video.sepia`, `video.text`, `video.tile`, `video.transparent`, `video.volume`. # 0.3.8.1 (11-08-2008) - Fixed metadata propagation during default transition in smart_crossfade - Changed transition evaluation order in smart_crossfade - Fixed transition function in smart_crossfade # 0.3.8 (30-07-2008) Bugs fixed: - Vorbis mono output is now working - Fixed parameter parameter description in the documentation - Propagate new delay in add_timeout - Fixed inter-thread mutex lock/unlock in playlist.ml - Fixed "next" playlist command - Fixed race conditions in request_source.ml feeding task - cross/smartcross: raise the default for inhibition as setting it to exactly duration is not enough - Don't fail when $HOME is not set - Fixed metadata update in `input.harbor` with icecast2 source protocol - Fixed shutdown function. Fixes #153 - Fixed `input.oss`. Liquidsoap now works with OSS4 ! Fixes #158 New: - Added a hook to execute a function when playlist.once ends - Enhanced smart_crossfade - Added string.case and string.capitalize - New "exec_at" operator, to execute a function depending on a given predicate - Added script example in the documentation to listen to radio Nova and get the metadata from a web page. - Changed parameters name in fallback.skip to reflect who are the fallback and input source. - Added a dump file parameter to `input.harbor`, for debugging purpose. - Added an auth function parameter to `input.harbor` for custom authentifications. Fixes #157 - Added "primary_queue" and "secondary_queue" commands to request.queue and request.equeue sources. Also set the metadata "queue" to either "primary" or "secondary" for each request in the queue. Documented it too. - Insert latest metadata for a node of a switch source when switching back to it in a middle of a track. - Added a 'conservative' parameter to cross, smilar to the one in smartcross. Internal: - Enhanced liqi documentation parser to build the website. # 0.3.7 (03-06-2008) Bugs fixed: - Now works on FreeBSD and hopefully other unices that are stricter than Linux about Mutex usage. - `input.http()` now has a `bind_address` parameter. - Harbor socket now has a timeout for lost connections. - `smartcross()` is now more compliant with the inter-sources protocol, fixes several "get frame didn't change the buffer" bugs. - Ogg packeting bugs. - Buffering policy in `input.http/harbor()`. - No "." in IDs and labels. - Resources: FD leaks, useless threads (threads leaks?) in `input.http()`. - `fade.out()` used to run into infinite loops when the delay was 0. New: - New documentation system and website. - Self-documenting server with a more helpful "help" command. - Moved to duppy: less threads, lighter load, and an configurable scheduler. - Moved to Taglib for more reliable access to MP3 metadata. - MIME types, notably for playlists and MP3 files. - New Jack support. The old one has been renamed to `in/output.jack.legacy()`. - Harbor: per-mount passwords and the stop command to kick a source client. - Official Last.FM client. - Metadata is no more punctual but interval-based, which suppresses some surprising behaviours. - Perfected daemon behaviour. - All `output.file.*()` now have the features that used to be only in `output.file.vorbis()`, notable re-opening. Added %w to the strftime-like format codes allowed in their filename parameter. - Add `clear_metadata()` and `map_metadata()`. Now, `rewrite_metadata()` is a simple specialization of `map_metadata()`, written in utils.liq. - Dynamic amplification factor in `amplify()`, e.g. useful for replay gain. - Lots of new functions in the scripting API: for lists, requests, system interaction, shutdown, command-line parsing, scripted server commands, etc. As always: - code cleanup, style, etc. # 0.3.6 (17-12-2007) Bugfix release: - Close Http socket - Add http socket timeout - Close playlist file after reading its content - Fix http redirect support for lastfm files - Fix file open leak in camomile support - Fix playlist uri ending with "/" # 0.3.5 (08-11-2007) Bugfix release: - Fixed #57: scpls and mpegurl playlist parsing - Fixed #46: Late cross-scripts bindings # 0.3.4 (25-09-2007) Notation: "-" stands for a change, "+" for an addition. - Language - Support for polymorphism, subtyping and basic ad-hoc polymorphism, which allows a much simpler API, notably for maths and serialization. * Added `interactive_*()` for mutable values. - The right syntax for settings is now set("var", value) and can be used anywhere in the scripts. - The volume parameters of most operators are now in dB. - Many builtin functions added. - Nicer type error messages. - Sources - Added `input.lastfm()` to relay last.fm streams. - Added `input.harbor()` to received Icecast2 source streams. - Added `noise()` to generate white noise * Reimplemented playlist support, added various xml and text formats. * Added mpd protocol to find files using mpd. - Operators - New effects: `compress()`, `flanger()`, `pan()`. - New filters: `filter.fir.*()`, `filter.iir.*()`, `filter.biquad.*()`, `comb()`. - Added support for LADSPA effects. - Added `eat_blank()` to remove blanks. - Outputs - Added non-default restart option for `output.icecast.*()`. - Added the possibility to tweak some settings at runtime. - Split `output.icecast.vorbis()` into `output.icecast.vorbis.*()` to distinguish between encoding modes -- and similarly for output.file.vorbis and mp3. - Better handling of Icecast disconnections. - IO - Added portaudio support. * Jack support is now somewhat working. - As usual, lots of bug fixes, careful polishing & much more... # 0.3.3 (06-06-2007) - Major cleanup of the core stream representation; moved to float arrays, removing several back-and-forth conversions and enhancing the perfs a lot; reviewed all sources and operators, made many minor enhancements btw. - Lots of sound processing operators: compand, compress, normalize, pitch, bpm, soundtouch, saw, square, etc. Add more shapes to `fade.*()`. - New track processing operators: insert_metadata, on_track. - Smart cross: allows to select a transition based on the volumes around the end-of-track. - Support for AAC encoding/decoding. - Several fixes to output.icecast.mp3 in order to support shoutcast servers. - Automatic format recognition for `input.http()`, support for playlists. - OSS I/O. - Unbuffered ALSA I/O for low latency. - Server interface via UNIX domain sockets. - Better output.file.vorbis with support for re-opening the file, appending, interpolate strftime format codes, etc. - Add pre-processing and math primitives to the language, new `_[_]` notation for `assoc()`, ruby-style anti-quotation `("..#{..}..")`, `add_timeout()`, `execute()`, `log()`... - Ability to tweak the internal PCM stream format. - Classify sources and operators in categories for more structured doc. - Started a few visualization operators, text and graphics based. - Several bug fixes: request leaks, sine frequency, switch, etc. # 0.3.2 (16-03-2007) - New portable output to speakers using `libao()`. - Updated liGuidsoap to use it until ALSA gets enhanced. - Implemented a decent estimation of the remaining time in a track. - Added the `cross()` operator allowing cross-fading. - Generalized `say_metadata()` into `append()` and `prepend()`. - Per-track settings for `cross()`, `fade.*()`, `prepend()` and `append()` using requests' metadatas. - Implemented `input.http.mp3()`, including support for icy metadata. - New `pipe()` operator which allows one to filter the raw audio through an external program. However, sox and other common tools aren't suitable for that because they don't flush their output often enough. - New `on_blank()` operator for calling a callback on excessive blanks. - Restart outputs on insane latencies. - Type checkings for settings. - Setting for not starting the internal telnet server. - Now handles old and new versions of Camomile correctly. - Internal fixes and polishing (switches' cached selection, empty tracks..) # 0.3.1 (17-11-2006) - More standards-compliant tarball - Generate doc with locally built liquidsoap - Try to cope with ill-formed mp3 - Updated for newer versions of Camomile - So-called "strict" random-mode # 0.3.0 (27-08-2006) - Many minor and major fixes at every level! - Conversion of metadata to UTF8. - Got rid of too many threads by scheduling all download tasks in a single thread, and handling all of the server's clients in another single thread. - Simplified the time interval syntax and integrated it to the script language. - New protocol: Wget for FTP, HTTP and HTTPS. - Ability to define a new protocol from the script, typically using an external app/script for resolution, such as bubble. - Ability to use an external app/script for dynamically creating requests. - New `on_metadata` operator for performing arbitrary actions (typically a call to an external script) when metadata packets occur in a stream. - MP3 encoding, to file or shout. - API renamings and simplification. - Supports transition, as functions of type (source,source) -> source in all switching operators: schedule, random, fallback. - Restart icecast2 outputs on failures. - Major changes to the scripting language which is more uniform and flexible, has functions, a helpful (?) type inference, and a simple Ruby-like syntax. - Timing constraints and synchronization are managed by Root in a centralized way, no more by the many outputs which need it. - Audio decoding is no more based on file extensions, but on the existence of a valid decoder. - Added the equeue operators which allows interactive building of playlists, supporting insertion and moving of queued requests -- queue only allows you to push requests and cancel queued requests. - A Python-Gtk GUI for controlling operators, or more specifically as a console for creating your live show -- to be updated, still unstable. - Alsa input and output. - Blank detection, automatically skips on too long blanks, or strip them. - Http ogg/vorbis relay, the way to relay live shows. - Interactive mixer. - The request system was mostly rewritten to really fulfill its specification. - The server is no more associated to the queue operator but is now something external, in which all operators can plug commands. This much more logical design lead to many more interactive controls. The syntax of command outputs was also simplified for easier automated processing. - Dynamic loading of plugins. - Outputs are now operators. It makes it possible to output different streams in a single instance of Liquidsoap, which RadioPi needed. As a consequence we removed the restriction that one source must have at most one father, without any extra burden for the user. # 0.2.0 (20-04-2005) - Proper initial release. # 0.1.0 (2004) - Release for academic demonstration, not functional. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/CITATION.cff�����������������������������������������������������������������������0000664�0000000�0000000�00000001455�15132732333�0015572�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������cff-version: 1.2.0 message: "In order to reference this software, please cite it as below." abstract: "Liquidsoap is a powerful and flexible language for describing your streams. It offers a rich collection of operators that you can combine at will, giving you more power than you need for creating or transforming streams. But liquidsoap is still very light and easy to use, in the Unix tradition of simple strong components working together." authors: - family-names: "Beauxis" given-names: "Romain" - family-names: "Mimram" given-names: "Samuel" orcid: "https://orcid.org/0000-0002-0767-2569" title: "Liquidsoap" version: 2.2.2 doi: 10.5281/zenodo.1234 date-released: 2023-11-04 url: "https://www.liquidsoap.info/" repository-code: "https://github.com/savonet/liquidsoap" license: GPL-2.0 type: software �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/COPYING����������������������������������������������������������������������������0000664�0000000�0000000�00000043134�15132732333�0014733�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) year 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. <signature of Ty Coon>, 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. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/Makefile���������������������������������������������������������������������������0000664�0000000�0000000�00000000060�15132732333�0015327�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������all: build build install clean test: @dune $@ ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/README�����������������������������������������������������������������������������0000777�0000000�0000000�00000000000�15132732333�0016025�2README.md�������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/README.md��������������������������������������������������������������������������0000664�0000000�0000000�00000022734�15132732333�0015162�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Liquidsoap Liquidsoap is a swiss-army knife for multimedia streaming, notably used for netradios and webtvs. It has tons of features, it's free and it's open-source! Liquidsoap is a powerful and flexible language for describing your streams. It offers a rich collection of operators that you can combine to create and transform streams. Liquidsoap is very light and easy to use, in the Unix tradition of simple strong components working together. Copyright 2003-2025 Savonet team [![GPL license](https://img.shields.io/github/license/savonet/liquidsoap)](https://github.com/savonet/liquidsoap/blob/master/COPYING) ![CI](https://github.com/savonet/liquidsoap/workflows/CI/badge.svg) [![GitHub release](https://img.shields.io/github/release/savonet/liquidsoap.svg)](https://GitHub.com/savonet/liquidsoap/releases/) [![Install with Opam!](https://img.shields.io/badge/Install%20with-Opam-1abc9c.svg)](http://opam.ocaml.org/packages/liquidsoap/) [![Chat on Discord!](https://img.shields.io/badge/Chat%20on-Discord-5865f2.svg)](http://chat.liquidsoap.info/) [![](https://img.shields.io/badge/Gurubase-Ask%20Liquidsoap%20Guru-006BFF)](https://gurubase.io/g/liquidsoap) [![Built with Depot](https://depot.dev/badges/built-with-depot.svg)](https://depot.dev/) | | | | ------------------------- | ----------------------------------------------------------------------- | | Homepage | http://liquidsoap.info | | Discord Chat | http://chat.liquidsoap.info | | Blog | https://www.liquidsoap.info/blog/ | | Bug reports | https://github.com/savonet/liquidsoap/issues | | User questions | https://github.com/savonet/liquidsoap/discussions | | IRC (deprecated) | #savonet on [irc.libera.chat](https://libera.chat/) (w/ discord bridge) | | Mailing list (deprecated) | savonet-users@lists.sourceforge.net | ## Installation See the instructions [here](https://www.liquidsoap.info/doc.html?path=install.html). ## Release Details Current release status by version: | Branch | Latest release | Supported | Rolling Release | | --------|----------------|-----------|-----------------| | `2.4.x` | [2.4.1](https://github.com/savonet/liquidsoap/releases/tag/v2.4.1) (docker: `savonet/liquidsoap:v2.4.1`)| ✅ | [2.4.x](https://github.com/savonet/liquidsoap/releases/tag/rolling-release-v2.4.x) (docker: `savonet/liquidsoap:rolling-release-v2.4.x` | | `2.3.x` |[2.3.3](https://github.com/savonet/liquidsoap/releases/tag/v2.3.3) (docker: `savonet/liquidsoap:v2.3.3`) | ❌ | [2.3.x](https://github.com/savonet/liquidsoap/releases/tag/rolling-release-v2.3.x) (docker: `savonet/liquidsoap:rolling-release-v2.3.x` | ### Versions Liquidsoap releases follow a semantic versioning as follows: ``` <major_version>.<minor_version>.<bugfix_version> ``` Where: - `major_version` is bumped when there are major changes, i.e. changes in the paradigm, major implementation change etc. Versions with different major versions **are** incompatible - `minor_version` is bumped when there are minor changes, i.e. new operators, renaming, new modules etc. Version with different minor versions **may be** incompatible - `bugfix_version` is bumped when a new bugfix version is published. Versions with only bugfix version changes **should be** compatible Please note that liquidsoap is a complex framework with a lot of operators and advanced implementations. For this reason, it is possible that a bugfix actually fixes the behavior of an operator the way it was intended to be and may break scripts that previously relied on incorrect implementations. Therefore, we **strongly** recommend maintaining a `staging` environment that makes it possible to test new versions before using them in production. In this context, the semantic versioning above should guide you in knowing how much scrutiny you should put into a new release before validating it in your staging environment. ### Assets Release assets are provided at: https://github.com/savonet/liquidsoap/releases. Published, versioned releases are available using their published tag, i.e. `vx.y.z`. We also provide **rolling releases**. A rolling release is a snapshot of a current, unpublished release. It can be a future stable release or a future bugfix release for a given major/minor version. For both types of releases, we reserve the right to update, delete and add assets to the release at any time. If you are looking for permanent links to release assets, you should grab them from https://github.com/savonet/liquidsoap-release-assets/releases, which reflects all our releases but whose artifacts are never modified/deleted. ### Supported OSes for pre-built binary assets We provide the pre-built binary assets in the form of native packages (or zip file for windows) and docker images. We generally try to support the latest LTS release of each OS as well as their most recent release. Here's a table: | OS | Supported Releases | Binary assets | Architectures | Notes | | ------- | ---------------------------------------------------------- | ------------------------------ | ------------------- | ----------------------------------------------------------------------------- | | Debian | stable (currently: `trixie`), testing (currently: `forky`) | `.deb` packages, docker images | `amd64`, `arm64` | `.deb` packages require [deb-multimedia.org](https://www.deb-multimedia.org/) | | Ubuntu | LTS (currently: `noble`), latest (currently: `plucky`) | `.deb` packages, docker images | `amd64`, `arm64` | | | Alpine | `edge` | `.apk` packages, docker images | `x86_64`, `aarch64` | | | Windows | N/A | `.zip` archive | Windows 64 | | ### Supported FFmpeg versions We support the last two major releases of FFmpeg. Currently, this means versions `7` and `8`. ## Tooling | | | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Formatting | [liquidsoap-prettier](https://github.com/savonet/liquidsoap-prettier) | | VSCode | [vscode-liquidsoap](https://marketplace.visualstudio.com/items?itemName=savonet.vscode-liquidsoap) | | Neovim | [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter), [formatter.nvim](https://github.com/mhartington/formatter.nvim) | | Tree Sitter | [tree-sitter-liquidsoap](https://github.com/savonet/tree-sitter-liquidsoap) | | CodeMirror | [codemirror-lang-liquidsoap](https://github.com/savonet/codemirror-lang-liquidsoap) | | Playground | [https://www.liquidsoap.info/try/](https://www.liquidsoap.info/try/) | ## Documentation HTML documentation is available on our [website](http://liquidsoap.info) We also have written _the Liquidsoap book_ which is [available online](http://www.liquidsoap.info/book/book.pdf) and in [physical version](https://www.amazon.com/dp/B095PVTYR3). ## Contributing Contributions are more than welcome: you can submit [issues](https://github.com/savonet/liquidsoap/issues) if you find some, or contribute to the code through [pull requests](https://github.com/savonet/liquidsoap/pulls). You can checkout the code with ```sh git checkout git@github.com:savonet/liquidsoap.git ``` Please see [our documentation page](https://www.liquidsoap.info/doc-dev/build.html) about how to build the code. ## License 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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 ## Authors - Developers: - [Romain Beauxis](https://github.com/toots) - [Samuel Mimram](http://www.mimram.fr) - Former project leader and emeritus developer: - [David Baelde](http://www.lsv.fr/~baelde/) - Contributors: - Florent Bouchez - Julien Cristau - Stéphane Gimenez - Clément Renard - Vincent Tabard - Sattisvar Tandabany ������������������������������������liquidsoap-2.4.2/RELEASING��������������������������������������������������������������������������0000664�0000000�0000000�00000001455�15132732333�0015134�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������----------------------------- - How to release liquidsoap - ----------------------------- [] Run the CI. This should run the tests and prepare all the assets. [] Update the versioned dependencies in `liquidsoap.opam`, release pending dependent bindings (see below on how to publish to `opam`). [] Update copyright years in headers and check that all files have license headers. [] Fill-in CHANGES, with the release date. Opam packages ------------- Packages can be published to opam using `opam publish`. Make sure to check upstream for improvement made on the package files before sending the updates. `opam` versioning for version suffix is: `x.y.z~suffix`. It is not compatible with liquidsoap's `configure` based version detection so make sure to avoid depending on version-suffixed packages. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/ROADMAP.md�������������������������������������������������������������������������0000664�0000000�0000000�00000005401�15132732333�0015300�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Backlog - Explore new compiled backends - Update the book - Romain to document new internals - Write article for ICFP - support for ffmpeg subtitles - use OCaml 5 (after it has matured) - use native (as in native.liq) implementation of switch (based on source.dynamic) - reimplement video.tile in native liq - rework buffer.adaptative - use source getters for switch in order to be able to play two tracks ever day (#2880) ### From lioquidshop 5: - ~~Better handling over core module erasure~~ - Switch stream callbacks to async first - ~~Add variable with current liq script name.~~ ### Maybe TODO: - remove requests and use sources instead everywhere (a request is a source with one track [or more]) (weak maybe) - Precise scheduling with queue.push, etc.: we could make the track available at some precise time if requests were sources... - this may allow stuff like `append` more easily - Add support for modules, load minimal API by default - Simple mechanism to tell source how much data will be expected in advance (e.g. 10s with cross) to allow automatic buffer management. - Redefine switch-based transitions. ### Nice to have - refine video support in order to have next liquidshop running on Liquidsoap (dogfooding) - use row variables for methods, using Garrigue's _Simple Type Inference for Structural Polymorphism_ - can we reimplement something like [melt](https://www.mltframework.org/)? - support for WebRTC using WHIP / WHEP - support decorations on a subtitle image track - make bindings to pipewire to support webcams and screensharing ## For 2.2 ### Done - ~~Separate language core (#2397)~~ - ~~Online version (#2397)~~ - ~~Available at: https://www.liquidsoap.info/try/~~ - ~~Needs some cleanup, definition of a minimal JS library.~~ - ~~Switch to `dune`~~ - ~~Separate standard library (in pure liq)~~ - ~~support for multi-track audio~~ - ~~live switch with ffmpeg encoded content~~ - ~~deprecate "!" and ":=" in favor of x.get / x.set~~ - ~~switch to immutable content for metadata~~ - ~~Add script tooling, prettier etc.~~ - ~~switch to immutable content for frames (#2364)~~ - ~~frame should be changed to extensible arrays (a bit like `Strings`) instead of filling a buffer~~ - ~~take the opportunity to change the handling of track boundaries (currently boundary = we have a partial fill, which has quite messy corner cases)~~ ## For 2.3 ### Done: - ~~Rewrite streaming loop~~ - ~~rewrite the clock system~~ - ~~the code is unreadable and overengineered ⇒ simplify it~~ - we want to get rid of the assumption clock = thread (Feasible but problem with OCaml 5) - ~~Optimize runtime: start time, typing and memory usage~~ - ~~javascrtipt/browser support using [WebCodecs](https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API)!~~ ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/SECURITY.md������������������������������������������������������������������������0000664�0000000�0000000�00000002446�15132732333�0015472�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Security Policy ## Supported Versions Currently, we provide active support for the following versions of liquidsoap: | Version | Supported | | ------- | ------------------ | | `2.2.x` | :white_check_mark: | | `2.1.x` | :x: | | `2.0.x` | :x: | | `1.4.x` | :x: | | `< 1.4` | :x: | If you find a vulnerability in an unsupported version, please tell us. We will assess the risk and guide you accordingly. ## Reporting Security Issues At Liquidsoap, we take security seriously. We appreciate your efforts to responsibly disclose your findings, and we are committed to working with you to resolve any security issues. To report a security issue, please use the ["Report a Vulnerability"](https://github.com/savonet/liquidsoap/security/advisories/new) in the GitHub Security Advisory tab. ## Responsible Disclosure We kindly ask you not to disclose any vulnerabilities publicly until we have addressed them. We aim to promptly respond to your report and provide an estimated timeline for a fix. ## Thank You We appreciate your contributions to enhancing the security of our project. Your assistance in identifying and resolving vulnerabilities is priceless, and we are committed to maintaining a safe and secure environment for all our users. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/bin/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0014443�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/bin/liquidsoap-macos-instruments���������������������������������������������������0000775�0000000�0000000�00000000366�15132732333�0022241�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # shellcheck disable=SC2046 DIR=$(cd $(dirname "$0")/.. && pwd) export DIR opam exec dune -- exec --display=quiet --no-print-directory --root="$DIR" src/bin/liquidsoap-macos-instruments.exe -- --stdlib "$DIR"/src/libs/stdlib.liq "$@" ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/�������������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0014440�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/.gitignore���������������������������������������������������������������������0000664�0000000�0000000�00000000045�15132732333�0016427�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������_build language.dtd liquidsoap.1 pdf �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/Makefile�����������������������������������������������������������������������0000664�0000000�0000000�00000000607�15132732333�0016103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������MD = $(wildcard content/*.md) all: dune @dune build @doc test: dune @for i in $(MD); do \ echo -n "Compiling $$i... "; \ pandoc $$i -t json | pandoc-include --directory "content/liq" | pandoc -f json --metadata title="bla" --template=template.html -o /tmp/`basename $$i .md`.html; \ echo "done"; \ done @dune build @doctest dune: @dune build @gendune --auto-promote .PHONY: dune �������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/�����������������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0016112�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/beets.md���������������������������������������������������������������0000664�0000000�0000000�00000013101�15132732333�0017532�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Integrating a music library: an example with Beets Liquidsoap's native sources can read from files and folders, but if your radio uses an important music library (more than a thousand tracks) sorting by folders may not be enough. You will also need to adjust the playout gain per track (ReplayGain). In that case you would better have a music library queried by Liquidsoap. In this section we'll do this with [Beets](http://beets.io/). Beets holds your music catalog, cleans tracks' tags before importing, can compute each track's ReplayGain, and most importantly has a command-line interface we can leverage from Liquidsoap. The following examples may also inspire you to integrate another library or your own scripts. After installing Beets, enable the `random` plug-in (see [Beets documentation on plug-ins](https://beets.readthedocs.io/en/stable/plugins/index.html#using-plugins)). To enable gain normalization, install and configure the [`replaygain`](https://beets.readthedocs.io/en/stable/plugins/replaygain.html) plug-in. To easily add single tracks to you library, you might also be interested in the [drop2beets](https://github.com/martinkirch/drop2beets#drop2beets) plug-in. The following examples suppose you defined a `BEET` constant, which contains the complete path to your `beet` executable (on UNIX systems, find it with `which beet`). For example: ``` BEET = "/home/radio/.local/bin/beet" ``` Before creating a Liquidsoap source, let's see why Beets queries are interesting for a radio. ## Beets queries Queries are parameters that you usually provide to the `beet ls` command : Beets will find matching tracks. The `random` plug-in works the same, except that it returns only one track matching the query (see [the plug-in's documentation](https://beets.readthedocs.io/en/stable/plugins/random.html)). Once your library is imported, you can try the following queries on the command line by typing `beet ls [query]` or `beet random [query]`. To test quickly, add the `-t 60` option to `beet random` so it will select an hour worth of tracks matching your query. Without selectors, queries search in a track’s title, artist, album name, album artist, genre and comments. Typing an artist name or a complete title usually match the exact track, and you could do a lovely playlist just by querying `love`. But in a radio you'll usually query on other fields. You can select tracks by genre with the `genre:` selector. Be careful that `genre:Rock` also matches `Indie Rock`, `Punk Rock`, etc. To select songs having english lyrics, use `language:eng`. Or pick 80s songs with `year:1980..1990`. Beets also holds internal meta-data, like `added`: the date and time when you imported each song. You can use it to query tracks inserted over the past month with `added:-1m..`. Or you can query track imported more than a year ago with `added:..-1y`. Beets also lets you [set your own tags](https://beets.readthedocs.io/en/stable/guides/advanced.html#store-any-data-you-like). You can use the `info` plug-in to see everything Beets knows about title(s) matching a query by typing `beet info -l [query]`. See also [the Beets' documentation](https://beets.readthedocs.io/en/stable/reference/query.html) for more details on queries operators. All these options should allow you to create both general and specialized Liquidsoap sources. ## A source querying each next track from Beets As of Liquidsoap 2.x we can create a function that creates a dynamic source, given its `id` and a Beet query. We rely on `request.dynamic` to call `beet random` (with `-f '$path'` option so beets only returns the matching track's path) every time the source must prepare a new track: ```{.liquidsoap include="beets-source.liq" from="BEGIN" to="END"} ``` Note that - `query` can be empty, it will match all tracks in the library. - we set `retry_delay` to a second, to avoid looping on `beet` calls if something goes wrong. - The final type hint (`:source`) will avoid false typing errors when the source is integrated in complex operators. ## Applying ReplayGain When the [`replaygain` plug-in](https://beets.readthedocs.io/en/stable/plugins/replaygain.html) is enabled, all tracks will have an additional metadata field called `replaygain_track_gain`. Check that Beet is configured to [write ID3 tags](https://beets.readthedocs.io/en/stable/reference/config.html#importer-options) so Liquidsoap will be able to read this metadata - your Beet configuration should include something like: ``` import: write: yes ``` Then we only need to add `amplify` to our source creation function. In the example below we also add `blank.eat`, to automatically cut silence at the beginning or end of tracks. ```{.liquidsoap include="beets-amplify.liq" from="BEGIN"} ``` This is the recommended Beets integration ; such source will provide music continuously, at a regular volume. ## Beets as a requests protocol If you're queueing tracks with `request.queue`, you may prefer to integrate Beets as a protocol. In that case, the list of paths returned by `beet random -f '$path'` fits directly what's needed by protocol resolution: ```{.liquidsoap include="beets-protocol.liq" from="BEGIN"} ``` Once this is done, you can push a beets query from [the telnet server](server.html): if you created `request.queue(id="userrequested")`, the server command `userrequested.push beets:All along the watchtower` will push the Jimi Hendrix's song. With this method, you can benefit from replay gain metadata too, by wrapping the recipient queue in an `amplify` operator, like ```liquidsoap userrequested = amplify(override="replaygain_track_gain", 1.0, request.queue(id="userrequested") ) ``` ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/blank.md���������������������������������������������������������������0000664�0000000�0000000�00000002657�15132732333�0017535�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Blank detection [Liquidsoap](index.html) has three operators for dealing with blanks. On GeekRadio, we play many files, some of which include bonus tracks, which means that they end with a very long blank and then a little extra music. It's annoying to get that on air. The `blank.skip` operator skips the current track when a too long blank is detected, which avoids that. The typical usage is simple: ```liquidsoap # Wrap it with a blank skipper s = blank.skip(s) ``` At [RadioPi](http://www.radiopi.org/) they have another problem: sometimes they have technical problems, and while they think they are doing a live show, they're making noise only in the studio, while only blank is on air; sometimes, the staff has so much fun (or is it something else ?) doing live shows that they leave at the end of the show without thinking to turn off the live, and the listeners get some silence again. To avoid that problem we made the `blank.strip` operators which hides the stream when it's too blank (i.e. declare it as unavailable), which perfectly suits the typical setup used for live shows: ```{.liquidsoap include="blank-sorry.liq"} ``` If you don't get the difference between these two operators, you should learn more about liquidsoap's notion of [source](sources.html). Finally, if you need to do some custom action when there's too much blank, we have `blank.detect`: ```{.liquidsoap include="blank-detect.liq" from="BEGIN" to="END"} ``` ���������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/book.md����������������������������������������������������������������0000664�0000000�0000000�00000001014�15132732333�0017362�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The Liquidsoap book Together with the release of Liquidsoap 2.0, we have written _the Liquidsoap book_ which covers in details the language and the process of building a radio. It complements the online documentation by providing a homogeneous and progressive presentation of Liquidsoap. [![The Liquidsoap book](/assets/img/book.svg){height=600px}](https://www.amazon.com/dp/B095PVTYR3) It can be [ordered from Amazon](https://www.amazon.com/dp/B095PVTYR3) (or [read online](http://www.liquidsoap.info/book/book.pdf)). ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/build.md���������������������������������������������������������������0000664�0000000�0000000�00000013176�15132732333�0017543�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Building Liquidsoap ## Forewords Installing liquidsoap can be a difficult task. The software relies on an up-to date OCaml compiler, as well as a bunch of OCaml modules and, for most of them, corresponding C library dependencies. Our recommended way of installing liquidsoap is via [opam](http://opam.ocaml.org/). `opam` can take care of installing the correct OCaml compiler, optional and required dependencies as well as system-specific package dependencies. The `opam` method is described in details in the [documentation](doc/content/install.md). We recommend that any interested user head over to this link to install the software via `opam`. The remainder of this document describes how to compile liquidsoap locally for developers. ## Overview Liquidsoap is compiled using [dune](https://dune.readthedocs.io/en/stable/), which is the most popular OCaml build system at the moment. `dune` is tightly integrated with `opam` so, even if you are installing from source using `dune`, `opam` remains an important tool. Generally speaking, compiling from source may require the latest version of the liquidsoap code as well as its dependencies. Some of its dependencies are optional and can be ignored at first and some are not. Keep in mind that, although `opam` is generally aware of required minimal version for dependencies, `dune` is not. If a dependency is outdated, `dune` compilation will simply fail, at which point your may have to figure out if you need to update a dependency. Each branch of liquidsoap is compiled using [github actions](https://github.com/savonet/liquidsoap/actions). When trying to build a specific branch, if the CI passes with it then, most likely, you are missing a dependency, or it is not the latest version. ## `opam` pinning `opam` pinning is a mechanism to update `opam` with the latest version of a package, even before it is published to the official opam repository. This is the easiest way to update a dependency to its latest version. You can pin directly from a local git repository checkout: ```shell git clone https://github.com/savonet/ocaml-metadata.git cd ocaml-metadata opam pin -ny . ``` You can also pin directly using a git url: ```shell opam pin -ny git+https://github.com/savonet/ocaml-cry ``` See `opam pin --help` for more details about the available options. ## Dependencies The best way to figure out what dependencies are required or optional and their versions is to use the latest `opam` package. Since `liquidsoap` development is using `dune` and `opam`, the dependencies are kept in sync via the local liquidsoap opam package(s) and this serves as the de-facto list of dependencies and their versions. First, you should pin the latest liquidsoap code: ```shell opam pin -ny git+https://github.com/savonet/liquidsoap ``` Then, ask `opam` to list all the dependencies for `liquidsoap`: ```shell opam info liquidsoap opam info liquidsoap-lang ``` This should give you a (long!) list of all dependencies. Then, you can query `opam` to see what each dependency does. This is particularly useful for optional dependencies on `liquidsoap-core` which provide opt-in features. For instance `opam info soundtouch` will let you know that this package provides functions for changing pitch and timestretching audio data. Lastly, there are two types of dependencies: - Dependencies maintained by us - Dependencies not maintained by us For dependencies not maintained by us, most of the time, we rely on the latest published version. Very rarely should you have to fetch/pin the latest version of these dependencies. For dependencies maintained by us, we may break their API during our development cycle, and you maybe have to fetch/pin the latest version when compiling the latest `liquidsoap` code. You may also have to check out a specific branch when compiling `liquidsoap` from a specific development branch when the changes in the liquidsoap code are paired with changes in one of our dependencies. Typically, this happens a lof with the `ffmpeg` binding. ## Environment variables When compiling Liquidsoap from source, certain environment variables can be set to control the build process and customize the build configuration. Here’s a brief overview of the relevant environment variables and their purposes: - `IS_SNAPSHOT`: Set this variable to indicate whether you are building a snapshot version of Liquidsoap. It affects the version suffix and whether the Git commit is displayed. - `LIQ_GIT_SHA`: Override Git commit hash (SHA) if the build system cannot automatically extract it from the repository. - `LIQ_VERSION`: Override the displayed version of Liquidsoap. - `LIQUIDSOAP_ENABLE_BUILD_CONFIG`: Determines whether the build configuration details are displayed during the build process. - `LIQUIDSOAP_BUILD_TARGET`: Controls the runtime lookup paths for Liquidsoap components. - Set to `default`: Uses paths detected in the OPAM switch directory. - Set to `standalone`: Uses paths relative to the binary location, ideal for self-contained deployments. - Set to `posix`: Configures paths to standard system directories. ## Compiling Once you have all dependencies installed, you should be able to compile via: ```shell dune build ``` If an error occurs, you may need to see if you need to update a dependency. Hopefully, with a short iteration of this cycle, you will end up with a successful build! Once you have a successful build, you can also use the top-level `liquidsoap` script. This script builds the latest code and executes it right away. It works as if you were calling the `liquidsoap` binary after installing it: ```shell ./liquidsoap -h output.ao ``` From here, you can start changing code, testing script etc. Happy hacking! ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/clocks.md��������������������������������������������������������������0000664�0000000�0000000�00000013312�15132732333�0017712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# ⏰ Clocks in Liquidsoap When you first dive into Liquidsoap, the idea of sources generating and transforming media in a streaming pipeline feels pretty straightforward. In earlier pages like the [quickstart](quick_start.html) or the overview on [sources](sources.html), we describe a world where everything runs in sync—each component producing data at the same rate, driven by a single clock ticking steadily in the background. But behind that simplicity lies a more nuanced reality. In practice, a media streaming system may involve **multiple clocks**, each advancing time in its own way. Understanding why and how that happens will give you a deeper appreciation of Liquidsoap’s design—and help you make sense of some occasional, but puzzling, error messages. This page will: 1. Introduce why multiple clocks exist and how they affect streaming. 2. Show common clock-related error messages and what they mean. 3. Explain how and when to use clocks explicitly in your scripts. 👉 Before reading on, it helps to be familiar with [sources](sources.html) and [latency](latency_control.html), which tie closely into clock behavior. ## Why More Than One Clock? 🕒🕗 The first reason is **external**: the world itself doesn't run on a single clock. - Your computer's internal clock may not match your friend’s. - Different soundcards, each with their own timing, tick at slightly different rates. - Remote connections (like over a network) introduce unpredictable delays. Liquidsoap interfaces with all of these elements—soundcards, remote servers, inputs, and outputs—so it has to account for those mismatches. There are also **internal** reasons: Some operators require control over time. For example: - `stretch` changes playback speed. - `crossfade`, and similar operators, temporarily consume stream data _faster_ to overlap tracks for smoother transitions. So if an operator wants to speed things up temporarily, it can’t share a clock with parts of the system expecting a constant pace. To avoid inconsistencies, **each source in Liquidsoap is assigned a single clock**, fixed when the source is created. ## 🧠 Visualizing Clocks Imagine your stream as a graph: boxes for sources, lines for data flow. Now, add a new layer: **clocks**. Each group of sources inside a clock "box" runs at the same pace. For example: ```liquidsoap output.icecast(fallback([crossfade(playlist(...)), jingles])) ``` In this setup: - A dedicated clock is created for `crossfade`, so it can speed up during transitions. - The rest runs on a standard clock tied to the computer’s real time. Here’s what that looks like: ![Graph representation with clocks](/assets/img/graph_clocks.png) ## 🛑 Understanding Clock-Related Errors Most of the time, you don’t need to think about clocks—Liquidsoap handles it for you. But when things go wrong, here’s what it might look like: ### Latency Control Errors ```liquidsoap s = input.srt("...") output.ao(fallible=true, crossfade(s)) ``` Error: ``` Error 7: Invalid value: This source may control its own latency and cannot be used with this operator. ``` What’s happening? - `input.srt` uses its **own clock** (SRT's). - `crossfade` needs **control over time** to work properly. - But this input won't give up control—so the operation fails. 👉 To learn more about self-synchronizing sources, check the [latency docs](latency_control.html). ### Clock Conflicts ```liquidsoap output.ao(fallible=true, input.srt("...")) ``` Error: ``` Error 17: clock input.srt has multiple synchronization sources. Do you need to set self_sync=false? Sync sources: srt from source input.srt ao from source output.ao ``` Here, both `input.srt` and `output.ao` have their own clocks, and they can't be unified. You can sometimes set `self_sync=false` to override this, but beware—this could introduce latency issues. ## 🧰 Using the Clock API For advanced setups, Liquidsoap gives you tools to inspect and manipulate clocks: - Access a source’s clock: ```liquidsoap c = s.clock ``` - If methods are missing, rewrap your source and clock: ```liquidsoap s = source.methods(s) c = clock(s.clock) print("source #{s.id()} belongs to clock id: #{c.id()}") ``` You can even create new clocks and assign sources to them: ```liquidsoap clock.assign_new(sync="none", [source]) ``` To connect sources from different clocks, use a `buffer()`: ```liquidsoap buffer(source_in_one_clock) ``` This creates a bridge: it queues data when clocks run at different speeds. Just be careful—if the clocks get too far out of sync, the buffer can overflow or underflow. ## 🌐 Real-World Use: Isolating Clock Domains Let’s say you're streaming from a soundcard to Icecast **and** recording the input. You want the **recording to be perfect**, even if the network lags and affects Icecast. Here’s how you do it: ```liquidsoap input = input.alsa() # Icecast with its own clock + buffer icecast_source = mksafe(buffer(input)) output.icecast(%mp3, mount="live", icecast_source) # File recording without Icecast delays output.file(%mp3, "recording.mp3", input) ``` 💡 This isolates the network-sensitive Icecast output, protecting the file recording from glitches or dropped packets. ## Wrapping Up Clocks are one of the most abstract parts of Liquidsoap—but also one of the most powerful. They help you manage latency, avoid glitches, and coordinate complex stream behaviors. While Liquidsoap usually handles clocks behind the scenes, understanding them opens the door to more reliable and flexible setups. And when an error does pop up, you’ll be ready. 👉 Feeling curious? Take a peek at your sources’ clocks and try experimenting with `buffer()` or `assign_new()`. You'll be surprised what a little time travel can do. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/complete_case.md�������������������������������������������������������0000664�0000000�0000000�00000003573�15132732333�0021247�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A complete case analysis We will develop here a more complex example, according to the following specifications: - play different playlists during the day; - play user requests -- done via the telnet server; - insert about 1 jingle every 5 songs; - add one special jingle at the beginning of every hour, mixed on top of the normal stream; - relay live shows as soon as one is available; - and set up several outputs. Once you've managed to describe what you want in such a modular way, you're half the way. More precisely, you should think of a diagram such as the following, through which the audio streams flow, following the arrows. The nodes can modify the stream using some basic operators: switching and mixing in our case. The final nodes, the ends of the paths, are outputs: they are in charge of pulling the data out of the graph and send it to the world. In our case, we only have outputs to icecast, using two different formats. ![Graph for 'radio.liq'](/assets/img/liqgraph.png) Now here is how to write that in [Liquidsoap](index.html). ```{.liquidsoap include="complete-case.liq"} ``` To try this example you need to edit the file names. In order to witness the switch from one playlist to another you can change the time intervals. If it is 16:42, try the intervals `0h-16h45` and `16h45-24h` instead of `6h-22h` and `22h-6h`. To witness the clock jingle, you can ask for it to be played every minute by using the `0s` interval instead of `0m0s`. To try the transition to a live show you need to start a new stream on the `live.ogg` mount of your server. You can send a playlist to it using examples from the [quickstart](quick_start.html). To start a real live show from soundcard input you can use `darkice`, or simply liquidsoap if you have a working ALSA input, with: ```liquidsoap liquidsoap 'output.icecast(%vorbis, \ mount="live.ogg",host="...",password="...",input.alsa())' ``` �������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/cookbook.md������������������������������������������������������������0000664�0000000�0000000�00000034360�15132732333�0020250�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Cookbook The recipes show how to build a source with a particular feature. You can try short snippets by wrapping the code in an `output(..)` operator and passing it directly to liquidsoap: ```liquidsoap liquidsoap -v 'output(recipe)' ``` For longer recipes, you might want to create a short script: ```liquidsoap #!/usr/bin/liquidsoap -v log.file.path := "/tmp/<script>.log" log.stdout := true recipe = # <fill this> output(recipe) ``` See the [quickstart guide](quick_start.html) for more information on how to run [Liquidsoap](index.html), on what is this `output(..)` operator, etc. See also the [ffmpeg cookbook](ffmpeg_cookbook.html) for examples specific to the ffmpeg support. ## Files A source which infinitely repeats the same URI: ```{.liquidsoap include="single.liq"} ``` A source which plays a playlist of requests -- a playlist is a file with an URI per line. ```{.liquidsoap include="playlists.liq" to="END"} ``` When building your stream, you'll often need to make it unfallible. Usually, you achieve that using a fallback switch (see below) with a branch made of a safe `single`. Roughly, a single is safe when it is given a valid local audio file. ## Transcoding [Liquidsoap](index.html) can achieve basic streaming tasks like transcoding with ease. You input any number of "source" streams using `input.http`, and then transcode them to any number of formats / bitrates / etc. The only limitation is your hardware: encoding and decoding are both heavy on CPU. If you want to get the best use of CPUs (multicore, memory footprint etc.) when encoding media with Liquidsoap, we recommend using the `%ffmpeg` encoders. ```{.liquidsoap include="transcoding.liq"} ``` ## Re-encoding a file As a simple example using a fallible output, we shall consider re-encoding a file. We start by building a source that plays our file only once. That source is obviously fallible. We pass it to a file output, which has to be in fallible mode. We also disable the `sync` parameter on the source's clock, to encode the file as quickly as possible. Finally, we use the `on_stop` handler to shutdown liquidsoap when streaming is finished. ```{.liquidsoap include="re-encode.liq"} ``` ## Generating CUE files When making backups of streams in audio files, it can be useful to generate CUE files, which store the times where the various tracks occur along with their metadata (those could then be used later on to split the file for instance). This can be achieved using the `source.cue` operator: ```{.liquidsoap include="source-cue.liq" from="BEGIN" to="END"} ``` which will generate a CUE file of the following form ``` TITLE "My stream" PERFORMER "The performer" FILE "backup.mp3" MP3 TRACK 01 AUDIO TITLE "Title 1" PERFORMER "Artist 1" INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "Title 2" PERFORMER "Artist 2" INDEX 01 01:12:67 ``` ## RTMP server With our [FFmpeg support](ffmpeg.html), it is possible to create a simple RTMP server with no re-encoding: ```{.liquidsoap include="rtmp.liq"} ``` ## Transmitting signal It is possible to send raw PCM signal between two instances using the [FFmpeg encoder](ffmpeg.html). Here's an example using the SRT transport protocol: Sender: ```{.liquidsoap include="srt-sender.liq" from="BEGIN"} ``` Receiver: ```{.liquidsoap include="srt-receiver.liq" to="END"} ``` ## Scheduling ```{.liquidsoap include="fallback.liq" to="END"} ``` ```{.liquidsoap include="scheduling.liq" from="BEGIN" to="END"} ``` ## Generating playlists from a media library In order to store all the metadata of the files in a given directory and use those to generate playlists, you can use the `medialib` operator which takes as argument the directory to index. On first run, it will index all the files of the given folder, which can take some time (you are advised to use the `persistency` parameter in order to specify a file where metadata will be stored to avoid reindexing at each run). The resulting object can then be queried with the `find` method in order to return all files matching the given conditions and thus generate a playlist: ```{.liquidsoap include="medialib.liq"} ``` The parameter of the `find` method follow the following convention: - `artist="XXX"` looks for files where the artist tag is exactly the given one - `artist_contains="XXX"` looks for files where the artist tag contains the given string as substring - `artist_matches="XXX"` looks for files where the artist tag matches the given regular expression (for instance `artist_matches="(a)+.*(b)+"` looks for files where the artist contains an `a` followed by a `b`). The tags for which such parameters are provided are: `artist`, `title`, `album` and `filename` (feel free to ask if you need more). Some numeric tags are also supported: - `year=1999` looks for files where the year is exactly the given one - `year_ge=1999` looks for files where the year at least the given one - `year_lt=1999` looks for files where the year at most the given one The following numeric tags are supported: `bpm`, `year`. If multiple arguments are passed, the function finds files with tags matching the conjunction of the corresponding condition. Finally, if you need more exotic search functions, the argument `predicate` can be used. It takes as argument a _predicate_ which is a function taking the metadata of a file and returning whether the file should be selected. For instance, the following looks for files where the name of the artist is of length 5: ```{.liquidsoap include="medialib-predicate.liq" from="BEGIN" to="END"} ``` The default implementation of `medialib` uses standard Liquidsoap functions and can be pretty expensive in terms of memory. A more efficient implementation is available if you compiled with support for sqlite3 databases. In this case, you can use the `medialib.sqlite` operator as follows: ```{.liquidsoap include="medialib.sqlite.liq"} ``` (we also support more advanced uses of [databases](database.html)). ## Force a file/playlist to be played at least every XX minutes It can be useful to have a special playlist that is played at least every 20 minutes for instance (3 times per hour). You may think of a promotional playlist for instance. Here is the recipe: ```{.liquidsoap include="regular.liq" from="BEGIN" to="END"} ``` Where promotions is a source selecting the file to be promoted. ## Play a jingle at a fixed time Suppose that we have a playlist `jingles` of jingles and we want to play one within the 5 first minutes of every hour, without interrupting the current song. We can think of doing something like ```{.liquidsoap include="fixed-time1.liq" from="BEGIN" to="END"} ``` but the problem is that it is likely to play many jingles. In order to play exactly one jingle, we can use the function `predicate.activates` which detects when a predicate (here `{ 0m-5m }`) becomes true: ```{.liquidsoap include="fixed-time2.liq" from="BEGIN" to="END"} ``` ## Handle special events: mix or switch Add a jingle to your normal source at the beginning of every hour: ```{.liquidsoap include="jingle-hour.liq" from="BEGIN" to="END"} ``` Switch to a live show as soon as one is available. Make the show unavailable when it is silent, and skip tracks from the normal source if they contain too much silence. ```{.liquidsoap include="switch-show.liq" from="BEGIN" to="END"} ``` Without the `track_sensitive=false` the fallback would wait the end of a track to switch to the live. When using the blank detection operators, make sure to fine-tune their `threshold` and `length` (float) parameters. ## Unix interface, dynamic requests Liquidsoap can create a source that uses files provided by the result of the execution of any arbitrary function of your own. This is explained in the documentation for [request-based sources](request_sources.html). For instance, the following snippet defines a source which repeatedly plays the first valid URI in the playlist: ```{.liquidsoap include="request.dynamic.liq" to="END"} ``` Of course a more interesting behaviour is obtained with a more interesting program than `cat`, see [Beets](beet.html) for example. Another way of using an external program is to define a new protocol which uses it to resolve URIs. `protocol.add` takes a protocol name, a function to be used for resolving URIs using that protocol. The function will be given the URI parameter part and the time left for resolving -- though nothing really bad happens if you don't respect it. It usually passes the parameter to an external program ; it is another way to integrate [Beets](beet.html), for example: ```{.liquidsoap include="beets-protocol-short.liq"} ``` When resolving the URI `beets:David Bowie`, liquidsoap will call the function, which will call `beet random -f '$path' David Bowie` which will output the path to a David Bowie song. ## Dynamic input with harbor The operator `input.harbor` allows you to receive a source stream directly inside a running liquidsoap. It starts a listening server on where any Icecast2-compatible source client can connect. When a source is connected, its input if fed to the corresponding source in the script, which becomes available. This can be very useful to relay a live stream without polling the Icecast server for it. An example can be: ```{.liquidsoap include="harbor-dynamic.liq"} ``` This script, when launched, will start a local server, here bound to "0.0.0.0". This means that it will listen on any IP address available on the machine for a connection coming from any IP address. The server will wait for any source stream on mount point "/live" to login. Then if you start a source client and tell it to stream to your server, on port 8080, with password "hackme", the live source will become available and the radio will stream it immediately. ## Play a short silence when transitioning out of `input.harbor` If the live connection is unstable, for instance when streaming through a roaming phone device, it can be interesting to add an extra `5s` of silence when transitioning out of a live `input.harbor` to give the input some chance to reconnect. This can be done with the `append` operator: ```{.liquidsoap include="append-silence.liq" to="END"} ``` ## Dump a stream into segmented files It is sometimes useful (or even legally necessary) to keep a backup of an audio stream. Storing all the stream in one file can be very impractical. In order to save a file per hour in wav format, the following script can be used: ```{.liquidsoap include="dump-hourly.liq" from="BEGIN"} ``` Here, the function `time.string` generates the file name by replacing `%H` by the hour, etc. The fact that it is between curly brackets, i.e. `{time.string(...)}`, ensures that it is re-evaluated each time a new file is created, the changing the file name each time according to the current time. In the following variant we write a new mp3 file each time new metadata is coming from `s`: ```{.liquidsoap include="dump-hourly2.liq" from="BEGIN"} ``` In the two examples we use [string interpolation](language.html) and time literals to generate the output file name. In order to limit the disk space used by this archive, on unix systems we can regularly call `find` to cleanup the folder; if we can to keep 31 days of recording: ```{.liquidsoap include="archive-cleaner.liq"} ``` ## Transitions There are two kinds of transitions. Transitions between two different children of a switch or fallback and transitions between tracks of the same source. ### Switch-based transitions The switch-based operators (`switch`, `fallback` and `random`) support transitions. For every child, you can specify a transition function computing the output stream when moving from one child to another. This function is given two `source` parameters: the child which is about to be left, and the new selected child. The default transition is `fun (a,b) -> b`, it simply relays the new selected child source. One limitation of these transitions, however, is that if the transition happen right at the end of a track, which is the default with `track_sensitive=true`, then there is no more data available for the old source, which makes it impossible to fade it out. If that is what you are expecting, you should look at crossfade-based transitions ### Crossfade-based transitions Crossfade-based transitions are more complex and involve buffering source data in advance to be able to compute a transition where ending and starting track potentially overlap. This does not work with all type of sources since some of them, such as `input.http` may only receive data at real-time rate and cannot be accelerated to buffer their data or else we risk running out of data. We provide a default operator named `cross.simple` transition which may be suitable for most usage. But you can also create your own customized crossfade transitions. This is in particular true if you are expecting crossfade transitions between tracks of your `music` source but not between a `music` track and e.g. some jingles. Here's how to do it in this case: ```{.liquidsoap include="cross.custom.liq" from="BEGIN" to="END"} ``` ## Alsa output delay You can use [Liquidsoap](index.html) to capture and play through alsa with a minimal delay. This particularly useful when you want to run a live show from your computer. You can then directly capture and play audio through external speakers without delay for the DJ ! This configuration is not trivial since it relies on your hardware. Some hardware will allow both recording and playing at the same time, some only one at once, and some none at all.. Those note to configure are what works for us, we don't know if they'll fit all hardware. First launch liquidsoap as a one line program ``` liquidsoap -v --debug 'input.alsa()' ``` Unless you're lucky, the logs are full of lines like the following: ``` Could not set buffer size to 'frame.size' (1920 samples), got 2048. ``` The solution is then to set liquidsoap's internal frame size to this value, which is most likely specific to your hardware. Let's try this script: ```{.liquidsoap include="frame-size.liq"} ``` The setting will be acknowledged in the log as follows: ``` Targeting 'frame.audio.size': 2048 audio samples = 2048 ticks. ``` If everything goes right, you may hear on your output the captured sound without any delay! If you experience problems it might be a good idea to double the value of the frame size. This increases stability, but also latency. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/crossfade.md�����������������������������������������������������������0000664�0000000�0000000�00000002524�15132732333�0020410�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Crossfade ## Out of the box Liquidsoap provides a default `crossfade` operator out of the box. It is a simple operator that does the work and does it well! Over the years, we have realized that crossfading is a very sensitive topic and that people care a lot about specific details and how well it is done. Since release `2.2.5`, liquidsoap integrates an automated mechanism to compute crossfade transitions that was contributed by our users. If you have the `ffmpeg` bindings enabled, all you should need to do to enable this feature is adding the following to your script: ```liquidsoap enable_autocue_metadata() ``` This uses the default, internal implementation. If you want more control over the automated crossfade parameters, you can check out the external [autocue](https://github.com/Moonbase59/autocue) implementation and its associated documentation. ## Custom crossfades You can also define your own crossfade transitions if you want to be more specific about them! The base `cross` operator accepts a scripted transition function that, according to the average volume level (in dB) computed on the end of the ending track and the beginning of the new one, returns the transition that is desired. You can find its documentation in the [language reference](reference.html). Here's an example: ```{.liquidsoap include="crossfade.liq"} ``` ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/custom-path.md���������������������������������������������������������0000664�0000000�0000000�00000004700�15132732333�0020701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Basics Starting with version `1.0.1`, it is possible to build a liquidsoap binary that can load all its dependencies from any arbitrary path. This is very useful to distribute a liquidsoap bundled binary, independent of the distribution used. You can enable custom path at configure time, by passing the `--enable-custom-path` configuration option. A custom loading path is a directory that contains the following file/directories: - `./camomile`: Camomile shared data. They are usually located in `/usr/(local/)share/camomile` - `./libs`: pervasive scripts. Their are located in `liquidsoap/scripts` in liquidsoap's sources - `./log`: default log directories - `./magic`: directory for magic files. See below for more details. - `./plugins`: default plugins directory (most likely empty) - `./run`: default runtime files directory # Adding liquidsoap binary In order to ship a liquidsoap binary which is independent of the distribution it will be run on, one need to also include its dynamic libraries, except for the most common. The following command may be used to list them: ``` ldd ./liquidsoap | grep usr | cut -d' ' -f 3 ``` Those libraries are usually copied into a `./ld` directory. Then, the `LD_LIBRARY_PATH` is used to point the dynamic loader to this directory. Finally, the `liquidsoap` library is usually added in `./bin/liquidsoap` # Configuration variables In the following, configuration variables may refer to either absolute or relative paths. If referring to a relative path, the path is resolved relatively to the directory where the `liquidsoap` binary is located at. In order to tell liquidsoap where its custom path is located, you need to set the `LIQUIDSOAP_BASE_DIR`. Another important variable is `MAGIC`. It tells liquidsoap where to load the libmagic's definitions and defaults to `../magic/magic.mgc`. Older versions of libmagic may require to use `magic/magic.mime` instead. # Full example For a fully-functional example, you can check our [heroku buildpack](https://github.com/savonet/heroku-buildpack-liquidsoap). Its layout is: ``` ./bin ./bin/liquidsoap ./camomile ./camomile/charmaps (...) ./ld ./ld/libao.so.2 (...) ./libs ./libs/externals.liq (...) ./log ./magic ./magic/magic.mime ./plugins ./run ``` Its configuration variables are set to: ``` LD_LIBRARY_PATH=/path/to/ld LIQUIDSOAP_BASE_DIR=.. MAGIC=../magic/magic.mime ``` As you can see, we use an old version of `libmagic` so we need to load `magic.mime` instead of `magic.mgc`. ����������������������������������������������������������������liquidsoap-2.4.2/doc/content/database.md������������������������������������������������������������0000664�0000000�0000000�00000010565�15132732333�0020207�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Database support Liquidsoap supports SQL databases through the sqlite library. If you build Liquidsoap by yourself, you should install the [SQLite3-OCaml](https://github.com/mmottl/sqlite3-ocaml) library, e.g. with `opam install sqlite3`{.bash}. In order to create or open a database, you should use the `sqlite` function, which takes as argument the file where the database is stored and returns an object whose methods can be used to modify or query the database: ```{.liquidsoap include="sqlite.liq" from="open-begin" to="open-end"} ``` table in the database can then be created by calling the `table.create` method on the object with as arguments the table name (labeled by `table`) and the list of columns specified by pairs consisting of the column name, and its type. Setting the `preserve` argument to `true`{.liquidsoap} allows not creating the table if one already exists under this name. In our example, we want to use our database to store metadata for files so that we create a table named `"metadata"`{.liquidsoap} with columns corresponding to the artist, title, etc.: ```{.liquidsoap include="sqlite.liq" from="create-begin" to="create-end"} ``` Inserting a row is then performed using the `insert` method, which takes as argument the table and a record containing the data for the row: ```{.liquidsoap include="sqlite.liq" from="insert-begin" to="insert-end"} ``` Since the field `filename` is a primary key, it has to be unique (two rows cannot have the same file name), so that inserting two files with the same filename in the database will result in an error. If we want that the second insertion replace the first one, we can pass the `replace=true`{.liquidsoap} argument to the `insert` function. We can query the database with the `select` method. For instance, to obtain all the files whose year is posterior to 2000, we can write ```{.liquidsoap include="sqlite.liq" from="select-begin" to="select-end"} ``` In the case where you want to use strings in your queries, you should always use `sqlite.escape` to properly escape it and avoid injections: ```{.liquidsoap include="sqlite.liq" from="select2-begin" to="select2-end"} ``` The `select` function, returns a list of rows. To each row will correspond a list of pairs strings consisting of - a string: the name of the column, - a nullable string: its value (this is nullable because the contents of a column can be NULL in databases). We could thus extract the filenames from the above queries and use those in order to build a playlist as follows: ```{.liquidsoap include="sqlite.liq" from="play-begin" to="play-end"} ``` This can be read as follows: for each row (by `list.map`{.liquidsoap}), we convert the row to a list of pairs of strings as described above (by calling the `to_list`{.liquidsoap} method), we replace take the field labeled `"filename"`{.liquidsoap} (by `list.assoc`{.liquidsoap}) and take its value, assuming that it is not null (by `null.get`{.liquidsoap}). Since manipulating rows as lists of pairs of strings is not convenient, Liquidsoap offers the possibility to represent them as records with constructions of the form ```liquidsoap let sqlite.row (r : {a : string; b : int}) = row ``` which instructs to parse the row `row`{.liquidsoap} as a record `r` with fields `a` and `b` of respective types `string`{.liquidsoap} and `int`{.liquidsoap}. The above filename extraction is thus more conveniently written as ```{.liquidsoap include="sqlite.liq" from="play2-begin" to="play2-end"} ``` Other useful methods include - `count` to count the number of rows satisfying a condition ```{.liquidsoap include="sqlite.liq" from="count-begin" to="count-end"} ``` - `delete` to delete rows from a table ```{.liquidsoap include="sqlite.liq" from="play-begin" to="play-end"} ``` - `table.drop` to delete tables from the database ```{.liquidsoap include="sqlite.liq" from="drop-begin" to="drop-end"} ``` - `exec` to execute an arbitrary SQL query which does not return anything: ```{.liquidsoap include="sqlite.liq" from="exec-begin" to="exec-end"} ``` - `query` to execute an arbitrary SQL query returning rows ```{.liquidsoap include="sqlite.liq" from="query-begin" to="query-end"} ``` Finally, if your aim is to index file metadata, you might be interested in the `medialib.sqlite`{.liquidsoap} operator which is implemented in the standard library as described above (see the [cookbook](cookbook.html)). �������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/documentation.md�������������������������������������������������������0000664�0000000�0000000�00000011101�15132732333�0021277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Documentation index **How to use**: Start with the [quickstart](quick_start.html) and make sure you learn [how to find help](help.html). Then it's as you like: go for another [general tutorial](#general), or a [specific example](#specific), pick a [basic notion](#core), or some examples from the [cookbook](cookbook.html). If you've understood all you need, just browse the [reference](reference.html) and compose your dream stream. If you downloaded a source tarball of liquidsoap, you may first read the [build instructions](build.html). If you are migrating from a previous version, you might want to checkout [this page](migrating.html). ## General tutorials - [The book](bool.html): The Liquidsoap book - [Video presentations](presentations.html): some presentations we did about liquidsoap - [How to find help](help.html) about operators, settings, server commands, etc. - [Frequently Asked Questions, Troubleshooting](faq.html) - [Quickstart](quick_start.html): where anyone should start. - [Complete case analysis](complete_case.html): an example that is not a toy. - [Cookbook](cookbook.html): contains lots of idiomatic examples. ## Reference - [Script language](language.html): A more detailed presentation. - [Core API](reference.html): The core liquidsoap API - [Extra API](reference-extra.html): Extra functions and libraries. - [Protocols](protocols.html): List of protocols supported by liquidsoap. - [Settings](settings.html): The list of available settings for liquidsoap. - [FFmpeg](ffmpeg.html): FFmpeg support documentation. - [Encoding formats](encoding_formats.html): The available formats for encoding outputs. - [Videos streams](video.html): Use `liquidsoap` for video streams - [JSON import/export](json.html): Importing and exporting language values in JSON. - [Playlist parsers](playlist_parsers.html): Supported playlist formats. - [LADSPA plugins](ladspa.html): Using LADSPA plugins. - [Database](database.html): Support for SQL databases. ## Core - Basic concepts: [sources](sources.html), [clocks](clocks.html) and [requests](requests.html). - [Stream contents](stream_content.html): what kind of streams are supported, and how. - [Script loading](script_loading.html): load several scripts, learn about the script library. - [Execution phases](phases.html) ## Specific tutorials - [Blank detection](blank.html) - [Customize metadata](metadata.html) - [Dynamic source creation](dynamic_sources.html): dynamically create sources using server requests. - [External decoders](external_decoders.html): use an external program for decoding audio files. - [External encoders](external_encoders.html): use an external audio encoding program. - [External streams](external_streams.html): use an external program for streaming audio data. - [HLS output](hls_output.html): output your stream as HTTP Live Stream. - [HTTP input](http_input.html): relay external streams. - [Harbor input](harbor.html): receive streams from icecast and shoutcast source clients. - [ICY metadata update](icy_metadata.html): manipulate and configure metadata update in Icecast. - [Interaction with the Harbor](harbor_http.html): interact with a running Liquidsoap using the Harbor server. - [Interaction with the server](server.html) interact with a running Liquidsoap instance using the telnet server. - [Loudness Normalization](loudness_normalization.html): normalize audio data using LUFS or ReplayGain. - [Profiling](profiling.html): profiling your scripts. - [Prometheus reporting](prometheus.html): metrics reporting via prometheus. - [Requests-based sources](request_sources.html): create advanced sources using requests. - [Seek and cue support](seek.html): seek and set cue-in and cue-out points in sources. - [Shoutcast output](shoutcast.html): output to shoutcast. - [Smart crossfading](smartcrossfade.html): define custom crossfade transitions. - [Using in production](in_production.html): integrate liquidsoap scripts in a production environment. ## User scripts - [Beets](beets.html): an example of a music database integration. - [Geekradio](geekradio.html) - [RadioPi](radiopi.html) - [Frequence3](frequence3.html) - [Video with a single static image](video-static.html) - [Split a CUE sheet](split-cue.html) ## Code snippets - [Code example index](scripts/index.html) ## Behind the curtains - [Some presentations and publications](../publications.html) explaining the theory underlying Liquidsoap - [OCaml libraries](../modules.html) used in Liquidsoap, that can be reused in other projects - [Documentation of some internals](../modules/liquidsoap/index.html) of Liquidsoap - [Documentation for previous versions](../previously.html) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/dynamic_sources.md�����������������������������������������������������0000664�0000000�0000000�00000001772�15132732333�0021632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Liquidsoap supports dynamic creation and destruction of sources during the execution of a script. The following gives an example of this. First some outlines: - This example is meant to create a new source and outputs. It is not easy currently to change a source being streamed - The idea is to create a new output using a telnet/server command. - In order for a Liquidsoap script to run without an active source at startup, it is necessary to include `settings.init.force_start := true` at the start of the script. In this example, we will register a command that dynamically create a new output based on an encoded stream and output it to an arbitrary url, as supported by the ffmpeg copy encoder. This script can be used to create a dynamic restreaming platform. Here's the code: ```{.liquidsoap include="dynamic-source.liq"} ``` After executing this script, you should see two telnet commands: - `restream.start <uri>` - `restream.stop <uri>` which you can use to create/destroy dynamically your sources. ������liquidsoap-2.4.2/doc/content/encoding_formats.md����������������������������������������������������0000664�0000000�0000000�00000022123�15132732333�0021755�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Encoding Formats in Liquidsoap 🎙️ When you're ready to send audio (or video!) out into the world—whether to Icecast, a file, or another system—you'll need to encode it into a suitable format. MP3, Opus, FLAC, WAV... Liquidsoap supports many, and each comes with its own settings and behaviors. At the heart of this process are **encoders**—special values that you pass to output operators to define the desired stream format. This page introduces how encoders work in Liquidsoap, how they influence your pipeline, and what formats and options are available. Don’t worry if it feels abstract at first—it’s a central concept, and it will click as you explore more! 🚀 ## Encoders: More Than Just Compression In Liquidsoap, an encoder is more than just a codec or compression setting. It **defines the structure of the stream**—and that structure must match the source you're feeding into it. For example, if you write: ```liquidsoap output.file(%mp3, "/tmp/foo.mp3", playlist("songs")) ``` You might think: “I want to encode this playlist as MP3.” But under the hood, `%mp3` is a **format specification**. It says: _This output will encode stereo PCM audio in MP3 format._ So your `playlist("songs")` must produce that exact kind of data—PCM audio, stereo. If it doesn’t, Liquidsoap may try to convert it. But it can’t always. 🧠 **Important:** The encoder drives the output type. You must feed it a compatible source. For instance: - `%mp3` expects `format(audio=pcm(stereo))` - `%theora` expects video - `%opus(channels=1)` expects mono audio So, if you use `%mp3` in your output, your source must produce stereo PCM audio. If it doesn't, Liquidsoap will try to adapt it: - Mono sources? Liquidsoap duplicates the channel to make stereo. - Stereo sources for a mono encoder? Liquidsoap averages both channels. ## Encoder Syntax Encoders use a special syntax: ```liquidsoap %encoder_name(parameter1=value1, parameter2=value2) ``` You can omit parameters if defaults are acceptable: ```liquidsoap output.icecast(%mp3, my_source) ``` Parameters are optional (unless noted) and can be reordered. You can also write `mono=true` or `channels=1`; both are equivalent. ## ⚠️ Format Availability Not all encoders are always available in every Liquidsoap build. Some require optional libraries. If an encoder isn’t available, you’ll see something like: ``` Error 12: Unsupported encoder: %xyz(). You must be missing an optional dependency. ``` In this case, you might need to enable an external dependency (if you are installing via `opam`) or rebuild liquidsoap. On our Windows build, only `%ffmpeg` is included due to linking limitations. Luckily, `%ffmpeg` supports many common formats. ## 🔍 Format Reference ### MP3 Mp3 encoder comes in 3 flavors: - `%mp3` or `%mp3.cbr`: Constant bitrate encoding - `%mp3.vbr`: Variable bitrate, quality-based encoding. - `%mp3.abr`: Average bitrate based encoding. Parameters common to each flavor are: - `stereo=true/false`, `mono=true/false`: Encode stereo or mono data (default: `stereo`). - `stereo_mode`: One of: `"stereo"`, `"joint_stereo"` or `"default"` (default: `"default"`). Default means that the underlying library (`libmp3lame`) will pick the stereo mode based on compression ration and input channels. - `samplerate=44100`: Encoded data samplerate (default: `44100`) - `internal_quality=2`: Lame algorithms internal quality. A value between `0` and `9`, `0` being highest quality and `9` the worst (default: `2`). - `id3v2=true`: Add an `id3v2` tag to encoded data (default: `false`). Parameters for `%mp3` are: - `bitrate`: Encoded data fixed bitrate Parameters for `%mp3.vbr` are: - `quality`: Quality of encoded data; ranges from `0` (highest quality) to `9` (worst quality). Parameters for `%mp3.abr` are: - `bitrate`: Average bitrate - `min_bitrate`: Minimum bitrate - `max_bitrate`: Maximum bitrate - `hard_min`: Enforce minimal bitrate Examples: - Constant `128` kbps bitrate encoding: `%mp3(bitrate=128)` - Variable bitrate with quality `6` and samplerate of `22050` Hz: `%mp3.vbr(quality=7,samplerate=22050)` - Average bitrate with mean of `128` kbps, maximum bitrate `192` kbps and `id3v2` tags: `%mp3.abr(bitrate=128,max_bitrate=192,id3v2=true)` Optionally, liquidsoap can insert a message within mp3 data. You can set its value using the `msg` parameter. Setting it to `""` disables this feature. This is its default value. ### Shine Shine is the fixed-point mp3 encoder. It is useful on architectures without a FPU, such as ARM. ```liquidsoap %shine(channels=2,samplerate=44100,bitrate=128) ``` ### WAV ```liquidsoap %wav(stereo=true, channels=2, samplesize=16, header=true, duration=10.) ``` If `header` is `false`, the encoder outputs raw PCM. `duration` is optional and is used to set the WAV length header. Because Liquidsoap encodes a possibly infinite stream, there is no way to know in advance the duration of encoded data. Since WAV header has to be written first, by default its length is set to the maximum possible value. If you know the expected duration of the encoded data and you actually care about the WAV length header then you should use the `duration` parameter. ### FFmpeg See detailed [ffmpeg encoders](ffmpeg_encoder.html) article. ### Ogg The following formats can be put together in an Ogg container. The syntax for doing so is `%ogg(x,y,z)` but it is also possible to just write `%vorbis(...)`, for example, instead of `%ogg(%vorbis(...))`. All ogg encoders have a `bytes_per_page` parameter, which can be used to try to limit ogg logical pages size. For instance: ```liquidsoap # Try to limit vorbis pages size to 1024 bytes %vorbis(bytes_per_page=1024) ``` ### Vorbis ```liquidsoap # Variable bitrate %vorbis(samplerate=44100, channels=2, quality=0.3) % Average bitrate %vorbis.abr(samplerate=44100, channels=2, bitrate=128, max_bitrate=192, min_bitrate=64) # Constant bitrate %vorbis.cbr(samplerate=44100, channels=2, bitrate=128) ``` ### Opus Opus is a lossy audio compression made especially suitable for interactive real-time applications over the Internet. Liquidsoap supports Opus data encapsulated into Ogg streams. The encoder is named `%opus` and its parameters are as follows. Please refer to the [Opus documentation](http://www.opus-codec.org/docs/) for information about their meanings and values. - `vbr`: one of `"none"`, `"constrained"` or `"unconstrained"` - `application`: One of `"audio"`, `"voip"` or `"restricted_lowdelay"` - `complexity`: Integer value between `0` and `10`. - `max_bandwidth`: One of `"narrow_band"`, `"medium_band"`, `"wide_band"`, `"super_wide_band"` or `"full_band"` - `samplerate`: input samplerate. Must be one of: `8000`, `12000`, `16000`, `24000` or `48000` - `frame_size`: encoding frame size, in milliseconds. Must be one of: `2.5`, `5.`, `10.`, `20.`, `40.` or `60.`. - `bitrate`: encoding bitrate, in `kbps`. Must be a value between `5` and `512`. You can also set it to `"auto"`. - `channels`: currently, only `1` or `2` channels are allowed. - `mono`, `stereo`: equivalent to `channels=1` and `channels=2`. - `signal`: one of `"voice"` or `"music"` ### Theora ```liquidsoap %theora(quality=40,width=640,height=480, picture_width=255,picture_height=255, picture_x=0, picture_y=0, aspect_numerator=1, aspect_denominator=1, keyframe_frequency=64, vp3_compatible=false, soft_target=false, buffer_delay=5, speed=0) ``` You can also pass `bitrate=x` explicitly instead of a quality. The default dimensions are liquidsoap's default, from the settings `frame.video.height/width`. ### Speex ```liquidsoap %speex(stereo=false, samplerate=44100, quality=7, mode=wideband, # One of: wideband|narrowband|ultra-wideband frames_per_packet=1, complexity=5) ``` You can also control quality using `abr=x` or `vbr=y`. ### Flac The flac encoding format comes in two flavors: - `%flac` is the native flac format, useful for file output but not for streaming purpose - `%ogg(%flac,...)` is the ogg/flac format, which can be used to broadcast data with icecast The parameters are: ```liquidsoap %flac(samplerate=44100, channels=2, compression=5, bits_per_sample=16) ``` `compression` ranges from 0 to 8 and `bits_per_sample` should be one of: `8`, `16`, `24` or `32`. Please note that `32` bits per sample is currently not supported by the underlying `libflac`. ### FDK-AAC This encoder can do both AAC and AAC+. Its syntax is: ```liquidsoap %fdkaac(channels=2, samplerate=44100, bandwidth="auto", bitrate=64, afterburner=false, aot="mpeg2_he_aac_v2", transmux="adts", sbr_mode=false) ``` Where `aot` is one of: `"mpeg4_aac_lc"`, `"mpeg4_he_aac"`, `"mpeg4_he_aac_v2"`, `"mpeg4_aac_ld"`, `"mpeg4_aac_eld"`, `"mpeg2_aac_lc"`, `"mpeg2_he_aac"` or `"mpeg2_he_aac_v2"` `bandwidth` is one of: `"auto"`, any supported integer value. `transmux` is one of: `"raw"`, `"adif"`, `"adts"`, `"latm"`, `"latm_out_of_band"` or `"loas"`. Bitrate can be either constant by passing: `bitrate=64` or variable: `vbr=<1-5>` You can consult the [Hydrogenaudio knowledge base](http://wiki.hydrogenaud.io/index.php?title=Fraunhofer_FDK_AAC) for more details on configuration values and meanings. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/external_decoders.md���������������������������������������������������0000664�0000000�0000000�00000002050�15132732333�0022123�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Introduction You can use external programs in liquidsoap to decode audio files. ## Basic operators External decoders are registered using decoder.add`. ```liquidsoap decoder.add(name="my_decoder",description="My custom decoder", file_extensions=["foo"], decoder) ``` - `file_extensions` is a list of file extensions that the decoder can handle. - `decoder` is a function used to return the decoded file. - You can also use the `mimes` argument to file files based on their mime-type. The `decoder` function has a similar signature as protocol resolution functions. This is because file decoding happen as part of the protocol resolution. It takes a `rlog` function, a `maxtime` maximum execution time stamp and an input file and returns a decoded URI. Decoded URI can be any url to pass down to the protocol resolution pipeline. Most of the time, it should be a decoded file but it could also be a `annotate` uri if you wish to also pass down decoded metadata along with the decoded file. ```{.liquidsoap include="decoder-openmpt.liq"} ``` ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/external_encoders.md���������������������������������������������������0000664�0000000�0000000�00000006547�15132732333�0022154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Introduction You can use any external program that accepts wav or raw PCM data to encode audio data and use the resulting compressed stream as an output, either to a file, a pipe, or even icecast. When using an external encoding process, uncompressed PCM data will be sent to the process through its standard input (`stdin`), and encoded data will be read through its standard output (`stdout`). When using a process that does only file input or output, `/dev/stdin` and `/dev/stdout` can be used, though this may generate issues if the encoding process expects to be able to go backward/forward in the file. ## External encoders The main operators that can be used with external encoders are: - `output.file` - `output.icecast` In order to use external encoders with these operators, you have to use the `%external` [encoding format](encoding_formats.html). Its syntax is: ```liquidsoap %external(channels=2,samplerate=44100,header=true, restart_on_crash=false, restart_on_metadata, restart_after_delay=30, process="progname") ``` The available options are: - `process`: this parameter is a function that takes the current metadata and return the process to start. - `header`: if set to `false` then no WAV header will be added to the data fed to the encoding process, thus the encoding process shall operate on RAW data. - `restart_on_crash`: whether to restart the encoding process if it crashed. Useful when the external process fails to encode properly data after some time. - `restart_on_metadata`: restart encoding process on each new metadata. Useful in conjunction with the `process` parameter for audio formats that need a new header, possibly with metadatas, for each new track. This is the case for the ogg container. - `restart_encoder_delay`: Restart the encoder after some delay. This can be useful for encoders that cannot operate on infinite streams, or are buggy after some time, like the `lame` binary. The default for `lame` and `accplusenc`-based encoders is to restart the encoder every hour. Only one of `restart_encoder_delay` or `restart_on_new_track` should be used. The restart mechanism strongly relies on the good behaviour of the encoding process. The restart operation will close the standard input of the encoding process. The encoding process is then expected to finish its own operations and close its standard output. If it does not close its standard output, the encoding task will not finish. If your encoding process has this issue, you should turn the `restart_on_crash` option to `true` and kill the encoding process yourself. If you use an external encoder with the `output.icecast` operator, you should also use the following options of `output.icecast`: - `icy_metadata`: send new metadata as ICY update. This is the case for headerless formats, such as MP3 or AAC, and it appears to work also for ogg/vorbis streams. - `format`: Content-type (mime) of the data sent to icecast. For instance, for ogg data, it is one of `"application/ogg"`, `"audio/ogg"` or `"video/ogg"` and for mp3 data it is `"audio/mpeg"`. ## Video support Videos can also be encoded by programs able to read files in avi format from standard input. To use it, the flag `video=true` of `%external` should be used. For instance, a compressed avi file can be generated with `ffmpeg` using ```{.liquidsoap include="external-output.file.liq" from="BEGIN"} ``` ���������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/external_streams.md����������������������������������������������������0000664�0000000�0000000�00000002460�15132732333�0022016�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Introduction You can use an external program to create a source that will read data coming out of the standard output (`stdout`) of this program. Contrary to the external file decoders, data will be buffered and played when a sufficient amount was accumulated. The program should output data in signed 16 bits little endian PCM (s16le). Number of channels and samplerate can be specified. There is no need of any wav header in the data, though it should work too. ## Basic operator The basic operators for creating an external stream are `input.external.wav`, `input.external.rawaudio`, `input.external.avi` and `input.external.rawvideo` (depending on the format of the data produced by the external program). The parameters for the two first are - `buffer`: Duration of the pre-buffered data. - `max`: Maximum duration of the buffered data. - `channels`: Number of channels. - `samplerate`: Sample rate. - `restart`: Restart the process when it has exited normally. - `restart_on_error`: Restart the process when it has exited with error. The last parameter is unlabeled. It is a string containing the command that will be executed to run the external program. ## Wrappers A wrapper, `input.mplayer`, is defined to use mplayer as the external decoder. Its code is: ```{.liquidsoap include="input.mplayer.liq"} ``` ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/faq.md�����������������������������������������������������������������0000664�0000000�0000000�00000020755�15132732333�0017214�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Frequently Asked Questions ## What does this message means? ### Type error Liquidsoap might also reject a script with a series of errors of the form ` this value has type ... but it should be a subtype of ...` . Usually the last error tells you what the problem is, but the previous errors might provide a better information as to where the error comes from. For example, the error might indicate that a value of type `int` has been passed where a float was expected, in which case you should use a conversion, or more likely change an integer value such as `13` into a float `13.`. A type error can also show that you're trying to use a source of a certain content type (e.g., audio) in a place where another content type (e.g., pure video) is required. In that case the last error in the list is not the most useful one, but you will read something like this above: ``` At ...: Error 5: this value has type source(video=canvas(_),...) but it should be a subtype of source(audio=pcm(_),...) ``` Sometimes, the type error actually indicates a mistake in the order or labels of arguments. For example, given `output.icecast(mount="foo.ogg",source)` liquidsoap will complain that the second argument is a source (`source(?A)`) but should be a format (`format(?A)`): indeed, the first unlabelled argument is expected to be the encoding format, e.g., `%vorbis`, and the source comes only second. Finally, a type error can indicate that you have forgotten to pass a mandatory parameter to some function. For example, on the code `fallback([source.mux.audio(x),...])`, liquidsoap will complain as follows: ``` At line ...: Error 5: this value has type [(?id : _, audio : _) -> _] but it should be a subtype of the type of the value at ../libs/switches.liq, line 11, char 11-18 [source(_)] (inferred at ../libs/list.liq, line 102, char 29) ``` Indeed, `fallback` expects a source, but `source.mux.audio(x)` is still a function expecting the `audio` parameter. ### That source is fallible! See the [quickstart](quick_start.html), or read more about [sources](sources.html). ### Clock error Read about [clocks](clocks.html) for the errors `a source cannot belong to two clocks` and `cannot unify two nested clocks`. ### We must catchup x.xx! This error means that a clock is getting late in liquidsoap. This can be caused by an overloaded CPU, if your script is doing too much encoding or processing: in that case, you should reduce the load on your machine or simplify your liquidsoap script. The latency may also be caused by some lag, for example a network lag will cause the icecast output to hang, making the clock late. The first kind of latency is problematic because it tends to accumulate, eventually leading to the restarting of outputs: ``` Too much latency! Resetting active source... ``` The second kind of latency can often be ignored: if you are streaming to an icecast server, there are several buffers between you and your listeners which make this problem invisible to them. But in more realtime applications, even small lags will result in glitches. In some situations, it is possible to isolate some parts of a script from the latency caused by other parts. For example, it is possible to produce a clean script and back it up into a file, independently of its output to icecast (which again is sensitive to network lags). For more details on those techniques, read about [clocks](clocks.html). ### Unable to decode ``file'' as {audio=pcm}! This log message informs you that liquidsoap failed to decode a file, not necessarily because it cannot handle the file, but also possibly because the file does not contain the expected media type. For example, if audio and video is expected, an audio file with no video will be rejected. Liquidsoap is also able to convert audio channels in most situations. Typically, if stereo data is expected but the file contains mono audio, liquidsoap will use the single audio channel as both left and right channels. ### Runtime exceptions Liquidsoap scripts can raise runtime errors of the form: ``` At line 3, char 45: Error 14: Uncaught runtime error: type: not_found, message: "File not found!" ``` These are errors that the script programmer can catch and decide what to do when they occur. Such errors will typically occur when trying to read a file that does not exist and etc. The [language page](language.html) has more details about errors, how to raise them and how to catch them. You can head over there to get more information. ### Crashes Liquidsoap dies with messages such as these by the end of the log: ``` ... [threads:1] Thread "XXX" aborts with exception YYY! ... [stderr:3] Thread 2 killed on uncaught exception YYY. ... [stderr:3] Raised at file ..., line ..., etc. ``` Those internal errors can be of two sorts: - **Bug**: Normally, this means that you've found a bug, which you should report on the mailing list or bug tracker. - **User error**: In some cases, we let an exception go on user errors, instead of nicely reporting and handling it. By looking at the surrounding log messages, you might realize that liquidsoap crashed for a good reason, that you are responsible for fixing. You can still report a bug: you should not have seen an exception and its backtrace. In any case, once that kind of error happens, there is no way for the user to prevent liquidsoap from crashing. Those exceptions cannot be caught or handled in any way at the level of liquidsoap scripts. ## Troubleshooting ### Pulseaudio When using ALSA input or output or, more generally any audio input or output that is not using pulseaudio, you should disable pulseaudio, which is often installed by default. Pulseaudio emulates ALSA but this also generates bugs, in particular errors of this form: ``` Alsa.Unknown_error(1073697252)! ``` There are two things you may do: - Make sure your alsa input/output does not use pulseaudio - Disable pulseaudio on your system In the first case, you should first find out which sound card you want to use, with the command `aplay -l`. An example of its output is: ``` **** List of PLAYBACK Hardware Devices **** card 0: Intel [HDA Intel], device 0: STAC92xx Analog [STAC92xx Analog] Subdevices: 1/1 Subdevice #0: subdevice #0 ``` In this case, the card we want to use is: device `0`, subdevice `0`, thus: `hw:0,0`. We now create a file `/etc/asound.conf` (or `~/.asoundrc` for single-user configuration) that contains the following: ```liquidsoap pcm.liquidsoap { type plug slave { pcm "hw:0,0" } } ``` This creates a new alsa device that you can use with liquidsoap. The `plug` operator in ALSA is used to work-around any hardware limitations in your device (mixing multiple outputs, resampling etc.). In some cases you may need to read more about ALSA and define your own PCM device. Once you have created this device, you can use it in liquidsoap as follows: ```liquidsoap input.alsa(device="pcm.liquidsoap", ...) ``` In the second case -- disabling pulseaudio, you can edit the file `/etc/pulse/client.conf` and change or add this line: ``` autospawn = no ``` And kill any running pulseaudio process: ``` killall pulseaudio ``` Otherwise you may simply remove pulseaudio's packages, if you use Debian or Ubuntu: ``` apt-get remove pulseaudio libasound2-plugins ``` ### Listeners are disconnected at the end of every track Several media players, including renowned ones, do not properly support Ogg/Vorbis streams: they treat the end of a track as an end of file, resulting in the disconnection. Players that are affected by this problem include VLC. Players that are not affected include ogg123, liquidsoap. One way to work around this problem is to not use Ogg/Vorbis (which we do not recommend) or to not produce tracks within a Vorbis stream. This is done by dropping both metadata and track marks (for example using `source.drop.metadata_track_marks`). ### Encoding blank Encoding pure silence is often too effective for streaming: data is so compressed that there is nothing to send to listeners, whose clients eventually disconnect. Therefore, it is a good idea to use a non-silent jingle instead of `blank()` to fill in the blank. You can also achieve various effects using synthesis sources such as `noise()`, `sine()`, etc. ### Temporary files Liquidsoap relies on OCaml's `Filename.tmp_dir_name` variable to store temporary files. It is documented as follows: The name of the temporary directory: Under Unix, the value of the `TMPDIR` environment variable, or `"/tmp"` if the variable is not set. Under Windows, the value of the `TEMP` environment variable, or `"."` if the variable is not set. �������������������liquidsoap-2.4.2/doc/content/ffmpeg.md��������������������������������������������������������������0000664�0000000�0000000�00000026210�15132732333�0017701�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# FFmpeg Support Since the `2.0.x` release cycle, liquidsoap integrates a tight support of ffmpeg. This includes: - [Decoders](#decoders) - [Encoders](#encoders) - [Filters](#filters) - [Bitstream filters](#bitstream-filters) - [Encoded data tweaks](#encoded-data-tweaks) - [Examples](#examples) Ffmpeg support includes 3 types of content: - **Internal content**, that is content available to all liquidsoap operators: `PCM` audio and `YUV420p` video - **Raw content**, that is decoded content but stored as ffmpeg internal frame. This type of content is only available to ffmpeg filters and raw encoders. It can be used to avoid data copies back and forth between liquidsoap and ffmpeg. - **Copy content**, that is encoded content stored as ffmpeg internal packets. This type of content is only available to ffmpeg copy encoder and bitstream filters and requires a fairly good understanding of media codecs and containers. Copy contents can be used to avoid transcoding and pass encoded data end-to-end inside liquidsoap scripts. ## Enabling ffmpeg support FFmpeg support is available via the external [ocaml-ffmpeg](https://github.com/savonet/ocaml-ffmpeg) binding package. If you are using any binary asset from our release pages or via docker, this should already be included. If you are installing via [opam](https://opam.ocaml.org/), installing the `ffmpeg` package should do the trick: ```sh % opam install ffmpeg ``` ### fdk-aac support in ffmpeg One common question is how to install `ffmpeg` with `fdk-aac` support. This can get tricky because you need the _ffmpeg shared libraries_ compiled with `libfdk-aac`. This means that installing `libfdk-aac` alone will not be enough, you might also need to recompile `ffmpeg` to take advantage of it. When recompiling `ffmpeg`, make sure that the `--enable-shared` argument is passed to the `configure` script. Also, compiling the shared libraries is different than downloading the `ffmpeg` command line. Most `ffmpeg` downloads include a _static build_ of ffmpeg that is, one that does not use or provide shared libraries. On linux platforms, you can check what dynamic libraries liquidsoap is using using ```shell ldd /path/to/liquidsopap ``` On macos, you can use `otool -L`. In the list of libraries, you should see `libavcodec`. In turn, you should be able to use the same command to inspect the libraries required by the `libavcodec` used by the `liquidsoap` binary. If this includes `libfdk-aac`, you're good to go! On debian, you might be able to use [deb-multimedia.org](https://www.deb-multimedia.org/) to install a build of `ffmpeg` with `libfdk-aac` enabled. You are advised to follow the instructions on the website for the latest up-to date guide. You may also refer to [this conversation](https://github.com/savonet/liquidsoap/discussions/3027#discussioncomment-6072338). ## Decoders For the most part, you should never have to worry about the `ffmpeg` decoder. When enabled, it should be the preferred decoder for all supported media. When using raw or copied content, the decoder is able to produce the required content without the need of any intervention on the user part. Should you need to tweak it, here are a couple of pointers: The `settings.decoder.decoders` settings controls which decoders are to be used when trying to decode media files. You can use it to restrict which decoders are being used, for instance making sure only the ffmpeg decoder is used: ```liquidsoap settings.decoder.decoders := ["FFMPEG"] ``` Priority for the decoder is set via: ```liquidsoap settings.decoder.priorities.ffmpeg := 10 ``` You can use this setting to adjust whether or not the ffmpeg decoder should be tried first when decoding media files, in particular in conjunction with the other `settings.decoder.priorities.*` settings. For each type of media codec, the `settings.decoder.ffmpeg.codecs.*` settings can be used to tell `ffmpeg` which decoder to use to decode this type of content (there could more than one decoder for a given codec). For instance, for the `aac` codec: - `settings.decoder.ffmpeg.codecs.aac.available()` returns the list of available decoders, typically `["aac", "aac_fixed"]`. - `settings.decoder.ffmpeg.codecs.aac` can be used to choose which decoder should be used, typically: `settings.decoder.ffmpeg.codecs.aac := "aac"` When debugging issues with `ffmpeg`, it can be useful to increase the log verbosity. ```liquidsoap settings.ffmpeg.log.verbosity := "warning" ``` This settings sets the verbosity of `ffmpeg` logs. Possible values, from less verbose to more verbose are: `"quiet"`, `"panic"`, `"fatal"`, `"error"`, `"warning"`, `"info"`, `"verbose"` or `"debug"` Please note that, due to a technical limitation, we are not yet able to route `ffmpeg` logs through the liquidsoap logging facilities, which means that `ffmpeg` logs are currently only printed to the process's standard output and that the `settings.ffmpeg.log.level` is currently not used. ### Decoder arguments In some cases, for instance when sending raw PCM data, it might be required to pass some arguments to the ffmpeg decoder to let it know what kind of format, codec, etc. it should decode. There are two ways to do that: - For _streams_, the `content_type` argument can be used. The convention is to use `"application/ffmpeg;<arguments>"`. - For _files_, the `ffmpeg_options` metadata can be used, for instance using the `annotate` protocol: `annotate:ffmpeg_options="<arguments>":/path/to/file.raw` Here's an example of a SRT input and output that can be used to send raw PCM data between two instances: Sender: ```liquidsoap enc = %ffmpeg( format="s16le", %audio( codec="pcm_s16le", ac=2, ar=48000 ) ) output.srt(enc, s) ``` Receiver: ```liquidsoap s = input.srt( content_type="application/ffmpeg;format=s16le,ch_layout=stereo,sample_rate=48000" ) ``` If, instead of using `output.srt` above, we were using `output.file` and saving to a file named `bla.raw`, this file could be read with a `single` source this way: ```liquidsoap s = single("annotate:ffmpeg_options='format=s16le,ch_layout=stereo,sample_rate=44100':/tmp/bla.raw") ``` This could also be done in a `playlist` or `request.dynamic` and etc. ## Encoders See detailed [ffmpeg encoders](ffmpeg_encoder.html) article. ## Filters See detailed [ffmpeg filters](ffmpeg_filters.html) article. ## Bitstream filters FFmpeg bitstream filters are filters that modify the binary content of _encoded data_. They can be used to adjust certain aspects of media codecs and containers to make them fit some specific use, for instance a rtmp/flv output etc. They are particularly important when dealing with live switches of encoded content (see [Examples](#examples) section). The list of all bitstream filters is documented on [FFmpeg](https://www.ffmpeg.org/ffmpeg-bitstream-filters.html) online doc and our [extra API reference](reference-extras.html). Here's one such filter: ```liquidsoap % liquidsoap -h ffmpeg.filter.bitstream.h264_mp4toannexb FFmpeg h264_mp4toannexb bitstream filter. See ffmpeg documentation for more details. Type: (?id : string?, source(video=ffmpeg.copy('a), 'b)) -> source(video=ffmpeg.copy('a), 'b) Category: Source / FFmpeg filter Arguments: * id : string? Force the value of the source ID. * (unlabeled) : source(video=ffmpeg.copy('a), 'b) Methods: ... ``` Please consult the FFmpeg documentation for more details about that each filter do and why/how to use them. ## Encoded data tweaks Manipulating encoded content is powerful but can sometimes require some specific knowledge of internals aspects of media codecs and containers. This section lists some specific cases. ### Relaxed copy content compatibility check By default, liquidsoap keeps track of the content passed in a stream containing ffmpeg encoded content (`ffmpeg.copy`) and only allows file and stream decoders to return strictly compatible content, e.g. same video resolution or audio samplerate. Some containers such as `mp4`, however, do allow stream where video resolution or audio samplerate changes between tracks. In this case, you can relax those compatibility checks using the following setting: ```liquidsoap settings.ffmpeg.content.copy.relaxed_compatibility_check := true ``` This is a global setting for now and could be refined per-stream in the future if the needs arises. ### Shared encoders `liquisoap` provides operators to encode data using `%ffmpeg` and reuse it across output. This is called _inline encoding_. Here's an example: ```liquidsoap audio_source = single(audio_url) video_source = single(image) stream = source.mux.video(video=video_source, audio_source) stream = ffmpeg.encode.audio_video( %ffmpeg( %audio(codec="aac", b="128k"), %video(codec="libx264", b="4000k") ), stream ) flv = %ffmpeg( format="flv", %audio.copy, %video.copy, ) # Send to one youtube output: output.youtube.live.rtmp( encoder = flv, stream, ... ) mpegts = %ffmpeg( format="mpegts", %audio.copy, %video.copy, ) # And to a hls one: output.file.hls( ["mpegts", mpegts], stream, ... ) ``` Working with encoded data, however, requires a bit of knowledge of ffmpeg internal and media codecs and containers. Here, for instance, this stream will have issues because the `flv` format requires global data, something that in ffmpeg terms is called `extradata`. When working with a single encoder such as: ```liquidsoap %ffmpeg( format="flv", %audio(codec="aac", b="128k"), %video(codec="libx264", b="4000k") ) ``` We are aware when initializing the encoders that it is aimed for a `flv` container so the code implicitly enables the global header for each encoder. However, when encoding inline, we do not know at the time of encoding the container that will be used to encapsulate the stream, even worst, it can be used potentially with different containers with different requirements! In our case here, you have two ways to solve the issue: If you know that all the containers will be okay with global header, you can enable the corresponding flag in the encoder: ```liquidsoap stream = ffmpeg.encode.audio_video( %ffmpeg( %audio(codec="aac", b="128k", flags="+global_header"), %video(codec="libx264", b="4000k", flags="+global_header") ), stream ) ``` However, it is also possible that one stream needs global header but not the other one, which is the case here with `mpegts`. In this case, you can use the _bitstream filter_ `ffmpeg.filter.bitstream.extract_extradata` to extract global data to only one stream: ``` audio_source = single(audio_url) video_source = single(image) stream = source.mux.video(video=video_source, audio_source) stream = ffmpeg.encode.audio_video( %ffmpeg( %audio(codec="aac", b="128k"), %video(codec="libx264", b="4000k") ), stream ) flv = %ffmpeg( format="flv", %audio.copy, %video.copy, ) flv_stream = ffmpeg.filter.bitstream.extract_extradata(stream) # Send to one youtube output: output.youtube.live.rtmp( encoder = flv, flv_stream, ... ) mpegts = %ffmpeg( format="mpegts", %audio.copy, %video.copy, ) # And to a hls one: output.file.hls( ["mpegts", mpegts], stream, ... ) ``` ## Examples See detailed [ffmpeg cookbook](ffmpeg_cookbook.html) article. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/ffmpeg_cookbook.md�����������������������������������������������������0000664�0000000�0000000�00000006551�15132732333�0021575�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# FFmpeg cookbook Here are some examples of what is possible to do with the ffmpeg support in liquidsoap: ## Relaying without re-encoding With ffmpeg support, Liquidsoap can relay encoded streams without re-encoding them, making it possible to re-send a stream to multiple destinations. Here's an example: ```{.liquidsoap include="ffmpeg-relay.liq"} ``` We cannot use `mksafe` here because the content is not plain `pcm` samples, which this operator is designed to produce. There are several ways to make the source infallible, however, either by providing a `single(...)` source with the same encoded content as we expect from `encoded_source` or by creating an infallible source using `ffmpeg.encode.audio`. ## On-demand relaying without re-encoding Another refinement on the previous example is the capacity to relay a stream only when listeners are connected to it, all without re-encoding the content. To make it work, you will need a format that can be handled by `ffmpeg` for that purpose. `mp3` is a good example. In the script below, you need to match the encoded format of the stream with a blank file (or any other file). The `output.harbor` will then relay the data from the file if no one is connected and start/stop the underlying input when there are listeners: ```{.liquidsoap include="ffmpeg-relay-ondemand.liq"} ``` ## Shared encoding Liquidsoap can also encode in one place and share the encoded with data with multiple outputs, making it possible to minimize CPU resources. Here's an example adapted from the previous one: ```{.liquidsoap include="ffmpeg-shared-encoding.liq"} ``` Shared encoding is even more useful when dealing with video encoding, which is very costly. Here's a fun example sharing audio and video encoding and sending to different destinations, both via Icecast and to YouTube/Facebook via the rtmp protocol: ```{.liquidsoap include="ffmpeg-shared-encoding-rtmp.liq"} ``` ## Add transparent logo and video See: https://github.com/savonet/liquidsoap/discussions/1862 ## Live switch between encoded content _This is an ongoing development effort. Please refer to the online support channels if you are experiencing issues with this kind of feature._ Starting with liquidsoap `2.1.x`, it is gradually becoming possible to do proper live switches on encoded content and send the result to different outputs. Please note that this requires a solid knowledge of media codecs, containers and ffmpeg bitstream filters. Different input and output containers store codec binary data in different ways and those are not always compatible. This requires the use of bitstream filters to adapt the binary data and, it's possible some new filters will need to be written to support more combinations of input/output and codecs. Here's a use case that has been tested: live switch between a playlist of mp4 files and a rtmp flv input: ```{.liquidsoap include="live-switch.liq"} ``` - We need the `h264_mp4toannexb` on each stream to make sure that the mp4 data conforms to what the mpegts container expect - We need to disable ffmpeg's automatic insertion of bitstream filters via `-autobsf`. FFmpeg does not support this kind of live switch at the moment and its automatically inserted filters won't work, which is why we're doing it ourselves. That's it! In the future we want to extend this use-case to also be able to output to a `rtmp` output from the same data. And more! �������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/ffmpeg_encoder.md������������������������������������������������������0000664�0000000�0000000�00000020754�15132732333�0021407�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# FFmpeg encoder The `%ffmpeg` encoder should support all the options for `ffmpeg`'s [muxers](https://ffmpeg.org/ffmpeg-formats.html#Muxers) and [encoders](https://www.ffmpeg.org/ffmpeg-codecs.html), including private configuration options. Configuration value are passed as key/values, with values being of types: `string`, `int`, or `float`. If an option is not recognized (or: unused), it will raise an error during the instantiation of the encoder. Here are some configuration examples: ### Interleaved muxing FFmpeg provides two different APIs for muxing data, interleaved or not. The interleaved API buffers packets waiting to be outputted to make sure that all streams, e.g. audio and video, have their packets as close to each other as possible. This ensures that for instance, the stream does not start with a long chunk of audio data without any video content. However, this can come with some increased memory usage due to buffering. On the other hand, the non-interleaved API allows to send encoded packets directly to the output without intermediate buffering. This can sometimes result in better latency and lower memory usage. The `%ffmpeg` encoder can use either API. By default, it uses the interleaved API when encoding more than one stream. You can also specify the interleaving mode by passing the `interleaved` parameter: `%ffmpeg(interleaved=<true|false|"default">, ...)`. You might also want to take this into consideration when setting your encoder's parameters. Some video encoders can buffer frames for a while before outputting the first encoded frame, which can also create issues even with the interleaved API enabled (the interleaving buffer has a max size too!). Typically, with `libx264`, you can set `tune = "zerolatency"` to make sure that the encoder starts outputting data right away. ### Encoding examples - **AAC encoding at `22050kHz` using `fdk-aac` encoder and `mpegts` muxer** ```liquidsoap %ffmpeg(format="mpegts", %audio( codec="libfdk_aac", samplerate=22050, b="32k", afterburner=1, profile="aac_he_v2" ) ) ``` - **Mp3 encoding using `libshine` at `48000kHz`** ```liquidsoap %ffmpeg(format="mp3", %audio(codec="libshine", samplerate=48000)) ``` - **AC3 audio and H264 video encapsulated in an MPEG-TS stream** ```liquidsoap %ffmpeg( format="mpegts", %audio(codec="ac3", channel_coupling=0), %video( codec="libx264", b="2600k", "x264-params"="scenecut=0:open_gop=0:min-keyint=150:keyint=150", preset="ultrafast" ) ) ``` - **AC3 audio and H264 video encapsulated in an MPEG-TS stream using ffmpeg raw frames** ```liquidsoap %ffmpeg( format="mpegts", %audio.raw(codec="ac3", channel_coupling=0), %video.raw( codec="libx264", b="2600k", "x264-params"="scenecut=0:open_gop=0:min-keyint=150:keyint=150", preset="ultrafast" ) ) ``` - **Mp3 encoding using `libmp3lame` and video copy** ```liquidsoap %ffmpeg( format="mp3", %audio(codec="libmp3lame"), %video.copy ) ``` The full syntax is as follows: ```liquidsoap %ffmpeg( format=<format>, # Metadata to be passed when initializing the output metadata=[("label","value"), ...], # Audio section %audio(codec=<codec>, <option_name>=<option_value>, ...), # Or: %audio.raw(codec=<codec>, <option_name>=<option_value>, ...), # Or: %audio.copy(<option>), # Video section %video(codec=<codec>, <option_name>=<option_value>, ...), # Or: %video.raw(codec=<codec>, <option_name>=<option_value>, ...), # Or: %video.copy(<option>), # Generic options <option_name>=<option_value>, ... ) ``` Where: - `<format>` is either a string value (e.g. `"mpegts"`), as returned by the `ffmpeg -formats` command or `none`. When set to `none` or simply no specified, the encoder will try to auto-detect it. - `<codec>` is a string value (e.g. `"libmp3lame"`), as returned by the `ffmpeg -codecs` command. - `<option_name>` can be any syntactically valid variable name or string. Strings are typically used when the option name is of the form: `foo-bar`. - `%audio(...)` is for options specific to the audio codec. Unused options will raise an exception. Any option supported by `ffmpeg` can be passed here. Streams encoded using `%audio` are using liquidsoap internal frame format and are fully handled on the liquidsoap side. - `%audio.raw(...)` behaves like `%audio` except that the audio data is kept as ffmpeg's internal format. This can avoid data copy and is also the format required to use [ffmpeg filters](ffmpeg_filters.html). - `%audio.copy` copies data without decoding or encoding it. This is great to avoid using the CPU, but in this case, the data cannot be processed with operators that modify it, such as `fade.{in,out}` or `smart_cross`. Also, all streams must agree on the same data format. - `%video(...)` is for options specific to the video codec. Unused options will raise an exception. Any option supported by `ffmpeg` can be passed here. - `%video.raw` and `%video.copy` have the same meaning as their `%audio` counterpart. - Generic options are passed to audio, video and format (container) setup. Unused options will raise an exception. Any option supported by `ffmpeg` can be passed here. ### HLS output The `%ffmpeg` encoder is the prime encoder for HLS output as it is the only one of our collection of encoder which can produce Mpeg-ts muxed data, which is required by most HLS clients. ### File output Some encoding formats, for instance `mp4`, require to rewind their stream and write a header after the fact, when encoding of the current track has finished. For historical reasons, such formats cannot be used with `output.file`. To remedy that, we have introduced the `output.url` operator. When using this operator, the encoder is fully in charge of the output file and can thus write headers after the fact. The `%ffmpeg` encoder is one such encoder that can be used with this operator. ### Copy options The `%audio.copy` and `%video.copy` encoders have two mutually exclusive options to handle keyframes: - `%audio.copy(wait_for_keyframe)` and `%video.copy(wait_for_keyframe)`: Wait until at least one keyframe has been passed to start passing encoded packets from a new stream. - `%audio.copy(ignore_keyframe)` and `%video.copy(ignore_keyframe)`: Ignore all keyframes. These options are useful when switching from one encoded stream to the next. With option `wait_for_keyframe`, the encoder discards any new packet at the beginning of a stream until a keyframe is passed. This means that playback will be paused until it can be resumed properly with no decoding glitches. This option is implemented globally when possible, i.e. in case of a video track with keyframes and an audio track with no keyframes, the audio track will discard packets until a video keyframe has been passed. This is the default option. With option `ignore_keyframe`, the encoder starts passing encoded data right away. Content is immediately added but playback might get stuck until a new keyframe is passed. It is worth noting that some audio encoders may also have keyframes. ### Hardware acceleration The `%ffmpeg` encoder supports multiple hardware acceleration provided by `ffmpeg`. If you are lucky and the encoder you are using provides support for hardware acceleration without any specific configuration, all you might have to do is select `codec="..."` (for instance on macOS, `codec="h264_videotoolbox"`) and it should work immediately. The type of hardware acceleration provided by ffmpeg are: 1. Internal hardware acceleration that works without any specific configuration. This is the happy path described above! 2. Device-based hardware acceleration that works with a specific device. 3. Frame-based hardware acceleration that work with a specific pixel format. The type of hardware acceleration to use for a given stream can be specified using the `hwaccel` option. Its value is one of: `"auto"`, `"none"`, `"internal"`, `"device"` or `"frame"`. For device-based hardware acceleration, the device to use can be specified using `hwaccel_device`. For frame-based hardware acceleration, the pixel format can be specified using `hwaccel_pixel_format`. In most cases, liquidsoap should be able to guess these values from the codec. Here's an example: ```liquidsoap enc = %ffmpeg( format="mpegts", %video( hwaccel="device", hwaccel_devic="/dev/...", ... ) ) ``` Hardware acceleration support is, of course, very hardware dependent, so we might not have tested all possible combinations. If you are having issues setting it up, do not hesitate to get in touch with us to see if your use-case is properly covered. ��������������������liquidsoap-2.4.2/doc/content/ffmpeg_filters.md������������������������������������������������������0000664�0000000�0000000�00000013020�15132732333�0021424�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# FFmpeg filters [FFmpeg filters](https://ffmpeg.org/ffmpeg-filters.html) provide audio and video filters that can be used to transform content using the ffmpeg library. They are enabled in liquidsoap when compiled with the optional [ffmpeg-avfilter](https://github.com/savonet/ocaml-ffmpeg). ## Filter as operators If enabled, the filters should appear as operators, prefixed with `ffmpeg.filter`. For instance: ``` Ffmpeg filter: Add echoing to the audio. Type: (?in_gain : float?, ?out_gain : float?, ?delays : string?, ?decays : string?, ffmpeg.filter.graph, ffmpeg.filter.audio) -> ffmpeg.filter.audio Category: Liquidsoap Parameters: * in_gain : float? (default: null) set signal input gain. (default: 0.6) * out_gain : float? (default: null) set signal output gain. (default: 0.3) * delays : string? (default: null) set list of signal delays. (default: "1000") * decays : string? (default: null) set list of signal decays. (default: "0.5") * (unlabeled) : ffmpeg.filter.graph (default: None) * (unlabeled) : ffmpeg.filter.audio (default: None) ``` Filters input and output are abstract values of type `ffmpeg.filter.audio` and `ffmpeg.filter.video`. They can be created using `ffmpeg.filter.audio.input`, `ffmpeg.filter.video.input`. These operators take [media tracks](multitrack.html) as input. Conversely, tracks can be created from them using `ffmpeg.filter.audio.output` and `ffmpeg.filter.video.output`. Filters are configured within the closure of a function. Here's an example: ```{.liquidsoap include="ffmpeg-filter-flanger-highpass.liq"} ``` This filter receives an audio input, creates a `ffmpeg.filter.audio.input` with it that can be passed to filters, applies a flanger effect and then a high pass effect, creates an audio output from it and returns it. Here's another example for video: ```{.liquidsoap include="ffmpeg-filter-hflip.liq"} ``` This filter receives a video input, creates a `ffmpeg.filter.video.input` with it that can be passed to filters, applies a `hflip` filter (flips the video vertically), creates a video output from it and returns it. ## Applying filters to a source When applying a filter, the input is placed in a clock that is driven by the output. This means that you cannot share other tracks from the input to the output. This can be an annoying source of confusion. Thus, when applying FFMpeg filters to sources with audio and video tracks, it is recommended to pass all the tracks through the filter, even if they are simply copied. Here's an example with the previous filter: ```{.liquidsoap include="ffmpeg-filter-hflip2.liq"} ``` FFmpeg filters are very powerful, they can also convert audio to video, for instance displaying information about the stream, and they can combined into powerful graph processing filters. ## Filter commands Some filters support [changing options at runtime](https://ffmpeg.org/ffmpeg-filters.html#Changing-options-at-runtime-with-a-command) with a command. These are also supported in liquidsoap. In order to do so, you have to use a slightly different API: ```{.liquidsoap include="ffmpeg-filter-dynamic-volume.liq" to="END"} ``` First, we instantiate a volume filter via `ffmpeg.filter.volume.create`. The filter instance has a `process_command`, which we use to create the `set_volume` function. Then, we apply the expected input to the filter and return the pair `(s, set_volume)` of source and function. The `ffmpeg.filter.<filter>.create` API is intended for advanced use if you want to use filter commands. Otherwise, `ffmpeg.filter.<filter>` provides a more straight forward API to filters. ## Filters with dynamic inputs or outputs Filters with dynamic inputs or outputs can have multiple inputs or outputs, decided at run-time. Typically, `ffmpeg.filter.split` splits a video stream into multiple streams and `ffmpeg.filter.merge` merges multiple video streams into a single one. For these filters, the operators' signature is a little different. Here's an example for dynamic outputs: ``` % liquidsoap -h ffmpeg.filter.asplit Ffmpeg filter: Pass on the audio input to N audio outputs. This filter has dynamic outputs: returned value is a tuple of audio and video outputs. Total number of outputs is determined at runtime. Type: (?outputs : int?, ffmpeg.filter.graph, ffmpeg.filter.audio) -> [ffmpeg.filter.audio] * [ffmpeg.filter.video] Category: Liquidsoap Flag: extra Parameters: * outputs : int? (default: null) set number of outputs. (default: 2) * (unlabeled) : ffmpeg.filter.graph (default: None) * (unlabeled) : ffmpeg.filter.audio (default: None) ``` This filter returns a tuple `(audio, video)` of possible dynamic outputs. Likewise, with dynamic inputs: ``` % liquidsoap -h ffmpeg.filter.amerge Ffmpeg filter: Merge two or more audio streams into a single multi-channel stream. This filter has dynamic inputs: last two arguments are lists of audio and video inputs. Total number of inputs is determined at runtime. Type: (?inputs : int?, ffmpeg.filter.graph, [ffmpeg.filter.audio], [ffmpeg.filter.video]) -> ffmpeg.filter.audio Category: Liquidsoap Flag: extra Parameters: * inputs : int? (default: null) specify the number of inputs. (default: 2) * (unlabeled) : ffmpeg.filter.graph (default: None) * (unlabeled) : [ffmpeg.filter.audio] (default: None) * (unlabeled) : [ffmpeg.filter.video] (default: None) ``` This filter receives an array of possible `audio` inputs as well as an array of possible `video` inputs. Put together, this can be used as such: ```{.liquidsoap include="ffmpeg-filter-parallel-flanger-highpass.liq"} ``` ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/flows_devel.md���������������������������������������������������������0000664�0000000�0000000�00000002554�15132732333�0020753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Developing Flows [Flows](flows.html) is handled on the [Heroku](https://www.heroku.com/) platform. ## Getting started First steps to get started. - Create an account on [Heroku](https://www.heroku.com/). - Install the [Heroku utilities](https://toolbelt.heroku.com/). - Ask a Liquidsoap administrator to give you access to the repositories. The repositories of the main components are organized as follows. - `savonet-flows` is the python handler to submit metadata: - the associated [github repository](https://github.com/savonet/flows-submit) - the Heroku repository is `git@heroku.com:savonet-liquidsoap.git` - `savonet-flows-socket` is the node application to serve the webpage and client stuff. - the associated [github repository](https://github.com/savonet/flows-push) - the [test Heroku webpage](http://savonet-flows-socket.herokuapp.com/) is updated by pushing on `git@heroku.com:savonet-flows-socket-next.git` - the [prod Heroku webpage](http://savonet-flows-socket.herokuapp.com/) is updated by pushing on `git@heroku.com:savonet-flows-socket.git` Some more experimental repositories include: - a [command-line client](https://github.com/savonet/flows-client) ## Useful commands Getting the environment variables: ``` heroku config -s --app savonet-flows ``` Seeing the logs of the socket application: ``` heroku logs -t --app savonet-flows-socket ``` ����������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/frequence3.md����������������������������������������������������������0000664�0000000�0000000�00000000752�15132732333�0020500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Fréquence 3 [Fréquence 3](http://www.frequence3.fr) uses Liquidsoap mainly on the backstage, for different purposes: - transcoding for different formats (OGG, weird MP3 relays...) - scheduling and playlist for audio backup streams, and test streams - blank detection They look forward to using Liquidsoap even more, and work with the Savonet team to make sure this tool can ease the work of webradios :) They provide an MP3 stream [here](http://streams.frequence3.net/mp3-128.m3u). ����������������������liquidsoap-2.4.2/doc/content/geekradio.md�����������������������������������������������������������0000664�0000000�0000000�00000004303�15132732333�0020366�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Geek Radio The historical webradio, founded by David Baelde and Samuel Mimram at the ENS Lyon. The very first version was, as many other radios, a Perl function called by Ices. It played files, one by one. On the campus, there was plenty of audio files available, so they soon wanted to index them and be able to ask easily for one file to be streamed. Samuel made a dirty campus indexer in OCaml, and David made an ugly Perl hack for adding user requests to the original system. It probably kind of worked for a while. Then they wanted something more, and realized it was all too ugly. So they made the binding of libshout for OCaml and built the first streamer in pure OCaml. It had a simple telnet interface so an IRC bot could send user requests easily to it, same for the website. There were two request queues, one for users, one for admins. But it was still not so nicely designed, and they felt it when they needed more. They wanted scheduling, especially techno music at night. Around that time students had to set up a project for one of their courses. David and Samuel proposed to build a complete flexible webradio system, that's Savonet. To give jobs to everybody, they had planned a complete rewriting of every part, with grand goals. A new website with so much features, a new intelligent multilingual bot, a new network libraries for glueing that, etc. Most died. But still, Liquidsoap was born, and they had plenty of new libraries for OCaml. Since then, Liquidsoap has been greatly enhanced, and is now spreading outside the ENS Lyon. ## Features The liquidsoap script schedules several static (but periodically reloaded) playlists played on different times, adds jingle to the usual stream every hour, adds short live interventions, or completely switches to live shows when available. It accepts user requests, which have priority over static playlists but not live shows, and adds speech-synthetized metadata information at the end of requests. Geek Radio used to have a Strider daemon running to fill our database. Since that project is now dead, a simple hack is now used instead: bubble. The usual way of sending a request is via an IRC bot, which queries the database and sends the chosen URI to liquidsoap. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/graph_descriptions.md��������������������������������������������������0000664�0000000�0000000�00000017726�15132732333�0022340�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 📊 Visualizing clocks and sources Liquidsoap scripts can grow complex: multiple clocks, many sources, transitions, and time-dependent behaviors interacting together. To help reason about this, Liquidsoap can now **display graphs of clocks and sources**, giving you a visual overview of how time flows, how sources are connected, and which sources are actively animated. These graphs are useful while **writing or debugging a script**, inspecting unexpected timing behavior, or **explaining a setup** to others. ## 🧠 What is displayed? Liquidsoap can generate two related graphs: - **Clock graph** — shows clocks, their relationships, and which sources are active on each clock - **Source graph** — shows sources and how they are connected To build these graphs, Liquidsoap needs to **run the script for a short time** so it can observe clocks, sources, activations, and time flows. No actual output needs to be produced, but time must advance for clocks to be observable. ## 🕰️ Understanding clocks Clocks control how time advances for sources. The **clock graph** displays: - Parent/child relationships between clocks - Each clock’s **internal time** and tick count - Which sources are **active** on each clock The internal time is especially useful for reasoning about operators such as `crossfade` and `stretch`, which may accelerate their clock relative to real time to prepare transitions before they play. When a clock or source is marked with **`self_sync = true`**, it can _not_ be accelerated — an important detail when reasoning about timing and transitions. ## 🔥 Active sources The clock graph also highlights **active sources**. Active sources are always animated by their clock even if they are not currently producing output. This includes for example: - `input.harbor`, which must actively pull data from a remote Icecast stream when connected - `input.ffmpeg`, which may be need to be configured to be active or passive depending on whether it’s reading from a file or a remote URL Understanding which sources are active helps explain background activity you might otherwise miss. ## 🔄 Understanding source graphs The **source graph** shows how sources are connected, and — crucially — how they are animated: - The graph is animated **from top to bottom** - The top is usually an output, but it can also be a regular source that is animated by an external source such as a `crossfade`. For example, a `crossfade` or `stretch` operator may accelerate its clock and animate its inputs faster than real time to prepare transitions or resampling. This top-to-bottom animation direction makes it easier to see: - Which source is driving evaluation - How transitions or clock acceleration affect lower sources ## 💻 Using the CLI You can generate these graphs directly from the command line: ```bash liquidsoap --display-clocks script.liq liquidsoap --display-sources script.liq ``` When these options are used: 1. The script is started 2. It runs briefly so clock and source information can be gathered 3. The script is stopped 4. The requested graph is displayed Adjust run duration with: ```bash --dump-delay <seconds> ``` This offers a quick, non-intrusive way to inspect a script. ## 🔌 Using the server / telnet interface If your script is already running with the server enabled, you can request graphs interactively: - `clocks.dump` — display the clock graph - `clocks.dump_sources` — display the source graph Great for **live inspection** without restarting. ## 📟 Using the scripting API Graphs can also be accessed programmatically: - `clock.dump()` — dump the clock graph - `clock.dump_all_sources()` — dump the source graph This makes it easy to integrate visualization into custom tooling or monitoring. ## 🌟 Examples Here are example outputs showing typical clock and source graphs: ### 🕒 Clock graph ``` Clock output.icecast: Outputs: · output.icecast [output] └── ffmpeg_encode_audio [passive] └── audio.producer [passive] · output.file [output] └── ffmpeg_encode_audio [passive] (*) Clock audio.producer (controlled by output.icecast): Outputs: · audio.producer [external activation] └── audio.consumer [output] └── mksafe.1 [passive] ├── safe_blank.1 [passive] ├── metadata_map.3 [passive] │ ├── mksafe [passive] │ │ ├── safe_blank [passive] │ │ ├── metadata_deduplicate [passive] │ │ │ ├── cross [passive] │ │ │ └── track_metadata_deduplicate [passive] │ │ │ └── cross [passive] (*) │ │ └── insert_initial_track_mark [passive] │ │ └── safe_blank [passive] (*) │ └── metadata_map.2 [passive] │ └── mksafe [passive] (*) └── insert_initial_track_mark.1 [passive] └── metadata_map.3 [passive] (*) ``` ### 🔗 Source graph ``` · output.icecast (ticks: 3, time: 0.06s, self_sync: false) ├── outputs: output.icecast [output.icecast], output.file [output.file] ├── active sources: ├── passive sources: audio.producer [ffmpeg_encode_audio], │ ffmpeg_encode_audio [output.icecast, output.file] └── audio.producer (ticks: 6, time: 0.12s, self_sync: false) ├── outputs: audio.consumer [audio.producer, audio.consumer] ├── active sources: ├── passive sources: safe_blank.1 [mksafe.1], safe_blank [mksafe], │ cross [track_metadata_deduplicate, metadata_deduplicate], │ track_metadata_deduplicate [metadata_deduplicate], │ metadata_deduplicate [mksafe, insert_initial_track_mark.6], │ mksafe [metadata_map.2, metadata_map.3], │ metadata_map.2 [metadata_map.3], │ metadata_map.3 [mksafe.1, insert_initial_track_mark.1], │ mksafe.1 [audio.consumer], insert_initial_track_mark [], │ insert_initial_track_mark.1 [mksafe.1], │ insert_initial_track_mark.6 [mksafe] └── cross (ticks: 132, time: 2.64s, self_sync: false) ├── outputs: ├── active sources: └── passive sources: source [switch, switch.1], audio [switch, switch.1, insert_initial_track_mark.2], switch [switch.1, insert_initial_track_mark.3], switch.1 [switch.2, insert_initial_track_mark.4], request_queue [switch.2], switch.2 [switch.3, insert_initial_track_mark.5], request_queue_1 [switch.3], switch.3 [metadata_map, metadata_map.1], metadata_map [metadata_map.1], metadata_map.1 [track_amplify, amplify], track_amplify [amplify], amplify [cross, cross], insert_initial_track_mark.2 [switch], insert_initial_track_mark.3 [switch.1], insert_initial_track_mark.4 [switch.2], insert_initial_track_mark.5 [switch.3], cross.eos_buffer [cross] ``` These outputs make it easy to see how clocks relate, which sources are active, and how evaluation flows from **top (outputs)** to **bottom (inputs)**. ## ✅ When to use this feature Clock and source graphs are particularly helpful when: - ✏️ Designing or refactoring a script - 🐞 Debugging timing, synchronization, or activation issues - 🧠 Understanding `self_sync` and clock acceleration - 📢 Explaining how a script works to others By making time, structure, and activation visible, these graphs provide a new way to reason about Liquidsoap scripts — beyond reading code alone. ������������������������������������������liquidsoap-2.4.2/doc/content/harbor.md��������������������������������������������������������������0000664�0000000�0000000�00000011103�15132732333�0017705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Harbor input Liquidsoap is also able to receive a source using icecast or shoutcast source protocol with the `input.harbor` operator. Using this operator, the running liquidsoap will open a network socket and wait for an incoming connection. This operator is very useful to seamlessly add live streams into your final streams: you configure the live source client to connect directly to liquidsoap, and manage the switch to and from the live inside your script. Additionally, liquidsoap can handle many simultaneous harbor sources on different ports, with finer-grained authentication schemes that can be particularly useful when used with source clients designed for the shoutcast servers. SSL support in harbor can be enabled using of of the following `opam` packages: `ssl`, `osx-secure-transport`. If enabled using `ssl`, `input.harbor.ssl` will be available. If enabled with `osx-secure-transport`, it will be `input.harbor.secure_transport`. ## Parameters The global parameters for harbor can be retrieved using `liquidsoap --list-settings`. They are: - `harbor.bind_addr`: IP address on which the HTTP stream receiver should listen. The default is `"0.0.0.0"`. You can use this parameter to restrict connections only to your LAN. - `harbor.timeout`: Timeout for source connection, in seconds. Defaults to `30.`. - `harbor.verbose`: Print password used by source clients in logs, for debugging purposes. Defaults to: `false` - `harbor.reverse_dns`: Perform reverse DNS lookup to get the client's hostname from its IP. Defaults to: `true` - `harbor.icy_formats`: Content-type (mime) of formats which allow shout (ICY) metadata update. Defaults to: ` ["audio/mpeg"; "audio/aacp"; "audio/aac"; "audio/x-aac"; "audio/wav"; "audio/wave"]` If SSL support was enabled via `ssl`, you will have the following additional settings: - `harbor.ssl.certificate`: Path to the SSL certificate. - `harbor.ssl.private_key`: Path to the SSL private key (openssl only). - `harbor.ssl.password`: Optional password to unlock the private key. Obtaining a proper SSL certificate can be tricky. You may want to start with a self-signed certificate first. You can obtain a free, valid certificate at: [https://letsencrypt.org/](https://letsencrypt.org/) If SSL support is enable via `osx-secure-transport`, you will have the same settings but named: `harbor.secure_transport.*`. To create a self-signed certificate for local testing you can use the following one-liner: ``` openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout server.key -out server.crt -subj "/CN=localhost" -days 3650 ``` You also have per-source parameters. You can retrieve them using the command `liquidsoap -h input.harbor`. The most important one are: - `user`, `password`: set a permanent login and password for this harbor source. - `auth`: Authenticate the user according to a specific function. - `port`: Use a custom port for this input. - `icy`: Enable ICY (shoutcast) source connections. - `id`: The mountpoint registered for the source is also the id of the source. When using different ports with different harbor inputs, mountpoints are attributed per-port. Hence, there can be a harbor input with mountpoint `"foo"` on port `1356` and a harbor input with mountpoint `"foo"` on port `3567`. Additionally, if an harbor source uses custom port `n` with shoutcast (ICY) source protocol enabled, shoutcast source clients should set their connection port to `n+1`. The `auth` function is a function, that takes a record `{user, password, address}` and returns a boolean representing whether the user should be granted access or not. Typical example can be: ```{.liquidsoap include="harbor-auth.liq"} ``` In the case of the `ICY` (shoutcast) source protocol, there is no `user` parameter for the source connection. Thus, the user used will be the `user` parameter passed to the `input.harbor` source. When using a custom authentication function, in case of a `ICY` (shoutcast) connection, the function will receive this value for the username. ## Usage When using harbor inputs, you first set the required settings, as described above. Then, you define each source using `input.harbor("mountpoint")`. This source is faillible and will become available when a source client is connected. The unlabeled parameter is the mount point that the source client may connect to. It should be `"/"` for shoutcast source clients. The source client may use any of the recognized audio input codec. Hence, when using shoucast source clients, you need to have compiled liquidsoap with mp3 decoding support (`ocaml-mad`). A sample code can be: ```{.liquidsoap include="harbor-usage.liq" to=-1} ``` �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/harbor_http.md���������������������������������������������������������0000664�0000000�0000000�00000012343�15132732333�0020753�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Harbor as HTTP server The harbor server can be used as a HTTP server. We provide two type of APIs for this: ## Simple API The `harbor.http.register.simple` function provides a simple, easy to use registration API for quick HTTP response implementation. This function receives a record describing the request and returns the HTTP response. The request passed to the function contains all expected information from the underlying HTTP query. The `data` method on a request is a _string getter_, that is a function of type: `() -> string` which returns the empty string `""` when all data has been consumed. You can use this function to e.g. write the request data to a file using `file.write.stream`. The `body` method can be used to read all of the request's data and store it in memory. Make sure to only use it if you know that the response should be small enough! For convenience, a HTTP response builder is provided via `http.response`. Here's an example: ```{.liquidsoap include="harbor.http.response.liq" from="BEGIN"} ``` where: - `port` is the port where to receive incoming connections - `method` is for the http method (or verb), one of: `"GET"`, `"PUT"`, `"POST"`, `"DELETE"`, `"OPTIONS"` and `"HEAD"` - `path` is the matched path. It can include named fragments, e.g. `"/users/:id/collabs/:cid"`. Named named fragments are passed via `request.query`, for instance: `req.query["cid"]`. ## Node/express API The `harbor.http.register` function offers a higher-level API for advanced HTTP response implementation. Its API is very similar to the node/express API. Here's an example: ```{.liquidsoap include="harbor.http.register.liq" from="BEGIN"} ``` where: - `port` is the port where to receive incoming connections - `method` is for the http method (or verb), one of: `"GET"`, `"PUT"`, `"POST"`, `"DELETE"`, `"OPTIONS"` and `"HEAD"` - `path` is the matched path. It can include named fragments, e.g. `"/users/:id/collabs/:cid"`. Matched named fragments are passed via `request.query`, for instance: `req.query["cid"]`. The handler function receives a record containing all the information about the request and fills up the details about the response, which is then used to write a proper HTTP response to the client. Named fragments from the request path are passed to the response `query` list. Middleware _a la_ node/express are also supported and registered via `http.harbor.middleware.register`. See `http.harbor.middleware.cors` for an example of how to implement one such middleware. Here's how you would enable the `cors` middleware: ``` harbor.http.middleware.register(harbor.http.middleware.cors(origin="example.com")) ``` ## Https support `https` is supported using either `libssl` or `ocaml-tls`. When compiled with either of them, a `http.transport.ssl` or `http.transport.tls` is available and can be passed to each `harbor` operator: ```liquidsoap transport = http.transport.ssl( # Server mode: required, # client mode: optional, add certificate to trusted pool certificate="/path/to/certificate/file", # Server mode: required, client mode: ignored key="/path/to/secret/key/file", # Required if key file requires one. # TLS does not support password encrypted keys! password="optional password" ) harbor.http.register(transport=transport, port=8000, ...) input.harbor(transport=..., port=8000, ...) output.harbor(transport=..., port=8000, ...) output.icecast(transport=..., port=8000, ...) ``` A given port can only support one type of transport at a time and registering handlers, sources or outputs on the same port with different transports will raise a `error.http` error. ## Advanced usage All registration functions have a `.regexp` counter part, e.g. `harbor.http.register.simple.regexp`. These function accept a full regular expression for their `path` argument. Named matches on the regular expression are also passed via the request's `query` parameter. It is also possible to directly interact with the underlying socket using the `simple` API: ```{.liquidsoap include="harbor-simple.liq"} ``` ## Examples These functions can be used to create your own HTTP interface. Some examples are: ## Redirect Icecast's pages Some source clients using the harbor may also request pages that are served by an icecast server, for instance listeners statistics. In this case, you can register the following handler: ```{.liquidsoap include="harbor-redirect.liq"} ``` ## Get metadata You can use harbor to register HTTP services to fecth/set the metadata of a source. ```{.liquidsoap include="harbor-metadata.liq" from="BEGIN"} ``` Once the script is running, a GET request for `/getmeta` at port `7000` returns the following: ``` HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 { "genre": "Soul", "album": "The Complete Stax-Volt Singles: 1959-1968 (Disc 8)", "artist": "Astors", "title": "Daddy Didn't Tell Me" } ``` ## Set metadata Using source's `insert_metadata` method, you can register a GET handler that updates the metadata of a given source. For instance: ```{.liquidsoap include="harbor-insert-metadata.liq" from="BEGIN"} ``` Now, a request of the form `http://server:7000/setmeta?title=foo` will update the metadata of source `s` with `[("title","foo")]`. You can use this handler, for instance, in a custom HTML form. ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/help.md����������������������������������������������������������������0000664�0000000�0000000�00000010255�15132732333�0017367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Get help Liquidsoap is a self-documented application, which means that it can provide help about several of its aspects. You will learn here how to get help by yourself, by asking liquidsoap. If you do not succeed in asking the tool, you can of course get help from humans. We maintain the following communication channels: - Discord: [chat.liquidsoap.info](http://chat.liquidsoap.info/) - IRC: #savonet on [irc.libera.chat](https://libera.chat/) (through a discord bridge) - Mailing list: [savonet-users@lists.sourceforge.net](mailto:savonet-users@lists.sourceforge.net) ## Scripting API When scripting in liquidsoap, one uses functions that are either _builtin_ (_e.g._ `input.http` or `output.icecast`) or defined in the [script library](script_loading.html) (_e.g_ `output`). All these functions come with a documentation, that you can access by executing `liquidsoap -h FUNCTION` on the command-line. For example: ``` $ liquidsoap -h sine Generate a sine wave. Type: (?id : string?, ?amplitude : {float}, ?duration : float, ?{float}) -> source(audio=internal('a), video=internal('b), midi=internal('c)) Category: Source / Input Parameters: * id : string? (default: null) Force the value of the source ID. * amplitude : {float} (default: 1.) Maximal value of the waveform. * duration : float (default: -1.) Duration in seconds (negative means infinite). * (unlabeled) : {float} (default: 440.) Frequency of the sine. Methods: * fallible : bool Indicate if a source may fail, i.e. may not be ready to stream. * id : () -> string Identifier of the source. * is_active : () -> bool `true` if the source is active, i.e. it is continuously animated by its own clock whenever it is ready. Typically, `true` for outputs and sources such as `input.http`. * is_ready : () -> bool Indicate if a source is ready to stream. This does not mean that the source is currently streaming, just that its resources are all properly initialized. * is_up : () -> bool Indicate that the source can be asked to produce some data at any time. This is `true` when the source is currently being used or if it could be used at any time, typically inside a `switch` or `fallback`. * on_leave : ((() -> unit)) -> unit Register a function to be called when source is not used anymore by another source. * on_metadata : ((([string * string]) -> unit)) -> unit Call a given handler on metadata packets. * on_shutdown : ((() -> unit)) -> unit Register a function to be called when source shuts down. * on_track : ((([string * string]) -> unit)) -> unit Call a given handler on new tracks. * remaining : () -> float Estimation of remaining time in the current track. * seek : (float) -> float Seek forward, in seconds (returns the amount of time effectively seeked). * self_sync : () -> bool Is the source currently controlling its own real-time loop. * skip : () -> unit Skip to the next track. * time : () -> float Get a source's time, based on its assigned clock. ``` Of course if you do not know what function you need, you'd better go through the [API reference](reference.html). Please note that some functions in that list are optional and may not be available with your local `liquidsoap` install unless you install the optional dependency that enables it. The list of optional dependencies is listed via `opam info liquidsoap` if you have installed it this way or can in our [build page](build.html). ## Settings Liquidsoap scripts contain expression like `settings.log.stdout := true`. These are _settings_, global variables affecting the behaviour of the application. Some common settings have shortcut for convenience. These are all shortcuts to their respective `settings` values: ```{.liquidsoap include="settings.liq"} ``` You can have a list of available settings, with their documentation, by running `liquidsoap --list-settings`. The output of these commands is a valid liquidsoap script, which you can edit to set the values that you want, and load it ([implicitly](script_loading.html) or not) before you other scripts. You can browse online the [list of available settings](settings.html). ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/hls_output.md����������������������������������������������������������0000664�0000000�0000000�00000011021�15132732333�0020635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# HLS Output Starting with liquidsoap `1.4.0`, it is possible to send your streams as [HLS output](https://en.wikipedia.org/wiki/HTTP_Live_Streaming). The main operator is `output.file.hls`. Here's an example using it, courtesy of [srt2hls](https://github.com/mbugeia/srt2hls): ```{.liquidsoap include="output.file.hls.liq"} ``` Let's see what's important here: - `streams` describes the encoded streams. It's a list of: `(stream_name, encoder)`. `stream_name` is used to generate the corresponding media playlists. Encoders can be any encoder supported by liquidsoap. However, the [HLS RFC](https://tools.ietf.org/html/rfc8216) limits the list of possible codecs to `mp3` and `aac`. Furthermore, for the best possible compatible, it is recommended to send data encapsulated in a `MPEG-TS` stream. Currently, the only encoder capable of doing this in liquidsoap is `%ffmpeg`. - `persist_at` is used to allow liquidsoap to restart while keeping the existing segments and playlists. When shutting down, liquidsoap stores the current configuration at `persist_at` and uses it to restart the HLS stream when restarting. - `segments` and `segments_overhead` are used to keep track of the generated segments. Each media playlist will contain a number of segments defined by `segments` and an extra set of segments, defined by `segments_overhead`, is kept past the playlist size for those listeners who are still listening on outdated segments. There are more useful options, in particular `on_file_change`, which can be used for instance to sync up your segments and playlists to a distant storage and hosting service such as S3. Liquidsoap also provides `output.harbor.hls` which allows to serve HLS streams directly from liquidsoap. Their options should be the same as `output.file.hls`, except for harbor-specifc options `port` and `path`. It is not recommended for listener-facing setup but can be useful to sync up with a caching system such as cloudfront. ## Keyframes and segment length In codec terminology, a `keyframe` can be understood as a piece of encoded data that contains enough information to start decoding the stream. Keyframes are very common in video codecs and can exist in audio codecs. In order to make sure that a HLS playlist can be decoded starting from any segment, liquidsoap tries to split segments on keyframe boundaries. When not possible, you will see a warning in the logs. Segment split is forced when reaching the value specified by `EXT-X-TARGETDURATION` to follow the HLS specifications. For metadata and extra tags, segment split will occur at the next keyframe. To make sure that all these requirements operate correctly, you should make sure to set a keyframe frequency in your encoder's settings that generates at lease one keyframe per segment. For instance, if your segments are at most `2s` long and encoded using `libx264`, you can use the `keyint` and `keyint-min` parameters, which are expressed in number of frames. If your video frame rate is `25fps` (liquidsoap's default), you should have at least one keyframe every `2 * 25 = 50` frames. ```liquidsoap %ffmpeg( ..., %video(codec="libx264", x264opts="keyint=50:min-keyint=50") ) ``` ## Metadata HLS outputs supports metadata in two ways: - Using the `%ffmpeg` encoder, through a `timed_id3` metadata logical stream with the `mpegts` format. - Through regular ID3 frames, as requested by the [HLS specifications](https://datatracker.ietf.org/doc/html/rfc8216#section-3.4) for `adts`, `mp3`, `ac3` and `eac3` formats with the `%ffmpeg` encoder and also natively using the `%mp3`, `%shine` or `%fdkaac` encoders. - There is currently no support for in-stream metadata for the `mp4` format. Metadata parameters are passed through the record methods of the streams' encoders. Here's an example ```{.liquidsoap include="hls-metadata.liq"} ``` Parameters are: - `id3`: Set to `false` to deactivate metadata on the streams. Defaults to `true`. - `id3_version`: Set the `id3v2` version used to export metadata - `replay_id3`: By default, the latest metadata is inserted at the beginning of each segment to make sure new listeners always get the latest metadata. Set to `false` to disable it. Metadata for these formats are activated by default. If you are experiencing any issues with them, you can disable them by setting `id3` to `false`. ## Mp4 format `mp4` container is supported by requires specific parameters. Here's an example that mixes `aac` and `flac` audio, The parameters required for `mp4` are `movflags` and `frag_duration`. ```{.liquidsoap include="hls-mp4.liq"} ``` ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/http_input.md����������������������������������������������������������0000664�0000000�0000000�00000001171�15132732333�0020632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# HTTP input Liquidsoap can create a source that pulls its data from an HTTP location. This location can be a distant file or playlist, or an icecast or shoutcast stream. To use it in your script, simply create a source that way: ```{.liquidsoap include="http-input.liq" from="BEGIN" to="END"} ``` This operator will pull regularly the given location for its data, so it should be used for locations that are assumed to be available most of the time. If not, it might generate unnecessary traffic and pollute the logs. In this case, it is perhaps better to inverse the paradigm and use the [input.harbor](harbor.html) operator. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/icy_metadata.md��������������������������������������������������������0000664�0000000�0000000�00000004245�15132732333�0021065�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# ICY metadata _ICY metadata_ is the name for the mechanism used to update metadata in icecast's source streams. The techniques is primarily intended for data formats that do not support in-stream metadata, such as mp3 or AAC. However, it appears that icecast also supports ICY metadata update for ogg/vorbis streams. When using the ICY metadata update mechanism, new metadata are submitted separately from the stream's data, via a http GET request. The format of the request depends on the protocol you are using (ICY for shoutcast and icecast 1 or HTTP for icecast 2). Starting with 1.0, you can do several interesting things with icy metadata updates in liquidsoap. We list some of those here. ## Enable/disable ICY metadata updates You can enable or disable icy metadata update in `output.icecast` by setting the `send_icy_metadata` parameter to `null`, `true` or `false`. The default value is `null` and does the following: - Set `true` for: mp3, aac, aac+, wav - Set `false` for any format using the ogg container In some cases, `liquidsoap` might not be able to detect if ICY metadata need to be enabled, in which case it will ask you to set a `true` or `false` value for this parameter. ## `song` metadata Most Icecast listeners expect a `song` metadata to be generated. This metadata should combine both artist and title metadata and will be played preferably. We provide a default implementation that returns `artist` or `title` metadata when only one of these two is available and `$(artist) - $(title)` otherwise. You can use the `icy_song` parameter to use your own implementation. Returning `null` from that function disables the metadata altogether. ## Update metadata manually The function `icy.update_metadata` implements a manual metadata update using the ICY mechanism. It can be used independently from the `icy_metadata` parameter described above, provided icecast supports ICY metadata for the intended stream. For instance the following script registers a telnet command name `metadata.update` that can be used to manually update metadata: ```{.liquidsoap include="icy-update.liq"} ``` As usual, `liquidsoap -h icy.update_metadata` lists all the arguments of the function. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/images�����������������������������������������������������������������0000777�0000000�0000000�00000000000�15132732333�0021640�2../orig/images��������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/in_production.md�������������������������������������������������������0000664�0000000�0000000�00000001660�15132732333�0021313�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Using in production The full installation of liquidsoap will typically install `/etc/liquidsoap`, `/etc/init.d/liquidsoap` and `/var/log/liquidsoap`. All these are meant for a particular usage of liquidsoap when running a stable radio. Your production `.liq` files should go in `/etc/liquidsoap`. You'll then start/stop them using the init script, _e.g._ `/etc/init.d/liquidsoap start`. Your scripts don't need to have the `#!` line, and liquidsoap will automatically be ran on daemon mode (`-d` option) for them. You should not override the `log.file.path` setting because a logrotate configuration is also installed so that log files in the standard directory are truncated and compressed if they grow too big. It is not very convenient to detect errors when using the init script. We advise users to check their scripts after modification (use `liquidsoap --check /etc/liquidsoap/script.liq`) before effectively restarting the daemon. ��������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/index.md���������������������������������������������������������������0000664�0000000�0000000�00000010160�15132732333�0017541�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Liquidsoap Liquidsoap is a powerful tool for building complex audio and video stream generators, typically targeting internet radios and webtvs. It consists of a simple script language, which has a first-class notion of source (basically a _stream_) and provides elementary source constructors and source compositions from which you can build the stream generator you want. This design makes liquidsoap flexible and easily extensible. We believe that liquidsoap is easy to use. For basic purposes, the scripts consist of the definition of a tree of sources. You will quickly [learn](quick_start.html) how natural it is to use liquidsoap in such cases. The good thing is that when you'll want to make your stream more complex, you'll be able to stay in the same framework and keep a maintainable configuration. Of course, using some complex features might require a deeper understanding of the concepts of [source](sources.html) and [request](requests.html) and of our [scripting language](language.html). We'll discuss below what liquidsoap is and what it isn't. If you're already familiar with it and want to get started, just jump to the [documentation index](documentation.html). It will provide guidance,starting with the [quickstart tour](quick_start.html). Liquidsoap is an open-source software from the [Savonet](http://liquidsoap.info) project. ## Features Here are a few things you can easily achieve using Liquidsoap: - Playing from files, playlists, directories or script playlists (plays the file chosen by an external program). - [Video streams](video.html) generation. - Decoding/encoding using any media format supported by FFmpeg. - Transcoding of media stream, relay of encoded media stream without re-encoding, sharing encoding to avoid encoding multiple times. - Transparent remote file access; easy addition of file resolution protocols. - Scheduling of many sources, depending on time, priorities, quotas, etc. - Mixing sources on top others. - Queuing of user requests; editable queues. - Sound processing: compression, normalization, echo, soundtouch, etc. - Speech and sound synthesis. - [Metadata](metadata.html) rewriting and insertion. - Arbitrary transitions: cross-fade, jingle insertion, custom, etc. The behaviour of the transition can be programmed to depend on metadata and average volume. - [Input of other streams](http_input.html): useful for switching to a live show. Liquidsoap can relay an HTTP stream but also host it. - [Blank detection](blank.html). - Definable event handlers on new tracks, new metadata and excessive blank. - Multiple outputs in the same instance: you can have several quality settings, use several media or even broadcast several contents from the same instance. - Output to HLS/Icecast/Shoutcast (MP3/Ogg) or a local file (WAV/MP3/Ogg/AAC). - Input/output via Jack, ALSA, OSS and PortAudio. Output via `libao`. - [Interactive control](advanced.html) of many operators via Telnet or UNIX domain socket, and indirectly using scripts, graphical/web/IRC interfaces. If you need something else, it's highly possible that you can have it -- at least by programming new sources/operators. Send us a request, we'll be happy to discuss these questions. ## Non-Features Liquidsoap is a flexible tool for processing audio and video streams, that's all. We've used it for several internet radio projects, and we know its flexibility is useful. However, internet radio usually requires more than just an audio stream, as such components cannot easily be built from basic primitives as we do in liquidsoap for streams. We don't have any magic solution for these, although we sometimes have some nice tools which could be adapted to various uses. Liquidsoap itself doesn't have a nice GUI or any graphical programming environment. You'll have to write the script by hand, and the only possible interaction with a running liquidsoap is the telnet server. Liquidsoap makes the interfacing with other tools easy, since it can call an external application (reading from the database) to get audio tracks, another one (updating last-played information) to notify that some file has been successfully played. An example of this is [Beets](beets.html). ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/install.md�������������������������������������������������������������0000664�0000000�0000000�00000012500�15132732333�0020100�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Installing Liquidsoap You can install liquidsoap using binary builds, with OPAM or from source. Binary builds are provided with our releases, either in the form of debian/ubuntu and alpine packages or as docker images (also for debian or alpine). Your favorite distribution may also have binary packages. The binary package and docker images we provide are compiled in two flavors: - The main `liquidsoap` packages are compiled with all available features and functions. This is a good starting point for general-purpose development - Binary packages and docker images labelled `-minimal` are compiled without the extra libraries and with a limited set of essential optional features Minimal builds are useful if you are concerned about size or memory usage. They also reduce the chances of running into issues that could be introduced by optional dependencies that you do not use. If your script works with them, they are recommended over the fully featured builds for production. Each binary build that we provide have a corresponding `*.config` file. This is a text file that lists all the features included in a specific build. You can consult it to know what features are available. You can also get the same information by calling `liquidsoap --build-config`, for instance when using a docker image. Binary packages and docker images are useful in that they provide a readily available liquidsoap installation. If you need more finer-grained build or if your distribution/OS does not have a binary build, you can install via OPAM, which is a very convenient package manager that can compile liquidsoap from sources and knows how to handle external dependencies for most OS/distributions. Lastly, compiling from source should be reserved to developers. - [Debian/Ubuntu](#debianubuntu) - [Alpine](#alpine) - [Docker](#docker) - [Windows](#windows) - [Using OPAM](#install-using-opam) - [From source](#installing-from-source) ## Debian/Ubuntu We generate debian and ubuntu packages automatically as part of our [release process](https://github.com/savonet/liquidsoap/releases). Otherwise, you can check out the official [debian](https://packages.debian.org/liquidsoap) and [ubuntu](https://packages.ubuntu.com/liquidsoap) packages. When installing our own packages on debian, you will also need to use the [deb-multimedia.org](https://www.deb-multimedia.org/) packages. These packages provide up-to date libraries and also support for `fdk-aac` encoding in `ffmpeg`! ## Alpine Alpine packages are also provided as part of our [release process](https://github.com/savonet/liquidsoap/releases). ## Docker We provide production-ready docker images via [Docker hub](https://hub.docker.com/r/savonet/liquidsoap). Docker images are tagged with a release tag (e.g. `v2.1.4`) and with the sha of their git commit (e.g. `a24bf49`). For instance, to fetch release `2.3.1`, you would do: ```shell docker pull savonet/liquidsoap:v2.3.1 ``` Please note that images tagged with a release tag may change while images tagged with a commit sha will not. ## Windows You can download a liquidsoap for windows from our [release page](https://github.com/savonet/liquidsoap/releases). ## Install using OPAM The recommended method to install liquidsoap from source is by using the [OCaml Package Manager](http://opam.ocaml.org/). OPAM is available in all major distributions and on windows. We actively support the liquidsoap packages there and its dependencies. You can read [here](https://opam.ocaml.org/doc/Usage.html) about how to use OPAM. In order to use it: - [you should have at least OPAM version 2.1](https://opam.ocaml.org/doc/Install.html), - not all version of the OCaml compiler are supported. You can run `opam info liquidsoap-lang` to find out. You can create a switch for a specific OCaml version as follows: ``` opam switch create <ocaml version> ``` A typical installation with most expected features is done by executing: ``` opam install ffmpeg liquidsoap ``` This will install `liquidsoap` along with the optional `ffmpeg` package, which provides most of the expected functionalities (encoding, decoding, metadata support etc) out of the box. The `opam` installer also handles external dependencies that is, dependencies from your operating system that are required for your install. Typically, this would be the `ffmpeg` shared libraries here, as well as `libcurl`, which is required for `liquidsoap` to install. In most cases, `opam` will simply ask for your permission to install these dependencies on your behalf. In some cases, however, you will have install them yourself. Most of liquidsoap's dependencies are only optional. For instance, if you want to enable opus encoding and decoding after you've already installed liquidsoap, you should execute the following: ``` opam install opus ``` This will install `opus` and its dependencies and recompile `liquidsoap` to take advantage of it. `opam info liquidsoap` should give you the list of all optional dependencies that you may enable in liquidsoap. **Note** `opam` handles external dependencies via your system's packaging. In order to build some of the associated OCaml modules, macos users using `homebrew` might need to add the following to their environment/shell configuration: ```shell export CPATH=/opt/homebrew/include export LIBRARY_PATH=/opt/homebrew/lib ``` ## Installing from source See the [build instructions](build.html) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/json.md����������������������������������������������������������������0000664�0000000�0000000�00000016430�15132732333�0017411�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## 📦 Working with JSON in Liquidsoap Liquidsoap makes it easy—and safe—to work with JSON data directly in your scripts. Whether you're loading configuration files, interfacing with APIs, or managing playlist metadata, JSON is a powerful format to master. This page walks you through how JSON parsing works in Liquidsoap, how type safety is enforced, and how to use advanced features like nullable types, custom keys, and associative object parsing. Let’s start simple and build progressively toward the more advanced features. 🧗 ### 🔹 Getting Started: Parsing a Simple JSON Object You can parse a JSON string using the special `let json.parse` syntax: ```liquidsoap let json.parse v = '{"foo": "abc"}' print("We parsed a JSON object and got value " ^ v.foo ^ " for attribute foo!") ``` ✅ Output: ``` We parsed a JSON object and got value abc for attribute foo! ``` What's happening here? Liquidsoap watches how you use `v.foo` (as a string), and it _checks at runtime_ that the JSON contains `"foo"` and that its value is indeed a string. If there's a mismatch, you'll get a clear error. Example with incorrect type: ```liquidsoap let json.parse v = '{"foo": 123}' ``` ⛔ Raises: ``` Error 14: Uncaught runtime error: type: json, message: "Parsing error: json value cannot be parsed as type {foo: string, _}" ``` ### 📁 Loading JSON from Files Instead of hardcoding JSON as a string, you can load it from a file: ```liquidsoap let json.parse v = file.contents("/path/to/file.json") ``` Let’s look at a realistic example. Suppose you’re parsing a `package.json` file from an npm package: ```liquidsoap let json.parse package = file.contents("/path/to/package.json") name = package.name version = package.version test = package.scripts.test print("This is package " ^ name ^ ", version " ^ version ^ " with test script: " ^ test) ``` ### ✅ Use Type Annotations for Reliable Parsing Sometimes Liquidsoap can’t infer types correctly—especially when you use variables only inside string interpolations (`#{...}`). In those cases, values default to `null`, which might be confusing. The solution: add an **explicit type annotation** to your parse statement. The type annotation is then used to drive the parser and pick the json data that you are expecting: ```liquidsoap let json.parse ({ name, version, scripts = { test } } : { name: string, version: string, scripts: { test: string } }) = file.contents("/path/to/package.json") ``` Now everything works as expected—even if you only reference variables inside interpolations. ## 🧩 Understanding JSON Type Annotations Liquidsoap’s JSON parser uses a rich type system that maps onto JSON’s structure. Let’s break it down: ### **🔤 Ground Types** | Type | Description | Example value | | -------- | ---------------------------- | ----------------- | | `string` | A sequence of characters | `"hello"` | | `int` | An integer | `42` | | `float` | A number, including decimals | `3.14` or `123.0` | Liquidsoap will coerce integers into floats if needed (e.g. `123` can be a `float`). ### **❓ Nullable Types** Add `?` to make a type optional: ```liquidsoap test: string? # test is either a string or null ``` Useful when parsing data that may or may not include a field: ```liquidsoap let json.parse ({ scripts } : { scripts: { test: string? }? }) = file.contents("package.json") ``` You can check for presence using: ```liquidsoap # Option 1: Explicit check test = if null.defined(scripts) then null.get(scripts.test) else null() end # Option 2: Fallback value test = (scripts ?? { test = null }).test ``` --- ### **🔗 Tuples** Tuples parse fixed-size arrays with specific types for each position: ```liquidsoap (int * float * string) ``` This parses a JSON array like `[1, 2.5, "hello"]`. Use `_` as a wildcard to ignore types you don’t care about: ```liquidsoap (_ * _ * float) # Only the third element must be a float ``` --- ### **📋 Lists** To parse a JSON array of values of the same type, use brackets: ```liquidsoap [int] # list of integers [float?] # list of optional floats ``` Example: ```json [44.0, 55, 66.12] ``` Can be parsed as: `[float]` ### **🧱 Objects (Records)** Use `{...}` to parse JSON objects into named fields: ```liquidsoap {foo: int, bar: string} ``` This tells Liquidsoap to extract only the fields you care about. Extra fields in the JSON are ignored. ### 🏷️ **Custom JSON Keys** JSON keys often contain characters or spaces that aren't valid Liquidsoap variable names. You can map them like this: ```liquidsoap {"foo bar" as foo_bar: int} ``` Example: ```json { "foo bar": 123 } ``` Liquidsoap parses this as a variable `foo_bar = 123`. ### 🗂️ **Associative Objects as Lists** What if you don’t know the keys in advance? Use `[(string * < value type>)] as json.object` to treat an object like a list of key-value pairs. Example JSON: ```json { "a": 1, "b": 2, "c": 3 } ``` Use this type: ```liquidsoap [(string * int)] as json.object ``` Parsed as: ```liquidsoap [("a", 1), ("b", 2), ("c", 3)] ``` You can even use `int?` if some values might be missing or of a non-int type. ### ⚠️ Handling Errors Parsing errors raise a `error.json` exception: ```liquidsoap try let json.parse ({status, data = {track}} : {...}) = response # Do something with data catch err: [error.json] do # Handle the parse failure end ``` --- ## 🧪 Full Example ```liquidsoap data = '{ "foo": 34.24, "gni gno": true, "nested": { "tuple": [123, 3.14, false], "list": [44.0, 55, 66.12], "nullable_list": [12.33, 23, "aabb"], "object_as_list": { "foo": 123, "gni": 456.0, "gno": 3.14 }, "arbitrary object key ✨": true } }' let json.parse (x : { foo: float, "gni gno" as gni_gno: bool, nested: { tuple: (_ * float), list: [float], nullable_list: [int?], object_as_list: [(string * float)] as json.object, "arbitrary object key ✨" as arbitrary_key: bool, not_present: bool? } } ) = data ``` ### 🛠️ Other Features - **JSON5 support** (for trailing commas, comments, etc.): ```liquidsoap let json.parse[json5=true] x = ... ``` - **Exporting to JSON**: ```liquidsoap print(json.stringify({artist="Bla", title="Blo"})) ``` - **Building JSON manually**: This can be useful when dynamically generating json output. The most common tool is `json.object` to build dynamic key-value objects: ```liquidsoap j = json.object() j.add("foo", 1) j.add("bar", "baz") j.remove("foo") print(json.stringify(j)) ``` In some cases, for instance when returning a mixed type of json in a http response, you can also use `json.value`: ```liquidsoap try # Send a number here: id = 1234 res.json(json.value(id)) catch err do # Or a string in case of an error: res.json(json.value("Error while processing request: #{err}")) end ``` ## 🚀 Recap ✅ Liquidsoap gives you a safe and expressive way to work with JSON 🧠 Type annotations help catch issues early and make your code clearer 🛠️ Advanced types let you tackle real-world data with ease Once you’ve got the hang of parsing, try exploring the actual `tests/language/json.liq` test file in the source repo—it's full of neat examples and tricks! ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/ladspa.md��������������������������������������������������������������0000664�0000000�0000000�00000003672�15132732333�0017710�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# LADSPA plugins in Liquidsoap [LADSPA](http://www.ladspa.org/) is a standard that allows software audio processors and effects to be plugged into a wide range of audio synthesis and recording packages. If enabled, Liquidsoap supports LADSPA plugins. In this case, installed plugins are detected at run-time and are all available in Liquidsoap under a name of the form: `ladspa.plugin`, for instance `ladspa.karaoke`, `ladspa.flanger` etc.. The full list of those operators can be found using `liquidsoap --list-plugins`. Also, as usual, `liquidsoap -h ladspa.plugin` returns a detailed description of each LADSPA's operators. For instance: ``` ./liquidsoap -h ladspa.flanger *** One entry in scripting values: Flanger by Steve Harris <steve@plugin.org.uk>. Category: Source / Sound Processing Type: (?id:string,?delay_base:'a,?feedback:'b, ?lfo_frequency:'c,?max_slowdown:'d, source(audio='#e,video='#f,midi='#g))-> source(audio='#e,video='#f,midi='#g) where 'a, 'b, 'c, 'd is either float or ()->float Flag: hidden Parameters: * id : string (default "") Force the value of the source ID. * delay_base : anything that is either float or ()->float (default 6.32499980927) Delay base (ms) (0.1 <= delay_base <= 25). * feedback : anything that is either float or ()->float (default 0.) Feedback (-1 <= feedback <= 1). * lfo_frequency : anything that is either float or ()->float (default 0.334370166063) LFO frequency (Hz) (0.05 <= lfo_frequency <= 100). * max_slowdown : anything that is either float or ()->float (default 2.5) Max slowdown (ms) (0 <= max_slowdown <= 10). * (unlabeled) : source(audio='#e,video='#f,midi='#g) (default None) ``` For advanced users, it is worth nothing that most of the parameters associated with LADSPA operators can take a function, for instance in the above: ` max_slowdown : anything that is either float or ()->float` . This means that those parameters may be dynamically changed while running a liquidsoap script. ����������������������������������������������������������������������liquidsoap-2.4.2/doc/content/language.md������������������������������������������������������������0000664�0000000�0000000�00000143036�15132732333�0020226�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Liquidsoap's scripting language _The following is adapted from the [Liquidsoap book](book.html). The reader is avised to check out the whole chapter in the book for more details about the liquidsoap language_ ## General features Liquidsoap is a novel language which was designed from scratch to handle media stream. It takes some inspiration from functional languages such as [OCaml](https://ocaml.org/) but features a syntax that is more intuitive to the general purpose programmer, similar to Ruby or Javascript. ### Typing One of the main features of the language is that it is _typed_. This means that every expression belongs to some type which indicates what it is. For instance, `"hello"` is a _string_ whereas `23` is an _integer_, and, when presenting a construction of the language, we will always indicate the associated type. Liquidsoap implements a _typechecking_ algorithm which ensures that whenever a string is expected a string will actually be given, and similarly for other types. This is done without running the program, so that it does not depend on some dynamic tests, but is rather enforced by theoretical considerations. Another distinguishing feature of this algorithm is that it also performs _type inference_: you never actually have to write a type, those are guessed automatically by Liquidsoap. This makes the language very safe, while remaining very easy to use. ### Functional programming The language is _functional_, which means that you can very easily define functions, and that functions can be passed as arguments of other functions. This might look like a crazy thing at first, but it is actually quite common in some language communities (such as OCaml). It also might look quite useless: why should we need such functions when describing webradios? You will soon discover that it happens to be quite convenient in many places: for handlers (we can specify the function which describes what to do when some event occurs such as when a DJ connects to the radio), for transitions (we pass a function which describes the shape we want for the transition) and so on. ### Streams The unique feature of Liquidsoap is that it allows the manipulation of _sources_ which are functions which will generate streams. These streams typically consist of stereo audio data, but we do restrict to this: they can contain audio with arbitrary number of channels, they can also contain an arbitrary number of video channels, and also MIDI channels (there is limited support for sound synthesis). ### Standard library Although the core of Liquidsoap is written in OCaml, many of the functions of Liquidsoap are written in the Liquidsoap language itself. Those are defined in the `stdlib.liq` script, which is loaded by default and includes all the libraries. You should not be frightened to have a look at the standard library, it is often useful to better grasp the language, learn design patterns and tricks, and add functionalities. Its location on your system is indicated in the variable `configure.libdir` and can be obtained by typing ## Basic values ### Integers and floats The _integers_, such as `3` or `42`, are of type `int`. Depending on the current architecture of the computer on which we are executing the script (32 or 64 bits, the latter being the most common nowadays) they are stored on 31 or 63 bits. The minimal (resp. maximal) representable integer can be obtained as the constant `min_int` (resp. `max_int`); typically, on a 64 bits architecture, they range from -4611686018427387904 to 4611686018427387903. The _floating point numbers_, such as `2.45`, are of type `float`, and are in double precision, meaning that they are always stored on 64 bits. We always write a decimal point in them, so that `3` and `3.` are not the same thing: the former is an integer and the latter is a float. This is a source of errors for beginners, but is necessary for typing to work well. ### Strings Strings are written between double or single quotes, e.g. `"hello!"` or `'hello!'`, and are of type `string`. In order to write the character "`"`" in a string, one cannot simply type "`"`" since this is already used to indicate the boundaries of a string: this character should be _escaped_, which means that the character "`\`" should be typed first so that ```liquidsoap print("My name is \"Sam\"!") ``` will actually display "`My name is "Sam"!`". Other commonly used escaped characters are "`\\`" for backslash and "`\n`" for new line. Alternatively, one can use the single quote notation, so that previous example can also be written as ```liquidsoap print('My name is "Sam"!') ``` This is most often used when testing JSON data which can contain many quotes or for command line arguments when calling external scripts. The character "`\`" can also be used at the end of the string to break long strings in scripts without actually inserting newlines in the strings. For instance, the script ```liquidsoap print("His name is \ Romain.") ``` will actually print ``` His name is Romain. ``` Note that there is no line change between "is" and "Romain", and the indentation before "Romain" is not shown either. The concatenation of two strings is achieved by the infix operator "`^`", as in ```liquidsoap user = "dj" print("Current user is " ^ user) ``` Instead of using concatenation, it is often rather convenient to use _string interpolation_: in a string, `#{e}` is replaced by the string representation of the result of the evaluation of the expression `e`: ```liquidsoap user = "admin" print("The user #{user} has just logged.") ``` will print `The user admin has just logged.` or ```liquidsoap print("The number #{random.float()} is random.") ``` will print `The number 0.663455738438 is random.` (at least it did last time I tried). #### Escaping strings Liquidsoap strings follow the most common lexical conventions from `C` and `javascript` and `JSON`, in particular, `string.unescape` recognizes the same escape sequences as `C` (except for `UTF-16` characters) and javascript. The following sequences are recognized: | Escape sequence | Hex value in ASCII | Character represented | | --------------- | ------------------ | ------------------------------------------------------------------------------------- | | `\a` | `\x07` | Alert (Beep, Bell) | | `\b` | `\x08` | Backspace | | `\e` | `\x1B` | Escape character | | `\f` | `\x0C` | Formfeed, Page Break | | `\n` | `\x0A` | Newline (Line Feed) | | `\r` | `\x0D` | Carriage Return | | `\t` | `\x09` | Horizontal Tab | | `\v` | `\x0B` | Vertical Tab | | `\\` | `\x5C` | Backslash | | `\/` | `\x2f` | Forward slash | | `\'` | `\x27` | Apostrophe or single quotation mark | | `\"` | `\x22` | Double quotation mark | | `\?` | `\x3F` | Question mark (used to avoid Digraphs and trigraphs) | | `\nnn` | any | The byte whose numerical value is given by _nnn_ interpreted as an _octal_ number | | `\xhh` | any | The byte whose numerical value is given by _hh_ interpreted as a _hexadecimal_ number | | `\uhhhh` | none | UTF8-8 code point given by _hhhh_ interpreted as an _hexadecimal_ number | This convention has been decided to follow the most common practices. In particular, `\nnn` is an _octal_ escape sequence in most languages including C, Ruby, Javascript, Python and more. This differs from OCaml where `\nnn` is considered a _digital_ escape sequence. These lexical conventions are used in the default `string.escape` and `string.unescape`. Here's an example of an escaped string: ``` # "\" \t \045 \x2f \u4f32";; - : string = "\" \t % / 2" ``` The function `string.quote` returns [JSON-compatible](https://www.json.org/json-en.html) strings. ### Regular expressions _This feature was introduced in liquidsoap version 2.1.0_ Regular expressions can be created using the `regexp` operator or the syntactic sugar: `r/.../<flags>`. For instance: ```liquidsoap # Using the regexp operator: r = regexp(flags=["g","i"], "foo([\\w])+bar") # Using the r/../ syntactic sugar: r = r/foo([\w])bar/gi ``` Using the `r/../` syntactic sugar makes it possible to write regular expressions without having to escape `\` characters, which makes them more easily readable. Regular expression flags are: - `i`: perform case-insensitive match - `g`: substitute all matched sub-strings, not just the first one - `s`: match all characters, including `\n` when using the `.` pattern - `m`: `^` and `$` match before/after newlines, not just at the beginning/end of a string Regular expressions have the following methods: - `replace(fn, s)`: replace matched substrings of `s` using function `fn`. If the `g` flag is not passed, only the first match is replaced otherwise, all matches are replaced - `split(s)`: split the given string on all substrings matching the regular expression. - `test(s)`: returns `true` if the given string matches the regular expression. - `exec(s)`: execute the regular expression and return a of list matches of the form: `[(<match index>, <match>), ..]`. Named matches are also supported and returned as property `groups` of type `[string * string]`: ```liquidsoap r/(foo)(?<gno>gni)?/g.exec("foogni") - : [int * string].{groups : [string * string]} = [ (2, "gni"), (1, "foo"), (0, "foogni") ].{ groups = [ ("gno", "gni") ] } ``` ### Booleans The _booleans_ are either `true` or `false` and are of type `bool`. They can be combined using the usual boolean operations - `and`: conjunction, - `or`: disjunction, - `not`: negation. Booleans typically originate from comparison operators, which take two values and return booleans: - `==`: compares for equality, - `!=`: compares for inequality, - `<=`: compares for inequality, and so on (`<`, `>=`, `>`). For instance, the following is a boolean expression: ```liquidsoap (n < 3) and not (s == "hello") ``` _Conditional branchings_ execute code depending on whether a condition is true or not. For instance, the code ```liquidsoap if (1 <= x and x <= 12) or (not 10h-15h) then print("The condition is satisfied") else print("The condition ain't satisfied") end ``` will print that the condition is satisfied when either `x` is between 1 and 12 or the current time is not between 10h and 15h. A conditional branching might return a value, which is the last computed value in the chosen branch. For instance, ```liquidsoap y = if x < 3 then "A" else "B" end ``` will assign `"A"` or `"B"` to `y` depending on whether `x` is below 3 or not. The two branches of a conditional should always have the same return type: ```liquidsoap x = if 1 == 2 then "A" else 5 end ``` will result in ``` At line 1, char 19-21: Error 5: this value has type string but it should be a subtype of int ``` meaning that `"A"` is a string but is expected to be an integer because the second branch returns an integer, and the two should be of same nature. The `else` branch is optional, in which case the `then` branch should be of type `unit`: ```liquidsoap if x == "admin" then print("Welcome admin") end ``` In the case where you want to perform a conditional branching in the `else` branch, the `elsif`{.liquidsoap} keyword should be used, as in the following example, which assigns 0, 1, 2 or 3 to `s` depending on whether `x` is `"a"`, `"b"`, `"c"` or something else: ```liquidsoap s = if x == "a" then 0 elsif x == "b" then 1 elsif x == "c" then 2 else 3 end ``` This is equivalent (but shorter to write) to the following sequence of imbricated conditional branchings: ```liquidsoap s = if x == "a" then 0 else if x == "b" then 1 else if x == "c" then 2 else 3 end end end ``` Finally, we should mention that the notation `c?a:b` is also available as a shorthand for `if c then a else b end`, so that the expression ```liquidsoap y = if x < 3 then "A" else "B" end ``` can be shortened to ```liquidsoap y = (x<3)?"A":"B" ``` (and people will think that you are a cool guy). #### Time predicates Time predicates are special boolean values such as `{0h-7h}`. These values are `true` or `false` depending on the current time. Some examples of time predicates are --- `{11h15-13h}` between 11h15 and 13h `{12h}` between 12h00 and 12h59 `{12h00}` at 12h00 `{00m}` on the first minute of every hour `{00m-09m}` on the first 10 minutes of every hour `{2w}` on Tuesday `{6w-7w}` on weekends --- Above, `w` stands for weekday: 1 is Monday, 2 is Tuesday, and so on. Sunday is both 0 and 7. Time predicate can also be parsed at runtime, for instance if you want to create them dynamically. The syntax is: ```liquidsoap # f = time.predicate("00m-30m");; f : () -> bool = <fun> ``` Be aware that, if parsing fails, it will raise `error.string`: ```liquidsoap # f = time.predicate("foo") Error 14: Uncaught runtime error: type: string, message: "Failed to parse foo as time predicate" ``` ### Unit Some functions, such as `print`, do not return a meaningful value: we are interested in what they are doing (e.g. printing on the standard output) and not in their result. However, since typing requires that everything returns something of some type, there is a particular type for the return of such functions: `unit`. Just as there are only two values in the booleans (`true` and `false`), there is only one value in the unit type, which is written `()`. This value can be thought of as the result of the expression saying "I'm done". ### Lists Some more elaborate values can be constructed by combining the previous ones. A first kind is _lists_ which are finite sequences of values, being all of the same type. They are constructed by square bracketing the sequence whose elements are separated by commas. For instance, the list ```liquidsoap [1, 4, 5] ``` is a list of three integers (1, 4 and 5), and its type is `[int]`, and the type of `["A", "B"]` would obviously be `[string]`. Note that a list can be empty: `[]`. You can extract list elements through _splats_ such as ```liquidsoap l = [1, 5, 7, 8, 9] let [x, _, z, ...t] = l ``` In this example, the value of `x` is `1`, the value of `z` is `7` and the value of `t` is [`8, 9]`. You can also combine lists in a similar way ```liquidsoap x = [1, ...[2, 3, 4], 5, ...[6, 7]] ``` In this example, the value of `x` is `[1, 2, 3, 4, 5, 6 ,7]` ### Tuples Another construction present in Liquidsoap is _tuples_ of values, which are finite sequences of values which, contrarily to lists, might have different types. For instance, ``` (3, 4.2, "hello") ``` is a triple (a tuple with three elements) of type ``` int * float * string ``` which indicate that the first element is an integer, the second a float and the third a string. Similarly to lists, there is a special syntax in order to access tuple elements. For instance, if `t` is the above tuple `(3, 4.2, "hello")`, we can write ```liquidsoap let (n, x, s) = t ``` which will assign the first element to the variable `n`, the second element to the variable `x` and the third element to the variable `s`. ## Programming primitives ### Variables We have already seen many examples of uses of _variables_: we use ```liquidsoap x = e ``` in order to assign the result of evaluating an expression `e` to a variable `x`, which can later on be referred to as `x`. Variables can be masked: we can define two variables with the same name, and at any point in the program the last defined value for the variable is used: ```liquidsoap n = 3 print(n) n = n + 2 print(n) ``` will print `3` and `5`. Contrarily to most languages, the value for a variable cannot be changed (unless we explicitly require this by using references, see below), so the above program does not modify the value of `n`, it is simply that a new `n` is defined. There is an alternative syntax for declaring variables which is ```liquidsoap def x = e end ``` It has the advantage that the expression `e` can spread over multiple lines and thus consist of multiple expressions, in which case the value of the last one will be assigned to `x`. This is particularly useful to use local variables when defining a value. ### References As indicated above, by default, the value of a variable cannot be changed. However, one can use a _reference_ in order to be able to do this. Those can be seen as memory cells, containing values of a given fixed type, which can be modified during the execution of the program. They are created with the `ref` keyword, with the initial value of the cell as argument. For instance, ```liquidsoap r = ref(5) ``` declares that `r` is a reference which contains `5` as initial value. Since `5` is an integer (of type `int`), the type of the reference `r` will be ``` ref(int) ``` meaning that its a memory cell containing integers. On such a reference, two operations are available. - One can obtain the value of the reference by applying the reference to `()`, so that `r()` denotes the value contained in the reference `r`, for instance ```liquidsoap x = r() + 4 ``` declares the variable `x` as being 9 (which is 5+4). - One can change the value of the reference by using the `:=` keyword, e.g. ```liquidsoap r := 2 ``` will assign the value 2 to `r`. Internally, this is done by calling the `set` method of the reference, so that the above is equivalent to writing ```liquidsoap r.set(2) ``` which used to be the syntax for some reference manipulations. ### Loops The usual looping constructions are available in Liquidsoap. The `for` loop repeatedly executes a portion of code with an integer variable varying between two bounds, being increased by one each time. For instance, the following code will print the integers `1`, `2`, `3`, `4` and `5`, which are the values successively taken by the variable `i`: ```liquidsoap for i = 1 to 5 do print(i) end ``` In practice, such loops could be used to add a bunch of numbered files (e.g. `music1.mp3`, `music2.mp3`, `music3.mp3`, etc.) in a request queue for instance. The `while` loop repeatedly executes a portion of code, as long a condition is satisfied. For instance, the following code doubles the contents of the reference `n` as long as its value is below `10`: ```liquidsoap n = ref(1) while n() < 10 do n := n() * 2 end print(n()) ``` The variable `n` will thus successively take the values `1`, `2`, `4`, `8` and `16`, at which point the looping condition `n() < 10` is not satisfied anymore and the loop is exited. The printed value is thus `16`. ## Functions Liquidsoap is built around the notion of function: most operations are performed by those. For some reason, we sometimes call _operators_ the functions acting on sources. Liquidsoap includes a standard library which consists of functions defined in the Liquidsoap language, including fairly complex operators such as `playlist` which plays a playlist or `crossfade` which takes care of fading between songs. ### Basics A function is a construction which takes a bunch of arguments and produces a result. For instance, we can define a function `f` taking two float arguments, prints the first and returns the result of adding twice the first to the second: ```liquidsoap def f(x, y) print(x) 2*x+y end ``` This function can also be written on one line if we use semicolons (`;`) to separate the instructions instead of changing line: ```liquidsoap def f(x, y) = print(x); 2*x+y end ``` The type of this function is ``` (int, int) -> int ``` The arrow `->` means that it is a function, on the left are the types of the arguments (here, two arguments of type `int`) and on the right is the type of the returned value of the function (here, `int`). In order to use this function, we have to apply it to arguments, as in ``` f(3, 4) ``` This will trigger the evaluation of the function, where the argument `x` (resp. `y`) is replaced by `3` (resp. `4`), i.e., it will print `3` and return the evaluation of `2*3+4`, which is `10`. ### Anonymous functions For concision in scripts, it is possible define a function without giving it a name, using the syntax ```liquidsoap fun (x) -> ... ``` This is called an _anonymous function_, and it is typically used in order to specify short handlers in arguments. #### Anonymous function with no arguments You will see that it is quite common to use anonymous functions with no arguments. For this reason, we have introduced a special convenient syntax for those and allow writing ```liquidsoap {...} ``` instead of ```liquidsoap fun () -> ... ``` ### Labeled arguments A function can have an arbitrary number of arguments, and when there are many of them it becomes difficult to keep track of their order and their order matter! For instance, the following function computes the sample rate given a number of samples in a given period of time: ```liquidsoap def samplerate(samples, duration) = samples / duration end ``` which is of type ``` (float, float) -> float ``` For instance, if you have 110250 samples over 2.5 seconds the samplerate will be `samplerate(110250., 2.5)` which is 44100. However, if you mix the order of the arguments and type `samplerate(2.5, 110250.)`, you will get quite a different result and this will not be detected by the typing system because both arguments have the same type. Fortunately, we can give _labels_ to arguments in order to prevent this, which forces explicitly naming the arguments. This is indicated by prefixing the arguments with a tilde "`~`": ```liquidsoap def samplerate(~samples, ~duration) = samples / duration end ``` The labels will be indicated as follows in the type: ``` (samples : float, duration : float) -> float ``` Namely, in the above type, we read that the argument labeled `samples` is a float and similarly for the one labeled `duration`. For those arguments, we have to give the name of the argument when calling the function: ```liquidsoap samplerate(samples=110250., duration=2.5) ``` The nice byproduct is that the order of the arguments does not matter anymore, the following will give the same result: ```liquidsoap samplerate(duration=2.5, samples=110250.) ``` Of course, a function can have both labeled and non-labeled arguments. ### Optional arguments Another useful feature is that we can give _default values_ to arguments, which thus become _optional_: if, when calling the function, a value is not specified for such arguments, the default value will be used. For instance, if for some reason we tend to generally measure samples over a period of 2.5 seconds, we can make this become the value for the `duration` parameter: ```{.liquidsoap include="samplerate3.liq" from="BEGIN" to="END"} ``` In this way, if we do not specify a value for the duration, its value will implicitly be assumed to be 2.5, so that the expression: ```liquidsoap samplerate(samples=110250.) ``` will still evaluate to 44100. Of course, if we want to use another value for the duration, we can still specify it, in which case the default value will be ignored: ```liquidsoap samplerate(samples=132300., duration=3.) ``` The presence of an optional argument is indicated in the type by prefixing the corresponding label with "`?`", so that the type of the above function is ``` (samples : float, ?duration : float) -> float ``` ### Advanced argument syntax Arguments can be ignored, typed, named (and renamed) and given default values and all these possibilities can be combined. Here is the syntax to do it: ```{.liquidsoap include="labeled_arguments.liq"} ``` ### Getters We often want to be able to dynamically modify some parameters in a script. For instance, consider the operator `amplify`, which takes a float and an audio source and returns the audio amplified by the given volume factor: we can expect its type to be ``` (float, source('a)) -> source('a) ``` so that we can use it to have a radio consisting of a microphone input amplified by a factor 1.2 by ```liquidsoap mic = input.alsa() radio = amplify(1.2, mic) ``` In the above example, the volume 1.2 was supposedly chosen because the sound delivered by the microphone is not loud enough, but this loudness can vary from time to time, depending on the speaker for instance, and we would like to be able to dynamically update it. The problem with the current operator is that the volume is of type `float` and a float cannot change over time: it has a fixed value. In order for the volume to have the possibility to vary over time, instead of having a `float` argument for `amplify`, we have decided to have instead an argument of type ``` () -> float ``` This is a function which takes no argument and returns a float (remember that a function can take an arbitrary number of arguments, which includes zero arguments). It is very close to a float excepting that each time it is called the returned value can change: we now have the possibility of having something like a float which varies over time. We like to call such a function a _float getter_, since it can be seen as some kind of object on which the only operation we can perform is get the value. For instance, we can define a float getter by ```liquidsoap n = ref(0.) def f () n := n() + 1. n() end ``` Each time we call `f`, by writing `f()` in our script, the resulting float will be increased by one compared to the previous one: if we try it in an interactive session, we obtain ``` # f();; - : float = 1.0 # f();; - : float = 2.0 # f();; - : float = 3.0 ``` Since defining such arguments often involves expressions of the form ```liquidsoap fun () -> e ``` which is somewhat heavy, we have introduced the alternative syntax ```liquidsoap {e} ``` for it. Finally, in order to simplify things a bit, you will see that the type of amplify is actually ``` ({float}, source('a)) -> source('a) ``` where the type `{float}` means that both `float` and `() -> float` are accepted, so that you can still write constant floats where float getters are expected. What we actually call a _getter_ is generally an element of such a type, which is either a constant or a function with no argument. In order to work with such types, the standard library often uses the following functions: - `getter`, of type `({'a}) -> {'a}`, creates a getter, - `getter.get`, of type `({'a}) -> 'a`, retrieves the current value of a getter, - `getter.function`, of type `({'a}) -> () -> 'a`, creates a function from a getter. ### Recursive functions Liquidsoap supports functions which are _recursive_, i.e., that can call themselves. For instance, in mathematics, the factorial function on natural numbers is defined as fact(n)=1×2×3×...×n, but it can also be defined recursively as the function such that fact(0)=1 and fact(n)=n×fact(n-1) when n>0: you can easily check by hand that the two functions agree on small values of n (and prove that they agree on all values of n). This last formulation has the advantage of immediately translating to the following implementation of factorial: ```liquidsoap def rec fact(n) = if n == 0 then 1 else n * fact(n-1) end end ``` for which you can check that `fact(5)` gives 120, the expected result. As another example, the `list.length` function, which computes the length of a list, can be programmed in the following way in Liquidsoap: ```liquidsoap def rec length(l) if l == [] then 0 else 1 + length(list.tl(l)) end end ``` We do not detail much further this trait since it is unlikely to be used for radios, but you can see a few occurrences of it in the standard library. ## Records and modules ### Records Suppose that we want to store and manipulate structured data. For instance, a list of songs together with their duration and tempo. One way to store each song is as a tuple of type `string * float * float`, but there is a risk of confusion between the duration and the length which are both floats, and the situation would of course be worse if there were more fields. In order to overcome this, one can use a _record_ which is basically the same as a tuple, excepting that fields are named. In our case, we can store a song as ```liquidsoap song = { filename = "song.mp3", duration = 257., bpm = 132. } ``` which is a record with three fields respectively named `filename`, `duration` and `bpm`. The type of such a record is ``` {filename : string, duration : float, bpm : float} ``` which indicates the fields and their respective type. In order to access a field of a record, we can use the syntax `record.field`. For instance, we can print the duration with ```liquidsoap print("The duration of the song is #{song.duration} seconds") ``` Records can be re-used using _spreads_: ```liquidsoap song = { filename = "song.mp3", duration = 257., bpm = 132. } # This is a fresh value with all the fields from `song` and # a new `id` field: song_with_id = { id = 1234, ...song } ``` Alternatively, you can also extend a record using the explicit `v.{...}` syntax: ```liquidsoap song = { filename = "song.mp3", duration = 257., bpm = 132. } # This is a fresh value with all the fields from `song` and # a new `id` field: song_with_id = song.{id = 1234} ``` ### Modules Records are heavily used in Liquidsoap in order to structure the functions of the standard library. We tend to call _module_ a record with only functions, but this is really the same as a record. For instance, all the functions related to lists are in the `list` module and functions such as `list.hd` are fields of this record. For this reason, the `def` construction allows adding fields in record. For instance, the definition ```liquidsoap def list.last(l) list.nth(l, list.length(l)-1) end ``` adds, in the module `list`, a new field named `last`, which is a function which computes the last element of a list. Another shorter syntax to perform definitions consists in using the `let` keyword which allows assigning a value to a field, so that the previous example can be rewritten as ```liquidasoap let list.last = fun(l) -> list.nth(l, list.length(l)-1) ``` If you often use the functions of a specific module, the `open` keyword allows using its fields without having to prefix them by the module name. For instance, in the following example ```liquidsoap l = [1,2,3] open list x = nth(l, length(l)-1) ``` the `open list` directive allows directly using the functions in this module: we can simply write `nth` and `length` instead of `list.nth` and `list.length`. ### Values with fields A unique feature of the Liquidsoap language is that it allows adding fields to any value. We also call them _methods_ by analogy with object-oriented programming. For instance, we can write ```liquidsoap song = "test.mp3".{duration = 123., bpm = 120.} ``` which defines a string (`"test.mp3"`) with two methods (`duration` and `bpm`). This value has type ``` string.{duration : float, bpm : float} ``` and behaves like a string, e.g. we can concatenate it with other strings: ```liquidsoap print("the song is " ^ song) ``` but we can also invoke its methods like a record or a module: ```liquidsoap print("the duration is #{song.duration}") ``` The construction `def replaces` allows changing the main value while keeping the methods unchanged, so that ```liquidsoap def replaces song = "newfile.mp3" end print(song) ``` will print ``` "newfile.mp3".{duration = 123., bpm = 120.} ``` (note that the string is modified but not the fields `duration` and `bpm`). ### Optional fields During the execution of your script, it can be useful to allow functions to receive records that may or may not have a specific field. This can be used, for instance, to model optional arguments. This can be achieved in two ways: 1. Using the `x.foo ?? default` syntax Here's an example: ```liquidsoap # This functions adds 1 to x unless options has a # add field in which case it adds this value def f(x, options) = x + (options.add ?? 1) end ``` The type of this function is: ```liquidsoap f : (int, 'a.{add? : int}) -> int = <fun> ``` which denotes that the `options` argument can be any value that may or may not have a `add` field. However, if this field is present, it must be of type `int`. 2. Using the `x?.foo` syntax Given a variable `x`, `x?.foo` returns the field value `foo`, if present, or `null` otherwise. The `?.` syntax can be chained and works with functions, which make it a very convenient way to drill deep inside nested records: ```liquidsoap x?.fn(123, "aabb")?.field ``` ## Patterns As explained earlier, you can use several constructions to extract data from structured values such as `let [x, y] = l` and etc. These constructions are called _patterns_. Patterns allows to quickly access values nested deeply inside structured data in a way that remains pretty intuitive when reading the code. Patterns are constructed using _variable placeholders_, which are either a variable name such as: `x`, `foo`, etc. or the special symbol `_` for any ignored value. ### Tuple patterns Tuple patterns are pretty straight forward and consist of any sequence of variable captures: ```liquidsoap let (x, y, _, z) = (123, "aabbcc", true, 3.14) # x = 1, y = "aabbcc", z = 3.14 ``` ### List patterns List patterns are composed of variable placeholders, etc. and spreads of the form: `...<variable placeholder>` such as: `...z`. The spread `..._` can be simply written `...`. See below for an example. You can use any combination of: - Forward variable names: these capture the first elements of the list. - One spread: this captures any remaining element as a list. - Backward variable names: these capture the last elements of a the list. Here are some examples: ```liquidsoap # Forward capture: let [x, y, z] = [1, 2, 3] # x = 1, y = 2, z = 3 # Forward capture with spread: let [x, y, ...z] = [1, 2, 3, 4] # x = 1, y = 2, z = [3, 4] # Forward capture with ignored values: let [_, x, ...z] = [1, 2, 3, 4] # x = 2, z = [3, 4] # Full capture: let [x, y, ...z, t, u, v] = [1, 2, 3, 4, 5, 6, 7, 8, 9] # x = 1, y = 2, z = [3, 4, 5, 6, 7], t = 7, u = 8, v = 9 # Backward capture only. let [..., t, u, v] = [1, 2, 3, 4, 5] # t = 3, u = 4, v = 5 ``` ### Record and module patterns Record and module patterns consist of either variable names (not variable capture!), which capture method values or variable names with an associated pattern. Record patterns are of the form: `{<captured methods>}` while module patterns are of the form: `<variable capture>.{<captured methods>}` Here are some examples: ```liquidsoap # Record capture let {foo, bar} = {foo = 123, bar = "baz", gni = true} # foo = 123, bar = "baz" # Record capture with spread let {foo, bar, ...x} = {foo = 123, bar = "baz", gni = true} # foo = 123, bar = "baz", x = {gni = true} # Module capture let v.{foo, bar} = "aabbcc".{foo = 123, bar = "baz", gni = true} # v = "aabbcc", foo = 123, bar = "baz" # Module capture with ignored value let _.{foo, bar} = "aabbcc".{foo = 123, bar = "baz", gni = true} # foo = 123, bar = "baz" # Same as: let {foo, bar} = "aabbcc".{foo = 123, bar = "baz", gni = true} # foo = 123, bar = "baz" # Record capture with sub-patterns. Same works for module! let {foo = [x, y, z], gni} = {foo = [1, 2, 3], gni = "baz"} # x = 1, y = 2, z = 3, gni = "baz" # If you want to capture foo and destructure it, you need # to specify it twice: let {foo, foo = [x, y, z], gni} = {foo = [1, 2, 3], gni = "baz"} # foo = [x, y, z], x = 1, y = 2, z = 3, gni = "baz" # Record entry can be renamed and ignored on capture: let {foo=_, gni=gno, gni={gna}, gni={gna=gnu}...rest} = { foo = 123, gni = {gna="bla"} } # gno = {gnna="bla"}, gna="bla", gnu="bla", rest = {foo=123} # Record capture with optional methods: let { foo? } = () # foo = null let { foo? } = { foo = 123 } # foo = 123 ``` ## Combining patterns As seen with record and modules, patterns can be combined at will, for instance, these are all valid patterns: ```liquidsoap let [{foo}, {gni}, ..., {baz}] = l let (_.{ bla = [..., z] }, t, _, u) = x ``` ## Destructuring function arguments Patterns are also valid in function arguments and can be used to desctructure function arguments before passing them to the function's code. Here are some example: ```liquidsoap # Take a labelled argument x and grab its `gno` method: def f(~x:{gno}) = gno + 1 end # Function type: f : (x : 'a.{gno : int}) -> int # Call it: f({gno = 1}) # Returns 2 # Take an anonymous array and adds the first two elements: def f([a, b]) = a + b end # Function type: f : (['a]) -> 'a where 'a is a number type # Call it: f([1, 2]) # Returns 3 # And: f([1]);; # Error: # At line 8, char 0-6: # # Error 14: Uncaught runtime error: # type: not_found, # message: "List value does not have enough elements to fit the extraction pattern!", ``` ## Advanced values In this section, we detail some more advanced values than the ones presented in. You are not expected to be understanding those in details for basic uses of Liquidsoap. ### Errors In the case where a function does not have a sensible result to return, it can raise an _error_. Typically, if we try to take the head of the empty list without specifying a default value (with the optional parameter `default`), an error will be raised. By default, this error will stop the script, which is usually not a desirable behavior. For instance, if you try to run a script containing ```liquidsoap list.hd([]) ``` the program will exit printing ``` Error 14: Uncaught runtime error: type: not_found, message: "no default value for list.hd" ``` This means that the error named "`not_found`" was raised, with a message explaining that the function did not have a reasonable default value of the head to provide. In order to avoid this, one can _catch_ exceptions with the syntax ```liquidsoap try code catch err do handler end ``` This will execute the instructions `code`: if an error is raised at some point during this, the code `handler` is executed, with `err` being the error. For instance, instead of writing ```liquidsaop l = [] x = list.hd(default=0, l) ``` we could equivalently write ```liquidsoap l = [] x = try list.hd(l) catch err do 0 end ``` The name and message associated to an error can respectively be retrieved using the error `kind` and `message` attributes, e.g. we can write ```liquidsoap try ... catch err do print("the error #{err.kind} was raised") print("the error message is #{err.message}") end ``` Typically, when reading from or writing to a file, errors will be raised when a problem occurs (such as reading from a non-existent file or writing a file in a non-existent directory) and one should always check for those and log the corresponding message: ```liquidsoap data = "bla" try file.write(data=data, "/non/existent/path") catch err do log.important("Could not write to file: #{error.message(err)}") end ``` Specific errors can be caught with the syntax ```liquidsoap try ... catch err : l do ... end ``` where `l` is a list of error names that we want to handle here. Errors can be raised from Liquidsoap with the function `error.raise`, which takes as arguments the error to raise and the error message. For instance: ```liquidsoap error.raise(error.not_found, "we could not find your result") ``` We should also mention that all the errors should be declared in advance with the function `error.register`, which takes as argument the name of the new error to register: ```liquidsoap myerr = error.register("my_error") error.raise(myerr, "testing my own error") ``` Lastly, if you need to make sure that a certain piece of code is executed whether or not there is an exception raised, you can use _finally_: ```liquidsoap # Without a catch block try ... finally ... end # With a catch block try ... catch ... do ... finally ... end ``` This is roughly equivalent to: ```liquidsoap finally_called = ref(false) def finally() = ... end try let ret = ... finally_called := true finally() ret # If specified: catch ... do let ret = ... if not finally_called() then finally() end ret end ``` The biggest different is that `finally` is called on all errors, including internal errors that cannot be caught by the runtime code. Errors raised in a `finally` block do override any previously raised errors. ### Nullable values It is sometimes useful to have a default value for a type. In Liquidsoap, there is a special value for this, which is called `null`. Given a type `t`, we write `t?` for the type of values which can be either of type `t` or be `null`: such a value is said to be _nullable_. For instance, we could redefine the `list.hd` function in order to return null (instead of raising an error) when the list is empty: ```liquidsoap def list.hd(l) if l == [] then null else list.hd(l) end end ``` whose type would be ``` (['a]) -> 'a? ``` since it takes as argument a list whose elements are of type `'a` and returns a list whose elements are `'a` or `null`. As it can be observed above, the null value is created with `null`. In order to use a nullable value, one typically uses the construction `x ?? d` which is the value `x` excepting when it is null, in which case it is the default value `d`. For instance, with the above head function: ```liquidsoap x = list.hd(l) print("the head is " ^ (x ?? "not defined")) ``` Some other useful functions include - `null.defined`: test whether a value is null or not, - `null.get`: obtain the value of a nullable value supposed to be distinct from `null`, - `null.case`: execute a function or another, depending on whether a value is null or not. ### Runtime evaluation of scripting values Similarly to how JSON is [parsed](json.html), you can evaluate string into values at runtime using the `eval` decorator. As with JSON, too, the recommended way to use it is by adding an explicit type annotation: ```liquidsoap let eval (x: {foo: int, bla: string}) = "{foo = 123, bla = \"gni\"}" print("x.foo = #{x.foo}, x.bla = #{x.bla}") ``` ### Including other files It is often useful to split your script over multiple files, either because it has become quite large, or because you want to be able to reuse common functions between different scripts. You can include a file `file.liq` in a script by writing ```liquidsoap %include "file.liq" ``` which will be evaluated as if you had pasted the contents of the file in place of the command. For instance, this is useful in order to store passwords out of the main file, in order to avoid risking leaking those when handing the script to some other people. Typically, one would have a file `passwords.liq` defining the passwords in variables, e.g. ```liquidsoap radio_pass = "secretpassword" ``` and would then use it by including it: ```liquidsoap %include "passwords.liq" radio = ... output.icecast(%mp3, host="localhost", port=8000, password=radio_pass, mount="my-radio.mp3", radio) ``` so that passwords are not shown in the main script. ### Code comments Comments can be added to your code in two ways: _Multi-line comments_ are comments that can span multiple lines. They are delimitated by the sequence of characters `#<` at the beginning and `>#` at the end. Anything in between those two sequences is considered code comment. Here are some examples: Simple multiline comments: ```liquidsoap #< This is a comment ># ``` Multiline comments can be nested: ```liquidsoap #< This is a top-level comment # This is also a comment #< This is a nested code comment ># ># ``` Fancy looking multiline comment ```liquidsoap #<------- BEGIN CODE COMMENT ----# Comments can also look like this #--------- END CODE COMMENT -----># ``` _Single-line comments_ are comments that are limited to the current line. Such comments are started with the character `#` without a following `<`. Anything after the initial `#` character and until the end of the line is considered code comment: ```liquidsoap def f(x) = # This is a single line comment. 123 end ``` ## Caching Type-checking scripts can take a lot of time and consume memory. To optimize things, this step can be cached. During the first execution, the script is parsed, type checked and evaluated. On second and any following execution, a cache of the script is used, reducing the typechecking phase, sometimes by a `100x` factor! Here's a log without caching on a M3 macbook pro: ``` 2024/07/03 14:31:41 [startup:3] main script hash computation: 0.03s 2024/07/03 14:31:41 [startup:3] main script cache retrieval: 0.03s 2024/07/03 14:31:41 [startup:3] stdlib hash computation: 0.03s 2024/07/03 14:31:41 [startup:3] stdlib cache retrieval: 0.03s 2024/07/03 14:31:41 [startup:3] Typechecking stdlib: 3.37s 2024/07/03 14:31:41 [startup:3] Typechecking main script: 0.00s ``` And the same log after caching: ``` 2024/07/03 14:32:59 [startup:3] main script hash computation: 0.02s 2024/07/03 14:32:59 [startup:3] Loading main script from cache! 2024/07/03 14:32:59 [startup:3] main script cache retrieval: 0.05s ``` Scripts can be cached ahead of time without executing them, for instance while compiling a docker image, using `--cache-only`. Caching can also be disabled using `--no-cache`. Caching happens at two different time: - First the standard library is cached - Then the script itself is cached Caching the standard library makes it possible to run the type-checker faster on new scripts. Here's an example of a log from running a new script with a cached standard library: ``` 2024/07/03 14:33:27 [startup:3] main script hash computation: 0.02s 2024/07/03 14:33:27 [startup:3] main script cache retrieval: 0.02s 2024/07/03 14:33:27 [startup:3] stdlib hash computation: 0.03s 2024/07/03 14:33:27 [startup:3] Loading stdlib from cache! 2024/07/03 14:33:27 [startup:3] stdlib cache retrieval: 0.10s 2024/07/03 14:33:27 [startup:3] Typechecking main script: 0.00s ``` Caching can be disabled by setting `LIQ_CACHE` to anything else than `"true"`. ### Cache locations Cache files can accumulate and also take up disk space so it is important to know where they are located! There are two type of cache locations: - System cache for cached files that should be shared with all liquidsoap scripts. This is where the standard library cache is located. This location is a system-wide path on unix system such as `/var/cache/liquidsoap`. - User cache for cached files that are specific to the user running liquidsoap scripts. On unix systems, this location is at `$HOME/.cache/liquidsoap`. On windows, the default cache directory for both type of cache locations is in the same directory as the binary. At runtime, `liquidsoap.cache(mode=<mode>)` returns the cache directory. `mode` should be one of: `"user"` or `"system"`. ### Cache maintenance There is a cache maintenance routine which deletes unused cache files after `10` days and keeps the cache to a maximum of `200` files. You can run the cache maintenance routing by calling `liquidsoap.cache.maintenance(mode=<mode>)` manually. Here, too, `mode` should be one of: `"user"` or `"system"`. ### Cache security Please be aware that the cache does _not_ encrypt its values. As such, user cache files should be considered sensitive as they may contain password and other runtime secrets that are available through your scripts. We recommend to: - Use environment variables as much as possible when passing secrets - Secure your user script and cache files. The default creation permissions for user cache files is: `0o600` so only the user creating them should be able to read them. You should make sure that your script permissions are also similarly restricted. ### Cache and memory usage One side-benefit from loading a script from cache is that the entire typechecking process is skipped. This leads to a significant reduction in initial memory consumption, typically down from about `375MB` to about `80MB`! Additionally, the OCaml memory compaction algorithm is executed after typechecking your script but before running it. This results in additional memory usage reduction with a slight delay in initial startup time. To maximize your script startup time you should: - Cache it before running it to skip the initial typececking - Set `settings.init.compact_before_start` to `false` to skip the initial memory compaction: ```liquidsoap settings.init.compact_before_start := false ``` ### Cache environment variables The following environment variables control the cache behavior: - `LIQ_CACHE`: disable the cache when set to anything else than `1` or `true` - `LIQ_CACHE_SYSTEM_DIR`: set the cache system directory - `LIQ_CACHE_SYSTEM_DIR_PERMS`: set the permission used when creating cache system directory (and its parents when needed). Default: `0o755` - `LIQ_CACHE_SYSTEM_FILE_PERMS`: set the permissions used when creating a system cache file. Default: `0o644` - `LIQ_CACHE_USER_DIR`: set the cache user directory - `LIQ_CACHE_USER_DIR_PERMS`: set the permission used when creating cache user directory (and its parents when needed). Default: `0o700`. - `LIQ_CACHE_USER_FILE_PERMS`: set the permissions used when creating a user cache file. Default: `0o600` - `LIQ_CACHE_MAX_DAYS`: set the maximum days a cache file can be stored before it is eligible to be deleted during the next cache maintenance pass. - `LIQ_CACHE_MAX_FILES`: set the maximum number of files in each cache directory. Older files are removed first. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/latency_control.md�����������������������������������������������������0000664�0000000�0000000�00000011266�15132732333�0021641�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Understanding Latency in Liquidsoap **What it means when you see: `We must catchup xxx sec`** ### What Is Latency? To generate a media stream, Liquidsoap runs a **media streaming loop**. This loop creates small chunks of audio or video data (e.g. 0.04 seconds long) and sends them to your outputs. If it takes **less than 0.04 seconds** to produce a 0.04s chunk, Liquidsoap is on time—or even a bit ahead—and it can rest briefly before producing the next chunk. But if it takes **more than 0.04 seconds**, Liquidsoap starts to fall behind. When the delay grows too large, you’ll see a message like: > **We must catchup 1.42 sec** ⏱️ This means the system is **too slow** and needs to produce data faster to get back on track. ### Who Controls Time? Not all media setups behave the same. Some components control their own timing, while others rely on Liquidsoap to do it. #### 1. `self-sync` sources (external time control) Some sources or outputs are **self-synchronized**: they _block_ until it’s time to produce or consume data. These components manage their own timing, and Liquidsoap just follows along. Examples of self-sync components: - 🎧 Sound cards (e.g. `output.ao`, `input.alsa`, etc..) - 🌐 Streaming inputs (e.g. `input.srt`, or `input.ffmpeg` (depending on its source)) In these cases, Liquidsoap doesn't need to check the clock—it just lets the source or output drive the latency. #### 2. CPU-Controlled Latency (internal time control) Other setups—like playing audio files or streaming to icecast—don’t manage time themselves. Liquidsoap must rely on the **CPU clock** to pace the streaming loop. Examples: - Audio files (`single`, `playlist`, etc.) - Icecast or shoutcast streaming (`output.icecast`, `output.shoutcast`) ### Switching Between Time Sources 🔀 Your stream may involve switching between multiple types of sources. For example: ```liquidsoap fallback([input.srt(...), single("music.mp3")]) ``` In this case: - `single` is CPU-controlled. - `input.srt` is self-sync. Liquidsoap has to decide **who’s in charge of the clock** at any given moment. It does this by looking at the sources that will be used to produce data in the next round of the streaming loop. For instance, in the above, only one of the two sources will ever be used to produce data. If it is `single`, the source is CPU-controled, otherwise it is `self-sync`. ### Synchronization conflicts ⚠️ In some cases, you may have to fix synchronization conflicts. For instance: ```liquidsoap output.ao(fallible=true, input.srt("...")) ``` can result in this error: ``` Error 17: clock input.srt has multiple synchronization sources. Do you need to set self_sync=false? Sync sources: srt from source input.srt ao from source output.ao ``` In this case, you should let one of the source or output drive the other one: ```liquidsoap output.ao(fallible=true, input.srt("...", self_sync=false)) ``` This tells Liquidsoap to follow the timing of the AO output. ### Diagnosing Latency Issues 🧪 When you see a `"We must catchup"` message, here’s how to go about diagnosing the issue: #### ✅ 1. Is latency CPU-controlled? If no self-synchronized component is active, Liquidsoap must use the CPU clock to manage time. That means _any delay in computation_ causes the system to fall behind. #### ✅ 2. Should a source be self-synchronized? In some cases such as when using `input.ffmpeg`, you may need to manually set it as `self_sync`. Typically, `input.ffmpeg` should be self-sync when decoding a `libsrt` or `rtmp` input. #### ✅ 3. Can the system keep up? If timing is CPU-controlled, then Liquidsoap needs to generate chunks fast enough to stay on schedule. If it can’t, you’ll see the catchup warning. **Common culprits:** - 🧮 CPU isn’t fast enough to decode/encode in real-time. - 💽 Disk access is slow—especially with network-based filesystems like NFS. - 🔄 Blocking code inside the streaming loop. 💡 **Pro Tip:** Before version 2.4.0, all callbacks in Liquidsoap were synchronous (blocking). Since 2.4.0, most callbacks are **asynchronous** by default. #### ✅ 4. Are other processes slowing things down? Sometimes it's not Liquidsoap's fault. Other system tasks—like `cron` jobs or background processes—can momentarily hog CPU or disk resources and cause temporary latency. ### Final Notes Latency can be confusing at first, but it's usually about one of two things: 1. Liquidsoap is trying to manage time itself and falls behind. Your system might be too slow for the task at hand. 2. You forgot to let a source or output manage time (`self_sync`). If you're still stuck: - Enable detailed logs with `log.level = 4` - Try simplifying your setup and adding pieces back one by one ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/�������������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0016677�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/Makefile�����������������������������������������������������������0000664�0000000�0000000�00000000166�15132732333�0020342�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������all: check: @for i in *.liq; do \ echo -n "Testing $$i... "; \ ./liquidsoap --check $$i; \ echo " done"; \ done ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/append-silence.liq�������������������������������������������������0000664�0000000�0000000�00000002246�15132732333�0022301�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The live source. We use a short buffer to switch more quickly to the source # when reconnecting. live_source = input.harbor("mount-point-name", buffer=3.) # A playlist source. playlist_source = playlist("/path/to/playlist") # Set to `true` when we should be adding silence. should_append = ref(false) # Append 5. of silence when needed. fallback_source = append( playlist_source, fun (_) -> if should_append() then should_append := false blank(duration=5.) else source.fail() end ) # Transition to live def to_live(playlist, live) = sequence([playlist, live]) end # Transition back to playlist def to_playlist(live, playlist) = # Ask to insert a silent track. should_append := true # Cancel current track. This will also set the playlist to play a new # track. If needed, `cancel_pending` can be used to for a new silent track # without skipping the playlist current track. fallback_source.skip() sequence([live, playlist]) end radio = fallback( track_sensitive=false, transitions=[to_live, to_playlist], [live_source, fallback_source] ) # END output.dummy(fallible=true, radio) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/archive-cleaner.liq������������������������������������������������0000664�0000000�0000000�00000000502�15132732333�0022433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������thread.run( every=3600., { list.iter( fun (msg) -> log(msg, label="archive_cleaner"), list.append( process.read.lines( "find /archive/* -type f -mtime +31 -delete" ), process.read.lines( "find /archive/* -type d -empty -delete" ) ) ) } ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/basic-radio.liq����������������������������������������������������0000664�0000000�0000000�00000001164�15132732333�0021565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/liquidsoap # Log dir log.file.path.set("/tmp/basic-radio.log") # Music myplaylist = playlist("~/radio/music.m3u") # Some jingles jingles = playlist("~/radio/jingles.m3u") # If something goes wrong, we'll play this security = single("~/radio/sounds/default.ogg") # Start building the feed with music radio = myplaylist # Now add some jingles radio = random(weights=[1, 4], [jingles, radio]) # And finally the security radio = fallback(track_sensitive=false, [radio, security]) # Stream it out output.icecast( %vorbis, host="localhost", port=8000, password="hackme", mount="basic-radio.ogg", radio ) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/beets-amplify.liq��������������������������������������������������0000664�0000000�0000000�00000001074�15132732333�0022151�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BEET = "/usr/bin/beet" # BEGIN def beets(id, query) = beets_src = blank.eat( id="#{id}_", start_blank=true, max_blank=1.0, threshold=-45.0, amplify( override="replaygain_track_gain", 1.0, request.dynamic( id=id, retry_delay=1., { request.create( string.trim( process.read( "#{BEET} random -f '$path' #{query}" ) ) ) } ) ) ) (beets_src : source) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/beets-protocol-short.liq�������������������������������������������0000664�0000000�0000000�00000000255�15132732333�0023506�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������protocol.add( "beets", fun (~rlog:_, ~maxtime:_, arg) -> list.hd( process.read.lines( "/home/me/path/to/beet random -f '$path' #{arg}" ) ) ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/beets-protocol.liq�������������������������������������������������0000664�0000000�0000000�00000001074�15132732333�0022351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BEET = "/usr/bin/beets" # BEGIN def beets_protocol(~rlog, ~maxtime, arg) = timeout = maxtime - time() command = "#{BEET} random -f '$path' #{arg}" p = process.run(timeout=timeout, command) if p.status == "exit" and p.status.code == 0 then string.trim(p.stdout) else rlog( "Failed to execute #{command}: #{p.status} (#{p.status.code}) #{p.stderr}" ) null end end protocol.add( "beets", beets_protocol, syntax="same arguments as beet's random module, see \ https://beets.readthedocs.io/en/stable/reference/query.html" ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/beets-source.liq���������������������������������������������������0000664�0000000�0000000�00000001125�15132732333�0022005�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������BEET = "/usr/bin/beet" # BEGIN def beets(id, query) = beets_src = request.dynamic( id=id, retry_delay=1., { request.create( string.trim( process.read( "#{BEET} random -f '$path' #{query}" ) ) ) } ) (beets_src : source) end all_music = beets("all_music", "") recent_music = beets("recent_music", "added:-1m..") rock_music = beets("rock_music", "genre:Rock") # END output.dummy(fallible=true, all_music) output.dummy(fallible=true, recent_music) output.dummy(fallible=true, rock_music) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/blank-detect.liq���������������������������������������������������0000664�0000000�0000000�00000000340�15132732333�0021740�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = playlist("my-playlist") # BEGIN def handler() = process.run( "/path/to/your/script to do whatever you want" ) end s = blank.detect(s) s.on_blank(synchronous=false, handler) # END output.dummy(fallible=true, s) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/blank-sorry.liq����������������������������������������������������0000664�0000000�0000000�00000000734�15132732333�0021655�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������live = input.pulseaudio() interlude = single("/path/to/sorryfortheblank.ogg") # After 5 sec of blank the microphone stream is ignored, which causes the stream # to fallback to interlude. As soon as noise comes back to the microphone the # stream comes back to the live -- thanks to track_sensitive=false. stream = fallback(track_sensitive=false, [blank.strip(max_blank=5., live), interlude]) # Put that stream to a local file output.file(%vorbis, "/tmp/hop.ogg", stream) ������������������������������������liquidsoap-2.4.2/doc/content/liq/check��������������������������������������������������������������0000775�0000000�0000000�00000000106�15132732333�0017677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # shellcheck disable=SC2068 ../../../liquidsoap --check $@ ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/complete-case.liq��������������������������������������������������0000664�0000000�0000000�00000003143�15132732333�0022130�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/liquidsoap # Lines starting with # are comments, they are ignored. # Put the log file in some directory where you have permission to write. log.file.path := "/tmp/<script>.log" # Print log messages to the console, can also be done by passing the -v option # to Liquidsoap. log.stdout := true # Use the telnet server for requests settings.server.telnet := true # A bunch of files and playlists, supposedly all located in the same base dir. default = single("~/radio/default.ogg") day = playlist("~/radio/day.pls") night = playlist("~/radio/night.pls") jingles = playlist("~/radio/jingles.pls") clock = single("~/radio/clock.ogg") # Play user requests if there are any, otherwise one of our playlists, and the # default file if anything goes wrong. radio = fallback( [ request.queue(id="request"), switch([({6h-22h}, day), ({22h-6h}, night)]), default ] ) # Add the normal jingles radio = random(weights=[1, 5], [jingles, radio]) # And the clock jingle radio = add([radio, switch([({0m0s}, clock)])]) radio = mksafe(radio) # Add the ability to relay live shows full = fallback( track_sensitive=false, [input.http("http://localhost:8000/live.ogg"), radio] ) # Output the full stream in OGG and MP3 output.icecast( %mp3, host="localhost", port=8000, password="hackme", mount="radio", full ) output.icecast( %vorbis, host="localhost", port=8000, password="hackme", mount="radio.ogg", full ) # Output the stream without live in OGG output.icecast( %vorbis, host="localhost", port=8000, password="hackme", mount="radio_nolive.ogg", radio ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/cron_add.liq�������������������������������������������������������0000664�0000000�0000000�00000000110�15132732333�0021147�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������cron.add( "0 12 * * *", { log( "It’s noon!" ) } ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/cron_id.liq��������������������������������������������������������0000664�0000000�0000000�00000000143�15132732333�0021021�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������let {id} = cron.add( "0 12 * * *", { log( "It’s noon!" ) } ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/cron_id_arg.liq����������������������������������������������������0000664�0000000�0000000�00000000136�15132732333�0021654�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������cron.add( id="minight-task", "0 0 * * *", { log( "Midnight event" ) } ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/cron_remove.liq����������������������������������������������������0000664�0000000�0000000�00000000035�15132732333�0021722�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������cron.remove("midnight-task") ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/cross.custom.liq���������������������������������������������������0000664�0000000�0000000�00000001533�15132732333�0022052�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������music = sine() jingles = sine() # BEGIN # A function to add a source_tag metadata to a source: def source_tag(s, tag) = def f(_) = [("source_tag", (tag : string))] end metadata.map(id=tag, insert_missing=true, f, s) end # Tag our sources music = source_tag(music, "music") jingles = source_tag(jingles, "jingles") # Combine them with one jingle every 3 music tracks radio = rotate(weights=[1, 3], [jingles, music]) # Now a custom crossfade transition: def transition(a, b) = # If old or new source is not music, no fade if a.metadata["source_tag"] != "music" or a.metadata["source_tag"] != "music" then sequence([a.source, b.source]) else # Else, apply the standard transition cross.simple(a.source, b.source) end end # Apply it! radio = cross(duration=5., transition, radio) # END output.dummy(fallible=true, radio) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/crossfade.liq������������������������������������������������������0000664�0000000�0000000�00000006147�15132732333�0021367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Smart transition for crossfade # @category Source / Fade # @param ~fade_in Fade-in duration, if any. # @param ~fade_out Fade-out duration, if any. # @param ~high Value, in dB, for loud sound level. # @param ~medium Value, in dB, for medium sound level. # @param ~margin Margin to detect sources that have too different sound level for crossing. # @param ~default Smart crossfade: transition used when no rule applies (default: sequence). # @param a Ending track # @param b Starting track def cross.smart( ~id=null, ~fade_in=3., ~fade_out=3., ~default=(fun (a, b) -> (sequence([a, b]) : source)), ~high=-15., ~medium=-32., ~margin=4., a, b ) = id = string.id.default(default="crossfade", id) def log(~level=3, x) = log(label=id, level=level, x) end let fade.out = fun (s) -> fade.out(type="sin", duration=fade_out, s) let fade.in = fun (s) -> fade.in(type="sin", duration=fade_in, s) add = fun (a, b) -> add(normalize=false, [b, a]) # This is for the type system.. ignore(a.metadata["foo"]) ignore(b.metadata["foo"]) if # If A and B are not too loud and close, fully cross-fade them. a.db_level <= medium and b.db_level <= medium and abs(a.db_level - b.db_level) <= margin then log( "Old <= medium, new <= medium and |old-new| <= margin." ) log( "Old and new source are not too loud and close." ) log( "Transition: crossed, fade-in, fade-out." ) add(fade.out(a.source), fade.in(b.source)) elsif # If B is significantly louder than A, only fade-out A. # We don't want to fade almost silent things, ask for >medium. b.db_level >= a.db_level + margin and a.db_level >= medium and b.db_level <= high then log( "new >= old + margin, old >= medium and new <= high." ) log( "New source is significantly louder than old one." ) log( "Transition: crossed, fade-out." ) add(fade.out(a.source), b.source) elsif # Opposite as the previous one. a.db_level >= b.db_level + margin and b.db_level >= medium and a.db_level <= high then log( "old >= new + margin, new >= medium and old <= high" ) log( "Old source is significantly louder than new one." ) log( "Transition: crossed, fade-in." ) add(a.source, fade.in(b.source)) elsif # Do not fade if it's already very low. b.db_level >= a.db_level + margin and a.db_level <= medium and b.db_level <= high then log( "new >= old + margin, old <= medium and new <= high." ) log( "Do not fade if it's already very low." ) log( "Transition: crossed, no fade." ) add(a.source, b.source) # What to do with a loud end and a quiet beginning ? # A good idea is to use a jingle to separate the two tracks, # but that's another story. else # Otherwise, A and B are just too loud to overlap nicely, or the # difference between them is too large and overlapping would completely # mask one of them. log( "No transition: using default." ) default(a.source, b.source) end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/decoder-openmpt.liq������������������������������������������������0000664�0000000�0000000�00000003502�15132732333�0022473�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������options = "" # BEGIN decoder.add( name="OPENMPT123", description="Decode files using the openmpt123 decoder binary", mimes=[ "audio/it", "audio/xm", "audio/s3m", "audio/x-mod", "audio/mod", "audio/module-xm", "audio/x-mod", "application/playerpro", "audio/x-s3m", "application/soundapp", "audio/med", "audio/x-xm" ], file_extensions=[ "xm", "mtm", "amf", "stm", "ult", "wow", "dmf", "it", "s3m", "far", "mod", "mt2", "okt", "med", "669" ], fun (~rlog, ~maxtime:_, infile) -> begin ret = process.read.lines( "openmpt123 --info #{process.quote(infile)} 2>&1" ) def get_meta(l, s) = ret = string.extract(pattern="^(\\w+).+:\\s(.+)$", s) if list.length(ret) > 2 then label = ret[1] val = ret[2] label = "openmpt:#{string.case(lower=true, label)}" ["#{string.quote(label)}=#{string.quote(val)}", ...l] else l end end meta = list.fold(get_meta, [], ret) prefix = if meta == [] then "" else "annotate:#{string.concat(separator=',', meta)}:" end # File is cleaned up as part of the request workflow. outfile = file.temp(cleanup=false, "openmpt", ".wav") try let {status = {code}} = process.run( "openmpt123 --assume-terminal --quiet --force #{options} --output #{ process.quote(outfile) } #{process.quote(infile)}" ) code == 0 ? "#{prefix}#{outfile}" : null catch err do file.remove(outfile) rlog( "Error while decoding #{infile} using ffmpeg: #{err}" ) null end end ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/dump-hourly.liq����������������������������������������������������0000664�0000000�0000000�00000000257�15132732333�0021677�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = sine() # BEGIN # A source to dump # s = ... # Dump the stream output.file( %wav, {time.string("/archive/%Y-%m-%d/%Y-%m-%d-%H_%M_%S.mp3")}, s, reopen_when={0m} ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/dump-hourly2.liq���������������������������������������������������0000664�0000000�0000000�00000000346�15132732333�0021760�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = sine() # BEGIN filename = { time.string( '/archive/$(if $(title),"$(title)","Unknown \ archive")-%Y-%m-%d/%Y-%m-%d-%H_%M_%S.mp3' ) } output.file(%mp3, filename, s, reopen_on_metadata=fun (_) -> true) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/dynamic-source.liq�������������������������������������������������0000664�0000000�0000000�00000002071�15132732333�0022330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������settings.init.force_start := true settings.server.telnet := true # Replace the path here with a path to some video files: s = playlist("/path/to/files") streams = ref([]) count = ref(0) enc = %ffmpeg(format = "flv", %audio.copy, %video.copy) def create_stream(url) = if list.assoc.mem(url, streams()) then "Stream for url #{url} already exists!" else out = output.url(id="restream-#{count()}", fallible=true, url=url, enc, s) count := count() + 1 streams := [...streams(), (url, out.shutdown)] "OK!" end end def delete_stream(url) = if not list.assoc.mem(url, streams()) then "Stream for url #{url} does not exists!" else shutdown = list.assoc(url, streams()) shutdown() streams := list.filter((fun (el) -> fst(el) != url), streams()) "OK!" end end server.register( namespace="restream", description="Redirect a stream.", usage="start <url>", "start", create_stream ) server.register( namespace="restream", description="Stop a dynamic playlist.", usage="stop <url>", "stop", delete_stream ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/external-output.file.liq�������������������������������������������0000664�0000000�0000000�00000000234�15132732333�0023503�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = playlist("bla") # BEGIN output.file( %external( process = "ffmpeg -i pipe:0 -f avi pipe:1", video = true ), "/tmp/test.avi", s ) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/fallback.liq�������������������������������������������������������0000664�0000000�0000000�00000000203�15132732333�0021140�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A fallback switch s = fallback([playlist("http://my/playlist"), single("/my/jingle.ogg")]) # END output.dummy(fallible=true, s) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-filter-dynamic-volume.liq�����������������������������������0000664�0000000�0000000�00000001443�15132732333�0025066�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def dynamic_volume(s) = def mkfilter(graph) = filter = ffmpeg.filter.volume.create(graph) def set_volume(v) = ignore(filter.process_command("volume", "#{v}")) end let {audio = audio_track} = source.tracks(s) audio_track = ffmpeg.filter.audio.input(graph, audio_track) filter.set_input(audio_track) audio_track = filter.output audio_track = ffmpeg.filter.audio.output(graph, audio_track) s = source( { audio=audio_track, metadata=track.metadata(audio_track), track_marks=track.track_marks(audio_track) } ) (s, set_volume) end ffmpeg.filter.create(mkfilter) end s = playlist("my_playlist") let (s, set_volume) = dynamic_volume(s) # END output.dummy(fallible=true, s) ignore(set_volume) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-filter-flanger-highpass.liq���������������������������������0000664�0000000�0000000�00000000560�15132732333�0025356�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def flanger_highpass(audio_track) = def mkfilter(graph) = audio_track = ffmpeg.filter.audio.input(graph, audio_track) audio_track = ffmpeg.filter.flanger(graph, audio_track, delay=10.) audio_track = ffmpeg.filter.highpass(graph, audio_track, frequency=4000.) ffmpeg.filter.audio.output(graph, audio_track) end ffmpeg.filter.create(mkfilter) end ������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-filter-hflip.liq��������������������������������������������0000664�0000000�0000000�00000000412�15132732333�0023232�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def hflip(video_track) = def mkfilter(graph) = video_track = ffmpeg.filter.video.input(graph, video_track) video_track = ffmpeg.filter.hflip(graph, video_track) ffmpeg.filter.video.output(graph, video_track) end ffmpeg.filter.create(mkfilter) end ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-filter-hflip2.liq�������������������������������������������0000664�0000000�0000000�00000001314�15132732333�0023316�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def hflip(s) = def mkfilter(graph) = let {audio = audio_track, video = video_track} = source.tracks(s) video_track = ffmpeg.filter.video.input(graph, video_track) video_track = ffmpeg.filter.hflip(graph, video_track) audio_track = ffmpeg.filter.audio.input(graph, audio_track) audio_track = ffmpeg.filter.acopy(graph, audio_track) video_track = ffmpeg.filter.video.output(graph, video_track) audio_track = ffmpeg.filter.audio.output(graph, audio_track) source( { audio=audio_track, video=video_track, metadata=track.metadata(audio_track), track_marks=track.track_marks(audio_track) } ) end ffmpeg.filter.create(mkfilter) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-filter-parallel-flanger-highpass.liq������������������������0000664�0000000�0000000�00000000773�15132732333�0027156�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def parallel_flanger_highpass(s) = def mkfilter(graph) = audio_track = ffmpeg.filter.audio.input(graph, s.tracks.audio) let (audio, _) = ffmpeg.filter.asplit(outputs=2, graph, audio_track) let [a1, a2] = audio a1 = ffmpeg.filter.flanger(graph, a1, delay=10.) a2 = ffmpeg.filter.highpass(graph, a2, frequency=4000.) audio_track = ffmpeg.filter.amerge(inputs=2, graph, [a1, a2], []) ffmpeg.filter.audio.output(graph, audio_track) end ffmpeg.filter.create(mkfilter) end �����liquidsoap-2.4.2/doc/content/liq/ffmpeg-live-switch.liq���������������������������������������������0000664�0000000�0000000�00000000744�15132732333�0023113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s1 = input.rtmp(listen=false, "rtmp://....") s1 = ffmpeg.filter.bitstream.h264_mp4toannexb(s1) s2 = playlist("/path/to/playlist") s2 = ffmpeg.filter.bitstream.h264_mp4toannexb(s2) s = fallback(track_sensitive=false, [s1, s2]) mpegts = %ffmpeg(format = "mpegts", fflags = "-autobsf", %audio.copy, %video.copy) streams = [("mpegts", mpegts)] output_dir = "/tmp/hls" output.file.hls( playlist="live.m3u8", fallible=true, segment_duration=5., output_dir, streams, s ) ����������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-relay-ondemand.liq������������������������������������������0000664�0000000�0000000�00000001450�15132732333�0023547�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������stream = input.http(start=false, "https://wwoz-sc.streamguys1.com/wwoz-hi.mp3") listeners_count = ref(0) def on_connect(_) = listeners_count := listeners_count() + 1 if listeners_count() > 0 and not stream.is_started() then log( "Starting input" ) stream.start() end end def on_disconnect(_) = listeners_count := listeners_count() - 1 if listeners_count() == 0 and stream.is_started() then log( "Stopping input" ) stream.stop() end end blank = single("/tmp/blank.mp3") stream = fallback(track_sensitive=false, [stream, blank]) o = output.harbor( %ffmpeg(format = "mp3", %audio.copy), format="audio/mpeg", mount="relay", stream ) o.on_connect(synchronous=true, on_connect) o.on_disconnect(synchronous=true, on_disconnect) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-relay.liq���������������������������������������������������0000664�0000000�0000000�00000001043�15132732333�0021762�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Input the stream, # from an Icecast server or any other source encoded_source = input.http("https://icecast.radiofrance.fr/fip-hifi.aac") # Send to one server here: output.icecast( %ffmpeg(format = "adts", %audio.copy), fallible=true, mount="/restream", host="streaming.example.com", port=8000, password="xxx", encoded_source ) # An another one here: output.icecast( %ffmpeg(format = "adts", %audio.copy), fallible=true, mount="/restream", host="streaming2.example.com", port=8000, password="xxx", encoded_source ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-shared-encoding-rtmp.liq������������������������������������0000664�0000000�0000000�00000001526�15132732333�0024666�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# An audio source... audio = sine() # Encode it in mp3 audio = ffmpeg.encode.audio(%ffmpeg(%audio(codec = "libmp3lame")), audio) # Send it to icecast output.icecast( %ffmpeg(format = "mp3", %audio.copy), host="...", password="...", mount="/stream", audio ) # A video source, for instance a static image video = single("image.png") # Encode it in h264 format video = ffmpeg.encode.video(%ffmpeg(%video(codec = "libx264")), video) # Mux it with the audio stream = source.mux.video(video=video, audio) # Copy encoder for the rtmp stream enc = %ffmpeg(format = "flv", %audio.copy, %video.copy) # Send to YouTube key = "..." url = "rtmp://a.rtmp.youtube.com/live2/#{key}" output.url(url=url, enc, stream) # Send to Facebook key = "..." url = "rtmps://live-api-s.facebook.com:443/rtmp/#{key}" output.url(self_sync=true, url=url, enc, stream) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/ffmpeg-shared-encoding.liq�����������������������������������������0000664�0000000�0000000�00000001165�15132732333�0023705�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Input the stream, from an Icecast server or any other source source = input.http("https://icecast.radiofrance.fr/fip-hifi.aac") # Make it infallible: source = mksafe(source) # Encode it in mp3: source = ffmpeg.encode.audio(%ffmpeg(%audio(codec = "libmp3lame")), source) # Send to one server here: output.icecast( %ffmpeg(format = "mp3", %audio.copy), mount="/restream", host="streaming.example.com", port=8000, password="xxx", source ) # An another one here: output.icecast( %ffmpeg(format = "mp3", %audio.copy), mount="/restream", host="streaming2.example.com", port=8000, password="xxx", source ) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/fixed-time1.liq����������������������������������������������������0000664�0000000�0000000�00000000217�15132732333�0021522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������jingles = sine() playlist = sine() # BEGIN radio = switch([({0m-5m}, jingles), ({true}, playlist)]) # END output.dummy(fallible=true, radio) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/fixed-time2.liq����������������������������������������������������0000664�0000000�0000000�00000000244�15132732333�0021523�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������jingles = sine() playlist = sine() # BEGIN radio = switch([(predicate.activates({0m-5m}), jingles), ({true}, playlist)]) # END output.dummy(fallible=true, radio) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/frame-size.liq�����������������������������������������������������0000664�0000000�0000000�00000000644�15132732333�0021454�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������%ifndef input.alsa let input.alsa = blank %endif %ifndef output.alsa let output.alsa = output.dummy %endif # BEGIN # Set correct frame size: # This makes it possible to set any audio frame size. # Make sure that you do NOT use video in this case! video.frame.rate := 0 # Now set the audio frame size exactly as required: settings.frame.audio.size := 2048 input = input.alsa() output.alsa(input) # END ignore(true) ��������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-auth.liq����������������������������������������������������0000664�0000000�0000000�00000001103�15132732333�0021615�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def auth(args) = # Call an external process to check the credentials: The script will return # the string "true" of "false". # # First call the script. Make sure to apply proper escaping of the arguments # to prevent command injection! ret = process.read.lines( "/path/to/script --user=#{args.user} --password=#{args.password}" ) # Then get the first line of its output. ret = list.hd(default="", ret) # Finally returns the boolean represented by the output (bool_of_string can # also be used). if ret == "true" then true else false end end �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-dynamic.liq�������������������������������������������������0000664�0000000�0000000�00000000640�15132732333�0022305�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Serveur settings settings.harbor.bind_addrs := ["0.0.0.0"] # An emergency file emergency = single("/path/to/emergency/single.ogg") # A playlist playlist = playlist("/path/to/playlist") # A live source live = input.harbor("live", port=8080, password="hackme") # fallback radio = fallback(track_sensitive=false, [live, playlist, emergency]) # output it output.icecast(%vorbis, mount="test", host="host", radio) ������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-insert-metadata.liq�����������������������������������������0000664�0000000�0000000�00000000742�15132732333�0023746�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = sine() # BEGIN # s = some source # The handler def set_meta(request, response) = # Filter out unusual metadata meta = metadata.export(request.query) # Grab the returned message ret = if meta != [] then s.insert_metadata(meta) "OK!" else "No metadata to add!" end response.html("<html><body><b>#{ret}</b></body></html>") end # Register handler on port 700 harbor.http.register(port=7000, method="GET", "/setmeta", set_meta) ������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-metadata.liq������������������������������������������������0000664�0000000�0000000�00000000454�15132732333�0022444�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = sine() # BEGIN meta = ref([]) # s = some source s.on_metadata(synchronous=true, fun (m) -> meta := m) # Return the json content of meta def get_meta(_, response) = response.json(meta()) end # Register get_meta at port 700 harbor.http.register(port=7000, method="GET", "/getmeta", get_meta) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-redirect.liq������������������������������������������������0000664�0000000�0000000�00000000572�15132732333�0022466�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Redirect all files other than /admin.* to icecast, located at localhost:8000. def redirect_icecast(request, response) = response.redirect("http://localhost:8000#{request.path}") end # Register this handler at port 8005 (provided harbor sources are also served # from this port). harbor.http.register.regexp( port=8005, method="GET", r/^\/admin/, redirect_icecast ) ��������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-simple.liq��������������������������������������������������0000664�0000000�0000000�00000000376�15132732333�0022160�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Custom response def handler(req) = req.socket.write( "HTTP/1.0 201 YYR\r\nFoo: bar\r\n\r\n" ) req.socket.close() # Null indicates that we're using the socket directly. null end harbor.http.register.simple("/custom", port=3456, handler) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor-usage.liq���������������������������������������������������0000664�0000000�0000000�00000001016�15132732333�0021763�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������settings.harbor.bind_addrs := ["0.0.0.0"] # Some code... # This defines a source waiting on mount point /test-harbor live = input.harbor("test-harbor", port=8080, password="xxx") files = playlist("the-playlist") # This is the final stream. Uses the live source as soon as available, and # don't wait for an end of track, since we don't want to cut the beginning of # the live stream. # # You may insert a jingle transition here... radio = fallback(track_sensitive=false, [live, files]) output.dummy(fallible=true, radio) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor.http.register.liq�������������������������������������������0000664�0000000�0000000�00000002522�15132732333�0023465�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������path = "/bla" # BEGIN def handler(request, response) = log( "Got a request on path #{request.path}, protocol version: #{ request.http_version }, method: #{request.method}, headers: #{request.headers}, query: #{ request.query }, body: #{request.body()}" ) # Set response code. Defaults to 200 response.status_code(201) # Set response status message. Uses `status_code` if not specified response.status_message("Created") # Replaces response headers response.headers([("X-Foo", "bar")]) # Set a single header response.header("X-Foo", "bar") # Set http protocol version response.http_version("1.1") # Same as setting the "Content-Type" header response.content_type("application/liquidsoap") # Set response data. Can be a `string` or a function of type `()->string` returning an empty string # when done such as `file.read` response.data("foo") # Advanced wrappers: # Sets content-type to json and data to `json.stringify({foo = "bla"})` response.json({foo="bla"}) # Sets `status_code` and `Location:` header for a HTTP redirect response. Takes an optional `status_code` argument. response.redirect("http://...") # Sets content-type to html and data to `"<p>It works!</p>"` response.html( "<p>It works!</p>" ) end harbor.http.register(port=8080, method="GET", path, handler) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/harbor.http.response.liq�������������������������������������������0000664�0000000�0000000�00000000661�15132732333�0023501�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������path = "/bla" # BEGIN def handler(request) = log( "Got a request on path #{request.path}, protocol version: #{ request.http_version }, method: #{request.method}, headers: #{request.headers}, query: #{ request.query }, body: #{request.body()}" ) http.response( content_type="text/html", data="<p>ok, this works!</p>" ) end harbor.http.register.simple(port=8080, method="GET", path, handler) �������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/hls-metadata.liq���������������������������������������������������0000664�0000000�0000000�00000000712�15132732333�0021752�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = mksafe(playlist("playlist")) output.file.hls( "/tmp/path/to/directory", [ ("aac", %ffmpeg(format = "adts", %audio(codec = "aac")).{id3_version=3}), ( "ts-with-meta", %ffmpeg(format = "mpegts", %audio(codec = "aac")).{id3_version=4} ), ("ts", %ffmpeg(format = "mpegts", %audio(codec = "aac")).{id3=false}), ( "mp3", %ffmpeg(format = "mp3", %audio(codec = "libmp3lame")).{replay_id3=false} ) ], s ) ������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/hls-mp4.liq��������������������������������������������������������0000664�0000000�0000000�00000001062�15132732333�0020671�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������radio = mksafe(playlist("playlist")) aac_lofi = %ffmpeg( format = "mp4", %audio(codec = "aac", channels = 2, ar = 44100, b = "192k") ) flac_hifi = %ffmpeg( format = "mp4", strict = "-2", %audio(codec = "flac", channels = 2, ar = 44100) ) flac_hires = %ffmpeg( format = "mp4", strict = "-2", %audio(codec = "flac", channels = 2, ar = 48000) ) streams = [("aac_lofi", aac_lofi), ("flac_hifi", flac_hifi), ("flac_hires", flac_hires)] output.file.hls(playlist="live.m3u8", "/tmp/path/to/directory", streams, radio) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/http-input.liq�����������������������������������������������������0000664�0000000�0000000�00000000262�15132732333�0021522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������url = "http://radiopi.org:8080/reggae" # BEGIN # url is a HTTP location, like http://radiopi.org:8080/reggae source = input.http(url) # END output.dummy(fallible=true, source) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/icy-update.liq�����������������������������������������������������0000664�0000000�0000000�00000001144�15132732333�0021452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������def icy_update(v) = # Parse the argument l = string.split(separator=",", v) def split(l, v) = v = string.split(separator="=", v) if list.length(v) >= 2 then list.append(l, [(list.nth(v, 0, default=""), list.nth(v, 1, default=""))]) else l end end meta = list.fold(split, [], l) # Update metadata icy.update_metadata( mount="/mystream", password="hackme", host="myserver.net", meta ) "Done !" end server.register( "update", namespace="metadata", description="Update metadata", usage="update title=foo,album=bar,..", icy_update ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/input.mplayer.liq��������������������������������������������������0000664�0000000�0000000�00000001204�15132732333�0022212�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Stream data from mplayer # @category Source / Input # @param s data URI. # @param ~restart restart on exit. # @param ~restart_on_error restart on exit with error. # @param ~buffer Duration of the pre-buffered data. # @param ~max Maximum duration of the buffered data. def input.mplayer( ~id="input.mplayer", ~restart=true, ~restart_on_error=false, ~buffer=0.2, ~max=10., s ) = input.external.rawaudio( id=id, restart=restart, restart_on_error=restart_on_error, buffer=buffer, max=max, "mplayer -really-quiet -ao pcm:file=/dev/stdout -vc null -vo null #{ process.quote(s) } 2>/dev/null" ) end ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/jingle-hour.liq����������������������������������������������������0000664�0000000�0000000�00000000173�15132732333�0021632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������normal = sine() jingle = sine() # BEGIN s = add([normal, switch([({0m}, jingle)])]) # END output.dummy(fallible=true, s) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/json-ex.liq��������������������������������������������������������0000664�0000000�0000000�00000001221�15132732333�0020765�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������data = '{ "foo": 34.24, "gni gno": true, "nested": { "tuple": [123, 3.14, false], "list": [44.0, 55, 66.12], "nullable_list": [12.33, 23, "aabb"], "object_as_list": { "foo": 123, "gni": 456.0, "gno": 3.14 }, "arbitrary object key ✨": true }, "extra": "ignored" }' let json.parse (x : { foo: float, "gni gno" as gni_gno: bool, nested: { tuple: (_ * float), list: [float], nullable_list: [int?], object_as_list: [(string*float)] as json.object, "arbitrary object key ✨" as arbitrary_object_key: bool, not_present: bool? } } ) = data �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/json-stringify.liq�������������������������������������������������0000664�0000000�0000000�00000000071�15132732333�0022371�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������r = {artist="Bla", title="Blo"} print(json.stringify(r)) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/json1.liq����������������������������������������������������������0000664�0000000�0000000�00000000203�15132732333�0020433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������let json.parse v = '{"foo": "abc"}' print( "We parsed a JSON object and got value " ^ v.foo ^ " for attribute foo!" ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/labeled_arguments.liq����������������������������������������������0000664�0000000�0000000�00000003223�15132732333�0023063�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Ignored anonymous argument # f : ('a) -> int = fun (_) -> 123 def f(_) = 123 end # Typed anonymous argument # f : (int) -> int = <fun> def f((foo:int)) = foo end # Anonymous argument with default value # f : (?int) -> int = <fun> def f(foo=123) = foo end # Typed ignored anonymous argument # f : (int) -> int = fun (_) -> 123 def f((_:int)) = 123 end # Typed anonymous argument with default value # f : (?int) -> int = <fun> def f((foo:int)=123) = foo end # Ignored anonymous argument with default value # f : (?int) -> int = fun (_=123) -> 456 def f(_=123) = 456 end # Typed ignored anonymous argument with default value # f : (?int) -> int = fun (_=123) -> 456 def f((_:int)=123) = 456 end # Typed named argument # f : (foo : int) -> int = <fun> def f(~(foo:int)) = foo end # Named argument with rename # f : (foo : 'a) -> 'a = <fun> def f(~foo:bla) = bla end # Ignored named argument # f : (foo : 'a) -> int = fun (~foo=_) -> 123 def f(~foo:_) = 123 end # Named argument with default value # f : (?foo : int) -> int = <fun> def f(~foo=123) = foo end # Typed named argument with rename # f : (foo : int) -> int = <fun> def f(~foo:(bla:int)) = bla end # Typed named argument with default value # f : (?foo : int) -> int = <fun> def f(~(foo:int)=123) = foo end # Typed named argument with rename and default value # f : (?foo : int) -> int = <fun> def f(~foo:(bla:int)=123) = bla end # Typed ignored named argument with default value # f : (?foo : int) -> int = fun (~foo=123) -> 456 def f(~foo:(_:int)=123) = 456 end # Ignored argument with default value # f : (?foo : int) -> int = fun (~foo=123) -> 456 def f(~foo:_=123) = 456 end �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/liquidsoap���������������������������������������������������������0000775�0000000�0000000�00000000076�15132732333�0021002�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh # shellcheck disable=SC2068 ../../../liquidsoap $@ ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/live-switch.liq����������������������������������������������������0000664�0000000�0000000�00000000744�15132732333�0021651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s1 = input.rtmp(listen=false, "rtmp://....") s1 = ffmpeg.filter.bitstream.h264_mp4toannexb(s1) s2 = playlist("/path/to/playlist") s2 = ffmpeg.filter.bitstream.h264_mp4toannexb(s2) s = fallback(track_sensitive=false, [s1, s2]) mpegts = %ffmpeg(format = "mpegts", fflags = "-autobsf", %audio.copy, %video.copy) streams = [("mpegts", mpegts)] output_dir = "/tmp/hls" output.file.hls( playlist="live.m3u8", fallible=true, segment_duration=5., output_dir, streams, s ) ����������������������������liquidsoap-2.4.2/doc/content/liq/loudness-correction.liq��������������������������������������������0000664�0000000�0000000�00000000457�15132732333�0023415�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This is the EU R128 standard settings.lufs.track_gain_target := -23. # Enable LUFS track gain metadata computation. enable_lufs_track_gain_metadata() # Initial source s = playlist("~/playlist") # Apply pre-track gain normalization. s = normalize_track_gain(s) # END output.dummy(fallible=true, s) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/medialib-predicate.liq���������������������������������������������0000664�0000000�0000000�00000000164�15132732333�0023113�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������m = medialib("") # BEGIN def p(m) = string.length(m["artist"]) == 5 end l = m.find(predicate=p) # END ignore(l) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/medialib.liq�������������������������������������������������������0000664�0000000�0000000�00000000217�15132732333�0021154�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������m = medialib(persistency="/tmp/medialib.json", "~/music/") l = m.find(artist_contains="Brassens") l = list.shuffle(l) output(playlist.list(l)) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/medialib.sqlite.liq������������������������������������������������0000664�0000000�0000000�00000000222�15132732333�0022450�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������m = medialib.sqlite(database="/tmp/medialib.sql", "~/music/") l = m.find(artist_contains="Brassens") l = list.shuffle(l) output(playlist.list(l)) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/multitrack-add-video-track2.liq������������������������������������0000664�0000000�0000000�00000000373�15132732333�0024606�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A playlist of audio files s = playlist("a_playlist") # A static image image = single("/path/to/image.png") # Mux the audio tracks with the image s = source(source.tracks(s).{video=source.tracks(image).video}) # END output.dummy(fallible=true, s) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/multitrack-default-video-track.liq���������������������������������0000664�0000000�0000000�00000000555�15132732333�0025422�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = playlist("a_playlist") # A default video source: image = single("/path/to/image.png") # Pick `s` video track if it has one, otherwise use the default one: video = source.tracks(s).video ?? source.tracks(image).video # Return a source that always has video: s = source(source.tracks(s).{video=video}) # END output.dummy(fallible=true, s) output.dummy(image) ���������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/multitrack2.liq����������������������������������������������������0000664�0000000�0000000�00000000345�15132732333�0021651�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = single("/path/to/movie.mkv") # Copy first audio track and video track # and re-encode second audio track: output.file( %ffmpeg(%audio.copy, %audio_2(channels = 2, codec = "aac"), %video.copy), "/path/to/copy.mkv", s ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/output.file.hls.liq������������������������������������������������0000664�0000000�0000000�00000001532�15132732333�0022452�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = mksafe(playlist("playlist")) aac_lofi = %ffmpeg(format = "mpegts", %audio(codec = "aac", channels = 2, ar = 44100)) aac_midfi = %ffmpeg( format = "mpegts", %audio(codec = "aac", channels = 2, ar = 44100, b = "96k") ) aac_hifi = %ffmpeg( format = "mpegts", %audio(codec = "aac", channels = 2, ar = 44100, b = "192k") ) streams = [("aac_lofi", aac_lofi), ("aac_midfi", aac_midfi), ("aac_hifi", aac_hifi)] def segment_name(metadata) = timestamp = int_of_float(time()) let {stream_name, duration, position, extname} = metadata "#{stream_name}_#{duration}_#{timestamp}_#{position}.#{extname}" end output.file.hls( playlist="live.m3u8", segment_duration=2.0, segments=5, segments_overhead=5, segment_name=segment_name, persist_at="/tmp/path/to/state.config", "/tmp/path/to/hls/directory", streams, s ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/playlists.liq������������������������������������������������������0000664�0000000�0000000�00000000554�15132732333�0021436�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Shuffle, play every URI, start over. s1 = playlist("/my/playlist.txt") # Do not randomize s2 = playlist(mode="normal", "/my/pl.m3u") # The playlist can come from any URI, can be reloaded every 10 minutes. s3 = playlist(reload=600, "http://my/playlist.txt") # END output.dummy(fallible=true, s1) output.dummy(fallible=true, s2) output.dummy(fallible=true, s3) ����������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/prometheus-callback.liq��������������������������������������������0000664�0000000�0000000�00000000770�15132732333�0023337�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A0 is_playing_metric = prometheus.gauge( labels=["source"], help="Whether source is playing.", "liquidsoap_is_playing" ) # A1 # B0 playlist = playlist(id="playlist", "my-playlist") set_playlist_is_playing = is_playing_metric(label_values=["radio"]) # B1 # C0 def check_if_ready(set_is_ready, s) = def callback() = if source.is_ready(s) then set_is_ready(1.) else set_is_ready(0.) end end callback end thread.run(every=1., check_if_ready(set_playlist_is_playing, playlist)) ��������liquidsoap-2.4.2/doc/content/liq/prometheus-settings.liq��������������������������������������������0000664�0000000�0000000�00000000141�15132732333�0023433�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Prometheus settings settings.prometheus.server := true settings.prometheus.server.port := 9599 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/radiopi.liq��������������������������������������������������������0000664�0000000�0000000�00000024141�15132732333�0021037�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/liquidsoap # Standard settings log.file := true log.file.path := "/var/log/liquidsoap/pi.log" log.stdout := false init.daemon := true init.daemon.pidfile.path := "/var/run/liquidsoap/pi.pid" # Enable telnet server settings.server.telnet.set(true) # Enable harbor for any external connection settings.harbor.bind_addrs.set(["0.0.0.0"]) # Verbose logs log.level.set(4) # We use the scheduler intensively, # therefore we create many queues. settings.scheduler.generic_queues.set(5) settings.scheduler.fast_queues.set(3) settings.scheduler.non_blocking_queues.set(3) # === Settings === # The host to request files stream = "XXXxXXXx" # The command to request files scripts = "ssh XXxxxXXX@#{stream} '/path/to/scripts/" # A substitution on the returned path sed = " | sed -e s#/path/to/files/#ftp://user:password@#{stream}/#'" # Enable replay gain enable_replaygain_metadata() pass = "XXxXXXXx" ice_host = "localhost" descr = "RadioPi" url = "http://radiopi.org" # === Live === # A live source, on which we strip blank (make the source unavailable when # streaming blank). live = blank.strip( input.harbor( id="live", port=8000, password=pass, buffer=8., max=20., "live.ogg" ), max_blank=10., threshold=-50. ) # This source relays the live data, when available, to the other streamer, in # uncompressed format (WAV). output.icecast( %wav, host=stream, port=8005, password=pass, mount="live.ogg", fallible=true, live ) # This source relays the live source to "live.ogg". This is used for debugging # purposes, to see what is sent to the harbor source. output.icecast( %vorbis, host="127.0.0.1", port=8080, password=pass, mount="live.ogg", fallible=true, live ) # This source starts an archive of the live stream when available title = '$(if $(title),"$(title)","Emission inconnue")$(if $(artist), " par \ $(artist)") - %m-%d-%Y, %H:%M:%S' output.file( %vorbis, reopen_on_metadata=fun (_) -> true, fallible=true, "/data/archives/brutes/" ^ title ^ ".ogg", live ) # === Channels === # Specialize the output functions def out(fmt, ~id, ~mount, ~name, ~genre, s) = output.icecast( fmt, id=id, description=descr, url=url, host=ice_host, port=8080, password=pass, fallible=true, mount=mount, name=name, genre=genre, s ) end def out_aac32(~id, ~mount, ~name, ~genre, s) = out(%fdkaac(bitrate = 32), id=id, mount=mount, name=name, genre=genre, s) end def out_aac(~id, ~mount, ~name, ~genre, s) = out(%fdkaac(bitrate = 64), id=id, mount=mount, name=name, genre=genre, s) end def out(~id, ~mount, ~name, ~genre, s) = out(%mp3, id=id, mount=mount, name=name, genre=genre, s) end # A file for playing during failures interlude = single("/home/radiopi/fallback.mp3") # Lastfm submission def lastfm(m) = if m["type"] == "chansons" and ( m["canal"] == "reggae" or m["canal"] == "Jazz" or m["canal"] == "That70Sound" ) then canal = if (m["canal"] == "That70Sound") then "70sound" else m["canal"] end username = "radiopi-" ^ canal audioscrobbler.api.track.scrobble.metadata( username=username, password="xXXxx", m ) end end # === Basic sources === # Custom crossfade to deal with jingles. def crossfade( ~start_next=5., ~fade_in=3., ~fade_out=3., ~default=(fun (a, b) -> sequence([a, b])), ~high=-15., ~medium=-32., ~margin=4., ~width=2., s ) = fade_out = fun (s) -> fade.out(type="sin", duration=fade_out, s) fade_in = fun (s) -> fade.in(type="sin", duration=fade_in, s) add = fun (a, b) -> add(normalize=false, [b, a]) log = fun (~level=3, x) -> log(label="crossfade", level=level, x) def transition(a, b) = list.iter( fun (x) -> log( level=4, "Before: #{x}" ), a.metadata ) list.iter( fun (x) -> log( level=4, "After : #{x}" ), b.metadata ) if a.metadata["type"] == "jingles" or b.metadata["type"] == "jingles" then log( "Old or new file is a jingle: sequenced transition." ) sequence([a.source, b.source]) elsif # If A and B are not too loud and close, fully cross-fade them. a.db_level <= medium and b.db_level <= medium and abs(a.db_level - b.db_level) <= margin then log( "Old <= medium, new <= medium and |old-new| <= margin." ) log( "Old and new source are not too loud and close." ) log( "Transition: crossed, fade-in, fade-out." ) add(fade_out(a.source), fade_in(b.source)) elsif # If B is significantly louder than A, only fade-out A. # We don't want to fade almost silent things, ask for >medium. b.db_level >= a.db_level + margin and a.db_level >= medium and b.db_level <= high then log( "new >= old + margin, old >= medium and new <= high." ) log( "New source is significantly louder than old one." ) log( "Transition: crossed, fade-out." ) add(fade_out(a.source), b.source) elsif # Opposite as the previous one. a.db_level >= b.db_level + margin and b.db_level >= medium and a.db_level <= high then log( "old >= new + margin, new >= medium and old <= high" ) log( "Old source is significantly louder than new one." ) log( "Transition: crossed, fade-in." ) add(a.source, fade_in(b.source)) elsif # Do not fade if it's already very low. b.db_level >= a.db_level + margin and a.db_level <= medium and b.db_level <= high then log( "new >= old + margin, old <= medium and new <= high." ) log( "Do not fade if it's already very low." ) log( "Transition: crossed, no fade." ) add(a.source, b.source) # What to do with a loud end and a quiet beginning? # A good idea is to use a jingle to separate the two tracks, # but that's another story. else # Otherwise, A and B are just too loud to overlap nicely, or the # difference between them is too large and overlapping would completely # mask one of them. log( "No transition: using default." ) default(a.source, b.source) end end cross(width=width, duration=start_next, transition, s) end # Create a radiopilote-driven source def channel_radiopilote(~skip=true, name) = log( "Creating canal #{name}" ) # Request function def req() = log( "Request for #{name}" ) ret = list.hd( process.read.lines( scripts ^ "radiopilote-getnext " ^ process.quote(name) ^ sed ) ) log( "Got answer: #{ret} for #{name}" ) request.create(ret) end # Create the dynamic source. s = request.dynamic(id="dyn_" ^ name, req, timeout=60.) # Apply normalization using replaygain information. s = amplify(1., override="replay_gain", s) # Skip blank when asked to s = if skip then blank.skip(s, max_blank=10., threshold=-40.) else s end # Submit new tracks on lastfm s.on_metadata(synchronous=false, lastfm) # Tell the system when a new track is played s.on_metadata( synchronous=false, fun (meta) -> process.run( scripts ^ "radiopilote-feedback " ^ process.quote(meta["canal"]) ^ " " ^ process.quote(meta["file_id"]) ^ "'" ) ) # Finally apply a smart crossfading crossfade(s) end # Basic source jazz = channel_radiopilote("jazz") discoqueen = channel_radiopilote("discoqueen") # Avoid skipping blank with classic music !! classique = channel_radiopilote(skip=false, "classique") That70Sound = channel_radiopilote("That70Sound") metal = channel_radiopilote("metal") reggae = channel_radiopilote("reggae") Rock = channel_radiopilote("Rock") # Group those sources in a separate # clock (good for multithreading/multicore) clock.assign_new([jazz, That70Sound, metal, reggae]) # === Mixing live === # To create a channel from a basic source, add: # - a new-track notification for radiopilote # - metadata rewriting # - the live shows # - the failsafe 'interlude' source to channels # - blank detection def mklive(s) = # Transition function: if transitioning to the live, fade out the old source # if transitioning from live, fade.in the new source. NOTE: We cannot skip the # current song because reloading new songs for all the sources when live # starts costs too much CPU. def trans(old, new) = if source.id(new) == source.id(live) then log( "Transition to live!" ) add([new, fade.out(old)]) elsif source.id(old) == source.id(live) then log( "Transitioning from live!" ) add([fade.in(new), old]) else log( "Dummy transition" ) new end end fallback( track_sensitive=false, transitions=[trans, trans, trans], [live, (s : source), interlude] ) end # Create a channel using mklive(), encode and output it to icecast. def mkoutput(~out=out, mount, source, name, genre) = out(id=(mount : string), mount=mount, name=name, genre=genre, mklive(source)) end # === Outputs === mkoutput( "jazz", jazz, "RadioPi - Canal Jazz", "jazz" ) mkoutput( "discoqueen", discoqueen, "RadioPi - Canal DiscoQueen", "discoqueen" ) mkoutput( "classique", classique, "RadioPi - Canal Classique", "classique" ) mkoutput( "That70Sound", That70Sound, "RadioPi - Canal That70Sound", "That70Sound" ) mkoutput( "metal", metal, "RadioPi - Canal Metal", "metal" ) mkoutput( "reggae", reggae, "RadioPi - Canal Reggae", "reggae" ) mkoutput( "Rock", Rock, "RadioPi - Canal Rock", "Rock" ) # Test outouts mkoutput( out=out_aac, "reggae.aacp", reggae, "RadioPi - Canal Reggae (64 kbits AAC+ test stream)", "reggae" ) mkoutput( out=out_aac32, "reggae.aacp32", reggae, "RadioPi - Canal Reggae (32 kbits AAC+ test stream)", "reggae" ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/re-encode.liq������������������������������������������������������0000664�0000000�0000000�00000000654�15132732333�0021254�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The input file, any format supported by liquidsoap input = "/tmp/input.mp3" # The output file target = "/tmp/output.ogg" # A source that plays the file once source = once(single(input)) # We use a clock with disabled synchronization clock.assign_new(sync="none", [source]) # Finally, we output the source to an ogg/vorbis file o = output.file(%vorbis, target, fallible=true, source) o.on_stop(synchronous=true, shutdown) ������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/regular.liq��������������������������������������������������������0000664�0000000�0000000�00000000317�15132732333�0021050�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������promotions = sine() other_source = sine() # BEGIN # (1200 sec = 20 min) timed_promotions = delay(1200., promotions) main_source = fallback([timed_promotions, other_source]) # END output.dummy(main_source) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/replaygain-playlist.liq��������������������������������������������0000664�0000000�0000000�00000000153�15132732333�0023377�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������enable_replaygain_metadata() s = replaygain(playlist("~/playlist")) # END output.dummy(fallible=true, s) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/request.dynamic.liq������������������������������������������������0000664�0000000�0000000�00000000524�15132732333�0022522�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������files = process.read.lines( "cat " ^ process.quote("playlist.pls") ) pos = ref(0) def get_next() = if files == [] then null else file = list.nth(files, pos()) pos := pos() + 1 mod list.length(files) request.create(file) end end s = request.dynamic(get_next) # END output.dummy(fallible=true, s) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/rtmp.liq�����������������������������������������������������������0000664�0000000�0000000�00000000225�15132732333�0020367�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = playlist("my_playlist") enc = %ffmpeg(format = "flv", listen = 1, %audio.copy, %video.copy) output.url(url="rtmp://host/app/instance", enc, s) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/samplerate3.liq����������������������������������������������������0000664�0000000�0000000�00000000245�15132732333�0021627�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# BEGIN def samplerate(~samples, ~duration=2.5) = samples / duration end # END print(samplerate(samples=110250.)) print(samplerate(samples=110250., duration=1.)) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/scheduling.liq�����������������������������������������������������0000664�0000000�0000000�00000000276�15132732333�0021540�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������night = sine() day = sine() # BEGIN # A scheduler, assuming you have defined the night and day sources s = switch([({0h-7h}, night), ({7h-24h}, day)]) # END output.dummy(fallible=true, s) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/scheduling_9am.liq�������������������������������������������������0000664�0000000�0000000�00000000101�15132732333�0022271�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������thread.when( {9h}, { log( "It's 9 AM!" ) } ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/scheduling_queue.liq�����������������������������������������������0000664�0000000�0000000�00000000275�15132732333�0022743�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������request_queue = request.queue() # BEGIN thread.run( every=3600., {request_queue.push(request.create("/path/to/hourly-jingle.mp3"))} ) # END output.dummy(fallible=true, request_queue) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/scheduling_queue_midnight.liq��������������������������������������0000664�0000000�0000000�00000000274�15132732333�0024625�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������request_queue = request.queue() # BEGIN thread.when( {23h59m}, {request_queue.push(request.create("/path/to/midnight-track.mp3"))} ) # END output.dummy(fallible=true, request_queue) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/scheduling_simple.liq����������������������������������������������0000664�0000000�0000000�00000000225�15132732333�0023103�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������thread.run( every=600., # This is the same as: fun () -> log("10 minutes have passed.") { log( "10 minutes have passed." ) } ) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/seek-telnet.liq����������������������������������������������������0000664�0000000�0000000�00000000631�15132732333�0021626�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A playlist source s = playlist("/path/to/music") # The server seeking function def seek(t) = t = float_of_string(default=0., t) log( "Seeking #{t} sec" ) ret = source.seek(s, t) "Seeked #{ret} seconds." end # Register the function server.register( namespace=source.id(s), description="Seek to a relative position in source #{source.id(s)}", usage="seek <duration>", "seek", seek ) �������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/settings.liq�������������������������������������������������������0000664�0000000�0000000�00000000251�15132732333�0021244�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������log.level := 4 log.file := true log.stdout := true init.daemon := true audio.samplerate := 48000 audio.channels := 2 video.frame.width := 720 video.frame.height := 1280 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/shoutcast.liq������������������������������������������������������0000664�0000000�0000000�00000000213�15132732333�0021417�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������source = single("audiofile.ogg") output.shoutcast( %mp3, host="shoutcast.example.org", port=8000, password="changeme", source ) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/single.liq���������������������������������������������������������0000664�0000000�0000000�00000000032�15132732333�0020662�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������single("/my/default.ogg") ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/source-cue.liq�����������������������������������������������������0000775�0000000�0000000�00000000703�15132732333�0021463�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!./liquidsoap radio = sine() f = radio.insert_metadata # BEGIN radio = source.cue( title="My stream", file="backup.mp3", "/tmp/backup.cue", radio ) output.file(%mp3, "/tmp/backup.mp3", radio) # END thread.run( delay=1., {f([("artist", "artist1"), ("album", "album1"), ("title", "title1")])} ) thread.run( delay=2., {f([("artist", "artist2"), ("album", "album1"), ("title", "title2")])} ) thread.run(delay=3., shutdown) �������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/space_overhead.liq�������������������������������������������������0000775�0000000�0000000�00000000570�15132732333�0022363�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!./liquidsoap # This code was contributed by AzuraCast. Possible settings: # - less memory: space_overhead = 20 # - less cpu: space_overhead = 140 # - balanced: space_overhead = 80 # Optimize for memory usage over CPU: this results in a slightly increased CPU # usage and reduced memory usage. runtime.gc.set(runtime.gc.get().{space_overhead=20, allocation_policy=2}) ����������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/split-cue.liq������������������������������������������������������0000664�0000000�0000000�00000000666�15132732333�0021323�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Log to stdout log.file := false log.stdout := true log.level := 4 # Initial playlist cue = "/path/to/sheet.cue" # Create a playlist with this CUE sheet and tell Liquidsoap to shutdown when we # are done. s = playlist(cue, on_done=shutdown) # Shove all that to a output.file operator. output.file( %mp3(id3v2 = true, bitrate = 320), fallible=true, reopen_on_metadata=fun (_) -> true, "/path/to/$(track) - $(title).mp3", s ) ��������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/sqlite.liq���������������������������������������������������������0000775�0000000�0000000�00000003153�15132732333�0020714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!../../../liquidsoap %ifdef sqlite # open-begin db = sqlite("/tmp/database.sql") # open-end # drop-begin db.table.drop("metadata") # drop-end # create-begin db.table.create( "metadata", preserve=true, [ ( "filename", "STRING PRIMARY KEY" ), ("artist", "STRING"), ("title", "STRING"), ("year", "INT") ] ) # create-end # insert-begin db.insert( table="metadata", { artist="Naps", title= "Best life", year=2021, filename="naps.mp3" } ) db.insert( table="metadata", { artist="Orelsan", title= "L'odeur de l'essence", year=2021, filename="orelsan.mp3" } ) # insert-end # count-begin n = db.count(table="metadata", where="year=2023") # count-end ignore(n) # select-begin l = db.select( table="metadata", where="year >= 2000" ) # select-end # select2-begin find_artist = "Brassens" l' = db.select( table="metadata", where="artist = #{sqlite.escape(find_artist)}" ) # select2-end ignore(l') # query-begin l'' = db.query( "SELECT * FROM metadata WHERE artist = 'bla'" ) # query-end ignore(l'') # play-begin files = list.map(fun (row) -> null.get(list.assoc("filename", row.to_list())), l) s = playlist.list(files) output(s) # play-end # play2-begin def f(row) = let sqlite.row (r : {filename: string, artist: string, title: string, year: int} ) = row r.filename end files = list.map(f, l) s = playlist.list(files) output(s) # play2-end # delete-begin db.delete( table="metadata", where="year < 1900" ) # delete-end # exec-begin db.exec( "DROP TABLE IF EXISTS metadata" ) # exec-end %endif ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/srt-receiver.liq���������������������������������������������������0000664�0000000�0000000�00000000221�15132732333�0022013�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = input.srt( content_type="application/ffmpeg;format=s16le,ch_layout=stereo,sample_rate=48000" ) # END output.dummy(fallible=true, s) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/srt-sender.liq�����������������������������������������������������0000664�0000000�0000000�00000000170�15132732333�0021472�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = sine() # BEGIN enc = %ffmpeg(format = "s16le", %audio(codec = "pcm_s16le", ac = 2, ar = 48000)) output.srt(enc, s) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/switch-show.liq����������������������������������������������������0000664�0000000�0000000�00000000330�15132732333�0021661�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������normal = sine() # BEGIN stripped_stream = blank.strip(input.http("http://myicecast:8080/live.ogg")) s = fallback(track_sensitive=false, [stripped_stream, blank.strip(normal)]) # END output.dummy(fallible=true, s) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/transcoding.liq����������������������������������������������������0000664�0000000�0000000�00000001320�15132732333�0021715�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Input the stream from an Icecast server or any other source url = "https://icecast.radiofrance.fr/fip-hifi.aac" input = mksafe(input.http(url)) # First transcoder: mp3 32 kbps. We also degrade the samplerate, and encode in # mono Accordingly, a mono conversion is performed on the input stream output.icecast( %mp3(bitrate = 32, samplerate = 22050, stereo = false), mount="/your-stream-32.mp3", host="streaming.example.com", port=8000, password="xxx", mean(input) ) # Second transcoder: mp3 128 kbps using %ffmpeg output.icecast( %ffmpeg(format = "mp3", %audio(codec = "libmp3lame", b = "128k")), mount="/your-stream-128.mp3", host="streaming.example.com", port=8000, password="xxx", input ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-anonymizer.liq�����������������������������������������������0000664�0000000�0000000�00000001752�15132732333�0022712�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Input from webcam cam = input.v4l2() # Detect faces (this generates a white disk over faces) mask = video.frei0r.opencvfacedetect(cam) # Pixellize the video censored = video.frei0r.pixeliz0r(block_width=0.1, block_height=0.1, cam) # Generate a mask for video without the face unmask = video.frei0r.invert0r(mask) # Put the pixellized face over the video s = video.frei0r.addition( video.frei0r.multiply(mask, censored), video.frei0r.multiply(unmask, cam) ) # We have to bufferize the source s = buffer(buffer=0.1, mksafe(s)) # Input audio from microphone mic = input.pulseaudio() # Transpose sound to generate a funny voice mic = soundtouch(pitch=1.5, buffer(mic)) # Add sound to video s = source.mux.audio(audio=mic, s) # Let's hear the sound output.pulseaudio(fallible=true, s) # Let's see the video output.sdl(fallible=true, s) s = mksafe(s) # Output the video/sound into a file in theora/vorbis format output.file(%ogg(%theora(quality = 63), %vorbis), "anonymous.ogv", s) ����������������������liquidsoap-2.4.2/doc/content/liq/video-bluescreen.liq�����������������������������������������������0000664�0000000�0000000�00000001011�15132732333�0022632�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# The video of the bunny s = single("big_buck_bunny_720p_stereo.ogg") # Input from the webcam cam = input.v4l2() # Flip the video around a vertical axis so that it is easier to position # yourself cam = video.frei0r.flippo(x_axis=true, cam) # Make the white background transparent I had to tweak the precision parameter # so that I will be seen but not the wall cam = video.alpha.of_color(color=0xffffff, precision=0.64, cam) # Superpose the two videos s = add([s, cam]) # Output to SDL output.sdl(fallible=true, s) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-canvas-example.liq�������������������������������������������0000664�0000000�0000000�00000001205�15132732333�0023414�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������background = blank() # BEGIN # Change to video.canvas.virtual_10k.actual_720p etc.. to render # in different sizes without changing the values below! let {px, rem, vh, vw, width, height} = video.canvas.virtual_10k.actual_1080p video.frame.width := width video.frame.height := height background = video.add_image( x=0.3 @ vw, y=0.01 @ vh, width=1562 @ px, height=1562 @ px, file="/path/to/cover.jpg", background ) background = video.add_text( color=0xFCB900, speed=0, x=234 @ px, y=4437 @ px, size=1.5 @ rem, "Some text", background ) # END output.dummy(fallible=true, background) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-default-canvas.liq�������������������������������������������0000664�0000000�0000000�00000001020�15132732333�0023400�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Standard video canvas based off a `10k` virtual canvas. # @category Source / Video processing def video.canvas.virtual_10k = def make(width, height) = video.canvas.make( virtual_width=10000, actual_size={width=width, height=height}, font_size=160 ) end { actual_360p=make(640, 360), actual_480p=make(640, 480), actual_720p=make(1280, 720), actual_1080p=make(1920, 1080), actual_1440p=make(2560, 1440), actual_4k=make(3840, 2160), actual_8k=make(7680, 4320) } end ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-in-video.liq�������������������������������������������������0000664�0000000�0000000�00000000205�15132732333�0022221�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = blank() s2 = blank() # BEGIN s2 = video.scale(scale=0.2, x=10, y=10, s2) s = add([s, s2]) # END output.dummy(fallible=true, s) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-logo.liq�����������������������������������������������������0000664�0000000�0000000�00000000204�15132732333�0021446�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = blank() # BEGIN s = video.add_image(width=30, height=30, x=10, y=10, file="logo.jpg", s) # END output.dummy(fallible=true, s) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-osc.liq������������������������������������������������������0000664�0000000�0000000�00000001544�15132732333�0021302�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Set the OSC port to match TouchOSC's default port settings.osc.port := 8000 # Input from the webcam with sound cam = input.v4l2() mic = input.pulseaudio() s = source.mux.audio(audio=mic, cam) s = mksafe(s) # We get the angle from fader 3 angle = osc.float("/1/fader3", 0.) # we rescale the position of fader 3 so that it corresponds to a 2π rotation angle = fun () -> angle() * 2. * 3.1416 # ...and we rotate the video according to the angle s = video.rotate(angle=angle, s) # Change brightness according to fader 1 s = video.frei0r.brightness(brightness=osc.float("/1/fader1", 0.5), s) # Change contrast according to fader 2 s = video.frei0r.contrast0r(contrast=osc.float("/1/fader2", 0.5), s) # We have to buffer here otherwise we get clocks problems s = buffer(s) # Output sound and video output.pulseaudio(fallible=true, s) output.sdl(fallible=true, s) ������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-simple.liq���������������������������������������������������0000664�0000000�0000000�00000000342�15132732333�0022002�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = single("video.mp4") output.icecast( %ffmpeg( format = "ogg", %audio(codec = "libvorbis"), %video(codec = "libtheora") ), host="localhost", port=8000, password="hackme", mount="/videostream", s ) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-static.liq���������������������������������������������������0000664�0000000�0000000�00000001015�15132732333�0021776�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������log.level := 4 audio = once(single("/tmp/bla.mp3")) video = single("/tmp/bla.jpg") # Mux audio and video source = source.mux.video(video=video, audio) # Disable real-time processing, to process with the maximum speed clock.assign_new(sync='none', [source]) # Encode video and copy audio: encoder = %ffmpeg(format = "mp4", %audio.copy, %video(codec = "libx264")) # Output to a theora file, shutdown on stop o = output.file(fallible=true, encoder, "/tmp/encoded-video.mp4", source) o.on_stop(synchronous=true, shutdown) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-text.liq�����������������������������������������������������0000664�0000000�0000000�00000000243�15132732333�0021475�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = blank() s = video.add_text.sdl( font="/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", "Hello world!", s ) output.dummy(fallible=true, s) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-transition.liq�����������������������������������������������0000664�0000000�0000000�00000000141�15132732333�0022700�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������s = blank() # BEGIN s = video.fade.in(transition="fade", duration=3., s) # END output.dummy(s) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-weather.liq��������������������������������������������������0000664�0000000�0000000�00000000250�15132732333�0022146�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������img = single("weather.jpg") cam = input.v4l2() cam = video.alpha.of_color(color=0x0000ff, precision=0.2, cam) s = add([img, cam]) # END output.dummy(fallible=true, s) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/liq/video-webcam.liq���������������������������������������������������0000664�0000000�0000000�00000000031�15132732333�0021742�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������output.sdl(input.v4l2()) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/loudness_normalization.md����������������������������������������������0000664�0000000�0000000�00000014504�15132732333�0023242�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Loudness Normalization ## Normalization If you want to have a constant average volume on any audio stream, you can use the `normalize` operator. However, this operator cannot guess the volume of the whole stream, and can be "surprised" by rapid changes of the volume. This can lead to a volume that is too low, too high, oscillates. In some cases, dynamic normalization also creates saturation. To tweak the normalization, several parameters are available. These are listed and explained in the [reference](reference.html) and also visible by executing `liquidsoap -h normalize`. However, if the stream you want to normalize consist of audio files, using the replay gain technology might be a better choice. ## Computing track loudness normalization Instead of using the `normalize` operator, which can have jumps, it is possible to pre-compute loudness normalization per-track. This can be done using _integrated LUFS_ or _ReplayGain_. Both mechanism work the same way. ### LUFS [LUFS (Loudness Units relative to Full Scale)](https://en.m.wikipedia.org/wiki/LUFS) is a standard for measuring perceived loudness in audio, designed to reflect how loud a track actually feels to the human ear, rather than just its peak or average levels. It's widely used to ensure consistent loudness across different media, making it especially valuable for streaming platforms, broadcast, and post-production. LUFS loudness correction in liquidsoap is based on a track's integrated LUFS which is the average LUFS over the track. Give a track integrated LUFS, we compare it to the value defined by `settings.lufs.track_gain_target` and compute its loudness correction accordingly. Typically, if the track's integrated LUFS is `-23 dB` and `settings.lufs.track_gain_target` is `-16 dB`, we request an amplification of `7 dB`. LUFS is the preferred method to compute track loudness correction in liquidsoap. However, because there is no standard metadata field to store its value, unless you careful prepare your files for broadcast, the value will have to be computed on the fly, which can generate CPU spikes. When looking for a track integrated LUFS, we first look if the metadata key defined by `settings.lufs.integrated_metadata` is available and compute it otherwise. With the default value of `"liq_integrated_lufs"` for `settings.lufs.integrated_metadata`, this means that we look for a metadata of the form: `("liq_integrated_lufs", "-23 dB")` and, if not present, compute the value. You may thus want to preemptively tag your files to add this metadata, typically using `ffmpeg`. ### Replay gain [ReplayGain](https://en.wikipedia.org/wiki/ReplayGain) is a proposed standard that is (more or less) respected by many open-source tools. It provides a way to obtain an overall uniform perceived loudness over a track or a set of tracks. The computation of the loudness is based on how the human ear actually perceives each range of frequency. Having computed the average perceived loudness on a track or an album, it is easy to renormalize the tracks when playing, ensuring a comfortable, consistent listening experience. Unlike LUFS, which is a formal loudness standard used in professional audio and broadcasting, ReplayGain is more of a consumer-level solution, primarily used in music libraries and media players. The key difference is that LUFS is based on precise loudness models defined by international standards and is required by many streaming platforms, while ReplayGain is simpler, less standardized, and not always accurate across all genres or playback systems. However, ReplayGain has support for standardized metadata fields and can be easily pre-computed using existing tools. ### Computing or retrieving loudness correction information The first step in order to normalize track loudness is to fetch or compute the appropriate normalization level for a given file. There are two ways to get this information, one that works for _all_ files and one that can be enabled on a per-file basis, if you need finer grained control over replay gain. #### Using metadata resolvers The metadata solution is uniform: without changing anything, _all_ your files will have a new track gain metadata when the computation succeeds. However, keep in mind that this computation can be costly and will be done each time a remote file is downloaded to be prepared for streaming unless it already has the information pre-computed. For this reason, it is recommended to pre-compute replay gain information as much as possible, specially if you intent to stream large audio files. We have two metadata resolvers: - A metadata resolver using integrated LUFS, the average LUFS over the whole track - A metadata resolver using ReplayGain data. The LUFS metadata resolver is recommended over replaygain. None of the two metadata resolver are enabled by default. You can do it by adding the following code to your script: ```liquidsoap # If you want to use lufs; enable_lufs_track_gain_metadata() # If you want to use replaygain enable_replaygain_metadata() ``` #### Using protocol resolvers If you want to control on which track you want to compute loudness correction, you can use protocol resolvers instead. Just as with metadata decoders, we have two protocol resolvers: - `lufs_track_gain:uri` will compute the LUFS loudness correction for this `uri` - `replaygain:uri` will compute the ReplayGain loudness correction for this `uri` These protocols triggers loudness correction computation on a a per-file bases. To use it, you prefix your request URIs with it. For instance, replacing `/path/to/file.mp3` with `lufs_track_gain:/path/to/file.mp3`. Prepending `lufs_track_gain:` is easy if you are using a script behind some `request.dynamic.list` operator. If you are using the `playlist` operator, you can use its `prefix` parameter. Protocols can be chained, for instance: ``` annotate:foo="bar":lufs_track_gain:/path/to/file.mp3 ``` ### Applying loudness correction After fetching or computing the replay gain information, the next step is to use it to correct the source's volume. The `normalize_track_gain()` operator is used for that. This operator is a simple wrapper around the `amplify` operator that uses the metadata defined by `settings.normalize_track_gain_metadata` to apply volume correction. Here's a full example using integrated LUFS as metadata resolver: ```{.liquidsoap include="loudness-correction.liq" to="END"} ``` ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/memory.md��������������������������������������������������������������0000664�0000000�0000000�00000017741�15132732333�0017756�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Controlling memory usage When using liquidsoap in production, it can be important to understand how to control the memory footprint of the application. This is not an easy topic as there are several layers of memory management inside the application and also some trade-off considerations between memory footprint and CPU usage. As of writing (version `2.2.0`), some of the trade-off that we are making with the OCaml garbage collector do not seem satisfactory in some memory-intensive conditions. Hopefully, this will improve in future major release (`2.3.x` and later). But first, let's look at what's going on. ### The OCaml memory model The OCaml compiler provides a garbage collector. This module is able to track memory blocks used by the OCaml program and free them when they are not used without the programmer's intervention. This is done by scanning the memory currently allocated by the OCaml program to identify the memory blocks that are not in use anymore. While this is transparent to the user (you!), this also means that there will be extra CPU cycles dedicated to this operation. How often these cycle occur help controlling the growth of unused memory but with the understanding that _to minimize unused memory, more CPU cycles have to be dedicated to tracking it_. You can find more information about the OCaml garbage collection on [this page](https://ocaml.org/docs/garbage-collection). Inside liquidsoap scripts, the operations that the OCaml compiler provides to control the garbage collector are available within the `runtime.gc` module. The documentation for these operations can be found in the [OCaml Gc module documentation](https://v2.ocaml.org/api/Gc.html). Typically, to change the garbage collector parameters, one can do: ```{.liquidsoap include="space_overhead.liq" from=1} ``` These parameters and functions make it possible to experiment and see if you can find better parameters for your application. ### C memory allocations Not all the memory in the application is allocated by the OCaml garbage collector. External libraries such as `ffmpeg`, `libmp3lame` and etc. need to allocate their own memory. This is usually referred to as _C memory allocations_ though it does not have to be allocated by a program written in `C`.. Another, more technically appropriate is _heap memory_ though, dynamically memory allocated by the OCaml garbage collector also lives in the program's heap.. 😅 This type of memory is also cleaned up by the OCaml garbage collector. To do so, a _custom block_ is passed to the OCaml program with a reference to a C memory pointer and how to clean it up. When the OCaml program detects that this custom block is no longer in use, it triggers the required operations to clean its corresponding C memory. However, things get complicated when considering how to fine-tune the garbage collector to account for memory allocated on the C side.. Remember that, as we discussed in the previous section, the garbage collector has to consume CPU cycles to free up memory. And, in the case of memory allocated on the C side, a single OCaml value (usually a small amount of memory) can actually refer to a much larger amount of C memory. This is typically the case when the corresponding C memory represents decoded video frames, which is usually a fairly large amount of memory. In general, the trade-off is: if the garbage collector does not run often enough, a lot of these rather larger C memory blocks are lingering longer, which leads to potentially huge amount of memory needlessly consumed by the application. Conversely, if the garbage collector runs too often, memory usage is controlled but CPU usage is increased. As of now, the strategy implemented by the OCaml compiler consists in tracking the ratio of OCaml held memory vs. its corresponding C memory and running the garbage collector more often when this ratio increases. However, this is not optimal in cases where the application purposefully holds large amount of C memory such as when doing video processing. In the future, we would like to explore tightening up our control of this mechanism. It should be possible trick the garbage collector by not declaring the full anmount of allocated C memory to make it possible to run the memory cleaning operations on purpose and at specific times, typically after a streaming cycle has ended. Most of the tools for that are already exported in the scripting language so, we will make sure to report our progress on the [blog](https://liquidsoap.info/blog) for anyone to test it. ### Audio data format Another source of memory usage is the audio data format. By default, we store audio data using OCaml's native floating point numbers in order to be able to run the application, including audio processing (crossfade, filters, fades etc) at the best possible speed and CPU usage. However, OCaml's native float are stored using 64 bits (8 bytes), which is a large amount of memory per number. If you are concerned with reducing your audio memory footprint, for instance if your applications has a lot of audio sources with buffers, you can do a couple of things: 1. Use the [ffmpeg raw content](ffmpeg.html). This means storing all the audio content as ffmpeg audio frames. This is an opaque format that works very well if your script can use ffmpeg end-to-end, for instance processing audio using [ffmpeg filters](ffmpeg_filters.html).. 2. Use one of the `pcm_f32` or `pcm_s16` audio format. These formats are less opaque. Their data is stored in a C memory array and can be accessed by the OCaml program. Some, but not all, of our operators do support them transparently. When using `pcm_s16`, audio samples are stored as 16 bit signed integers (2 bytes, the audio CD format). When using `pcm_f32`, audio samples are stored as 32 bit float (4 bytes). 16 bit signed integers is probably enough for most applications and consumes 4 times less memory than OCaml's native floating point numbers. The `pcm_*` formats can be required by the encoders by adding `pcm_s16` or `pcm_f32` to their list of parameters. This will, in turn, inform all operators and decoders to operate with this format, if they support it: ```liquidsoap # Mp3 encoder, pcm_s16 encoder = %mp3(pcm_s16, channels=2) # Ogg/opus encoder, pcm_f32 encoder = %ogg(%vorbis(pcm_f32)) # FFmpeg AAC encoder, pcm_s16 encoder = %ffmpeg(format="mp4",%audio(pcm_s16, codec="aac")) ``` For both `pcm_*` and ffmpeg raw formats, you can use also conversion functions (`ffmpeg.raw.decode.*`, `ffmpeg.raw.encode.*`, `audio.decode.pcm_*`, `audio.encode.pcm_*`) to convert content back and forth. In general, working with the `pcm_*` formats is easier. If you know what you are doing, though, working with raw FFmpeg frames can also have some advantages. In both cases, there might be an increase in CPU usage if your script needs to process audio (for instance via a `crossfade`) when converting these formats back and forth. Finally, if you need to store large amount of audio data, for instance to create a one hour delay, you should consider using the `track.audio.defer` operator which was designed for this purpose. ### `jemalloc` Lastly, the user-land memory allocator [jemalloc](https://github.com/jemalloc/jemalloc) can be used to control all memory allocations (C and OCaml). This allocator is particularly good at preventing memory fragmentation, which is an important topic for an application like liquidsoap running short streaming cycle involving small amount of memory (FFmpeg frames etc). The allocator is enabled by installing the `jemalloc` opam package and is included in all our production builds (except windows). It also comes with a lot of customization options that are exported via the [runtime.jemalloc.\*](https://www.liquidsoap.info/doc-dev/reference.html#runtime.jemalloc.epoch) functions. If you want to explore more, we recommend [reading about it](https://engineering.fb.com/2011/01/03/core-data/scalable-memory-allocation-using-jemalloc/) and then exploring the [manual page](http://jemalloc.net/jemalloc.3.html) which contains details about all the available settings. �������������������������������liquidsoap-2.4.2/doc/content/metadata.md������������������������������������������������������������0000664�0000000�0000000�00000006647�15132732333�0020231�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Customize metadata using Liquidsoap Liquidsoap has several mechanism for manipulating the metadata attached to your stream. In this page we quickly detail and compare the different operators, see the [language reference](reference.html) for full details about them. **Warning**. The protocol used by Shoutcast and Icecast before version 2 does not support many fields. It mainly support one: `song`. So, if you need to customize the metadata displayed by these servers, you should customize only the `song` metadata. ## The annotate protocol The metadata are read from files, so the most simple way is to properly tag the files. However, if it not possible to modify the files for some reason, the `annotate` protocol can be used in playlists to insert and modify some metadata. For instance, in the playlist ``` annotate:title="Title 1",artist="Artist 1":music1.mp3 annotate:title="Title 2",artist="Artist 2":music2.mp3 ``` the title metadata for file music1.mp3 will be overridden and changed to ``Title 1'' (and similarly for the artist). ## Map metadata The `metadata.map` operator applies a specified function to transform each metadata chunk of a stream. It can be used to add or decorate metadata, but is also useful in more complex cases. A simple example using it: ```liquidsoap # A function applied to each metadata chunk def append_title(m) = # Grab the current title title = m["title"] # Return a new title metadata [("title","#{title} - www.station.com")] end # Apply metadata.map to s using append_title s = metadata.map(append_title, s) ``` The effect of `metadata.map` by default is to update the metadata with the returned values. Hence in the function `append_title` defined in the code above returns a new metadata for the label `title` and the other metadata remain untouched. You can change this by using the `update` option, and you can also remove any metadata (even empty one) using the `strip` option. See the documentation on `metadata.map` for more details. ## Insert metadata ### Using the telnet server This operator is used for inserting metadata using a server command. If you have an `server.insert_metadata` node named `ID` in your configuration, as in ``` server.insert_metadata(id="ID", source) ``` you can connect to the server (either telnet or socket) and execute commands like ``` ID.insert key1="val1",key2="val2",... ``` ### In Liquidsoap Sometimes it is desirable to change the metadata dynamically when an event occurs. In this case, the source method `insert_metadata` can be used. For instance, suppose that you want to insert metadata on the stream using the OSC protocol. When a pair of strings `title'' `The new title'' is received on `/metadata`, we want to change the title of the stream accordingly. This can be achieved as follows. ```liquidsoap # Our main music source s = playlist("...") s = mksafe(s) # Handler for OSC events (gets pairs of strings) def on_meta(m) = # Extract the label label = fst(m) # Extract the value value = snd(m) # A debug message print("Insert metadata #{label} = #{value}") # Insert the metadata s.insert_metadata([(label,value)]) end # Call the above handler when we have a pair of strings on /metadata osc.on_string_pair("/metadata",on_meta) # Output on icecast output.icecast(%mp3,mount="test.mp3",s) ``` We can then change the title of the stream by sending OSC messages, for instance ``` oscsend localhost 7777 "/metadata" ss "title" "The new title" ``` �����������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/migrating.md�����������������������������������������������������������0000664�0000000�0000000�00000075077�15132732333�0020435�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Migrating to a new Liquidsoap version In this page, we list the most common catches when migrating to a new version of Liquidsoap. ### Generalities If you are installing via `opam`, it can be useful to create a [new switch](https://opam.ocaml.org/doc/Usage.html) to install the new version of `liquidsoap`. This will allow to test the new version while keeping the old version around in case you to revert to it. More generally, we recommend to always keep a version of your script around and also to make sure that you test your new script with a staging environment that is close to production. Streaming issues can build up over time. We do our best to release the most stable possible code but problems can arise from many reasons so, always best to first to a trial run before putting things to production! ## From 2.3.x to 2.4.x See our [2.4.0 blog post](https://www.liquidsoap.info/blog/2025-08-11-liquidsoap-2.4.0/) for a detailed presentation and cheatsheet of the new features and changes in this release. ### `insert_metadata` `insert_metadata` is now available as a source method. You do not need to use the `insert_metadata` operator anymore and the operator has been deprecated. You can now directly do: ```liquidsoap s = some_source() s.insert_metadata([("title","bla")]) ``` ### Stream-related callbacks tl;dr: - Callbacks have been moved to their own section of the documentation for better standardization and information. - Callbacks can be executed using `thread.run` by passing `synchronous=false` when registering them. - Most callback arguments should be accepted as deprecated arguments. - `blank.detect` could not be updated in a backward-compatible manner. - `on_file_change` in `output.*.hls` has been updated to pass a single record. - `on_connect` callback on `output.harbor` has been updated to pass a single record. Stream-related callbacks is the biggest change with this release. They are now fully documented, with their own dedicated section in the doc and can now be executed in an asynchronous task when asked by setting the mandatory `synchronous` argument to `false`. When setting `synchronous=false`, the function to be executed by the callback is placed in a `thread.run` task. This is done to make sure that the functions executed during the streaming cycle do not impact the streaming latency. Otherwise, a callback function takes too long, the streaming cycle gets late, causing issues with the runtime system typically resulting in catchup errors. Callbacks have also been moved to source methods in order to unify the codebase, options and more. In most cases, callback previously passed as arguments are still accepted, triggering a deprecation warning. With the new source-related callbacks, instead of doing: ```liquidsoap s = on_metadata(s, fn) ``` You should now do: ```liquidsoap # Set synchronous to false if function fn can potentially # take a while to execute to prevent blocking the main streaming # thread. s.on_metadata(synchronous=true, fn) ``` Additionally, `on_end` and `on_offset` have been merged into a single `on_position` source method. Here is the new syntax: ```liquidsoap # Execute a callback after current track position: s.on_position( # See above synchronous=false, # This is the default remaining=false, position=1.2, # Allow execution even if current track does not reach position `1.2`: allow_partial=true, fn ) # Execute a callback when remaining position is less # than the given position: s.on_position( synchronous=false, remaining=true, position=1.2, fn ) ``` With the other callbacks, e.g. `on_start`, instead of doing: ```liquidsoap output.ao(on_start=fn, ...) ``` You should now do: ```liquidsoap o = output.ao(...) o.on_start(synchronous=false, fn) ``` ❓ **Asynchronous or synchronous?** When registering callbacks, you have to specify if you want the function to be called synchronously or asynchronously. If the function is fast to execute or requires precise timing (it should still be fast to execute though!) then you should register with `synchronous=true`. Slow tasks that are not time-sensitive like submitting to a remote HTTP server should be registered with `synchronous=false`. ⚠️ **Asynchronous callbacks execution order** When registered with `synchronous=false`, callbacks are executed using `thread.run`. This means that there can be a slight delay in their execution. Also, execution order is not guaranteed to be respected. ### Error methods Error methods have been removed by default from the error types to avoid cluttering the documentation. If you need to access error methods, you can use `error.methods`: ```liquidsoap # Add back error methods err = error.methods(err) # Access them print("Error kind: #{err.kind}") ``` ### Warnings when overwriting top-level variables The typechecker is now able to detect when top-level variables are overridden. This prevents situations like this: ```liquidsoap request = ... # Later... request.create(...) # 💥 Cryptic type error! ``` Previously, it was far too easy to overwrite important built-in modules (like `request`) and end up with confusing type errors. No script changes needed for this but if you see: ``` Warning 6: Top-level variable request is overridden! ``` …consider renaming your variable. ## `null()` replaced by `null` Previously, `null` was a function — you had to call `null()` to get a null value, or `null(value)` to wrap something. This confused a lot of people (and the typechecker wasn’t helping). Now, `null` can be used directly: ```liquidsoap my_var = null ``` Function form still works if you need it: ```liquidsoap my_var = null("some value") # Explicit nullable ``` ## From 2.2.x to 2.3.x ### Script caching A mechanism for caching script was added. There are two caches, one for the standard library that is shared by all scripts, and one for individual scripts. Scripts should run the same way with or without caching. However, caching your script has two advantage: - The script starts much faster. - Much less memory is used when starting. This memory is used the first time running the script to typecheck it and more. This is what we're caching. You can pre-cache a script using the `--cache-only` command: ```liquidsoap $ liquidsoap --cache-only /path/to/script.liq ``` The location of the two caches can be found by running `liquidsoap --build-config`. You can also set them using the `$LIQ_CACHE_USER_DIR` and `$LIQ_CACHE_SYSTEM_DIR` environment variables. Typically, inside a docker container, to pre-cache a script you would set `$LIQ_CACHE_SYSTEM_DIR` to the appropriate location and then run `liquidsoap --cache-only`: ```dockerfile ENV LIQ_CACHE_USER_DIR=/path/to/liquidsoap/cache RUN mkdir -p $LIQ_CACHE_USER_DIR && \ liquidsoap --cache-only /path/to/script.liq ``` See [the language page](language.html#caching) for more details! ### Default frame size Default frame size has been set to `0.02s`, down from `0.04s` in previous releases. This should lower the latency of your liquidsoap script. See [this PR](https://github.com/savonet/liquidsoap/pull/4033) for more details. ### Crossfade transitions and track marks Track marks can now be properly passed through crossfade transitions. This means that you also have to make sure that your transition function is fallible! For instance, this silly transition function: ```liquidsoap def transition(_, _) = blank(duration=2.) end ``` Will never terminate! Typically, to insert a jingle you would do: ```liquidsoap def transition(old, new) = sequence([old.source, single("/path/to/jingle.mp3"), new.source]) end ``` ### Replaygain - There is a new `metadata.replaygain` function that extracts the replay gain value in _dB_ from the metadata. It handles both `r128_track_gain` and `replaygain_track_gain` internally and returns a single unified gain value. - The `file.replaygain` function now takes a new compute parameter: `file.replaygain(~id=null, ~compute=true, ~ratio=50., file_name)`. The compute parameter determines if gain should be calculated when the metadata does not already contain replaygain tags. - The `enable_replaygain_metadata` function now accepts a compute parameter to control replaygain calculation. - The `replaygain` function no longer takes an `ebu_r128` parameter. The signature is now simply: `replaygain(~id=null, s)`. Previously, `ebu_r128` allowed controlling whether EBU R128 or standard replaygain was used. However, EBU R128 data is now extracted directly from metadata when available. So `replaygain` cannot control the gain type via this parameter anymore. ### Regular expressions The library providing regular expressions has been switched with `2.3.0`. This means that subtle differences can arise with the evaluation of some regular expressions. Here's an example that was recently reported: In `2.2.x`, this was true: ``` # When using a regular expression with a capture pattern to split, the value matched for splitting is returned: % string.split(separator="(:|,)", "foo:bar") ["foo", ":", "bar"] # But not when using a regular expression without matching: % string.split(separator=":|,", "foo:bar") ["foo", "bar"] ``` In `2.3.x`, the matched pattern is not returned: ``` % string.split(separator="(:|,)", "foo:bar") ["foo", "bar"] % string.split(separator=":|,", "foo:bar") ["foo", "bar"] ``` ### Static requests Static requests detection can now work with nested requests. Typically, a request for this URI: `annotate:key="value",...:/path/to/file.mp3` will be considered static if `/path/to/file.mp3` can be decoded. Practically, this means that more source will now be considered infallible, for instance a `single` using the above uri. In most cases, this should improve the user experience when building new scripts and streaming systems. In rare cases where you actually wanted a fallible source, you can still pass `fallible=true` to e.g. the `single` operator or use the `fallible:` protocol. ### String functions Some string functions have been updated to account for string encoding. In particular, `string.length` and `string.sub` now assume that their given string is in `utf8` by default. While this is what most user expect, this can lead to backward incompatibilities and new exceptions. You can change back to the old default by passing `encoding="ascii"` to these functions or using the `settings.string.default_encoding` settings. ### `check_next` `check_next` in playlist operators is now called _before_ the request is resolved, to make it possible to cut out unwanted requests before consuming process time. If you need to see the request's metadata or if the request resolves into a valid tile, however, you might need to call `request.resolve` inside your `check_next` script. ### Regular expressions The backend to interpret regular expressions has been changed. For the most part, all existing regular expressions should be supported but you might experience some incompatibilities with advanced/complex ones. Known incompatibilities include: - `(?P<name>pattern)` for named captures is not supported. `(?<name>pattern)` should be used instead. ### `segment_name` in HLS outputs To make segment name more flexible, `duration` (segment duration in seconds) and `ticks` (segment exact duration in liquidsoap's main ticks) have been added to the data available when calling `segment_name`. To prevent any further breakage of this function, its arguments have been changed to a single record containing all the available attributes: ```liquidsoap def segment_name(metadata) = "#{metadata.stream_name}_#{metadata.position}.#{metadata.extname}" end ``` ### `on_air` metadata Request `on_air` and `on_air_timestamp` metadata are deprecated. These values were never reliable. They are set at the request level when `request.dynamic` and all its derived sources start playing a request. However, a request can be used in multiple sources and the source using it can be used in multiple outputs or even not be actually being on the air if, for instance, it not selected by a `switch` or `fallback`. Instead, it is recommended to get this data directly from the outputs. Starting with `2.3.0`, all output now add `on_air` and `on_air_timestamp` to the metadata returned by `on_track`, `on_metadata` and `last_metadata` and the telnet `metadata` command. For the telnet `metadata` command, these metadata need to be added to the `settings.encoder.metadata.export` setting first. If you are looking for an event-based API, you can use the output's `on_track` methods to track the metadata currently being played and the time at which it started being played. For backward compatibility and easier migration, `on_air` and `on_air_timestamp` metadata can be enabled using the `settings.request.deprecated_on_air_metadata` setting: ```liquidsoap settings.request.deprecated_on_air_metadata := true ``` However, it is highly recommended to migrate your script to use one of the new method. ### `last_metadata` The implementation of `last_metadata` was updated to clear the last metadata when a new track begins. This is more in line with most user's expectation: last metadata is intended to reflect the metadata of the current track. If you need to, you can revert to the previous behavior using the source's `reset_last_metadata_on_track` method: ```liquidsoap s.reset_last_metadata_on_track := false ``` ### Gstreamer `gstreamer` was removed. It had been deprecated for a while. We expect `ffmpeg` to carry most, if not all of gstreamer's features. See [this PR](https://github.com/savonet/liquidsoap/pull/4036) for more details. ### Prometheus The default port for the Prometheus metrics exporter has changed from `9090` to `9599`. As before, you can change it with `settings.prometheus.server.port := <your port value>`. ### `source.dynamic` Many operators such as `single` and `request.once` have been reworked to use `source.dynamic` as their underlying implementation. The operator is now considered usable in production although we urge caution when using it: it is very powerful but can also break things! If you were (boldly!) using this operator before, the most important change is that its `set` method has been removed in favor of a unique callback API. ## From 2.1.x to 2.2.x ### References The `!x` notation for getting the value of a reference is now deprecated. You should write `x()` instead. And `x := v` is now an alias for `x.set(v)` (both can be used interchangeably). ### Icecast and Shoutcast outputs `output.icecast` and `output.shoutcast` are some of our oldest operators and were in dire need of some cleanup so we did it! We applied the following changes: - You should now use `output.icecast` only for sending to icecast servers and `output.shoutcast` only for sending to shoutcast servers. All shared options have been moved to their respective specialized operator. - Old `icy_metadata` argument was renamed to `send_icy_metadata` and changed to a nullable `bool`. `null` means guess. - New `icy_metadata` argument now returns a list of metadata to send with ICY updates. - Added a `icy_song` argument to generate default `"song"` metadata for ICY updates. Defaults to `<artist> - <title>` when available, otherwise `artist` or `title` if available, otherwise `null`, meaning don't add the metadata. - Cleaned up and removed parameters that were irrelevant to each operator, i.e. `icy_id` in `output.icecast` and etc. - Made `mount` mandatory and `name` nullable. Use `mount` as `name` when `name` is `null`. ### HLS events Starting with version `2.2.1`, on HLS outputs, `on_file_change` events are now `"created"`, `"updated"` and `"deleted"`. This breaking was required to reflect the fact that file changes are now atomic. See [this issue](https://github.com/savonet/liquidsoap/issues/3284) for more details. ### `cue_cut` Starting with version `2.2.4`, the `cue_cut` operator has been removed. Requests cue-in and cue-out processing has been integrated directly into requests resolution. In most cases, you simply can remove the operator from your script. In some cases, you might need to disable `cue_in_metadata` and `cue_out_metadat` either when creating new requests or when creating `playlist` sources. ### Harbor HTTP server and SSL support The API for registering HTTP server endpoint and using SSL was completely rewritten. It should be more flexible and provide node/express like API for registering endpoints and middleware. You can checkout [the harbor HTTP documentation](harbor_http.html) for more details. The [Https support](harbor_http.html#https-support) section also explains the new SSL/TLS API. ### Timeout We used to have timeout values labelled `timeout` or `timeout_ms`, some of these would be integer and in milliseconds, other floating point and in seconds etc. This was pretty confusing so, now all `timeout` settings and arguments have been unified to be named `timeout` and hold a floating point value representing a number of seconds. In most cases, your script will not execute until you have updated your custom `timeout` values but you should also review all of them to make sure that they follow the new convention. ### Metadata overrides Some metadata overrides have been made to reset on track boundaries. Previously, those were permanent even though they were documented as only applying to the current track. If you need to keep the previous behavior, you can used the `persist_overrides` parameters (`persis_override` for `cross`/`crossfade`). The list of concerned metadata is: - `"liq_fade_out"` - `"liq_fade_skip"` - `"liq_fade_in"` - `"liq_cross_duration"` - `"liq_fade_type"` ### JSON rendering The confusing `let json.stringify` syntax has been removed as it did not provide any feature not already covered by either the `json.stringify()` function or the generic `json()` object mapper. Please use either of those now. ### Default character encoding in `output.{harbor,icecast,shoutcast}` Default encoding for `output.harbor`, `output.icecast` and `output.shoutcast` metadata has been changed to `UTF-8` in all cases. Legacy systems used to expect `ISO-8859-1` (also known as `latin1`) for metadata inserted into `mp3` streams via the `icy` mechanism. It seems that, nowadays, most software expect `UTF-8` out of the box, including for legacy systems that previously assumed other encodings. Therefore, by changing this default value, we try to match expectations of the largest number of users of our software. If you are using one of these outputs, make sure to test this assumptions with your listners' clients. If needed, the characters encoding can be set to a different value using the operator's parameters. ### Decoder names Decoder names have been converted to lowercase. If you were relying on specific settings for decoders priority/ordering, you will need to convert them to lowercase, for instance: ``` settings.decoder.decoders.set(["FFMPEG"]) ``` becomes: ``` settings.decoder.decoders.set(["ffmpeg"]) ``` Actually, because of the above change in references, this even becomes: ``` settings.decoder.decoders := ["ffmpeg"] ``` ### `strftime` Add file-based operators do not support `strftime` type conversions out of the box anymore. Instead, you should use explicit conversions using `time.string`. This means that this script: ```liquidsoap output.file("/path/to/file%H%M%S.wav", ...) ``` becomes: ```liquidsoap output.file({time.string("/path/to/file%H%M%S.wav")}, ...) ``` ### Other breaking changes - `reopen_on_error` and `reopen_on_metadata` in `output.file` an related outputs are now callbacks. - `request.duration` now returns a `nullable` float, `null` being value returned when the request duration could not be computed. - `getenv` (resp. `setenv`) has been renamed to `environment.get` (resp. `environment.set`). ## From 2.0.x to 2.1.x ### Regular expressions First-class [regular expression](language.html#regular-expressions) are introduced and are used to replace the following operators: - `string.match(pattern=<regexp>, <string>` is replaced by: `r/<regexp>/.test(<string>)` - `string.extract(pattern=<regexp>, <string>)` is replaced by: `r/<regexp>/.exec(<string>)` - `string.replace(pattern=<regexp>, <string>)` is replaced by: `r/<regexp>/g.replace(<string>)` - `string.split(separator=<regexp>, <string>)` is replaced by: `r/<regexp>/.split(<string>)` ### Partial application In order to improve performance, avoid some programming errors and simplify the code, the support for partial application of functions was removed (from our experience it was not used much anyway). This means that you should now provide all required arguments for functions. The behavior corresponding to partial application can of course still be achieved by explicitly abstracting (with `fun(x) -> ...`) over some arguments. For instance, suppose that we defined the addition function with two arguments with ```liquidsoap def add(x,y) = x + y end ``` and defined the successor function by partially applying it to the first argument ```liquidsoap suc = add(1) ``` We now need to explicitly provide the second argument, and the `suc` function should now be defined as ```liquidsoap suc = fun(x) -> add(1, x) ``` or ```liquidsoap def suc(x) = add(1, x) end ``` ### JSON parsing JSON parsing was greatly improved and is now much more user-friendly. You can check out our detailed presentation [here](json.html). ### Runtime evaluation Runtime evaluation of strings has been re-implemented as a type-safe eval `let` decoration. You can now do: ```liquidsoap let eval x = "[1,2,3]" ``` And, just like with JSON parsing, the recommended use is with a _type annotation_: ```liquidsoap let eval (x: [int]) = "[1,2,3]" ``` ### Deprecations and breaking changes - The argument `streams_info` of `output.file.hls` is now a record. - Deprecated argument `timeout` of `http.*` operators. - `source.on_metadata` and `source.on_track` now return a source as this was the case in previous versions, and associated handlers are triggered only when the returned source is pulled - `output.youtube.live` renamed `output.youtube.live.rtmp`, remove `bitrate` and `quality` arguments and added a single encoder argument to allow stream copy and more. - `list.mem_assoc` is replaced by `list.assoc.mem` - `timeout` argument in `http.*` operators is replaced by `timeout_ms`. - `request.ready` is replaced by `request.resolved` ## From 1.4.x to 2.0.0 ### `audio_to_stereo` `audio_to_stereo` should not be required in most situations anymore. `liquidsoap` can handle channels conversions transparently now! ### `auth` function in `input.harbor` The type of the `auth` function in `input.harbor` has changed. Where before, you would do: ```liquidsoap def auth(user, password) = ... end ``` You would now do: ```liquidsoap def auth(params) user = params.user password = params.password ... end ``` ### Type errors with lists of sources Now that sources have their own methods, the actual list of methods attached to each source can vary from one to the next. For instance, `playlist` has a `reload` method but `input.http` does not. This currently confuses the type checker and leads to errors that look like this: ```liquidsoap At script.liq, line xxx, char yyy-zzz: Error 5: this value has type _ * source(audio=?A, video=?B, midi=?C) .{ time : () -> float, shutdown : () -> unit, fallible : bool, skip : () -> unit, seek : (float) -> float, is_active : () -> bool, is_up : () -> bool, log : {level : (() -> int?).{set : ((int) -> unit)} }, self_sync : () -> bool, duration : () -> float, elapsed : () -> float, remaining : () -> float, on_track : ((([string * string]) -> unit)) -> unit, on_leave : ((() -> unit)) -> unit, on_shutdown : ((() -> unit)) -> unit, on_metadata : ((([string * string]) -> unit)) -> unit, is_ready : () -> bool, id : () -> string, selected : (() -> source(audio=?D, video=?E, midi=?F)?) } but it should be a subtype of the type of the value at radio.liq, line 122, char 2-21 _ * _.{reload : _} ``` In such cases, we recommend to give a little nudge to the typechecker by using the `(s:source)` type annotation where a list of source is causing the issue. For instance: ```liquidsoap s = fallback([ (s1:source), (s2:source), (s3:source) ]) ``` This tells the type checker not to worry about the source methods and just focus on what matters, that they are actually sources.. 🙂 ### Http input and operators In order to provide as much compatibility as possible with the different HTTP protocols and implementation, we have decided to delegate HTTP support to external libraries which have large scale support and implementation. This means that, if you have installed `liquidsoap` using `opam`: - You need to install the `ocurl` package to enable all HTTP request operators, `http.get`, `http.post`, `http.put`, `http.delete` and `http.head` - You need to install the `ffmpeg` package (version `1.0.0` or above) to enable `input.http` - You do not need to install the `ssl` package anymore to enable their `https` counter-part. These operators have been deprecated. ### Crossfade The parameters for `cross` transitions was changed to take advantage of the new module system. Instead of passing multiple arguments related to the ending and starting track, those are regrouped into a single record. So, if you had a transition like this: ```liquidsoap def transition( ending_dB_level, starting_dB_level, ending_metadata, starting_metadata, ending_source, starting_source) = ... end ``` You would now do: ```liquidsoap def transition(ending, starting) = # Now you can use: # - ending.db_level, ending.metadata, ending.source # - starting.db_level, starting.metadata, starting.source ... end ``` ### Settings Settings are now exported as records. Where you would before write: ```liquidsoap set("decoder.decoders", ["MAD", "FFMPEG"]) ``` You can now write: ```liquidsoap settings.decoder.decoders.set(["MAD", "FFMPEG"]) ``` Likewise, to get a setting's value you can now do: ```liquidsoap current_decoders = settings.decoder.decoders() ``` This provides many good features, in particular type-safety. For convenience, we have added shorter versions of the most used settings. These are all shortcuts to their respective `settings` values: ```liquidsoap log.level.set(4) log.file.set(true) log.stdout.set(true) init.daemon.set(true) audio.samplerate.set(48000) audio.channels.set(2) video.frame.width.set(720) video.frame.height.set(1280) ``` The `register` operator could not be adapted to this new API and had to be removed, however, backward-compatible `set` and `get` operators are provided. Make sure to replace them as they should be removed in a future version. ### Metadata insertion The function `insert_metadata` does not return a pair anymore, but a source with a method named `insert_metadata`. This means that you should change the code ```liquidsoap fs = insert_metadata(s) # The function to insert metadata f = fst(ms) # The source with inserted metadata s = snd(ms) ... # Using the function f([("artist", "Bob")]) ... # Using the source output.pulseaudio(s) ``` to ```liquidsoap s = insert_metadata(s) ... # Using the function s.insert_metadata([("artist", "Bob")]) ... # Using the source output.pulseaudio(s) ``` ### Request-based queueing Queueing for request-based sources has been simplified. The `default_duration` and `length` have been removed in favor of a simpler implementation. You can now pass a `prefetch` parameter which tells the source how many requests should be queued in advance. Should you need more advanced queueing strategy, `request.dynamic.list` and `request.dynamic` now export functions to retrieve and set their own queue of requests. ### JSON import/export `json_of` has been renamed `json.stringify` and `of_json` has been renamed `json.parse`. JSON export has been enhanced with a new generic json object export. Associative lists of type `(string, 'a)` are now exported as lists. See our [JSON documentation page](json.html) for more details. Convenience functions have been added to convert metadata to and from JSON object format: `metadata.json.stringify` and `metadata.json.parse`. ### Returned types from output operators Starting with liquidsoap `2.0.0`, output operators return the empty value `()` while they previously returned a source. This helps enforce the fact that outputs should be end-points of your scripting graphs. However, in some cases, this can cause issues while migrating old scripts, in particular if the returned value of an output was used in the script. The way to fix this is to apply your operator to the source directly underneath the output. For instance, the following clock assignment: ```liquidsoap s = ... clock.assign_new([output.icecast(..., s)]) ``` Should now be written: ```liquidsoap s = ... clock.assign_new([s], ...) output.icecast(..., s) ``` ### Deprecated operators Some operators have been deprecated. For most of them, we provide a backward-compatible support but it is good practice to update your script. You should see logs in your script when running deprecated operatords. Here's a list of the most important ones: - `playlist.safe` is replaced by: `playlist(mksafe(..))` - `playlist.once` is replaced by: `playlist`, setting `reload_mode` argument to `"never"` and `loop` to `false` - `rewrite_metadata` should be rewritten using `metadata.map` - `fade.initial` and `fade.final` are not needed anymore - `get_process_output` is replaced by: `process.read` - `get_process_lines` is replaced by: `process.read.lines` - `test_process` is replaced by: `process.test` - `system` is replaced by: `process.run` - `add_timeout` is replaced by: `thread.run.recurrent` - `on_blank` is replaced by: `blank.detect` - `skip_blank` is replaced by: `blank.skip` - `eat_blank` is replaced by: `blank.eat` - `strip_blank` is replaced by: `blank.strip` - `which` is replaced by: `file.which` - `register_flow`: flow is no longer maintained - `empty` is replaced by: `source.fail` - `file.unlink` is replaced by: `file.remove` - `string.utf8.escape` is replaced by: `string.escape` - `metadata.map` is replaced by: `metadata.map` ### Windows build The windows binary is statically built and, for this reason, we cannot enable both the `%ffmpeg` encoder and any encoder that uses the same underlying libraries, for instance `libmp3lame` for `mp3` encoding. The technical reason is that both libraries import the same C symbols, which makes compilation fail. The `%ffmpeg` encoder provides all the functionalities of the internal encoders that conflict with it along with many more format we do not support otherwise. For this reason, it was decided to enable the `%ffmpeg` encoder and disable all other encoders. This means that, if you were previously using a different encoder than `%ffmpeg`, you will need to adapt your script to use it. For instance, for mp3 encoding with variable bitrate: ```liquidsoap %ffmpeg(format="mp3", %audio(codec="libmp3lame", q=7)) ``` �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/multitrack.md����������������������������������������������������������0000664�0000000�0000000�00000017320�15132732333�0020616�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# 🎚️ Multitrack Support in Liquidsoap Starting in version 2.2.0, Liquidsoap gained support for **multitrack operations** — a powerful addition that lets you work with individual tracks inside your media files. Whether it's audio, video, or metadata, you can now demux, remux, encode, decode, apply filters, and more — all at the track level! This unlocks advanced media workflows, like keeping multiple language tracks, adding a default video stream to audio-only files, or even mixing and matching content across sources. Let’s walk through what this means, step by step 👇 ## 🧠 What is a Track? A media file can contain multiple “tracks.” Think of a movie file with: - 🎧 English and French audio tracks - 🎥 One video track - 📝 Metadata like title and artist In Liquidsoap, these tracks are made accessible through operators that let you manipulate them individually. This opens up many possibilities — but also introduces a few new concepts and conventions to learn. ## 🔧 Requirements: When You Need FFmpeg Some multitrack features rely on FFmpeg, while others don't: | Feature | Requires FFmpeg? | | ----------------------------------------------- | -------------------------------- | | Track-level encode/decode | ✅ Yes | | Encode or decode multiple audio or video tracks | ✅ Yes | | Demuxing/remuxing tracks | ❌ No | | Source track manipulation | ❌ No (unless decoding/encoding) | To fully unlock multitrack functionality, make sure your Liquidsoap is compiled with FFmpeg support. ## 🎬 Using Multitrack Media Let’s say you have a media file with multiple tracks, like: ```sh movie.mkv ├── audio (English) ├── audio_2 (French) └── video ``` You can create a source with this file like so: ```liquidsoap s = single("/path/to/movie.mkv") ``` By default, only the first audio and video track will be used: ```liquidsoap output.file(%ffmpeg(%audio.copy, %video.copy), "/path/to/copy.mkv", s) ``` 🪵 **Logs will confirm the detected tracks:** ``` [output_file:3] Content type is {audio=ffmpeg.copy,video=ffmpeg.copy}. [decoder.ffmpeg:3] Requested content-type for "/path/to/movie.mkv": {audio=ffmpeg.copy,video=ffmpeg.copy} [decoder.ffmpeg:3] FFmpeg recognizes "/path/to/movie.mkv" as video: {codec: h264, 1920x1038, yuv420p}, audio: {codec: aac, 48000Hz, 6 channel(s)}, audio_2: {codec: aac, 48000Hz, 6 channel(s)} [decoder.ffmpeg:3] Decoded content-type for "/path/to/movie.mkv": {audio=ffmpeg.copy(codec="aac",channel_layout="5.1",sample_format=fltp,sample_rate=48000),video=ffmpeg.copy(codec="h264",width=1920,height=1038,aspect_ratio=1/1,pixel_format=yuv420p)} ``` Here, `audio_2` is present but unused. Let’s fix that 👇 ## 🎛️ Custom Track Handling What if you want to **keep both audio tracks**, re-encoding the second one to stereo? ```liquidsoap output.file( %ffmpeg( %audio.copy, %audio_2(channels=2, codec="aac"), %video.copy ), "/path/to/copy.mkv", s ) ``` 🪵 Logs now show `audio_2` being processed: ``` [output_file:3] Content type is {audio=ffmpeg.copy,audio_2=pcm(stereo),video=ffmpeg.copy}. [decoder.ffmpeg:3] Requested content-type for "/path/to/movie.mkv": {audio=ffmpeg.copy,audio_2=pcm(stereo),video=ffmpeg.copy} [decoder.ffmpeg:3] FFmpeg recognizes "/path/to/movie.mkv" as video: {codec: h264, 1920x1038, yuv420p}, audio: {codec: aac, 48000Hz, 6 channel(s)}, audio_2: {codec: aac, 48000Hz, 6 channel(s)} [decoder.ffmpeg:3] Decoded content-type for "/path/to/movie.mkv": {audio=ffmpeg.copy(codec="aac",channel_layout="5.1",sample_format=fltp,sample_rate=48000),audio_2=pcm(5.1),video=ffmpeg.copy(codec="h264",width=1920,height=1038,aspect_ratio=1/1,pixel_format=yuv420p)} ``` You now have a file with two audio tracks (one copied, one re-encoded) and one video track 🎉 ## ⚠️ Playlist Caveats If your source is a playlist: ```liquidsoap s = playlist("/path/to/playlist") ``` …and you request multiple tracks: ```liquidsoap output.file( fallible=true, %ffmpeg(%audio.copy, %audio_2(...), %video.copy), "/path/to/copy.mkv", s ) ``` Then **only files with all requested tracks** (audio + audio_2 + video) will be accepted. Others will be skipped. ## 🧭 Track Naming Conventions When decoding, track names follow this pattern: - `audio`, `audio_2`, `audio_3`, ... - `video`, `video_2`, `video_3`, ... These names are fixed by the decoder — you **can’t** rename them directly when reading media. For example, this will fail: ```liquidsoap output.file(%ffmpeg(%audio_fr.copy, %audio_en(...), "/path/to/file.mkv", playlist("..."))) ``` Why? Because the decoder doesn’t know what `audio_fr` or `audio_en` means. Track names must match what the decoder emits: `audio`, `audio_2`, etc. Once you're remuxing tracks, however, you can assign **any name** you want! ## 🔄 Demuxing and Remuxing Tracks To extract and rebuild track sets: ```liquidsoap s = playlist(...) let {audio, video, metadata, track_marks} = source.tracks(s) ``` You can then remix these into a new source: ```liquidsoap s = source({ audio = audio, video = video, metadata = metadata, track_marks = track_marks }) ``` Tracks can also be added or replaced: ```liquidsoap s = source(source.tracks(s).{video = source.tracks(image).video}) ``` One limitation is that it is not currently possible to add default tracks. The following **won't work**: ```liquidsoap video = source.tracks(s).video ?? source.tracks(image).video ``` ## 🧹 Cleaning Up Tracks Want to remove `track_marks`? ```liquidsoap let {track_marks=_, ...tracks} = source.tracks(s) s = source(tracks) ``` This is equivalent to the older `drop_tracks` operator. ## 🔌 Track-Level Operators Many operators now work directly on tracks. Examples: ```liquidsoap mono = track.audio.mean(audio_track) encoded = track.ffmpeg.encode.audio(%ffmpeg(%audio(codec="aac")), audio_track) ``` 🚨 But beware: some operators (like inline encoders) put the track on a new **clock**. You’ll need to re-derive metadata and track marks from the new track to avoid clock conflicts: ```liquidsoap let encoded_audio = track.ffmpeg.encode.audio(..., audio) s = source({ audio = encoded_audio, metadata = track.metadata(encoded_audio), track_marks = track.track_marks(encoded_audio) }) ``` ## 🏷️ How Encoders Detect Track Types Liquidsoap uses **naming conventions** and **hints** to determine what kind of data each track holds: Priority order: 1. `%audio.copy` or `%video.copy` → auto-detect 2. Named content type (`audio_content` or `video_content`) 3. Track name contains “audio” or “video” 4. Codec implies type ### ✅ Example: Explicit typing ```liquidsoap output.file( %ffmpeg( %en(audio_content, codec=audio_codec), %fr(codec="aac"), %director_cut(video_content, codec=video_codec) ), "/path/to/copy.mkv", s ) ``` ### ✅ Or by naming convention ```liquidsoap %audio_en(codec=...) %director_cut_video(codec=...) ``` Internally, Liquidsoap maps this to content-type info. Once handed off to FFmpeg, **track names are lost** — FFmpeg just sees numbered tracks in order. ## 🚀 Summary Multitrack support opens up powerful new workflows: - 🔄 Mix and match audio/video/metadata across sources - 🧱 Build custom media containers - 🎯 Target different formats per track - 🧪 Combine synchronous and asynchronous operations (with care!) We’ve only scratched the surface — go ahead and explore the code, experiment, and let your creativity flow! 💡 And if there’s an operator you wish worked at the track level, don’t hesitate to [open a feature request](https://github.com/savonet/liquidsoap)! ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/on2.md�����������������������������������������������������������������0000664�0000000�0000000�00000000776�15132732333�0017144�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Savonet was at the [ON2: Test Signals](http://testsignals.org/) conference in Berlin, on October 22-23 2010. We presented Liquidsoap, but also held the first Liquidsoap workshop. <iframe src="https://player.vimeo.com/video/16528307" width="640" height="352" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe> <p><a href="https://vimeo.com/16528307">Liquidsoap presentation at ON2</a> from <a href="https://vimeo.com/smimram">Samuel Mimram</a> on <a href="https://vimeo.com">Vimeo</a>.</p> ��liquidsoap-2.4.2/doc/content/phases.md��������������������������������������������������������������0000664�0000000�0000000�00000003400�15132732333�0017714�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Liquidsoap execution phases There are various stages of running liquidsoap: - **Parsing**: read scripts and scripting expressions, can fail with syntax errors. - **Static analysis**: infer the type of all expressions, leaves some type unknown and may fail with type errors. - **Instantiation**: when script is executed, sources get created. Remaining unknown [stream types](stream_contents.html) are forced according to `frame.*.channels` settings, [clocks](clock.html) are assigned (but unknown clocks may remain) and some sources are checked to be [infallible](source.htmls). Each of these steps may raise an error. - **Collection**: Unknown clocks become the default clock so that all sources are assigned to one clock. Active sources newly attached to clocks are initialized for streaming, shutdown sources are detached from their clocks, and clocks are started or destroyed as needed. Streaming has started. Usually, liquidsoap is ran by passing one or several scripts and expressions to execute. Those expressions set up some sources, and outputs typically don't change anymore. If those initially provided active sources fail to be initialized (invalid parameter, fail to connect, etc.) liquidsoap will terminate with an error. It is however possible to **dynamically** create active sources, through registered server commands, event handlers, etc. They will be initialized and run as statically created ones. In **interactive** mode (passing the `--interactive` option) it is also possible to input expressions in a liquidsoap prompt, and their execution can trigger the creation of new outputs. Outputs can be deactivated using `source.shutdown()`: they will stop streaming and will be destroyed. The full liquidsoap instance can be shutdown using the `shutdown()` command. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/playlist_parsers.md����������������������������������������������������0000664�0000000�0000000�00000007431�15132732333�0022041�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Playlist parsers Liquidsoap supports various playlists formats. Those formats can be used for `playlist` sources, `input.http` streams and manually using `request.create`. ## Supported formats Most supported playlists format are _strict_, which means that the decoder can be sure that is has found a correct playlist for that format. Some other format, such as `m3u`, however, may cause _false positive_ detections. All formats are identified by their _mime-type_ or _content-type_. Supported formats are the following: - Text formats: - `audio/x-scpls`: [PLS format](http://en.wikipedia.org/wiki/PLS_%28file_format%29), **strict** - `application/x-cue`: [CUE format](http://en.wikipedia.org/wiki/.cue), **strict**. This format's usage is described below. - `audio/x-mpegurl`, `audio/mpegurl`: [M3U](http://en.wikipedia.org/wiki/M3u), **non strict** - Xml formats: - `video/x-ms-asf`, `audio/x-ms-asx`: [ASX](http://en.wikipedia.org/wiki/Advanced_Stream_Redirector), **strict** - `application/smil+xml`, `application/smil+xml`, [SMIL](http://en.wikipedia.org/wiki/Synchronized_Multimedia_Integration_Language), **strict** - `application/xspf+xml`, [XSPF](http://en.wikipedia.org/wiki/Xspf), **strict** - `application/rss+xml`, [Podcast](http://en.wikipedia.org/wiki/Podcast), **strict** Playlist format is driven by the **Content-Type** and **Content-Disposition** HTTP headers _(see m3u example below)_. You should make sure that your HTTP endpoint returns appropriate values for those. As last resort, you should be able to use the `settings.http.mime.extnames` settings to add or adjust support for your endpoint's mime-type if liquidsoap supports its corresponding playlist format. See for instance issue [#3451](https://github.com/savonet/liquidsoap/issues/3451). ## Usage Playlist files are parsed automatically when used in a `playlist` or `input.http` operator. Each of these two operators has specific options to specify how to pick up a track from the playlist, _e.g._ pick a random track, the first one etc. Additionally, you can also manually parse and process a playlist using `request.create` and `request.resolve` and some programming magic. You can check the code source for `playlist.reloadable` in our standard library for a detailed example. ### Remote M3U playlist example Here is an example of a m3u playlist being read from nodejs/express . liquidsoap script: ```liquidsoap #!/usr/local/bin/liquidsoap p = playlist(reload=10, "http://localhost:8080/radio/playlists/0/playlist.m3u") ``` nodejs/express app: ```js import express from "express"; const app = express(); app.get("/radio/playlists/:id/playlist.m3u", async (req, res) => { const playlist = ["/media/foo.mp3", "/media/bar.mp3"]; // Liquidsoap will use the file extension from the `Content-Disposition` header to guess // the playlist format res.set( "Content-Disposition", `attachment; filename="playlist-${req.params.id}.m3u"`, ); // Otherwise, it will try to guess the file extension from the playlist mime-type. res.set("Content-Type", "audio/x-mpegurl"); res .send(playlist.join("\r\n") + "\r\n") .status(200) .end(); }); const server = app.listen(8080); ``` ## Special case: CUE format The CUE format originates from CD burning programs. They describe the set of tracks of a whole CD and are accompanied by a single file containing audio data for the whole CD. By default, the CUE playlist parser will add metadata from cue-in and cue-out points for each track described in the playlist, which are automatically handled with source-base operators such as `playlist`. The metadata added for cue-in and cue-out positions can be customized using the following configuration keys: ```liquidsoap settings.playlists.cue_in_metadata := "liq_cue_in" settings.playlists.cue_out_metadata := "liq_cue_out" ``` ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/presentations.md�������������������������������������������������������0000664�0000000�0000000�00000003122�15132732333�0021330�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Presentations about Liquidsoap ## Liquidshop In January 2021, we organized [the _Liquidshop_, a workshop around Liquidsoap](http://www.liquidsoap.info/liquidshop/), where you can find lots of presentations around Liquidsoap. In particular, Romain presented the main features of the upcoming Liquidsoap 2.0: <iframe width="560" height="315" src="https://www.youtube.com/embed/VT6TEjJzWoY" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> and Gilou did a tutorial about setting up a webradio with Liquidsoap <iframe width="560" height="315" src="https://www.youtube.com/embed/B8l8uqBS6-c" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> ## FOSDEM 2020 We presented liquidsoap during the FOSDEM 2020 conference. You can watch the video here: <iframe src="https://player.vimeo.com/video/388951779" width="640" height="360" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe> <p><a href="https://vimeo.com/388951779">Functional audio and video stream generation with Liquidsoap</a> from <a href="https://vimeo.com/user27259977">Romain Beauxis</a> on <a href="https://vimeo.com">Vimeo</a>.</p> The slides for the presentation are available <a href="/fosdem2020/index.html" target="_blank">here</a> ## ON2 We were part of the ON2 conference, during which we presented liquidsoap and held a complete workshop about how to install and use it. You can see the details about this event <a href="on2.html">here</a>. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/profiling.md�����������������������������������������������������������0000664�0000000�0000000�00000002201�15132732333�0020420�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Profiling scripts Sometimes, some functions of your script are taking up time and you would like to optimize those. We are not speaking here about the encoding of streams, which usually takes the vast majority of the spent computing power, but of functions written directly in Liquidsoap. In order to understand those better, Liquidsoap has a _profiler_ which records all the function calls. It can be enabled with ```liquidsoap profiler.enable() ``` (or by passing the `--profile` commandline flag of Liquidsoap) and the statistics can be obtained with ```liquidsoap print(profiler.stats.string()) ``` It will output something like ``` function self total calls + 0.359139919281 0.359139919281 302000 list.add 0.324638843536 442.74707818 202000 if 0.242718935013 442.951756954 102002 list.cons 0.230906486511 442.277146816 101000 ``` where each lines consists of a function, the time spent in the functions, the time spent in the function and the functions it has called and the number of calls to the function. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/prometheus.md����������������������������������������������������������0000664�0000000�0000000�00000010455�15132732333�0020634�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Prometheus reporting When compiled with optional support for [mirage/prometheus](https://github.com/mirage/prometheus), `liquidsoap` can export [prometheus](https://prometheus.io/) metrics. The basic settings to enable exports are: ```{.liquidsoap include="prometheus-settings.liq"} ``` Common metrics, namely `gauge`, `counter` and `summary` are provided via the script language, as well as a specialized operator to track source's latencies. A fully-featured implementation can be found at [mbugeia/srt2hls](https://github.com/mbugeia/srt2hls) ## Basic operators The 3 basic operators are: - `prometheus.counter` - `prometheus.gauge` - `prometheus.summary` They share a similar type and API, which is as follows: ```liquidsoap (help : string, ?namespace : string, ?subsystem : string, labels : [string], string) -> (label_values : [string]) -> (float) -> unit ``` This type can be a little confusing. Here's how it works: 1. First, one has to create a metric factory of a given type. For instance: ```{.liquidsoap include="prometheus-callback.liq" from="A0" to="A1"} ``` 2. Then, the metric factory can be used to instantiate speific metrics by passing the label's values: ```{.liquidsoap include="prometheus-callback.liq" from="B0" to="B1"} ``` The returned function is a setter for this metric, i.e. - For `gauge` metrics, it sets the gauge value - For `counter` metrics, it increases the counter value - For `summary` metrics, it registers an observation Finally, the programmer can now use that callback to set the metric as desired. For instance here: ```{.liquidsoap include="prometheus-callback.liq" from="C0"} ``` ## `prometheus.latency` The `prometheus.latency` operator provides prometheus metrics describing the internal latency of a given source. It is fairly easy to use: ```liquidsoap s = (...) prometheus.latency(s) ``` The metrics are computed over a sliding window that can be defined as a parameter of the operator. Exported metrics are: ``` # Input metrics: liquidsoap_input_latency{...} <value> liquidsoap_input_max_latency{...} <value> liquidsoap_input_peak_latency{...} <value> # Output metrics: liquidsoap_outputput_latency{...} <value> liquidsoap_output_max_latency{...} <value> liquidsoap_output_peak_latency{...} <value> # Overall metrics: liquidsoap_overall_latency{...} <value> liquidsoap_overall_max_latency{...} <value> liquidsoap_overall_peak_latency{...} <value> ``` The 3 different groups of values are: - **input**: metrics related to the time it takes to generate audio data - **output**: metrics related to the time it takes to output (encode and send) audio data - **overall**: the sum of all previous two groups Each group of metrics is divided into 3 subsets: - Mean latency value over the sliding window - Max latency value over the sliding window - Peak latency since start Latencies are reported over a frame's duration, which is typically around `0.04` seconds. Thus, in a situation where liquidsoap does not observe latency catch-ups, the overall mean latency `liquidsoap_overall_latency` should always be near that value. These metrics can be used to report and track the source of latencies and catch-ups while streaming. Typically, if a source starts taking too much time to generate its audio data, this should be reflects in the `input` latencies. Likewise for encoding and network output. Keep in mind, however, that enabling these metrics can have a CPU cost. It is rather small with a couple of sources but can increase with the number of sources being tracked. The user of these metrics is advised to keep track of CPU usage while ramping up on using them. ## OCaml specific metrics The prometheus binding used by `liquidsoap` also exports default OCaml-related metrics. They are as follows: ``` ocaml_gc_allocated_bytes <value> ocaml_gc_compactions <value> ocaml_gc_heap_words <value> ocaml_gc_major_collections <value> ocaml_gc_major_words <value> ocaml_gc_minor_collections <value> ocaml_gc_top_heap_words <value> process_cpu_seconds_total <value> ``` These metrics can be useful when debugging issues with `liquidsoap`, in particular to track is an observed increase in memory usage is related to OCaml memory allocation or not. More than often, if the increase is not related to OCaml, it can be safely assumed that the issue might come from an external library used by `liquisoap`. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/protocols-presentation.md����������������������������������������������0000664�0000000�0000000�00000012204�15132732333�0023170�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Protocols in Liquidsoap When Liquidsoap plays a track, it doesn’t just _magically_ appear — it has to be **resolved** from somewhere. That “somewhere” could be a local file, a remote URL, a database entry, or even something generated on the fly. That’s where **protocols** come in. Protocols are little rules that tell Liquidsoap: > “If you see a request in the form `protocol:arguments`, here’s how to turn it into a real file or stream you can play.” 🎯 For example: ```text http://www.example.com/song.mp3 say:Hello world! s3://my-bucket/path/to/file.mp3 ``` In each case, the prefix before the `:` is the protocol, and the part after it is the argument passed to your resolver code. ## Built-in and custom protocols Liquidsoap already ships with many ready-made protocols, written in the Liquidsoap scripting language. You can explore them in the [protocol reference](protocols.html). But the real power comes when you define your own. ## The anatomy of a protocol Every protocol is defined by a **handler function**. The handler: 1. Accepts the protocol arguments and some extra helper parameters. 2. Returns a single resolved URI 3. Can call other protocols in sequence, building a chain of transformations. The function always gets two special variables: - **`~rlog`** → A logging function. Use it to write debug or info messages that stay attached to the request. - **`~maxtime`** → A UNIX timestamp after which your resolver should give up. ## The `process.uri` helper 🛠️ Before diving into the examples, it’s important to understand **`process.uri`**, a convenient helper for creating URIs of the form: ```text process:<binary> <arguments> ``` When Liquidsoap encounters such a URI, it will automatically execute the given command and cancel it if it exceeds `~maxtime`. If you provide a `uri` argument, Liquidsoap will first resolve that URI to a local file. Your command can then use two placeholders: - **`$(input)`** → replaced with the local file resolved from the `uri` argument (only if `uri` is provided). - **`$(output)`** → replaced with the path to a temporary file whose extension is taken from the `extname` argument. ⚠️ The output file is created **empty** before the command runs, to prevent race conditions on file ownership. This means your process must be able to overwrite it. By using `process.uri`, you can safely wrap external commands in a way that’s time-bound, predictable, and integrates smoothly into Liquidsoap’s request resolution chain. ## Example 1 — Fetching from S3 ☁️ Let’s say your files live on Amazon S3, and you want Liquidsoap to fetch them on demand: ```liquidsoap def s3_protocol(~rlog, ~maxtime, arg) = extname = file.extension(leading_dot=false, dir_sep="/", arg) process.uri(extname=extname, "aws s3 cp s3:#{arg} $(output)") end protocol.add("s3", s3_protocol, doc="Fetch files from S3 using the AWS CLI", syntax="s3://bucket/path/to/file") ``` Now a request like: ```text s3://my-bucket/song.mp3 ``` will be downloaded locally and returned as the playable URI. ## Example 2 — Database lookup 📀 Protocols can also be dynamic. For instance, you might store file paths in a database keyed by track IDs: ```liquidsoap def db_lookup_protocol(~rlog, ~maxtime, arg) = string.trim(process.read("psql -t -c 'SELECT path FROM tracks WHERE id=#{int_of_string(arg)};'")) end protocol.add("db_lookup", db_lookup_protocol, doc="Fetch file path from database by track ID") ``` Now you can request: ```text db_lookup:42 ``` and Liquidsoap will resolve it via your database. ## Example 3 — Adding preprocessing Want to normalize audio before playing? Or apply a voice-over? ```liquidsoap def normalize_protocol(~rlog, ~maxtime, arg) = process.uri(extname="wav", uri=arg, "normalize-audio $(input) $(output)") end protocol.add("normalize", normalize_protocol, doc="Normalize audio levels before playback") ``` You can chain protocols too: ```text normalize:cue_cut:s3://my-bucket/file.mp3 ``` Liquidsoap will fetch from S3 → cut the segment → normalize it → play. ## Chaining magic The real beauty of protocols is chaining. Each protocol resolves to a single URI, which can then be handed off to the next one in the chain. This means you can build complex request pipelines: ```text voiceover:normalize:db_lookup:1234 ``` 💡 Here, `db_lookup` fetches a path, `normalize` evens out the audio, and `voiceover` mixes in an announcement. ## Tips for writing robust protocols - Always **respect `~maxtime`** to avoid long-hanging processes. - Use `~rlog` generously for debugging: ```liquidsoap rlog("Downloading from S3: #{arg}") ``` - Keep your commands secure — if you interpolate `arg` into a shell command, validate or escape it. - Test each piece of the chain independently before combining them. By mastering protocols, you’re not just telling Liquidsoap where to find your content — you’re **designing the path it takes to get there**. That’s a superpower in streaming workflows, letting you pull from anywhere, process in any way, and still keep things flowing smoothly. 🚀 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/publications.md��������������������������������������������������������0000664�0000000�0000000�00000002343�15132732333�0021132�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- header-includes: | \DeclareUnicodeCharacter{03BB}{$\lambda$} ... # The theory behind Liquidsoap ## Publications ### Liquidsoap: a High-Level Programming Language for Multimedia Streaming Many of the advanced features of the Liquidsoap language are described in [Liquidsoap: a High-Level Programming Language for Multimedia Streaming](/assets/docs/bbm10.pdf). The article details in particular Liquidsoap's handling of heterogeneous stream contents (e.g. audio and video), as well as the model for clocks in the language. ### De la webradio lambda à la λ-webradio The first published presentation of Liquidsoap was made in [De la webradio lambda à la λ-webradio](/assets/docs/bm08.pdf) (_Baelde D. and Mimram S. in proceedings of Journées Francophnes des Languages Applicatifs (JFLA), pages 47-61, 2008_) -- yes, it's in French, sorry. It gives a broad description of the Liquidsoap tool and explains the theory behind the language, which is formalized as a variant of the typed λ-calculus with labels and optional arguments. The article describes the typing inference algorithm as well as some properties of the language (confluence) and of typing (subject reduction, admissible rules, termination of typed terms). ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/quick_start.md���������������������������������������������������������0000664�0000000�0000000�00000024501�15132732333�0020767�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Quickstart ## The Internet radio toolchain [Liquidsoap](index.html) is a general audio stream generator, but is mainly intended for Internet radios. Before starting with the proper Liquidsoap tutorial let's describe quickly the components of the internet radio toolchain, in case the reader is not familiar with it. The chain is made of: - the stream generator (Liquidsoap, [ices](https://www.icecast.org/ices/), or for example a DJ-software running on your local PC) which creates an audio stream (Ogg Vorbis or MP3); - the streaming media server ([Icecast](http://www.icecast.org), [HLS](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) (via a HTTP server), ...) which relays several streams from their sources to their listeners; - the media player (iTunes, VLC, a web browser, ...) which gets the audio stream from the streaming media server and plays it to the listener's speakers. ![Internet radio toolchain](/assets/img/schema-webradio-inkscape.png) The stream is always passed from the stream generator to the server, whether or not there are listeners. It is then sent by the server to every listener. The more listeners you have, the more bandwidth you need. If you use Icecast, you can broadcast more than one audio feed using the same server. Each audio feed or stream is identified by its "mount point" on the server. If you connect to the `foo.ogg` mount point, the URL of your stream will be [http://localhost:8000/foo.ogg](http://localhost:8000/foo.ogg) -- assuming that your Icecast is on localhost on port 8000. If you need further information on this you might want to read Icecast's [documentation](http://www.icecast.org). A proper setup of a streaming server is required for running Liquidsoap. Now, let's create an audio stream. ## Starting to use Liquidsoap We assume that you have a fully installed Liquidsoap. In particular the library `stdlib.liq` and its accompanying scripts should have been installed, otherwise Liquidsoap won't know the operators which have been defined there. ### Sources A stream is built with Liquidsoap by using or creating sources. A source is a media stream containing audio and/or video, track marks and metadata. In the following picture we represent a stream which has at least three tracks (one of which starts before the snapshot), and a few metadata packets (notice that they do not necessarily coincide with new tracks). ![A stream](/assets/img/stream.png) Liquidsoap provides many functions for creating sources from scratch (e.g. `playlist`), and also for creating complex sources by putting together simpler ones (e.g. `switch` in the following example). Eventually, sources are plugged into outputs (typically named `output.*`) which continuously pull the source's content and output it to speakers, to a file, to a streaming server, etc. These outputs what brings life into your sources. ### That source is fallible! A couple of things can go wrong in your streaming system. In Liquidsoap, we say that a source is _infallible_ if it is always available. Otherwise, it is _fallible_, meaning that something could go wrong and the source would not be available. By default, an output requires that its input source is infallible, otherwise it complains that "That source is fallible!" For example, a normal `playlist` is fallible. Firstly, because it could contain only invalid files, or at least spend too much time on invalid files to be able to prepare a valid one on time. Moreover, a playlist could contain remote files, which may not be accessible quickly at all times. A queue of user requests is another example of fallible source. Also, if `file.ogg` is a valid local file, then `single("file.ogg")` is an infallible source. When an output complains about its source being fallible, you have to turn it into an infallible one. Many solutions are available. The function `mksafe` takes a source and returns an infallible source, streaming silence when the input stream becomes unavailable. In a radio-like stream, silence is not the preferred solution, and you will probably prefer to `fallback` on an infallible "security" source: ```liquidsoap fallback([your_fallible_source_here, single("failure.ogg")]) ``` Finally, if you do not care about failures, you can pass the parameter `fallible=true` to most outputs (or pass the option `--no-fallible-check` to Liquidsoap). In that case, the output will accept a fallible source, and stop whenever the source fails and restart when it is ready to produce data again. ## One-line expressions Liquidsoap is a scripting language. Many simple setups can be achieved by evaluating one-line expressions. ### Playlists In the first example we'll play a playlist. Let's put a list of audio files in `playlist.pls`: one filename per line, lines starting with a `#` are ignored. You can also put remote files' URLs, if your liquidsoap has [support](help.html#plugins) for the corresponding protocols. Then just run: ```liquidsoap liquidsoap 'output(playlist("playlist.pls"))' ``` Other playlist formats are supported, such as M3U and, depending on your configuration, XSPF. Instead of giving the filename of a playlist, you can also use a directory name, and liquidsoap will recursively look for audio files in it. Depending on your configuration, the output `output` will use AO, Alsa or OSS, or won't do anything if you do not have support for these libs. In that case, the next example is for you. ### Streaming out to a server **Note:** in the following, we assume that you have installed the following optional dependencies: - `cry` for icecast output - `vorbis` for ogg/vorbis encoding - `ffmpeg` for ffmpeg encoding Liquidsoap is capable of playing audio on your speakers, but it can also send audio to a streaming server such as Icecast or Shoutcast. One instance of liquidsoap can stream one audio feed in many formats (and even many audio feeds in many formats!). You may already have an Icecast server. Otherwise you can install and configure your own Icecast server. The configuration typically consists in setting the admin and source passwords, in `/etc/icecast2/icecast.xml`. These passwords should really be changed if your server is visible from the hostile internet, unless you want people to kick your source as admins, or add their own source and steal your bandwidth. We are now going to send an audio stream, encoded as Ogg Vorbis, to an Icecast server: ```liquidsoap liquidsoap \ 'output.icecast(%vorbis, host = "localhost", port = 8000, password = "hackme", mount = "liq.ogg", mksafe(playlist("playlist.m3u")))' ``` The main difference with the previous is that we used `output.icecast` instead of `output`. The second difference is the use of the `mksafe` which turns your fallible playlist source into an infallible source. If you want to use HLS instead for streaming, you can do: ```liquidsoap liquidsoap \ 'output.file.hls( "/path/to/hls/directory", [("aac", %ffmpeg( format="mpegts", %audio(codec="aac", b="128k") ))], mksafe(playlist("playlist.m3u")))' ``` Once started, this will place all the files required for HLS stream into the local path `"/path/to/hls/directory"` which you can then server over HTTP. The HLS output has many interesting options, including callbacks to upload its files and more. See the [HLS Output](hls_output.html) page for more details. ### Input from another streaming server Liquidsoap can use another stream as an audio source. This may be useful if you do some live shows. ```liquidsoap liquidsoap \ 'output(input.http("https://icecast.radiofrance.fr/fip-hifi.aac"))' ``` ### Input from the soundcard If you're lucky and have a working ALSA support, try one of these... but beware that ALSA may not work out of the box. ```liquidsoap liquidsoap 'output.alsa(input.alsa())' ``` ```liquidsoap liquidsoap 'output.alsa(input.alsa())' ``` ### Other examples You can play with many more examples. Here are a few more. To build your own, lookup the [API documentation](reference.html) to check what functions are available, and what parameters they accept. ```liquidsoap # Listen to your playlist, but normalize the volume liquidsoap 'output(normalize(playlist("playlist_file")))' ``` ```liquidsoap # ... same, but also add smart cross-fading liquidsoap 'output(crossfade( normalize(playlist("playlist_file"))))' ``` ## Script files We have seen how to create a very basic stream using one-line expressions. If you need something a little bit more complicated, they will prove uneasy to manage. In order to make your code more readable, you can write it down to a file, named with the extension `.liq` (eg: `myscript.liq`). To run the script: ```liquidsoap liquidsoap myscript.liq ``` On UNIX, you can also put `#!/path/to/your/liquidsoap` as the first line of your script ("shebang"). Don't forget to make the file executable: ``` chmod u+x myscript.liq ``` Then you'll be able to run it like this: ``` ./myscript.liq ``` Usually, the path of the liquidsoap executable is `/usr/bin/liquidsoap`, and we'll use this in the following. ## A simple radio In this section, we build a basic radio station that plays songs randomly chosen from a playlist, adds a few jingles (more or less one every four songs), and output an Ogg Vorbis stream to an Icecast server. Before reading the code of the corresponding liquidsoap script, it might be useful to visualize the streaming process with the following tree-like diagram. The idea is that the audio streams flows through this diagram, following the arrows. In this case the nodes (`fallback` and `random`) select one of the incoming streams and relay it. The final node `output.icecast` is an output: it actively pulls the data out of the graph and sends it to the world. ![Graph for 'basic-radio.liq'](/assets/img/basic-radio-graph.png) ```{.liquidsoap include="basic-radio.liq"} ``` ## What's next? You can first have a look at a [more complex example](complete_case.html). There is also a second tutorial about [advanced techniques](advanced.html). You should definitely learn [how to get help](help.html). If you know enough liquidsoap for your use, you'll only need to refer to the [scripting reference](reference.html), or see the [cookbook](cookbook.html). At some point, you might read more about Liquidsoap's [scripting language](language.html). For a better understanding of liquidsoap, it is also useful to read a bit about the notions of [sources](sources.html) and [requests](requests.html). �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/radiopi.md�������������������������������������������������������������0000664�0000000�0000000�00000004163�15132732333�0020067�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# RadioPi [RadioPi](http://www.radiopi.org) is the web radio of the ECP (Ecole Centrale de Paris). RadioPi runs many channels. There are topical channels (Reggae, Hip-Hop, Jazz, ...). On top of that, they periodically broadcast live shows, which are relayed on all channels. We met a RadioPi manager right after having released Liquidsoap 0.2.0, and he was seduced by the system. They needed quite complex features, which they were at that time fulfilling using dirty tricks, loads of obfuscated scripts. Using Liquidsoap now allow them to do all they want in an integrated way, but also provided new features. ### The migration process Quite easy actually. They used to have many instances Ices2, each of these calling a Perl script to get the next song. Other scripts were used for switching channels to live shows. Now they have this single Liquidsoap script, no more. It calls external scripts to interact with their web-based song scheduling system. And they won new features: blank detection and distributed encoding. The first machine gets its files from a ftp server opened on the second machine. Liquidsoap handles download automatically. Each file is given by an external script, `radiopilote-getnext`, whose answer looks as follows (except that it's on a single line): ``` annotate:file_id="3541",length="400.613877551",\ type="chansons",title="John Holt - Holigan",\ artist="RadioPi - Canal reggae",\ album="Studio One SeleKta! - Album Studio 1 12",\ canal="reggae":ftp://***:***@host/files/3541.mp3 ``` Note that we use annotate to pass some variables to liquidsoap... ```{.liquidsoap include="radiopi.liq"} ``` The other machine has a similar configuration except that files are local, but this is exactly the same for liquidsoap ! Using harbor, the live connects directly to liquidsoap, using port `8000` (icecast runs on port `8080`). Then, liquidsoap starts a relay to the other encoder, and both switch their channels to the new live. Additionally, a file output is started upon live connection, in order to backup the stream. You could also add a relay to icecast in order to manually check what's received by the harbor. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/reference-header.md����������������������������������������������������0000664�0000000�0000000�00000001552�15132732333�0021623�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Liquidsoap scripting language reference The **Source / ...** categories contain all functions that return sources. The **Input** functions are those which build elementary sources (playing files, synthesizing sound, etc.). The **Output** functions are those which take a source and register it for being streamed to the outside (file, soundcard, audio server, etc.). The **Visualization** functions are experimental ones that let you visualize in real-time some aspects of the audio stream. The **Sound Processing** functions are those which basically work on the source as a continuous audio stream. They would typically be mixers of streams, audio effects or analysis. Finally, **Track Processing** functions are basically all others, often having a behaviour that depends on or affects the extra information that liquidsoap puts in streams: track limits and metadata. ������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/release-assets.md������������������������������������������������������0000664�0000000�0000000�00000001101�15132732333�0021345�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������This release provides liquidsoap assets before they are published as a new versioned release. You can use it to install the latest stable code before it is published and test/prepare your production environment for it. Rolling releases can also be useful for us to quickly detect and report bugs before the final published release! Assets listed in this release will never be modified or deleted. Feel free to use them for packaging or distribution purposes. For more details about our release process, please checkout https://github.com/savonet/liquidsoap#release-details ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/request_sources.md�����������������������������������������������������0000664�0000000�0000000�00000007775�15132732333�0021707�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Playing files is the most common way to build an audio stream. In liquidsoap, files are accessed through [requests](requests.html), which combine the retrieval of a possibly remote file, and its decoding. Liquidsoap provides several operators for playing requests: `single`, `playlist` and `playlist.safe`, `request.dynamic.list`, `request.queue` and `request.equeue`. In a few cases (`single` with a local file, or `playlist.safe`) a request operator will know that it can always get a ready request instantaneously. It will then be [infallible](sources.html). Otherwise, it will have a queue of requests ready to be played (local files with a valid content), and will feed this queue in the background. This process is described here. ## Common parameters Queued request sources maintain an _estimated remaining time_, and trigger a new request resolution when this remaining time goes below their `length` parameter. The estimation is based on the duration of files prepared in the queue, and the estimated remaining time in the currently playing file. Precise file durations being expensive to compute, they are not forced: if a duration is provided in the metadata it shall be used, otherwise the `default_length` is assumed. For example, with the default 10 seconds of wanted queue length, the operator will only prepare a new file 10 seconds before the end of the current one. Up to liquidsoap 0.9.1, the estimated remaining time in the current track was not taken into account. With this behavior, each request-based source would keep at least one song in queue, which was sometimes inconvenient. This behavior can be restored by passing `conservative=true`, which is useful in some cases: it helps to ensure that a song will be ready in case of skip; generally, it prepares things more in advance, which is good when resolution is long (_e.g._, heavily loaded server, remote files). ## Request.dynamic This source takes a custom function for creating its new requests. This function, of type `()->request`, can for example call an external program. To create the request, the function will have to use the `request.create` function which has type `(string,?indicators:[string])`. The first string is the initial URI of the request, which is resolved to get an audio file. The second argument can be used to directly specify the first row of URIs (see the page about [requests](requests.html) for more details), in which case the initial URI is just here for naming, and the resolving process will try your list of indicators one by one until a valid audio file is obtained. An example that takes the output of an external script as an URI to create a new request can be: ```liquidsoap def my_request_function() = # Get the first line of my external process result = list.hd(default="", process.read.lines("my_script my_params")) # Create and return a request using this result [request.create(result)] end # Create the source s = request.dynamic.list(my_request_function) ``` ## Queues Liquidsoap features two sources which provide request queues that can be directly manipulated by the user, via the server interface: `request.queue` and `request.equeue`. The former is a queued source where you can only push new requests, while the later can be edited. Both operators actually deal with two queues: _primary_ and _secondary_ queues. The secondary queue is user-controlled. The primary queue is the one that all queued request sources have, its behavior is the same as described above, and it cannot be changed in any way by the user. Requests added to the secondary queue sit there until the feeding process gets them and attempts to prepare them and put them in the primary queue. You can set how many requests will be in that primary queue by tweaking the common parameters of all queued request sources. The two sources are controlled via the [command server](advanced.html). They both feature commands for looking up the queues, queuing new requests, and the `equeue` operator also allows removal and exchange of requests in the secondary queue. ���liquidsoap-2.4.2/doc/content/requests.md������������������������������������������������������������0000664�0000000�0000000�00000012754�15132732333�0020320�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Understanding Requests When you ask Liquidsoap to play something, it’s easy to imagine that the system just grabs your file or stream and starts playing it. In reality, there’s a small but important process that happens first — one that makes Liquidsoap flexible and able to handle a wide variety of media sources. This page explains how **requests** work, how Liquidsoap resolves them, and how the **protocol system** fits in. The goal is to give you a clear picture of what’s going on without overwhelming you, so you can better understand what happens behind the scenes. ## What’s a Request? 🎫 A **request** in Liquidsoap is simply a way of saying: > “Here’s something to play — figure out where it is and how to handle it.” You create a request with [`request.create`](reference.html#request.create), passing a **URI**. A URI can be: - A file path: `"/music/song.mp3"` 📁 - A URL: `"https://example.com/song.ogg"` 🌐 - A custom protocol: `"media:12345"` 🛠 A request on its own doesn’t play anything — it needs to be used by a **request-based source** such as `playlist`, `request.dynamic`, or `single`. That source will try to resolve and decode it before playback. ## The Request Lifecycle 🔄 Here’s the general path from URI to playback: 1. **Request creation** — you pass a URI to Liquidsoap. 2. **Protocol resolution** — Liquidsoap figures out how to handle that URI. 3. **Chained resolution** — some protocols return another URI, which might trigger more resolutions. 4. **Local file reached** — the resolution process ends with a file on disk. 5. **Decoder selection** — the file is matched with a decoder that can read its contents. 6. **Playback** — the decoded stream is sent to the source for output. 🔊 💡 **Note:** A request can resolve to a local file but still be unplayable — for example, if the file’s format doesn’t match what the source expects. ⚠️ **Warning:** If you want to use `cue_in` and `cue_out` metadata, they need to be available to the selected decoder! This means that they should be set during protocol resolution using the `annotate:` protocol. See below for more details! ## Protocols: How URIs Are Resolved 🗂 A **protocol** in Liquidsoap is a handler for a particular kind of URI. The general format is: ``` protocol_name:arguments ``` When a request’s URI matches a known protocol name: - The protocol runs and returns either another URI or a local file. - If it returns another URI, resolution continues. - This process can repeat several times until a local file is found. **Example:** ``` annotate:title="My Song":http://example.com/song.mp3 ``` Here’s what happens: 1. The `annotate` protocol adds metadata 🏷. 2. The `http` protocol downloads the file 🌐. 3. The system now has a local file and can look for a decoder. ## The Role of External Decoders 📦 Once a local file is available, Liquidsoap checks for an **external decoder** based on its MIME type or file extension. These decoders can be optionally configured in your script to extend Liquidsoap's supported file formats. If an external decoder is found, it’s used to read the file and produce the final, playable file. ## Creating Your Own Protocols 🛠 Liquidsoap lets you define custom protocol handlers. For example, you could create a `media:` protocol like: ``` media:123 ``` that looks up a database record and returns the corresponding file path. This can make working with large or structured media collections much easier. ## The `annotate:` Protocol 🏷 The built-in `annotate:` protocol lets you attach metadata directly to a request without changing the file. The syntax is: ``` annotate:key1="value1",key2="value2":next_uri ``` **Example:** ``` annotate:artist="Liquid Artist",cue_in="3.",cue_out="23.":/music/song.mp3 ``` ### Cue Points ⏩ Two useful metadata keys are: - `cue_in` — skip the first _N_ seconds of playback. - `cue_out` — stop after _N_ seconds from the start (including any skipped portion). In the example above: - `cue_in="3."` skips the first 3 seconds. - `cue_out="23."` stops playback at second 23, so only 20 seconds are heard. This is particularly useful with pre-processed tracks where intros or outros have been identified. These metadata need to be provided at the request level so that they are available to the decoders! If you add them later in the chain, typically using `metadata.map`, they will not be seen by the decoder and not acted upon. ## Other Built-in Protocols - **`autocue`** 🎯 — Automatically detects cue points and fades for smooth transitions. See the [autocue](autocue.html) documentation for details. - **`http` / `https`** 🌐 — Handles web-based media by downloading it before playback, just like any other protocol. - **`say`** 🗣 — Generates speech from text when configured, useful for automated announcements or DJ-style breaks. ## Why It’s Useful 💡 Understanding requests and protocols isn’t just theoretical — it gives you practical tools to: - Work with many different kinds of media sources. - Build custom workflows for databases, APIs, or generated content. - Control playback behavior with metadata, without touching the original files. It’s one of the features that makes Liquidsoap adaptable to many streaming setups. ## Next Steps 📚 To explore further: - Read the [Protocol API documentation](protocols-presentation.html) to learn how to implement your own. - Experiment with `annotate:` in your playlists. - Try chaining protocols to create more complex behavior. ��������������������liquidsoap-2.4.2/doc/content/rolling-release.md�����������������������������������������������������0000664�0000000�0000000�00000001220�15132732333�0021513�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������This release provides liquidsoap assets before they are published as a new versioned release. You can use it to install the latest stable code before it is published and test/prepare your production environment for it. Rolling releases can also be useful for us to quickly detect and report bugs before the final published release! ⚠️ **Warning** ⚠️ Assets in this release will be deleted. If you are looking for permanent links to release assets, please head over to https://github.com/savonet/liquidsoap-release-assets/releases For more details about our release process, please checkout https://github.com/savonet/liquidsoap#release-details ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/scheduling.md����������������������������������������������������������0000664�0000000�0000000�00000010064�15132732333�0020562�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Scheduling Tasks in Liquidsoap Liquidsoap includes a lightweight scheduler that lets you run code at specific times or on a recurring basis. This is useful for automating things like playing a file every hour, announcing the top of the hour, or triggering a command at a specific time. Scheduling in Liquidsoap works through **threads** — simple tasks that run in the background without interrupting your media streams. These threads are managed by the scheduler and executed as needed. They’re not operating system threads, just scheduled functions. There are four main APIs available for scheduling: - `thread.run` – run a task on a regular interval - `thread.when` – run a task when a time-based condition becomes true - `cron.add` / `cron.remove` – schedule tasks using familiar cron syntax - `thread.run.recurrent` – an advanced interface for custom scheduling This page walks you through the first three, with examples to get you started. If you are looking for a more in-depth example of how to use the scheduler, you can refer to our blog post [Precise scheduling of tracks](https://www.liquidsoap.info/blog/2023-03-25-precise-scheduling-of-tracks/) ## `thread.run`: Simple Repeating Tasks Use `thread.run` when you want to run a task repeatedly every N seconds. This is the most straightforward scheduling method. ### Example: Log a message every 10 minutes ```{.liquidsoap include="scheduling_simple.liq"} ``` This task will run every 600 seconds (10 minutes), logging a message. You can schedule any function here — such as sending metadata, modifying a source, or queueing a track. ### Example: Play a file every hour ```{.liquidsoap include="scheduling_queue.liq" from="BEGIN" to="END"} ``` In this case, we assume that `request_queue` is a `request.queue` source used elsewhere in your script. ## `thread.when`: Run at a Specific Time To schedule a task at a specific time, use `thread.when`. It takes a [time predicate](language.html#time-predicates) — a Liquidsoap-specific language construct that returns `true` when the current time matches the given interval or time. ### Example: Run a task at 9:00 AM ```{.liquidsoap include="scheduling_9am.liq"} ``` This function is ran every time the predicates returns `true`, which should be during the 9th hour of the morning (hours are in 24h format). You can refer to the `thread.when` and `predicate.activates` documentation for more details about the implementation. ### Example: Queue a track at midnight ```{.liquidsoap include="scheduling_queue_midnight.liq" from="BEGIN" to="END"} ``` ## `cron.add` and `cron.remove`: Cron-style Scheduling If you’re used to cron syntax, you can use `cron.add` to schedule tasks using a familiar string format. ```{.liquidsoap include="cron_add.liq"} ``` This example runs the task every day at 12:00 PM. If needed, the function returns a unique identifier for the task, which you can use to remove it later: ```{.liquidsoap include="cron_id.liq"} ``` ### Explicit IDs You can also pass an explicit ID: ```{.liquidsoap include="cron_id_arg.liq"} ``` If the ID is already registered, Liquidsoap will raise an error. This is useful for keeping track of scheduled tasks in complex scripts using meaningful IDs. ### Removing a Cron Task To remove a task, use `cron.remove` with its ID: ```{.liquidsoap include="cron_remove.liq"} ``` ### Cron Syntax Recap Cron strings follow the standard format: ``` minute hour day-of-month month day-of-week ``` Examples: - `"0 0 * * *"` – every day at midnight - `"*/5 * * * *"` – every 5 minutes - `"15 14 * * 1-5"` – weekdays at 2:15 PM The implementation also supports the following shorthands: `@annually`, `@yearly`, `@daily`, `@hourly`, `@monthly` and `@weekly`. ## Advanced: `thread.run.recurrent` For more complex scheduling needs, advanced users may use `thread.run.recurrent`. It allows full control over how a task is rescheduled after each execution, making it possible to implement dynamic or irregular schedules. Most users won’t need this API, but it’s available if `thread.run` or `cron.add` don’t fit your needs. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/script_loading.md������������������������������������������������������0000664�0000000�0000000�00000002457�15132732333�0021445�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Script loading When you run liquidsoap for streaming, the command line has the following form: ``` $ liquidsoap script_or_expr_1 ... script_or_expr_N ``` This allows you to ask liquidsoap to load definition and settings from some scripts so that the become available when processing the next ones. For example you can store your passwords by defined the variable `xxx` in `secret.liq`, and then refer to that variable in your main script `main.liq`. You would then run `liquidsoap secret.liq main.liq`. If you ever need to communicate `main.liq` there won't be any risk of divulgating your password. When available, the variable `liquidsoap.script.path` contains the path of the current script's file and `null` otherwise. ## The pervasive script library In fact, liquidsoap also implicitly loads scripts before those that you specify on the command-line. These scripts are meant to contain standard utilities. Liquidsoap finds them in `LIBDIR/liquidsoap/VERSION` where `LIBDIR` depends on your configuration (it is typically `/usr/local/lib` or `/usr/lib`) and `VERSION` is the version of liquidsoap (_e.g._ `0.3.8` or `svn`). Currently, liquidsoap loads `stdlib.liq` from the library directory, and this file includes some others. You can add your personal standard library in that directory if you find it useful. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/seek.md����������������������������������������������������������������0000664�0000000�0000000�00000003606�15132732333�0017370�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Seeking in liquidsoap Starting with Liquidsoap `1.0.0-beta2`, it is now possible to seek within sources! Not all sources support seeking though: currently, they are mostly file-based sources such as `request.queue`, `playlist`, `request.dynamic.list` etc.. The basic function to seek within a source is `source.seek`. It has the following type: ``` (source('a),float)->float ``` The parameters are: - The source to seek. - The duration in seconds to seek from current position. The function returns the duration actually seeked. Please note that seeking is done to a position relative to the _current_ position. For instance, `source.seek(s,3.)` will seek 3 seconds forward in source `s` and `source.seek(s,(-4.))` will seek 4 seconds backward. Since seeking is currently only supported by request-based sources, it is recommended to hook the function as close as possible to the original source. Here is an example that implements a server/telnet seek function: ```{.liquidsoap include="seek-telnet.liq"} ``` ## Cue points File-based sources support cue-points to cut the beginning and end of tracks The values of cue-in and cue-out points are given in absolute position through the source's metadata. For instance, the following source will cue-in at 10 seconds and cue-out at 45 seconds on all its tracks: ```liquidsoap s = playlist(prefix="annotate:liq_cue_in=\"10.\",liq_cue_out=\"45\":", "/path/to/music") ``` As in the above example, you may use the `annotate` protocol to pass custom cue points along with the files passed to Liquidsoap. This is particularly useful in combination with `request.dynamic` as an external script can build-up the appropriate URI, including cue-points, based on information from your own scheduling back-end. Alternatively, you may use `metadata.map` to add those metadata. The operator `metadata.map` supports seeking and passes it to its underlying source. ��������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/server.md��������������������������������������������������������������0000664�0000000�0000000�00000021064�15132732333�0017745�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Interaction with the server Liquidsoap starts with one or several scripts as its configuration, and then streams forever if everything goes well. Once started, you can still interact with it by means of the _server_. The server allows you to run commands. Some are general and always available, some belong to a specific operator. For example the `request.queue()` instances register commands to enqueue new requests, the outputs register commands to start or stop the outputting, display the last ten metadata chunks, etc. The protocol of the server is a simple human-readable one. Currently it does not have any kind of authentication and permissions. It is currently available via two media: TCP and Unix sockets. The TCP socket provides a simple telnet-like interface, available only on the local host by default. The Unix socket interface (_cf._ the `server.socket` setting) is through some sort of virtual file. This is more constraining, which allows one to restrict the use of the socket to some privileged users. You can find more details on how to configure the server in the [documentation](help.html#settings) of the settings key `server`, in particular `server.telnet` for the TCP interface and `server.socket` for the Unix interface. Liquidsoap also embeds some [documentation](help.html#server) about the available server commands. ### Using telnet Now, we shall simply enable the Telnet interface to the server, by setting `settings.server.telnet := true` or simply passing the `-t` option on the command-line. In a [complete case analysis](complete_case.html) we set up a `request.queue()` instance to play user requests. It had the identifier `"queue"`. We are now going to interact via the server to push requests into that queue: ``` dbaelde@selassie:~$ telnet localhost 1234 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. queue.push /path/to/some/file.ogg 5 END request.metadata 5 [...] END queue.push http://remote/audio.ogg 6 END request.trace 6 [...see if the download started/succeeded...] END exit ``` Of course, the server isn't very user-friendly. But it is easy to write scripts to interact with Liquidsoap in that way, to implement a website or an IRC interface to your radio. However, this sort of tool is often bound to a specific usage, so we have not released any of ours. ### Web interface Another simple way to test the telnet server consists in using the ```liquidsoap server.harbor() ``` server.harbor api: https://www.liquidsoap.info/doc-2.0.0/reference-extras.html#server.harbor command which will start a web interface accessible at <http://localhost:8000/telnet> providing an emulation of a telnet. ## Interactive variables Sometimes it is useful to control a variable using telnet. A simple way to achieve this is to use the `interactive.float` function. For instance, in order to dynamically the volume of a source: ```liquidsoap # Register a telnet variable named volume with 1 as initial value v = interactive.float("volume", 1.) # Change the volume accordingly source = amplify(v, source) ``` The first line registers the variable volume on the telnet. Its value can be changed using the telnet command ```liquidsoap var.set volume = 0.5 ``` and it can be retrieved using ```liquidsoap var.get volume ``` Similarly, we can switch between two tracks using `interactive.bool` and `switch` as follows: ```liquidsoap # Activate the telnet server settings.server.telnet := true # The two sources s1 = playlist("~/Music") s2 = sine() # Create an interactive boolean b = interactive.bool("button", true) # Switch between the tracks depending on the boolean s = switch(track_sensitive=false,[(b,s1), ({true},s2)]) # Output the result output.pulseaudio(s) ``` By default the source s1 is played. To switch to s2, you can connect on the telnet server and type `var.set button = false`. ### Web interface A nice web interface can be obtained by running ```liquidsoap interactive.harbor() ``` interactive.harbor api: https://www.liquidsoap.info/doc-2.0.0/reference.html#interactive.harbor after all interactive variables have been defined. This will start a web server accessible at <http://localhost:8000/interactive> on which you can easily change the values for the interactive variables. ### Persistency By default, interactive variables are not _persistent_, which means that their values are lost if you restart the script. This can be changed by running the command ```liquidsoap interactive.persistent("vars.json") ``` after all the interactive variables have been defined. This will store the values of all the interactive variables in the file `vars.json` (in JSON format) whenever you modify them, and reload them next time your run your script. This can be very handy for setting parameters for sound effects for instance. ## Securing the server The command server provided by liquidsoap is very convenient for manipulating a running instance of Liquidsoap. However, no authentication mechanism is provided. The telnet server has no authentication and listens by default on the localhost (`127.0.0.1`) network interface, which means that it is accessible to any logged user on the machine. Many users have expressed interest into setting up a secured access to the command server, using for instance user and password information. While we understand and share this need, we do not believe this is a task that lies into Liquidsoap's scope. An authentication mechanism is not something that should be implemented naively. Being SSH, HTTP login or any other mechanism, all these methods have been, at some point, exposed to security issues. Thus, implementing our own secure access would require a constant care about possible security issues. Rather than doing our own home-made secure access, we believe that our users should be able to define their own secure access to the command server, taking advantage of a mainstream authentication mechanism, for instance HTTP or SSH login. In order to give an example of this approach, we show here how to create a SSH access to the command server: we create a SSH user that, when logging through SSH, has only access to the command server. First, we enable the unix socket for the command server in Liquidsoap: ```liquidsoap settings.server.socket := true settings.server.socket.path := "/path/to/socket" ``` When started, liquidsoap will create a socket file `/path/to/socket` that can be used to interact with the command server. For instance, if your user has read and write rights on the socket file, you can do ```liquidsoap socat /path/to/socket - ``` The interface is then exactly the same has for the telnet server. We define now a new ``shell''. This shell is in fact the invocation of the socat command. Thus, we create a `/usr/local/bin/liq_shell` file with the following content: ```bash #!/bin/sh # We test if the file is a socket, readable and writable. if [ -S /path/to/socket ] && [ -w /path/to/socket ] && \ [ -r /path/to/socket ]; then socat /path/to/socket - else # If not, we exit.. exit 1 fi ``` We set this file as executable, and we add it in the list of shells in `/etc/shells`. Now, we create a user with the `liq_shell` as its shell: ``` adduser --shell /usr/local/bin/liq_shell liq-user ``` You also need to make sure that `liq-user` has read and write rights on the socket file. Finally, when logging through ssh with `liq-user`, we get: ``` 11:27 toots@leonard % ssh liq-user@localhost liq-user@localhost's password: Linux leonard 2.6.32-4-amd64 #1 SMP Mon Apr 5 21:14:10 UTC 2010 x86_64 The programs included with the Debian GNU/Linux system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. Last login: Tue Oct 5 11:26:52 2010 from localhost help Available commands: (...) ├─ exit ├─ help [<command>] ├─ list ├─ quit ├─ request.alive ├─ request.all ├─ request.metadata <rid> ├─ request.on_air ├─ request.resolving ├─ request.trace <rid> ├─ uptime ├─ var.get <variable> ├─ var.list ├─ var.set <variable> = <value> └─ version Type "help <command>" for more information. END exit Bye! END Connection to localhost closed. ``` This is an example of how you can use an existing secure access to secure the access to liquidsoap's command server. This way, you make sure that you are using a mainstream secure application, here SSH. This example may be adapted similarly to use an online HTTP login mechanism, which is probably the most comment type of mechanism intended for the command line server. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/shoutcast.md�����������������������������������������������������������0000664�0000000�0000000�00000002565�15132732333�0020461�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Streaming to Shoutcast Although Liquidsoap is primarily aimed at streaming to Icecast servers (that provide much more features than Shoutcast), it is also able to stream to Shoutcast. ## Shoutcast output Shoutcast server accept streams encoded with the MP3 or AAC/AAC+ codec. You to compile Liquidsoap with `lame` support, so it can encode in MP3. Liquidsoap also has support for AAC+ encoding using FDK-AAC or using an [external encoder](external_encoders.html). The recommended format is MP3. Shoutcast output are done using the `output.shoutcast` operator with the appropriate parameters. An example is: ```{.liquidsoap include="shoutcast.liq"} ``` As usual, `liquidsoap -h output.shoutcast` gives you the full list of options for this operator. ## Shoutcast as relay A side note for those of you who feel they ``need'' to use Shoutcast for non-technical reasons (such as their stream directory service...): you can still benefit from Icecast's power by streaming to an Icecast server, and then relaying it through a shoutcast server. In order to do that, you have to alias the root mountpoint ("`/`") to your MP3 mountpoint in your icecast server configuration, like this: ``` <alias source="/" dest="/mystream.mp3" /> ``` Be careful that icecast often aliases the status page (`/status.xsl`) with the `/`. In this case, comment out the status page alias before inserting yours. �������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/sources.md�������������������������������������������������������������0000664�0000000�0000000�00000010640�15132732333�0020120�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Understanding Sources in Liquidsoap 🎧 When you write a Liquidsoap script, you're not just stringing commands together — you're **building a streaming system**. At the heart of that system is a powerful and abstract concept: the **source**. ## What Is a Source? A **source** is more than just a stream of audio or video — it’s the fundamental unit of streaming in Liquidsoap. Think of it like a little engine that knows how to produce media, frame by frame. Each source emits: - **Frames**: Small chunks of media samples. - **Metadata**: Information like artist, title, etc. - **Track marks**: Signals when a track starts or ends. At any moment, Liquidsoap can ask a source: _"Give me the next frame."_ The source responds with a small packet of sound (or video), plus any metadata if available. This model lets you combine sources, decorate them, filter them, or choose between them — all in a script. ## Building Streams from Sources 🧱 The Liquidsoap language gives you **functions and operators** to build sources: - Some functions produce **elementary sources** (e.g. reading from a file or microphone). - Others **combine or transform** sources (e.g. playlists, fallbacks, crossfades, etc.). You can build complex behaviors from simple building blocks. For example: ```liquidsoap radio = output.icecast( %vorbis, mount="test.ogg", random([ jingle, fallback([playlist1, playlist2, playlist3]) ]) ) ``` Here’s what’s happening: 1. The `output.icecast` sends audio to an Icecast server. 2. It gets that audio from a `random` source. 3. `random` picks between `jingle` or a `fallback` playlist group. 4. `fallback` plays from the first available playlist in order. Every time the system needs audio, this little pipeline wakes up and produces a frame of data. ## Sources Are Not Always Reliable (And That’s Okay) ⚠️ What happens if a playlist runs out of tracks? Or a file fails to load? In Liquidsoap, we say that a source is either: - **Infallible**: Guaranteed to always produce data. - **Fallible**: Might fail at some point. To keep your stream running smoothly, your output expects an **infallible** source. That means somewhere in your source graph, you need a fallback plan. For example: - Add a static file with `single()` at the end of a `fallback()`. - Use `mksafe()` to replace failures with silence. Liquidsoap can **check the liveness** of your source graph at startup and warn you if it detects possible failure. That’s a safety net to ensure your stream doesn’t unexpectedly stop. Want to allow failures? You can pass `fallible=true` to most output operators — but do so only if you’re okay with your stream pausing and restarting when necessary. ## How Streaming Actually Happens 🔁 Once your script defines a set of sources and outputs, how does Liquidsoap keep the data flowing? It all comes down to a **clock**. ⏰ Each source is assigned to a clock. During each clock tick (i.e. iteration), Liquidsoap: 1. Asks the output to send a frame of data. 2. The output asks its underlying source. 3. That source might ask other sources. 4. This chain continues until some elementary sources produce real data. This forms a **streaming loop**, and it's central to how Liquidsoap runs. ## Active Sources 🔌 Most sources are passive: they only do work when asked. But some are **active** — they need to run even if no one is listening. For example: - `input.harbor`: Accepts live streams from the network. - `input.alsa`: Listens to a microphone. These sources are **always receiving data**, so they must process it continually or risk overflowing. Even if you don’t route them to an output, Liquidsoap keeps them alive. ## Don’t Block the Stream 🛑 The streaming loop must stay fast and responsive. So, **expensive tasks are offloaded to background threads**: - Downloading remote files - Reloading playlists - Checking metadata - Polling URLs This means: - Don’t rely on remote files that stream directly from NFS or the web. - All remote files are pre-downloaded into a temp file before playback begins. Keep the streaming loop light and snappy — it’s the heartbeat of your system. ## What’s Next? Now that you understand what sources are and how they work, you’ve unlocked the foundation of Liquidsoap. 🎉 Want to go deeper? - Explore the [scripting API reference](reference.html) - Learn about [clocks](clocks.html) - Experiment with your own custom source graphs ������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/split-cue.md�����������������������������������������������������������0000664�0000000�0000000�00000000567�15132732333�0020351�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Split and re-encode a CUE sheet. CUE sheets are sometimes distributed along with a single audio file containing a whole CD. Liquidsoap can parse CUE sheets as playlists and use them in your request-based sources. Here's for instance an example of a simple code to split a CUE sheet into several mp3 files with `id3v2` tags: ```{.liquidsoap include="split-cue.liq"} ``` �����������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/stereotool.md����������������������������������������������������������0000664�0000000�0000000�00000006114�15132732333�0020635�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Stereotool support Starting with version `2.2.0`, liquidsoap supports the shared library distributed by [Thimeo Audio Technology](https://www.thimeo.com/stereo-tool/) using the `stereotool` operator (and `track.audio.stereotool` for the low-level, track-specific equivalent). This feature is enabled in all release builds of liquidsoap starting with `rolling-release-v2.2.x` and should be enabled if you compile liquidsoap with the optional `ctypes-foreign` opam module installed. The operator can replace the use of the stereotool binary in your script and offers multiple benefits. In particular, it has a **very low latency** compared to using the binary and also operates synchronously. The operator should be quite easy to use. Here's an example: ```liquidsoap # Define a source s = ... # Apply stereotool to it: s = stereotool( library_file="/path/to/stereotool/shared/lib", license_key="my_license_key", preset="/path/to/preset/file", s ) ``` That's it! You can apply as many `stereotool` operators as you wish and at any stage in the script, thanks to its synchronous nature. However, a current limitation is that **the processed audio signal is slightly delayed**. This is because the operator has an internal processing buffer. We do plan on delaying metadata and track marks to match this latency but this has not yet been implemented and will probably have to wait for the `2.3.x` release cycle. This means that, until then, track switches and metadata updates might happen slightly earlier than the corresponding signal. We're talking about `50ms` to `100ms` earlier, though, so that might not be a super big deal. For the same reason, the source returned by `stereotool` is an _audio-only_ source. Otherwise, other concurrent tracks such as video and etc would be slightly out of sync. If you need to use the operator in this kind of situation, you might want to use a `ffmpeg` filter to e.g. adjust the video's PTS to match the audio delay. In such case, you can refer to the `latency` method that is available on the source returned by the operator which should indicate the delay to compensate from the processed audio signal. The operator's `preset` parameter has a companion `load_type` parameter that can optionally be used to only load a subset of the preset. You might refer to the upstream documentation if you need to use it. Lastly, `stereotool` is a **proprietary software**. While we actively promote open source, we also want to meet our users where they are and, for a lot of them, this means supporting the sound processing provided by the tool. However, to use it, you will need a license. Using the operator without the proper license will _not_ result in an error in your script but the audio signal might have spoken text and/or beeps added to it. Using the operator with an invalid license will be reported in the logs. You might also use the `valid_license` method available on the source returned by the operator, which returns `false` if the license is invalid. In this case, the `unlincensed_used_features` method returns a string indicating which unlicensed features are being used. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/stream_content.md������������������������������������������������������0000664�0000000�0000000�00000015775�15132732333�0021500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Stream contents In liquidsoap, a stream may contain any number of audio, video and event MIDI channels (though this has not been tested in a while!). As part of the type checking of your script, liquidsoap checks that you make a consistent use of stream contents, and also guesses what kind of stream your script is intended to work on. As with other inferred parameters, you do not necessarily need to read about stream contents typing if you're still learning the ropes of liquidsoap, but you might eventually need to know a little about it. In liquidsoap script language, there are three sorts of objects that rely on stream types: sources, requests and encoding formats. A [source](sources.html) produces a stream, and it is important what kind of stream it produces when composing it with other sources. A [request](requests.html) is an abstract notion of file, often meant to be decoded, and it is useful to know into what kind of stream it is meant to be decoded. Finally, a [format](encoding_formats.html) describes how a stream should be encoded (_e.g._, before output in a file or via icecast), and the stream content is also useful here for the format to make sense. In this page, we explain how liquidsoap uses stream types to guess and check what you're doing. ## Content types Liquidsoap supports various type of content to be produced as the script runs. ### Internal content _internal_ content generally refers to content that the liquidsoap application can produce and manipulate. For audio, the default internal content is `pcm` floats using OCaml native 64-bits float array representations. This is the format that allows the fastest manipulation. For video, the default is a C in-memory arrays of plannar YUV420 data. For users concerned with memory consumption, we also support two additional audio formats, `pcm_s16` and `pcm_f32` using, resp., signed 16-bit integers and 32-bit floating point numbers. These formats may increase CPU usage, however, as we do need to convert back and forth when using them in audio manipulation operators such as `amplify`, `crossfade` and etc. See [this link](memory.html#audio-data-format) for more details. ### Opaque content Liquidsoap also supports content type that are opaque to the application, provided by the `ffmpeg` decoder. There are two: - FFmpeg raw frames, which are decoded plain FFmpeg frames - FFmpeg packets, also referred to as FFmpeg copy content. These are packets of encoded content These type of content are consumed by FFmpeg specific operators and it is possible to convert back and forth if you want to use them with our internal operators. However, their best use-case is to keep them as-is end-to-end to optimize for memory and/or CPU usage. See the [FFmpeg support](ffmpeg.html) doc for more information. ## Global parameters You might have noticed that our description of internal stream contents is missing some information, such as sample rate, video size, etc. Indeed, that information is not part of the stream types, which is local to each source/request/format, but global in liquidsoap. You can change it using the `frame.audio/video.*` settings, shown here with their default values: ```liquidsoap audio.samplerate := 44100 video.frame.width := 320 video.frame.height := 240 video.frame.rate := 25 ``` ## Checking stream contents Checking the consistency of use of stream contents is done as part of type checking. There is not so much to say here, except that you have to read type errors. We present a few examples. For example, if you try to send an ALSA input to a SDL input using `output.sdl(input.alsa())`, you'll get the following: ``` At line 1, char 22-23: this value has type source(audio=pcm('a)) but it should be a subtype of source(video=canvas) ``` It means that a source with a video channel was expected by the SDL output, but the ALSA output can only offer sources producing audio. ## Conversions get a type error on seemingly meaningful code, and you'll wonder how to fix it. Often, it suffices to perform a few explicit conversions. Consider another example involving the SDL output, where we also try to use AO to output the audio content of a video: ```liquidsoap s = single("file.mp4") # Output video here output.file( %ffmpeg(%video(codec="libx264"), "/path/to/video.flv", s ) # Output audio here output.file( %ffmpeg(%audio(codec="aac")) "/path/to/video.aac", s ) ``` This won't work because the first output expects a video-only stream while the second one expected an audio-only stream The solution is to split the stream in two, dropping the irrelevant content: ```liquidsoap s = single("file.mp4") # Output video here output.file( %ffmpeg(%video(codec="libx264"), "/path/to/video.flv", source.drop.audio(s) ) # Output audio here output.file( %ffmpeg(%audio(codec="aac")) "/path/to/video.aac", source.drop.video(s) ) ``` Another conversion is muxing. It is useful to add audio/video channels to a pure video/audio stream. For this, see `source.mux.video` and `source.mux.audio`. ## Type annotations You now have all the tools to write a correct script. But you might still be surprised by what stream content liquidsoap guesses you want to use. This is very important, because even if liquidsoap finds a type for which it accepts to run, it might not run as you intend: a different type might mean a different behavior (not the intended number of audio channels, no video, etc). Before reading on how liquidsoap performs this inference, you can already work your way to the intended type by using type annotations. For example, with `output.alsa(input.alsa())`, you'll see that liquidsoap decides that stereo audio should be used, and consequently the ALSA I/O will be initialized with two channels. If you want to use a different number of channels, for example mono, you can explicitly specify it using: ```liquidsoap output.alsa((input.alsa():source(audio=pcm(mono)))) ``` ## Guessing stream contents When all other methods fail, you might need to understand a little more how liquidsoap guesses what stream contents should be used for each source. First, liquidsoap guesses as much as possible (without making unnecessary assumption) from what's been given in the script. Usually, the outputs pretty much determine what sources should contain. A critical ingredient here is often the [encoding format](encoding_formats.html). For example, in ```liquidsoap output.icecast(%vorbis,mount="some.ogg",s) ``` `%vorbis` has type `format(audio=pcm(stereo))`, hence `s` should have type `source(audio=pcm(stereo))`. This works in more complex examples, when the types are guessed successively for several intermediate operators. After this first phase, it is possible that some contents are still undetermined. For example in `output.alsa(input.alsa())`, any number of audio channels could work, and nothing helps us determine what is intended. At this point, the default numbers of channels are used. They are given by the setting `frame.audio/video/midi.channels` (whose defaults are respectively `2`, `0` and `0`). In our example, stereo audio would be chosen. ���liquidsoap-2.4.2/doc/content/strings_encoding.md����������������������������������������������������0000664�0000000�0000000�00000002354�15132732333�0021777�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# Strings encoding Liquidsoap operates internally using the UTF-8 string encoding. Most strings inside the application are converted to UTF-8 whenever possible. Conversion is done using [camomile](https://github.com/ocaml-community/camomile) automatic string encoding detection. If the conversion fails, the string is kept as-is. The list of encodings used for automatic detection is set via [settings.charset.encodings](settings.html#list-of-encodings-to-try-for-automatic-encoding-detection.) There are some exceptions, however. For instance, filenames and paths are not converted: if your system expects paths to be in a different encoding than UTF-8 then we do need to keep strings representing files and paths in this encoding to prevent errors. In general, you are advised to set the string encoding to UTF-8 on all systems running liquidsoap scripts for consistency and clarity. However, if for some reasons you need to tweak string encoding, these settings can be of use: - `settings.log.recode` and `settings.log.recode.encoding`: set the first one to `true` and the second one to the string encoding you would like log entries to be converted into. - `settings.metadata.recode`: set to `false` to prevent metadata from being converted to UTF-8. ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/video-static.md��������������������������������������������������������0000664�0000000�0000000�00000000674�15132732333�0021036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# A simple video script The other day, I wanted to prepare some videos of my favorite reggae and soul tunes for uploading them to YouTube. My goal was very simple: prepare a video with the music, and a static image. After briefly digging for a simple software to do that, which I could not find, I said ``hey, why not doing it with liquidsoap''? Well, that is fairly easy! Here is the code: ```{.liquidsoap include="video-static.liq"} ``` ��������������������������������������������������������������������liquidsoap-2.4.2/doc/content/video.md���������������������������������������������������������������0000664�0000000�0000000�00000020367�15132732333�0017552�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- header-includes: | \DeclareUnicodeCharacter{03C0}{$\pi$} --- Basically streaming videos does not change anything compared to streaming audio: you just have to use video files instead of sound files! For instance, if you want to stream a single file to an icecast server in ogg format (with theora and vorbis as codecs for audio and video) you can simply type: ```{.liquidsoap include="video-simple.liq"} ``` And of course you could have used a `playlist` instead of `single` to have multiple files, or used other [formats](encoding_formats.html) for the stream. In order to test a video stream, it is often convenient to use the `output.sdl` operator (or `output.graphics`) which will open a window and display the video stream inside. These can handle streams with video only, you can use the `drop_audio` operator to remove the sound part of a stream if needed. You should be expecting much higher resource needs (in cpu time in particular) for video than for audio. So, be prepared to hear the fan of your computer! The size of videos have a great impact on computations; if your machine cannot handle a stream (i.e. it's always catching up) you can try to encode to smaller videos for a start. ### Setting up frame size and positions We provide an abstract API to specify video frame sizes and positions that is independent from the actual rendered size. This way, you can define all your elements and have them being rendered at different frame size without having to change their placement or size values! This works by setting up a _virtual canvas_ that is larger than the _actual canvas_. You specify your positions, sizes etc. in terms of units for the larger canvas and they are translated automatically to values that apply for the actual canvas. We provide some default values. They are all `16:9` ratio using a virtual canvas of `10 000` pixels height: ```{.liquidsoap include="video-default-canvas.liq"} ``` The returned canvas is a record with the following methods: - `width`/`height`: size of the actual frame - `px`: define values in terms of virtual pixels - `vw`/`vh`: define values in terms of percentage (between `0.` and `1.`) of, resp., the actual frame width and height - `rem`: define values in terms of percentage (between `0.` and `1.`) of the default font size All the positioning methods are functions. For convenience, you can use the infix operator `@` to make things more readable. For instance, instead of writing `px(120)` to define a size of `120px`, you can write: `120 @ px`. These two notations are equivalent but the second one is more readable in this context. Here's an example of how to use this: ```{.liquidsoap include="video-canvas-example.liq"} ``` ### Encoding with FFmpeg The `%ffmpeg` encoder is the recommended encoder when working with video. Not only does it support a wide range of audio and video formats but it can also send and receive data to many different places, using `input.ffmpeg.` and `output.url`. On top of that, it also supports all the [FFmpeg filters](https://ffmpeg.org/ffmpeg-filters.html) and passing encoded data, if your script does not need re-encoding. The syntax for the encoder is detailed in the [encoders page](encoding_formats.html). Here are some examples: ```liquidsoap # AC3 audio and H264 video encapsulated in a MPEG-TS bitstream %ffmpeg(format="mpegts", %audio(codec="ac3",channel_coupling=0), %video(codec="libx264",b="2600k", "x264-params"="scenecut=0:open_gop=0:min-keyint=150:keyint=150", preset="ultrafast")) # AAC audio and H264 video encapsulated in a mp4 file (to use with # `output.file` only, mp4 container cannot be streamed! %ffmpeg(format="mp4", %audio(codec="aac"), %video(codec="libx264",b="2600k")) # Ogg opus and theora encappsulated in an ogg bitstream %ffmpeg(format="ogg", %audio(codec="libopus"), %video(codec="libtheora")) # Ogg opus and VP8 video encapsulated in a webm bitstream %ffmpeg(format="webm", %audio(codec="libopus"), %video(codec="libvpx")) ``` ### Streaming with FFmpeg The main input to take advantage of FFmpeg is `input.ffmpeg`. It should be able to decode pretty much any url and file that the `ffmpeg` command-line can take as input. This is, in particular, how `input.rtmp` is defined. For outputting, one can use the regular outputs but some of them have special features when used with `%ffmpeg`: - `output.file` is able to properly close a file after it is done encoding it. This makes it possible to encode in formats that need a proper header after encoding is done, such as `mp4`. - `output.url` will only work with the `%ffmpeg` encoder. It delegates data output to FFmpeg and can support any url that the `ffmpeg` command-line supports. - `output.file.hls` and `output.harbor.hls` should only be used with `%ffmpeg`. The other encoders do work but `%ffmpeg` is the only encoder able to generate valid `MPEG-TS` and `MP4` data segments for the HLS specifications. ## Useful tips & tricks Video is a really exciting world where there are lots of cool stuff to do. ### Transitions Transitions at the beginning or at the end of video can be achieved using `video.fade.in` and `video.fade.out`. For instance, fading at the beginning of videos is done by ```{.liquidsoap include="video-transition.liq" from="BEGIN" to="END"} ``` ### Adding a logo You can add a logo (any image) using the `video.add_image` operator, as follows: ```{.liquidsoap include="video-logo.liq" from="BEGIN" to="END"} ``` ### Inputting from a webcam If your computer has a webcam, it can be used as a source thanks to the `input.v4l2` operator. For instance: ```{.liquidsoap include="video-webcam.liq"} ``` ### Video in video Suppose that you have two video sources `s` and `s2` and you want to display a small copy of `s2` on top of `s`. This can be achieved by ```{.liquidsoap include="video-in-video.liq" from="BEGIN" to="END"} ``` ### Scrolling text Adding scrolling text at the bottom of your video is as easy as ```{.liquidsoap include="video-text.liq"} ``` You might need to change the `font` parameter so that it matches a font file present on your system. ### Effects There are many of effects that you can use to add some fun to your videos: `video.greyscale`, `video.sepia`, `video.lomo`, etc. [Read the documentation](reference.html) to find out about them. If you have compiled Liquidsoap with [frei0r](http://www.piksel.org/frei0r/) support, and have installed frei0r plugins, they will be named `video.frei0r.*`. You can have a list of those supported on your installation as usual, using `liquidsoap --list-plugins`. ### Presenting weather forecast You can say that a specific color should be transparent using `video.transparent`. For instance, you can put yourself in front of a blue screen (whose RGB color should be around 0x0000ff) and replace the blue screen by an image of the weather using ```{.liquidsoap include="video-weather.liq" to="END"} ``` ## Detailed examples ### The anonymizer Let's design an ``anonymizer'' effect: I want to blur my face and change my voice so that nobody will recognise me in the street after seeing the youtube video. Here is what we are going to achieve: <center><iframe width="560" height="315" src="//www.youtube.com/embed/E7Fb0wV3h5Q" frameborder="0" allowfullscreen></iframe></center>This video was produced thanks to the following script: ```{.liquidsoap include="video-anonymizer.liq"} ``` ### Controlling with OSC In this example we are going to use OSC integration in order to modify the parameters in realtime. There are many OSC clients around, for instance I used [TouchOSC](http://hexler.net/software/touchosc) : <center><iframe width="560" height="315" src="//www.youtube.com/embed/EX1PTjiuuXY" frameborder="0" allowfullscreen></iframe></center>Here is how the video was made: ```{.liquidsoap content="video-osc.liq"} ``` ### Blue screen You want to show yourself in front of a video of a bunny, as in <center><iframe width="640" height="360" src="//www.youtube.com/embed/zHikXRNMQu4?feature=player_detailpage" frameborder="0" allowfullscreen></iframe></center>The idea is to film yourself in front of a blue screen, make this blue screen transparent and put the resulting video in front of the bunny video (actually, I don't have a blue screen at home, only a white wall but it still kinda works). ```{.liquidsoap include="video-bluescreen.liq"} ``` �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/content/xml.md�����������������������������������������������������������������0000664�0000000�0000000�00000010672�15132732333�0017242�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Importing/exporting XML values Support for XML parsing and rendering was first added in liquidsoap `2.3.1`. You can parse XML strings using a decorator and type annotation. There are two different representations of XML you can use. ### Record access representation This is the easiest representation. It is intended for quick access to parsed value via record and tuples. Here's an example: ```liquidsoap s = '<bla param="1" bla="true"> <foo opt="12.3">gni</foo> <bar /> <bar>bla</bar> <blo>1.23</blo> <blu>false</blu> <ble>123</ble> </bla>' let xml.parse (x : { bla: { foo: string.{ xml_params: {opt: float} }, bar: (unit * string), blo: float, blu: bool, ble: int, xml_params: { bla: bool } } } ) = s print("The value for ble is: #{x.bla.ble}") ``` Things to note: - The basic mappings are: `<tag name> -> <tag content>` - Tag content maps tag parameters to a `xml_params` method. - When multiple tags are present, their values are collected as tuple (`bar` tag in the example) - When a tag contains a single ground value (`string`, `bool`, `float` or `integer`), the mapping is from tag name to the corresponding value, with xml attributes attached as methods - Tag parameters can be converted to ground values and omitted. The parsing is driven by the type annotation and is intended to be permissive. For instance, this will work: ```liquidsoaop s = '<bla>foo</bla>' # Here, `foo` is omitted. let xml.parse (x: { bla: unit }) = s # x contains: { bla = () } # Here, `foo` is made optional let xml.parse (x: { bla: string? }) = s # x contains: { bla = "foo" } ``` ### Formal representation Because XML format can result in complex values, the parser can also use a generic representation. Here's an example: ```liquidsoap s = '<bla param="1" bla="true"> <foo opt="12.3">gni</foo> <bar /> <bar>bla</bar> <blo>1.23</blo> <blu>false</blu> <ble>123</ble> </bla>' let xml.parse (x : ( string * { xml_params: [(string * string)], xml_children: [ ( string * { xml_params: [(string * string)], xml_children: [(string * {xml_text: string})] } ) ] } ) ) = s # x contains: ( "bla", { xml_children= [ ( "foo", { xml_children=[("xml_text", {xml_text="gni"})], xml_params=[("opt", "12.3")] } ), ("bar", {xml_children=[], xml_params=[]}), ( "bar", { xml_children=[("xml_text", {xml_text="bla"})], xml_params=[("option", "aab")] } ), ( "blo", {xml_children=[("xml_text", {xml_text="1.23"})], xml_params=[]} ), ( "blu", {xml_children=[("xml_text", {xml_text="false"})], xml_params=[]} ), ( "ble", {xml_children=[("xml_text", {xml_text="123"})], xml_params=[]} ) ], xml_params=[("param", "1"), ("bla", "true")] } ) ``` This representation is much less convenient to manipulate but allows an exact representation of all XML values. Things to note: - XML nodes are represented by a pair of the form: `(<tag name>, <tag properties>)` - `<tag properties>` is a record containing the following methods: - `xml_params`, represented as a list of pairs `(string * string)` - `xml_children`, containing a list of the XML node's children. Each entry in the list is a node in the formal XML representation. - `xml_text`, present when the node is a text node. In this case, `xml_params` and `xm_children` are empty. - By convention, text nodes are labelled `xml_text` and are of the form: `{ xml_text: "node content" }` ### Rendering XML values XML values can be converted back to strings using `xml.stringify`. Both the formal and record-access form can be rendered back into XML strings however, with the record-access representations, if a node has multiple children with the same tag, the conversion to XML string will fail. More generally, if the values you want to convert to XML strings are complex, for instance if they use several times the same tag as child node or if the order of child nodes matters, we recommend using the formal representation to make sure that children ordering is properly preserved. This is because record methods are not ordered in the language so we make no guarantee that the child nodes they represent be rendered in a specific order. ����������������������������������������������������������������������liquidsoap-2.4.2/doc/content/yaml.md����������������������������������������������������������������0000664�0000000�0000000�00000001256�15132732333�0017402�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������## Importing/exporting YAML values Support for YAML parsing and rendering was first added in liquidsoap `2.2.0`. This support follows the same pattern as [JSON parsing/rendering](json.html) but using yaml-based syntax, i.e.: ```liquidsoap let yaml.parse ({ name, version, scripts, } : { name: string, version: string, scripts: { test: string? }? }) = file.contents("/path/to/file.yaml") ``` and ```liquidsoap r = {artist = "Bla", title = "Blo"} print(yaml.stringify(r)) ``` The only major difference being that, in YAML, all numbers are parsed and rendered as _floats_. Please refer to the [JSON parsing and rendering](json.html) documentation for more details. ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/dune���������������������������������������������������������������������������0000664�0000000�0000000�00000001615�15132732333�0015321�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������; Regenerate using dune build @gendune --auto-promote (include dune.inc) (rule (alias dummytest) (package liquidsoap) (action (run %{bin:liquidsoap} --version))) (executable (name gen_dune) (preprocess (pps ppx_string)) (libraries re liquidsoap_build_tools) (modules gen_dune)) (executable (name subst_md) (libraries re liquidsoap_lang) (modules subst_md)) (rule (alias gendune) (target dune.inc.gen) (deps (source_tree ../src/libs) (source_tree .)) (action (with-stdout-to dune.inc.gen (run ./gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) (rule (alias doc) (target liquidsoap.1) (deps no-pandoc (:liquidsoap_man liquidsoap.1.md)) (action (ignore-outputs (system "pandoc -s -t man %{liquidsoap_man} -o liquidsoap.1 || cp no-pandoc liquidsoap.1")))) (install (section man) (package liquidsoap) (files liquidsoap.1)) �������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/dune.inc�����������������������������������������������������������������������0000664�0000000�0000000�00001277656�15132732333�0016116�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ (rule (alias doc) (package liquidsoap) (deps (source_tree ../src/libs)) (target protocols.md) (action (with-stdout-to protocols.md (setenv PAGER none (run %{bin:liquidsoap} --list-protocols-md --disable-deprecated))))) (rule (alias doc) (package liquidsoap) (deps (:header content/reference-header.md) (source_tree ../src/libs)) (target reference.md) (action (with-stdout-to reference.md (progn (cat %{header}) (echo "\n") (setenv PAGER none (run %{bin:liquidsoap} --list-functions-md --disable-deprecated))))) ) (rule (alias doc) (package liquidsoap) (deps (:header content/reference-header.md) (source_tree ../src/libs)) (target reference-extras.md) (action (with-stdout-to reference-extras.md (progn (cat %{header}) (echo "\n") (setenv PAGER none (run %{bin:liquidsoap} --no-external-plugins --list-extra-functions-md --disable-deprecated))))) ) (rule (alias doc) (package liquidsoap) (deps (:header content/reference-header.md) (source_tree ../src/libs)) (target reference-deprecated.md) (action (with-stdout-to reference-deprecated.md (progn (cat %{header}) (echo "\n") (setenv PAGER none (run %{bin:liquidsoap} --list-deprecated-functions-md --enable-deprecated))))) ) (rule (alias doc) (package liquidsoap) (deps (source_tree ../src/libs)) (target settings.md) (action (with-stdout-to settings.md (setenv PAGER none (run %{bin:liquidsoap} --list-settings --disable-deprecated))))) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target protocols.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md protocols.md) ) (target protocols.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=protocols --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target reference.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md reference.md) ) (target reference.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=reference --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target reference-extras.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md reference-extras.md) ) (target reference-extras.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=reference-extras --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target reference-deprecated.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md reference-deprecated.md) ) (target reference-deprecated.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=reference-deprecated --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target settings.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md settings.md) ) (target settings.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=settings --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target beets.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/beets.md) ) (target beets.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=beets --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target blank.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/blank.md) ) (target blank.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=blank --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target book.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/book.md) ) (target book.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=book --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target build.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/build.md) ) (target build.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=build --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target clocks.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/clocks.md) ) (target clocks.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=clocks --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target complete_case.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/complete_case.md) ) (target complete_case.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=complete_case --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target cookbook.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/cookbook.md) ) (target cookbook.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=cookbook --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target crossfade.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/crossfade.md) ) (target crossfade.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=crossfade --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target custom-path.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/custom-path.md) ) (target custom-path.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=custom-path --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target database.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/database.md) ) (target database.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=database --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target documentation.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/documentation.md) ) (target documentation.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=documentation --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target dynamic_sources.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/dynamic_sources.md) ) (target dynamic_sources.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=dynamic_sources --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target encoding_formats.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/encoding_formats.md) ) (target encoding_formats.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=encoding_formats --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target external_decoders.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/external_decoders.md) ) (target external_decoders.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=external_decoders --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target external_encoders.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/external_encoders.md) ) (target external_encoders.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=external_encoders --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target external_streams.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/external_streams.md) ) (target external_streams.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=external_streams --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target faq.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/faq.md) ) (target faq.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=faq --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target ffmpeg.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/ffmpeg.md) ) (target ffmpeg.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=ffmpeg --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target ffmpeg_cookbook.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/ffmpeg_cookbook.md) ) (target ffmpeg_cookbook.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=ffmpeg_cookbook --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target ffmpeg_encoder.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/ffmpeg_encoder.md) ) (target ffmpeg_encoder.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=ffmpeg_encoder --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target ffmpeg_filters.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/ffmpeg_filters.md) ) (target ffmpeg_filters.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=ffmpeg_filters --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target flows_devel.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/flows_devel.md) ) (target flows_devel.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=flows_devel --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target frequence3.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/frequence3.md) ) (target frequence3.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=frequence3 --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target geekradio.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/geekradio.md) ) (target geekradio.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=geekradio --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target graph_descriptions.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/graph_descriptions.md) ) (target graph_descriptions.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=graph_descriptions --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target harbor.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/harbor.md) ) (target harbor.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=harbor --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target harbor_http.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/harbor_http.md) ) (target harbor_http.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=harbor_http --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target help.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/help.md) ) (target help.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=help --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target hls_output.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/hls_output.md) ) (target hls_output.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=hls_output --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target http_input.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/http_input.md) ) (target http_input.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=http_input --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target icy_metadata.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/icy_metadata.md) ) (target icy_metadata.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=icy_metadata --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target in_production.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/in_production.md) ) (target in_production.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=in_production --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target index.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/index.md) ) (target index.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=index --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target install.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/install.md) ) (target install.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=install --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target json.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/json.md) ) (target json.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=json --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target ladspa.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/ladspa.md) ) (target ladspa.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=ladspa --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target language.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/language.md) ) (target language.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=language --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target latency_control.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/latency_control.md) ) (target latency_control.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=latency_control --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target loudness_normalization.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/loudness_normalization.md) ) (target loudness_normalization.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=loudness_normalization --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target memory.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/memory.md) ) (target memory.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=memory --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target metadata.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/metadata.md) ) (target metadata.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=metadata --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target migrating.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/migrating.md) ) (target migrating.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=migrating --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target multitrack.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/multitrack.md) ) (target multitrack.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=multitrack --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target on2.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/on2.md) ) (target on2.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=on2 --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target phases.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/phases.md) ) (target phases.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=phases --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target playlist_parsers.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/playlist_parsers.md) ) (target playlist_parsers.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=playlist_parsers --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target presentations.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/presentations.md) ) (target presentations.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=presentations --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target profiling.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/profiling.md) ) (target profiling.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=profiling --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target prometheus.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/prometheus.md) ) (target prometheus.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=prometheus --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target protocols-presentation.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/protocols-presentation.md) ) (target protocols-presentation.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=protocols-presentation --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target publications.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/publications.md) ) (target publications.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=publications --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target quick_start.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/quick_start.md) ) (target quick_start.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=quick_start --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target radiopi.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/radiopi.md) ) (target radiopi.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=radiopi --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target reference-header.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/reference-header.md) ) (target reference-header.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=reference-header --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target release-assets.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/release-assets.md) ) (target release-assets.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=release-assets --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target request_sources.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/request_sources.md) ) (target request_sources.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=request_sources --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target requests.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/requests.md) ) (target requests.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=requests --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target rolling-release.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/rolling-release.md) ) (target rolling-release.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=rolling-release --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target scheduling.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/scheduling.md) ) (target scheduling.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=scheduling --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target script_loading.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/script_loading.md) ) (target script_loading.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=script_loading --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target seek.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/seek.md) ) (target seek.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=seek --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target server.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/server.md) ) (target server.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=server --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target shoutcast.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/shoutcast.md) ) (target shoutcast.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=shoutcast --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target sources.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/sources.md) ) (target sources.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=sources --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target split-cue.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/split-cue.md) ) (target split-cue.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=split-cue --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target stereotool.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/stereotool.md) ) (target stereotool.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=stereotool --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target stream_content.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/stream_content.md) ) (target stream_content.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=stream_content --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target strings_encoding.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/strings_encoding.md) ) (target strings_encoding.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=strings_encoding --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target video-static.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/video-static.md) ) (target video-static.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=video-static --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target video.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/video.md) ) (target video.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=video --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target xml.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/xml.md) ) (target xml.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=xml --template=template.html -o %{target}) ) ) ) (rule (alias doc) (package liquidsoap) (enabled_if (not %{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target yaml.html) (action (run cp %{no_pandoc} %{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html content/liq/append-silence.liq content/liq/archive-cleaner.liq content/liq/basic-radio.liq content/liq/beets-amplify.liq content/liq/beets-protocol-short.liq content/liq/beets-protocol.liq content/liq/beets-source.liq content/liq/blank-detect.liq content/liq/blank-sorry.liq content/liq/complete-case.liq content/liq/cron_add.liq content/liq/cron_id.liq content/liq/cron_id_arg.liq content/liq/cron_remove.liq content/liq/cross.custom.liq content/liq/crossfade.liq content/liq/decoder-openmpt.liq content/liq/dump-hourly.liq content/liq/dump-hourly2.liq content/liq/dynamic-source.liq content/liq/external-output.file.liq content/liq/fallback.liq content/liq/ffmpeg-filter-dynamic-volume.liq content/liq/ffmpeg-filter-flanger-highpass.liq content/liq/ffmpeg-filter-hflip.liq content/liq/ffmpeg-filter-hflip2.liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq content/liq/ffmpeg-live-switch.liq content/liq/ffmpeg-relay-ondemand.liq content/liq/ffmpeg-relay.liq content/liq/ffmpeg-shared-encoding-rtmp.liq content/liq/ffmpeg-shared-encoding.liq content/liq/fixed-time1.liq content/liq/fixed-time2.liq content/liq/frame-size.liq content/liq/harbor-auth.liq content/liq/harbor-dynamic.liq content/liq/harbor-insert-metadata.liq content/liq/harbor-metadata.liq content/liq/harbor-redirect.liq content/liq/harbor-simple.liq content/liq/harbor-usage.liq content/liq/harbor.http.register.liq content/liq/harbor.http.response.liq content/liq/hls-metadata.liq content/liq/hls-mp4.liq content/liq/http-input.liq content/liq/icy-update.liq content/liq/input.mplayer.liq content/liq/jingle-hour.liq content/liq/json-ex.liq content/liq/json-stringify.liq content/liq/json1.liq content/liq/labeled_arguments.liq content/liq/live-switch.liq content/liq/loudness-correction.liq content/liq/medialib-predicate.liq content/liq/medialib.liq content/liq/medialib.sqlite.liq content/liq/multitrack-add-video-track2.liq content/liq/multitrack-default-video-track.liq content/liq/multitrack2.liq content/liq/output.file.hls.liq content/liq/playlists.liq content/liq/prometheus-callback.liq content/liq/prometheus-settings.liq content/liq/radiopi.liq content/liq/re-encode.liq content/liq/regular.liq content/liq/replaygain-playlist.liq content/liq/request.dynamic.liq content/liq/rtmp.liq content/liq/samplerate3.liq content/liq/scheduling.liq content/liq/scheduling_9am.liq content/liq/scheduling_queue.liq content/liq/scheduling_queue_midnight.liq content/liq/scheduling_simple.liq content/liq/seek-telnet.liq content/liq/settings.liq content/liq/shoutcast.liq content/liq/single.liq content/liq/source-cue.liq content/liq/space_overhead.liq content/liq/split-cue.liq content/liq/sqlite.liq content/liq/srt-receiver.liq content/liq/srt-sender.liq content/liq/switch-show.liq content/liq/transcoding.liq content/liq/video-anonymizer.liq content/liq/video-bluescreen.liq content/liq/video-canvas-example.liq content/liq/video-default-canvas.liq content/liq/video-in-video.liq content/liq/video-logo.liq content/liq/video-osc.liq content/liq/video-simple.liq content/liq/video-static.liq content/liq/video-text.liq content/liq/video-transition.liq content/liq/video-weather.liq content/liq/video-webcam.liq (:md content/yaml.md) ) (target yaml.html) (action (pipe-stdout (run pandoc %{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=yaml --template=template.html -o %{target}) ) ) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/append-silence.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/append-silence.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/archive-cleaner.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/archive-cleaner.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/basic-radio.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/basic-radio.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/beets-amplify.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/beets-amplify.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/beets-protocol-short.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/beets-protocol-short.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/beets-protocol.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/beets-protocol.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/beets-source.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/beets-source.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/blank-detect.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/blank-detect.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/blank-sorry.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/blank-sorry.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/complete-case.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/complete-case.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/cron_add.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/cron_add.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/cron_id.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/cron_id.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/cron_id_arg.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/cron_id_arg.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/cron_remove.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/cron_remove.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/cross.custom.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/cross.custom.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/crossfade.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/crossfade.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/decoder-openmpt.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/decoder-openmpt.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/dump-hourly.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/dump-hourly.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/dump-hourly2.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/dump-hourly2.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/dynamic-source.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/dynamic-source.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/external-output.file.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/external-output.file.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/fallback.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/fallback.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-filter-dynamic-volume.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-filter-dynamic-volume.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-filter-flanger-highpass.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-filter-flanger-highpass.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-filter-hflip.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-filter-hflip.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-filter-hflip2.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-filter-hflip2.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-filter-parallel-flanger-highpass.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-filter-parallel-flanger-highpass.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-live-switch.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-live-switch.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-relay-ondemand.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-relay-ondemand.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-relay.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-relay.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-shared-encoding-rtmp.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-shared-encoding-rtmp.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/ffmpeg-shared-encoding.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/ffmpeg-shared-encoding.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/fixed-time1.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/fixed-time1.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/fixed-time2.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/fixed-time2.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/frame-size.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/frame-size.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-auth.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-auth.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-dynamic.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-dynamic.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-insert-metadata.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-insert-metadata.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-metadata.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-metadata.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-redirect.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-redirect.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-simple.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-simple.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor-usage.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor-usage.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor.http.register.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor.http.register.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/harbor.http.response.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/harbor.http.response.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/hls-metadata.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/hls-metadata.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/hls-mp4.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/hls-mp4.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/http-input.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/http-input.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/icy-update.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/icy-update.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/input.mplayer.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/input.mplayer.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/jingle-hour.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/jingle-hour.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/json-ex.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/json-ex.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/json-stringify.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/json-stringify.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/json1.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/json1.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/labeled_arguments.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/labeled_arguments.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/live-switch.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/live-switch.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/loudness-correction.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/loudness-correction.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/medialib-predicate.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/medialib-predicate.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/medialib.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/medialib.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/medialib.sqlite.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/medialib.sqlite.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/multitrack-add-video-track2.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/multitrack-add-video-track2.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/multitrack-default-video-track.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/multitrack-default-video-track.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/multitrack2.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/multitrack2.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/output.file.hls.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/output.file.hls.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/playlists.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/playlists.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/prometheus-callback.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/prometheus-callback.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/prometheus-settings.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/prometheus-settings.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/radiopi.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/radiopi.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/re-encode.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/re-encode.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/regular.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/regular.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/replaygain-playlist.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/replaygain-playlist.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/request.dynamic.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/request.dynamic.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/rtmp.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/rtmp.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/samplerate3.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/samplerate3.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/scheduling.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/scheduling.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/scheduling_9am.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/scheduling_9am.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/scheduling_queue.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/scheduling_queue.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/scheduling_queue_midnight.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/scheduling_queue_midnight.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/scheduling_simple.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/scheduling_simple.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/seek-telnet.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/seek-telnet.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/settings.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/settings.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/shoutcast.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/shoutcast.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/single.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/single.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/source-cue.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/source-cue.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/space_overhead.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/space_overhead.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/split-cue.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/split-cue.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/sqlite.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/sqlite.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/srt-receiver.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/srt-receiver.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/srt-sender.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/srt-sender.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/switch-show.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/switch-show.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/transcoding.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/transcoding.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-anonymizer.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-anonymizer.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-bluescreen.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-bluescreen.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-canvas-example.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-canvas-example.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-default-canvas.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-default-canvas.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-in-video.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-in-video.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-logo.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-logo.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-osc.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-osc.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-simple.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-simple.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-static.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-static.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-text.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-text.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-transition.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-transition.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-weather.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-weather.liq)) ) (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq content/liq/video-webcam.liq) ) (action (run %{bin:liquidsoap} --check --no-fallible-check content/liq/video-webcam.liq)) ) (install (section doc) (package liquidsoap) (files (beets.html as html/beets.html) (blank.html as html/blank.html) (book.html as html/book.html) (build.html as html/build.html) (clocks.html as html/clocks.html) (complete_case.html as html/complete_case.html) (cookbook.html as html/cookbook.html) (crossfade.html as html/crossfade.html) (custom-path.html as html/custom-path.html) (database.html as html/database.html) (documentation.html as html/documentation.html) (dynamic_sources.html as html/dynamic_sources.html) (encoding_formats.html as html/encoding_formats.html) (external_decoders.html as html/external_decoders.html) (external_encoders.html as html/external_encoders.html) (external_streams.html as html/external_streams.html) (faq.html as html/faq.html) (ffmpeg.html as html/ffmpeg.html) (ffmpeg_cookbook.html as html/ffmpeg_cookbook.html) (ffmpeg_encoder.html as html/ffmpeg_encoder.html) (ffmpeg_filters.html as html/ffmpeg_filters.html) (flows_devel.html as html/flows_devel.html) (frequence3.html as html/frequence3.html) (geekradio.html as html/geekradio.html) (graph_descriptions.html as html/graph_descriptions.html) (harbor.html as html/harbor.html) (harbor_http.html as html/harbor_http.html) (help.html as html/help.html) (hls_output.html as html/hls_output.html) (http_input.html as html/http_input.html) (icy_metadata.html as html/icy_metadata.html) (in_production.html as html/in_production.html) (index.html as html/index.html) (install.html as html/install.html) (json.html as html/json.html) (ladspa.html as html/ladspa.html) (language.html as html/language.html) (latency_control.html as html/latency_control.html) (loudness_normalization.html as html/loudness_normalization.html) (memory.html as html/memory.html) (metadata.html as html/metadata.html) (migrating.html as html/migrating.html) (multitrack.html as html/multitrack.html) (on2.html as html/on2.html) (orig/css/homepage.css as html/css/homepage.css) (orig/css/style.css as html/css/style.css) (orig/fosdem2020/clock.png as html/fosdem2020/clock.png) (orig/fosdem2020/index.html as html/fosdem2020/index.html) (orig/fosdem2020/logo.png as html/fosdem2020/logo.png) (orig/fosdem2020/radio.gif as html/fosdem2020/radio.gif) (orig/fosdem2020/remark.js as html/fosdem2020/remark.js) (orig/images/basic-radio-graph.png as html/images/basic-radio-graph.png) (orig/images/design/background.png as html/images/design/background.png) (orig/images/design/background_page.png as html/images/design/background_page.png) (orig/images/design/logo.png as html/images/design/logo.png) (orig/images/grab.png as html/images/grab.png) (orig/images/graph_clocks.png as html/images/graph_clocks.png) (orig/images/liqgraph.png as html/images/liqgraph.png) (orig/images/schema-webradio-inkscape.png as html/images/schema-webradio-inkscape.png) (orig/images/stream.png as html/images/stream.png) (orig/images/tabs/tab_API.png as html/images/tabs/tab_API.png) (orig/images/tabs/tab_about.png as html/images/tabs/tab_about.png) (orig/images/tabs/tab_developers.png as html/images/tabs/tab_developers.png) (orig/images/tabs/tab_docs.png as html/images/tabs/tab_docs.png) (orig/images/tabs/tab_snippets.png as html/images/tabs/tab_snippets.png) (phases.html as html/phases.html) (playlist_parsers.html as html/playlist_parsers.html) (presentations.html as html/presentations.html) (profiling.html as html/profiling.html) (prometheus.html as html/prometheus.html) (protocols-presentation.html as html/protocols-presentation.html) (protocols.html as html/protocols.html) (publications.html as html/publications.html) (quick_start.html as html/quick_start.html) (radiopi.html as html/radiopi.html) (reference-deprecated.html as html/reference-deprecated.html) (reference-extras.html as html/reference-extras.html) (reference-header.html as html/reference-header.html) (reference.html as html/reference.html) (release-assets.html as html/release-assets.html) (request_sources.html as html/request_sources.html) (requests.html as html/requests.html) (rolling-release.html as html/rolling-release.html) (scheduling.html as html/scheduling.html) (script_loading.html as html/script_loading.html) (seek.html as html/seek.html) (server.html as html/server.html) (settings.html as html/settings.html) (shoutcast.html as html/shoutcast.html) (sources.html as html/sources.html) (split-cue.html as html/split-cue.html) (stereotool.html as html/stereotool.html) (stream_content.html as html/stream_content.html) (strings_encoding.html as html/strings_encoding.html) (video-static.html as html/video-static.html) (video.html as html/video.html) (xml.html as html/xml.html) (yaml.html as html/yaml.html) ) ) ����������������������������������������������������������������������������������liquidsoap-2.4.2/doc/gen_dune.ml��������������������������������������������������������������������0000664�0000000�0000000�00000011177�15132732333�0016565�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������module Pcre = Re.Pcre let generated_md = [ ("protocols.md", "--list-protocols-md --disable-deprecated", None); ( "reference.md", "--list-functions-md --disable-deprecated", Some "content/reference-header.md" ); ( "reference-extras.md", "--no-external-plugins --list-extra-functions-md --disable-deprecated", Some "content/reference-header.md" ); ( "reference-deprecated.md", "--list-deprecated-functions-md --enable-deprecated", Some "content/reference-header.md" ); ("settings.md", "--list-settings --disable-deprecated", None); ] let mk_html f = Pcre.substitute ~rex:(Pcre.regexp "md(?:\\.in)?$") ~subst:(fun _ -> "html") f let mk_md ?(content = true) f = if Pcre.pmatch ~rex:(Pcre.regexp "md\\.in$") f then Pcre.substitute ~rex:(Pcre.regexp "\\.in$") ~subst:(fun _ -> "") (Filename.basename f) else if content then "content/" ^ f else f let mk_title = Filename.remove_extension let mk_subst_rule f = if Pcre.pmatch ~rex:(Pcre.regexp "md\\.in$") f then ( let target = mk_md f in Printf.printf {| (rule (alias doc) (package liquidsoap) (deps (:subst_md ./subst_md.exe) (:in_md content/%s)) (target %s) (action (with-stdout-to %%{target} (run %%{subst_md} %%{in_md}))))|} f target) let mk_html_rule ~liq ~content f = let liq = liq |> List.map (fun f -> " " ^ f) |> String.concat "\n" in Printf.printf {| (rule (alias doc) (package liquidsoap) (enabled_if (not %%{bin-available:pandoc})) (deps (:no_pandoc no-pandoc)) (target %s) (action (run cp %%{no_pandoc} %%{target})) ) (rule (alias doc) (package liquidsoap) (enabled_if %%{bin-available:pandoc}) (deps liquidsoap.xml language.dtd template.html %s (:md %s) ) (target %s) (action (pipe-stdout (run pandoc %%{md} -t json) (run pandoc-include --directory content/liq) (run pandoc -f json --syntax-definition=liquidsoap.xml --highlight=pygments --metadata pagetitle=%s --template=template.html -o %%{target}) ) ) ) |} (mk_html f) liq (mk_md ~content f) (mk_html f) (mk_title f) let mk_generated_rule (file, option, header) = let header_deps, header_action, header_close = match header with | None -> ("", "", "") | Some fname -> ( [%string {|(:header %{fname})|}], {|(progn (cat %{header}) (echo "\n")|}, ")" ) in let header_action = if header_action = "" then "" else "\n " ^ header_action in let header_close = if header_close = "" then "" else "\n " ^ header_close in Printf.printf {| (rule (alias doc) (package liquidsoap) (deps %s (source_tree ../src/libs)) (target %s) (action (with-stdout-to %s%s (setenv PAGER none (run %%{bin:liquidsoap} %s)))))%s |} header_deps file file header_action option header_close let mk_test_rule file = Printf.printf {| (rule (alias doctest) (package liquidsoap) (deps (source_tree ../src/libs) (:test_liq %s) ) (action (run %%{bin:liquidsoap} --check --no-fallible-check %s)) ) |} file file let mk_html_install f = Printf.sprintf {| (%s as html/%s)|} (mk_html f) (mk_html f) let rec readdir ?(cur = []) ~location dir = List.fold_left (fun cur file -> let file = Filename.concat dir file in if Sys.is_directory (Filename.concat location file) then readdir ~cur ~location file else file :: cur) cur (Build_tools.read_files ~location dir) let () = let location = Filename.dirname Sys.executable_name in let md = Sys.readdir (Filename.concat location "content") |> Array.to_list |> List.filter (fun f -> Filename.extension f = ".md" || Filename.extension f = ".in") |> List.sort compare in let liq = Sys.readdir (Filename.concat location "content/liq") |> Array.to_list |> List.filter (fun f -> Filename.extension f = ".liq") |> List.sort compare |> List.map (fun f -> "content/liq/" ^ f) in List.iter mk_generated_rule generated_md; List.iter mk_subst_rule md; List.iter (fun (file, _, _) -> mk_html_rule ~liq ~content:false file) generated_md; List.iter (mk_html_rule ~liq ~content:true) md; List.iter mk_test_rule liq; let files = List.map (fun f -> Printf.sprintf {| (orig/%s as html/%s)|} f f) (readdir ~location:(Filename.concat location "orig") "") @ List.map (fun (f, _, _) -> mk_html_install f) generated_md @ List.map mk_html_install md in let files = files |> List.sort compare |> String.concat "\n" in Printf.printf {| (install (section doc) (package liquidsoap) (files %s ) ) |} files �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/language.dtd�������������������������������������������������������������������0000664�0000000�0000000�00000047060�15132732333�0016727�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!-- Copyright (c) 2001 Joseph Wenninger <jowenn@kde.org> modified (c) 2002 Anders Lund <anders@alweb.dk> modified (c) 2003 Simon Huerlimann <simon.huerlimann@access.unizh.ch> modified (c) 2005 Dominik Haumann <dhdev@gmx.de> modified (c) 2008 Wilbert Berendsen <info@wilbertberendsen.nl> This file describes the XML format used for syntax highlight descriptions for the Kate text editor (http://kate.kde.org), which is part of the KDE desktop environment (http://www.kde.org). You'll find the "Writing a Kate Highlighting XML File HOWTO" at http://kate.kde.org/doc/hlhowto.php This format is identified using the SYSTEM identifier SYSTEM "language.dtd" Files using this format should include a DOCTYPE declaration like this: <!DOCTYPE language SYSTEM "language.dtd"> You can validate your syntax files using checkXML from the development package of kdelibs: checkXML yourSyntax.xml If you see any 'validity error' lines, you should fix them. If you get a lot of 'No template matches' lines, everything's just fine. You've produced a valid syntax file! It's also possible to use the (much faster) xmllint which comes with the GNOME (oops:-) XML Library libxml2: xmllint - -dtdvalid language.dtd yourSyntax.xml (don't use a space between the two - That's just because XML comments don't allow that:-( To use your syntax file, copy it to .kde/share/apps/katepart/syntax/ in your home directory. You have to open a new instance of kwrite/kate to use the new syntax file. TODO - find a more readable way for the - -dtdvalid stuff, it's just annoying xml comments don't allow it. --> <!-- Entity declarations You can use '&per;' instead of '.'. This seems to be useful in <item> elements. TODO - Are there any more such pre-defined entities? --> <!ENTITY per "." > <!-- Boolean type Attributes that are of type boolean allow the following values: 'true', 'TRUE' and '1' all meaning true, 'false', FALSE' and '0' all meaning false. It is encouraged to use 'true' and 'false' instead of the alternatives. --> <!ENTITY % boolean "true|false|TRUE|FALSE|0|1"> <!-- Default Styles Allowed predefined default styles for itemData, available are: - dsNormal, used for normal text - dsKeyword, used for keywords - dsDataType, used for data types - dsDecVal, used for decimal values - dsBaseN, used for values with a base other than 10 - dsFloat, used for float values - dsChar, used for a character - dsString, used for strings - dsComment, used for comments - dsOthers, used for 'other' things - dsAlert, used for warning messages - dsFunction, used for function calls - dsRegionMarker, used for region markers - dsError, used for error highlighting. --> <!ENTITY % defStyles "dsNormal|dsKeyword|dsDataType|dsDecVal|dsBaseN|dsFloat|dsChar|dsString|dsComment|dsOthers|dsAlert|dsFunction|dsRegionMarker|dsError"> <!-- Language specification name: The name of this syntax description. Used in the Highlighting Mode menu section: The logical group to which this syntax description belongs. Used for sub menus extensions: A file glob or pattern to decide for which documents to use this syntax description style: Attribute style that this highlighter provides to kate scripts [optional] mimetype: A list of mimetypes to decide for which documents to use this syntax description [optional] version: Version number of this syntax description [optional] kateversion: Kate version required for using this file [optional] casesensitive: Whether text is matched case sensitive. [boolean, optional, default=true] FIXME: This is not implemented yet priority: Priority of this language, if more than one are usable for the file [optional] author: Name of author of this hl file [optional] license: License for this hl file [optional] indenter: Name of the Indenter to use for this highlighting mode per default, like "cstyle" [optional] hidden: Should it be hidden in menu [boolean, optional, default=false] TODO - Which matches are affected by casesensitive? keyword, RegExpr, StringDetect, WordDetect...? WARNING: due to helper scripts, the language opening tag must be on a *single line* and *cannot* be split in multiple lines. --> <!ELEMENT language (highlighting, general?, spellchecking?)> <!ATTLIST language name CDATA #REQUIRED section NMTOKEN #REQUIRED extensions CDATA #REQUIRED version CDATA #REQUIRED kateversion CDATA #REQUIRED style CDATA #IMPLIED mimetype CDATA #IMPLIED casesensitive (%boolean;) #IMPLIED priority CDATA #IMPLIED author CDATA #IMPLIED license CDATA #IMPLIED indenter CDATA #IMPLIED hidden (%boolean;) #IMPLIED > <!-- General options --> <!ELEMENT general (folding|comments|keywords|indentation|emptyLines)*> <!-- List of folding indentationsensitive: If true, the code folding is indentation based. --> <!ELEMENT folding EMPTY> <!ATTLIST folding indentationsensitive (%boolean;) #IMPLIED > <!-- List of comments --> <!ELEMENT comments (comment)+> <!-- Comment specification name: Type of this comment. Allowed are 'singleLine' and 'multiLine' start: The comment starts with this string end: The comment ends with this string [optional] region: The region name of the foldable multiline comment. If you have beginRegion="Comment" ... endRegion="Comment" you should use region="Comment". This way uncomment works even if you do not select all the text of the multiline comment. position: only available for type singleLine. Default is column0, to insert the single line comment characters after the whitespaces (= before the first non space) set position to "afterwhitespace" --> <!ELEMENT comment EMPTY> <!ATTLIST comment name (singleLine|multiLine) #REQUIRED start CDATA #REQUIRED end CDATA #IMPLIED region CDATA #IMPLIED position (afterwhitespace) #IMPLIED > <!-- Keyword options casesensitive: Whether keywords are matched case sensitive. [boolean, optional, default=true] weakDeliminator: Add weak deliminators [optional, default: ""] additionalDeliminator: Add deliminators [optional] wordWrapDeliminator: characters that are used to wrap long lines [optional] --> <!ELEMENT keywords EMPTY> <!ATTLIST keywords casesensitive (%boolean;) #IMPLIED weakDeliminator CDATA #IMPLIED additionalDeliminator CDATA #IMPLIED wordWrapDeliminator CDATA #IMPLIED > <!-- Indentation options mode: indentation mode to use TODO - Explain (weak) deliminators --> <!ELEMENT indentation EMPTY> <!ATTLIST indentation mode CDATA #IMPLIED > <!-- Treat lines that match a given regular expression as empty line. This is needed for example in Python for comments (#...), as then the indentation based folding should ignore the line. This is only implemented for indentation based folding. If the folding is not indentation based, the emptyLines are not used. --> <!ELEMENT emptyLines (emptyLine*)> <!-- One empty line regular expression. regexpr: The regular expression, example from python: ^\s*#.*$ casesensitive: Sets, whether the regular expression match is performed case sensitive --> <!ELEMENT emptyLine EMPTY> <!ATTLIST emptyLine regexpr CDATA #REQUIRED casesensitive (%boolean;) #IMPLIED > <!-- Highlighting specification --> <!ELEMENT highlighting (list*, contexts, itemDatas)> <!ATTLIST highlighting > <!-- List of items name: Name of this list --> <!ELEMENT list (item)*> <!ATTLIST list name CDATA #REQUIRED > <!-- List item contains string used in <keyword> --> <!ELEMENT item (#PCDATA)> <!-- List of contexts --> <!ELEMENT contexts (context)+> <!-- context specification name: The name of this context specification. Used in '*Context' attributes [optional] attribute: The name of the ItemData to be used for matching text lineEndContext: Next context if end of line is encountered lineBeginContext: Next context if begin of line is encountered [optional] fallthrough: Use a fallthrough context [optional] fallthroughContext: Fall through to this context [optional] dynamic: Dynamic context [boolean, optional] noIndentationBasedFolding: Python uses indentation based folding. However, Python has parts where it does not use indentation based folding (e.g. for """ strings). In this case switch to an own context and set this attribute to true. Then the indentation based folding will ignore this parts and not change folding markers. [optional] TODO: - Explain fallthrough. - Do we need fallthrough at all? It could be true, if fallthroughContext is set, false otherwise. - Make lineEndContext optional, defaults to '#stay'. Reasonable? --> <!ELEMENT context (keyword | Float | HlCOct | HlCHex | HlCFloat | Int | DetectChar | Detect2Chars | AnyChar | StringDetect | WordDetect | RegExpr | LineContinue | HlCStringChar | RangeDetect | HlCChar | IncludeRules | DetectSpaces | DetectIdentifier)*> <!ATTLIST context name CDATA #IMPLIED attribute CDATA #REQUIRED lineEndContext CDATA #REQUIRED lineBeginContext CDATA #IMPLIED fallthrough (%boolean;) #IMPLIED fallthroughContext CDATA #IMPLIED dynamic (%boolean;) #IMPLIED noIndentationBasedFolding (%boolean;) #IMPLIED > <!-- Common attributes attribute: The name of the ItemData to be used for matching text context: The name of the context to go to when this rule matches beginRegion: Begin a region of type beginRegion [optional] endRegion: End a region of type endRegion [optional] firstNonSpace: should this rule only match at first non-space char in line? column: should this rule only match at given column in line (column == count of chars in front) --> <!ENTITY % commonAttributes "attribute CDATA #IMPLIED context CDATA #IMPLIED beginRegion CDATA #IMPLIED endRegion CDATA #IMPLIED lookAhead (%boolean;) #IMPLIED firstNonSpace (%boolean;) #IMPLIED column CDATA #IMPLIED" > <!-- Detect members of a keyword list commonAttributes: Common attributes insensitive: Is this list case-insensitive? [boolean, optional, see note] String: Name of the list weakDelimiter: Use weak deliminator By default, case sensitivity is determined from <keywords casesensitive> in <general> (default=true), but can be overridden per-list with 'insensitive'. TODO: - Should be weakDeliminator - Explain deliminator - Doesn't seem to be supported in highlight.cpp --> <!ELEMENT keyword EMPTY> <!ATTLIST keyword %commonAttributes; insensitive CDATA #IMPLIED String CDATA #REQUIRED weakDelimiter CDATA #IMPLIED > <!-- Detect a floating point number commonAttributes: Common attributes AnyChar is allowed as a child rule. TODO: The source code allows *all* rules to be child rules, shall we change the DTD in some way? --> <!ELEMENT Float (AnyChar)*> <!ATTLIST Float %commonAttributes; > <!-- Detect an octal number commonAttributes: Common attributes --> <!ELEMENT HlCOct EMPTY> <!ATTLIST HlCOct %commonAttributes; > <!-- Detect a hexadecimal number commonAttributes: Common attributes --> <!ELEMENT HlCHex EMPTY> <!ATTLIST HlCHex %commonAttributes; > <!-- Detect a C-style floating point number commonAttributes: Common attributes --> <!ELEMENT HlCFloat EMPTY> <!ATTLIST HlCFloat %commonAttributes; > <!-- Detect C-style character commonAttributes: Common attributes TODO - Did I get this right? --> <!ELEMENT HlCChar EMPTY> <!ATTLIST HlCChar %commonAttributes; > <!-- Detect an integer number commonAttributes: Common attributes StringDetect is allowed as a child rule. TODO: The source code allows *all* rules to be child rules, shall we change the DTD in some way? --> <!ELEMENT Int (StringDetect)*> <!ATTLIST Int %commonAttributes; > <!-- Detect a single character commonAttributes: Common attributes char: The character to look for dynamic: Uses 0 ... 9 as placeholders for dynamic arguments (in fact, first char of arg...) [boolean, optional, default=false] --> <!ELEMENT DetectChar EMPTY> <!ATTLIST DetectChar %commonAttributes; char CDATA #REQUIRED dynamic (%boolean;) #IMPLIED > <!-- Detect two characters commonAttributes: Common attributes char: The first character char1: The second character dynamic: Uses 0 ... 9 as placeholders for dynamic arguments (in fact, first char of arg...) [boolean, optional, default=false] --> <!ELEMENT Detect2Chars EMPTY> <!ATTLIST Detect2Chars %commonAttributes; char CDATA #REQUIRED char1 CDATA #REQUIRED dynamic (%boolean;) #IMPLIED > <!-- Detect any group of characters commonAttributes: Common attributes String: A string representing the characters to look for TODO - Description is not descriptive enough, I'm not sure what it exactly does:-( --> <!ELEMENT AnyChar EMPTY> <!ATTLIST AnyChar %commonAttributes; String CDATA #REQUIRED > <!-- Detect a string commonAttributes: Common attributes String: The string to look for insensitive: Whether the string is matched case INsensitive. [boolean, optional, default=false] dynamic: Uses %0 ... %9 as placeholders for dynamic arguments [boolean, optional, default=false] TODO - What's default of insensitive? I'm not sure... --> <!ELEMENT StringDetect EMPTY> <!ATTLIST StringDetect %commonAttributes; String CDATA #REQUIRED insensitive (%boolean;) #IMPLIED dynamic (%boolean;) #IMPLIED > <!-- Detect a word, i.e. a string at word boundaries commonAttributes: Common attributes String: The string to look for insensitive: Whether the string is matched case INsensitive. [boolean, optional, default=false] dynamic: Uses %0 ... %9 as placeholders for dynamic arguments [boolean, optional, default=false] TODO - What's default of insensitive? I'm not sure... --> <!ELEMENT WordDetect EMPTY> <!ATTLIST WordDetect %commonAttributes; String CDATA #REQUIRED insensitive (%boolean;) #IMPLIED dynamic (%boolean;) #IMPLIED > <!-- Detect a match of a regular expression commonAttributes: Common attributes String: The regular expression pattern insensitive: Whether the text is matched case INsensitive. [boolean, optional, default=false] minimal: Whether to use minimal matching for wild cards in the pattern [boolean, optional, default='false'] dynamic: Uses %0 ... %9 as placeholders for dynamic arguments [boolean, optional, default=false] --> <!ELEMENT RegExpr EMPTY> <!ATTLIST RegExpr %commonAttributes; String CDATA #REQUIRED insensitive (%boolean;) #IMPLIED minimal (%boolean;) #IMPLIED dynamic (%boolean;) #IMPLIED > <!-- Detect a line continuation commonAttributes: Common attributes --> <!ELEMENT LineContinue EMPTY> <!ATTLIST LineContinue %commonAttributes; > <!-- Detect a C-style escaped character commonAttributes: Common attributes TODO: - Did I get this right? Only one character, or a string? --> <!ELEMENT HlCStringChar EMPTY> <!ATTLIST HlCStringChar %commonAttributes; > <!-- Detect a range of characters commonAttributes: Common attributes char: The character starting the range char1: The character terminating the range --> <!ELEMENT RangeDetect EMPTY> <!ATTLIST RangeDetect %commonAttributes; char CDATA #REQUIRED char1 CDATA #REQUIRED > <!-- Include Rules of another context context: The name of the context to include includeAttrib: If this is true, the host context of the IncludeRules will be given the attribute of the source context --> <!ELEMENT IncludeRules EMPTY> <!ATTLIST IncludeRules context CDATA #REQUIRED includeAttrib (%boolean;) #IMPLIED > <!-- Detect all following Spaces --> <!ELEMENT DetectSpaces EMPTY> <!ATTLIST DetectSpaces %commonAttributes; > <!-- Detect an Identifier ( == LETTER(LETTER|NUMBER|_)*) --> <!ELEMENT DetectIdentifier EMPTY> <!ATTLIST DetectIdentifier %commonAttributes; > <!-- List of attributes --> <!ELEMENT itemDatas (itemData)+> <!ATTLIST itemDatas > <!-- Attribute specification name CDATA #REQUIRED The name of this attribute defStyleNum CDATA #REQUIRED The index of the default style to use color CDATA #IMPLIED Color for this style, either a hex triplet, a name or some other format recognized by Qt [optional] selColor CDATA #IMPLIED The color for this style when text is selected [optional] italic CDATA #IMPLIED Whether this attribute should be rendered using an italic typeface [optional, boolean, default=false] bold CDATA #IMPLIED Whether this attribute should be renederd using a bold typeface [optional, boolean, default=false] underline CDATA #IMPLIED Whether this attribute should be underlined [optional, boolean, default=false] strikeOut CDATA #IMPLIED Whether this attribute should be striked out [optional, boolean, default=false] backgroundColor CDATA #IMPLIED The background color for this style [optional] selBackgroundColor CDATA #IMPLIED The background color for this style when text is selected [optional] spellChecking CDATA #IMPLIED Whether this attribute should be spell checked [optional, boolean, default=true] --> <!ELEMENT itemData EMPTY> <!ATTLIST itemData name CDATA #REQUIRED defStyleNum (%defStyles;) #REQUIRED color CDATA #IMPLIED selColor CDATA #IMPLIED italic (%boolean;) #IMPLIED bold (%boolean;) #IMPLIED underline (%boolean;) #IMPLIED strikeOut (%boolean;) #IMPLIED backgroundColor CDATA #IMPLIED selBackgroundColor CDATA #IMPLIED spellChecking (%boolean;) #IMPLIED > <!-- encodingPolicy type Attributes that are of type 'encodingPolicy' allow the following values: 'EncodeAlways', 'EncodeWhenPresent' and 'EncodeNever' --> <!ENTITY % encodingPolicy "EncodeAlways|EncodeWhenPresent|EncodeNever"> <!-- Spellchecking specification --> <!ELEMENT spellchecking (configuration?, encodings?)> <!ATTLIST spellchecking > <!-- List of character encodings --> <!ELEMENT encodings (encoding)+> <!ATTLIST encodings > <!-- Encoding specification sequence CDATA #REQUIRED Character sequence of the encoding; must not contain new-line characters, i.e. \n or \r character CDATA #IMPLIED Encoded character; must be of length 1 ignored (%boolean;) #IMPLIED If true, then the encoding sequence is ignored for spellchecking --> <!ELEMENT encoding EMPTY> <!ATTLIST encoding string CDATA #REQUIRED char CDATA #IMPLIED ignored (%boolean;) #IMPLIED > <!-- Spellchecking configuration encodingReplacementPolicy (%encodingPolicy;) #IMPLIED Policy for replacing encoded characters in replacements for misspelled words --> <!ELEMENT configuration EMPTY> <!ATTLIST configuration encodingReplacementPolicy (%encodingPolicy;) #IMPLIED > ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/liquidsoap.1.md����������������������������������������������������������������0000664�0000000�0000000�00000012371�15132732333�0017277�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������--- title: LIQUIDSOAP section: 1 date: Jul 24, 2019 header: Liquidsoap @version@ footer: Liquidsoap @version@ ... <!-- .TH LIQUIDSOAP 1 "Jul 1, 2016" "Liquidsoap @version@" --> # NAME liquidsoap - a multimedia streaming language # SYNOPSIS liquidsoap [ _options_ ] [ _script_ | _expression_ ] # DESCRIPTION Liquidsoap is a programming language for describing multimedia streaming systems. It is very flexible, making simple things simple but giving a lot of control for advanced uses. Liquidsoap supports audio, video and MIDI streams, and a wide range of input/output operators including Icecast and various soundcard APIs. It can perform a broad range of signal processing, combine streams in various ways, support custom transitions, generate sound procedurally... and all this can be assembled as you wish. Input files can be accessed remotely, or even be synthesized on the fly using external scripts such as speech synthesis. Finally, interaction with a running liquidsoap instance is possible via telnet or socket. Liquidsoap scripts passed on the command line will be evaluated: they shall be used to define the streaming system to be ran. It is possible to pass multiple scripts; they will all be ran successively, and definitions from one script can be used in subsequent ones. A script will be read from standard input if `-` is given as script filename. Information about scripting liquidsoap is available on our website: [http://liquidsoap.info/](http://liquidsoap.info/). If the parameter is not a file it will be treated as an expression which will be executed. It is a convenient way to test simple one-line scripts. When running only one-liners, the default is to log messages directly on stdout rather than to a file. # OPTIONS \- : Read script from standard input. \-- : Stop parsing the command\-line and pass subsequent items to the script. \--build-config : Display liquidsoap's build configuration. \--cache-only : Parse, type\-check and save script's cache but do no run it. \--cache-stdlib : Generate the standard library cache. \--debug : Print debugging log messages. \--debug-errors : Debug errors (show stacktrace instead of printing a message). \--debug-lang : Debug language implementation. \--debug-levels : Debug typing levels. \--debug-subtyping : Debug subtyping. \--disable-deprecated : Do not load wrappers for deprecated operators. \--enable-deprecated : Load wrappers for deprecated operators. \--interactive : Start an interactive interpreter. \--list-deprecated-functions-md : Documentation of all deprecated functions in markdown. \--list-extra-functions-md : Documentation of all extra functions in markdown. \--list-functions : List all functions. \--list-functions-by-category : List all functions, sorted by category. \--list-functions-json : Documentation of all functions in JSON format. \--list-functions-md : Documentation of all functions in markdown format. \--list-plugins : List all plugins (builtin scripting values, supported formats and protocols). \--list-portaudio-devices : List all available portaudio devices \--list-protocols-md : Documentation of all protocols in markdown. \--list-settings : Display configuration keys in markdown format. \--no-cache : Disable cache \--no-deprecated : Deprecated: use `--disable-deprecated` \--no-external-plugins : Disable external plugins. \--no-fallible-check : Ignore fallible sources. \--no-stdlib : Do not load stdlib script libraries \--opam-config : Print out opam's liquidsoap.config, for internal use. \--print-json-term : Parse and output the script as normalized JSON. The JSON format is used internally to format code. \--profile : Profile execution. \--raw-errors : In normal executions, exceptions raised during the script are translated into user\-friendly errors. Use this option to let the original error surface. This is useful when debugging. \--safe : Disable the effects of \--unsafe. \--stdlib : Override the location of the standard library. \--strict : Execute script code in strict mode, issuing fatal errors instead of warnings in some cases. Currently: unused variables and ignored expressions. \--unsafe : Faster startup using unsafe features. \--version : Display liquidsoap's version. \-T \--disable-telnet : Disable the telnet server. \-U \--disable-unix-socket : Disable the unix socket. \-c \--check : Parse, type\-check but do not evaluate the script. \-d \--daemon : Run in daemon mode. \-f \--force-start : For advanced dynamic uses: force liquidsoap to start even when no active source is initially defined. \-h : Get help about a scripting value: source, operator, builtin or library function, etc. \-i : Display inferred types. \-p \--parse-only : Parse script but do not type\-check and run them. \-q \--quiet : Do not print log messages on standard output. \-r \--request : Process a file request and print the metadata. \-t \--enable-telnet : Enable the telnet server. \-u \--enable-unix-socket : Enable the unix socket. \-v \--verbose : Print log messages on standard output. \-help Display this list of options \--help Display this list of options # SEE ALSO Our website [http://liquidsoap.info/](http://liquidsoap.info/) and the HTML documentation coming with your distribution of Liquidsoap. # AUTHOR [The savonet team](savonet-users@lists.sourceforge.net). �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/liquidsoap.xml�����������������������������������������������������������������0000777�0000000�0000000�00000000000�15132732333�0024125�2../scripts/liquidsoap.xml���������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/no-pandoc����������������������������������������������������������������������0000664�0000000�0000000�00000000113�15132732333�0016234�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Please rebuild with pandoc installed to generate liquidsoap documentation. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/��������������������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0015400�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/css/����������������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0016170�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/css/homepage.css����������������������������������������������������������0000664�0000000�0000000�00000005167�15132732333�0020500�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#play { background: transparent url("images/blocks/play/block_play_bg.png"); height: 124px; margin: 0 0 2em; } #play-left { background: transparent url("images/blocks/play/block_play_cl.png") no-repeat scroll top left; width: 20px; height: 124px; float: left; margin: 0; padding: 0; } #play-right { background: transparent url("images/blocks/play/block_play_cr.png") no-repeat scroll top left; width: 20px; height: 124px; float: right; margin: 0; padding: 0; } #play h3 { text-indent: -9000px; height: 124px; width: 58px; float: left; background: transparent url("images/blocks/play/block_play_arrow.png") no-repeat scroll top left; margin: 0 8% 0 5%; padding: 0; } #play p { text-indent: -9000px; height: 124px; width: 203px; float: left; background: transparent url("images/blocks/play/block_play_text.png") no-repeat scroll top left; margin: 0; padding: 0; } h3#abcs { display: none; } #fleche123 { background: transparent url("images/blocks/fleche123.png") no-repeat scroll top left; width: 393px; height: 155px; list-style-type: none; padding: 0; margin: 2em 0; position: relative; } #fleche123 li a { text-indent: -9000px; display: block; margin: 0; padding: 0; height: 132px; position: absolute; } #fleche123 li#fleche123-is a { width: 113px; } #fleche123 li#fleche123-dl a { width: 135px; left: 113px; } #fleche123 li#fleche123-ej a { width: 82px; left: 248px; } div.step { margin: 0; border: solid #C9F; border-width: 0 1px 1px; background-color: #FCF; margin-bottom: 1em; } div.step h3 { height: 20px; margin: 0 -1px; background: #906 url("images/tabs/tab_red_bg.png") repeat-x scroll top left; } div.step h3 span.left, div.step h3 span.right { display: block; height: 20px; width: 10px; background: transparent no-repeat scroll top left; } div.step h3 span.left { background-image: url("images/tabs/tab_red_l.png"); float: left; } div.step h3 span.right { background-image: url("images/tabs/tab_red_r.png"); float: right; background-position: right top; } div.step h3 span.text { padding: 0 0.5em; margin-right: 2em; float: right; display: block; text-indent: -9000px; height: 18px; background: #FFF no-repeat scroll center top; } div.step h3 span#step-download { width: 68px; background-image: url("images/tabs/btn_dl.png"); } div.step h3 span#step-install { width: 92px; background-image: url("images/tabs/btn_iands.png"); } div.step h3 span#step-enjoy { width: 119px; background-image: url("images/tabs/btn_eys.png"); } div.step .step-content { margin: 0; padding: 0 0.5em; text-align: justify; font-size: 10pt; } div.step .step-content p { text-indent: 1em; } ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/css/style.css�������������������������������������������������������������0000664�0000000�0000000�00000011202�15132732333�0020036�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������body { text-align: center; /* Required for pesky IE */ background: #FFF url('../images/design/background.png') repeat-x scroll top left; padding: 0; margin: 0; font-family: Bitstream Vera Sans, Tahoma, Verdana, Helvetica, sans-serif; } #wrapper { margin: 0 auto; width: 600px; text-align: left; /* Overrides the previously set center alignment that older IE versions reqd */ padding: 0; background: #FFF url('../images/design/background_page.png') repeat-x scroll top left; } #header { padding: 0; margin: 0; } #logo { background: #FFF url('../images/design/logo.png') no-repeat scroll top left; width: 515px; height: 106px; margin-left: 2em; } #menu { list-style-type: none; padding: 0; margin: 0; height: 18px; position: relative; } #menu li { list-style-type: none; padding: 0; margin: 0 1em 0 0; height: 18px; width: 83px; text-indent: -9000px; float: left; background: transparent no-repeat scroll top left; position: absolute; } #menu #menu-about { background-image: url("../images/tabs/tab_about.png"); right: 344px; } #menu #menu-download { background-image: url("../images/tabs/tab_install.png"); right: 258px; } /* This is for shipped documentation only.. */ #menu #menu-doc-index { background-image: url("../images/tabs/tab_docs.png"); right: 258px; } /* This is for shipped documentation only.. */ #menu #menu-doc-api { background-image: url("../images/tabs/tab_API.png"); right: 172px; } /* This is for shipped documentation only.. */ #menu #menu-doc-snippets { background-image: url("../images/tabs/tab_snippets.png"); right: 88px; } #menu #menu-support { background-image: url("../images/tabs/tab_docs.png"); right: 172px; } /* This is for website only.. */ #menu #menu-doc-api-www { background-image: url("../images/tabs/tab_API.png"); right: 86px; } #menu #menu-developers { background-image: url("../images/tabs/tab_developers.png"); right: 0px; } #menu li:hover, #menu li.active { background-position: 0 -18px; } #menu li a { display: block; } #index { width: 390px; padding: 3em 10px 0 20px; position: relative; float: left; text-align: justify; font-size: 10pt; } #content { width: 560px; padding: 3em 10px 0 20px; position: relative; float: left; text-align: justify; font-size: 10pt; } #sidebar { width: 140px; float: left; font-size: 10pt; padding: 3em 20px 0 10px; } #sidebar .box { background: repeat-y scroll top left; width: 150px; padding-bottom: 47px; position: relative; margin: 0 15px 2em 0; } #sidebar .box h3 { background: transparent no-repeat scroll top left; width: 150px; height: 19px; text-indent: -9000px; margin: 0; padding: 0; } #sidebar .box .more { text-align: right; font-style: normal; font-size: 8pt; padding: 0 1em; } #sidebar .box .box-bottom { background: transparent no-repeat scroll bottom left; width: 150px; height: 47px; position: absolute; bottom: 0; } #sidebar .box-say { background-image: url("../images/boxes/box_blue_bg.png"); background-color: #CCF; } #sidebar .box-say h3 { background-image: url("../images/boxes/box_blue_top.png"); } #sidebar .box-say blockquote { margin: 0; padding: 0; } #sidebar .box-say blockquote p { padding: 0 1em; margin: 0 0 1em; font-style: italic; text-indent: 0.5em; } #sidebar .box-say .box-bottom { background-image: url("../images/boxes/box_blue_say.png"); } #sidebar .box-see { background-image: url("../images/boxes/box_red_bg.png"); background-color: #FCC; } #sidebar .box-see h3 { background-image: url("../images/boxes/box_red_top.png"); } #sidebar .box-see .box-bottom { background-image: url("../images/boxes/box_red_see.png"); } #sidebar .box-hear { background-image: url("../images/boxes/box_green_bg.png"); background-color: #FCC; } #sidebar .box-hear h3 { background-image: url("../images/boxes/box_green_top.png"); } #sidebar .box-hear p.content { padding: 0 1em; margin: 0; } #sidebar .box-hear p.content img { margin-bottom: -5px; } #sidebar .box-hear .box-bottom { background-image: url("../images/boxes/box_green_hear.png"); } #footer { border-top: 2px solid #C9F; font-size: 8pt; text-align: right; padding: 0.3em 1em 5em; color: #666; clear: both; } img { border: none; margin-left: auto; margin-right: auto; display: block; } img.grab { display: inline; } hr.invisible { visibility: hidden; clear: both; } pre { padding-left: 10px ; border-left: solid 1px ; border-right: solid 1px ; font: 1.1em monospace, fixed ; white-space: pre ; color: #444 ; overflow: auto; } code { background-color: #eee; font: monospace, fixed; } ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/fosdem2020/���������������������������������������������������������������0000775�0000000�0000000�00000000000�15132732333�0017161�5����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/fosdem2020/clock.png������������������������������������������������������0000664�0000000�0000000�00000043047�15132732333�0020772�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������PNG  ��� IHDR�����{���&-��1iCCPICC Profile��XYTKL%Ar9 "%IEQ"DTA (JPPDzA%szY�|##P�GF;{V8})1vvV�)Yn<Y0P�C G5�*X�03](!6XDK(`uֱ&qr0D�xZ_ �⏧!rs6R?5a=`J?�txNs"XrCA\6 ވiCfs^[Nu=E0 ϩx28y%g5D07#l:Ts#G9Qc͝6w:lG%;ƾcdDž:o, 0-fR릞x <h1)7OtúΈam L5VNf}aon q-X-0-~% t.1"mshk紩9nS$8 P4 6[L/A �lQ~ph@ |FP�`5�#?ͧ h &,A;nWğ\{BD05aͰ&X 4'ZBzHU@7?fӇyb^G":lC"RhmD>"͊2h%d$}.22Bqf-d(A K+nbS/?20sw-pfkh"rwku}[jpghjPp,>#( r r�a#C=(P7-�H&l,�ՓMzDlȌ zX�[܁7b`dF@&GqPNs\WAh>x z�x ́E A@R@Ɛ�C>PA)>(*JP tC]P >@_%E1xPm(5>ڎ BEPèbT9Վz@>`��,-8.KMQxƢh~ ^h : .AWѝg1Íh`1n L&S\CfŲbŰsdž`Szlv8pR8m-ĝ]qx<^oGZ|+?_&0D[?a!PAI!LLD16щBL#/hhhiii4{ii.<IK5=L[E{7:::Q:=:Xt5twF~ГeSK0D zf {Ko0`\`"332322u1Mp$Q1ɟA:GK'd!!BG #O0cŘ͙Cs/2?ec!($feYEYYXX.e]bgŮ^>aʑ)iϙYysK•uk-}{ǔ']^V^=c|:|Tc|m|Y; <XtL|#DR :&!4''l-"\'<$BQ )y ]TLUhXXذ8xxs D)^IdddJJE*uJO#.!].BVF_&^NfLUJ6]I6m=KNY.LB<IB>]WIBsE:ETfy%)�2dekʫ***T> T}ƬfPnޢSCE#VƬff洖VVָYQ~3:*%C/136nPp#(1ٸxD$ȤdT4,99ż|BbE%e;+Ih(k 6"66M;1([X{;RIydNNyNŝ;\\\j\ms؝ӝpX4<95]l{.oN0;vh.R(( ,:!X70xjH-·jZV Aɻ3qg_TdfhFh(f{Ls,3r7_#%!)1"{䮬]SI&IɔHJHk:$]. }nfdo.>3:R>R:+?Q\NaJ.%!CŇ~Wv{$`n~uSARQ룍e]JEqEV'O9R\2PjPZdO]:s:ꙗgM6Þ?7YRJʜժj՚Zڼ:T]\݇ ^z/]l$sl=k}ep9+>WZ^hPktMٍPƹf7:nj޼~KVU@Kmy֌ֵ;wfڃ;vtvy}{7[4n<R{Xqcr'O?Uyأܫ{O_ѳ͟?t|Kӯ^-;~p{QcFc߽z~e"cnpofZaɇޏ'>E~Z/ff&׾~V_ v #߳pr nxUb/_kkkѾW@�V@��fnU`B., =L >#VD  m?]}C%c=S3%[iOK\<(^N$Hbfae&-''lbjfnai'/jclD4Z3f2iʬۼŢUu#v\Qnhw'vo>|~,?),HQ 5 s F,:]p8xWuRk`tꎽ'^ع�PBAcnࡤyG,c:.ShQXDYɍO-Ɲ8+Yuζ|l.|UkQ׳O57s+mwRwth%ݝq9?yrɞ^>rxgiDу^tU0eX 盕Dzw~5!ũ>}L6CL93w}~_gߋJ-=]I+%׀%h$ihW_2d|yeum*"7YIa~{7+rDD%bŻ$j$Җ22t)(R TTHjk_45uru|-  9PF\671wPd\zk}צ6.>QIYs]r>y۫~;FQ)NA6TP0p4ȕ?cV0D]NI1Ʌ)-'Siɤk۟y@̇E9_WckEv'BK>|l+S*T#qn"b+yW: _ڄm!ySEض;i:<puzt'9Ozf~<j``ؗɯ}]>ȫ;Ҹ{ I)3>RIל|K//Z~Osj{a5WQ6 s�o244#P L$R/- l6v#S%WgR^3޿-/*-"Z,$- "qTRJMQjR:MFDl6mU{*))F* (+(i}V/0X<eKN]{]Oҿcioknd<eRbj;nGYHY,f~fS``G?(s+[{' ;zI-R:*k!Na7#w&DFiDƠcfbv%%$޾-iC}>  ͊N9[ppM^㑻}#GGJy9qd)Y)W8PQr ]UP[_䥐˳WU^nh kw+]U[K|DžN{W<zP30o[㵓OsOuo~[/X��`;C ���{P�Z�BO"  Ԁ_!9e]`B2@ǡf I\Piz� ExҼХH&9Uaxp6<Oŷ8 q~"BCyBN[MNKOǰȘ1eXId5r/s <1$Av)^XNV.n<0E>O$#bz&\$)* :)vA<FBCʗDVlJ=T%Ge >UHuTEFְvN^OaMV]f=#SV˶x;V{Q5G+'sKk۴S+r{W/=P-(9=V,,j6F+6'n$A)Ȯdה{S[,32ˇN+/(1<)]&xF\¦2EPzŦzpSʍ㷮o[4̣ݫ=2}۟<D823=67%㋙_Ͷϛ}KadqK+lďMK�U`\A: F A6Ptj^�JQWP< ΀e6zÎ 4b Xol#{^DYb LE3FLΐ^6>dgA51, sy6Ѱ5rr<%5]cˋo x"X(+,+"-Z&%n,%U9TOYmrYqʏTn^P+Q?e-è;ת_opŰIi#^ombN.]Knyz nqƧGNRCB.FҊJ(JLRO>28:>}׾)$zc}!o4?(;'KO]r+Uq5Zu _NuMiƵ[n۴錾5>g"L_:xyxtǃ3_g_̟gt%a@ + 4/Y@qP4A!;i+]!`1oNn )B:q0]2=?}C$8iYKk2''; 7{gw(X^ב{Nq e Uw~Mf-w Y=mCosL̴ -ZY_ (­փIz-cτ!2�K dJ5gקd7dVq@Oz#JZӡKG/%_$U{b2H?}^&vŖzyWoi:<w֥vRG{vJ?xL|2k߷ڳwA/_y}wxە1n;&&?OL?_fqo糾z|E?Rj_:B\i\uu}1 Dk��fdm(��V֖V!0�w68k8õ./j��iTXtXML:com.adobe.xmp�����<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/"> <exif:PixelXDimension>471</exif:PixelXDimension> <exif:PixelYDimension>123</exif:PixelYDimension> </rdf:Description> </rdf:RDF> </x:xmpmeta> ��,IDATxXTGQĮQc,1v%1cFhĖh&"F`ablQQPHQ#, ͽ +, >rwʙ3{2&"@�(4e M "D"@D\#"@�(d\ (#D"@Ε� DB&@ε8"@� \"@�(d\ (#D"@Ε� DB&@ε8"@� F\^JM� DPܼyje0Oa}*}Dڟם4y#2O\Z4+J_4IAFO&y8YVJquJBt:+v+RQ}t1 y&&bйix8H-Ք@j٪ħ"yJpԫJfy_"<13objUń!^f2N<NJBt:+v+bny/R{ݐMz6{56\cȋB� DhEު9w|ƹi%"@� Jȹ*D"@ޖ�9׷%H� D(0*S6$D"v5d7j*(ĥK4R:/_VEDRK 5(^P1JxvGP}Wރ}2^nT u{:=)hQ1?O�0\y[3,ϫӻr<ХuUSЭ:wz A�06*>`ljm84h{vNdp!c9[8:UWM^sU"u҆2"@J/#ZzjF� G iD� %�9ހ> D#q`բ)@w "@J៤^mpuGie|DNq Lv1Y8RjVM#}n$ N©<"@`|oM0ZxqF@TlY̝;@#@ %KB B%ɘ?>*Voȹj6oތvڡre5i)P?_,*@H$hٲ%ʕ+W,RSSKK></ŇYf*Z?7oDϞ=|c,T+_}ݫs0Ҝ0axAjD%fW^-6MǏCA㑙#F`޼yR}ooo06l8{411)r֯_.\XekE21JO )66ҥKISPnՋ͹apww/333,^_:64)aO 7oWmRZ"@�0n)P-內1a(g ߅!)h_g=.2X>M_V{3GոVD� M g~X\zuQhҮ~tJu4G&r./fxlοH+h6'GA2w@p,1qƕEj]drv4zN\��sr!WAfBQ0Xs~8&BܿGYATt}+O~&4*)qO<:o6A֊P@aq(`qj>sKnV.p:Ch4U'q aX"ަ- i*OtE<Bp)0e4@~M<7#`Qt"$:Fu2"qfYH*¼aMsZy^}gQ,v8G<{"bN)BxiggДHM, ?-).QxU`gA3kp5r4]h2 UU'ù[?ޙ>=+|-Jpu$gxQ#q}@<Y\1A%4n]co�1hhZ@T%Xm;Zc0qtK~= g2xy1\{e>Alͩдht ǢJ .<'@_o0uB>뢢,#*dkGI=zSpuet5" h4tJ0='nѽ|0a`{yVl~Dc02?�#aVz: 61M[29p eZ,*5J;޶x}{}.zy0⃺ ,s߃onfg2^\K>o9Xv105dBQA'))8:l0gqcnݕSR)SNV"ʾ*FGG&\O.~7̓qivUȒ% *(7O H1Vus4_懯A/! GDBjv."&fGki.V̝P$ZVSw;w|#xU?H}×/[-[,Ueك5˗c-ؼRxL:ÚZOk?}+.r"n݁]Wa!]ʽY̶˰jq<  L SVIqxǩ_aQ@,nj&vG`޴鋁Gw~} S1F1gqi]'ng 3G/-j @jP18[%gpg<׬>8Atem)lQbx:LR^?s86 <$aI GhC<L{]ÎxG m>8#E}?| 3yFm:UxuavGG0i;t*쀻+t%=޲8Rڏ2uqa 4Pc'{r>,жC5b2>9C~ͣ\%æ8Vh 0X,8\/fo0?Zp.u3?Vw[\%aaXw_>^haѡKlV׉X~)8S2&M/x8&:k7$OSl6یMªQ}ZXO- >ϗmiJZ:Lv67GoU-b4_]V$Tqu2RB]qS[ S̚5>y?u[&VzzV8&&t? ,\S(.z&s#KG-G H֠dup8;z~:UóC4kc=1 ۇ! wQ..KWr'v F*v"\ڤw;)pRi;|3ul9?WdTi&J_5 ^WX|O+[*\Y12swC`ax We*c"/&;X =Okbv/ xWyF>Q/Hx]LcȐEbX;kOgF2j.<9t PŹ8+T؊u񵸒Ӽ!m{h"ڎR,یslqzlƺiX!o)ذ=bt0AghdΝgAn =pؼ`A(SI/b(%lX=epq J [C{Op"4{V‰$/t+SKQ4vUIh$n^m9k@$-VY,C~i1?ݒ7o6kH=�͸Așfra*"=<v+Uې2/h0pO6ZR$;&7_n9XZޠ=ڄ:mgØcYg,|[քϩXe<eeIŒ#ce[}obnڦ\dq$mτc g;ռ1{jyKRd:Z".4\h#8֩>"~vj<As`nga|EHnVc ~4Rc98ͅu*Šߺ _s Qc<RakA|xڭA:<Gl9a37)NX:#B>G~ $=K~˧hT%'Ckcnȍ.G8 #jӻAc>]%mZCeN/ ӳ˱ger^!T)7fz<Tq};\G nZ8Uւ4 _�Y"dOIK�@ep0,9"%Q#=\^A샐Z8 Tm7,aO_ #a�f\ �->DRVIkv=)>NFx迼XOU*F%ևI`2T,#=d#+JjW]mb2I};Ue^Jf_XeLa2.[UCX\W&{<>_"dl jsfsrrbwfVuVT{~~X盜&%-"e,x7'\JF9-١'  k_NcRNʢ.|+ KMː* i.N{Q^& fs-OW_12nOOO6~xEN}N+gVX9<=v>d;wfW\ɕ2^%o|ވu3I|b_!70flSЖ`feB ? ٤-f[b_2>+zsd<b?:`72JnB6DB@Qwm;}ΦL`'ߡ'ٽ_.0ՠ7,=e ׿Ne17ZM:M,/žI6BNFiy;L;;`{6x FmH#/.CjoT_%P԰O4q޲e ;vl"C<E~1,#X۟i֘_τ~tp$>&r:$ 4徛h=Yj䀘. ۝,3kKbCƫ+K0^;Bip%fs } nيwVGl/~.R:Ug'o;nO7ۺje%YQ7V~A:[F?f>.Q8l ٮl'Xz4RWL231</ll`sl>ovv/ =b 9rs}[*8C>d 4 ˎr[4`y0]˭,# t<D(F_g#8ea[v)*SH1^(XEx6Uׁ,1=~:\U9W" H`,U,Jؿg̛ASc4]JGh'_,ri+V 0>ܐ LC̴X*7jLE\~8B1Q<S\0PgC ,d/v"7EC,EÎM}{ί U~ȍs&a's9ߞb*|&*+S"{IM}}gS!Lse 5;cYvVn.۝kx_* &ZA3sǹybME=}>ŀ'9]*b;[Vv|fxŊ␥z?%/-_*�vm>rXnǥcڲcF>v}/r{Y 7ﭸgDG6r(vM1=`W+͔De7.eG7ilA{-].Z6b'[~YLR4IT0;oQtTU5s }nTR%Gu%qOAnU',P|0.(CDu5GyʧrqE&SpYj# \?H!_na_9@M #u}hE\7gg rCd]45U9W >yF0>iI|#d0ז:΂72F-end*M\ߔ|ι*%=NεS+":d"g4k>J U4 *.#1EE\둲9<Y*O- rUI9ohb=^fľ_݉J%'iQ!lulbW6;{6fMMaA (@sM B{-|irq!ka62r]P/}9Wyyũ,B/, @?2 ѹ_MQPCTEiuqⲰǥP묨ko7sݸ5[n~?ה3*.Zʇp3}77wn9zHnxo*([w-zCxLC63XzZW:;ffʏg ߙxhPehcMn DK2p#,o_@ڭ~Gɵ$&JҊ-y~/OXݾ3k_iZ\"=uc鯃H9 L ]CJ#q~ } W#Y=)OJ%&&w3T"w)lXTGL-<HCVyWCBBYK[|⫓%΅}N={%)C@@kq<ydDDDW^\bӣO>x%o"'وant1>>cƌ$f8}$^\[o{~VbL]]][loo;vzR ӛ9axuM/GJMs ѹrӹ .oݰc)B\ʗ"Pt"CʂЊ|MAQ&H"P <n3�biH3\Ŷ~sIpx| c.A/ȹfϕoLC\2Dɉ@NX)S;8|3'%`$5lbokH8P~Tdڸ&@~vF\ifjߐ k=n*T>x)fwLL'DjgUاJՇ H"@�(![3ZBZ$DRA،2<5ԯwxahfjG"@|[8?RxpONUe$l6TU λǑȲR՜xn,,dT \_ɾ_hxTxzܼiwϖ׵0a7hr\W]5 $O"@0tc^l ߔ]Iu>zOmLrMr|M~qۆ{U~FW]ZW"@J)MvF\Ǯ^CI] {:VO'%) D]qx#8%IE"@�3vš�$"@�9Wos ^}R"`ȹ|@ "@<Tr[ܶ#͉�0^4s5޶"@s5mI, D�fkS/#D##@ ##T]"@�(\+N$iD"@%`$\@ � Dr"@�0ZrDO:J�0Dv1ZrrDO:%E:"`hWJWIgcDڄ4"DRNHKy+R� Ds5 e� D4]q K,�d9&&&H{(z κ )@NL[ή�) Ѝ�9WݸasBHҥKӛ5kK):aaaի޽[tRIFGHmKX'KlRW%L}Fp=>ynnnzL"+NhQQu++)"@t#@]Qݺ"DUhWUT %,_MNٿ˗0ֹ)(2n^~IŽڅ;X_3gV &+%N(:L9-Ѥ]%:)jHGQD@ĽW Ɉťm~{-j4BЪ7\-~'`$4j+!18MvGyT4Щ=C])?SkYe)(C[>FSypf]:ZBSg4#^!^Ƴz`` V[0C8v5Zv~!|pSZtH!Dd炳7`@* A]E^\Z+.,Wkl#/|;,t@@F'z& y?!Ce#Ei/zÜ^քV&ŽӮaGt]{~C~r%X4þ.'\")f(JѢQTran^n~_`gWt(NFv1k+*6-`WtBob!rf x=�~�udO"z! gm>d p[(,Ka�{8=HlЛibH 9 zO7!&քڌ60d=xߞ{GFr°7 u]5>ȾV?Ldž}ZtvݣkD, Ssch1h/nJ -,wEBK(QY$ERhtvmnc4ltt D>}`=?g׮z0TtR53`TXV�[xu(D+?HUD5`)DBrUo QJ<w ٮZL4<ht'V5Zu D\c;3;/G[5"*DhWc:YEPto.7&6-z㣦xolkI2<�hd+LV &qq/ͰFf|^# خiXmKLJ$= QHNJAjWzD1C Ua8W\e$f 19dHdOoU ێO<>%-C +S`_1+pr^)6#Y.ll333EV?!\߻i1{•u΄Gvcί@fZ V S_[�!X=3'<khWJM!*a,_UAk)uzO j`+7R <"j7S*V5b:`O8M1#;f<zhWWo2Ch70NYr(\Iͮqs2K1 "_#/kDb8G*߮`"T^ 7C/<z*mY< V}_<JE E[sd樴5\a|Ƙ`lZ@bA%: g_YO/VU,3+@6tlW_~P(3FGHaQI9ʓDITbcs x85%g$H%e1_grfЖ`W+R Ѕc؀re Rc>;{m_Jxm~<po*c v q$[76FiCCCYzAIhKNe*>׃f:̬ac+Dۿ 7-W|,0m2 01ˎD@;p$L bi=|?ݏ+ wڄF<VړH"J]O %wWELjv?uМp9VW,37�S̽'D2c$3WkNPbcclX)8^|w}ԄPHkq6MJ6N:]v-5k/Oo߾4T`ȹpjDϭ^CPt HH>oI%"@�(Ĺ?Vj[*F� EN@Ӯ8F\W"ou*"@F ]qLNtL� D [L� Fk� D ZZ[E� Fk� D ZZ[E� FH+f*"@JaW$52 J�C� oWp�튣}סD"@~sU‰� D>rϔ$"@ jO� OHsM2 *}JDڟם4y#2O\Z4+Jm6IAFO&y8YVJquJBt:+v+RQ}t1 y&^Ǹт7[j gCaĊUrr6ebq* IUWT O_"<1Su>S;8֯2.&,2 VG.I|%n:iT\n&ml=\oG~M~݄UTlpyIS DP1]_a$"@1 j Lu$D"%@εHqSaD"` ȹC+S� DH s-RT DrTG"@�(R{>1����IENDB`�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������liquidsoap-2.4.2/doc/orig/fosdem2020/index.html�����������������������������������������������������0000664�0000000�0000000�00000020712�15132732333�0021160�0����������������������������������������������������������������������������������������������������ustar�00root����������������������������root����������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������<!DOCTYPE html> <html> <head> <title>Liquidsoap liquidsoap-2.4.2/doc/orig/fosdem2020/logo.png000066400000000000000000000120061513273233300206260ustar00rootroot00000000000000PNG  IHDRUj"sRGB pHYs B(xiTXtXML:com.adobe.xmp www.inkscape.org 1 UXIDATx{\}w^!GheڠD[*$MDM)nMMBK_H6yؤPBU )Q[T%iQ0`?13;{fyܹ~qϜ]9Hb/~Z\ N-vErǭtIŪUjw94f`'UZD 56YITJщj`AiJ6ms7VV[V.LTHJ%*~X^v'D\&ɳlkg"@%N&It{D~Iѩb VΤ>my B] lߚU1 AHBl.OQUM*յwz[&<ױQnI?eZ}E@ -D(2TBO^WM< ƃ*u 'd:iRTeTP[ylS5uZ\kQACmbjQlF@vL{QBJX~npJViXmhp=~>rբjhOH|~z|;0]\jzOsgF`Zg"(wپo6#G^!xu- 3Q nΩYlts:jDIɅfdG5x 9*Fed" *-{nmraYK[7FR+ y:pb'MsT,th/jq!lMTsIѱ\}b9,HҙQ}6-ZQF&P#Vfڤ!cG'=Kb72uhVϮpo9:'hS53roWuTIIgc6D5[N+mek`6yt@`˭ޥNC`^pxoLD猋wHm\Kuk~lj TU&pkP}`C_JƒN+;?#Og0=7}eB1~RDubb.qVDn;t3 eABN.FS"lw DVWլr 70H/i1yU_kI;>Zn ;C-^sHzv:o3}Ku ;"k ,]Vv_ٌ&L1;Iqg&|$D4p'"WA@W<.6\oKaU-DC.L8s@rzo/WE%C-SZ C$?00uTiktMuZ*C KO(h#q^)yzvp~v й>ٲ0H E>kgQGR+ywKEkc}c6wv+c2ϸ,Gc̠j%ȽK* 1H'6:\Pph7Nf\g7]|`'`P `47):+d-.|4 4dr {fږoTo[X[^ibO}tTOi * Eyd jG n(P1 El!j/vk߫ <Xfja46{51UZ-@XN; gቕ4dd L ʿvy3 nًˇ3[@-&r긮 p rC&j/3E~y{Ҹ]eNwk^ +xV:Q*сdc]4]@nk8HՓ.tD"޾-ɸjkx@j!U;@M8N=d jFqк2{g/=4[<ڍ3>Qئ4hQf骚j3X\(lS4(Mu9 2 eBPUjq ?)`pNP{~q|'oI=p-OƠvc4_e׸Q@;O= :OQp{Kg$t.8֣s?<ʏ KDž,=6~7iԵO^Nqvq6ceT^,~//fϞ_$~m c7~xC2RJȚJ,nTmk&'\q;hWvً gI T?()|䴈NIr%x/V3x:SI-`Zt|; 9X+g⸜ {AhOYLJB65AR˜q~x0|JٖۦɞP4HNu <+ZRW@pFRAmYߊy_ f\*eWUj_X -q2{@; Nu*ɵ*[=sJ}HwVtTWKXs-i,ݙ_XItnXFAYpfDXNCm wwRTӖ+8UbsBW`Y{qdqz[N'PQBNhwNO-W+u0~)Xh}*W2Np7{Fo>m/0HDP)N6;Hh1zoeFr2-}kj䆖T1 v1=MzNl!N6T6b׽-:UEYOi9E PV+pz$hNh;>^kYN~wr'1h%>LUT1}do6w.*V,:/IȤMiIbQmET?z*72) P/ Ձp]%^9l;_@B< 4I#qN%Zɢp8 b:(:CܕB&Go;^aَE:P =+T%]@Ǽ·}bٲNdUIENDB`liquidsoap-2.4.2/doc/orig/fosdem2020/radio.gif000066400000000000000000056343341513273233300207700ustar00rootroot00000000000000GIF87a^         #&" ##"",*"#"),#""$#"$"%,"#!*#$$*$$*3$*%*3%$&5+)&2)+;)!*%!*H#*$$*,%*3+*,=*-++2++>2-4)3*$42,4435435;+6=36+;6*F6(-8P;84F94;:<4;>;JeCJRNJC9KDK[OKKML;bL?LMUoMUMRB?SKDS]NSTLTLBUSSUC[UKTWK^WDbW7vXXlY@[[St[@u\KI]oR]Tk]K\^Lc^LG_^U_]Sbt[b[\bTcbScb[jbTkb]^cy]dcudYNj\cjZYkdlkZdmdsmZWnndol~oZkpd~pc\qsqcksl~slkttstnmzst{m{{k`|rl|zs|u{}ss~{t~gil{t}uuutu{u{|zum||rv}|||s|wrt{x}uyz|vv{||{|w|}}~~! NETSCAPE2.0! ,^ ʥi[ȰÇܸyD~2cǍB)ϣIRvcǒ;0Uc&#sܩO tF{"IS)M:S<aVQt(jmhKM_o\ Emһx6t߿ 6È+^̸ǐ [pΥ02ƾq۩^i˖Ė]8۞nxn!qu{S~DC3p`nB*dZyU O|yY_^"˟_|V?Ͽ1 eFudhNE%w'zx䵧z'X> ("X^ubꉸv&7s=A fVY5X;/xjCY$N;#(OXf%Q<#J^ ek3  &FO͠J34JNyD*N&%Zf)#iޚ`~VixjjofjiHЈɜuL3wC>蠏G.{Ɇ6zf[hn5\׬%E/uq^ٓ-;%>fI|rQx^4@^| R"Zh;Gf1#j(ըJۤ6i zaH%]iD:&j_``,cyKZNl~ӗ@zt*sL_#II^jK ^"ܦ26IOfd OIHiJN:̝Qaϵq[Phd `W@F*Hp#3ycK_ᦊƱŪ $zq6K-QήiJz4wMNyWO +Ԝ&vmBJtX;]^Yz6-DVMKc-nyVC^h0| ɸ*pK\7Z$̥SrI4QZwTh]U=kY7E;F׽霮E۟ͯ2^L3uM4:+:2 iN޸fu+ksNڪԫmmMaGR$`c6Ϭ^3ԘƎ&n@;5יaNw:yPƲ0/ ϊ$W*ֶ`V?h>94Fgjbtq"(3p*Qk-V-EڹN5fN>3qBKXj>Y ǖkG$969[ujoD,f{[5lS㖛Wԭ D$/:>V[,/G\2瞄pg;\r+>NmQپAfR؟pCtyuxRMa-isid8# s8{yqvemIo,#K<Qs[8`8cvF"K6u \R\C/zY~a/+=:_n4K8#E Js7U@{w>OƜަ HA~ۣWwW1PB_owvXs6AS3O:@wi 7e}SO3ui '~;D8w~~W{c\vswFcCv7-F<yhgGVS@G &98s hHwwƀfʐ~|GCc(x+|8<,ATkfy\jp)rxFْyszu91H.i[lYF̆Z6j8x )Z=Yv#tv{锧hpGh$))0()ylvc ɐVI$XJl `C)CRdi(9YSyK'IDC҉ѩRM`Nذ Ր)Ci))b`sÛo9YkʙvdXs''zI7)ZMӠ M>?7lXT[ o"J$jƝiբ'sl9Ty -E ZPeassԹ,O {[yȟ.IphI)b1d0e -ִnzmـR7 uJ*{Rwmoy^(i[J, cx@fd/$JV&^y~JzvU%z%I٨2]M:4@ jڧ\MoĉǙuj<~`z9} t[OrCg:?Wxȣv#F (FF1$в0ek?a(Ok}v%;kj8T oi$7KsIR0aV?,0o2 V^HC{hAv '9*wB@Q{SۀK3xSsڞrveD':FKb74}뼒ZC{[$&ۻ&Yְ,X?k¤׸X%y61ث k{g@ D;Xӫy[L NܹKy쥭8 2ɭ;"<՛=w}(7w^ܽ70~+at̢+= zxH(3GyE?(]ʨE,`0,<.0 Ŵ;ŭ{{p[o |'&[5 p<^ħt<=hH˽𲡂|;*ÅcdiJUy\[<ˑ+9tLHK|ɕl {@![\,qYƆ,40׊vR˙eTEK:F2,ӕFnI.sذ dMl<Zw%= ac.?h25nl9nkBq[)L,eN^npY>?~|ϤEv˜-G~zߖ><|~7OUM,դX-).۲WRSڔ>:彝놎ڈ>r' K%l g.K$!{~z/xfŎgTzƾR'Ӟ%Mk./;5Bf:}nVR6PdoWLᰮފ|ɹz]nUz^Nd_flh?.%m,n_X-aq^ؠ }?YMiؼ,ff^ =Hsn{O\joN` 7-)edo_zVvtl3՝ ;EW`  4(08գXO=ӥ#x|!E$YҤɏ*L%1_Τ9e͗t{uۚjIp2eפNņիPNuWaV*VjF!ڳװcʘq5W.[3nDIC֔aĉyOv@5T)S[j9YWfE[Z){p ~e?hnziM,7I '^xc=#ZҥMFz2IoJ[C4eaH{ 2qon|o{l90qβ0nhN+"gP+VR)6:=ؓ7 KI@$kʠS3a13a<î)P4.ԐCkDڨQMnDh 3sj,͙r\3@zd.I#R=&{Ҭ( J+8ڼ 0Y<2DS7Au 2D0F*aҳXI&5*C7ш6Zl!œDa-5OUvك$9T*+:aZuu]vE$L$V\u]*؛IXހ4xMg'{"H("F6ϫv27r˵tbwWDR.WXo~{v&Tp-\tQ^vb8ۋ +C =7UH.9̓UܴE _O倪p;)-n]芯Zo;NJu]w168횽/ S lC(Aߎ{1";yXxۿ)Vv2jé>gqB9eW~i Bs1'sxsn;b G[hct")VmxpuwG&^ek&L}TytT̓,uss[ֽ}OUۑ> \IwK54-/ _2J#䍍85 fzI<61Z`0A(c jv2e B8W>jcE0ѼsC'l@z;@p2@{Cn-.b40YDH/Kd&bLZtPD)F8p?u!y<. H"1uT"DFVj G:E!4d\0Iz'i%8 pS)fBƑɫ+y0%A; Dv@%TQ UpB* Jy,% ȁ<zN .tO .-HxP0h C[Iʥe UHq U b0 U&":72?TU(z?=|iLetd{>PLՄBMz0^|dW}\ZQKX"joVCna2ZD9G]y׼k pA"t {Et~IC !58i鶶 Ȭ4l1 Alw'>1 DA Zi=: 2Ӻ|D$[ۀmWmpXȟ ֧ȥO 8*q`BBf; KVrD rBb'4nRaZvp~KakoӝΧ>mq58UQmc na K]5qEL7AAB`Y5pld=Ə :!ȃi ,\.YOfd@8TRWI9öPE ;"мfN*'f Ztg6A۴¡QgQ(ŵ*oӶ5 nN$P1S! /AU1X5JgܷYs峟o&]z#_y3 ERSGٺajGH䡎cd}Ba$DL*<'Oy 6:"hc>8 蓇}# @8pgҢWZ?{÷0 p)==:ꞂF@}ڠdXh Ї|O-グC>;;;` sxKc";d @)?̿;4}Z { D*"|4! %,f`BC9 (t)*Bӫn`h $4,H'74۹dNX} C`O,p ufh[G=(.)@ *,;j9@9PBQ=2S#F.W$WXY;8D cx_D `rHHq+p/MNY5^CeG4 xm\!}pZԷ^^΅R,/Yݖspr#i'.9 iN۶O`dL0q3ѳBc T ^%/a!l;+}r8WnﺃκCVHdMbG9۾u(ϥ(U4-`8`,0Q`l_5!"K3cYւE>WU8>6a=G!U9*QzˉHu0DO0R0'$L dpTrbхbY5`MSd8 ,xFeU.XM!UCYe[anS-tfJЇi+Bb,15Dp [BhJl.LH@) 9H1&&0vw6Ap|.m,BRO@RsY6nK()L^|4,P3;Pqj{fhvhV0Β@MLTNBVhec[H+8jD+{$$Ȃ7K-hȇv\";DHHnd*P LDj(aĶVu/z|^ l&H؄-fꦮ&n1&`ORʌD#((5DMy9 2,>zM+@g0_n-sH)elb ~^~nFf!K`mo*By i4r,H(H#uQ]/D?(R;9f[FnoL>,t`6֜Xoxq c]o/.>r6VXyim9C>p*7r#E7kiS=Ԏ(giD*3p9Gv8l6Rt׮V&o[X3)4HEP0Ã1qsn Uhgxǜ7b>p`ilT+y3y'K9ðOjNLVLzw΃!;CKw!:a6Vu<1(k >錟{ {(q?Wu7yol\` .ZMȀ ?HmrUʮ@K=)wk72d!%J(c.բhbʼn6ntVsJ>rذe A j4Xw'Оg(QֳʼnQUjTSxkk֬ "DcT .TpF$N31d-'S,;fU3"ƈm|gkR"Cez*V\@\c@j=[p=0u5x#_0P8P6$ƘcQvb*tEmDiG|K @([B+r 5ZFR&G4'O7 d\qB@`v.Da' c9&_1JE;෍~p10WYhma^)v#V labX8ZфbgA4Lb8>XH! $`A %B@B w\TR)r97 +u9\ LQT,.ƚ'YQ _NħW`[tU@ `rv!P.;*QjYf9O4L29bX $ ƪaACNBTrN3T8ٍ/p&"tSa?ge^ E0dP(su@d)\-qxin<"89!-Js}=;ǬF#m3 0-a'L}RY%v15I=xLwLnK#hLR| QTt0%^h&7!C&!0%0;n fRd%YѺUPMDoh0BtiPQ*҈v0m4"~#d`LKB izYV\収48X} HueK\"k*NtKk6ƴRM%Vf4cH$֜ f/Rggth@Ƌ'AP! B%͛)ݽޏ|{"H> ?ե"PuJA 8Y׉Zupe@_*Z @ \&0 e-V f9D0$; tf}-]RA:iO`"y͆ a d1LܮZ]*zAq2|$zkWĒ=Q wAmgw>褃<_ vE^('AyMX^| n yd O :^;1( p$BC4X?_A _[V WUޠxb9u1d' LyjWeVܒE.`ujF)m"BUL\@pբ.RB0']dL1HCb(Q枖'u5握=})V&dɅb ^ۧ^,Dv"5"C>C5H64Q;CJ 3%PB\:> jCiک?N"&vD,uy^DHʍ Yكp.QIPcPNnzu@pJ%\@!@/%x/1˄FB @-[_rl\<@yO^r|fWFݡ  h \bv0="Dhn" %-0Yª.4 $p\ 4 $Dp*s0Ξ fe}x.K SXv[U bFaLHYJujςco(V'f%an~󍅫'O (^2]1y]@ أbd$垄 @4aAAt-i'wX`2^\lՇҜ  *\Mo]H@"Wfߠ2rDJ\`]MyoW'{2iAr™*_@>or-tAB3:?Pl2Xp}F;s,L4Rv2'|V&t^/Nr =օ l8d$1dTn3 @[/C-r.'H?VMVoi45 Nw!%uH2]./K,ʹ`ΦsU@ h25@V'OMa)0X tAUic*Yz<#ס5r5:<ʯ,+\U3bV&u莉tJpBtMlbMN/5doiRD|IoKȚ P670Aj{&T͸05mvnv>p GU^Qa]ts#2 ތh" @t] b' nZ`cwʤ (VJwy7o(ADI!Hs|7}Ci7ȫ l 4S(1x Kkp;\8ܕ7# (ʢ~_+^Y,EkbSr=.9R$ 7ϞG Pz̒7~C,xb` @<X5Ht`NǚK[Ûy`H /4_̞+w88EI5*F1 gdôx#ARb!xcz(ۀPy7 (쀫+ڣ ӹ*S4k HD;4g *Bʮ`H(ܠO;k8N`MV8 `P" 凈;7y#;` w@df S?)sZ=ODD7؀PvW=% ޘ8f0^tn+# F/,_T%Ћ;u |[  ̀út PSlylA ЀTɳ>?DA~G*wD!C]?'ڳxcFQdH#Iweɉ'\޶TR'N8SE C9p@၃K (@V@8 ,z4BXt=Bk9de*U -{Bp`A$qrdZ(Sr͛9o$5JU#:lpAr DeK[$G-4~yI)ԷeLW4mn P4CոY:-p`RMzT`w+b80/+'ȀT)S0{L +q352'\ Xk-؊(b|X\!2<2N:9`1ji+Χ#J@"ϼ+/.o jr0C+.p*l!= %5q*P"4AftFo1(n|"="rX9BZARWqIx| cJvˠ;zzJ6j+O.F/2݂+ȏ# {_89"lK=r65}v,NuW,F8 *%ˎE-nGcn1ʸpW;B_Ҥ{ '9ɩ4]sEL̯ t p4)ErZg@xڥT ^Nd ۾>D6y̍W71 ,Q1^."qGr0M>8V\<(CZj ]V"s ]@ghJbA ^)`wBN34cmT bܰ{;$yDK|Yߎ>%NѢ8d`7y%=]|v1ڧ]%WS-G} L ^A`j0'f`khI36? ( y#z3ݼ8 q! wIw&c@z&'bJUN(sIAr)H%7O2GRŘ®e:^E<HAÔa׵1",C-h3\!yHި 9y DzPU|G'wMmrxS)B#dsI`".btZSr'Q5i\h0QeOUrFo)UdE6J} 2U,l8,B5!j籣~J'SCLQ$'P |+Nfv֏Z+1ԗ@~\HHSKY95kRZB/B2bop:v ie/~ogCڑENg/5U <ubfk1`J.ny.gJ}$PKҨ\^+h"V,C.ܒ4*ueδz7No'Slp_2V}}Uꗗ~W'qP)B`Wp"& mR\i&O[at@2dNHn.C'~ޟb9Hldx"9S2h '֩DcUAw1Arh{%Vr|ЗhJwVcEĮ6[&+*[֮MBlt 7LksLeim/FT&:.O@=34SҺrDEJ(\#yxm ) laGipp;eAtv?rFq`I,fgX>mܝxcEoS[P.3`P-@Ux`bS=KyApNT8giG^8ǀ^7Ѱ1׬q(-Tq D+IOZ6z)`e6(8paFx&' ¼yBFk)靠xwѾ54Sxi9w{F)dkK YAx KbzE>!=WMt?]V*'t~" JD pOS'J?F@E< Ng.@2+/wֈgNX8HE y`*@ Ġ xẑNBExܢx2HhMXl,mTH( 챃n@ݫI (!p!M]JNZ&@RNrEDof͢ăw|/%bv80+'8G__r('E` |@O@ U~J**] 2  r+.+#r"e 3qKj%ȪE/r0(*qf&?P,rk_Ɔ1S | K/S6sH>OD*K+OS+S}~*@4`-d@F~62/ g> +lu(@*2L.\"4/PO[j,)&'G l@ zs2s<7R=#4,>̀k&|B!'T;edfHl"J\b䐏"nN|r?NDMSR\* 0,fn#3oH#S >cGթ @.gw~"fDjsRQ)(B( +JNJ4AG)B7w: j蒹r9.ffNj̲F -.N Td_NCdD0\ f63q"-ވHP 2 PU[)fRQO dRְ,Ki:U&${L`(^KC.͊ͥJ gl-LWOtpN`u"Yo9>q u5[UQQ>t\~:C[` 9 fdPevhhU$@-Q:kVq\a)#X;1T!!",|0V"46c9%X"#&˅h1\Û:nB*?ks\-Aǝ]n_*(P\O"|t BXɉ,!Ρ.]Zڹ-55ԾS:V[5X\AO'Y|kwڲE&w#qaR"=ڸK8/Ӊꏏ Z%<=.Ի3Y]̣+-'Y~4:ZK\P.OBC7;AHp\{лO-MŸڥV!}:!|Hʡޡ<ԷԱ;]WeՉAV6e_V '͎kX!톦׀pCgil`zfB[7VEY !<,kpDME ,[2w.FƺLェ)jՅK5NO*~}GhYYŰ'y'}@M˞zބ/6AlX>+XH= 9Uj1~:6;r6鵶bC]bYLĮlø)ʊ>iPN?k'vx`WI- b3 yЌK/ƍ;z2rlydIDB×L|gڼ3Ν<{躡 ҥL5TTZ4 THㆊd#kmx".<8VyR(@4nQe0{"Fћ7;0l 5Z!)zII \C1M̛S/[&Y"ҎvD >LJ(RM>|+Q6ޯVrU]W_PX嵘Z(Qu=Y^Hb6*:PiG ͖WƛC!rN=5F!$ uX vȱRKsSL9iey^t>CSQI^TVR=hj48Xa[ e ~6vY8Wd5!^"f H)(fh,fpY\j#DNTAbTP@j"cNIB':U%vSRYȴLCH}ycޚ~{AՉ(v#jpe(]>e|! `x1A]ꅙ]p0d{V]f"f0k65@QAE r--'FւN2Iw,"SK<$3t-e2Զ n;.-z*Ǫ ryaycsٕ@c>\8VcFuZd:*2B%"]93 K04mFs4IۤMgԶf|ZʻZE 0 ruKa&^Hmsh50c _a=[8 TW޲>4}n !);(dzt¿~LuG%LO]s*K]IJƁboAZG4T!@ /h^ t0 5Fm=`|[@'30APoWbH1$X h@&Y%}Nu[ v@h.\+8p @d^[Ø4_ )J tfL0=Cv1Rv-3l Ptب~adNJ9K>,lhx(G'vkgt.M2L&ӲINBfov(G=ta̿ަU2Dxib=k.J '1PB/-2בsq9 8dЦMLA'{" r j,wRq.g=I|-yK"Jyyja7I({]ePiEZ|( Y1I2t<;l7NrB~&< dMtMJOAF-m*F%aLU.UJU%`kQBGoZQ br5IV҅]u%#D^{d}#!4v@YT8f0ϒ'NCiKVxXDC*8O໡]zC쪃 F)GE1! ]Gˁ2$WolyP^7]<'ʍa lwpVҡbƪ lLt?:K_rS%-ݴNN9<`يE ^DټArj G?.n-cgp|n"ŌO G:qDfjO"QG3b Kn2YJ$DN؄CsG`yݲ9& zv^Xt.pj" ;طb@U.*Dkc{kʺ+szdC m~DaJ SfpyZ_uT%U7"**e"cytW^9]’zgLE +cBUC 'BTġߓ;w^-|1#(M e/kWLXZ7" B )k8Uq0tE>U=0 b-XL,Vr%{ɽ9Sx`{80ю UTzly]P&xYǖpHh9߃*uGe a(MS|ƧRȷkq'3;g7r1cq Շ}'65C(x2W"yomgeegAtsvryDOh&2u`]7u(w0t@e~V7 [wU*gYħE|r&d!I2;hKqъRES RVx8>PPVX<h w307-cgb=hiWEb`؄@T0'-$B.# +:@kGX`2&"Y}$2^"\ ِ SՐqe(x 'I0uY\  k%V("w\GdV*YQ=i2EEi,CqMI0?7zI5D*}BȐ1Ia1YdU2 7J0R!x)V/!I\[B}kHh=@z55vc$d;&Gd_OʥN#.)aYcY0ƚC@@ ?P5(f_f([h@;)pC}a6x*|]E)qCPsPG(vy`VeS. y{Q1y͈(2s+u{w[I[OIX.jM!Ƞ jX:e| bN(@}ݺƊ{+;h`xuɨ @r/{ 8'x98*"sB^::"i[*GKôڥOO%c11G͘.f7%,M @ pI9TPO{<*^,8~w'g\U\\P eVKQu6a%m!6Y$W0t̵I{gM/Ó5{z^`;X7dCwIH F(ʞ 'qı|Nk;wác̃!Ua]/lǕg.U0uQ;l3P?T, D6z*\Bay 5zY0S0<[[I,[b^@xx@rh0  @ ݀ T=|i˺,]쿿Bzadu['se(QwR1[.Z͌ ]U)9htw' R*u5O>`=/-<}c[,8OM}Y.Q„rPpi 0 W&HS}'؋f+"DTVQqJt6a i!@lƅ}"țu.'*C!5o)uVLSX\uޣ{0mk"edn! ,^ ʥcعǐ#J(QE2NdDZǏBb(S<ٮK0-lcƛ8)ND=ySgNHw]ʴiҦPJ5ⶫ/WN^óhӪ]p[ƜKWݻx MJ⼿;n\+^ذYƐ#KL2dD-k-Èli ͦU6kzI%Gus=Ro4};!MX4G}nFE|jdխb"D`̌M0凯^{&Ͽݿrh`H1l%tw[vt(S u6Ly;1A8QLS_Kň4(`MXpùNqwՔbQm[㓎PcYՏ" $~e"5Xfvɨ&:5ɟ:nҨ΁sSg!M9H ΔI:(^ ^ܳI%gM%i%JBȳnu6]꫰9;+JG$ y]BwF'賍[S9>!OХW4drOpsߙ*+~z߮:ln9qhlVϳvA+1gG`@BͶtmB>$#ٕϋl3JηsaKp9Je7-,7Ȝ~lQ>Mr}_6ـv67Moζ޻fjcM-b(i>Cݨߌ(pU-d<#ˣW+jXcrk] 5ۋ49lY op/;?,GN[.P؇[ Ydz餃n5r^_Ѝ+1ny:h-id5tؾ(, B"45#z_ڬ3B䰈-jЋ`73PD!U7Qs!*ٗDb|Lb%!t!ICz|Η+NQ~ +^׳drr.'}J505ضU}̣+voKGܥ<ԯu_1f%O7Ct0әܦ28LcBnv3q+(]hNTSh~ډw.'^:"1&OyQBusP/j,ˤF3')hrqa FHq&:f% +Ju?L/? Ҁ{nJ{(n#Oݷ9&k*T1?~FA "a҄T3 &^Y]`'β*E`Z@Uqm.#ӺVqw]'EyNf:PՁmiQ1mYk`َh, w:ϊUeM vǖ㵊[ X>nVgoFm"&٧b2˜գEa|htb7=tYPr/lO e%i*'Ge(ý5E:}~{/v\Np_GV| =|YY C e uՁL;&Fq2Мf5ݫo&WujRl,*c`2 HNɺd&9耲,RZQ+^9Ĉ.)GMQo6|Ub0ͳ9i1gVnJz*?äq]i.괴OWu/fTV^5CX&K`p% Aom 968=i_stY6q/pnsaZn +@~xtVaW~kgwT \+2J4SUX`Їfa8۹-&NnX[|Sqߍl" ~rzVzhYkmGOpGׯse ~j*#'ŝ>.eϳя%c=G7!lv%6{d{ZS,/1u.{{WЅ>_YM 8$>[rDOǽG٨w^ٕ}$6z|H8J ݂]G!Ѭ ]A.n} ) hF;0|Jg].xN}ڷ}Xlm*K}|@e12fWowG{t ~{ܦAPMg\[u^qrXh>nz} h]e7~y](c-:Heu=w &t7CfweN(4jQEC|ƇGcio'CxyEh]dg}sv-89,grWq ȅ&4>achw7{hfRG6Gp4q68أw(GXh B`րv8b}2eGdo%v'*X{6GDVGuqx\U=z|؇~X%Nx/sXzhHˆl0/ʄ'f܄ wwG7jHdK>Okr8 8xzxCx7L}; f}2;[RHRf PtlfxH(vHYGmxJi7$fSՒ-|M0b^hF4Ij8ySdE9iB$KYv?P{r?4aRY^W}uCC2rH[3Iefۖjjln)cp= s`;r{h}yHaW[I/[&Uffə>DgHyaٚ*931,,euiBy3IW9.&flj&@n.Ֆt9y+XVlC9p"Ş婛F1rذ*5֜FeE7suxQ l~&I:d`ZȒǨWd`Woؠ `)PỤ&z*Ydu|1YyY5zU&9j<:wHݧ^r v:󙜣Jmuuo)): ه:8yRÂckȇH*٦!I)].ԛoTLwFʨ%NA8mBȩ7 *l}aۀ Vڟjd5d>Jr"&bŎvyJ!z {3[˺ +?D!Kk)[[ӻZYȘYx7ۺqJQT:-bYYr+DK1W0;9 x;zCp7,SנVyBHKLG\RMi•w5}|w:3o 'Y?%eCUQ0C4lT|ն;y@B[D(E~;-|`[';W[7W,[ٮZ z\CYH,Vg<lBknxY MtvǏsD#5͠ Oqq+wohk˱Qzl}$I-)Yy[>l~.fQ=Iht:Wʤ| w@̱\u⬺e5ʌr Xjj̸)f?$ʛ,֩I<7l洘7׾ߝG)״XNM&r(ԃLo/ҕrp,7u>|]_Ϋ.Q9'ٍnnyB6 }^xkӝnȼ~}nE NfqtjK9;aݠ~MȾ^ Ğ~ZY_·點큱B.n[؜ Τc,,G?HCѶ铜8,Oƞa|YYYi/ g]y:SO]YɾjLM̌m:Zo-9 A -[~K4.-k 5nw[ Ϙ?ϛlu>NHqw1)ZY;yMOkO>p60td~Ϧ!-5QSZKYyo>ƐsdMP*ϻ,J  ֨_ֽօ }`:ڴ_T $X> .dAB.:ѽȑGv5Tt2ʄ SMa1uO@ 4(QI^6n8QYJZ:uF[Ŧ#(l>iբ5om[qR(.ݲvcGE6eJt hÌӲdɬ%%ڹPKEttsu_cFGv]kuko{ +^x`PMNDN 3}2̛yflG6ZkRax+@ :΅"hD u ̔93BKK4DL; tT2"Эrԑ7xG Gn:p*pZrE^܅^nʰSFdvb SDԼIQEbxsNRm!tA;*`dr-x,+E-3I&N$S'3E&52M sx*sbuO=O\TWB{?CC Dmt&!ItqRgvo'c:;DRUUeUNWcJYmUum_7`u41ƠVZ +' u.[>vej>t EME݄8yCz-QЁ~ǰBF:eƜ-ya皨8h٨2\b3ώ\}ᦞ70ap;!pZ8n"/!pd@MYO!L';8qGEw:RU-Z 6I&~AI1+lJl&\0AG*#'aF!wHONl$EYxģI,LD3 (MU6Ò.U)wV)Y6ħkSڥ':q[#\4=E!c\@9g_4 >뚧-qhpEu90ZtTu *ƞ9G5`T}TIԟc,MScPVO X,t7TuP})^^3Z!6.#*V4PgErQl/v9쩓Խj+[Akl'L7 ;q\dd#<;cD:P3@+F*PL3!QB|-ma Y+DvۂmE`6$G5KÐBt1Bݏún^oP^Bjs*PS24+tm`)޺/LH䈹F-8-F5P`v;q]e%wNm6=߄ScԨtv7Ưfq_]W@|W~1 Uf<&;7jpg5wXή2%` \B<]c.o4X4Ysҷt֭v <~@>n O NB̀UDv_}ƓSeNҒh N:i0Y̤!(\,7Kyօ7xkHϽVK;1c&v`Ke-Vƥѝ2;"IcW00O>-^!.4M\Aaӫ⽺[&kT=h1TA34=06".c}NW,*?K3s`@J$HNmɽ27ҋ9o"펱nP&n*wVdKv\|Ѓ&ؔUE(,x;!&i֥{ q?W ޥ‡YB1ⷽKeuu t}·>aD! JAV|"H7퀈e4AW(@ Pv wcag-73'd lkt9>#8H }Ї}V;h @9HIVо;1-Ђ1?'?'ۺ2;-3`д B!\9*ӎ0p#`%lgz9k> dVHt'1IKHD9> '8d{ѵ3+=* hAC!B"$:Pθ X#>3}L~k${*x9ȄAS?FC,(LF ضTčiS#I0I!tI^ܯlɲKzZ D I~wmlL@0RŠh8LdJ_ ʯ 9xJc@l HLKİ˱ƎF(\ˤRzK]BEʇvh ,νtmM8,$Jl0FĝL_qʧC2 AxMьȉCA$PF"?-xMjiFo-@],GcMȈv@N`PLIB N EzŦ\H4k,HxϭT,gLO엘Y*P9Y Gb`vPuHmLt``%CCDHJ>0c8ȬLFQ%]և$MdF -TD}$i+3;0bh3s U["wTOHWUXW[D{sxzK7xQ=`_R0wP~N0Sj9rE(B9 2 ed$Ѐ4'Ϳ( TE|Yj|L2kbp5ߊTH|iƓ9VnEDLT@GRxTHX Cx>8XKSޔ؄XT?JH>A3Y*@Ė \Y-mEױՄ@B9 A$C%#KXWD{2WiڑWW RX=H.G$*K8%VDتHHh(8Y-T$KgmSyuY%֍WL$>w0Qഷҍ䧑W_(]J9}&XN\FPZZ]{Ŋ[E(= ][e=] XFmC^^ +]ő|_[4'GE8UhwǕ(N~~MRCH],'a [UZZȆlp5d&x(֋}Hu?̇f^6Ղ(@a\1dcXzK][w ,x$YC6jbtLd܏[IXW)@G.ذP쨅d!fe[Їw2cX `6>k?|9C?T.ac>V?K8ABN va-0`d,H4$A c,}]}@fګAbN[uL.drV}`e3ƌX x6vkAcl24Ysߥ 4AHFIxoI<sFm;8dCjpBd}־SK2)~d]NQ!yb`2GO^S;km V[E@ Ȁx;N.\Ԃ,q7cKgc@;O<()HEoIX>q31Bco]tk@ruxcvЇ,A)!feZD WxY(W# xzճqcEH 5?dVwJ%\iBq]aݹ81d[GX4)p~G4Y'K"b:M_UBX2[hcR_E55 S&4Kk\)Ƨ  m$1ve;`bFMfFDಋc Ȁd̀ @ih.l>qɳ:?w9Yc@'#v@xr>eTLN-(jidzĉŇ 8[`COf -~lLi/4NbxBvm: ]@'wk;Cw ff FwMgeO|C';.lYO%~UQ v G04x#H=>ȃT7!؁h8D Ka~l3d_h2yctLVy73rp7z tPzzF_㧷̀ 2dwc>>bbpP~<#Hx!xQT%6*HV V-d|O ;Xy\X(G;Z=u8Gkko,h „1lat'hČǃ ={nJxJ=A̘x`SM@ hP4BΝ8A5TԘ"ň .8p$NFK6 ZZֲe+fq4U Gz zNI<7Xya %J6I*jǽzG#wnhi҃G2bb+ңpHqQ>E@@fQz $$_رf0 WK/ʂu٠bY"˻qCD,pHqFRΑQ7HULt$@ <@)Wb͐v4\%,4Vi E LyB @̔beEsԣ w$yZmk. N WBvQ,1znf=EPedyRGֱU&CiFGik |kI*UPqVx숤DeK*TvYrdguCb;1 VԈbO`xZ#B*j$y͈mZPv]}0/b<#dŗY}խ|l+@W>Ց58&_Oъ%pK0\AqgP(mr!H`qKQ#J"~ShIB"`5;xtݏ1'>~wMZUqk#cWUPkW\_NwL,lInYhdDpDŽA żQp10=@D1;XϪY-cf,2#%:;M~z^@Mt=܀h@Ϥ揆Z9Hgl쀳iDV6|"4(V-:HDQ z22R;.}ϸ0E $ڎ_]VZT3 \u#5#1wG!|Iw?QUkNt'<ל0sڂWM_pC5Eo:Y*E>o$QH5h6( :5/!w_M"Y!!Ǒ$ X:%`IG] =&K:tNysQNn` UFXD&(} dCq P籅Dl썗N8xK6S =$EXh}#)I:c gNEY6 -ΑEIa+ @5/(ޠ՞%5B-XCEoɐ '$o`qB-$@L}D`A8`u T*QIDSy&P8b)V䡌XJld Pۨe5$H4<6atn,oy9=\X`P lX@x ] >>Y\JNL_5T`Tᱢ0 لZHQWȁxJeae~-/43ʡ)HȍD58`BE!qG!/ <@ p!$AM6?&f"mtC|8a"MФUDD\X8}~@` afcKF!,)c3nmp p L x@7d:\9  cA=3% A< 02d%#@6C#A"E!1)>R`PR||=dTNEeT LW;(Wd͙TU\dA$Kd$M0EOO@E HoKQF(Ğ#)LBUc x-2 ?e&a_=(!\BalPOh%@z<^*G:|]'Fe@;pLRv^IU[d@ AH|$ <0Ppf fx&$LB!f=fTބ ov@#jZE%)"=C-\]@PuҢSb._B^Aţ@ TF}yzjLA{2i8p |1 T JA(R&m̥@' ީ^gY@fE8ى"#g騖\ߎ\_Ώ ֜0Z1iB^LpEL%RjQ)pID%F(A<@=f)'K&.kRiCIFT.$SlOB%$^Ԣd+~ǬkFNNj %ˑn^[\*M܁~Q d0@ DdEt"jj!D#=֣ ɩ1v0+%>Q98TÀdG~LLBTF)Tq\kaq:G=RN P+Ӓ5 @S(T9 h 4ۼüSn>3%LlA0 XNJc,CDH6؂@CM $Z`9fs~+G]j$ ~5TkTPaԛc^է:-*Ԟ؁Bы4%X'B0tg(>Cڮm!>4  0W&B %f FCE CNʠ^e\JPP,: LL&ǤPW{SV\ʐl@.in@(G'L% !."/5KC=TGB % @  1looFB Dx&ð/x?jzJR&߹h+hUv$*IRgaUE0+ڀl$pa  "!!J=4<4!BP34/Ş  "dCB")/ #'< CJ*^Jn&U`}OdnDG#'Лm^Z5 \   q2la!'.L9 %CoHh2#"2P2)2A8,2OJzYauk9.~!/#D^v>M_^N =⥈G̔J4?Q3LG`FEa}ǖ1WÆPeAąN*:0+8Գ&O>s 5H_@)iAr"t)+,2hG"@ujF0WIe>bVHfktZ4LCקueD@4e Pc2<>h %L(v3_lD]~uXXIBZsZN-_#CW41|kJRq''1.RdtJX #bb{f] x @4eO_fOH|A7} Av&v|JəWhn,*˕ozLEo|5P TyYE?ʇ]ks_ZcTxR=}9wGAT3SZ(A xgYNWveW @|KAATC/}԰BAkk A|f|yQi.ى58YI'nfDY1%]UuNaQGʲv@4EIͲȸNY4GUq7n~3X 7GbO=>V~9ìo7qYru(*N\ y]ǵD&hhNG y$+v^MMG~lzz@*w=U[5){W ںίBoO8|6V|`kaY'|D̝VUu13)Rv̌wx_A T@~\LX@ 7Mc4A-D<+|:ЃC)W5p끥2dPWdek RMZAe$Qֵ{J+{^%Et5p@ $wTޡHA8+Vh!DeC.ն=N\nfxp=$.\w} 9hIZVB& 4I@灞?)}4> @S_+p@ӦHiJ.J NH$ B-B "4+̃ s*2%aSԬ΂3LD 94a͵ MlM7 $)"θ"RNI$r%ˮv*H2`?ێ&;FA&ꨳ֫1B̀RXtR*rB$ 3/%hqRR0CQTUt3^40b;Lj^H!k#27# Rb b!'J)2PrT!b' 5)  =~@KQ -A*J2@DTQR1.ТR$½8b ЅS\ "DQj W]}W܀ceVنewމңg ݲESV& Lv3{ ;}jP4?C@`AaR*"/?-"TՎUlτa7{uX"ayɉfy!eygg٨^k^4kbMu?-;NFx)l*;ib\~L/RG5X 枴K/Ի/-A <0cXuUn$GdjYy 9E8:+]NgQx[I yыOIϘ廙MYQ;E- }d'8 )cSM/_3ZZ2 D.Jp, Cຒä pcnN\c~Y:Ptٔc@2IKH<70xpCLq@w Bw pV~^D)JP&΄ ^W0X/.!$CH/Vbȱ(q/<- B友]b@10Bk|RO*kt'iJRB:e8  ( ;O$Lz4L>Z b O͘,Ll5$ wD-ѣ~\r7\&Dt1l!-z%gO`{`%9*:)1XcMCejp—S9(pevp `ii jXS'~332#ʁr cC#jD_Vы"Iڨ:`V)i4НnK>-*xB:?fg;JR ;Ɇȩ+h dAQ6+ڪf9"l2qֳʦHlm~VO8suR];_y%ՖK:^")! UASji 2;Aݮ&!˿̓=>wAJ $m /P ;*ϾY'mZ8tb~ dž5a#sX[j\IuFFWVS۩q+K0+$0hYT?&rObts0U]uU'r`Ao}f@ ζWy݇+ʌяZQLh=6~MZ*0NjR6kͯ…!py7b\Zɒ\ -D+ڹbhȮ>/]s& [:d%b>bE.̮}d2+4ܞ VjmrF+o+cNtsrV24}ֶ ̉<QD>Kl NNU9-L;hV&XCYPrO^#G`zB}ߋkO~R;VAfN m 0kʼn'nd\17( / Ӌsԩ`"or* njJB CCP2@Gؔ+@x B0A:,&*kjIC#Ѝb)nu"/Z0 C;>h'BhhiPkmɴPip1㼜0=Z$m*-n LOaP8P!aÖfHlwN"rpzPRB=n>i [p(ХK&JOD ]13>ʩ6+`&lZ +* :B2c@ 'A7|+kn"tбQ ED%&MR'\OJ`B1fM>\ń#*D-NpCN@[ 0p xx AA_)g#W"H$ Q⥯ӤӺdIw)ͯ$0k゜Nb0ˎ kؾfN`-.J$r"o :4#@$_R%Y_RQ&}(Є*l/']"& `>L%@ *R+LR`TQ"b1J m4/L$Kb)?2oL'ʢC4Y3 )*E=JS@8+v¤)8@fB$dS(/p{v~.38AC59-BaR03wsg1%Ӹ&<..Ť[ kR֩H\*˅' KL,.(ab/$ 47eA`$Ҋ,T%186CC:Q:It&+ގ)Ӏ ;F~*0|Bvt\4P>(i=(*^.z@F“@ |SA.ALH74:I>4sGN_z"<O0fC&KP{t%N*^ĥ= wjIiJIXY❆!?BtT UoUBVeuVVW#"DW2!sXs&F[l`QY5^ʲ&$$u_ ÑbH^ZKH "R2R*@( zhf^)V_Vu\2:c2`_X%VQ6Kd g!-# .)N +c3(['l,uZgTSn oR2ēEf[BorVg7t2"TDWT*k:LEETI??1h:rHGslTP(+ ,w,zHloR gXA6oWrgyUp}w|q@:q]"l#jBP<=kQ+ LcH=`[+^*׀-,*^S~m^1Vbwyo%_wux6_JD`nFUyYb^p*ƗY-dBȸ{=_ĺG}@nAfma0 .C.~.r! ؔuxpBbؓ2%&(+x24Ȑ0 K D2+sJ} zva"o06IRhx!88}bPyЉ xFÛDQ3JJkØ7sz)-! J͌`~1DB@BFrB9X{wBb O1Xi*ɮ2j!2b%6$MLhY* LmtjaaY~ Ayg5*8t~Hh9`'GT*M9yT8s't5Fa>`$d&>%b k`WM~@ $D 湞mpM9IA!x GM&i("'L,J PdLSm ~9 (%y:"-uNUT4eyF()_\OS׾)JG{'81_2zl*ܩQ-2i^  aIR<rPJL؁߿~Ajb4` *H\ 2d P#RWpI Ta'\H|{^/Ѝ*Uekդ$QZΡJN=%QH2u Sn/"DhНV qE\uW^dbiWa(h0`$@eiZg_9@T@rnBApZ4WQEq\r@PqT]>vdr-GHMzG%TSOWiW؆'u~& UrA^<4"`g=_$6"Ȁ:PpBj vB 9CiHR=cNHutIY4e]A-!7Is*O&;{nWgQٕVej[) ^zvbDAX)`Z*aƢ{5_mj(Zͪ)TŮDKL8Qӷik:d$ZtGQ^NUn[&ካf{m箺5a*X暂|kfpi_~mz gHae)Z| #pډwPxǂfr\^@j<r! 3ӌQ,aJϓ$A{9\p[]<,y:ޛU95շO*hB[6 |Wمas.V7)2P %gfuZX|,\zm'`PTPk %="QNBFW:yIñ"n{$KPcSе+) :5#(A r.! m@ⰿ1Sc"u6N&O 95#hQ;T m$~6B"MW`MlQl$ d/V%t+H{BB\-J3b8/>"1 noU&6ʐ}%BվC jQy p@#$P!`d@,g;[GBKwTus\OYٮYJ$;40UI0.p[&TjS!R1bX5Ooz<5.@a h $ MH2)L DG9Sr5˃'R=d4CP'#"=w/}eT' qgC"qh{ ZQT j<@1Us8XF >hBҸ?6T"brxԚ4!0'4 $}B`2t5Q4V-r%4ԐZwF[y.ITUUār1P[Ce0-1/%(.ZjFo)p-mte8*5M;ؖ=8b"!q,aS|Ҁ5@nPE{3ٕV(D; ':`NX~תT-oB /nJmmWZ SAlel ?JڨpP^XBx>Trxb0D_`/f?~ ipuC)"Oux )H)16@cx4Z|бzM@1 Su΁'}p(rJ5{0M 0S މ )2V};Yl#AF "1SgnXz"P˭8,;้:K_V]gm}ME>ݹ-:g@q\=t)R%S Xt T0X/C5Nbf # pޯұzPKD r $ VZՆDPg9Q#O䒔)1f|֢$</_%uSs# Pc.N뒹tKrI=2b5j|`+AgX262d ?rKqqԞ}\yG:x(]Cxn „jA'|FST~y$:Wmm聉E-By|<5#wi692\{jepR6etŤ=S;EAY_ 4}DQHqf7lr;m65llvwruxWaxg6a%ڳhz1c! @\tq7cuPw>1~1'eFotv{HBAvrp[%rjqQTv]}XFCE=}oux~.~ kJb I%*QgKq!0tsnw>ojx1+^BG_\S07n@N=p+uMepǙNH IS#QG#ȓ$6(FW<[c:gn t U}RG[P/|{*iqYu{c$GwdЮ@9 9 ص#$*>E{NNڪ갱;greHPRrQE"6r*~7KsgK!SIt)z=&{Ac AEe:j9 Flxo_BQ+ KjJGx0*KফGqj!6V{"ᷓv z&30r6K#14"Ik|{?ṟ c_ʮTK{,; bPM`2Ck`~Gr6nˏ[ͫ_Uu="ty!#02|#b5bz)pAC;I N J{hU"I|HG U!T')0#|kKr'߆tm 7%btsV('!2DBt)Ò;{Y8L)J,TM;m9?qY9<)zX)n}lD]vqLJy 3h(+U2(YI1r# _-=W'1[FۮS9M+>RG߱kL Zf~wq@K'GU;Ĭ x,8"DmI"}ѲɄcEG=!"Q<< imCK۹H9ũH%2ڥcVRSe-p*lOz`wVZĀ SJ菞r{x{"̌sZŕ `5((bt }ҏQ!~n6W:<,uyY.v5U Ԧy@0sjטY=]P}^BsGd0 ͐8)Ã# xvrxC 52&*\l)Um7W 2/\#71vr;fLԸqEٙJ\@J02m5Q7-YZ4R0 f[MgL5x ֿܧX}<b}2 ٸC )~Ȣ E p{'6ƧܶAW{z}8Q{ Hđ\'Bd(u۬<^[*J/]+-{1l%Қ1VbݰH"$'r#VQeڀUKˮ󍿧t!nԇvEǨ|;5-Kq6 p `H km\1_'|bx6[#+ZwVkb*bc ӫa"|/ qqSI.MqΧhU>m(>$5Y?Yvp`f>þjntZ/T8>]3]u+.LHt\^J7WyY?t61~ 6 >{l8uhYb;xxԨK٬{Cɺ5 Z|2gocOo  w+ZUbrrmѿ|Վ`*E] 1.~a tKB 5ct¼Rc# Wq[V0qPn:n ʝhq@i娀kڤIph)o+$mL̈́ntA,H0A 6,p 0C 2 dppbE6l%Ŗ5 xB=}VB 3 jCRK>e"Ǎr`D>/0)1 OS!#E =|рƀ! ,^ dٸɓǯÆ IHQ"3jȑc C~Fd7vNXQKbI͛8%>'/'̘@ʼѣ+'"]ԧӧ]d)B -śٳh6Ƕ[ ʍXqavs΅᾿՞LÈ,̘Y#K^ϰƘ3k,o;'n%+^&vd~6'k뭶۸oct47SKsRixp(fSYC ε䛲ŋ[m7po%8E vw]MմgrQ(gv݆e݁ .(b F |wauMa_5vF$`;EwL ;5dF%un!wFRY8FX\UXyd#ng?6FpbQ81 䣐>iXe wr%R4} ;%mS4ؕcԒ~\Z],wc۞wkmz;ľ>&ҀC2Vdsz:dv{#ZT$EuS*&UvSkN߿F7+kfK͞G'FKv wYvu 9`#{+,??nnk%abDevEj/eҽ*0PTKl ?Vۖxuìڀ5.x#L<w0mxny[T_-xZmuM2ݹv>lCxRn:5+(l7znW]IǓ1/'󉮦+ojNףJ^z:w(MM%53Ǯ7CBxQֈ؟6.?m΋u Zҫrck):P'AtL W:#z5Ajdz_ ɗbrHfCBx k aE,VMf =iO;Ī;.by% wekю.dF($7!fG;.RKb DkbjN1Y}SuF ,Q*Pd@lRNéDZU^5d+FEut$YMK(zc༆J۪S=J}!T|oUg^YkО_*؁'2\1WyZnK]MdyYjP,x=loGK^4r'[vAGmk[Zؼ#6qG&E?R:k]5۝S< MG<t(| x4@GBtTǚ` z+. B+ .lWr2vl jMԶZlȸ |3l|٥rNIrﴧ} r^x҄LA4?iP;{ԤnlQ:cYf;Ezxڨ!%5a6-ZP#h6y.Iyzާ8AnmmN!>cr-ѭn 9q:},c|REyʚ=?-ބҤvxq;trn-S;xBp{ e.nd՟%O.s;e9"K 9|aI>/;5;HsM[Mq.tӃS!ߺkxSRV]FGEY}j/7lܓ xxͶK|)[ўWӭꏇ%vvwzW^^A0CPJߧsJXZ~Gt :qVtTh'lmhvuʀ|U'oBUV~8h8Uc>hEX@^rUe6by{H}Vq#af.x\TȈ}Y;?X^rzdVp#{愥{V3vqqe ȅ\Wh??@}!ER<Șʈ^̈xćP($H(8g_\h x\HfvH=pB*6z swEDWxO؇Gjm06phG ٍW9mfW?>PZ7:'%)tb(CB='A!QJ/ccْ~'DN#ME.gO G0'~0ImICI>Ǒn=2ԔsxgG8)DE[$v$ vW~Ha3| ư ^t>c9wWiٖextY:ECR7tE?Y 9 dIٓ@ZN)rjzYOй6adwR5\)5 /)x?Zz(3(aNɜُiu[ idvdgZ:YT%zxy셞iqoSAFIU_gz)\A)Vԝɢ*I9hYY z[j)ƌ 6W|YOYKE*S'k&<_:0.Z0YJf]Z99ZKDkN9OV<nD@[$SQZ{zbi4\ZZDjpʨ:*yDtצib艇;ysVsO}1Ù湪bjjSzڇt*wZ ʬ^] pȣrsP m#7+11RzT#j4Og=n Oė;@{k4GWt3^ǤGW 밥UEI8jرJ:۳@R'22s ڈ/=ǖ4z=:Y _A۵JG2*hKMO;=R2TZk^۵%TbHf{PKDr:˸ĚIp{:8;` 8 w{;{~&A|)ېd\C`Vi[7j'QX2C 귵{ )T+7PՆ)~WR++YBw˛TQ'<TKitڪz˳k߻s+jqA:Y7戮d ܾYz~;{X[K>t:h*kx zWpwk:EgkB (!I絻x0OY9,M<38  u Hܤ˯!N0t\v<0eP[ṾP,Y"`<OƵ _o qܿsǘۤ|,;nM8ja Q,_Oa-u nz,G,}j[`  Unint~Q 'd~2{h.o[P~T ^wX>ߌH-ŕ҈NJǍiP "Wp}^]YSJNM,j`~ (0U0N !dVh~,뮞 LɾR0~܋ǭnԨ.PU@ WGo ۭWEUNj~F~,BԾ636[ A fTכp/qgN^HJ/sP,*6P[PN霐 T _u_Y7\yȅ?󄞂K@nJߙPSho6Wp_ގ0` |4?npۂNZэ{/>\M!QPe GOoG?jro/m{oW`c0[` R^'T*K//Ak%L HЀ$QTp[6ԉ\4ҥK0c!E4I”TvKo.ͤYLs9uDOADQIeڴPQNZUYVWWw֭ T TJC#JӬ;~FdJ,[-浛7sYѱdn|ϟM#]魩Uf:4S^V-f @ x%>&N5zh0)Y2n8ɖwbd]yTS(mҮ]Vw\v]+Ύ8QNqN$;i:: )ӎ;oä{ BqDfC6n/?-N@@@Z.JI^hHQ + 2 -q8(eD,*RpOkpF1Gih l:T$IxJ ܰ4>{vp d-8t͈0"79(^L҅N:NpШ~-JPlEtW^˃3EHsR<8 2( 3 O "8 ;?F?zoZz ]jtqr߉ʞŕ՜{;E7Q&3K‰hth*pÁ~ƛysl{A&T^v %{eOudr$Paayc_]A"[_#E qu5q\#k G'` U@!%AY չ(;ރEAi#Ap1MS1F%gPՠhkZ<$@ᡠ]CH5Px:=`@4(}TVgP8_H %.\ FX/Vsܡ/~+# D$G:dٳE hjIr eD24qb" ҔWӚ*Ўd?; kg$ 0L0)(z{fEY &JX5x_8::F) :ru5R<ٌO(Rd@>YO(3Lh}#싁:\ 1De4"egG=zEO5< Rp6R L Җ6?)II%v  Sd2*N[ V VJLli]-Jys)reUY+PSk%zZ/n SJ$_nns]9X-T ΢ J>[F=6[ue-;`#.4Yum$j͛ZiN]aBB[VoǟT>ߴF*v#KW +0HUw+o nW5ZIE 8=o]yjb$`M>z2V,mB4H:ASG!yYGשd0 'Om>YFYx09wv5P.Xe %í-˲pg9;1c cxG;Q)4T'Y Sxu2 IeH% J2GZAp2 QOOλ͆#E0Qiݱ>)['jq !&{4+"f LQJP5Ms/*C*:q [ Xw04*a )%jSgxé,ʦ`~ r@Wh @$ /f*tC6oTz, [ B ع[n  v0xԃH 93=Bx2obcf3*TLE252qu> ! <]l6i䡏cP^OR`'!h5y璎Uۧ0+4ܬG=+/ =߫@D b X{UXK>9X&{&h,о;Uc'/9 Їdf2xA*hV\j)S7ԻLE3+;G36I.lýYӚ@81@Nk;` ~)X<+1(El?'0;0¢){8U{=AۇuK>H;'|BNP( <+4ywfkb;} F+3T+˲-;K xFhL 8yE(ĘA賅I+0,,E8FDϋ%+s7i7=DciAH3&\D@ ˘R㨵E\F0E)i6۲xFF 4k;(E 1} .[XrDGu\4$xHdPDz$;~rG; fD) h;H&C;J:z6č[:]l63H0;bz,;CFcjəyC .Fɫ$4Ӈi GK4P08.H7܁`'J]:JuAOD)c?8`OmVH@ `KA*ćBȉ82C)2L=.dܲ@(ʌ C۩Ʀg⒏G|3i0A@MuL?uv4$Ђ͚B|cBZ5!;8&* O`VDXQ-Ȃ3NK?Ҋ4'1,O).l&2LPet,O8қ3)ɟd+A8tGX?<vD S ҂C8o42.Ui}5wN8$ lOQ2'x x*|N ͣ8֣82L&F'=J,R.  Q3@|H_I0A)HEm}:u;)pDPjUXםC8H}p}8Q~؇m`@(p20O02q #F48bU|!U" $!U=dհLXYU)-Hs- IVb]YcMֿ3gm[GX4)plS?@se^ykp4`G)X& ,=;0sZH#;Q}؇ڮ…mŅK@L-ȼ+T}1Й:䝺?H=ŏt=۰KUT*Fcܬpx R%\/E\z`8?hH9O֜V7ł%06wDC8=x+.z;im]}]ٍ]ŅIMM$8BH8PjsH^v JU"]U#eO`lR2X$Ia Z:LI,@\Kߘm#hM<>XJ(0x@P;_A뿬KuGiPHY]yֵ`mЄҰa1bBaAP q@ޙp[aa7Ff6LQ6_&H5OU~v6Kʀ 8à T!B*P_9w(r\49b)00c`Xu;. HWօn0=Z?GW6dCKP.#T!Sa` N-\F8eTUk{e ;H"RT&6BEAcn|whUxs̃GxmP# eDD8u78^ިՇץmtZOކm`mmpg|gN T)tpᾪ>Fh"MO@fSf<^Ufk#_d倌Rh[[V hÐ&LDz#rBi@xLLI%8#`1P -HW k*aC5Y:5}`] n _`OC>{ʭ6Fk@kh-k9BkkxlϹ&̒뻾6U.>}R 8l® @ŶL[l7ޭ@NH_͖RHpm#Xwp$` 0NY8=AN.HAtfP'Ek}4nBv(1 dAB´6EČ 05l6He0KlVb&e΂, i]\ŶTNp &`,%@< oFڡ~A=_hvxڵ]_(tJЃ3Y`xʍrU.|nnm4{I{VGȃ2ݷ?xd x}q{qr #EqcsZZm4>ù=)k'_- *v22IR$I#0P-Ə$Qբ]˨RJ]IU%{qذiSRg#)#!,_ƜYfΝ,1bP7 *|7'8WVc'R:;)9e dP1\BEWwBU07FXa-!c=8YvY yde|fCi۠ZkAohPA @p=gk=@Νd 5Y])]#ذPxGy}$~2Sć)$;rV):=K 7wL5S$X9O>4ͬOYM2 +! +I$%rq"?0WyYEBc:a !e&SL 3i$ID,6 &S# 3ُ6es#Ibv$:6J$N^"Gqiq0%Ȁ8.@N0AB%D"HJDIKi](UiD:NY@PjQ sA{"V`>&ьf*jV .x`F$Zgaqد~s+(@ 7 gSPRq T.`t0D 4%.Нb!zh؞w$Q:$6+, h|+$ȰYre֣|f=tSG>I! M d@'09P!m4^h!l9JC)e%$[yH S$@J^&I'P"t@NJye5VBt"b *IO S  );+= gJs\Ѵ Bag Ơ2 Ӌ`x얐W n$$ȅ :(@"G89$)Q!,NP)*3X9)WJ9P%a5 5ŏ ǘbKB@DU@A@5 U5QGUGMkRyX1$,>&u0#XgP  ` z<$Fv-9!1.89lm08ސlc)Z9Iқ$ʔraai XWJ".T-NO:j!̨S؁ J 9` n6*U|UO\d2c `XQ J i&yS?i!Z eup{_r  P%8r*dRr'U\!Xʦ4ع hxã;-&dA k!ipzc*$&smfqC+!0ȠM0D,h볦-OX2 hd#"_p7hN@gH-,@u oC $qsQ&cGU}uJVIZCz"v7)mB Bxx02?<y~`}ũU1+6qn-UzڲjE2$"Ef&ll%_7;KpYHqT_j&o778Ts N&(lhjtUPi߇.x8 b.@i9bX*>!GTQpR$,%}T( =bx  )>a+ņٚ%®jVA;ـ ] ͗ʑ"i)i"$NŃXސPre n|iH- *_FI$DadCKh A05 -cTAM!*0FA 4꽙-AHShق% LB* +L!Ar2Ht1$&iuRgjVEJHce3'b @}RR+KE˛^V`PcbGA ltGi <݆byثELD&<^6N/Ԧ4m9Ve*H $"($u£7O#uC3LT ̎fiJlmPTnF֛тlg{:TFH IJ}k v(Ÿ!!"T%X*%W"-~lجF\))B.$~eT.]JG欝ZF=GC5T=&%q+EmnRT5-vΣBnZ.\AT$@:f 2/fAA0-e%AG " S{e.ү /z?^i,c"|11z֛bY(6pRT$-FCfٍd_yVqPopkMڀ8nV@0@J[Hİ @2+ Aȅ|@ت79eCL1ײܒ_ $lq`6ۜd%p+.h1~*ftzM*yhJ00Dk!/r"T ܀H2`As O3B(Kr n-re2,?*-N Yq@?C@n(cI{@)mC=T5_% @-KGn%AV%["UG^)77ZBڎvz s""sЛ ,=#AA>ĒB*p!AH x P^̓<Na8ڵ-o`4.{F`yL($xd2j41فcNLST"C26e2uLmHԄ-.sE|:J5r $lM$Y [ 8gC;9ڨj^OGL.?:%e|Jh$4b'9_Vж@ =aOE<|<ZVg>џѣrHrHA$4pMcϾ*F.؇=xgȘ:ڳ}D|O{rb)h"'9ePb\H/6 MĻ~[~w\̀'\)LM-tsPܸq(VXC9nXd#IeJ+Y\Ith{uC" 6r4ͱԆ HSpjA Z(D@!+f>{W5rȐp&@ Pfp &I$(Ir +Woqz"yFnݻuۇ<YqyH$ō[;t =fwELVew/G43k̹ϠD 8`5RKJƒ4Р 8@2 ڢπ*d 0Hp  T 1AZ2>4NM5ք*j2x-~ B8LrX&x[Î&N0$$͕8[bϜYG'| @- :D[4A P- D JA ˲Rs`sI+^+Iv`I&i+cι-IۅN2934b;̓%:딓NsWSO>IS[t*jrEJENILz J ѲPt:dED .`<^G 2X!xcAحe gyxZiaa Sp&lG1nbh\6m ]vLݯQ&SzŗS 7~XDQCObHձZMx(c9R *ѼG(ek1]i. 眅uMh!hi^}i*j~Loͩ;su?=v/)~nD5tnFU * VlQPpKKD٢z+)KUsUv(#]LԨnuVh:IhNnЂNIzeGxCR.y2&6w=DJPFgQ3ohDE~YY*xd0 1 bv9t0K<@ԥnHi>.C >x ΁BuB yb ]b9pxuֳ7 1` gD! AA0.c)Lf nLŬq0# F LAU@3bn+&uzc5 .kv4pNԢQ3#/BMR 5N< I94R׿!r)A(BV NP]JaD.L^v1 ZXT/fIj~R"ID![#Y"6̴YXBYOH3Y*>z=eA`:TԠ R (!5ڐZL]=W 7U곫8G' խJɁop1 EFW.Q LDTcL1c̠- gI^ .;kjn2㦏zƴ*m3W%x%}C*)*X R \!ԑt@6TITyGA;AA0*|/r9|fDAESJ&:+d F2FBtUF+f iftt3MG P3 "2b" tI#u7]ISJJB)n@*J@`*EO~Z .Ù,A{!BXq3NtUXSv&hb|)*o\PL W]C`T5+BW0+6zV@7YlAm4&4L@xQNGЈ)&Yal.d(*^;0&Hr2) @N\OYq=!<8HjPY?ҝЄ_Xmň\cp~١4UVTs,+$hrSsT'ي6ehs&G3 b13Z-8z!JY'Cp}ѷOz}-^_*TK2Ctp6duc d5,h҈=B_ܨf5Uldβ|4:?Ax6⪾,2ŚtY٬ϺRqY/O] oh.T'OB`B֚0\-=G4&3`fGp4ϓA9i"[.;4R7[1.1r;Ѿg䭁[So)8fUU nIbX{[) O f t0Y JO#-׋;:́7l",YBϤCA<{mjS?'V;k.~NKg{S7NB`$9|,[H63/i!lx2\ÛG=N.8^ |c#V| [oB*jWq/l$uA1:<=x%2$xUAA<#Sn˻ڀKj`So9&e|t.+mP: {S*N֋^&`;'G:?SkPk>%AlA{OciNFKݝ٢H`%K+q;ˢ0c ,my<;B @bVv/eНNXFM.NL] AnAA7˷>lݹݝD ._u[Aju'/B|@t؟h 2R.b6كڣ AfaHuMԁ=a, v tmݳu~ |.Yz,"Eޑ(n9`9;10 >e1dSeϹt 5,Á;@a<nx_n{;]n$E.g$+חe>wRF2~lz3x7yCԡma&a;r 1c!O1ĉY1ƈǎݺu|iBʕ,[| 3̙3 i <TP*UaL9dSxB \$@ @Wj85رR4xPah ² 7 DyA5 ``A8y$K-)S"ƌfb:{lSpΙF| ԤIcf3,5|ouċ‹g1Ȓ'S|=ɓϠC"w#8T[RRJ]XjpC|mNm{@vX%^QU2 Kafhgoo5QDtΊO!g7C?8pCMU#ް=}7ePrQ) _*Y<:ak((@BSM1;yH^ucãMʐ2~HQf8'D4F콪{J! )3="`4(T.s1$Ls!oh$ ?Cp01w|6"W̊KEgf/)?1O< LÜ cvlb R Lop$ЀSɼ i&F-(Íta.7R t%4E"*zxXXLQt_yKpqZj"[Δ3erYz +~1+aiVjBlB0 : [Sx!9'(r#!9Q$2O~{@ZZYQSDB H]"YDUn﫣 ksbV w{Q+[SZ@Lm\/Gj4pK4E(\hFHؿ1J+r`rjkqjmQ!o};EHBa ,m#tڦ:P`}f쪐ۍRT nU* mCYBLBi :~FKhE)]{QR2~2M>Uc;2! $ҟD3fbI I(mtpZ嬑·V0 ^Nr&$řv.2.,ox{F@qۃ`Ms\5m(!A~”˴N_az f- Th<53nvpJ ,Yy[½S+fx>"[DY}'a@nf4HK*FsE(,IWHeOPQ!. %kJP$躅MoGl8%rq>&\4A R ^kEܞON|eVƕc>@LHJt%.K_@Xݱ Q3PΚA$W?+q7*H":NO ا,ᕭx`Lfwm%b7}2z?13|1i=V4b<XJI0uMy0hQpI·HJP@kwvZ3uх>WBaȅvMER(& hC}"HGSDYu;ԁ8d\(7|#vZH!70f)r`O6š$1E7Ji=PI$m8-27ƅs>)&Ch]yQ{$%y]z2h@A.Ԉu. !ȇ8cp\@J`;@-H%%&qk8p,!IS7QnijAAQd4wD1{q:c(5- Ћm1%mU@Eu?C8HD0SH^#d4ݘ07X;ӷ^M1؇6U#`eXSɗRZIyZDsޒ]gv\BQVqØR(5c`)9 {cRXF(G = ME!8|5iITH,,pFMUur!bf' `9XXr؏)3aC$\!VL%BY8_@)؞I2ԗZ3!6BdP8*0Ԥ8ȟ"r<ȉIr+z^ &,h.YbR׵oR:qh0RE3:FuO!u֩@nD< r48ɨ<9EA2(3M51yS~h2B GwP:xR*c(e 5aT8YVSZp&k oy Ԑ\>cVdx Ҩ{6:>@R|9@%#v옮P#5u7Ik|xȯePVh{Q)b68dYmZ ЋZ(H{e*$yz ':+=)2u!Wɨ8{Eʙ2Be0IjxqӯJN=3guoMRJ\:UKeށLG&naz3áxj4}z8`LӢjh?}Q,{ =z˨>Pk2ZT0W q[yWḻVjF!-z\a\ PʶO}׻h1]AA&t-TV:?gTN%Z9.o}{|:d_)[79T0[8DʸPTbqK6? aU=; 5%!Pz\$#}Fzf19NGcSdLvi@4xӉ<в|z7Sч!'l)Jj7PNQ([4ҶFLqBO6Q0 0BCV\MR K' wZ6\ eS5xCqϊSn]хjSs7I6D `LqȄ6  }ZDVVTކHTGN9D&RA33̔o ;MUjXCdށؓ@P ,ҽN@@gv +.c5Lоd %:pU:FC{fߴ&%vwX{<01d%lܓف9wL0QB߂vN0}+6Tp$sw u6+~vZ型kbX7-Aq~]+ۋo:tkG& ,.<dݘ_+ ,BG~TRm #jAdˍB&+k/¢` +'z/p~p7>ԭ +-H "@^Mar‰$~ݎcI\9N΀ ӎ(;ϼ* rϫ"K> A ,a@*#+TnTzn&;AD1T\VHrt@Y!p(49$69(%i)M,{'. kUKRHa[23) +}) ڂ7؏ ˫ ,AsP k@:ihJ)- 1{i{znK꣯,Z[`^"l,(`k^h&3Ȁ[n*3\f+ּF] KK! ,^ dٸǰ!#JHʼn2jȱǏ!͝ɓ!4J^\ٰJrɳO jM,SǍMs<uԫ8Ƶ!;Q͛ǯ۷pʝ wݻx[s.CޥKp\mN̸1bP˘3k~{r[,,`-ET.魵ֈvDl ]ϡAy,j&s. s*vcjա>'u[))< lA7lrCׯmS$q|o?ds;=P&v^CC:FNPfd'DI)Q: )O6$v9=*iNyf)Y:™ԝwOp"YQiٞM6"*!mk2*hKe(*?6-67XJ+Vus*&:Vv^5j-ȢcP+T.!B0p # pSe:^o[%cwۤD7 Rk*$P/G5&nkl%l&Ê,KI٬^5#p묫܃=ޣ)N#Jx^5G/GߪƠv~6̈́ z街V L~ӘzhA&-ykRW9MO`WI{TFs@DWv=dbC݃՜6ر ?a@`'"Z1%(A V0SG5\iOS v $$ B9isrAza&`ȣz{fΈl_8=&6qqňdHI ZXP M{bMFFݱHj:j^.Frcg ݱ_yczC})݆ !" Qґd1 WJ^jSRM4!p?UYğFrđU ,OsdÖc.# sV; G$'j(eˍ6AeÑ`2ijRU wP)щUPC!5#N9{c ?oAG>:H )TCFQtq.ANP`G?:CxVK R5NMob" ,uY%mڱ8iNH 4vP cbJS=nR-&ܩ mLԢY5V3&AjVzJ$9it3M\lJ%k,'tkYxEJ>FEo[,XZM(+d"h ^Ei:ڵkB\G[ ,XٰOq\úp.,i;bKcZ }TKX ]DPͻشbo5X/M#l)}kja:`eCP;Ug68L7 qH2xg6/AG6W [lIk̕Ηt=MI_nDLhYֆn2se(O~0l]y7^3+Vvּ9S[f`##KRbR;G>d~41Ig9Èd?MsZ-q?:q @kwRVuB`۬&vyo~Kٔ2C}Uͪ2<^mIYn2c&]j[J[zq˾ڛvd7~t;?J˸eC!e@UgI܋of8[+f kq\8cb<<|~6ys.UorX5Yvq Ygco4X˚VTd )ozz=e_e]}k@<щ~՜$tw~2uwkzq`j'o,qV!5b6EyGq%wn~nQ.+~F5$!7SEH,;(ƔFBoCm C~|2~% H{gxGxꇁxMu!W%G0;Cry|Ť;gaFm/3˰HG='le!l~{5\X8\3do44lhThŇP05qx~5jc): =l(cb5~w(yV{l}eHNyYw6z)gt8~kh~qX\pdYdw&WGӉXp)VId؊M'.q%wVYrf~ PK+¨?"#+%(1wK(:kt%ix'^ '[09(E~RH`(\XwV|2a$82wVBeTX+{@7qj6>䃐 irE=SIHhM^<9`pHhxO+inlu*c`D>ب=4Dt4`)ׄ_G0DYIٖGY>ÔOtR.,X Z@OT; \yo"Gp[H?)Djm)rDGq(ecxcz|L~IiȩL@I HT8fKGqscyy{ [YAqb=Yvd鄬َlC eVTiW陘3VO4uv7Xs]ٞ e ԐV)i9,&99=9?=|3i鞐Y)U[ YVU}Qũdʔ 頲i6l97Q^8VWУАrARxe**kԀܙ87sӡu6~ ^9p(٠jUɤwPG8"=] =o g"e9?nɠ4Jz!¤pʙRء4ɥ9C|ک:*EE!i:G|Xh K57;s"W_?uorŦ`!XU¦y !juvr4Bxi ^svC(*UǪh *g jD,jw{)ʧj| 몚z*III*DZ ! _ښ:G7y*\u˧o s P kؑg[믔 |y~bH`y +|jȰD JfdRgxJ2&;IۆZd㹰CdZUe7\`NO"˝F\64L=G> 3TRCu[h|۷u$0 '%B5zID+Ce/ O 7w'JH`B;sZu YٺI5 {Pt{Zr}˻˹o+)$K 17fAʛH΋P5 ʬ!ZjJZ9Qj8 PDrx{f;q߫oˬ*Ҫ, +p8Ǹ|#BBTs>+p 5t( -T#9'GxK9,;X= Ģj$DlMjJY@ P )G6)2X,€[×"kh\IiEp 0Ǔs ?HwyЫ||Ʉx莊N[vaLȍ+nl`q ǖl 0Gs9dlL$L3|ʎi Cȋ lȶEˣ@Ӕ ̐sO$M|dfx`'$}8lڜƋL>vNPZpWRW[-])Ηgs7.K0^%NvuØ}'"PT@|nTn؝ (~мNfA3^T[vÈ|hG>^NnvP b?}ŝx1C0.Uns<VNb|7Q>c`l@^+?^YV7':ߕ.C\@_dfvԾ꼰mQYSO9Y_k=MբeL -MCdsos`xim2^nMEE_Ë_hP\woOOv،mbm?́Z_ejU?X?nTK=N Ȟ9IDBʕ/dȌa!3%2ӨQQ^KGDY̤k֮K.)9qҡÇO@{%:QE YSQ>UZUCnWaV Z,wiվSnڴm|]0H>$Ќ3WW"Qx$IŠEZL0oϳ?"M:V+Wخe+u=ܸg3ٵjۺ+ݼwDI ?eCǑmcw6,&?@hh60 z;mݮ2)/WLNt}{85Su(_ۂ{5~kg/݄|ֱ |  .wS?PKk'3ɈD192֤-)xk, X6|0#$@r>H 81!ŶLPƉVҒz]quHCHK͐G(Q,LԏƖu!$$g?1(@#g*Ryd:I'la =|V1utztm0(' 烋l# uh`TIqR l' rQrCq,nFUz->$n%irX*ni`HiYD^2eNёKz0"jm> N8Ysҍj8яGD;[zlJDX–ip%P  zy>(&TDZhRs9lH&-x f"8Ynh2g,ZZbp&B a>A"Q$l΂/@JmfSM j8`'@5+.a}&p\9-g*1 O&d!.,[lذ*dԅҮ|@h\DW>oaj7VUH-b0;!+G֓][aIb} C0) YNCY-|V$:1K@$=dKELY=sTrZg ث`4_IP%jAjd#(jr4zƮns1koDö88]vxiW;hі(QF@Go[ҚKKس2G~n h+Xm{c n7 o$ @y jP#W"Z1^t 5 ' "Byr-@z-_ q sJK&QY^Q!n(=`13H h7~+A@Bp+dSqrc8DecϽ 7dOzOݧ<ʟ h)7ZMܒ9O2*>\?@=&&*25cݣ;  c{)P|ș蓾 9;3JjC;;۝iH(c? . bC 㿦ÄG09)@>C@ȃ&8H@*S> }X&y>y|;xÍ< ܥӾ;+";рA<j¤ `pC+?&5ι¯ȇv?vhTث<գ:1:zJ3ӧ'`<mCyFyy+DCu\DDb+"x)ܾLJDPh1.,2J HCB .3@K@+] ]9E`'PXWK.#0؁:gt3H6sCkF}F_i@8;dt :ElJB!;D/j##/1g&b4"=oE$s?K-O ] H5-\/7>XE7)X!xHpI p1ϙFFxBh86C5YJttJքGD x 㽪TJ<P9,HXOڤ 5Nе8D+iF]+ &|ԀB3M`΢"Tˤ]UN6̹"Z\Fv-LG=dPbEޝH^U)[#̔Lȃ2V ;ąPhD'+uu_8`=Z]O,=rPN N ^]F ALޫ@݆XNE,8ޤ()-8NPb{jvp'=]};$X\IfhPDP\x_5b]eS+pŬMcMA;&@0 fQINxڌ4GfB1ZJǬdOЄG@R5^OV\3+| kx{`0Z}C>t\H<1V_D ;+b83.c%jh Lm&ݼPhAˠ,ֽAhhbw>WM4F]Fg˓dW%h`,S [%յuhtOOOV>6^"^e␶e[&i~؆G )*HVIH; -pY2ijEэc+*r`&8> jjgCm!s-daH]|%g&4ӣ<~톦3SЄ0kH; {s)]lw&iw2@΃IhVl'H F bppfվfckMjj fjumsrrj٥ 7%xNr{X3Ba`v'VhTP6جSUڋyy IWUID(g|ymd%_׷&f0LX7hPְ` dijn\8JtȉH_+W u9|߇0f؆n[sf(/̂&&I|Ԩ BA͚Mq'R"ƌ/#tF,i$s*߱TnݺmJ=AΜ:,P ABFx J]tԤ d`50XjV2d81,Y#R$I%JЭkyň1c%UU;tc7.V0=ć #7T(1C wDQ˖ ]~W(#kfӮM;_,wS7ױiNfiۺ1&/_̜o(O,M^a 4EƊ7o>$HK\fjԙ'OSEQN-U` `Rd5!W p!`Z)'55W\sݵbz_"4缃X:0/P#\6,|` l[lY9[>K84yu5njd6b#̹.P&B eHĜv .PcM:c[|&Ң~6E:R>%pi"NMVNX*bT*ZPA@X)؀+jׯfxb-HC X!rTp D*x@eܢkn?ڦn.Î}So7M'aH1sB.T'xjk>aqJq~!__uɗ́NW3;p< lϵrhD+]Я4fb*+ 5& [sM9YeB S9"3qܫE\[lɶNK춎o}6p MP_YD4=UB8t"H9X3 "ٜF:ρkT 6a\Wn(V` %@bb)KL6A)ȡ`F,$/x@'LBf"E\X(-*A9b~[0AA%52=L6hBLAT ۠I/|uIi]9rB0PĤg=#Xţ=e=>F\`?r^M%RqkX5$@%_:Sm)VJU3AYA 5$]$I .YMKb/#\ lQA Kœ2cK1&8A@ PA"d!!4U&Ą͂5x WcFKXXY>%##j?pFg\]B^_Q%^/YQpEX()[ HeVTԍfn&t0@LL܁Ө J kjt8/$8\Bd0Z֨!4dUVhA;!5tzXgۀȣܠtV u>>0`XBB%`V:y'R"Wg.-IMK*h6h8_N˱;H*$60#PD)'f4]V0TClj>(tMZN9 iv ` ܀\.Y@&lyg"RHwDs"T J|Ѹ- W8ȅgR@hEZHٍ/ LB*LǢ* $B(B(,@܋ZGӛƍjs };;ꪢK\>ևԪ  [WN%e ؐx0+m2]a^@LkbHN[e2 QYݝ,(EE /HK4-С!(\%wUg)lU Z6Dl?jtV\Z.b'v`U,0Xl .N'ڌbXⱺMB!<-]lAmld:dnT$dO"rP7'k8_ԁB-Fj4-*gX-<~R,)JJ?n@.Ygp-. f_Fna.ҌQU %LPynXs5dF겮z9/؀-$sw,tA@Y5HB,&] hDSAy|(z`t.'`CG62Y7C3Iq'h/Y vgĴ\2R Wf,&Le5QROZ.h"4mAb^ (Vg@x= d2uZ5,7ZRPA9D䴕2\wuEЏ_?^`s`{ta+b+#އb36J{#|' xfǥMl.QNߔI2ٜJO2uR^Ȃz@FTӅ^ (onp泞ȍtb/vcK צt~v I0q43pUPPrgO%sxAȊT2\ʮd-yn :z 糃 x @ PPWԑ l2 5z(sG҆=lzz`aԇL/i9A31ͥ9aޒSP%Ep68KZ$<𑈉LSd΢a;#oX#{o A;{  xO1^¼#WrZe=V1wfzw^J7ޭU ^Xo}ł"&##}~6vl Pü6| w~緀SlEX6hCK=EFK#w=tDۇ<cЖho8 =*@侚f :K>E9yͮ -GV]\{e^k;ϓp\ps x)wsgL AӨ/eڴ8={8rWF#?3yd=+Yf̘(ilygz4q4':t爾SwtݶfjT4+V%aDذ*l$J*Uaz5jռy+u߿<\rV!C (>L!È5HPr I8q$m(Th_}{Kn+VroiȌ߻s7\ ˗X;jFV-N:u%6lji^Nwִ~L0~MszY'fXZ0 `˫,Z-{F{n8<}?рU q B r겹(/)z֗W p:u$Ә XwVkF6񊗵KLZp;-^׽d>A7$P}{_z ~Zп%3 x&/ hS&i1]WF2I#Xe*o6c!o\ȹ v%E2u/g!6"Sb=Q<b-BJcFq@Q}舫 6p/Ԧ^0M`458iԻ'p[WX! [i!>J}Th,m),eC<ƎQ),0P,Ѯ/qmnp/bNYAVlKWZ;IOHNAB# N'? vA[PX.p!5, (JHl 4КaQYUa^գUsr9wvQi+H 5-Rjl֢Nn}k\UH k*4bH5'TT}Uj [+(8NL(F@Pk[gSӵ_4꣕qh"SEe]hX$&i+v kY <9{$!5ʎW*Am~1[ RTnu@mߑR S9M1(LTtAN%Yͮ;HkZMwVy=^oe!.H1pϨ/T_$ M2`VVr;݂e mp _%|v8%/.˗%V1ü_ u@ؒ UwIgAڷʓw)S`)XY`%ERZ޲a qA }f4[up+2~`ruǕhvRQLYY eT[]m*Q%ʴjiPm&)zU8q)LaAjE<y6fwņ\:]QfeTICcECx+8!Co/j=P=i9͓%OF({[[KvZNA^!̬/r>v@_TYŹy҄?϶؎#DjZC* QHB(,Y?!=U\`8ߓ; :R@,7E3LZ?W-Vwe~g錯shZ J؃`pBzD" I6du7}_"ݢJsX .mcq0s_:`٣R^.X=}3{>ڝ.pcfj 6>J " .N$dɚݔOj /M܌0V2ujDת /0d3IlIBf&VLs,H1j0+zkмZO ܮ-- 5"%&,04g܌~kwbBL7#Sְf0H3`dV gf4͖0̤ͱT0 @>d-.A*q4{ި E0ߦuceP+4d,iVE.بgipxe\}4JcP#Z@H5) lC8KR_# Iim'x)HM1LG^- Tr t f#ju( Я`/ʂ E@-BJ@Hv-6q-4 (RK : q /ב}BmcTqǢZ/4gVvb Q#!HVÒ3h1 龫p2[F.#5r5R,ŀ, g?vtor`LGHK^Hkr24HJI)pt䤲T(cJ.TT"(+03I24NT,b==esLviIf7c+Icu#1,=5vh6iawB%d+bi ^qvIôV]g1Ώg:UƖӶ*6D#,*+ul+e_9 nQA>%܃!lAO^o;_Չpgk/Ck!7r,LoI308OGr@gs=|;|D,FBQ7pVQSaw5 WO>cPcxZ5B|LF yDypqz) J*' S17/wU:^Q$+U4IUESSm~ A(W=#@Al,FetxCvo? 4 5F t0 Ŏ1dl\afe*H A,άC=|PcEuwX hvax7ArWmA{ ؇YH>GO)V.g8-|A6u7+DW#DOΐ+*V<VZEtg=F!(A#ۜ ޔ ]&XձlWyC*TXT֑¨Ff'o;b8j ;,֐DZ_~Za2#Iq- 4G®~<@7s##֏}$cEK^㷠],]m>`+zxa%=0= x52=u-=ywbʡշ!g1UH2 -4~{CsDCt2/WPbo 'jI$ZĈYH%Z4CҤUTӧICfJ !ZX *Ƙ 0d5pb9Ykih$`ii<`e \ pY=(A"Vn#pB 6D QQ\q1ɜwa!XwTlISN-pa,y?{R*%NaiQ՞vd J p .h@r"F͸ʗȀjZ:jY4bFe(*ŨnW:dd8ҽ6t uM.D7iGYS*A3o,eF!b7r`&N8Q2AAIB"G>R7LdG<ʤ{gPKz!Vm+\ `Xene_ BQIS8bAL0R1%7#aBa@bҫ<2ot:P* G J@;y:͞iS9bPQ:O)Unhpׂ}ϣGyɘ=,}C#9YkRLF)rzǹeu gBD$_xTpի:]O"X}&iTc PRm"D#JWcU^ FRJ!}H')qfEő0B"Qi7L?MI`7;]DTB &0a9mkU7j:Фp3,{P͔ C hhc Pv3PFq-r_f?h/_]"v4 iT.Ud@'1ͪ-խW䲸^"(%f[J|8͕qt!Z8G=I QW,w8eTْmSBb Eb Vր@ "2fA Dζ0[dH- 8pXc8 ix`Ax)JXeyA#ݗln-s T~FU٥U8Zʱ+Gµ:1zdBgKv4S88<@smCp-_)0'Z|r|ع!mu݇a?ؖ&o8ͷw^'}K5P@l }eB09 IqSa m~sM O'"VD)YcO} =Ac-˯s;1#*sK7L_&h,;#4 /#,e*&7j9wQ#k Ou5 o*Kxksw^3_Մ|[?lg /)jiaY*>3^d6:Y;G8`fA6H`6S9gB5v wIBV?Q Bv.;|||.0}cgт)?9}xgE}0~2XiSG1T_W1t]݅>E*8;cTKuA`'{%TO-a,rItGb|%|wr%(1n7;ȃ烑o2#S+1zٔFK'@V7Efyq[p  qkK%;!{c k`:9B†A4OçpV"Pym2և~=8U}s@'@!^}51؋E2u5*?dW8bi ,k'&> HdFf(A{_iI@Ypf @A|h@θ\#8Q~2}2*%!>%H8HxhRLy'Rb"Hwx? R~Wek;X3aNt );[&OYUa*;IPV<&QM'z8$('I}d>-y8g1s5H&#etR-?>Y1(G&SeL9Z'PzG[7dkjF, 8aix%P؆Ue`D[muf8\JfyT@dYC1P/p rY׈>e>ח1x!˩vQӄS^%EFq8*SSw$ԣf[ZL`UHԟ(&SB$0$w¥{ȇ88&GjxyBsZyX3D3%?L>CP@%R?ܩn}Q@1p=+XAd`H"2Q?0T`(4?.Y]BjHϕhpr(8I p:+pF$YV9)#, eby6+IhdS1OpQ:Ix:fl )4%Ztڥj#|z%i ٨.y!AhQapq&%yi)i1}%!YQ@>Ex[`Fe3Z:c8v\qIR)4l Y^ S0,Xu62R:X!h[;hRʟꕚoD#ѪW*w{_ғ8ۄB"DZG`'T>oƤ2I0ZB-s 0 0UDWY4ֵ+Ӱ¡aL⇶ȉ^YWv12{*FsUpySRє ,i,:)vY9[?9ʆ4pzw&k}hӸ#;>?jAKK!W41孏ILRҲ)o1E+=NBEA`ظa';IpO`[# K[JYİ*\S܃! |Rˉl]RN)[,9IF+"Sխ|+#_̖1L{k [705IB塪 IȏlQ]o#j(3T,hőB4_\LH&EI<@Y0t.p 0 ͌ܿ /UjM !~)V7 7 2?>{Z piqH;⼄)S3#[KMa`K io6nK"":-@͉C̭vN!&*I̾|þxoʝE_+WF)9++|& `a7X?;tό4/-4>a 4ͯӇ6j=}B>D~*H t\q$ꚭIPuy'J%ydOX@Y1z7@eLU:7f9kc`i׭yٮy,B05]8L#jLbA9;@akB8򅓓K$:#WLPw„9Nn}⻐}4&Mq7|j(݂M6}٭ۭܰ2룋ԁY݂Ncv=&j+_Sj* d=*W##p_yA  .ҜY>[ 7@ d9V<_:FP&Qrx9Sl-{\8]{-8YtnļSΈj2|nqo~(4\E3y:Yh6qH@Zi(_O\;h.]jo0qunw>4{1ovX mL#" kG$;~<6=րH*Yܕ cv tî+Ej쩆n; ~Xao泥3\:Ē:[d03NvIJ NN`"䭭Luqju<"GR,߻Zݸ)Ov;Ky( 66nNsQ#*ʋRmL:B|n[oN+$4ۺ1-.{Zek> jxʁSN5rȐƒIp,SkFue8! N\1c`… s *Hٱ >(ѥKf6m)?DZ B֭]zk 6|D+l'ɔ6=dM?)W }U,S{cUmXL@]ixa9Y#ݺxp`.,X %,R <: cDcE NM6>16i!ڭ"~(^D~19{1GU(a(j,,*+! *zO#aA/B*1P@%9 #PT /l& ;1DC EX7$JyQAk|F*F` $r(#C`:P}(Xk(h(iKj -DS3 ⤂ %HrHŒD:3`B TC EPu JDFLq)$m1:- 7]n1Ts@4DiNQX8R(AB)C,ܑH(iNԣiȻQ!>eH "\O!*PV @2\hӒdک7@m2}bBFp! ,^ dٸcaCxṛHn3jhƉ 7춭du\i×0QŒ͛.cرϟq O8[tiÅPjFj5iXbƵ+f`Ê-γ;$ɒ0=_wl-܃o}X#1XcM,U b,5#@ԖAXm:,8+4mMN9MKoBlqky^k:E MX; gʄ-v=EZvnNcTFL['-\LB654;GКuFW/MI>Y/夗\{ތ=0˜-; lF3:PaWUF!c&l)-PIE ;,k/3=˄ .C{Ƈ] y Hˍn1%yߘy3 뭫qEB}_>h`WGod [o5։~bW omHrۺ'1:JI@oVइz0t#@Y5JB.z` u~b_XDQm}PUz;*LW-4 !G1t.p_sJ&|U ䷭2"! Y1R:,M#I6R3| +loFl)Z48g$T>-tWW5. =|ؓt Kk\ Y2:O$s'E*bie&=8Ύ"H vD:ьƞZ>KR@*LIh3P8t>b *<:Q`঺MCg5N");Ր7U4M>}m, IJ&h %+ٕ.W 7-r2OD,7;9W{3'ыTɭmn%985LT]^eWV1*qXk5NON>qQ ^j_3{Mt򦎺O9]bWU4rwl~s!\&Wt}\i]4KmMN'J:8;8(&,Ӽ1b~\u2Xfk6!nMW6ÿ;kg8{wjU[Yt0kOz~x %sAnU~A'WyIU"J;ݣ_KgrK2K⍋{~ǁa?|e d7 g]΋oo/`ek}ABI 6~77 \xU?~Q1swBLCBdg0!H5y&hnWktX6,G{3x6F~q _~7p\W(Ƃ|sK%xA)*Ԃ#G:4hͧg4T(rlA6&:GX|%06(%Sh9ymM-72HUbd/8lVQIa?{m8x xlTx3eׇWmEwn^?gHiH\>8QL6a{7|؄Sh}w'_(?Hz4@_rP-HpXX {Uz}5H~Ȋ0hHk/m(7fx5PC+Kŗm|ytYdZ6$9}htux `c?h~ ]:rPIj s |&7 U4')PU%)wxv$26c00lX>B|d0S682DKqtJ0L}(؏،؎gH\6 y;S6rdudkm閝GsZv](w|L~iTY Yiur YagbՁ i9Q٩}*i8k_sc鵌oUXw¹ؗ$Tٜyv|ƤwvHEY\s^]x:_S1eXw虞iIt P 0I Q;IU( JkUr7G'OCFJBMLL ˃ [b`}H< f|m o׌5ME=cx-q%|Q@Oκ;  [9_\dmKp;W1uԦ=QvUι ۄD_csܣ{mu["S&FU[yP֍ݯվ[ȑȹ;>VsSk*|Ѭάaԧ]d@Op̹O 25.`č4|ހ 8[A&~*ڬ}_/Ѹ6~Œb[sN޼P0_ %.^9T-nc 3N - mbtQV@=U ywn@kR݄^凞Z\c~>0 ^8~UgcMƫnJN+n߅~W*M]M,Ȟ n柾SZFd6ٟFJZ~+׳ dN*Nδ`~.dt.}. kE0y"{z|W^B T4V~`-/b8?h< ?7g-s͕NRq eA1IW"5JN Q< XM;,_dWk_/ܛW~xɜO:p_Y7 e~ P{S@\o VлS ލOH+dW~*kk\P_ 0XŀUOCn,;WA "Hp†%|XD KGэ$Yw)Uֲf@M 0xPg#nxB ._,%/WnZʘ8:uzJ.aaŎ-ͬkiŭe+ΜiZ[ ݷ%w߿/R\xC#6\qq㍀%O&R:`ʤӦN>5haD DP)ԧReWz5NVKoj}kqܫWrɐFp3VAѝ;g9%fg {?+h'v*ڶB1ʫzSN9J/( 6|;;l"h6Di%2/묽F(p"?֐J5*1\xK,h L j/5NBQL0˲AcZ=t,aFrHb5" HTRNh̊)ݲrRsPK/IO.MD<QM4ST514H޳1n%ؿ*J2%7ai9/mBǰ +ݔ8~.-25dKË<\5^Y\m`OrXaebhMdk.krb7]5KR*Oz[쒷^e^N_ ޚq=8(gP"a VhȊYn)F%|KjV/r[B=iVk5{Fk]у)YndzozW 'V衛NJYI&8'RwΑt[XĒDAOl!tNlXoc]^Uv垻V؍wHw']H< G$9ǴYJUGԶg?tX(@aAAw %T>iTT~'SW;mw2ݭ8;F p?"м Z0GCrmե|P1T1 B! a&4! f@'TAjl! CKϐ |F~hqsjf3ݭ'3vV Ht-RbТ=bTz^,4CҰ$ !0 R@B;$8 o B."4Hy-<^"DǒYꕀK>AOlF_Ry*,pE0d)٥4G\CP)@a H@|P$P1mE}ܐD*ep殈$p:ic"HQ=.2ݠ[E"#MAu4 ЅB X~p_u5RtBIᵓ~)&KURH~sMـ4D7( ,kRӨ"@)Բd>1]`-& a,X Dnk\ZH xk]◾nl|I#M,򦯠3(Eޏ(=~HU};I ;q Vb^ C\7| L0 Xg}+D{ߢԮ H%%.7%l.8@ę휗@$ xٕOj3c~GQ r4 mtK=m|9v {`,T@|FEؤ+&d\6O|U@ڬ&6gq@F<jwgLc\*1! }y#t]WCkd?!H-EVb][j<8g$̼ f!904 G*V3/ΣuULbH.p?L\wNV3#O9,;:R9@Ns1H7nCd'! H\NɎ!X',.[睪oMЉ9Oלdz=<`)X^Ob O3g{㽪|@z`~m,, s+lr'/c.J7_y-&þַugɜVv3m^RoY|x)4%u3"}=h{ m>pA G P`g-vܮn^ͧ%k}tLJxΚYw c+ GV.տt΄=œҐ>)>H)0!hs7萋i»Pij7 ;8"\$%S?cb?3*ӣ Jixu{ m`G0CxLIEp,8#x@H D( Ħ,5j)u̾z<8 4(ڗ 1ºAdK="؜V  4!%$,LHhBZ#E#@0 Ddv; X jDJDE\D$ +HYpßk<[5C)s Kٗ;DdCĸzD6Ÿ{I(v,H'\y2<+HB17X# ȀDrE]p0F[yrzR<I8kLB1:DDDop 4HlAv@RLhL7%8yQ܁4&GP`;i@sy ց@ȆtH`aAysB'd̵ZF3K% jL Ĕ r#641@Z |H cdǟ4 +#Q$<dB),%K8?:jJHq=4!d#$F:)+<7#hjL?A *T*ӢǍ=tLMTB?#Lϼ$#tF޺s$[NPZ d(jZ˲ MG|dyKB,b7[,r.`uڙyVSkswwOhV`&j=3Y4Вe6]'\\sC;kH?-((׿Tۚz*k-!BfG+y$JB$D0tۦڸI iPK@[Ju x`I H]Pb^P[ܲeӫQ #¤ш K̓. l\pBC@DIAdFM-?S7n\pRxq݉BQ2'Pێ_ ީ4Бu`}h}[xA('HF쭅%G_!YDxwHy.k_=)1˻zֿ\A6ݣED<$6 {}5 fy)(B 9GMi7~a݆T1a-h[ !?.$K$N>:C=i{+lA%BbS 0@C(=/,=|# "k` Q763 ~MG;hfώ h$ӈwU@شfhCvPdEfdz! 0NP[ۤiNgId MR)gĚb=,Z[he]e@<2a,A3#e={`!X(Iת+́(HEdx+Ϻ9 N8^J^sgEaCHGg8MQjdd.AVRPZc\ŅN8ٹ,hle@h(ꮫ/QQivh+M`B@4&؁W 1\p+FRp6 ːj0cvp[އmz3h\nȆMpd2rM &)dT:TkqN̕\TJ6;JIHïOxD0DHl2$L;P̆ʞ6|xsFJv[v_`)0y H@lK hm!d".k87Y\$zhSk0ܩs|1NnvlVC%σ ߿t B(R3Vn H#m& ɞ\QF`kp. KKAQKuڙZμrJ%?k5"NW]qk<bI5Ix9*)*`Ƞ- B/w4i3se7mx9H:oMH;,Z( sﭒ^T.4h==)&k_ǝ_>ʵYz){OqYNNC%AnnV2;8Us\0H2BS(u]OPo) Hv3m0R/2Vx;0$d rNj?WmZbu:,hY眳(ntrBzM"[z}AF@<D%ZԬk1DhKKC *P"-"@~* OWj d?u[X`3#Xȃ) ('H`V0Mv*`o6z͢\*i&vw4Xh/8 XX *ciWٙhY$-\pAE9\i ZțP]"rL5ùr2)QFsvM̞q33yuL)ȑEЃma3֠ҧr1NU;બV)e+n%V,e,uaV1*uqc bKyKnmmYڠZf w}A Nq1TS5g i"G0>=; WP q, lb>ly Q򀡎JS9N;'Ϫ Uk5@([bW(\[1kWƁ6[ cµAmkUm] )`ĉY FDM:4xlnn#H#01WP"eЄkqnLf5K%R Ykn\Ҁih,dQVW V0R׽d}"ߎp3v=& ssuF Rx!3d,!aE-jašIcNªf1(ID$ LD '< _>gGmf;YDa MhpKtM+]aV4tXVN4.|ZFܠQ Ҩ&}HJ(G9uG\2DƀñK(XGu& Fr%1jBtxD0Q2R ]P'+_YXrjM(=eiY$f%2iXc֯jj}Vx@6<\mq[rP6~nsVy{ 8A;d6CЎ>ZA ;tH{LMHÄ!tHUY8GJS%2=-Liь'6M(/u*aUnS+ꐆ`e]oYS` V=A$%l+3ָy ^cZOy5#p1p vC |A L`*ڥ H* lN~2hj Z W0nkrޡxCeiv9cn/'>1l\NxB~ ( rrhͦX|e| U.1-,ob2oL :.L,/ǻ_jaf>rҕ*ـ' ؠW;[ dP?ynxJrݮ1eq,%t4 K脿GŔr@BjPrT^yȹh^T`M\W\M`X]<^Tٖm8@=t)uq (Aw;%,*A\4A]4C%AQ .MɀpCȇ dk}C<zV5^b\åIh94\D$ـV tZ ͍^E2SQVPOԠ^ZyO0d1Am}lv a0V!xP5XL0&y!L/yꔁ|@eoqƕM<\L" b?b#< Q "*#OILT %n`5p\*VheOrQ\K|\H\]`"A[` aR 0 c0f%؂-HC98-C6>6Jލ Xc"ɞ0'B  ` A-(:>DLs`\qP=e 6^jcrE"Z^ʉNHK %rϫXTDJ b$" ^0 H n˼tSbg-J9cwya dGPOZ'd44+/xk0ed`#T,IG} nD'܂51\Mh,eXhi9ԃ[#x=?B`#%x^ZG_BXQZDqMDA&Eb%`IbPOڬD-]id@Mkl!`㽁$ƸP@\O".& )*e@' uNZpe'y"[J#J"Uɧט_b\Janmi Q-Vt ͪ)VQTetI>TYk&nx0f L((|R<+&l $z]+%LB^fE Ao$f܅Kԃ=fN+tP\qTV_8שeJ `K<f_Do &~Lϴ@7tMƳEh눒h}m , P0)HDB$\B(Fꠐm EF-LVh H\R=xlZ.h.pMJJlMj*Kz@ 6 e6UD Z E-*0e\7elAiflA@ BA@d7lТz)o$V¯q G:c,VO @*E@X؈` Z&YnR0ɕn\ ЫH oXؤMV2 G go0p!p76"AAFMᆬhlR9>n/yqrqB d4@003VQO؈2/Jf n_ T\ NKHDFfAr$O2%Mf& J(BCByG@wɑa uZ7J2ίc/[B/[ish1g-33kH a$A:X2Y z7EG93Kn\TO J3Hᖼ02W9#0 u e0BuB$h)DBE4H:r`)q<rwf[4`,]s i4t6K8DA:yW^5VEܑZ0A iMeRZtRӲ4ͳosd@W?X$?  ZMʬӤl,%_C~V`; {.l ӮO@`PEPFhh nqV"JsMոseua( 7)o>C>GrXFw 8`LkONT:Aq-$5l4qv5wII7`w@KHhfSH8upUg{OX$ˆ^viUb\5M6b`u\VMh~|u$$3/p x  AJ+.@Ug29Cx_[9t:Kny"y1@;*ͮD"}Q8Y@w5|kwy\;Tp.+`xn0 $3:@ Lz#tmA'C15M9et#cz3yL)F$Rg7~6q8 K3![iyHy9vjmMnV0N/2ҡow (:3z <7_ ou_CkZt2Kt'RBǒ+;y4ѵȏ P|ůEEE1i7o`Z HΠ쩃Mt H~"ز?2Gp ;3H$lT4\؇B1`GV?|wJiXԳ%h5ڱZ=pw >P1b۬Lq$YQe=ʽvbv^TRu=n;@l`-,ڳe䫷|/a 4qA A$(D iԉV1k2aCaz4^c~#4yeJ+Us&ǐ3gC;٬Y,<5ziң6 ` rH"G(W*#  >0@PPՠlB=p٪(<@p׀v@`PpL08ȐF*F8B5|$I(VhR>~0#HId8!`kbƊ)H,ɗ7M5^ϟV Uz?N0h;*X(H+J ,-BL`3,L(4 lTL3NJKM(Z e p-7$MnɆnY8iqıΞ9Ӝ,b1:OJ/&6ir>>T0R! lPPb3$98mz@nAes-G $fA bT1ݙꑦN վH0jdNzul#(YXc3tvDGq4%,QP"N*35U[ѨRc0(/7?krc?aĨƙB4,`IjV6ԓMnBPUЖ+2[( ; hP%`Tf@@ ,4  2.ck=H3r猟IU!lD-iPڧ*9+L@mRm ]hC Q R\4QaJ!rz-46UȪ .H* XZ)&V!1̑&<J1$ 7KU9;Xpի|JF1ŭ 1k4D!m[*mPS!al(T؄BuW) ZBA!YШl>ϓ3R#iY>v'6^)m7`R#FVMn6 La%dˍݞH vu1/n`;\Bx3)eVw&oTZ_sXm-a_)Q40?l miHn+Cr`)w(? HӨ`ƻZ8";Y\g88*#渳qLߔ:ѯkld`)uJO IAlaQjjO4=CjE{!] ]K`YPgBs^ͩxXg_v(o0Q=H;S~߮:u؇+(Lg:m6i!FeB0-v_\ lԇ8u 7׸ ;Ʊ:q5e{Jgq&؎m]6u=S,P^Д'@Ԏ3V[̉dKG8XDZW_w>p1 |N<'i+q gE>rT,Qf`.TIDwe ^+ds? 9B]0NQ {W WR5`]Ptu^&&`QΠe.}?L= `0*j6\'#ͩ^O?<]u_<;6`Q MbImR" PN]/D1\{:wO.Cr ŊŎf؊"olT084Hۨ+,lZb* nBFӊeTg*ˬy|,cRN 5KX˻p P .$4*P% %n=Ȟ?hQG*,0H4EV)BnREkI3m6גpRl,(N31Q+3+Erbz.$:M.)0L_-S֏.g(D?(2l4Nn0Q(P2m!*qM22 APt#31"7S'Ⱥ3l7wL4HzX> | .,$4 7EO' O00:Mϒ\0' A31 "U;1$+3ʳ =⏨b=Er=ɲ,tz训-DOmoSްSp&.CB3 0 3 (!D 2'S(.ΔFCMF3M%VZ&ʲL--G?Hthzg5# }sI49jKaBW!2S:qLuLED3QMTCd ϏJHBE)T4OIDO].FzQܬ8I0A#(A+Ox1Wa80(ˡ2YTQUU=.M[ւP4@G.ZxTX|UX'QG JIMeN1Y[-!5\`\] H]NRƳ+uu-cnR/Jr3G5oZ.*! !Bm>4aʶd mKs] ea*c:5hT<FsjRdN[Y%籑 >0Tϖ Uhp/S AIJzt:oȍϝ2:fȊ<&8ޠ˩"/p8zn*FIzP+\hoXO\W\8m'/v MѭX!{ U-ݴ徟h[J'M[0>hipn?N&})¹׋IÇ]I]q%Ysd|Qyک=&Ъrѷ!ҹ & 'քJVIL>/=*~D3e 5CZI%{6Z NANM/}[ˠEWطBPqˣ+6djooa:puq=&4{NQ:ç~| , A Lmn2% ZQ⒪`̡3P\t6|(!Btѥ1#y|.m! ʕ,[| 3Lj0`N < DR8zh8}ZA)T!V *4}3O4[@g"D` );fК5W HQ5R8qbD$IPy 3f$OL/8Q$?rqF#ZFwcrP6c'7"OUhn!C.RJ6PZbˡ+We}YdT}:=Vz_6vB >DQhEd]Aefd&BZh@`jհk!&C AD!mfnUS E WCwrqH"dJץdӔVƔv߁%xEQGf< ^VUǦu~$SZaWQgvIUY^e J؃ CcF&caVqgJ bjca2Θ5$ѡ:'PCYp)ǤsOF'u6pe-e[zPjz0^yyiTSIm^{i_di~t9&QN9'GWd([:a2hcNp9JYdAHvC*kUÃA8q/'S5C+G\s=tS*lTބSv]SUUfJ[m>@NIVYzy-8 wLq%WK;Ab V ^]`aĐ]h,i *fgz5 7k\$j%* [S.s,Z`ENѕN}EXG÷r_ uWPep1&&)eͅ0ܥvTwR 1sG.!ܫu/~e㎷S2Um7eUp#<|otzO>')Lh=1?y]擗9V l4^ 6emT >uA)5L<P,a u "!: (%J+Zd=XyOV lj9E/l'3X S@EvQ!k kl| f"468iT=hT&FpGI2$-IރX`|[R|C9T^m-n1*BV9'l* ku'/c *DBJa y0̓~a 3A栐X7F6Y`B B E:8Gԡm#S0Y( b9;[_{ޓJ+c^ (&Ҍg Kd'F؉Є XN3(.ɝ aL=3 5 bq, LUAUPE-qqV AH=$RNS'@ibMTg h,Qw,MI.Q3)Y{Z75+w\&Qm9³Ȅ9m[x?UUdvp Apڨ6i#g?ÇիjmIZֆ5ShDgj=@b9e] JibyH=Pa-tcB i^ɂjCtBʤSAkz,mUQ;z-rK2*]$ÝI>d!6Lk(WSe,5^mņR}&Au`q+N-xQ|@+a BK71iu'%6DoSjqhk<3 Խ}̣uDI/h==j'ʪ^IyQۀwAo*8Y)F޲8E[u~.}F3DA&LyaÝhr3Bw[7 b y#U)yo=MwcUp.\-lÌ2, Jb[^ex6-3:cΎ˱%Ō`N-42 yޣI"H|'0Ϊnu&.4Yd=g=z*/΁ݚBNKh;7fDig; r!˼܅ db 4> ETBʀx0Ҩ=)roO#KO&z1BYZpXp E7k`V`<&Xt0S0fv {qx4{Q42u-}a*XQ]NCCGطbSBD*@{sv7z$z(E8eZuF{d{Q{ 6?emO9(R %9w+wDQ!'AtX/ه}#)I0HYH6x%<׃?B8W瀄JXjI1>QWIteTgkVM3 ua7%s(wGl=qpQwbF0Fs>׌+>nTGjZhgV>^ߺ`L n׈-p57Y7;*:2JA>eLiMjI2ov^~vٮ9ի n2E%ji@*q1{'*t! I);:6 n !]jQ9Ӿp^2"*nl`/eRe4 >`s>APN|);j"Q= 7׀aOh]Nn^WO,nc-]S(V+gh̼ 2.<ag'T]N &R  o ~]rS&{ܣϊt\nJ!/)[j3q[T֍K_oH,kr;1F1w%$ Y6k=|xܻrԭ֬2BRHL4I$J)d)Ll:=}>PAEG@T)RM>US9ƍ]nթD?,-1 PAnQ~69.p͐/…{3h)jذQcm eĉ-T̘!ZlV5I^|Aa ˧ 8A$20 b3>HDZy"h 7TDކnfk9;*-rd**҃j`cD1 N^*=@!\#TxvڤB sv3iI SQh@AHWCuEtDծ[ct) ۬D?j7`eNuŒffY0 i?5DˈS19N 5 >jJţ@@*A1hiLpKô 1F=!zhN2'p[r@&8̐bA"%G%/)=`5I%qIZ%:X;p~JOR(1ƀQ~ €?`{x;YO$ -n\Ύ1QVLd6G?H)'i8QIZq7bNtTk|;#来 ' W!kN2$M6)@l\kM$ƒ.˞İ-%35$Cm N"?41 )"n@z`uN׼~.Ugi.K]7E 4` K\LF f)uWtT$I&do MAD$v0VŐ2`@ E my;ZbMt4FD H&<2_;5x I C` [XДuMI|bz,[Z=K+.M jy)uUr@"hpm! U& QK < Z܌^ipMً-mNר(V* LS.xEoMYʩ\,@*j/P2)"Vn=X3n+k@.<)p3Y2rS$RyhB#tLChD{,󕎊ܤР[L{̕6D8u9%#g@W+d̓֎a!.! ,^h I#\ٸsǰCw"J|Hn/jQⶏ C\Gu6[ɲ˗0cʜɲM-O]N@dGhDjԦ)U*ӧӤ-JRX׭ eP%? W))4 *7O]y˷]} pp`\l󯼇leˋ/k̹ϠYnno;y튪~NRQ먺6y4f`+-\dN;frȎ$YՅcaMen=zχQ}*jRrvzmL)O@\[oq\; >ǐ<1mTτNGPiTTl sI z-am@vBXT|c1N)T~# 3x[WlBMr 6ɠPeQBlH! 5WvZ)Tev'ΚlvQȔ47ڈL5 '2;4>}4UHpm[[l N>yfSeA}Xrjv&cibTByҚz=AkI(Թ{j쟀%P%ɢ 80(zSi;"Qh!%5&!j,lW˾Δ곎濻o 7f'Wl,xh:IfBZrq2i:#3:Cpu 3:m4dcL3M/>&zu5Զj3QsM6'v1jÝ ܉x#5`m+^,M:3 :1<mE7xb'EN0k 걎NzUT6o.jn YNC~d]Ռ/Ӧ|x6Ns:`}6X39!Nn:{;w>ROv2Fj )lB6N$|Meݲʡ|X5Fx oUǾ$c=dyP~"vx ` 퀢򐒱jq) 3bTp^68 `9VXXXpVP %m]^Cvw4[(ķH57$Jm*ēT&FEyTb.8.#0iH ?gF4Q)p8J}tXӸvrhy`5LϏ e "w NH<$-Kl\W^iu/2yk![ %(q 2.{I4)Q.UZM#+Mx5SD].yKiy dS! L nє氨YM`Sy]#U7i'97ᡓ 9wӟ:!}~8^^ BBbS5b:QVtwȈ49RaSfbJR=)夸Pi+(kॗ);g:Jc^rOI^m>ʅȍFEhRw)L6uv*ժxƌxD6WWQVdjdVs}HJ[wٓ{ _ Ӧ${JlcGPnqTئiO廜5xbw3Ƨc4ZYZ,#ڰt=UXŭ~]X6˖aKܣU`,,ȆU#x[ NJ46۴#Qj˹2<6bpԤ~zzw<7 f} Y딪5dLS:Um dzWjx)ƆhXmqK8VO(DKtJ^hWN,*/X2W-[.pXZAr0T3P(P.>%t!bd^in6c ǘ&#AMq- K[OvfRl]ivW|&w1OvU/5T'#o::(_y..=A=:l-K/M<@C Gn l'>X5CɨL5 iU 8{gRsx8Ui?hyVju9 z]3+DsG].8H0NG|@hg6-GHn9G1}ԑ?@vDlyX/Kً 8NYms#guT9x?HBS2Ɍa4s9gYAm9lӇa% S)rgdq^YY4b=:9])0BIفTAҨ')()V aY[SX\٘蚈 瑪62c,9閵śI':)HqvI4W)Qt_eYwǔfH'I[8iJAa7 x~V'igătFX7)GtŵPJrx2L9%H%DDqS )_&)K,ڢ.Z0*65I35ڥ=4rgPV0 `l@ wFɑ GsBHZZw]bȹ0VqJBFm#D$ʜfӐf檯 (1'wꨩԫu7g`Xd60E0 `HX6THzm=yb6.K@(-:!;:  )gQu++a/{9p/*Sʐ˭I0?ADGkw : ~wBͳBu:UU0pd+й[ )˯Mm8+<;[ܚ`yAh9B;qk[ʼnS+A۸+[ ; kHgjr y8,>;K^tSOkx7+>+MLX PI ikVIWp qK й;l; Ő?@J3yӺ4)43vkjtŰ4xNL*j2#FSwSNTcL 6kÝ6r 6ڡwFP~,FT,砙l݋Q~OKby(\PIɛ,9YމzœG%})1T<| ɚ-l n€;n %[B,F݂ xLO=%4@S8,TTd@^glӝ{h6- [4U48N̾Wp]δר˥?{qQفZ =+ ͊؎Lnpܼg mF}Oٟ-3-ףMȭ}ڬvM;Q}UM$M lƜ0֓];lܗ z0Tѽ~twka#PdޙTe\j|MߌM , |Na MP=q)J޿=;=&N)ne⡛d =B0ͷ6T(:<>>-nAA[mᶭLNINS#^cd`&Lk m0 , ->I׭JBwv>Lڄ+RHpX 9ϜSN. =mӝߞánn}xZ&$뵝(((5@N!NΦ~zjES2.jr-=kKK3.e P#>O0 >Ɩ.*nܠnFf^/3 oѳ?\{ 7PNN0\4 ج ,N5~9PE/xJ] 2O beoɍMáh .=kFzߨ~^Q ` /%>H<_?b Ɲv -Cy涩q·2[ZO!#05 S\|!8 A_,šC+c4jԩWt&bŠAVdIs)U;rJt1YM2ug{A%ZNI4ԩQEB]ZwY}n}sfU7ͬm͘[ T(BE#4AÇ$SP!HfLBW.|%ĉ;ڨ#ȐŬ]ki+˖鬺Vԩe}ke7JtTVjUSʘ)Ŏ-{vZڵm B 5TW5|<1CG܉V.1kkgѤ]'ٮs慄 p(tj,zc˭;./*A; 3!ã"Z2L=aFkL5Kh+0z*#628&\CJ+0G4sk̊*28 {RfB*GS) % !O$,tѠƘŠAL 8`KNnHB tuD52(8y3^ 켳m@Q=̋ݽD0$^XeLY+;`=aV;(isqXYXiʸjzZ'E9*=Y. H\ K^-Žf!&&z4ߑfmQr[QjDVia?.luMNS~{ynAo%p>!BF81LI#{pB1Aw* AZ?]l@TwXRCۦO*Tj@:֩ *j!TBf3PЈ8 k}e9-'2A OЄ,p41ĬDd llxC#Dv aPɎj"D:r^lfeE0+R^0  ePuH%: FAGDxB8k~9$,MisY9_9wIKuCK3|aɥve @%@W0*t+ z#F5ry]qC:| AɄ *Ynt<Ԛ~ ٹSl)YNjk+lPmSmWjNRӞ(ϽT@(duD}V_)˅"uɓ"+p*&1=XGQ0"LZ Li^ So6e,cYX&9U OSv%'<2z.+c T5 5 emV]ZAVNqRPaQbi@W4` H](܁'MԶsP =~,,N++n짚*ZԔoR|F_TG،umWg+^BWzOp~wRBą7| L T885RYe_k ^vM,Jy6YNrPVTwe|@#TQ5r}mV641d> 뉱x۰{r $Wa~B`LI0C"pAljHvkGZGl ,{;IUmrBmTM9/URPf"a]rŠǒ L)YYa;qQP uĂg?saufZjlWHZPw%-(M/%Kb'7OTYjx ~-WSxkq"$=M Tm9 OB=XS`¼[Dܡ]pCz3'ύi[șזۨm}mO>q*.BRVU9,;?o P]8>is l`R?RRYq9c;[]66O@*-5]>pcֱ8VzǦ7H=G0B+8I%i]6=lbs>+RA*~:7| xOM$-*PUQ/uZ=0qڻGJbU.9H=4#0 0R$颮 <1j𪇝>ru:?fȋ7[z*S<&jy: ҃3(1X=;أ136# oiX9KEȃ<0IGX?Â! C/gD`z8C'1豑J`uô('4r/:"ħbN7U gڲ+DhjzB˓/LH\ZCxR8JHX=#@eD 1hj 0 S?||ȚBǮ@DSDs, HfD #B$RESJ褡#:Tr56U?[cB bE26]dJxJ R8LȄEp:@(8%谺2@ˁgăi=(lɚ%} IIѹ0GyTO+GǨ 5sP\RJ*;8EtB0;%KqCA7wXJJEH.%`)(6dj$63LЇŔ?yL јL|1xLʵGt@SM'zcr50jU .B0lȲ|t\NKc `&B)|s,LD? OE̓1%-{:~O<|2Ma!mQˋ׌MZ38-523 tRLKG؃ ?i2iS(LXEyOOz*&kTjyB&E!m< `M M+0W*Ѓ +DtfҎ̄5Dchd\FU!DsX7Qt8i 9ʱ$3"3T?@=#o)3F4 -IAjdAb=A֎͜chihc_POQ->]Yk_ ymٸx<R[e-Mrh@% yJxƥW|5U}KbЗe6(+>ػ[ϰρAMV fu`wy`cZr(uYdy|zJR;!O$tɀr w pB8ԋ-ս:jHEE!s5Rx5> M25m0¥8e[\8v8X@`Zӣ@P9Zf,`gd}~a睇xb%\*+sXaEnak}QJ/HOPa$ QEe_!vz=yMR& Ut+OX .N \G+b* -O & Ug[P.vwm\8\i@A&dCVdtNgFVG^n}/L܋ P<e_y#UUW6,pY[c)b3-XLP`f$P0yj!cP^v@Lmw؆T 1xFhĠ@&A~Xt>uvQHzOY-}䂶%r-  僞 0% t(89'f~hZv=1tiVD^<)@--HjzBlwK֓集IJH Adq.^ѥ6K K^Sf2Ѥ7|jzj߀vOWWP P\넌Ysn,u8O09;.2%ৼV9j8[6vfȊ`PD`<}UJJwu 4Yj(E>ܻuK2kY*- yKOz#fve_xTVh&fڄܲX.}ciMyƾc2n  fMlw^DY:fʆ_l1<ol [(g'o։]y\m[,{)%\p &jm 9Z/^',VDWdVc=M.8%È XG>j`^llw`I+o-G͸/r0'GZ'IJ,YK"\HzQ: mܦ >Ԏg K}hYAOp!OMxFPf.<i8i ulf=އm(<()h lQ84\A.d[G\u!,)D$O Ks5w|m%s>\ %-q-^DD@;聿 qEMxiH g~|}_fmހx~8uuo!rO3pl@Zéxxtx1GJGaQ/j2"p?w8{f)Px쨁®p1\Ƞ[pLvxxG2%1 WӴrI"waGb5h8n8b[r5Z\!bu_8 3 fa@Xi<eI6f}ZA(fj>l1٦Bn)`g >$fEr17EP xv6tʀ8*Uc9R{'|"c d:N3'|T3OAU d$G"Ҋ)\fazHb(;l#> [*ء/H׌U689YB>@G2de 0i=9j$B*PQxצIDg٠~*( RK5ʔ5٠cMNN\jOhZPL*\/3N 38 EqUlG'Xs5Z I(αo%8̂Z-O5 [u Yܝ= Zc=VkB hcAt/ n6 k\ _A`Ub<(iaƅ\,T${:oŗ5s-P;Lsc*?M7D"b8fqEl1Suv5 5hs-ֶmh߸Հ")oLgFp>ʀ% rd*S / 8;tQwa.3v8ɬq;e&:4D x?Onl'n@Є$&^1O-vZF,sMYn\V\7Hrc Pn]Rh%$HRJP:I1D Nt!&,VBP'HAXѮC,])ŻwfHEώaMPa&y9exĐ57 8Nc(cΘDlŨpfMyȶ;H4 Jq[x,\\T nI@J& D8yPa";\!JUJr'p8cTd Gо%/CP [lPJfWx4YFq&iґ63- ^9-1[x[x.(W~MzsTLƐ\k hIɊ6kӛRHP *]J'xI D->Ȥ);|z'yՊ)4H(p%0H OTUɍTvXոՙD%)+[Țα%ke< 8N#S B> gJH[ 蕑)խJ>u8ÉldҐ~ #IYZ~5H X"Q!ek]{Op$JX4SzD"t@$0E)14`M@z թR.B#f\I.^G?cb$e}#Z3u+y+}]0Fkޖ^LF| `7nRhD'H_O z58(St%Y9( dq [<{k!c ` Xq,RILb&bDCL{Wȼ FV&%52Cn2L.Z=@\s`ܑWa41=ŷC R"!>Vi \Aq _A #J# Rf@IB,tx'kƎł? *@)<mRUP\#¶d YYcljR3>5yӧݒY۬'2Cy r v_%v+T2\#8ވ|DKQ*18D*p ,+D3DBH@P01Av ǃHy%Ob1**"AtcLV(E4Q!8^usDA:촶{ =DoK.:iFwz:7*n|>4Zoob#l&qRE]0@mԀ$s(1= IHXx@}hAdtM/B" Ȁ B)О&Ԋ)VL AYԂ|Q_12J,]eaMH[ wFح_ ře~yIͶa"ΒDDaDm"X] eZ#9>hcq9a[ZaU@=ƶc>??͆Dݍlp@䓟cȆB d~ˀAFa#D@9I%FPdFny'6$/E7H+34C,pѾ d$`TL:h119@R2Z,q.#T&g5JbU7awqecXO \0ZD}I@ f[#==NKMɗe\╽DwNUac1"$D^gQ Me6 d&VLzi/(B,BP@ \d"HQT!dmcTAr], '4rB%sfsZH9c*\gve#IIxY6ig ҋJcݐ nh)_be]-Fr$ \L#.m(Ie:eFh GfA,%l_I7EvubxΓ>IL ND`)|K!Fޭ vK}'iߥܟ1c9ܚL@0Y->F aNM"yWU ]5= JIƜ! &_ D)нWmPBԤ%`D6~kȩPenevmoHBCR". V*V2Xm. .B.€Ȏ,@0a$چmp8\ul ߰| bgT ODox ! ½ǀ:l"n Z~-BCB`lPiDIoZr0 :norzĂ/j&nuB/%=A&A*A= W t^q#A[ؘgDLyDn&6pVp׎0"w1Q+05VE$, '%Ld1i,q:r/1n*1$@?pbpL=v|lt `]U@@ m.\8senA~HSC!R#=`G6YQ tr'4& gLDD29fNBfv77H=˛k0@e`Lcr- 4d_@J!(DܤMpAO uz s85kc$T$X5 WGkA)7xR"rT"4ZSZWLN$\Ӫt]7 ]F_=> abUA26~6RCP"e bZ)t7l$8u=2 @ ,8/,R@4vXrnoӂ2h9Cp/c?g_rn%+?+˵vZtvqCp{y D7yNbϕƙlPίL } Ն]@Ҫ ql8h88o"T @7@ l-d@EÔ2(8`3-%8_t]y>@4F.DF,V _]层%Z|86! A $x'x PElD[8pvrOa:={+}0hQBfg`=5-'ڽM-y] 0;}D\)ȋ6=%`@ $lB$$d$Y7Rn>嗵2Il>ӃsTOxpaB p0da JPQ6THq1 pY*TxР"Jtљ3WxpzN]4zt@ p U$ #ILC_."KJwJ)R:@*`"8@+ ` jH-C*.k*r" |Yxq8c[ћljNfᥘk>M4{;4ZIـkx7$g8Sn 0;+)<@/=pb%?l"9TϚ ժ2 +#F0*İ8L9. j"ZnUVIǛdZ$R+W'kͷ#};,CNuK1̙j4V́MRS*˓>*J@mSO3\bi Tˤ[4.0 L´C<}+E գ-lZmVWyV\s^K&Yc-M*Okӆ;睚TYvڞzhA6}iB|+ԗ9 ~ G]M!XRԥO/4u" Ԕ-]j` ,2Z!nas WY\7/|a oXC܄8̡HC%} "<=e`XĦ6)j๞BU@5i,RP1!'Qcq{1G3LЏQ yg"si'+4H%6Lv*k'Q'asN*Nd;3XN0 @ǣ\,UKN/vD0Ksp1#f1=_튆#ZC,Ӥ Χ[D E@0%" )`k v QxʢDeBjI8SfC#f1 4yÇI)9U\t}F xL[.+) vH?+)RH<dk"b)CLTU-*؄/)ReYFԀՇ(Y( XZ؀[9a眭3$' "9~ L>rWMM*їe0۬3/Q&N׵-֧NzrI옢kA)nR"ԏ#AM. nu) w]WhUPTWQ/VVsaXK.0^=k-x_jtmq)SߟL'bxRV>zp͔Ā]O)q0Y5y HU98 P0D8s~; bsM&D]=~)c)5)Ux+КvSnSRHއRA9mW~S.Tgлgs-Ƶ4FLp )6}-9sfQ8իnuCYuGw:RJSap $Aw^2#L utf.^ w!g>车.WP<%a;u>`LۢR@4]B[cif6^)3s!cD@}̀ol2d,8z-5OA"ٰvj[`-h0NnNOD+&|O maĊ ,Q 400PC 0 ENÈD%+b`*Nj( uP- RƂQ-PҬ P Tao  m5A!B+Q?78A|D ;&% "'n KO%D (%cn?DmQNFBQ.A,p^m,%o1؎EIQ1e%& W A.rѰɯ& x`00B#P.fAl8D%%]l& 38px~L-lTѯ=%=hh*0+վ ` 4aB2IR-Qڒn䒻4q/ e/M0=i+/,JµPLj1e o"2.(R+2rBOk_t4O3r#1OfT$--p- hΦX,%8q2$1P9a"N9>TQ(LB=.N&JJ*[!Hklxc+3بRr25T)5m- %,Lc6dB%ebBsI1sCC?F:kM)c)?jN;wn4k_@}ZHs5?!qfMa@=a!JT$5V5 T Ô2tCLє&DIhDTbNN Ai*ui^/Pr1զ+q?rQx/5S7S? 7oLVS8TUݑUTCSV=TV7ʬSMM%PɝP%,fltHN^ a鹦U4͆/a!@5\ƕJɠZXr6MTBLg_cu_:w*[VNJ~0L("f@m)(&֯tQ,c2![I8r&n3u\e5cw5K5^qv/1sցgg_郝[HxFG\_iU/ u]\]+JpUppqwOҥ?"61t='>T a]l2A7tɅ2X*x*Iu.:#+ZuʁG-m6e @[ hM?o-v#Xly p}zVװ^v"{ P gY v'-YJ6k=}qBtGW~Lx`,*#T:{Y {;4S.svԎ ӊSڰ ya֚\@ HmꩮBµ[QP7a5쁲;.{ϻgջ0;R/ & 3lIIrj2<\d0kPW9…כȀ @Tڤ|Q uomGadf7Ƨe|]bEFzTB; 1<)9sgץ%%x.UrĨ$ϋK=cL9/w_@]{'du6-rW/ac[>4CY[꽞ŜxC $ MB5$[@1{g5Ѹj/!C4)F^4K M=CEh2>ݯ0",zI(o-Xny,`. @bl;lֿ} -^AQVu뵰gg@~:^f !x`BE13t^X_49??*(m"'R#E.$yn`CH.( < .o@󋦊C#-BE89 ]_m0 < C2PC *ѢE=x䃐#>hp e4hJ+S¬I&{>p2hʄ!9hp` #jxj#ňTkĉ,ZxՌرW9$@reqmtSH1=826 &Ō;6'.bɏ+7G޹sީ[z6_b:լ[~ ; ض=`8`q"Ţ1yDI/<K $w@hЈK3p@{6zaC #xH5 Xeɖ= T89ܠ 7uC *u^'4uZ`pbK0YYd"("aFf}h&jHcvnQo#paE1gFuܑDL5%`uNH$AOE$A`tz9pB)_ y`V\UfE'i}Ju R]YafQ'xhbeg,i66㧢X[hPBjאAȐ"GEiv4Mדw e0Un@J TAgSvՅ|'v'z &]NXh_Ap`v ra<ؤXb{i&i~ ÆXo) ;)$$ZGڕ H@:dRЩӱ*kv=Q?]jVjvޙVS*81^RW;™A৅b f%HsNRZ9׃). kz*Aё 7Q#'ϪԮ M=:TPU  RUYT@j!vw橄[ ~SSeu F%UdcSVeͶnטp0wl\Q_.Ϻ*ώ&5x/}Lѥ@QM9v찟#Ӗn:`+up!+f;I/ʚ=#lbTӯqfm-RNm _:ŖS *|T…Dg&)v%CQ>]n'ZC,T-LO遚d% ۢBѮ`iL lpt wvv'BL0y/BeCIK.>pVvbpqCD= $!VR%8@zJĉU N{z{'/fq]5.t* k^K C S+aMR5S) %HRfH6>YL0Dj9I72tT`sҗ[QB ]I35Āѭ1C#Jl3h&L͔JNb!Tc4կ8sRJH%xp&,)TȄ.IQD'E.tQDſ3Q@Qh() OLF39ҜARM !L;j`Ap˦)&D'إ"ͮ(ʋdPȉt"cp\=Ԭ8A\3̣ӏtfW]ǃuTl4JT,ch'C6R"jj.tFYP}rE$3 RS Ň, AJֵֶ",: i0. A. 45+r>LA19FZ:S%4=neX[%'A ENqER6g9Kb>MMwrvXXڙD42&VLňA.Pv@{SЁg+/6 c(&LDg>[ḻawb6xL,P9XL-ыiusvF;|D=f`" 2[%yA;a Uj+&7^.߱(T݄y2U-$tuBirC˺X C2(F!"`QBӃІ>h5:FNu'0}ӆ鲕: pOcl%5M]dI8]]ΑOsQ;OlIFg,0)V,TP 20O8mq !jR`6 <hCv#ZьwlR[ƞGN{l!Jjnvz9RI\9&yެ~ma{*u>9kt5mF,Ũ s*$aŸ>k2 aqc=~nn {tYE $: 4ٷvv0|A>A̾^WK{PnOfGYx?~u,R0@;T ( >@#.A*Iyը?OSnmM'LcR'1=/ucc'Q3d{IEC ?@.4j=uT|Ƈ2kv%@,!}%dq1x]ax.v[J@~s@C{tEG\A6gKoGo(i9&}.zi*=XN=>C8?7xj7PI jUY-+d|%UZS%.Tep;'pp/ [R~:jAb\Syyy$ՄPth:BiUHhi\u5ՀC7{jqq%["5QkBI0DAZBuk[ˆ_!6zBmi4E?Beщhtu5'zxx]~Ȁ?(X{C4_N4{5p{7+#Th% v|72.OqlqYsbg1@T4mi~f? x&Hg|E%)ji5{d5,{f>XkĈ?âks7&?a?KI":95`f" &Fxy '8;575c1x BEGG7zKiM0Oy>qVS60V .ˁVpVc8#ᆷCD$fq8DQk>!^%NH!%)GA}[ DfxR `IaysDQYS\r9 'yE2b'&NU=9V%fuVj'pٛp!32E%)3>q|B%&#?TpY- Yj}t2fm`Qe!F 4I'la i.,`s&}B Ѐ2HhyTo+0U_'N)>(rVٳSX`:dCHx`h9/9$ Orڜ. +=fa2pRr,b]'?` 9h.6.K2qPE Q"GJ P)cz+DӃ>[ 5p>8pw2 SEpa^ǁdȇ!,jJIC pM{zc9Ar- vKl4tpKIPc!PCA!0 B@Nw򔔖2ڪXXqԩK8va#(ú^*k8Qd:NLR=8=$3ZʙX݁!X|X(JlE3xfmLl p.`Tʩy0 ͠bRF7TXw,j YSUI Bg(I q';8@Nk!j,!3D)T #EA?#-$A{jh!`t[f$mx8Ymv[*FZG!M*QS%5fں4&<&pa'j3# JisˇJbq*3Pwt}i!dm0 6K7Z ::`=[ @ W{tN:oT UZ}[T#P53;oث  N\S;U8F#e߁J9<%a!NV[aW~ M#uQ$E`.(;Ny~i~Eڍpd3 w0|]W~Pڄ>=rqZڰlЏzaOdJ2ǂUCiڼ9B_d"-UrlT wq0 ɠ ,t>3#*KNڍY&] |Grƥm۸v m+>DlΡ31g}/cݼ r  ؅8cB?܈p ɐ ٞIĎBy)X9YMAc9`==]vL .&, Wd%[&Q2/ud_Cg4@lPw0 ɀ *NNZ?7;+,B>NVN?;"m{߂,y͜ PAB*j<|H 'd$*W̐R$1wJIil̉)L5͝;WNuۚ5KFE=@iM>T85@]@#̞=[mKV\pUЕk]|o|o߿5[Ç95l ,3\A \`aaVH:ÃZu3Xmn<4q#ع"NsiS:s;y*(ԤKoJUU[x=}zZjծmbyFm˿j샺Ta(Ͳ6lBs<#(4P, FM,F H,&6c i 2fQ醬n~ j(jI'ţ<(4 >Ҫ./ϰS>T 1kHdB ӳ4+5!jhQz8-jG;K.%gp2QkBg"CR's<(R+*6{/^ۼ4c ,e ˋ-nx?tHT=1DV SAV3%-xQMIm410Tg9tyPKɺ#SrIZ UDJϴk{ vկ%yE0KS^΁ُUl/cGjŒML#(2X hGs:gD0EkpqHgB *_5%i '6THa|᫰:-Z3ٲ| 3eM>pvQyDy*쳣P^Z P\4R(*غ^9[1`t2r$m J"hvfXqgf( 5v+ ,;r,[5ȃҙgKǼ1*@4 ̠Ar0OBA5kkZQeyt3Wg%E/)H^rezhr\Ƥo0+ G2Ɍ.@\|r־gYXT3х}2ѹ> $6oR&hF  qv5B Bb9OsLg9Af66[ - |8 )R @Sl$5`,u.TA"? `xPFCR)ebӠ;U,aӼC.dDIOddŬ90<_<>f T9hC,4GhuB Ed*Ы8cБ&h Un'y: Y;$$tC>B2 R. wRn?$=eO0C"?ThK "tgzSHBcf3ˢZDHGk,;41ԏ&=)6Bf=- 7}9& wSq&gł0NO S Z{I_2UTuAZ4K?`Ȫ*c X4V #AHk])(EJW,mOguX2"KYL&|Cfb W@faE) \fSVU?5 h@jk,/oظY4$ÕCrn7"w'd|t)V]A1,sp̻K񒷼DP'Մ,@\ B iaAE:O#e F(g ]"GɀʂfQ> ngm62r;z.6b.iYv,0j-YL.K(}AJxU[,ZTZit+FD"֪0t`-qܹSذS*zMPU4\X<DBX'V|JfP2yǹqm u6j  [fhO{AE(Gm$qʒ7_ KZ0DZ[A;LZ٪,Uyϴ4Yf1] 0ы>ybx b7i!.D;O-~ &mkS!'I>_Tem?Ҁs1@c/tȝ#S$2%'T'ގ)(> =>oV7Y2E*A 3 s9$H=7Ѡ:c ֫32rKwk42@ҮC!.q>kheغ؛j [ ?N+㛷ȪiЀG3)x 1<8=J=x1@'ҙ)Dcam;"Aܓ' 1@`A{A9A5Qs*雀 )*ԙ-2Si Gy6205,8(;@/c) V:3" Rڪ3,^2JA3b< [8j\c Ι[4 h-yhD)+(jIhdZƬzAGp(! ,^h I#\ٸcǐu81"D3Z#FwBIIRL K'TwH$|ɳϟ@ riӤ!Eڬ4iM1Jujө)V5f! Zrٳh1#pPx˷/_~L n]n|8t;">޼OϜ9|Rؗ}h{'L9CĽsͭZZj EН3|8CسodȻ;oB_Swp9QKGeʕYfiU.}Ofe3<rr s El6lHH];zx[I>8^Oh"Өߌה:6޷{.(\WlBMsFL6;"5ِm VLVr("㩣eHy&f[҈Sk>lԌ2uwgs4)IsmI6H&)wQ6yYVХw^N@騤z%3y̪pthgI:"R)3*  s4묳 H c=_Emnk%5z4AY놗ާ`rhRk[k{j , pqԢl;F\%^R+TY (5N,;2ẩ[v #:/< 25 +ٌ/& ;7K̭%g-9tm `a}^b).㖲:0c;oF< Ŗ 21<#CsDŽ-MƘn.E҈M4 +[hYru`M6s_Yrm*kdw9xsx޷_w5^c'S[+B y `Gχl0uDT)rT5}Ր8:g-0 ZRpj0Ҵu,f6ұw+ͨϾsXA KXPr'\?搈[EIK1mv ]oJ"GҚTE(kTdZnU4)+PmmbaT)Ma[]%tkag?]ΐujӼ0&pR1 _m5[O<]wK.%$A~\ "l( 6Y#va$kXn׀| l$.1':ٱFֽruhc'9^ @;pߒ׆84 NfUUj9@ƆLbnČ_6DULds1[9vƳx_󹼥6An9:R)RNW'J?\2a1jȐ=]W(- q,Xulí̿fa9zˮ0g]8w Mi:Yq.Yh,dόf{$ wnil:P֛sAfIomkC=`w6ruFKĦJAtsYv9Sָ%n-iImb VUt ,Q> {R^rgoVV_(owE|ˮMˍ꤮4s>yZױG Wv-p9ѷ=b߮c뺏C<|75o|2w3!y5o}*'o!F׳^e_:|{5v4!-8w/|^PYxTqJwcun}bEXDٷeAfv5}1{Ypg~g5@P3s"woKUa]&r{%~ڤBa'? x|qwfy#D bR4EsIkeń7DegvBws":lJ6D$<5zVa2w4x^ccaְD G|? Db758W| }g^vc1Vxd{6t\`G~ 1Tfh|Wo 5^X>TE,ndEu6_ $`_ `eEVhXeLaaaFÉ=W`4iŠeA#Gȋ苆n0G@`X%B$wƅh!eި@W`mHH~hPu`kFVOdXZ8UxhhdX%'H0h : tt8Vy \x~VRSƘt u\48lW&HҘ-9p.ǔ‰4Y|x|:Iy< T>e*4}B}舘fH M#(CP=gW)e1xo?H{zU:u6 n9v~dXzٗ9D5Ϣ796(wc#Jxy)h(WF:s*H =?\>jV&lțٛ$)Cdw87 LpgxLTŒ`ysU$Wy ߣȞy\t:lY(UI ڜ&F):xp':g(^V'ڡiS(FF՝u>4x-HE,Z:9] j:EvTXd&3sٸ~ֹHzhP9l5xZʣ]ʀn7zjdejsx>i{kʦ(rohEVȹ\jӇ@): ?~g w騐z0X6hYF ʀUvٞd5ۥhl@{aQv~y{V ZGx p pf=@NCNԼt w4fu᪳{  LEWXZ^5\^`~ߎ,[jg(q#IS NvV@B{Ś$ܜZbJ{5ϪcLY跍,N`nն}/}]>h>P=CbP@s^DnV= bZ ޚ׍m堃v< ,.M~xTcU~9D28n~`9>"\vnZ]~NĞ#[~;n,'O u5r`0*H S@Bꘌ$| ޭt^.R;snՍv|1GNj %p>[_( f qTFM9 qs?c?MF?,Ž>t  `;Kp]oMcpB]v}hVKpVٓ4@;ù._ȳ{m/];ޮ ?HP c.cʕ-cDQQ^ʥGa!E 5)cR˕-͡YM9u֔9O?ZQI>Q|gզHmJϩRa *SDRSgOq#obA 8JPh`rBl"m*JV3+ W6 9TgpygtNUVF:F6oSja܊:ϲ٫պXclȜ~H3Pwe^!{GiO9)cAy!Q{+Z`$w)Ctr93߉aTCǺn ƨvcE)^=nNB[y)_Ɍ]R+T|C8=%-WS 좽xb$1H@ i,G: n$(A~k[ppo]1f4 x{8ƹГCUÂF<"NAM| IE *zFP2J@ *1ß|kM^(rHcҐ-UB1Q b p4 Ѹ9@_J)x9i"S1H0XB\NS wt@vCLj1 kfbk tdS&hw$2%!;aH%qLFp4n)4JQVH S9OZ\DL~,P0F-qtbH3f,‰Y)b55xDn4a=ULw(C,dA -މySK "'~GS}XjЂȕ/<h.z]Gb}a4[ZH@Ԕ#xE&6eM!EPN'12A }Є&hA*jqj$U9^YX U2]@7UQ=OءZ0C#Emo=fQ Tkqh.ĨſJYG3lSCA],~: wl0A&.g窢T 85i]ù $q-l7َqh X:|@bc[c2W٪]6׹X5dt УH(])@ SXE5N-_索|BZ}~[~ա+_nKQFaVlpBL(9 kq/2wâL8ث0s L@Bn5815cg~(]_%L6oi4u2ـT;Aa&a- Uy9k-x [xtC.AHT`$̺04ܪ=ѐ6Z2Eɗdr0 drJ'WT !xŜlpMW=WZT,̋AqZ'?suILE " )ec=AUP['@3vm݀6hp۬D=U[߇;bl7mq Jxcz|ǑߨZfہ@4n"'kn]#@~g ApBAC[EL1AH|瀴ل K/B_{[h|^'Ӣ>ynMo5%(Â%0@\D(%B0Ĭ1 A6KoPQ~)"88;$ <˛Cɮ,;}IYIIA|z0۲v5DĈ2: B~ۼdYkZ FL(H4#M#`*hYjL %˕\Nq>DmfXHPE];, Ww`rWFWZػ<Ӻ $ 6I_-%I,։&H\G@HTh}j]FeJOO"MSpMr'TG؃O vmEDh-W\=[IN^ۍshwb$[# Z%Z5 T^u8}(C+Xc%3@Q[B2Vk 2kl`YJN݃(Y  DRL8@ZE+UP pd@tH%s + i'G׀4b~8bvn&R&&AU^je~,+u7.-\.3ʥreŨ,[b.p4.9Dfc7ri΂X3HE)vTfNp؁4̆hʦnz6qz=iwD;KHԽ6 "avh8L|-+lhv^cUB99MDEz|!KZapKR|@ȸZi3ަHVjb4Ơ_[fP3b^&8 0FY!I$v嚃eD?{DQQk `4v($idCEALiO.KpB(2H2Xg$Q8Φ!U$_h NAHPnZN^q&_nZ=Z>nz;ʠFD9o [;U$hĦ8/U |ZuwvO@vkgwmw`I1Fr?Dt?tx5)~xd-H\[CqO xkւi&z9 D@97@Kۺ~Ƭ-Sqciy~:72x PCj@vG2_"I/Iγ ?Ns27V 6^M]V=O96w{3 NI`VD-LS!빵[ȟ @ھgzv$ wV*O'?hq‡KpQ5k-bŊE񚸐"C+i$ʔ*Wl幘1MSnݺmJM'P (j4J(i"h!FDM![(@R VaĈpG|p"E AuoT\)|e,SĔw8QH*8|}@!)\!J"QjI&%رOʮmwԤI;vٴi8 pArC=DjEQF9ȑ/Ǜ OeƔ= `QI0} UUiTDUVeQ8h~I10ZT0ht}W 6ŗ!N<SHFdc Dfuf )ԕBUa"9NI9;H1մ;׌/Lr?pav\b]+$rcG'G≤^Kǒc |LR7q0|t RXE`Tx)$PVQ66P[*h&ixb#X)5&Ta8Y 6Hi8: vprkMfl<9.:QVXOL'| 3aT#yq J(oE`B6րGҞh] ;lT: Ow ZB>@S"Ԩg ._S U%\•iv,< a(æ0AxՄ,҇?KDLdxT@V~Bdahژ1Wk&4Q(C0Dᚶ8H *r'@Y9U AI -UPL)&Kah,BFVg*%YDh˕h\PD0^0BVaHf 1ڽvG8ĖVtqcWʸܼp[;֥}DF3LH$3bQ Ca OhB~@Oh4A*2Ҡ?)HЂ^,II:P1`x*AD{ RgSHҏp,PL o'4JjaYLejE> մjp\Y:nVUYaG|"x ՀK̐IRuR?_ՌX| SBt)8h0.W$Alv*4d b1Y_1>Яrpy*&%g?hIa#DXTsFQU=\DW4]ԟ@ W pذPuR*ep&tI"b@'>-2^!6s'ԣ6Jy$QW{B9@x]&L:.(Q(RA;^ ղs"3"쀘8o{ 7 WQOKآJ;񎮂rhx@A5(9R[J&Ԑtfƴ2|魇5rسp?KJł@{ݯt@Y%T>!{,TfM(VTQIe2A@1qDžm aȉd ^qL.0)A+(<#`Bѕ$   ͛W w=!8=]:hq}_L/݇X-@dIEfeVXTHmTFJMVEEEUH[R `%Dڤ)l(=`mHf܇e%"pq9P' B,[B@ b C6d`Z4ARy- 'RN4 3$%A 3dIZױ)]Y `]ɴU#Ea?b>$O@1%2&R!$9]r tUR5^7`%E$H82~ D #YE,#SM>? Mabal dOQB6bDdd EVMDUOބ;w9$ԋN< vq  eН\܅ ayEݤ>I$Oj_PP-nRzf<%T5*\YE0@\Xg=@ YE8@' 9,] ^! @.P@m-B&bRc%P$}1+B*BZ@ 4A"BIn$deݢhW 2xCm/^Knj>f2~]`]3Dq{r2'TM-)u+]XwЩyAڪ8'%\ i dF- Z|%g똆 cyeQŻ\ص~Zz+C hc:ȁ%+)PB! %ԋ)=oVߎLo}D&@y F@PϊHI0@rFI9im@ &C©,frM=H 0trЁ$02.lB"A KhxxmZ#lmBRmo;PRLT:(T/WcY@XxЩPqaDN1͊V.\[P[ UvȄcX쎈X#7$0%@ahr't'# eI 4B'$r*/.w4Oʨ/oq[Rn:/&s5J=m ]"F"z]AZHXɴ b~%gУ! #b=[A>3ᨰ?O@WwBZU$ HKCK,7\CEjqj~GwJt0[I3Jtt I[L#"[FX1=S'zg8;X8 - dۻ Ӥ)UWU+f ?OWvoC@#,L1SOK]I] -$\\sjP.uQG`Wnj1a7a|Ucg`dS0US4TXtnNQ<I)dqٴ xj?tlWU3EAVnowx px'Oxr/׬57-(6j^w2fvW1wUzPI72POgvU@.!O`h @Qg 1@whq6G%h$O׊fޅ!`kek@n˶bs arnoĀ﹞@ &6&ܒCG-x7HxOߪwDwNr`? y9TFz~]c.f,9StKM)y%g_H\k)JT9CiSc D@ L3 `g@J41CmVw5n%0ꭧNߦx{z디hhLdz3 !:ɜJ,8Y=HMxJ[}swnh?H7b~=T.`QZ"rTi_&>5>&Ky9 0DIA#@ujP::y烾/}J8飾fR=A@B 6t0@xB*lI%J :rذB2\|bTpxΤ BЋvVDtD -Iˆ&ktM1T>$qkmkU8I 'R@9BdH-`@r'(\nJ UpZ:q^hџI Zt~Xvvl٬ն}zx[\pq{5@>~}DUãH&QTrE(}tf ?@pQ0"Ыj&*`Z02 (,T˭ ˮ~M@`؁?<ᤓ[f3:+M7P3δdKNH؂$u#&{3ηYι;ػhr!J*%Ts%[=𩧊f" ">" =GHpAFx0+ `u'=Iӈ;sr,ڇ$ "Ȇ1,σl6qZ'\O2=t)OtpC;04G0:*,JL/T $zaQ4XA16lV\ӹW;XұmX׈$7tgxe*2 -ں VA3kwpT<@&=::z;'>ɧmTGJ(RK '<]>c@"A~M^eyu]Yk9I$o˙sdwwαh :UH*(U_QBϙ h,4;mL{# RУTCJb|9+T$po8Q8 7͞_YaL<G't@0@%@I 5.r=_+SS P`%sJ5<ةO~Z֒W کy]Ҿ @BLQ!$)Klp:pr>}I\̆toBMh$D,eDxmZɢȅr)3 }꣓:#,ѝSy M#HAv(\Kl2!Ẅ۟9&6щb:XgˢUw-Pޠ26H%"D襵\$#e❭@B r!ANȐL$ - 2dGE:YOfD(WޜYWTeXJ/[.!Խl  %"O@3?(г=d! `\]`KM&5rrS'Xw8Det8zҕR4=M%S]/p W%Á}_ R0E^ Zad$eNsqKȔt*oZ"IK3qO0)εA.**oOd9xUDr8*TYh | jjX+I$4$S|+NnL-NijKvi%D@V_&v fZ%(\KX*V13qԶmn:뷩 .=׈T-]G [>^lUxOCԶCj E$XK7ifJ }{ U7;"[,`\NdR( |`'%Xdpk肩y?9pNG%Vrm^JI10ղ(` Ki-E//BТF0g [5nᱱ\_1E0!hgG>isJNAv&k~y{fMvgzխ~@OYm}r԰QJ/",sIQTT6aWTgQ&+*#5Q (O;[m W=6r "L(nŲڂڒ4@H@KH 7ˣJ9OȂd(9NnKFl, CC4L8MMQE\TROifePPQSQcO#$/T S%jN\.0.^ dmL`X2AT!MVMVml&aEYtW \uRWu܅mJPeC'܂rLvw)_boEQ6&TK-ྒ3 zhvE ! Snz +̡I,}WP}'~m]d*kȈ ʄRNX.(] 3>VQ %x@yJ&EϨ@y1 w_׸zAi@ niz,5YNIJkw}3txTQ7uW$RwzuJvwQlVc'Zެ>w?$j+$j 's;/-A|_-A瘎.1S|M2W9.g} ZWcnP#jȐ$ JAhbVbፍ掘"%UYCwd9@ @oxxoTq6 ^8NaO!keqn&8P{T~NNu\玒,Mfl2% 'eР%b+Vo iן@` :Rk\hAt#φ-58M?GsFZJb[g~7yczPFeۃ.(wAC5eBZkT,YL i <hZvԕyy}z˙[I7&O#yg1΋?VB42me; Ą{+JB-ز] ST Saslqxk=ZKq)F"*JYzm­M^mSn; A#pn K4 o~6 v/d LשW*%们黵e{Kurx )fɲ6ɕ[El͂VNaN\;YbFhågA)-. |@G8т֭ZʗdPQ$GlkhBtI1|Ř"N=f͜bs;~8ɔ%ܹwԭn/1B8:լ[~ uj6p2qbr9?#Ņ+rd̔+]Z2f"4K=?(y2!n Nr6زc8AV[l5WW]LDZ_Y‰aDXTa]YhFb&8mnraC38DF$]v@4Tt?M7dwO $]O?TP'_|vUuVZt_XcA%Z69\W 2h&b:aChy!hfډ>jb*(OD)EGBQq%XtTt&MYyII@%%RRN@O Ad OA|VIEaEgiVJ .4{=`'A%U矂Zh&٢#BJoIm9QjڭBt} Xj=nA-:w* POzHJmSP h雜 `UdiѢ)Q۪Lז)._V*c˴9n!6joՐ⛯@*e#GoDp-܌%%RG3smLC32R>3y9EV: 4e3:\T>u_C-TnM3RHժ(餶i/bE%^UBg6LI\RܰjgQ$?QjxS<lڳT#Dc^dSVNB[ 9`E t3Bvb%dv3]%;K6*=&+EH11H5U ~10飖Ye9LA#%l! $Mij5u0^DY:RswFnG9-HQq)ARU孄cydEB@'ls2Ф"D!ɈUK%+/Z9=,JĖqN Ls!F1UT4Sl1D-e57qИRAlYAĥD 2+IxC4BA g P,>k_4Im"Sj JAf 5s!*VhtXOW[Re#HD!YSa$ n9Bw>R[u7-KZC)-U @5?$@YQ ;ra 9 Z꩖{.AH@Tւ*KKVK22 0`4:DՈr$(, 2gֱw Ǧ$1;N($U4z|z2cYyR3)x*! @J.Pys%f^]L8˩UuKtʎo pr(eǤK^GȄˤ`Y'. !TS'bU)UB a; Q WȬpcjq:(rN3|;`wc|ຄvô;݉VR*c{k,Yfm3qϪ, r5`/C ~hh a5mm8Xv#STg jo1ы44h\U1uS6 p)=u,Y~3 |gL^0,lL!3@b^W 3PGu ~$VutX?~'YwfȏY>zNo"k.u./۷K[W#* i< '`G5mh:r(09Q厼@sMG;v{@KsALBnW)yOdE`7~C2,L`wD׹Zetx!>֔ݷ836HDϺ4ޏG[g=5XHsbI Gy()lA睚_ -*0;`9q r`.ѧju=c?lWqh{ 1]A)6 #*F!JShR]37t5,Sa==Mh>sqƲNB3w~rHX->7.{"!߰zVz@XqnQRb\q_ p;68'Eh;w}5iHG6f}s%h2;%`x7Ԃ vkx/;LqG$]a̘8YamaNII\h@r(s_s8qfP؄`Q/iEIfg5]n@h9uw{rWhAdCc10AA#A}`CGtAO%CKSyD~;yՓZa쇎r-oR..H LQf ͐ WiaIFy(9#obA?jn Hmj桜})Ҝ%B'نU=ۧ=3T߲t^&Y~c&>L?- aM ^5P ̐Bњ+RQǶVIw)ԆAl1Dr:Dr(kgDRZX:KI1'o60c|M,QGYǝC8'w@;Dꨎ:AZcД2` 6 EzIʚXcT)ʡmFm|76>\'u)եA% # wiz^$|:=7Z#*t^&_be*-痃@i:&[hBzFKslQg* :?bGxf\4a&]|5RY?]1j*#lqO%. "͂>79H-H`ʭI 0:檩)f:F.B]Զn e~3Lz'FYtbBstux?ĚB,DS%Ϻ 33y0J7JH:P p uE7[pڳ>[@;"qGk6vdYaAQT+b\buМG}(Yn ))12 z1&%'KhE ʀk8P˳fEaZ{3lyƛk9zhbk7#10c%C5t#l{jo^T;I38~.E`0 kEث芤XaԚ1gȡoCAhvR:]kbKk4w bm?#Կ"*.1B41yC^a29J>>q 0՛ `(;Qؽ^[f;%nڜZRqr4I:칯t 9D,wHZ%j,2OK;?:x m\sܸ{ܛ뺤b)3$$[F*Q>j[yqRkS<4htF:WAwgAYI8+W ˟Yo;“q9fS5f{q0jR)~\[-)eR)5Xϑ]')#jSz(npgBn˵X<ʰjEPUɐ \!e(ѸVRڕoQF$wr%wpan1XtAC͌ɫqz$Bl># 4]Dk\y$0c"pmsf%hNnމs#4ry~{ڭf\g]$.J`< CҎnfCb74]xZM;ts>|EqNrb#u] k"_$\;dOƧ7TPa.!  B *1`=rR%TDrDJ-]0e*nܼ#M(iN7sY4΢,z$z#4k #V訵G ̂8AȮ!fumW !Vల޽)FH#-Z1CFr؁ 'U|fΜ1Nm͚ #j֭ JE?lI)֐p@m*T*MD;?9t)S='͑Yrm.ǭ3吶ںnͨ[k9a 4! k$Ė`L3D30~5 74(, #6 (n8nlι*%*O'j:ʪ*K<#+'HI; , rA?A,C@š%DA!34Ѧ&20<G$ďND.lT?1[\GpRN]G8 ۋ&4H2>8=BRKN*i/|kd'Т 9ȘK*Ss)wʙOTPu"/r ^Eo =J!ߖXŖ*{TiGx̔{CPtꦆIJ>X J"7ҵdU,afipN;i-0\754DyIX?і߀TS.9*.2P`XLӀhz+ה~h FJ*FkY*N2 ԙK|!sNWi6]xz͚Jňn8(|kL))N#jJأ`+IvpX 񙡥1 c ;=\hrChMQ<$صmlI] FnUv0RaݧTDeQPXOzcx緼GYbWvݭp2@:E5x\-}c@"agP1.M(HTi`]DITS90Z&GgTXG#@H'j ^#fLE v4p6_ '# P@vjuY z1]٣r -H$,!'T@V5"CJfژE`PIwJ-,A<e2C^l,g\&FJNRBF.tcV)SmUǺqDѣ+6ӛ ^S R9V"ujUfnK^kJTJW[ZԫM}/g6Սx+б ;0($7WL(NJ䖛1NB1kLp1q0ùm:ăVcdcM$S l2ub{g3` ?[%qv1:@ &d=-{xr:|l5 #82?NA_Q5#\SPqB(\ݾCvCڔ"鰛Qϩy>|;:~MoΣCnzct3$>yQ֠o-zƺi솚c8\/Ǐ[i˷{i,|G@m}H<\WQ=tc@9P~/qn :PF0CΆ*ݧ eΈ L"eu B\=Pⓟm.Y5ʯ(GïPkʴ;0e@Ld/ %.X~/-)8q^6F9JLcVҕ{ X\z| K^rfF"QEE#"~0tڙ% Ln<>e\x3nOd9'>|,HZrcI-sҗ$d0^(LBIІ!*7kp5^6&kfq{"-RVLzұgٳbNxR%-#wt.M Ȁ1xt(2P&áP ix;chzWĦ8% aR~WJgSXU?өimүǂJKè*$Â.uE/ 6}ؠWv&xMVGկc-Y)Jms ~FN6uu'lf# ;p֨'@׌n5t],5eJ0 hWZV0j/VlHklվ6! \_`e\!N_sx"Nw-&dnea DM Z/2kZ$H-G /6H-U %ùVmc~>5@ާq`U[mt)YaCl2+{aeuWh7^'Tl^ز >lX;&rc PCNnrXrA1Bcf2 jĨ؅Yb\.a3Hɺ١ðלWζms۸κ+ng]T>?˘.",]*zv4 oHyH\fD|i+f,4 ͺ݉ёΦ%+Y4.7Jv;%ZMcMÕMEKZ:V/gy;fmTߎ/q*an:.}ZIkwژ$:+!2LEVZE|C j 98LǍs<| Sx3ҋS+ﵮbRcp֑z'ޭom!'| lX~:s 00j}@5Z}/}}p9GD~Dmq~@{3#D3{\~B;7DB7t'}%}в xa}#fZn7zgՆͤ${g_~ C$sB)Hn*R-N'zWACjՃ9WUHDⴄ |FCY{x2+s|&C^<}jWZHzDxzzRBvw)ycdXcxH6CC>D8+NmAT~[yo6'l-TwwvgxXD8XDGVe 3S)dXFi}VkXЉ.Gw8Ê4nHt7:,E((+qw EҪ:qfO[t.{B* ׃c[0 Ѓ< <8{W%jz~ETG5K1mC`{w7@Ϡ;DF/HOܞtV6$<[E>+5[ ^`?do~(ҝ ʔ 5P T<zyo=|V}Ww.dNeܸRM#h`i (" K)\TTIf_`ƜieN9{%ThbGEܔxԩ[/Xb˾5pԫw߿f=-saFfϸɔ_N،;S pAj4!Tk5^m&jjhy%|Ns8bn*:;kR,/ <[= jϪĢomO4JA@= \r-8T2MBC@L1lΪPLQZjѯu1T>" $oMA*;T$'J`P0ԅ'^TALNN\Nf1ʱO|+P} %V-[tZ&s,H҉ `L5e-$bTK B0ǴVc7\ED^{0g*Yh Y%YL ["]['0UqG㠴OXW5w[6^Ud\%j(ZWD5`B~Vcnaf*)nLN貕J 8**8p=-I]uTc֭ޢFkIJ}sgX sak񮧉 E78:l *@|@B[gSΉ^l.{dJ^gLOÓ+i<9JAFA ;XF5줗ΟQeTc™E$j>bm"X+JdzB@c,`.rU9HB]vWRS-,1XE)xb>lIX!T) F|-0X%Z"lkUe@Dљ"ζI%p:9n| [̇LuNp,lJ3齄*: ] EdNvcĴT5 NA K@B~P$ wz$,,g|J$.zzu^K9*`bp?Zm@&XWa$$ťppa ofCs;[i'$^U4Xl,$z H8P F'ءU ^uGNr_89)2zrXYfD0Ab;ztA R6rF)sL dgVG_$b hp W@D+AJW_S#OwԡѨ[jĜ:WF6`.@=ә>H g9cp. A({blvV-J{ 4an4UD8s+(p\d y5{\`r:F}lŇw"@wW[uiЃp,~9 NC(zN'fc!:MKUη:m-zz=ѷ &[_;nnp'`ր5Oo6{6,:MOUjƖ|q NBa s>d&Ic<+v1[y׼Zr@bQ-;(n=כ+3C+­ 뵊2D>i(*ycfXC23؃+DcT(p1\?jgK+Z?!s#';T?O#:כ#;*B ;$=۰, , |Q@A 4=46#<6cjp;?̑EpE4#h >*:'@A+BADl$[D I,BKZ$)'&3YK89ڜC0*|ǭI,ĈD1,ZC\Q.JȄS8Jp=#d($9QX FCdz` t;t!H GJ\u(sLњ2ڨںQ48ŭ)(K\E730={ӕ`HzR(SI.X#<` Ft3kKFyħ=Iuu6DGnњ9ij?jL =} òKl)O踇ڊ$RȄt,%%0؁ 1Z@jBܼ l-ܟ KDq MC;143ħ|jJiTJLHȳ<&;Fk h%4HN\N2J[8#<%/$TQ5E(#hUm(UC0ťP. M s` Vam =RSDNuZQނ 8HpeFHMP/ͳCw>B*3L3cHj#P\ŁTmXL N<)I\e3:^EӽdSbcE9mVzQͨǎIB娭ae VqՈȀ#tUW!vJP:CWH7eHVKt?HMhf2Z[ƅ 5Һ o ։}zM%e; 0=rY̠$B]G)MJ pEO OODhOI_E*EUU ؀́+HI{%3!w[%V]SX֛Su(B.[ܚ WPH 6&iĥOܬEaHf\*UE;0sߩFwkXч%r Zȗds2uصmI" [źG~HJxM^Mތp^rLWW$RDHamRR4HRK8]tˆ ]5UuOҤZ< 6iSKV lm`* dݚBP@ p TE- uPJvXlxdPQn]R=Q`j[)L +[eL2K0TM F+evT/\a1l33Dȸc`IxC2CDDod-]` }dvtpwx&{VS=#=}%ĭJ̼头G]+Lc˼`Έ0Mx'hcTj/kiC(.H)I)!Po8!1IէЇއu\PY͆jw^jѾ*mPmdh>ljLeUg%p†k>0=ϙ hOJZ|j_@TCgf$ٻ<4P2$14+ЂK] K)1[ (J\KL;Ђ;h."h2m/mobzFDI֬bQO ߚ &T4 +鵶fE>Bn"D$N1L$aL.h*pV^sQ8᬴* '&_,_1h H3C hs p/6pEqTRLtQ-.jzpu#>0ΜOXL3r`,DxDHK.H8e_:yQb&7u 9H=;I*g \?m1^>4mn<@ 7Y޺Bj>_ސ@Wn^DwD 3phw"6Vh>PF|uyʮEЂmFm]w\ou&2v47mcpUp3+9޽B\NʖǸst {!0*(;xV`KH;ıDO}YW ULb|F s~x>Wow'(Db@$kh.mpxx=`hvn]5VYGjt'E-nh1ķ;Oy)mT<9n@b xDtDW$) pMzez['iv}zw`I('h {\UbP'piǐT @Tx^:L%+Re A-iYQp0קl'DB ƒN0B ;| 2dx )RdLNرd+sfCW&Ιv'PtEΝ}sPtTuPe@e{ǃP4~45T3)5CR#dLw\Ҋ^ATaX*(Nm%=5,Z}V45Nscb:Ȁd*d%yvYk=Yg_zxAi@x%Y eRF,׃sm4'p*C xpG*(=*ǼS6⴨B4!W;6d . a\rQC|F,tA!vpz_\nh_k,9*6P*ɁVkٶ歸x /&jpt sl0Q$A0G}Z JrLfNou15Sls,+0rtT7۰ȩN$=gXs|hvHыz,+:t_VN5VbNdr5o-h}!fI fskpHdN Ax{A  4i}Bc2>va+lqg"#]b @ng;WBt \|$'0 @%*vIUÊO [؍cu1hm0C|Ġ9= r(Qdbєg4Zi+WdtQ0*?0Q|{ %+HG:lͬ5k.1CBH"{ yH*!. @DdЄ,!'kJrq ¤$>"XxBPK8s/ `CU0?M 9hʈڳl3|6x=Z!׻Ǿq]\FlÌoP{mRH7M (`Y%A#:]Y. T  F0:I!4TeD"TiLiԴ#i(Ekˉ jf2Z=ijFP^n@K|cvr4ptXcӾY}GNgzx&KT%VAuec=mTo URye@Duim9ĥ/mkGB3ђ|+ Ԁ_ωCFA J(yk` [cO*+q\bIGS2%McӈE LZMxj{%.B!]d56N~ٷUFc$#=G 9nT6[ +P)w얒¦Xo7@ޙ hAG =#ѕ.;a`P1`. 5%Hhr(1Ҡ#92*clOrۣ-1yk&Mts c1W4з́װsvmdތDq][. ϨUGNnv` ^l Z  N?;8U"e#'SP4Ny$ BdLP9CrDUm>L`)Y5{ lԝrshٚr>"gWW,\7zޓ;"{ ޿¼!wFJp "LNuc+V f"$l&DPu -mmAY2\͚顃] ̡[E_@H{@ݔUjgdۗHt  KYٓסq,[!y\a x@)e@J@X- U:$\T`+)HB㍁:$-=q0^' Z8"nڮ]e ^:0SZO`*dA ) Z!*ʠOFZ JIՍ%]kT[ .Hx]%%١ݟ= @_A'BfՇ[Dɰ>>l$AU a,B$$Aؖ<9!*"Z(!2?:"$$dQٯ-^u'bcJiaVEٛ!m zu[bȓM|"S;UA tTG2^*`.! V! _(N8>,/ZV/R,h ap^فC<"@=8\%]$%O֬\d Sy R$ "k*r@ 2Sظ)$+8buɸL$ rD W]2J!lỲx@Q&n_5 \VH9:LCmC* :V 0/!8A=@=K^A' 5Z" d@@ ]eCZ4e_L$b6h*BY>J IGp]"/"klg[p :Y mȑ#IkfT&n"xfC9FȄL3B*B,,M T%_*%2ï"+V)F⏉ޭp0\N$+:=܃p')*K f2flkAS IӶb[OfHdw~=kfNX4k~]%B ) +aBA @GRl,5AJ@I. *Q**b^pҬ:tplE ϒ me8KFf mlI9!IxmE{mpDqO]G 4aYZT!C!L/ܞ"`!A&+PLiډ^, C6ծIA/{Jb^抩v.N:rH^I@ nY9o/d$[Ikd8O$o:!A:Lomsh tBȁΖڔX^TzBMpL`P9$:,p60ɍWFnYaq.]zWF"xIL f@jIj2FaJs 2 128vD5>"j4N5E[)/q?Ӭ}񍄮3Ɩdfz ~-aZ"\hbre"I;bAKeKאHY>ɋ}0'{a+(ϖr*AwTBʔև\g4IA@ Pfi=G ^ sp*a0s3;4(:מJףFn۸hٓ\ { H$h R󙈄 Mk&?RjPo(KAX5L,` AD۔ x&BɎ4t3τ蕴IJJ)cc4h"ߓN6aF8SwY4hb{p]lDZ{Mjf@ct ģZu>lVv7~`7%a{2'vyttu .(Ah'|frnط3g~ wf8v +YJlO^0n_xvpw  Z B2å=Tw3۸_xI&ȏ+J #4OIF CNePeh3V[ƺ܏ }I=9KtDkDHy?Vs0`Cy9p( @̂F'Ō;bvュgqwK)Gn%֜ɹeIڨyH;3/>SۜDf5#_d >ù C<d{po;/ p G{:Z+06N:4xľKS9yoɔ+ddhf<|gPc}ֻFO-k<[e{ݞMU,G-UԤ ;  $ &BDa-,TGz;G`? 7[y7hz @uMLWK淗wb\f3պnWޕNBjA]d`,> (H.7 Paȥ{1luOb#G{S3˪~S:5c 6tbD(98P""?H%tɠA \0`J x>pPJB) aD#FpD A$q5JذT\1{֬*Nz5& B@H[Q ј k:{KهlZe RkРjB5|tDB,j 6@a/"+lᆛlp1[a̱ām9 Y'3klQzj x[ȸ"t(A#J(L2$F lr)R;)k̠j @! .8|,$,Jx@>`?FlLe[Tt;rܬ EtNXcqspނN#}-28(!b`%5 "o=1Wr'0דi/Nzkx?AzJ" NK/tQIVTNQֈelq7]׎&p*x**In(42ȼ&s9jT ++B0lI+S[ F,{x~ Si\ܸW(ʴ6M,qKZV9;*j?sj?T " z!;8B A5DQFm}FFqn!ޛy$N4SzNҲh_܀]%7}RTq7\eR#D瘟 P@x ډ*V@I&w1%LxtH *bpI8B4+Vh¦0e'D97ݬ[}4i*ADj('dٟ`;\Fd XŃKE5`lhFPЂEo%@ 8RG#ց|w#SX2%%z*ǘ<))Ђ('>BЮxU`YTVdne-ʋ 4#)625K@v6}[P42ؠ%tœlKyDH|gN&<ӔO禒stDw)S 7:(\9esT @Y+ry e.g-DJcPJ3&0;@䈒E=)fv-LsƴRß$QЩNyJYLas1za @1F9.wP J} T8az(@R-iDL z\3f80;t9Y,buGӈl4Mc!ܒAU̲QFڂz0&%_ pj-{.eQ̩zݫ[C)f 4mkJ^vS݄~wdJޢ[؉z.eoy`>/ghM [u{WL!Z djİ0Q ւKUBl*q cw=<OL̪xNF"s0O(}z_=QE6r 8 Z道Ră!1 F~KejmvqRԠss^&WɭdM~$Ck9=P[D:Q)1`KT7/C]Qb.ޖ+fٻN12`&Z-Si]9&ߟ>ņ_"8eՀ:&\îveԦ2){erq6gTqjT3U7x&*zs6E}іPn%D`&[%GнL,™0PHx(j;O=sVJ &Nj|*,73 ِy +KW9[-ZM dg(2QXR0&,꣒:Jr3M4Ŗ r:89~d qB;Am95$T 6pXUHµDD&njul匣=& ;zisVo"cB? I7W#ow<@<&dLjG:I }Ocbwc;z:|d1nU2J.`K, w ?b=biO`WXsk⦶L42ٛut㛾黾[|v4YD <يuwwQɒ͒U9iyM5jբ Mk&` | >\v+8XsSQ̸|;uxQ ȅo)Gc/yTe$>=ϒh0zif.Y;&}hᴢԌD͇& } X4wUofImGs+kSp Z}"ǒ ێe@H}`O QQũ'NWѧ&c7xt]ɚ_=-XOf9Yb^.Sˊз˔g[]%³]MeYmYnZ%%ȹr@Hww߹r(1G#)LHYs$: L-2&/-,޺[]ۺR_Y?b]cfӱXb 0a 90D!*(xqA4<ؠJh%[L  G1z819y2`pD#N#H'QjJ%++\pJ˗+T䰁1mEMI1bؽk%UUf.>8׻޻Y^͗!x :ѤK>:j 0lg@%ؖh$ ˗(t&)Vppf΄P0ES%(@tȡ6eE]wᅈ'ua- cE&eag]a@k=yWAVl!nFP~iG!%q3Ҹ8U0t6UAJ @ U]R yTpB =LWYw]EfgCe֐[PN5 ^v `9XdSem֙z(A8d(ۚcCp}9pE%|Бp"tH:&jq&syupPL Ri LTNleW^Ti6PO妀vyk^VS͝zkeZh!k#% &ꉴDOT~#LoB+XC> ,6j+jM I8ŪRA|dS?"!@LRn{q>mXbQAT{>B\~k]fa^H`橮 Za~u=I7rhQ\õ.7purl{ jh&-D792?7SA/,yLG6Z~k%F)ySVhI g cX ЮuS0K$6qwSRŏuFB͸pxve.9TC!=#+mR2RڪeV\yc0@-!~4H|?*y^5EU# Y{v}E  sV'*_\;Nڏq "6Cک_m܆~Kd~F-Tu-a P,qld'ebͻhQJTbS>]Xx`9#n:`>S֡o"":Go bml13#Xr\a|aaL7IQMJdUT{=wO Ss74d),{ӄY8O`co)1=>פ2i` BapE;%v7)+X&Ȉ[ ޷ ֘(bJZ8/G*m7IgIyE&WHG?bi9!+_Tn 3 YF!vnT0 j׋_rf+X6F А}x$i *),)Mv{čuJW(hk#CAA|%fNEK HdTF"*2eD [Bp3A7[\ ,K,!rqcEf rq–aqYwB36| P AQr0 IyY^[=yz"is覔+T)7`0R1[DJ\ VVM#pLj]? kcFɇ Ű̊ab* c#e³L%#6+ &6BqC6[TT'`7Tz]&Hu#B֠ckBRC5Vjc?Eƚ Ț*e&F琶;*B *Jȭx[kRm3T:tɮvճcZ&8,Vyl2@4ą3kzdvEPU`Ő z*'i˱5ʶڻD##Ļ"j*dZ"o6FZ#708[)N d=J(CBDJ`eVY4j|Ik[P ); ~u{ ̍R)<<ŲK9TjHh2seyySN0867uF߻Z*hcA$f(!¨TM@7Jfv0q@` N P\R)(x#e-D#0;1۟3i,{Sɫ7"Qש9 0!sǧDD"D<j`/iS6-=W%WKkP@0 ;,aXqxɠlf" M Q9mU ˱jZTctAT=2b?XI{_*[7SJֆWlxɧ$yR<ΩL//1A?\_H_"ӛC,lH;SI,<=I!К ]IJcT5U`NRq @"cX)m+҉RFF[!;2ғC6ᔗX-C&uGwH>J!o=`, -h*֘C!rp @ 9XmΘ,ygH`J#)87ªעE=ʑw%ID\F)E#t!SZ]õZM==ەCSړqmjr@]q @gp$*]q!gZg"P3+Zgruҳ ׷=,^\јn~=K5&d;;픊a*kH.$19T.+٪9ĕ\5ԖfeVQ:>'@oB.D;^2Dë3LhP)LzC."qH4^=B|CU޿2,V jY"SuB pL%!rF.6#.˅JHqhAFٞ垢ᠾ=;2UxLCUu6iZWD=~4lTs{R.A.׸}GޒO6#M C݆Dc|{w7_I?]z >[~"_ZLOtOVpXt@d 5R# &? ? 6/_t3t?I(SCk+ƔTb}P$"_I+Jٍ6}0H%=] eX]nT߶HӤF_Ia-e&ʟB]}i/;ynó}NU +Yy1z@KS7N| 45]P?R(?UOd6ZO,P/b?@Y@X  "LPB ć lB-I> #8YI*G`A2DА$Q4ܬ 8e$-b̐KF2v̞=H,iν=G9sSwZ34!@`… FX 0p@`^Ɯқ2VaŌ-=WDZT)6t8Zᇅsp8gw0#' ʠO], 4D:fQ R*RiPV*^;|DlܹUg l1D03 2*L3 a@+.5nk# S[M*$!nr3(Q6>.󈅉p )) 3 &`Jj "0Jj#HxXo+*j’Ìw*@ 3A'p’0ppӭ5 90EQCkD?s۔ŏ`DKs)PTP ' ,J2 Nk3 hUh!'b is>D`9o<8ND7=@ͬC9P4tmNa$8ƊrH3"N"VɝbP_y,G+E Jl -1jp+0VL5 MmWpŕ: Lg |^ $ ƗHg;u# ~aQ 6DmQjUȊh);%(L*,dHagXe˲lisN/_ vhB-zD#4'2;~@"N`ԖäA:u`(>Vʶ3awG80_*C " hX|̱ƷCBQS{wΝ1=sA1N"}ZHF6YhgA ECYװTR*YIxL2հ" oU80LINFP!|`th4r"Wx?"`~Cv!1B)Ё!`[ukԌ(nuC eH[ǜb"} Ji& LJ۩T*"5+ U@\7;|=!e8x:b$ĞdF f0N` ׌4PAq7 ؼ5kR1V "NV<*-WDR憫 J{2X-I,rC#BIÜ@Υ=]2A eAET yfH#"D~S<"*әRQ,裉ʗ`uFf&$Mp4@B IxU<Bʮp=:CDD8,;Sw ėnDys[p3c{ԧ>uKȈ)Ы q*5T6UC#l;55QHNd`,;"]K$=@!#@0$AD f= v MO4bI Xaϔ!4/ MŝdvL"8:8Sj.2ِJ5Nv<P Z^'Q:HL+0SwPR@{d%(@Jub&PrK.:)(ܚ.) FAѤd=&} fx2uG#>Ar"?x.=Q0l&{&OxCULݝJwG L_\2LM<'! ,^ &\ԨSÇ%H1a7vin5~R㶓ۺᴗ֡IM0sl0ϐ<2J(u?eSӏPG&JjCu5u)J5}.5jƠ w,I#u[RSxM ]K:;sս[{2S:_3')OIq1Q (O-C'1s3<@d[`5V]B9Om]Pވ!%x}\_[$ZI{VT7!&b!7hD-!"p)XUyo1gzf:TI[aÅ @f0ـ8t#"w oD0Qc3jY+z(V͜4 8zWV:, i9ؖ9r:lCzX^m;D*콄RTx4[44*60V*˨/ mqPL6p[Մl$4:Dcnj{٪7s0o&5Jl+,M5 \OCp\MtXnl3dG-l uՐ6RNBT=؋Z.R2.~kk-Ӕ,$NXL2KFd5 yk/-9~*䝜?~4?I}'x^l,3Nhn :tˈ*3[G7ԾXk~gqa^NN74f\#e.49(iR]o&6^BdC JdD.8͟R[&V̼ QkbS^(73ɐF1ϐjVsFuk wӕ"HBbSŦ'<wJ'庭/acJP0x%bey3~laXeOickq^gz:2:`7eb1K s / kKXj'0Zfl N9 lTgwaWY0ağ0\5+V-/7Lo66}>p26d"M55ǸT{- I-;TXse w=_~[uK2!e1Z3R}r 32 #ǰ3{cIeŅ M^٪ͬW U8;FLcF%E}RRxo'Jֲspnzڹ>me5LPrAl հp6cڦ#]6ob]3 3ZʯrWQ7c~B wc&)<̎1\01(i6-!k'׻`<[+J+ר@PBYɜ9өh$/ɒ܉#(8Ǡzꤞɛ%cugIiɟПZ+J2FiDŽ2:2:2P٠h@Q(6`8droʡ_p B+d ɜȢ҉OYHdAʚܩڒ= g*DEaidXz $/Ѥdc6Q Y\:z.fIPP3Ri"؁IṁpJxRV-JL[p _^y6bjꌷg~ښZp `-6zAj4cڧcs1`[ ʗZ]:rDN` #KɬڄʒI.JEbݷ@֧.U :hGn:G* Xzw `e{C: ` %`EPU0: Rqn-J\HS\+3QΘ^Xڧ;!>[l; 0 Q V:R{8%Y$ɷZ9kbj keE)j[Qж[0q@ @[Kk-{G-ɩ)[v4Rhc _g!۶Zk\JsV.|ٷRz5 8IhKu лKԁ!=QP۹{ Ej wiJ֠ۚ;{{ںg5$J0=֣\Ǜ7Фibmר]o{Nˌ e8zѓԫ4ZމyΨհ05HܘN7}݉Zݗ~?*] ">}Z4 ɞ]9T0̝n_u७K KUk|ᾳ({-$#P(δy,S.^=` P7H;;0 @fPwIHqΞ6WQ>U׆`^ޑ*ԕ d~6T|1{anp.:^TpxN . N |OaлO?'~ޗnuv->`S PosB .cKn‹nJf쮇[R-߫lWo:6W/MP@>\Л~~FiVΨs(dZ~}ÝZ>:,Ȭ3OUnXtNƿʷ($` l3QO__#0% H :..][`i- UžN`e3Z_?.|_?H4K߇n>ROB NPN\@NfkMtl_3_OP77iTt!0no%4 @ŏFL .dȌXq̖-ViԨ-^!u$b)Uֲ51etM9͡gO %Zs.eS:ZTYߡkUzaŎ%[VUF6+RΥ[׮\k:V6__&a 4cHl-qx)$&l%˚4gά2hΟyoZNE[U񥹻F5yqpf|jwޜ]m5;o3_0(=<cKd!C."̢/02p#>:$Fjנ Ϛh62԰ +jJ9 )|6$DȲ*1EÊr jB+/Joރ/$Jo Jˆ'"@&4?WxɅAtm%h$qMM݊bCbjNqO>Dd{Fut-zG H#+6m'5쇅+*.@0sL3L)M5783bΟfQPf ЪEQfԎGu3C&R!SOCe "AJ#P Vp[qU%^ãm)cCDPl]ViQVE|VGaovJbʹ{t]1$A _z} iX"եNXn{RVx@).ZfkXs 5ofNN\, 1Xyw_:.5[5+X%nFL6z볢jAXѲ9)ԞmU^YIu5]&oHa J' 7phQz cz}:(3O~U۫ҩS+ee׍Ocmlha!pxg6R1/$Fm9Tt=/FeG=HaOj+Q=wݘ'Ci_6riJ>t!6If4Ç +@5 "ȡ ֩k 9qЃas)԰R羶/)@*FRר??PfCe"H'gd{5l'FRbE`rZ`>1}do=HǘA9$H|%83$ GȏЂHk7q$SP_8#DE%1AiU,(4TPFL@;eG96o%DN/H.j¼EJ ӑM Hj|ɮy$1*8lZ9O'pXݍ19.UAUU81YܢLlyjH$BuC8-jcLCFɍb`DGgAGr4RDT 5Ovs-eLāP&@?{yTVvhOoF)d*Ui!!984"El{ڢšI>iWt@0Ge㪶Te<yꕆ,:@ХaI-$3h,Y>*S* -,!PE0ۭb^L -mm 䖭ᭈ~US1,Fц픙L յ.v]AŌUE-Q7 {>ULbr'4 -J{=|pmmUKF=nW^zJWs)0qkx@Ѕ [L={1Gm)QE*t^) F0B1ȷ-WdA)L+Un!**焅ٓʖ _|\Tnqۉ7S1}x *>Yf v[Sf(C) ?ApKrY5ּbLs3>Y}"0X(;a^W^YKl\2sX ˁQ %죧g< C8g 952:D ,,A̐u#dB (7(gXV}#z\tq݈J}".@d4q#٪3``*RȘvxy7 X Txh-5U#>s:75{OpLώQK]UYc+yk1#x|rl;^g13-ǯRرf؂:v9а> L@rɇYewu~K^ߺ:0\K4:ðdS>B> ( s/(ʊ7>P091,pŃ9&`h B!* ,4N(?R¤@sW&B6R3; JyiPq3` {s⋻3Br9[KЃ0.PD7@[s I$-j< M-Od0ϓT+# yT+EJE[4ɰ`+^u°\2{(FcT[RF{;Ԃ,PJ6>C19>=(0@3u G& | 'd! Qw:֋*O5*ӫ151k3㵗3bHd'@d̂FOi,E-A0JJp?p( $PGhӹdLJLJhJl|UDJfʅ]T3²$Oŗ'N9,FsJedF#I,gfRԎfO\Dta@a%l.uPP LБK%UkS4QLLIpr?e$LQdҤ,1SS$RӤO05v:92@/ ܤ xH\@ C@}ۍ6^mVNa!ED؄mI[Gm O-Y=[ԼW'ۗm]C)HPhKMxm'}2<`(FUU6e:31uZ\R܍)]vfXN-V %Sv]^35'& U,=PNay^u}a @Z~A9D6;˂'ZKm,V#؜+v<紴i5EG- -bn>4XA .]`RHF5.5PM΀}Nja7K](0ӊ_bK"<$IQ(=JRo0SNe/bV[Sٖ M)Cut#R,u6倗q=>\b̀UץeZ@#)md4dm44dQOFz4{D&-{VVg2~IiR4AW굘囝^-K;Yp xlfg<>tƹlvdHmϬLx>(%7>}L(5fʼwxgy{U>e} #o=5U+\ً\`2c (fdv~agn봫6(:˒iTmfmi>Pp81ij%̯ jy|H)9jhXjvN^4>YM +[N׆nb>S| f3NKFQVxC7H,@DNvOx V]^DA e /EhN@6|\Se (bh>~f8SN>ӌsJ܊vh| f<(2%0e$VnDzjjʞwr@@efo wʢj(`1×%N QVW^-mvP+mU~if;h\Su;6ޠnsX+G炆7&N;ksG o`(`xJ/e /VYhf(?V0'N)%^wu spU8k 7DOD0& `KOdgSG*@G%s̀O YQknÎDGOPUZbxh8TMlwNw[Jr 4 @n$_ӶvzuEвsҳVa'T}f&K3-B@B1'~ h2'(@NN0Tv8MtCui(`JO~Lo7 JW-)Eϛs7YvyjVg'^.2\^˵NU r38 "I190GOD'U=gkgvafvfqc@28o<4ww`}؇} J}t _-ϔwp<@6^ٺR%OPY{- S|%H ?Z|*(2@@D|*p$ZηᡯZǪUV휹 *2,j(RkJ2U(sҦSn6f+2pH7nh[=pz-o?x8-؀ą'f`#F!É5l$ԪDB5S j"\tٌvbj 9Y %jMBRrړJpI\qbf6g1pBIP5dF剏y}:X2<Ȁh-h.$)_kʐC| B!z !\cV_27eva@9*16BIf-M;p .D9q^p1Ps!u'nD;!epK-q2_)TPwOjӄ\%Wh)~3e*b27H?D[&ZNU*m5 N-5ޛ E}ĭ<$eϲO@PkZ#UvB`z Y%":'!nEma8mh:ܡm h.B;h.=m0 k<#)NrO9!DŦPu0@ 89Jt zQ'#Yefԭnag_u32 Zd\  3S&)pP!4NCuR "l-2 vǰ/jpG3>q-4>8_lR}#mwIp~+2j/ hq W2]*&ƺ/t,_ƌ@iN׹5Or{#_O6<(rH3fC"D["$94!Ol R,mBA0Z;14imk~#3&BtL֌9N#pyMEEArW0\';$3kk!9gG8њ /'JPzlp]LkoMfEluGQ45Q*"@ F7 k&`3 C Ғӱt5N=ڊ>PlO q6e!ZxDrS1XeV` {k "˩#>qS+ưC@ŽMQiFm^|^%:dPˬ`U^U pY )m@&ux &_'€=QD6@ga<<7L)DB"L)"H]f@A4؊!xKG<4@^3`5)PԀ>7l RY xʬX- Y"# ٵm W b `2T:ȏCEam"7lC+DB$hB)#^a_ taTZGE]#GF8D9Сpa`Y"Sb$HY"Ə1bbԊdEprMR(^^ *b*"i4F DNZă-`"?t.l0nB$# "I1U#C`cjFpc7#`9);ZgM:S)QbPY␴ 0\i(m\6H$^ F܁'DBR 3YhThe^%+FɋA5TCLĄuڅOA$|$$"$$n|l fd>@S2\NXiR,$ř)Pj;ef9mNw؀ pǬ$UYα gH 8GI>Ѥt*\[n[^uw%TXuHC0(ʠ $ qH٧iTՏFV, & (@z̧%\'|B$84cA*H\쀮,FAt2xl6ЮB-Ҿ^ӖCQFN-LɹqLֽ |ƞDWأf8'5~ȅ݊ 2\ڠi< L%, AA}&6 |+&l<$A&Vʼn*j^kG$klU>0DTEUjn&KJyb]kKdN@DrW HꡖَW7$I+6zHkdfOsOIlZC6$&Dks+M/DO8mWmv~.有OURJ%ڎ'@NE׍ɍwk[x:yK B,Դ&/LAix"7wS3hOIsy![uL)Z5W,haB 6txBU،Y'<dH#I4 #Xrp3gƒ <Р4Ӂ 28zСOw2H*h )Te jĉ(kRq(Il@HI2eJҝ/JQ#E.4˚7oĥ|38tΣG&M:YvpwխèIݻy6 hc3tR 0u@P; -j4THjA^sبVl)zAm\TWu Sr@l+h[$g!RP4 OÇ!V{M!HΡ۲ȭeѪ*.&3*3; ʻ@&γK;+-o+>l"DLlp(s  lB KP "@9CC9 Qsh-;NA2T /:]򀦙R2R{ mJp@*ibi8-k8+ 2.1]+*6\8 2PбYO?DHEMAECE5Qu!=QR)eLaN8& T0saUhuHꔤJu'nKXf*6%ˆZ8c8,ftu5]DםwDGMwR4MzF"Har(ol@8S]5@8r 9 H.o,2Y%P-^"*dlkI̊6B8wvEݗEƚ{'#Fʹ&"x gE]bj'#1uAb>Kp™uĵU&2ˆ= 4d(E/uߍ`:8lD0ә,Y{8W~%U|'3"y(l wI'J駖^g.Šڱe0T ,D#*iVtG7pJ3J(\I I?gIq ,<ƨG+`:6h#ѭ1Lv & y`B֮U`Z ֻwzSc FZ'Qi^yBNs ?Hͯ];&REb m5 x%%lWSY!clK;YsصKs V`( puUXbXS*NY[tiu]V *t%/X D% D [Ce(DC%Ql[#[h*,(*҆)-?a PY0GGi,.WZXHx.qiAq*2IHъͣ!]mIv,0LȪ_ħNٝ> /wsP=j5@-l~jpO!3',AAE'>d{HȆdtedW{j`Dy3v7NaUeBnѦ*J\Qfn~30Y΀=;! [ a Oq"m}8qGSo[t%IDuN;1OW²#z-lm[`!GNAð =:T3BGz枒z*ñw:vE'W6qyo4%৵?[@u ot[Oo;?Z{İy>wYG:I %Pޥsٿ1q۷g ;Kk0(i Ho)V 1`+ˮtf,0' +oآ~4C/H cV8+(2伏^e(e0褀UJWZo+iK, x^-X AL.jC9+Bf31?T1(NcɕGcАHRRJ(@I8HTm'y*̓tghk@`5@IkIW+| 7.+1STjfRBSB1FL#12L* ۼ:O/S˾~%Kv)  h`PcS 9 t2S#TGTMk2/UdUC1BpJ</ ӸL‹EEQjm: ~J"1d.Kv-` u6V9J-Ө^=88/\4B't95`*R3^eidJGu_IJc@PAo)]:s;qFT`Caף=J Bb ~@ISlGUrtd|SbS5'/QVTUKYmCy9cGp8vRl&i3Dl$vzR@*r:`e`7׃,5-r). b b tK7Dpcv|6<6鮴d/B. !RSO(m2 #HW(ւvLT T"x)ED4N}uVhH8Xnoj$q^KYp džn`R%Ķ >4d~'RpKBULs*S1WxYx7_9[ީK'lh1cPx@ýb-Y`  #X@?+rG%d|AjQ$vkI]' 'c~OR9yVG_%휷mpv刂M nN4rYB.7{j_m ZQ6Z׆d/SB{e$K+),&2[p'[ŻJ8)>H7,MKoixC9hX-ɭ+W=87;p|ItJZ[[}A3^M~6I4 o$)"ל I[֫aʈk~K`ҳh`ZG'z'/ #)"͠mvIA PmEYAG^]j@#T4Xb@%TaÆ s8QEGx\AH `J4:լ[n v2A#Rd!D<2Ft\X 7/C jڼ94Q5<0hC`04|L-7ň{$WdEgEE9d$E5gUy%bev ,Tca13Yeeg6k2HcF[m2&nуq`Er4@#T ݀JdNIxTfe{@P4mWGW]ID }xZ_\іa]2CZ\h!_"",Xb)VaHeݣ;yhj#m;Jգ|)@%HxOt%p?C7J.ye85 2 _o^ eEi]E@,\C %_TP(餗 nc(*j^&{j+AL*&Aob5%F R@ aw@ӎym 0R@S| .Tk@@vy]N8!o*T8[Wc(֤ c#vp+"0qo1EA++1ABB/ %>8 IJ TLH-4> ERD%4{N?-R&' vDu ZE(;+#0o38)'B|wvV1~o# >yx90&!{峝78Cx8-'(Ip+)&E?'O ;/xg[p$`pA6)NԋLNj ^' ,Ād|#^|9 dH }Ui(ғa vC1=D< :JSmc&pg8DVkJ)RE|=Ĕ%ηJ!b$"B#,R&d*ʵtpg=irY 6rH#Masթ\x1*Q pDDS/=uȓ^>% Ҧtb@%oƬA~3g]$r<5ACEpt( b](DB"I.z¬2̗j+T0Mt cNsІY7hU6bVa RzN9թ4TyϫbucZ5W{OZJR,+!i--,#3)gӕ+Ef4tz% 1I MD@n(O4xlgY% eCJkڍ AB#,ethnTdm=V7̪V3UR6?\1#s-qXPOQQ0[/bcw*td̞Ų-Zoi "R|-S]`shQ0nJY5;)~nϵS%CYJzĎ+*L1|Ls^{v3;,[Pc4q_L^ H@9S&\,;rU,J\əI6r&k󱐅PPWIө.u76wl당[s@}"Xw*}ufu~ȉUzQtaPS{Kf*|ۄv*%g24EahauApK2A}OYQA2ۧ=gn~_Ji~7 ~jw@^I*Ivuŀ/N8b gyfn ph(4/EK"FQ?DRN#2Y%SfPVdBuy:~ :x WQo0D*7p[Up{v CDɁ{ +GgwζmOe|&w|$ hS5wZ"!\iAC~q0;H" yYe ͐ Ug1I&WPK^g%32Q?XoQqp&|1"ъWg;5:= F[ӂa17UAG(EE@o9Hy pBrz;IB+r^zk(o{9_Rq.le]g1xqxϑ%w1jby|¤-QAsx=Q0d {UP T N N!9XIWgrKЂR#v6@Vu?:) 8hg"wA~`RXU9X/6q@Q5RCrt/`QncYVP ~in UA wY#y>#EhRibGvypҁY]3%xk8t4Xwr,)RsySyJ猆iYc&ė$'%q{Ib%bVŅ7(>-fI"W:8A.@)?ٙX B9wLbivyb xI:J# [\ՠ0QO\MWס!f˖!Er%Q8$hX]D$6:rhᴚ}?2 ZFjuL*#N (%Bĩ\\X]13G3L^-- c2獲cdb2L5@dp!@P  ѨR)CemU֜'(rtVኪvMWRg58 Ц P8 c1@CGC5FfxO\J]?w#QG ֥Yq=5J{4J)MJ4QFmYLz'C1:& HcTq, o'  Z";ʱ:#nR;IO3کa%EMΒ%V7g!ZVb-cHY*74CA6^ h?#ar0\%P-LJ r +Q`[cN1Ym$׭Jz8lC$u[g_֡=(vZY4&R{-$;ˣ53%NJaOrٺ{F1Qʭ^kh1nU Vc5%Y"ܤBQ˽ܲnҢiˁ>C&E@Fkh拾J־q7xa(SWzkɉE?ۿ:|+p5a'!X%'Av!n'-WDN$G1VA!Ta;}7%(|q0 Q"­&é47\a6l9m(|FHQ,SfD,E"Eqƥx(m1[‡#PC.8^Q~KeR99[9TxYr!*\ }u68$pBla` VZOu\,&ɦ Qɒ)LgޛuX,Նc]>,lLFfjcoLrz<[@3tȂLIk̇Ol .@kZZMgW' K—җXq,Ԃs2GA&-57 nlSrT}  ÜМ:S""&%HGApFܙgg=39} Qh&]mM[7!<:T=HXj<Aߤ߀3\&#x U'b̘.PLט%XfձʲdY^ |[@ټ`E)ck|^#4"7.MWprLk9_-^ф8`4E.{'kNuQ,]\kv,%5g'OPP"@H P$e A4FX#*WKF޽v"R%sLq0MY22sH 00` ҥMFd֬C8;Am3d*Q7;ofaYNJ?{sD&Q.9>&NMZW\(%8T@pSO. XnmZ` #خu -+/iS8Ja*m4F$ѴN|M Emf(8~v~#H1I~;'h;)k𔂱7TmMEqnO] uEVz"d:vI2V$JךJfqasR}NҫڦuܪҀ @tdIѪe-h@v1ќEw '~/%BeLh6)?RA֤)F&2ңdB#*Rau 8v|3y_6?X$JM7%X9"tWxZj qqKdi 6M$ | YUCҘF!Va ,6[*Sx<DDqqQkL=]|VJ$Q-ı^f(czlFX"P#ֶY$蒟x26 XI{# @\VQbYe^!R|)bBrd?/*Obx [BӀG, n5ZR)P)C@XgRexSِ0o E 23c>ٓ{Ĕ)a%fFP~4ʚgۮx2$ x@bLw# @.u <]*פK T4tAB$cCO|S(Buo$NJJE c2i MO 2+Sa J?TlFr vX·(eAJH@lNgäYֹҵz:2#R p>EbB0'8!= ҅@ 24!2TC%iQ IĒtsňnw[T9\lI7Nu=mjW\BvZt+϶:rōluݱAߪ.Bj7i! ,^@&\Ӻq&‡ IHq"Ĉ&خ]ÍASI/ZLl0cʄ钤m8qgO'PH*uװ<\IͧUjݺ$Rx`Ê I)vʌ Jۻw_N_o5N1i/-{>-43bEox2_kLJ;]u{WEzkbWpvwM*lQ#^6(/s41A*R1H[:\'Gj]I CF`aZTFMrE96[o`TSiFӌ%IA/L`2LHEaX#L3ʎ\UhhM*D<4˄wt|L9EO2Qg?)PY2Iht:Uh*DVj<9 ]`Ԣh?z]_#Q:@c;1ӺJ[_A;f @Q.N c_ԧ>TQfAUX՝Ъ.ά8la&Τ4\g( Twl8Wbtt%.(O~Y0_+>O91:{@UtPX&},daJyY6tUgɺBͪ.P+]u&i*fq_I[!ќ茫KZM >UZ}ꦑJ\5!Tsp d҄FօW gKZ/={IÆҙ}ԢPp@ Hr|عΫܟY6^XF5nk^SJ#:9ZpT;;Xlƥ|qa 2ӰMlI-֌zc$~pd(9(IJ70^ ͆#16\1 ħ\ey-nl{9fm6/θV,xgz3J~(!jڑDGGz54LNN.CӜ!MZ>>,jm_:a q^}jֳ$iWayu=p=aAlg+yt&} "3#Ӯ0tEM?u;M49]0/Kjo8[N` vmxTk8x83G38qj;ڒ8;Mvr5 EdUZ4 ޜr#{T'Wb399 <IigBRo7.iKgtX>֧kv_c/T_(nq߁%M t*7!c WX!?ZJr*ӓ i?m}YnSEݫ9 Ng@MxdE ɤE|Vu7@P}v} 6EmߗUZʴGP~v~FjW6qokӂKgCjpkM^14'E@WUxZ0gʷp5 OP  PG hyE6; hA{DIf6PzKx0LeO`8⮹ɫ/hֵ*o-c˟jg6Hvn˳)s; u20{K @ [lc[C۱ZyCUYV+pc^ۂ06e})Wӟ׹y~{xJt۰{[v J[Qcz+;U+KCCsNjrhʬĶL4G} k`HdEKz+J+?k\+wEck+ﻵ-jid~۟sGvH&0YGpI z;۽;zǴ{cۋ{ʘ+-o*{4g6'p >d?Wp[E|}_O̓QLSL 5&h~7clFS{9,ps\u\WPc0;p:,?/V Y\;/ʹ[j{\Ɉ;hpG =sT@|[Zn'utz)˿<:18[ ó6{̻P { h-P|؜ͩcJ @LKu8)el~Lׇ6M/' ol#p֌ ۬ʭK T@[櫆X0˺)2Ĭ胸5l =I`8}W֥\ی HQ{_iԍ$;q|ĺrp]- _62IN0g}kz[APrT<Ȇ ؁`nvN'8ޒm#]#>H`j]B\Dд.gH(Mc^;8hK0Dp s<]jR.Jݚ{f9۾#P>`٘mߦ/Q.L7RZ?=kh:*( ENT^ҁIO.7S5Zz2 P$?L0~8BЍ5_upxm|E>SP>Ğ ί\ m.ә %p@J׬-HQht>X穇V<~ңc}ε~^FžH`^_a^8]z}{N3yMN->^b躎N(?ǎz>_ W ^ - > ?%ڮ+Dܷ@Hyw~y&1\Un}XdBhBN7ж;_.#xǽ٩qp+=8jί'k*]_0hPOjO e5oqOs>xO7@\p_ l5L1W+>ﰆ)Nwo +^x,0{/T\ڜD 뺰@C$XX2^cСCMX:5nX\GލyI)U$Ir%J|1eķ;tz;y$JśI7bihňQNjիҴJ̫/[|PTx[ q咸 )yP˅ +!L8NF:KW/ǎMl dnn(ĥ3~ٜȖ/Uۼ^љs gmعRLz3iUSŚukׯaǞen@m~(QbĆuK/ŏAܩ,^#SerB!J.7sGR% ܊mZZ Pي+K,++Bd"갋/0cQhAO=+>RB4HL믤_ 0J*)J,'Aj <&0jP+4T 8>kѻdѰDyQxhhHq̉#C/0c2?Z2)G0KT(8=GOKk2B3lar67\9L\!Fq YtQD-&\H00%V\F 5TU;ܷanb7ud`C"P.A#REBDP$ *J~r/ZPc$a}hab>O}Z6Wu>p=?flE4a3W@g4  0&L^b%2&1#STN'mO u y $3D|L$,E%Z Ik]*T'N&Re5wRb1zI bpYz, 4?e8OLs$fWr Q -gqS Ts?HzCfLdIy t)|W(Tmn)P ꬠ$J*Xib>HB{b JL曋_qTȱЇ>r \-pO3WI,*"a W7N 7*KuKxKC.;qSZU jau\hԧ.X" (GM#QP*u-a}娱]A%E$ N 3gdz[U&bkPRi["ިcy-l K$P-xyEMA5tSјլZCIo\pY S~PQ'ѱ䓪a29Bz>H{ٓFQɞğDBÊDs..:UX=)4>؃=H.'$r4aS%U` >gM&hYLGٙEY`$a[Bu*GRLLG\,O$ pZ<jPZ0} [0[xX0!s[Te[^e+<8 ܎ >A91Y]\Mɭ\,SGWͶ "DJ J?#Ɓ׍Fd ۥ] @ >[5^M^%NRx X،=S4 -5>:e8Hd>p7}VlL-ְ34QV_%=#XW G'N8TaE|(@Z@c^1wY[ſua6aAa[hi|@a^7YݪM4E<_"VL#Y+;țMluO LpTb-F绁$8B}93V0``E^;F^.?69I+c&BBD=$ݜ9aAaU<`Ȁqk!V_ L?xvGKYz;'T0XWG(bbBDWj%} @|𸞐L^ ^2c9%<i?f=Ѹ069()賨^m~u~PÈfuZu^NT%v= ju6,fׂHjmf0.k账hh 5^VZceh<s@V8_ilȀ A"vvgSO ejVf\TT+>6ȃL;wܻ3F 'o o}k BwnNlPEր㨰ײĒHdx8x ^rxjk =rH@FrW2HQ\Q\ &7s:`fk`=nfHBg>2^(2,>-tS %oDQMvoS҃w`i`~Ix@p4-܁'Q\Ɇڭ ~9=ok8&NqF fw>?$q3֪lvX:q_>tObv,8G>=⚥]Ӟ_Q9xz9 4-N1ھG ( /7N ?fv,^En(Ni-wVDsAPL+u1ڞjefL $X4rЂD[C' {-8G/a[x瞡15'0Nƫﺡߔo? 8>r?fASM_D(KY_#S62 '@E*W4h\*|g :̡CĈ'Rh"ƌ7b<\9u"ב֬d RΜ,JAC } Q.M ԟ$8` >hi 6j5j1>|$(z8ٛ%,b JN'Nb3w&nkSpP!C xDBVsS|w5Ѯm۵A]C|W/n E#$J-[ 6sD;B&H:*ءQRj)׮^nk?"E =^}WZ(bܑ'$"AdMA)pV1Ur漃m5˜@p p- 9Di F\H#tRJM2PBMNߕ^Q)Q{DmEWYYbn- > hW&T(H$wDmXC[o}FL"BԂ1Ո"Dlk#pQDIY7+Rk kGsD'%u/ 7e^~P,՛FSjrTWZi[UQpؖg$'fJ(h_:wQ! !beAV %Rݻw 'HGM8톏(s4 1@c3! ;4{(%S7%E{l;VPX$O^eT2`U//WhF 9Ŀ|SL10dLFE@BSLq\\ )q5T *([m#-*B8
lGdsɖ2tMKY@vUS,t%|-Tv5TzN;U{E}//[e muf1 >8Q\qE7N1 炴<%Xp1 B[Vt5MP&cuj wRB0 iS񒧼@% %pһ܃\e|"%>tb`,Y [8әI~_70P@`(F@@B4sp4F6i|G;l̂ĨJ(IRwQH4e!oRj@|Ck@K\ROO+Gdʜ)>UP pÖ0I.7%@@)6>,b` \Y'6GQ8918Cʣl:!Y g%9`Rl6`IO$pD<Ԋ{f++ T+;t_20cO&W33LRd1[CFV VmA}LC1)D> U{̈nR[# &6P/,!&=ZO8o˚sTßÝFqɊ\)`.Z%R;ֺb׹,bh * T{^ܳУwĩGvW+1 96 mE*aJu| \j&6P^+_ymd0k6bLSJǂ!iӦ΅l*  œ`-)gت)EDSrkW*7hkjqC:5WDmuDBL NX*[:9>oSoU lq_EӉ `fm0JǣB'TSv#:|a=Wb\MOQD>tv{%>1Ūy+Z դ , ?"H\2 \^X<bLdIlpdñYt9G  U\-AvcĶ+܄ڢIr紧EБ%]]7-t˛t-]l}jkW+-mӛvu 1V \b'P]Ve JߺQo^>Hkn3cu.;:!d*p% BA#tL]b >CQ՞D9A)OUw lZx یYpD]VTx- T8p_lmД\@^hA' ;u'A4AA"\"!܁3GFQCH:E W 6I` |uDiWR1qڹQ@X1q1\є8@#ғPB[뼎 :65nC,DB"<$xaā=:b!"", oc ]G$N]%PR&@ `?ΐO) vxh`5DUBLVM\$F-.@Ml Hх9!AVl}KF1:T3ކ5yU7t;I %QC7l+¶5Afk@D"DRUc,bV*DpVqWAXZ②U@^0@MR_>1vu\faǭ^̡Dbr!@ddcpL-A1'*}}Q5v.| d̅kHc9XoUfm;ޞo=XX3%g)Xh@i e^`xq\j.R|XCxq@\ Xdݐ'bOšg.^IjYZ̧dLFYOIC14*MhIe@#4܋ 8F bE(ąfhnh"H:HġRo˩(?(A.ɌhZ &A@/ɛx9U h| G9@| aIaH {JhXig1Rf,}i˱aPnQ;;l.&DFkdmmmzmYDXd׎bٲQP@U xx@h a[&Aր[&ED6n:L !|/oxN/44T@Q~&.\B"D&&<BƂdt Dn\(C݃xHhm*p/AUGYb"[f.LVZRضqJQCŨX` nі&ӕoo< L(Bc36#<&30+.&D I?5= M t0>ĭ$$]S@O8kFG,6NR蒋BAʀ1 Z0[fsWACKA6i#lQO(q4B$8([T,$C:psuW{u=Bl /( 3!u` !< Vwq3rCl&$Aw<RuvϵIw7}]9Eژd1Y ߳U=2=$3:x8v#-V8yQVҺwR ҨV Ít-@U@Eo.k9@xy_j&gAA &бO$+|A>^@\ݤ `5\8w?!6->4#3ksU@|OzmW>)4*zY8֝E~ f.´EX` 4!0m"&B B41EZ@kr:s)@랮qX WKB\;DkǬjz'l]>lE;ӥARu֟-"ca65ɣ|c AA$B,|BAmnoF_p#tfߦ-}!R>#gԿux,U ǽ qq@XrR٧HˋUL=}`HG/ῌfdyX @ |d,~,`Do,{#C櫳3nS7m7w>~qQ[X5rQ#Rp0B 8P 8>x`䁌EP9mG%6;*@1 # ȣZ cOkEAG| vN^1_ :_Y;w !Ir4pH `djqԑ4IdIo2+R@HPc2y I@%Y2(^Bg?U&L44sH=xrBXFN=Fn 6l葞LW$qP*@DNf#0`GFqT$%YKV! ,R a B|f)6ODeJ,ZbFрfԥFH'wlͪF $̈XGJ$ G6b7Јo Ȱ$9Kb26<=͂ր%)Ezjtd6O30;=;:Є1Bec>@G,XpzB8#Sdr"#im!&0B4`#xNrrg9Ware.\'qΕYEdJK eTe5pTCOD“fTI"W PkLINem21p8 Α49W *55/0R\%>̑>6iLm*=:S/Z*dfYs5tob#:%?n$ >յ q&[$D' 9s|C,9ws*b E AtZe-[jgYr 6XVy^fqebG!+dBd7a:Ie缤N<'`97]ѕ?knv ]z^D)k/޲#t7mR:)zKԒf"T1` /=lWA%" _U P!h)bDt{mj툭`%@?*X %sZpA6à`TcuJ%꒙U2MeK ưz`fM)0I=独c ַmrFL'$J^z8KBF'f!jTXt`MsHh*&]O{VTvi {õy+ `-! [ޓڑ5 ڎ9(Mit"<ՐMhDpxF5Jzǵm OxB>p_ztB;nʭI Otx'cm&k8oWzb^+]Pm3 9Ȋl1:%@!a [hE-|VX=˷\ p'( .OS|lq1GR I2f}hh7=@#]vC D1X/b9ˤB]4xq*n@dMV2\_;ҌJ'^NB#.$gpk'Mqہe ͞=ϡܧb|O !xG؂#`x9TZ8جXˤb; d|Io.%::.Ď/ZѮ(/*m, yo9LkJJ@CoTV)WͶ/Elʦ%]f@4Z!Epʬ]yjg9F 7h8ZPKϽb(hHc#4 XG (͐ʏTObp&,!Y cO΋ qvNHm/ȴЌWXkW#8«fD|pʏ\Qlo vQmQRE QyI!3H' iP,/8@@q"јdFJzQZܱ,Km&!jP6/ pF.  R a2";77E;016HwD5M5- Y!j@Z[B5JyrJ%[O6u3Ƶ\5]q$fC"dx1*U!"޺# ̪,ζU3zNdIi ~pzB@ͯ Ǯko)\w)R<ۨ!t#$唚4p $o`͗)qIDw[p5U{Y" 2czB“o$ۣ`HPf%2!z 4pY~K;Ϳ !4ƅ,k!8w=<6f+=87jn7Bk ot޺򂲃:y$c}W>"ћsȑy d Gxk̶n}=}]ȉ5^]޸Ujؘ'3v灂V]Tla@M3cU`atDmF%FvG}Yƻ:[Ud! \5Py%ɟ='_on)#3{GX[|U `1wfG}P}gɚC+!@ X.jAu}蓬ρ|bZ:~' v_scܨ9fw~ Y }_k#>cҦz$hcFk{Oߍd`e}?o +${[k)czRD9=W@l;у9{7tDa&U8Qsz: *"S$-TXH%+XH$k j13l{f͜Nsz 4ϝDw;u뚮KZ5֭\z5@5A 8pPId nݒ̑Æ^9jUQ"uŋ떈+p%>p A m<~:5S7=0=L`w$IPQcWxߓ&!1DiΝB˛'h=J:***vlnCP]%\-XYa`#(c _}`Yuvh6 4j$l9Zn$tRB'옂 IDQFAR1Qr9ёDXשu'!L5d^?zRN=TfV_YfZu dIr  =uug\7,ע &bmg9fdPjj:'T7PDYF9ǑH#oOTpNPArT%XZ O_'yiN=sO5n~ .WpEVYtVw6' h@6`+7j'iUZ8\s;Zm@C >4a\^T21.]5| ,5ALv$+^Μ9B4Mu 7io\V!~+2&QRR%,Nt #Nx|؇$d ; cYh"f (43%u ,#XHpꪠE8/BT GJe{D5%m%*f E }h28 z@!!#GJ@41CûJf3iDPR4.2j#B C1$.x wDBB%1'rLHXyI JU-HMVB!;hT y*(uh8c-\JT'`,| ̌NdGs.)({P~9u,7@ZBuxdDنoWɽ 840/aSuBxo*5z2Q\V$F: iGG_?f_ v2f;:)Qy3L@fF04O @%Gttѓ' K#d E ۩2$HH3PgP>B81, VƵ\t]%Jlk;n::%. ;aqcgkF*-L f:,I\k9LBJ5ɼE/)p `NܢGtJnMbeΦG%)b [Xj@ o\V5uMRt 1\( d@5 'uI⬖9TpUf" <RxKǴ0L n ;*P𘃭LB9yZwt7ifDYvbʡgeɫ@w+n $!ArK*и5Xj[,\"NXgKӪ`>$Cnw˵m ݦC>.iğ9a˥ 6T2M1Tu!5`WE]}h(x46GܤG6b1㚁p@k_4$2ԎK  :Yk(LiG5*C7PNW+TYSelk{qE2jW|Ɩ"K/'[|?;m@q`V֤VGnPhl&Flʑ9 ȫ/W0h),eǘNP wINJg< w˛M \q)0[e8ڑvɣ6\+XݏF^᧶wR^] ;c$txQOLp(6E"bC7Bun5P/Ճ~u8QFuX u؉^'IAgx'Q/mOl&fXB#rΗcN]3S7aOK֌(6G'7#.qAwnQ/[P@눓!SHh{ȀD#|5b/((8(g#CŁYTm*64~y5O&ő>TV&WPE0DaZgf=E߀먓;#Ux@&Yhs }"7M RPRIm( UiͶ[gcPH cu@yaǓpI#[XZd /ex |]%J>(  H`bM~gx)}uy"m9*sEn6t*X4_49)`C(v\fj=SxkZ(1OIwH?b%u Y3W !-$i$Ogs tLևtT:  J6Q?cahfYQYw'/'Xhg-NjѕLem(H70svy1i2"xH%Tzq %-hVؓ ͐ Rѥ p>3hsaMrGŁ﹐F$ps( (%D;q|l$Mwh)qC!B(2AHd'~1Bxi>`5_&9DVf Ġ%X *&*s)x~⪯HЗ/|T"`%(z]oH<|J.r4&T0#xkׂ1(*nv0syV"9'ۺTiBHU3SZVFJA0 Ю ^Z{9h$}a(Ư{($ °y<g [jiOW ,PYKcYP>{YÇ$1erg}5+Z ;h{%0t*vCzsGDL1 $}OE%}6DIꤐ({%EZ ^5. @QvY|jz Ʈ{!#9ÏZɯr%5"0"'hY= f<+*3 0Ia@aY|ܸ!Qx(ͭ4P{ v/)1ut{&eİn]:hv0 Csqpy{zd&_"3&P l@IX-]̸Z;s{#W[ٟcʴ7iº,,0)GsjrW|( zKC#;87Be 腬YT{ 2ݼXcӊK|A-(ehEݥq˽ѕ',$lQ Ê%Z~Al\ ϗM5.T U1"q쏫{7wYg5U][r&粊~ޙ%<|wفDZ;ۧ}'niV^a!Xx>qv@a7p.^d\[)*k">N\ȅ-B;hѾ]((n9ykb }(wt!|5"VY^^6`>BDDQ@,Y>1V%ȭ˝-cޘc1bNĿO&nb l )-KS%PP.޼J49TM,| # `#=eqe|G' ,*p'X9/zNJ~P7^^I^4Hρ"`]P3DIl%LP_8.%^oʨƑ9\bC7T@*:dD#.Ċ#><Å C(cGG4&69p@i7#д4d\$ *W|KFX9vx%ڵ޽SwݶfRB^{5` *d8 VxkY_̝|@a *E:Mf, ᇇ7rGO1"K,P(" (?K@N\DAe`f 1.9}'ѣ 43N15|$q 3",;,Isq . /<4J 1R*^(̳J mT5\s 椛!vGߞk#Tx4F Q.誣.ʅPȎ"r0c<*JZos/@1k`&d*@ A\++-d:GP ?TH!DD#Q?4XlU;F)$rn z2H,9\HTU$+gWTj&2!4-h@5Y^v"RA^uuguP-hHZRE`NHXut,G ; 8F\ ɚU* ? v16zA@#ʂLjbc% Et(]dlHQmW jּle@;F% BAsK9c"¤$4 Cz&rF`n5.ti QBjbF3Qf,*U4R38:)F|2+Ʋ4CVٚXŔHfK8@!&fuuTX:GQD`$NDSj߂Sz' IB 2p i]EA (^b%ޕE~s@ U 2Iw޷Tu^0ƲI1Jj6?`eaKWH <@X2)Tڀm㓬iRЃ = NP;bErYQ42xq ԁ%؈eFUU|H;EX"} FmMh _)7Se3L 3#"A ^0! V0y𵴝 ZZLtŌ9.V& hD#`@"AI|`cVjIRi yԎ=3cj%0Q8$-9fpűu};%]s&QIfI][ b@B"Og,sD +}i*q_2 ^)[J1":eR4颪]cOEٝb<㜅NuYȬX4-EXL)[% ]Uaԭ֚ `Ӎ0[Lc`E"@p r(J_*KMtb8 2yCg޽&Qޝ7}39r3v5])K=#^3+EVe6'G(?gw( wyэnlc02H] 8 (4I4٣+WR %rpG(*Bͨ;,!0(A!qrn*>[8]q>z qị8*ʬ󫁁@u zZ<[}I0\ncRE($ 8@|I7:( 8봻1 5,3cp1" 07A75k At;9E 6QѪq+y   (`.E]%P@0aHGx>(0)$Qb6 Ɖ4*"(M.Z"?̗ C;0 ǓXp5ҩKxX6ȁ-L,? /6;9ۀ81-+FS\?Sl9j AK4"=q+8xCPGž lKx>mœ!\i2k:~Gb{sBM6f~o T>]t'<~kdо -pǐ,8Ąm +rMz4Ψ[zab> O& .|ѐym^\sPCv2lZ]oɎ;R\?<ߑez =ES+]E`PXIEjv:VHaT5UQFUW=ك]Zl/GP^Nz}ci H_EߐDiL#LzdG5)>eaD]{H Rɤ;eNcJ+b0lڐ:#:Hܟ5'ʤg2asUD=iTS!^S} ꗑNb2^EOM7[U@F(16^PJM6-#:&KzbIM4h5f5z" mC*nS(U^:% F -;ko ;SE3E*\ *QklAbNi5ɢ :^Dc9Jk_5)2ʾKRFqڞS;՜ct9Ьt%ܲ5$cy [yу 8>8ᆻm!G8z>G,es9F#5vKI3wڴQ<| ylk265{,9]lQ!ۮµ%f6E}C Bǿ"q!(DD!P&0aq5y:aA ̃D¬CFqeϻX@4zSﰡQe(F1  !9-5ґ04-KX u6' ܝ]8l[516O,F2@:>&T8ֲBG(=ϖ<X҃$$%gDrCd0y?N&1 G;bV.&Uˆ LAh4-a(2^Ӎ3ٺ_pSi4dHG;'zf3ɀ% jVDb6Ba)&q'N* X(NV"8i,跢<3>5~z鳆 A)T,YLS9QdCjʋUo0rHa0 0)c,tAAΒpedO/s;w8^u_Fab F>2zBwr/bK@jRyiN]3mC+Lk*b;G{ɸ︇h ;}Zljƛy߯C}d%;O5/YQOTE,&7(Yb>qu.jVͶ c^cCL|";2Ԋwˆ4߀c[ d5 ,ظy@[^wx˻ƹ}礊6"i ܯ.aHEP;8ݸ;qA&KcU9[|77)oyr=2n[lJ+w4cA7KW6+sz]B뒦3ec8h_Ysz4;3*KVz] >3|PN5vew< h#V0Lyo^vї}caѷ9, ~@?=:;E?u2]5XU]|S n}}}݆z(@@~zmzq~n7ogS{2h{xMU _l&F F] x]7 ekv"7[Ev x   rN8X' m,/x+b*$Fc4h_5bɶXkm9(2^h׀ ~AhXEwtyw =dN VzS8~Vl#X &xm&&fx7y]ˤ2SOn\Uvb1vT3Q|؇OۣAf A(rW ngj#hf B6s(ւxkl:(Zx$7?7eNb凾؇ԅ%bjao'*ZtͨY(`fg&y'8OܨerOz={XUHBw7X_7lcg5׏:2b傭6Y~z)!~#Z7OA&pFW(8af/Gh'_d$X=zVJH8xBL8W`8tD D Arn0[qvGSdeVhhTWtJZ%>aUqCXfd%G(`7"KceKFhQŐד Yj ZQuwFiOTqÕ)q Q0Ùg0S>K`7߅lCIz/Ep 6yəyU ,٠9kɑ:4=I>yp gX7y7+`gQFxI {9.PVy iefdT^ިJIeyyZOWUX2c t6_C!YqygBKE>vZQ*W)qz"۩T_*^ =&=}e g*pk*`n:xu uw_sʠԧXtC髿YB Ffʓ㧦2e`F6U:v0 IQxz{fFzUCR |ّ9ա$FfCv~E*~ `ZcП=~n4Zꮻ]7(I !˯F8AW7 k_nʭB8: Kך_ȥhٱ g!(& )Rײ(z\^ `FG8j f6QWй˭ @ }g벋#i';˲Khb!'vuz0ePI$l'cJl݇$*3|g{u:<<# ċwcAقJ\ap*Q@łܼW,ܪ  6L+ +K:,#[%28uks.ٚ4FeGy {06ILńX<*XŧɓcLG@<ګLcS8``Tv e.f 5JSpUr; N)ܜ17̼J(w{͊3PX\ZXj^s'jov\l%&L,D mK8t̽f ד2z-:W ]<(='-::[Gig6,7;8,- =.<H~:7L頾P4J-##%J"=L y&loOq.J;N= {^nu.чާ#~^\ #@}=b>]8J OK tꨮ6)A.HHN7UAz2~ l[N(a.ߍp>mhԾ{K>Vsn|>~r.l~Ao #Q9DH[^>`&\+`k.OBxN1MqਤA\n,4/6 P[NBW|K Q?}_WMz)Gl.BnL?7,uDd:s/q۸>兏ⷌq0ϣ vX1wPꤟএPJzejĚD-F=~tw@q$SPaȐ˗+c$N'N`GaD"YHɒYcyḵISMΝCO$Z;H-]SQNZ*>YZJW_%u̞;w>ilƥ[]v+ͯi5 +.p$OaÇ9P rr Eu#HaLDYLk\73QojԸ̧U.gyղO [^tκE-p .[^xpË(P7Pr K؀8H D# 2@"'2>o?h -`n"lABNڬ8O*E(=G3Qԩ2tZjCE&*Rp5.i&ғSOOT(L &SV`.zB!2ou-P5 vXiHPLvYf#׸@͸Za[m2G7O!˭4ŚvA7+J"^T]U0 U -`3," ) VaqX&<'E6JڎlCƊ[GzHȓ&w+&gL@& UYm4쳄|A'urW酣\iǵn mfkSg> Gm^nt"YIwCgUeq"qF)?r7楘B*mX?EwJ;uS`7bkf?iLwOv% OUWj: :H \ӕ FЂGp{6Q2> }SU?9ZG}zp,NX.m9P£rOKF@,@A H  P Sxx8rM-^לP{*\!n\-Qa EåP]b#Tm[X'%Eꐢ'x~Sx lAXpQ"`BڨA82ѫ^ e/| LBAd9RZE^ő dUvd&߱INv? ɔ L%x@h% |' .;h 0HcN<̰)2S5 M|]Ƕ4)d` r«l` EtRgEFZD lC~N.MӻkW\h2;CԒDiыh<>4wO!ьw"KW^]b*шzKHiO8"(IHXCCMSsfUT?hW'xh7Ֆ;i2;kZ)< ++Sn[f 5מӃLas 7,Acr]vm i/&b(n9FA׍V,YjGi}}Lmg`d$ Q@.` ԡqEtk8[s >!!8FU%Tz㦌7uVc-;,~'*(i~c'qk|@#(A WUP+׫6E //~B2>= }ߔaV1 B bxB Bx wUټ! q\NjtG7C5:W"Wfm{ۦ\ 27%ۼFpbJ&">-ŽuSpP&Of\ )|&vbtB؇R)Ly4N>QRX:+rXG G/2.}1t^I!n#^VT)PXb֖Bˍmm;7D"DHۑ춡"76H=|OO^#XI>d@ׅ;5Z1&aaɖb]DvXR*&0 X@1laoyVF"YiU(q槣o=4IwꃯX|_[~,^i o, T캦SW UfQ dvpNRNw_iB4`a 0 z;pњ:WO,?͒v^"'%ԥYW:۲Q+)$xj(3D6b`aCTE؃K=hA.#;#?kZu!J&%<:p2+7QX38ZK02T,r2%@33 KP`;>)7>=xAHp3AiAlȳLAuk!:BZ% 7 y*ܲ0ʲ-2JZ2:3 >3d9\A;(`A>7p/x)0.A$<ДIĸ&FNI,-J &%?Ң,l٫-Rd+RI# 1sE3@}tCbEk39;cD;,`&`F3H[sFeaEl T#%4FoBRK܆[:Qb,|0XcGMں)z*C|@}=1ENpý8dO0# `|H 2iټ.4z3Iޜ$KBimKMKurYKUzGȼDO(<򻘀z :GxR8L?(8#HXM'Z(ؼB"ƎT$+ Nd-BŖ-˄:R5c+ǘ)ڪ1)̵cƟ,Ђ 1F j>gROP O?p\#0؁{< O*?H-űdB1fE"K 5 pDS|=.̶:(QtIyN ={jL|4DHM騤t"&:QL t份c H*h䫆2 Mt:=i,}-%0ДPi xDۢ+ۢ:Q+-S(1C?M@eF ,TЪXTuXLȄJO4X#OL(PJO͚PEQHeH,m,:zcYZk\`ν,8EVYd50J@  <LV',VoSB$UDm*LMG,O NŚ]6Y" Ym2O%U&K؏N{Yvd ق؍m=ST:gp@HY$@jQLEc44{ۤcTJEH#-@NEJ$ qeUmM Nw(rO'y:]-QIS\/٪J8ڊز]ݻMQx@i] Ρ1m PÍD=p]fpWOI !TM*@Vj͉ث%IbemXx [ݢM:T:-Y`P1kuQ ޻,E7<=^;W(݃47;\j0r#}_MU%U\qmRx 캡!)+/4*[ ^ 9=VQJV^@eL[4>lʭ䄉daщ%I:O݁%Q!nbEIbK?Ibpz+(8c* |5(Ua3ep =99a@ ܡ@|fRT4>#}X/HνKd oHgu^MdJeFQb,-`U8VV}:5`]\^ X`N@9۫ ;bM%8f4=Ђ=EsZdSHl%@n+St;b$!RgZgungK^BP)&z{`3OTWbUV\^V-;P]}>-ۇӀ hˁ?2eQ=pB+fLX>FT@؄[h߸DB7mdo#hpll(f*g{^jt \>[~[_렑hȽ:ux833[hkn`pfZfGX7 HAl2nv}y.-9g2 j]XӖآˀ:e :ݮ LejE=Zjxj}u vh+4p.A9(HZ0l_&o b`j}~j22Y:-voI֖[ w8cfYߋ f>c[(<.822PFqN>BZgnt=lbUVqFU "_#&r7cMX*|YKS&聖efDXu9 )xtE>ö$9P=Ovc9&rqlN@*1WgE//;ۍ+ f c5k_nBWeUP/C4͂&YBHB8 88'.*fM΀p*[^7֐}E `7]6`xԣWja;D+H p S R`0 Ad+XjΙh"ƌ7r4]}hRɔ*-ZkҜKh\Ҭiʊw{nmJ=AҤJ(A7REa* %T|Am װ -{냸 ƍc(T6*80FlGJP< fS6oe*Jt𐁂(dHau R0DDI91'%-#oʕ[qMoRRs=Zt)xpxAFKhςkaE6|O>,v^~_vb5 6!YeegRq@vje !Al!fЃqPDI YU^{}}g1&\ 8V^_u ` %1YR^QDi %ڠBlՖZkbɋQ#p5Gi nD$Qv 8%H5 Wb% SǕWWyXyb5sb z@=٧av*C 0.!Ԩ[ A[0DdM6ÉQ=$QDC3Ht/\c*!*PN^ $@1{Re|"հ ֲ>|`g BIT zL[jB{!G|G=#M0Y3 ׳dp8S(d_s6 'D sS<1,2ܵ[+, ɾ^ ]p x#l39g_1 A#p4F'4a"R1T#U-s,pءcf}}A=7ݰ@S @`~oee8Š\,f*WpWEA#4s͔QaF!ԤtFA @;vڑ|5qkxF)oy30Up TA+S5e '  K #xd U!ӘNhcQ@TAp»(xdWOБ|ȣuG;1 NkS!rX(셤4m4aݒ4.}K tLCceRPVv91P@`BQ3Uja8A *8n^+qcǞQpG7юll\ Al^p|ӓDN`F* Ianwဖ0IA"XW2XKJO+ 8edtE]rt,Ը00k501 (hjd:D i 7c*NF49ՖN|g<7ψadh 4ӟe^Hce2,3*ښ<AbG9D:Ӎu@B*\ac.5bȔ#4}㾤NC`٩| uԩmLG:XԤIGlS*ϩZ-\Y>UrKbۑ} dRշ4K: ).WnuX@_[n4ECxMa B >pBSrBxmQ3x0P+2OazS![Җn~i Le)Ac+f}e .q5N{`#$8@5 I ׼NFZ UxN 4<bo\j{,gC$p(68J;  [! j 1p)_]4b>JDQu,ˎ 3ٴB2Gw)!9 f0'Ӛ!2Zb#BV_8*. Ua>_LBSh@~<0z#wsQM~3;-h%+ӀhiLǺ?zVZ#zVHbRRsezpQB̫ PE-޻kM$ǰr0#hFmik,'^frD$ 6o4 N|a*ח$;wb,G-}! IԊMӭ1x ''-׼:p,~qoNC{!repw?M-A#H!&܁RhE{z ];1ȂՇd$A,;izӭ/̺.|R`qo2PCh'aver hy׻G0 We[o$95T7eGGIx|-*X"!̟nx4oD0^AS:%O!9޽؞Sak@G@*_UjX@ hvq_hqx@Um\ DLU'MC7m5_A$'B+|{P}HM nMu`E$^TTck`]A Řs ^LLH* @m.HU9i1HP0bM0RvC;16.8#.$܁dAfQ13FEY8=AF&k6#SP$>@`]E^ x"T YWl"` lEP(FD=ѵ`Xޙ/noI'qB+L22c3^L^?%`@N  E\>,Xقi:z;[j^G!f MjmBn ^%N?b" Z(tɈ РAL'\hŅ$u'ghآ`nzRRd<L$XB"c^B-L5]<ʃP&`B)#8$Ԁ"DlD^)Y鎾l%R3lQt#^%t x@>WV$@( Z@@@.%X(ȉeu ĴdCv_ _*)z>]F J"X$BѤi7NMBJBƩ`Un`)郥jOvB$Zeb pbhVY4lʘ [f%G*%^" d<ތyzޥEtAL+4e>i7tC,hB$D&#A\$+@uk1d+=X?8`lfżkX B g0WAZ Ȳب k"\n'T@_p,^}ǢR*eH@vħ>P<)$B$)hB"A0"A4,X6-6XE҅Vmr~mA:8 ؀ Y (`DgN's \*^$pl-njbH4<38b#&/)\jB(.LF0=]9Ci]0|*>PMaj*~(s2fX\oq( %tb/ NNU%  Aj.F A Pff"<Lr&&D@b DBCjك^0׃wpjnqdGtT[30 puU!b艥^ PYvn@ !+h)B$o1A$ǵnjOi rH!'%""#cGރ.IR Zq\ g@wvrI_+Up8!6FLATف] 0c ", 37q;1C3h5c(47@TA 8s^c::p;/D<7$3mY}/wX PL qr'rB?D@)>l)ɅZb)cP4dMXHtHJB$JIM[ fÝ/mRuk~"/99DTs [u%_%h˛+ ߿bX-Z 6CE\D?\$3k`c@ 3'!B,fA`3݂낛6(R!JVj6kz$ƿ5[m۶ H1np6~[Q,ؕ@jyfd @ ` $AB)`BMewW&5YP0{{9 9|Ϸ#Cۨ}w~b=R *F28)7\d5Lst,o $@A"hށ"/@ q'g`lg8y֌09TU5GAّ]?W ܔˁh\h/vTDA dey*ULE l'5AhdžNGA7^k2:ˈÓ?zիm(ZXcxo)&Y\ *2]``؅&[FyUGc tـjAg877B-$Cz=x P';Q&iĢ۷3^m0֩o)KXLBmkCj/yt]zTߊyմdO/"8si{9/^9Cc|W~U) n@mCkqbe*ȸY9إ'&_AmZ8_>!`aabsg>W6^z'-ڱCGoո+Talzc4Т . |NĬKg0Aq2bD)VwcFz%;aH#IxǓ˕Sf3f gN;yhP0p FPQiԨ8px H^m`V]zZns0D#FTo @(qT,r b'N|Mq"CApu[BqG/ uY\JٳA^y%̘3kxp<)_RmV+ݴaD+ln[ hV˷fHaGɈki*R([F246r52p -6\MhiEQ{j:148 ưʻ2 *j-*!+;,1B(H \p!1kP" iN9r('jIZNu,"~dh=&ZmɆ.+֯lw.ՌͰOf R褌9G`lQi4gX/s[8uW'(oR*+ul+gPNl'(;DQr֓٠S8#8-4W}#, Oh{ !HƯ WaǫiR9PX"mv= _#6hwJٸ~L ϧ.hgr*.沲b|F4آIUj*}VwdgGrL=-ǶB^(e)38@1R~iMqH)%moWn;.NēL\VmYhPaVo걆2Gv,F pU"ˬN:'B\+i``0b~AB.NXs+>KYWOyC#jq֭ +{P91nӲC0Q/^+\Z bRZ;CRfK'@2 xЄ gA#%0dLd0lά05{ꘝ@N 3u Q^yKH&J+qJ/ޓL %A9"eFp M0P8 v$/tU&ķuu;י2J9.^Iq[Q3W6\ۑN)20Pm+G0Ap/9 A~eA ҃$EJ5 #:)tqD줘;FEރexF1ʠ Ҡ  ,px l貯$OAN2onC.ؘH<%K@**-le0GbdHx P篼6p .l  =)Pz@xVްnEpLOĚ.紌8.<]Х,t~/G Fh+-0FʂNJ04H k DmnP>$- c  ~%~ .I`Ty<̡ /#a 5 C$pŮ/=P)@l~LF2Em+Ѯ- 6:1 &C 1UqYXquilq0w͚WѼ03/$1v* -~Rm6X.D.b#xe }%9%&mE 9V1C^by3)S I|2dz<3uͼЋ+9\>_ulU?mǾ@-y@_bАQxn/'B-t0e {:s"2I;2-33s6EE)4SF~|r stxJ s.nk|-[$i+{2l1J1SK Ԥ/(;aDIT Eה<44NQ=SK-L"U,k[k*hf (Ce-hS8ET930P&\񔎋y2HLr1*YU_U8NKl5OoW,u)0WyDR`ǜ [:YNf⃔L>B+S@K\Cuy^T?b]ݴE58b5T,uP2_*_GEƔM,ZtdϪîaopJb 9bx LLJ;T2UdE[esNuhV90_G>Oog3O` VКM;>vi#*ЎwC vn>/JbkRkS1@lL]SVeWUٶmB=,OHEԃER2R#A@*s,>@oUxBnf;^:uQ0Sp"+#9Wjsks2YY2@@&ҪscZkSQ.Fd[Ӆu~Մ:vQ37DƫQѡ9P&Z,WoXn@ˀ`تyy$+ȦT[[8.CP:cO>#GTAh; qM٨;'WzETo²9 vyu}7HZP&\d>N`W^WqbI4B L&xT4uoN/o  M;pN5[P;C9+^x;G2)\ofz׻[|s<bR)ݢnY}sS?ŖlyN_ DY3<9YfbfTgy5guGBfHQl$7woY,1fj&@W 9\L|)<nM)3sU}A ytܱ㏵T#՜{'{v9@gF!y%SP:U{OW`)gLe@Zkr=]]eFV` P .acMF^1Cq4B0פi/(jC]EM>wSZT t]> S ץP < Vkg~3 hV_2(ر cW;!6h3`馝آzJ[* IK1M6ժӓ5M%ԕ7$T ۥ:xL],mq^}Gr=Gر2 CbnboEG'5o:7p<*$Ig-evYuRe7u5RKp\uIr[&-6q; i4J/!:q/o-n]Fb>f۹gImPIS02&w7 N|bewV um}h2 SyR Z{.N /^:~vf0)P$ 3#IN&&+R(xiS78= ;زBH_ζ%[Ń}.t_p Y ZN,G =XUrR )0y:y$ex) \4gˀ dA62 2.-TZ1E16S4HxE-#*[rtwy5x9d[ʏuK8+aY B6IqQK)>('@yV G2h3u1hrDP5X"׀jx )KiJ@TF,U8p嫘##ITGG* ̟P*YM40dYWLk/;=ԕlF./,!#Λį tL%ɠKAQ-awZ STX N~3"A 0OOαn pX 2Zapy~r2ej#\ҖZ\asV)/\ 3?sZ b$#IEHU=+WMV P,vu_eL5ALò BRԂ^z>$뫓妙 (wZnW^&kl&#XV htB`DpbȨFgATsN%ăJ.""Bkc|`FP m~@dMKZ'Q4$8'-Ccgi|zxoSEM; z;ɚl P̘A29j}6|=mJC,8HUi:<Ӵ7jP2 pU?h^ԩ[G _2ѠOA} Mx>xgS#HXiXsX׈Xȉe+_p%&n@F 7bUՑMLRW:)9 nnKnS >G*8z4fj7B&<Y f V 8#ɚ,8hYe5`'fiqgۘGL9%xyQ8Ate$hBf8ߙ= B@W:M#byמ"!~qh` 簚П_1}j3. 9_ڈ87q({;nu1!Z82X1|顝B2QG 34CLIyw]&FTi i~MZxQZT>C.Eإ"M_X$[Wi’7mr:i톎̣QIGᅲsIM-Q\\K~EE))*:@(1gX.3h]و&<c1GȄhȦBt2QxwjnkjMOsb%/0pK9pҋ^| `0aHgXX,a8$(Ia h0)st [w*C9똘?{1Ӭz$ř4I#K!w'\.+dv*C.a*(1$> wUeIE { n!U-wEmG L"` Vt:=ABw5h7uT3R8tGb*~*I{Oɘ tB7?0 &뎷DkM_JQq?jJL-,F;a}Cfk\kMc%-f<8tKp Iʟ AJ:Ũ9@;XلX{K-9*`ih%د1Lw7ٴJjL+W%}+iK:2!23q-8 r}Q>@\G 2N!HZP,@܇Wi\qqoPb$2{Pkc^A,*dw|<&~ӫ$;%C1ACgɪ/ =, S"؇.T^xxLhX*-AS|>ڑ}H ƍPhLeAwńj@+iX#ӴsRamm/Hzk{Rm2ڹӋs$2K̍eHa7I+̇1\!DQ'ppYptHʩnhY'8O~zAm0HU.Cirm^RxVLTXQF ?>I$ąޙ- Sbc`NκbytX9 ɇWnB3 Mx `ˬ$6t:Qk?mK(,'$t|OiؗkfeBbкM8o==c ^ C)b0 ?94Ro5*% ),*^Dj+li{Kϩ|eH[9B|L-{Itŝ-,9_oܤSI`)P  vN'r`ھ )oA@ <0"E  k<|PB2\@<`A k@RE 0@Le~<R={VAE-!QRLYuܵ(bŻ4h a8PXrᡕLa#HIdAd"fbT8v ^(0GAF@vLcͰŊJ>A 4PA!o\iqb1`C!I.@,!6N *@;0[RQCr+<'Nѓe/m!XT-"޲r00 2$1A0` ^$c޸FӈtXnt4y eU/ *hlyq D'T"3ԓ%h`$ՕG1"Y\!ė"uqļUҔWeHUC) $dJ  A898-,6BV<b;GH$)d2t;ũЌ e{·P΅]~r qA`0X ! XLPv I2"d֐NMP/&!@n`QyJ}(R'ֳ,'5|^뤮n%/{b*R7M[׺ڕ]oOt7QP x1e >`j*A86[dcdM;h"o#-g? _O>``q:a Zems˜zKT\#ԉq.]oxR@+>>KPސ4˭*U+ؐ@:PaU,^n/!2NECcolF3|)HZVLeH܎h@9jbWqq!јreA^W\Ni Uwɿ1k;Ru)򕋠 ֕X)p _M@bhF7utҸ;iC%nvCV 3uPH YIRp@'mJ`i%[AqHt.z_ڹlROʶqAw$(7ϲQ Nb1biH/nMe 7yn6DL.ȽnpWˈX'AmH.T:>C,1.!?.W2YRj_3YE h2^sx=?&sZku\tEy ub#R+MzG@ fc}Ձ{ހ,7YS$X@ P3'ᑤ?}wDGlZȽmGG Q_?ſEj JUKͯ8;0#ѿK l4 < @@)yDTA41؂/'(! ,^ &f)\P!;v"JFqċ16ܨPGܸI )2ɓ(OnF-cŗ524 溛)ȱ͙{ (;u5VhMMCvc״MxXiLmiϪm4(”OL˷ǿ6LyHq38㑕 /G[8ӨO{L}zcװadO_jܪ0`և=fM< ?ސ]ЧB|ɬ\m6m!D z!<)u{-]55|xF?o!#H9~~ 6 i[huCMKi6mv0@'fjWI9aꅔ͌48fx8^XE-B_T-"YdPxxK2ThVNM>MJ#5kq %xI%'76ڈcX;7uccJ#n ;FԑGYZ():)+q.gXSM5;r(A{bߪ3ynfƟxÛQw&hE a8 Sc<_%eOR5 tUrC}2İ_Y圉=ms V. z<*P!JhA 1D&"/^ e((yc6d/ľXLQ!f!Dt6n;";J98QU1Y[{kPx]`?76mFdDJ4ZXdd^bcG ޮH!GgH$R @dMMJn|$'6k`Ls faRzBڈc:y_Ql´1#) QI8D S͈Me%/ GvvLLc;T$I m{ӟ=mm萤Pzlp W͂&ףl8F&NfyE h:f^G{VeBp%iIe#u@6@.&o0 )!ȳ֫3Nyxu冚TrKײIP8Ⳡ -h:69ʱr+q1cc2 6c"tol\+ nU$f9~"qk*g: !.{!jvs$r6[ ѺW iZd"Hćpl +/^~~ɜ=t7 “3Md/15bBlt[#|XߩɪSh#c8Fw0 `e t^])+IV6 F:{>Ȧd=zVkXMzBǑ5!/O䞢%tL mZօ.* eZ&Ifa±n hC=*-(ȖWlG᪋BXmbBa8:Qّi[%t3Qym Tw,׭%૭AfX:0uً6?rۋ3W&)A,j7voXɷՀ-nrfEmbl6wj94ohg/Wp ur }^#Xh{ylک6Q㵫xV;-`#0]r+W7]_֜~6qnR,.5˳TML9gf%{)fWX0hP`} }$0es({05,.}᜹sv -C =TijGg}I.=[vr'*Mcnme15WsjRFu;)U^г2FɇTzFmTYW4t4@ e*nZ6q37}u~Idh ~ t l-uws |SG{o`vwge%2^y|bZa8}ƃlrU v1+TTSuԂ 0z'n' 8:{oaM=BnRF}{'Մw.Rx.TX}T56 YVcHܗtwoI Z_Kev/X vxHƇ}ow{87c8\$<)58Rt7T8R^8 8hY$xdgLÈ8ZNt~ur̀ tHw&}x٨f^HDKM1E[/*CN.FYM8e4@t Xc?֍޳g}?9ՊCGr$m (m Uj8؋5w(q ]vajBx㔐 Y=ITB897}W]MY|WlXRD`XGn, Ƈ0S3k[Ite<>YX\ԓ WY"BH1XJTq^JS5Zɕ(%g@ Pjpfu{UIhf4{7<[5 —>yAj\uOFf ۳|c_3+>AI3uMXhE"aOUk9X—J㙚0TY@3Xb93HssyD JOq=J@(%'i0E`xНZLfɔFPyhg)Xwvɷ4ZY9Isi{B^oIHwxq9sX׹zRVʝ99eI&BhvsOӛ:̺{:T9dm*PF:: P $:azmc|A*ٗJfF}TI~)xsߊvZQFQcĨf a=دyOxa5XYjx7ǰ7W~ަiwH[D;vJ Đ6$f& ({T[0K2+Kٚv >K@kBUpD{˫ GMtIP+zs˪O_{DJ8he`i@A;Wۮ0G { ]j KK믇:] ٷtPĶPvn{Q@TcPFpK Uę%;{)1{3;b;6dKi pHNz5{ʻ[@q r԰DꘋHިʽV+ܩMH囻`Ev; p "I{ȫW`{vп0 ENl|aRDjs≮4۷ѥ&f(dUV#'*;ڱ꫘S8<6Xp9{6iHkG|42׀vljd R&*INX-2|Ǐj;0j-иrܺ9^{fh|uX*@~L<*P>JXʻ #7MĖ̺KbڲadJhiL CS|PP9HYܹRګs9+1ʼy * lƟL͝< ̬͠HgH , P#`JS+D Eset}/h?)4}Ӆ]-UkmN v9 #ԅ#(N/H 骱5~}'ZŊĥ}@~YBRo6NN.FM*FtVNq傽庽))~+ƭʱFu!(Z [?R ^r^GC`~ޏ^IMPp <؜ٛ욡}N(hw0Jڍn4O~T}] ξpnٮ* A^,EД| )p9NH苻v:Iv "nt  @  M\pR ra(wK4یgLf~g$OI^0:ڍXof` Nx?. +ɛ[ߝ /w_ST8lfͯ[|O$?l p .T?kO)ocۀtxxAoD_f^܄_Wş˝ w6Wo kl߽ 9 8뜯NG䣏ڕ_=/u?ȞA|?|/_O$JW!X`<:тŋ.u9Xa1ɒYڵkE4WҤ9t)ULyes1eh`۶m<аaC] 4PC 5 .$S,3 ƉE-nƘgkQ iCU *t;8{"}gҹKFwoY[ٴk.7 %>ՠwo_čoLdcli-'nعF悿jN{m tmڎZ ؚrj6bҍ| B#,Rk9P.@N;3;H!ӌ#~f2-@$SP5{JR@js2#M$ ˯4k0DF|.6# X L6/FŮ Գ=Z^>0!}zrQvj'QJeС +y&1P94+`E |sVl8xb> @C1LJQ"f+rMW %OR;LMW]5v|5Yi39?Hl .߂F8YHЊrŢB  Ri/$lUzھ64k$r"3CQ]vk ^w;>U_`V-hfVX{b$Y-&9RlVFud5 + = _>Ttӕ՚kvYmֻ)}= VZ^yErEdϥ&,0ڭT+xlVRx۶}ePv&y_0Uti㵹P#Lh |X Ts~1~pA5wL%7N;q3#%9A*) Ph:u ^\mF݋р~'({!ӉMat v гN)B8FQiL$96*ӓEyHPH'T6O}*N!h@ /Z͖U:F%aQ'L;&Bp䴍>3YY}NTJh!!Ab840Sִ7% `uOԧ+bPOѨHg@ҟ*™?c*6V<8%A N1+TJ+-hH.Y삪e('A_DtBWauX-KvbiL6 s:5]8KA4>%A8|-leBPkzhld#m9 vcY$Q"7<ɽ%T\K#uN.|N+ޤ U`/4E|g##xQ |(6 Z`2 *DYrŀ$i p 4?A`D"Of&vwݩ$)*<'ɋgz,o=*_|Z\ug*`!k.&ʣŶĠݙ/OmT *%܌TbFs)<9"sgՠXS MBu2^ wp;6h.X&=w5-T`x(kCG-:ʓC51Hj.YZ6'WA7uc3r( '*1 Ĉ??d. I.@7x_B&`-'8k)i@Er,14n1Z9,u[pҀ $x?<1AB9[#B'CBBKD@,X#h#@r@ˉc)C$C3$ A1"*z=K7*`s3(K(u{dC D?L:13TӿˍiI0EJG,D (3Z8b-$" \'bEqEAEYLTmEk l/ts*v#U(dLF-yK2CɄѢ8OK[O,: ϔO1,d7TWi R׬H8HlUܴ8$@ ɸN[пsX$O J?;DL;\N%kOfѢQ >/!ClH juCcd|< ʾ,RL:R"N"B̪1+Sr#8S1 B=E XmA6geC|&ϹH?H+RVEY7Ҁ`DƻHL)M8ARBiLDc$)NKWpSB@„_́+\jj6#L(B6ig= GϤkH\C,vvAF#?uHTE9/"3-zM,-D';DP8M)Y;SX8B0GӜT%QXjȆ)Xum1Hu(Y_,[X%J /t<&Td|<3?[2-*-:|m-DD;P.L1n48R@Xs%@#6|Xղ޸Q嘴uo].h}UӨ-Yfۃ M2{așcqpE<^ًKKMCڏ<@:dB}USΖLfpJXDv@0bݐݩ]v`UY E!^5Rx Q^ɁX4:T$ּȁ R'Ņz$68l$claTtN/WlRKghHfnl(;`hqגDB0o9hξ> 6h!/Hն1[dH2jFm^5|F0(UI_ٌNGm<ƫHuM=dy+)UHb]9B09KlӚiMPoF;uD` :>pm f Z EpuۙX9gYq 9vqU9GHD2p 8FFR^C e 1-)G9 eiV+W_n뼙 2;v0 m*c$KuhntAnfE_n˰Zs@HHv@XFwnW^lIQ0ޅv Ż GqxwxztrU# bk ki_W'X^87?XM%h/͕9VvIazv~!;@$Hn_yjlQ)MaǮDbN*JKju1c08Q',e?v^l5"ĀYHrh hmliP`ORMTf9U)HV|fA f@"- C$}-a*j4<%CM2xNI8`q~Ĩcׇ/4P`&wD "^d.^ۈ!18ֶƷqd%;{}ܣj :ZNAS@kcχNB= yQE@5!3qܳCm[4jz V+;"T7 $|!wf46|)8.Tq Dp=vK,1D3a"b Sl< 9DtGoFۜGML@ D]Ruɛ *yi^]+4>!>_ e;6_ UmxdxL n}H@@H< NjmގX?oe@@q@ 䀥TG<pY# EM LA* `!!B" J;.0CJ5/bفh "a_=@.FaMT!K:m2 Nd a׼Ɠ{,o@ "!$"D"X9@bЕ$"<$L% %'OmC)"?;8d3xA@\$TAp Wb.H"Z;0[e #"c]_$H"Dx $DEķXH`:Nu #n=>+mATA3 #d-LC7Ġ; B")b74C,4AXd  dٗ9"J…YIL9D/KlW v (L!Pqt[8v1eScu!"#=b]WKT$x“)$C6C34Q$=c5K]^a~>ygbrYx\cҔJfae.e2DNGȞ iOJ hD̹H}oDR'(T T 4nr`#feu%LAq;t4B$DB)),B`>iW t'd\4aj1fJV|B`\Ɛ3F5JAm՛騄غD%Ѐ"ޣ5b$  3L'3+HtB(\)Ăx:@ d G.&*.BnYҪ8t@fJS. ؈m|l9P]Ƅ::ĄmOPA$Y` B)03|])|B(lB(k,HBt+P _VA#3\Y .,>lI*eRlZ,u!DTv,ꁄɢ,P6`8!mTޜ؄Y%9XX#,#@'LB(.&$L AWm 41؉=X.-󢧴XcGrmZ R8往)%f:@Н)+l?Փ|mt PUrlMxn @A'h$8k,"C18=ڂH*l*H>oG=2_eZ/_{։XiVn`D@ FUuTݓ8Ҁ@= h'r+&o}_A8^N0 d b*p 3^{,LLnjP>p#S ;ZLdF jXk'Ă/~n"tB-@8C1q +ױ[Hf pblnLO@EG!gM Eԩ:$!b$_J߶cnOM8܀k )%خ+# ΂pp0 H  #13d6mLnR܆EV<-Qts) 0BX\`&An&FITDԀu>Kae,o:xA#s03DWE/EpT)İ"eX O I*Q0hA ȳbhb M@ 4NVYjR@d-X7p0g{WL5=dzbuVVWFW_W3^@f2Z q׎fmTDYFKF;<.`o9#"M3Ob:͎Xvw:wz]lld^'X"^uy}9lKֶay:ˡF d *[E"GyrQ*ojjӬvvl\۹w`s(=G $d|Y p82݋3|3 H:KJ; \zЌ@D؞&)nM+i|CA!['TTZ*cj ,}|+h.λ!$m@H .L(:C0lA-h B.QDsvI }*U,AD;0CK<9YQb+tN9O!TI%m벀 d'ʄs>[HJҼ8k4Mct O,|f9,C$9B,EשH[DNFFx P5/]I'+""X!RHzvKj!cQk%1<MC>V9Cˑpí S,R\ K!àvZİHH0s+:ka##H2fi WKX67!7oeq2ޒ}i&LQ^Sv=911 }`@01`6#:l1`կ@?' X!C:|$$; {@ ƩD.KTqB7~P)p̛ qV-KEbHÍd@w 'G9 maR #!ja(BA扬s 8E2M4UpQbxP>pBہ{.H#xf[x~$JAhཻ oay acd&Q nbNFDBB-| [Xd0s4{-el a/'`k= r S_3-Ԝ\^$[E-i {%H("g X,%y'&w3 F3@ЂP(R(g.':a*`Gg0dH!6p !%/,d_JQF}BJW ԇ`"":SKFH#6c A E=j˴Ԡ9uʼEj&ntBb.I1'ZuWWS;q/Y:>@.q+!FW63i,DRoއU:;bNO^2[^HgYvYI0F9Z5'-~{ֶ~df4lMyuL`001>s3MNU~ Xd%+=2{$|SeoS~H]տWlg.uHh\euj4KqYUڤ9mHtN=0;O! vBg5RhH2sڱ w?f^TF־$k2<XsNvqKŲLq`մ^@W\J԰}&W:iO}gc?BT4قNDHѷGWUQ L[br CSxnOi߸vrt01$\%33YvR .y:w*A NOA6:vhڤX)%-XZ>ӭcz/)@&F>2Ds7HgO:68+P!Is mt)LJk!<,˝^G3דlLPݑO=tIKt/yS ]!6 'G ]zwR)/?Cԧ„UPètG:~S|WwEani4|˰Yѷ V5g5g(kY?Nqv\Ы?A@]h bHG,l//B4hVOu&}dU(JcFcNAJҏ hr// @ .v$6<*Ppa~D8ʨlмУJF7!*#txE j8*OO:y*y ͉dIdBfBȢ knd n8-|08Bt>j抮a .<(j#`#j,`AC qB& ;qmqqJp# -{0p@ )s KJ'DPtnejgH#j`gl +nP=p# ^ >^}nF;PA>4/ ` L`RqV^Qf(#*`ډ$ zIFೂn f!' rQ "?,q o_>RLG/H,IRb@rU1Yh::b䲣h#od".BxH :NګYr'n)&ɢq*S*'*%+0B輲,C@5SLs$o˃f$5`ìoJR1~hkH3TNԲn'0_PŽH U M̆o*2y2#r3;G*qG4E$'&6G,?2 a3>j6$BC$ kjaՎS/o@$2td(x !mEB**ѓG* i=r&,? 7`6In`Gm ˱K 20cQ+ :#B.{QC7:`).FvTCEE<_FCF +σtı>THA5F) .c .PG%ƒ{2bY/K]N -Iެ@tT_vB Rt G{, 1%P 5b.q<.3PD5KUk0/a5VM,E5N}g֍>YGQ/> %YfTh?U}~؇Rݱoerk@ <#Vo|o'E렭h'1m4jI;K!_w53ϓ`Й5/4 $˲biLbrJ`6,9@06RR㭲{PJ24*SO $ҕc 0c @rz dG;e6R_hN3s 6iI=ř$6RU$Ųj ]XK`L6)$M1W**.SRf6CU5:ULM;V!']&r=g6ksϲnmbVt Ij,ߴj`6Ml.L>lOww=\WoT9ە]ݯ+Dq,dRbzzs*WHjn^8s~#PI@D 8,gФl{f虀+4GĬNKGbP~qI^kb ;);wBƭ'fEArE|gȹ@Q|h>+ j,vQJ&ǣ%J s/f|\ɳ@pt0ogk9f`I^u bT6qX-:DqMӍ=җX1#qj,IG~mAq 4?L<(=nsJohh-` X墁mO(+Xr?ؔ"gPQWXX6ΚY '$ }bZjXcs~#Q8{̩qf)ou{ ~`uo׸9Ơ8c:vI;X{۹g.W⹄K>IrIe:;v >]|׮ JtX`x<"JH,'JSZ0@ Le fAc9*#wbar{5is=gH:4w:yw"۠B/ ?IF j\!XM0'4G@']ڵ{ 2.h_ڶ@&Z.fp3{yz>Ӆf$ucMω85ە빹+V>b*K`. oBeٟ4b1أ_[(je\cڷPW{3}A (ה{-sWy?*M7c#IĨ_6*sAy2{d߰=ˣ@pf*1Lm.0I?nKg@j{Y\m AClC⯞~E^޸^H0NϞGk&IH^\>>*uE¬?5yP+faGȈ%dqU|ʮf_[V;׵vsz b8κhah/C/ pC bh5j #o7nir G*v=hT6ū"_,&,r0KsaΒˉ`=7J"fJ3H7~lΨ9[PCQ&c (\a4cMK RGXl1 ADIw~ɰkLֳWNB>yV.>?9q^t,mNl8J U˝j@ݹ+GE엣]0Gj20gx=;ўBf͂# >eKOA ȥ#EF͹jǺEu5]Ep$ T7+>m_exy#6B5 %zvjң d`υR!".a phaxx30ILDn qAE K5 MbXp &!⡠9(uH;X2*! A9VIŽ*ҖCJ @H^ݫF5 ΘZm$g!!$DP8OjlܠTVL^M9~+^P'ˆAj܈p1A 3$AG R9loV5:<```4;ISȡg=%芖rA%ƅ莟Z225`6DEMŠDܠ7+/:! TS yo*!g4ʏbR\ˁ Οiw*QdT DkO1r#D!cmЀeP8 HɑtԼ\9TIU]zוb a3/y@ ;0l*'? h+1 ^hmhXYNXCmwMQ#b&ڮ8 o[rӚc$Rau @DtO/us]F/ .v}Rp7&0w c;3s)'C EBO5+e՘zpƽr4[pvtF1yUG NumkEl+V+'a!d+.ıA\b80 bT?pΪu9QuAx|uEݤ@68-[5U, WYJ/DJ#?9]KG)PANQ {sчј Ǝٻ;mȀ%BJ"ԉ9+kP3siG:d>⿴x195}^\s3^ u!wJ~Uз0`ݸ]ȯ9qPmxBIslY\ONv/CO4wLڱj:iXif`̀$]>Wֲ4B`TUL}xP4(mB/gm|5LQ$b֋O%Cڒ>RĀm gla bf> q*:!+s6F l-qwl hDqTsPw}sds"LKd6tK~_2o`y48QvGquVGfR|z\m1 by$XyAq-v4VUSJPh$/4!(}:J}x2We1~a3#-~A˄t)UBhKBogHXaMMOQ cxue@ST8v5B9l؆2KEr]Us ehr(a$sc0h~or:tNj 54ңGH(e)׉XH;2.t$.ud|;*XGq(tl(@p)Gxrx@ xȸ4+/'sLFy~Kiqj%`5M"Y(p~fxQXtN6 m59b*ЊW9:vW6i Շ%ss?&hBdbX7Ls#Ht0Hi#*N'#G3$EWuUY72]ǎr2I0c%1EtoJB4Swf O)yEc]4R4K)d!Fgis"@f!qmمL6~fo"<#}5"D(CQz^NHB ?3l,gJi*ɃdCzR) ,}W4cI%m|eqKw;b6nYV22fFGBh7N`S2Ř A9I9 x k;j)𞮸NcHƆlqlѤ;s.v}$ʗ:t.Р}N]O7dhhjVH9"W}?d9)16*.SLيZ'?D D UfɌΗ~HFTZ:=曳USO'#tf`ycty[Q{TsSTZs%zе?/ o"Ǚ;8qv&X xy`O"I 9x?0g_!" /ZBm%,޸#x Ŝ~Q+)7lQB1Y`N=Pf@hϚ2s1Pdmޚ6;wfI<['b18Vc:i9K"Xyg"WOwY[Zb#;(8q3ӰgKt6o7en679JRw08;!KrlӸqX@:4eFʪvei: @`Ŋҥb$@x-xUC;^]+.l"wR*AP# @Z|<[,ᬽ2L\9 a 8#B̞L90,K†Ţ!5}e[Jwg\ ]/5ww#t1pl){$MtI`ӌ  v`%l2- hk lrں>|{*P(I>?48s,4WT!hƱ|2wS,@X{w6'~q,)e[tLё0p Mِ P `A$fPٚ=-<|NT;!0u$ {7@][xL|į^?UZ[\k}ȝn!x^D^;(So'ڝy P b - `f-33{{ w6.N$J; b{0?UubT/'}E I:#d/[]2Q3yokl빝2=20A0 >P ` "$< L>psmoțxLj*{;Nizb+B꺨 1l^oNx~q~Ĩm!zh$nonB(͍s6Ad⚪݌A02C4M~o .<@,B-#OolU^M\~;!%DYJ#AlNd@^q!!5BӐ\Cr(a3Gen~tpUVq RP O` v''ԫ \7353?r:JʓgM?Ij9jXe,(N}ayϺ1sEΉJx ]cdZ=_6xG,iBP? n=' o a0wOR%/M$g_Oi*1$Uk/{}nnя\ .g/?#:Dx7 FEO)hVNf-0E18JfBs-DDuӦS( N0*[L2C*\pˆ%l,BM*JРE9@4C 205* *d81F ]mQǎe mj(J&N,קMp8_zw{Cb,5A5\@xfΝ'f|h9LߨWÃ![7X|g-o.<\Dt'R>͖';MN\KRLyIK.eҤkfϟEuT>4Zi 'r* `` H+( ɮ밾628i2)X@. V 3_ 4F}P1V Ͷ.m~s8A 'cιwJi2#>CKH%4+ϼZ &J(F/-=:)\[1@&˂L2XP ~ءJEР q{H] dFs 䖳:mC`<#NhB BlW@3k *O|k ?`**s誆s*FmNHK8qL3,4+VJTlwqUE _4TpqvJ"2p2DFZh[pJmvTx0;P0{N _*)O}0[-x6 =7?+9QsU2TVAeTTC02=D]LqA;WS+Ჟ+6ք@SQ81o6[蒮w>ƒ\91 !Ɔx'DWu?"aC+H#xopmVLYg %t\sI EaDC3RtB d󳵴.XB"pA d$#8=r;#jIZ6}C F:rA BxA P2=~e`Lq~GqOM*`܇7{TNDp߈&r(*茋]0>U%xhf c7ގ"X"*P&Ԩ9Q wZ6EiC| dXcHANBy "_Uy <1|" u\`HS|j@t&3KG& h@[x %E(P̨$q @H4$ F5"Y#h$:Q*x4,r_$B8H t+(u#{Xno{ݓT( ;"kt顜 Fj*uMmL2Y`>qdTʼ<]A5omp3o&! Od-$-:QB0䰆E,tэfrhUVVpuf RZrE_L4Ԁ<rD_) r-P!RO'9^k6`:ֿw| :MWJud.N@%tZ t% ZC2 ="1BG:a q|)TS(nlc`!@LI/{6 fmrY>  jZK!"ltaz&LmVurd1+f_ SJwǷVO^lSܹhy7 W\rh%IBCtYwQvNwuZiRdOE6bNY|0J7KĢ} u[C΃t3@c}Xc$)e4aLΖ&'~*hyfzDIJAfIp9]5Y@T`F(YC9c=cͥTVY) 5&O]*nj&J!]I]K{2$? P {a AD+7c3e\j͸dVS`6X{g %:Ϋj+0yZJ@,d`*Î>l`B$e$Wk-\+tΥL3<-N[R[!;<,tsi_4IWmmh3 gLd9 Nl g>묭rc.uMʎ-|Ռ x0˳?f9LTvW֖末s9R^) >#cM1ڳl}Ъ~sFz̷~'4<2$c:9;*K/m*ϡ攏4_=aۜԿKlv vԾ)۝A 1%jO{EBe<-~K7 MS:t ]/}A B[ȕ.0{w#ga`RGlyLF1Qhp}0< YԠ5l>|cP/]ԤV5k ("QZ瀣eY@f$fcI%G1'BG>rfbX hЁ8 ^ַ3o$qH^J.HL+.<:ϫgjҗq<,ŵ/̌YKGKlb>Q4KAe(pe ѧB ͆i5J~c1Z:ol3'IҚ\ o+"ga&'DO[xgbC>Ebo^I3^@\ϋFBw:7*jÇ^PȠkZ bt̨Xgub m-3A`ߞ\Y׀5 | {*#ΰ_kϿB7&,rcvmx`=_RM|yV}ô!d%/L"9ЪjB,X%̘J".w~* +hlFreyJ3T1؎uFaoˡk\QH^R[4d2N\#0.et_-,܏5=.tы{r9)Z3#gCƿuw>S&]Gsؖ3?n+:DƦ1Ը5YIT 0^}m^bȽpq][uT-nM~u9[?eSt>6_2(FIk5=xnE<ѭR 3\!.F gkskOq!?zU; z9~b75.ns+;Ҋ'Kv\y4E7eT|.MOz Q҂2ѭbO+fzJF]z;avuer纤uWf&/jn8ƨnnnC' yhj<-{g$G3xK}mht q?ٓi8.{E8.Y83|YSWZ`.P]I D6sHolaG{)syU4:n)ucwZ9a)Phs)He VUVP0 IY҉Sdw։~ڥWyމ՗li5y^yɅx4 tF9_[[0y%HFiID AٝT@)JA UvcEJ.UpU2*zz甕`6U09ʣ 0 [zK|䅞OB]Ȥ$lyRuoy:Sar)WbD6Pjʦ;IO i5spHA H^T:0ȝx[U7'W&IRQ:nzP %Obp+wp__-Z>:#Z}Wǫ\৵^_4Ɗ[VI'4 ̺DkZ[c`o: I|ڭFTj@LjᣮGuvyu>FO{' j6jz8{9[`=: T{e+fKWVK"hJjkvE1۬,u5Tl;n M ڋ~opK|S+*PRW#%Jʞ:zI1Ί6K9mjsوJ zẴLմzSZTuX;_\a:7XR +IQZlۦcpc9 MiٷUK˸XJȻm2 k 190>Ȼ˶W d^[D1ɽ<(fxA5ؔbHa.P*I[՚dۏިA |Aػ]&.:+.-2S? kt @@ܡ= M^Fv 0HvY"N慶Xp$mo^dR w?L Lɟۣ N~Hmt -Ub>d^eKN4+1޾v>}>qypS4.~8묉[\ ~)BEߤ۾^xÜ;x 0 @\Pc ݚ4&NW <͓c~S3Ͼc pkPB o;i*\'iZ˩zAX@לCU97_BO=E~pXTboKoTOBN]| _ _$o )o-f WxT {?󄵾 rrNʭv%ͿW`_=: (~7 ` x:=ύӚ ek.d@ 6aC D4|(q$NPay劙11eƴONhϞ -V4Y2kIZ3tSMCWUtSJݻs%[Ye]{kb.ۺv+׬[ تT/f Z%?&A  Do!Ǝ EJ'*[~!3S-c'PCEtjc^uyܼiO7[k=^{WUUnS!gd+lV`B:{>q# 贍V3#n)V6b 7p7tJ 1{暲İSq::E\d+< æ«/l/>":t2:@3r$D`^CA.-&2jr ;,(!rN脑WԳ:z;q KO@1"0oШ'4 TJBiJh )2-j£N.P pS@kOfOh{; QekF IJƚJ-ԾPC#>}VHD(Uiu/ > +*dZ`\uh!VcMehCvЯMFo͉t'ԲLO<7rT"KH$$x`58X~ek&xR3lslkӆdoKM/"<ʬx\@<'-hj>" rдP{#„j҈̽|>辦0 ^6aAh,|٘秷-w`]6@%Q}A`! ,y i"@xmT x)mM4j+ C9MVYxXi`*lqiz i#vQĦ 0n`Xѧ>"C%*IfLU4hJm, 8StD s1䂏} e8'AAȡ r $lQIީd2i$HjAvnUL%}| .q!,/֒#^@#罶k Z7 moP*LBNn!)0y[4 R*)` S@xP8 aQw-KYe rTh%tq$ (7`Tk'$.t@խ!bbXl|x9;q}H<48 {@@ 4`]Xu+9d(n[U<[/—{>*] H`)c?{3BUs+[f`IR LX7׃ajK1Z3:I328 7R7ʈ>M3 hA  b5P[]I!$B1?5G8mhTB+,T1,$0 ;c823=PC ,( ,":;;C蛆?L0N* DܪSyFOA$:~Y΋u$B3D=[bHJL@=(XUD +6dx ū^; 9>GHɻʱ7H!cVcxpFɃ-"-2G tTLtML;4{JG.X30Z8HE3 |y,)@ܝOtC[%h\O{DHlc Ǖƈ&(H1I<2k,p:#͂Å E(R LE#1hU8Hkػ9"rJòT,F\˦f OK5{9TɗlH%d+ +0GKLLDN%S.fp̔BH%@@ȁ0\x8ˊldGMϴ4LbK"M D ̀"|D"WS:%K|N +L$ȼ \G-hLKjI9uX(Jp=#pJ0lOΒK`,a#OɬvfPw9 @Iӹ@PPI㌀P`ڊΰb(BI Y;|QfRHJ4*S HM$ҳ̐ϊdRۤTG܌8PRUX2|20/A1m nK(R+NKLjzIKQ??S\EẂ(Ss ՄԱ8՜%=## (=Xh)=R-a~FBDśnDjP"UBЏIc9.U=!J ORX=@,Vc<ܷ9˝<)܋H-Ɠ> ?;խA-р Z2Gɦ]-?B.m[ j`jZ6,f`Gȃ2PE,#pHY|8sR$ޙ5VM}\OMMH5ZxCؤe_}]F>ߘt'Ѐ;o|N0|زe:ijJD&D8kJDxk# +DPԿլsjaފ\9&a b礈xLAUKɫ{<xĢ -1 $3Kvhj$2@h/ (p@r 3h8ڶ _fqgossVs0nNш(#M /lFDTh]•@rO/Qr,7 HHvp&s-4gk2suI CsPub({d `RWA~KTv~>eHRjo`"8+u)*yT/zy uy_+J/q0WWxwgjiO.*-x&)#UyT>{׍\:ayg"?|{h=cΎ_hzkOWxJ1ȫ?&*I d {S>(紗P]"P5,8A+8*7Hw@yfP۩˶ɀc1>/x?x˿[7z^|e_LDg>H``` &"Ƌ#F )TF+L@LIfM4'=ع*رjJ;g)ԧR黬ZZ+ذ]őz,ZkjBk۵r2u*ӥJק$<.l0b'HC *j`qC&4a,ؘ􃄪Qbi5#GN" _(a"˘6qҔYg )zpbѤJ֬+ivZ_m}Xdpb X8f*\ AD`h!ZBaZpQ'Q-wJ`K(M=瀉=uwvU9s4M7,=ÝwFw%TuM}]\X~؀ e7fMrYixj&9m#fPADJHR)T܌.ńcs=! @jAj!uG]wE*O΢QHs]jY` _^ߙilogd ڙ x@V胀fxn>B % 4RJrjC,jOD)1vؑuD`Q%L*h e0LY䯿3r=%;VdYޱ-[srWլLXAf9حu@[FCb6/HQVR]S ȜM @ @QZl 1Q#TY3;T3G1!CVY sTeU]BY6Rs*LZ2A0!j 4PSg Yvݵ9ܧf; AHZkaF Ϣ]xSx͎;2kJ"yNȌ|yw+S^П^e9 iA0l\re(ЀSH:< )`~PxZ`/O) 'Cz1J-}ՐFyҤo.1Zdl}S"?q \f\+Y`@ j%P2 ] 5!׽5ʈ0XּDIA9$)WQ%x #9: *rS-T K:ر _LrC"B!apuyUh*ί/_K+#x!bc@ ֎3c'q&C(@jiK G J(B<;EzPL[Vq UXGF [B Ib ]U]b)r~`jZP;1̕hgJ JQ u-R]")TT.ϭ<4ԡu 0BPVvh^U~0[6|ILHpz1c7 8!ʑ"8™=T"ݜprk6mm̧f@ma B g]: fLBYpVs(T.K/?) x9"Hzۻ)q 9[j|:.D4he !:*e|5o5Yт&fP"R*o 6"+1&4A`n-ZFlcK_-9t.ixA OS48zq$0v*i `ȞDCr1`2PFDI yਢ[`s1/Za;26MX$?>$ۊP\x7twip q_r㨔nTfkS7ZqrPҒoV[ @:!!L }xW8s*T! T^ 2$0BIJw_mDD,֍!ӕ41o}ޔV;KIWpP+8؝ȀlwUk4xFݴB=9k4M@,c( MSao5x!\y8|Ps3Ӟ6pD,L ^C#hdw9ݗUhҽ]s;w“pDG"N4V6n.C!Zj@TyB4#QH46XCB"< x-)N__q]uI|Q'8A2ah_hUR( ^DF_4DuӨ`3I l G\^UDH<<$+L?)])6#X`؟VAp`^}TXu`iW ^R . E)AeL 5QέE)a61r OTM \ @mmJթHJ `+ Q앏CƂ)@],B)H P gQvw WS>%TfyL(:a\,BUԈN.m\˲^e e&&d+dg)+P&T FN Tgg: eR1j-V&VBS\DӤbWPaPP)8Ɖx܋F8 hl , \&\wbc"p(xJd.Vh`Cv{ i6C}:}OF@VbC @8@D_N ֬H\Aa5N'7:AqUQ Az҇t&6ZZDvigʝOKDїW<^-O-gu-f1D^EhDJ䈜Eix:xD&( $A]3d؀ 0PX=x$"`  . C"Rj9`&f߇|9FܕePƂj/b^oo8M #䀱@ ^R-d5dzzQj" z϶f.Vkƻ@fe+(~IbD-Zd`Kf 8&!܁xNRqF"tB2x5C{ŖUl,&V+ f|dUb%7AHE쉷(CtƬ̮kM!RjoI|m*@a&_2L~m/MnlZhn4.~E͗2lUdpSgH])_>BʨTk`Ζjؽj?R8ځ~m`-*ɬ/J+.&..e)ԯbƪN\-hEho2aWWInj_BLcԫ%-q֘0ĩhA.2yP,1~\2 ~&o2'+' 2 rPdnebWӴ+c٫}F5@jE03I s@9 *zrB-lm:,{b247$L3.c=B%X ݆1"bD@r r 8YO sI s"92(s@GF" A-B'!g15'CtJ4ZF۪-*ߗ9(@DH]13L7hp-5dS N ({rG84\5R' D 8=ԧ&^zc c\J;Sj2! c ͱZ1'G]e4{ѐY3(x)uBRi!6pަ ZBHF Yk-wpBIHC98j]. 6x``+F^YUZAٹ6 r{ ]zLh`C0؂'xB)IiSkjXaw'um&̪Abs%H+faCPGYw \UtV' !'̘ 3{TmG6T*yƓ5L5uFOkFNqKEyLx9 !X'O!y@Z,'www(ƸƋ7/J?ŕbM-g\O~NE8K:y=8ڌo9y(!_WR4ET۹CmMK靮y[Ɨ:EYTF<'7gzw})% u4Gc|_׺`0B:zytb=-x{55kĘ[:ܐROBAm ȁ3 )B&3 i}5p~B{6Kely]77OZ)2v-apK Dwm쓭S@ `efL9s 6tbDq)FtXbF8vZjҦlSXtfL3iִY3@8 C 5Rd 8pjT >hp@֬ 4 j0U pW0PaD 5T#QR@|5 %C4Q5g"K=3wNFիelm#liO\m#4О$iʛÉ7><Ξ>pbѤI5pz]ڊzװoD8T-nmnyb0`'QF\49 h/x3̰c1iA 5\ȶjOs.Gy@J}.O:uIɃT mbG؇+$/] В'_ӿ9r_˷陕)T<7RҗS"ֽ*TjWyV*#T,i˒ y!!Hȅ*A ԛMAG(҂BL3_CgVCB"?/I$>:1euZPIXu_o"A]P)P@F .?CXB~ TH 7PSp h5Q !|C}9X5~S&DTESv;%zŵ"uJwN2 yEm :֑ xģSqT~9@H"{F"a(k,r6#cLӸd&9YN¨Fs֔!ɎiMy;O@ܳ^gS4)ɕ{Γ . W0/wGaSz9^r *FgBTYLS6C@q48hN`2I4.>*'N%vx*jHbe]Ƃ0 oxAy'Q6 a0cv4 it'"nvt}Όɚ޴,SxԀR">S~Uˁ2>.~<&DOV)=)2JehΦ+-\/DJ]r<]y 0Q`-\+LיrV]brpWuI8sc)Sn)f9i @te:tC9J'։e~ٱD`>-oـr@f JRX%Be:3p>IWz8/qLDA@)D-J5#+91?)iVS1kw*2;W‹ pMV5%K1ʯ}I0~-ю2K T~lh#p!%_;[ٞr (''UeJc[n,?Rnc-\R`C*Mo:zc\{`xOh .nbX@FYݠB̬TXųp`JLAN GzmpyƮn;kpȠMO7 xPP.'RӐ>Ժk$Ѐ YFP0F VH*w.JGNЯ {t !OKwpq|PR<k n + ֜b nÂN:;d1o>Dm!"$)s(E # iRMV#O*Lr#'qMɟ\rκŐͼH ysLxDK)ܨLPlq`kzg(u23)))@n**+YD+7N#,,Rx& > 4+k ,7,Ĉw2ͪ兊v$¸ONKMW>O/J6aFSdh s7"3}&qr3S63+m̸&Qݸ$-3,ˋˁRek>6*o,0BO,[8˪zStr fs7!)*/G0S;C>T`nntlw`=_T4#=Vs$;@FJvlΨ2pnp8` p Ra49g(J!Tckr\9rb+9L@S-=̀M J欻԰xD>O׋5c'mQt P A}`Qk9\  "R+B1BK>TmBL/K/x̴=ˍUU |~@Zu(MT+N)_1X &)R*fH@b'YwA`Q# Ad-TdR4µSU&D4]o+uMOI*rzb]lWMuʂr^4˴c"{$N/my ̀b Pby [ovdG,JQ~TsX\]E+٫3f%pK(V_S;~ ,lXiIRv + jtA &krgc&TVmvݖ6eQS*&`TG'fmVQ- !0X#UoU N=:(ׂLm(0wtbK/F (v JA@ȶζuC6dcvtSu"unG;RH6mnxOth pU54'*WDqzaJ ,LƂdjވْ ֵDWb`}) RhcnA@~7v!ms; e2xNVa4UKb=E{NQt5= (7уπ2wp Êt׷Z=Amou(Ӈme`Ckߐyx6~zbEQ?` X<#X+KWlG_ã="Gw)gKkpVk؍5 &u8vю1}/TY2:F46 sfW-_@ٲ ̋*H">,ƍ戍w 9{!y,۴meW[F.:Sm5ʼnwoN̼9=VS]֨cGg`'C,.W)vI3W"k|+sxd]W9D&-JQy_E8ϔ9P@H]5^';#X0U&ČYW2dp+-{NUTgc[4xznͻIУ )R*,G'Vl.aO'C@$m'^ѷaT 쥑:L ?QJ]ENRIhK`-‹YXߔ!:70g 9a9h'#!Sn#2`az@.E~#]QӪB8@ o8D~0bDˆoh%V,B,| 4_`ej|YDCU I*fĐy UT3v *XszU8dv+֚ZJ,.TOx7@ *p0#p#*08`@/Ō;~ y^ 8pf\l/̈́Xb$լ&2Dg;Er[4xƼҥqVWXy͋{vW kHJ SP%tu4fۻ/;Z•K׮ aQ)ƀeE`MemhŴFAm8\nZI FjvnmI &M-pR1UY;Uu͹FTw9b1SrG\V]^|g[o\u-h@s#PCD0B<"6rɠeefEҍFP ,a#  7&m}bMzujj0X̙hZtՍ CDw)xPWUzXŠ|^~ 3IfiQE@-dz`Rh2Jb*aFp*8E"DUJ/jįn]3SO'1 jB ECĵD ծW9 9ZnV},Yp&6\ym:ߵyj-/tRj6j. GKoVC&iXu?İ+1 äFJ< <$a1SOayǂLp%~ڹ09s*xXbyz;CejN!6tBntbEƵMQ796 ?#l<GKIVN^l~Mf'㚹`t]U ̮l ǝ|'I,U:Bh\ S醀Z7:ӚJDSUEOSs5)((C eNg4Dz#hPk%"$Rq/'dAٵUN 嘪 p ln^%;bHuױdSrC#Jh}+iZں&}(mk;R2-u%B(0,]=H )YG{MvVi kJT;z#ZxW|xk TL!lgF(:Cg3rK(fR5>'?-C}VQV-4kC*Aj5d+[D$-F f] Łz1cD61uj҄Vh䦆/=#J}i`q9P`k0zB64i&!Rdm @?[T@PfRIт3mᦢ=^9e9 .d| @6m>QYWFu?U@([,:7"?:RIHFڒ2Lj9,Alg =Ab y;.$u*tE̦Yy{zf)D}CtL- .[bK| S̍Dsff1jP>;'{ I? v/5(4bgQR4|@|gfw/Q0|ll1Bv&A%=pw'mC5}9~4~\y>3?%3jTETQOwe`PE(jFN3K?fT;Y!!dPq0d3L̖!WlFA;B&d.U`v=׷m3g0S7I!r@CE6?1y,D CepI!?7eф4?`: U`7VYX{ BU#{sWƶQj0YBfZp `MpqA`18d(8޷ss(35qY +KTTOxNA*QLUra/zX$(WgtFq{\./Wd.0\fgdWȁl?E0UȀ bѸ- }_Xanxd8>7Rtl2#,[QGPa/`: (`i[[%LF,!(:l4ew*k!@SOSA0x @ ֐ p `X}/m[!8%C॓7F<NE9?8yV9CHTwOEUX!LcXfh@ :[Fs5C VV1ypI/9f|yyvb0K~B%0 l)ɒ.I} /73g8:eBudEdGǎƸM8GTIwKh;aEK){R䕫u lĀ WbQQG gŷg=qK2^?Uj =0 @Z*1f 87 eb<"TO6Tx}JWo&i@]54KSK$ Ltz>Q vw6kES4.`E @ %@ )I 'rc5sCMPaQa(1ui[:?CG3C-bx(c٫N %"/?Bz* ;tj&Aف+F>SrX)أ>P/Y}Ym`Nu=s*ima\> ᗡnCxY95#OpҬ18qnl:Gٖ5y[.e:!$k0 Saq9lL)c` )xR+ k{[uA""٥xw7`') "()\6)_ڐg?f`4x&Z/aQUӋ.D T.pp ސ6]}`[xȂ8`ڶxiu+1i{iy&Od2iJtM_ #k4Ѻ.Us? Fj: {[?Ƌ Ѻ[@y j 97 ,i(&Rhp7^Swۼ)nYs{w^C`Q`I0/00øZpy TzA#ʴ,ѨT)xP p~ 4e^{ `N2i h簰pxwqZ\Fi'd+y6??t&K'96THDwf JUX5@"pu{s[~ɴs'U)Ŧk(a*Wv@~UP=:]M1~hg쵶` fPf`o c p(^,'̺*숇C'&CUB @HWo+%1J"i [\[ʮvˋeCw\x ِ p 02Z[?]; im-hh꠶_"\s&U`,d)EgIHVMgxS4VfϻϒNu:\.z̜0pа yXav6plM,\q< p̬ iqh{('Spds=t<"?؁jEj1[Eb"l5LՂbB7խ|poè,j6v Ȑ ~( `Mp} Ҡ 0xXҼ]'=׌i6~9~@NFN26SBU(JʊH:4@șO[rħm?4v@%Ts1̊rm' ( my +a}~+1CY=AMn#7M((z^wuV:F}bԖY^N|)}FWv `%` }M̍ƈ@g}̼ ` $,'O 㬫8MTo:>w@NlXI)Wʭup<˧ g5)/@X CS(6kW|d3Ƨ*,8,bgs쵠Z 'b #,p!-tCy3@l+C;ޑx! ɧQY."a m9KD{pfĨ1bC>bb>@jsXH^bp+r@ўޙ=@#5^J~l$>@͓~q L}uUF q{2>4VP' -2NpВ-|=օ7\ 2.' *.壝 n#6C}1C>@I-_kcv Qlk4(Y -<\lp t !x 9988 n R183AF5Mp~Dz(jR/>lLAUDX ֨A#A -^hQ J &J*o9ldY={fH UQF ,Dfzp!DIV͚5s]z=6w̚E.;i4z1<@^*paL+U>\1C?C ʕ-WA m3 FC#7<{"& %";޸ ʆ8Ό1vH$M v+mxDc64U lȲ= Q/hR5XV\v=?t9k@vyGil!$ ⡉'^ / 0sl;.C\, *l4@4z`-9 .6hsHla%id$ TC!6:R.¾&8Ȁ cdLƚtD`T'b'Ė{ǚi]3J ½ x C ȓh3NILL1k1bQ4Lxl8~,h!4$M=N1( N2Z6- .]bL`BAv W73U *Лέޫc9D>yӓAp?sawFzYiD SMtTdC5US`UeѳNA[w!|rVP7tb E]veEڣ[/%\ss8̃*ڼZ͊ ̆cTAĉN *,j!iyDhXSN]G`đG1OFƨ^c|')Ԙ7HYQh8炶-K봻St4݉Zkbe>*v9-JkK&,>iؙL0T%2U9\*p[FW*A\)0tه3(M$&=;҄搅@&Nu|݅ &AC#8щ=?:1 \\bATBlnuQj/bDРbKw =C &9Ȝ() ݋ZFV^F8PP "!͏h,^@z$")AK:Dn%C ;\I"81 bd+@FDɨn4Ʋ&ӰE9W ϨDY/[dE)<`X@ vCF R%3Z6,:F@qcǡhs3Dsc^t&ǐy%tsHp%ArOPd Hru܀/W>Yw[ran$XF#I s0g:k>3,#% ZЩ0hBz[CɰԀO݌P%=i1P ZpB~z ! ,^I$_ͺǰ!nHqu2j̸mC:㴏5D٭i0c:f.ܹSm̂ JѣByʼnH^ "ӫX*9N[uF=ٶ|aLl}xН߿m*ro^u[̘ǖ#Kfn]㋄3kὝC :"b:]"ROxK$~EfjoZq8ɖԒS)ӣL1c;6dX5:f Z|9W}M\ܹM&<3,;-#s0΁WQ:%Hw^yw)egEewF@^{ PI3(C/.%4֘U(`5@J#2QS&FxPJ*UԒA^Xn%\E_`6 +k^b8 ;/.X)Q;4$^  YΠD*7fXG99%OsƩ1)բ^Ts襗5L*AC{\`*03ooygr1)䠼 诀JP蠲"UtW촉^4fF> piHzhc'믾 ,ܩC0kp,;MCJF۞'a4T欄Cz+M54J+#אּ>ħ7'-g1qnr<74SaѸ;-,5[c5Y4[вZ w9m~Z|Wxq=-qq#``hGmm+bٿ0:ZǕLDmH݈:a91E,u iTN9;!Q4>-gu-p _«D5kUrsm͋^V8kuzunڇD ٨qqViyhPEa9s$FM[Bk1*̻ޝ(-r,e};ޭ2.Yܺ4zc f.7k}vr i΅վo_&gYGмg[^Ĥ/wpm+}kG7o Vؚݟm{?nؕL0/R \[.f?XpN$o ?~OtNx)C']|>zuKy}hfvדu/XϫMӿ/O[r6|z7s,Wn$x;Wx6WiL' ַn5sjpc 7Iyy `uELS\Gz~2gf'gwW lwЄ ouRA8FŐwycr (g|M'>'0@}xu}ʐcKWnw S5ǃE| t~%d&+bz`7xHaeW9Ё8 &n64g:["c0}GE+WQO RhP0 0tnpЅ^crGv[Fjh9g6p8aBv/jTv~yWw8]LJxx HRzvy6wdIN؀Q8cЉ灡hW_Svo'pgcЂqh`_Nw2J2>&mX WyʸH| ҨX(`.U vV戎ahW'؎xwh6iX`;ҋ11#4`Phx5ig8 uǑEPc @ _x*;N/M2RGKVvzÓ0c#Bze PP3wSIim9V|ǰϷf.Up[ 0 eYw8si`gaY]pY~z\090Cgŗ7uV$ٛiC'ig 3h9[9ceE`#ىh`{Y&n0?3Y9F flzNy/2Ri%Fśi }yy (En|Ib9 yڹz]Өe|fY&jYɢ*jyq%2qhiiXǷ[=Aj`*U@[aɡX~76'%/ʢ. 0zn:9Z;ʣAzO9 ƇYƳpG\e'Ijje*{qPtyZ{ ŤW@cԹƨڊ8*՗dg뉦٩"զYuy_ :zzjc p^yix*UY)Nwꉩ{٬0XZmيwjpᚱ|Z>p*6PIʮzv0PHtE6A&Kc{z 5[7f(I܊kb⪠  q*PT ګ)aXʥAv<>C~he\HJ[ujN Jp *9PN@Z]fcv DXf{䗶 G0&r{0Ŭv;dy[{K^M{ P(б`V p@z+frֹ_zz6qkzXIJC@r䚻*PPKK^dϻ4ibg[iכͺڽ$jD M⋒4[^˾` #%[{W@`C3{jP"j ,::l r16 ?XT P#P>PS`&P x:Ad;K n{>LTz@ipѤ]E`ڴa N]'OVЫo,omM^Mm,m< BnvDw ?>p P>ޮ]Wm̮j#I *Mk̦mvulr>^E簬̩Q=$=Z00+Nks6Öcyγ@T~3jfԯժ^N?|[ q#9 + _gn.8>6N^皌 ڧi3S @x ~$]; ϶.0 n~^ȎIm˽ /ݾA<_RN ^%_`NF{gb[x3L,5N<_vB@oCyvoG=IM/omN@]V'_q =y&Vi@͚~misOqw>f4SO~%( 䴮Yo 7_daKc- Org}Ѫ? 9N:ADݷ]E?դ 邑/k40t<$G|dg/";ON#'T@Ԧ4+U@ 䒋/'F9 )5D؋SG:Ӝ4>kD@KCvDZFrMʞ|I/%9ըSv5@N7zOz-xVXe ^UæxU29z/aKc#Neٸxc5ii7'b1q5׳xMk(=m^n 4 ~k y (sɅֵ`^KVX e ?HbM?))ʎNZh8} NuŮfՎZ^st"wҰ$,a BP1"Plk۠wBCzӡ^qh{( WA!|IJ@60= 5E.ԯM rOO:]6(R?#nIU18@0źc:( KBR`c~Є,4):wĘ̏57rW"w$%4$J}ʏ6@@#m(|T'f\p)tN?LہEOX/V1A |KDs f>:#i(W$$ yR0ܓHiDֈ@"6 sJ (. 2ha<@b6uD=5`^-eeD?QSGC\) vm*?( d:n]i"s%|QF Ky}0ʬj-XG3RAE! XBҀ) )h8KiHO$uWi,tMݬ{]잃 +Yjb )a/%"ϸv$T%cY&~5BQ(6%miaY#9[gZZ1ZU.m(4a| :=Kۚ[F{X~ ZRP"oawX#$>S[DIm|S9(*<*vh=!R+ WL<۱i8 XQZ "s.d.\ۯ#sNj'.ݽ_;^Hٴ/k$ӏ!i0MM# l6YV$8e3! F5bmS|J tp*./ "D-WfNo0C(`}]Cu<] `8ei"~ӒHF)lr80S$?W ZW CDk Q#:V19ǖ3S([x>蓾;>i4ۆ_LEp.-,X$D@<; ۋ8?>?l=k t+TK 䩞A,&Q2NS ~ӈ2$b"@>4AЊK("<|i5ifHLL%B $f#"ѓ6ɹlۿ)<h r“L`6t&锖796@z="=;\)+A<ś8RLJ?@%8Á8?'];cW A#HI NL̸ɼ˼̤ԿcJ8iHJ9Hd ׌%$3\ٸM b\K*($&H-0N>ڵJu`_ J(:pP Q3POp L<Uϥ δWMY4MzN)7 |=824C M͍N, Ƶ@EVj%Zmf(,pȁD*dG|G-QF[O!M_)R_HRԴh&rԐ)͈˒+R%77R[ SrБ$OwP똸4DdtA3.#(Q@j.NToHWu]WuGo# J] "uKE=UT%U*X@#I' #BK~C$@Z7 H'2UIcaaIxCXE(=-(@s\tWc ʝQ`We׏Gi|}K @ Om/?ՙҨ$" OsC;h *\2'u5;+OF_Z j \ji(\LCЃK:-)Y1A٤5r-so8Z|\p#8PWW]ȁ0XZ$s' t2Xl"  X^S$UfK-`> -܂UO_OIXUPDC(,8#'@DkYˮq-]EE]}Km]3Y|-X H}+[!%CRQe\.+KH-ΑB.^UD8('Ȃ)+lu-I͆߆9M.]*=-VॣZ[,)CXTiMr`䡬 ҉R(c]'vvr^K[xBa`u DB $` `0 8u`\M(hA5)dmS'C+X6vM ^1`/bEz:y`>cKc'mMMi2'a$^l4 F .p$d4N#B;@ "0IZCߴQS\]e;q~gύe6uE SeW0(`4#cc/Fb24|4~Ԭ|˹T,10P hۋX^? f1eN 3p=ן<5g^Wh N.e  0a%G6ܔ|+ ` 5h.@7Ѐ6cl&d4}dp,@ px dJ6Ópѵz9Ծ~jj5ZN]e@s>Lxݷ%gs=Zm%cU ӜTF=9l>I@. p@MoȀkHlI0$1|$1mFm~Ɩfmp(}m6 /mnЀm߮hb^crrClZ!Kh: Tno&0N ovKo}WFV>p~.ZpY&Fp^p0R/$I[hiRS2lFP9<qp@/t.5W nA)A&`//P Pog #Og8p@4Er )uoo"0.brI&Ϙnahz}U9 t=54Q;溉9k\R>MlKC?t,@6&"H~l2,8 [Kl6Uҹq/KK77]nwwGZQךkPzˏWo~zhzsi[&ꈟ-XS v@B 事yZ5,ù,2޲kYWts{P~G,xFyVAƇBhCȧk_p͟弁xzlgx'!0s0 %]  <( A 0P"H#F#N2%/_,F)@BgO N!`ҪU;gZrz껰躒-Uڴjײmhr҅fzׯe^+\odȐk| V`2̚7g&P`%T ҤATA2lpˆo,G#8Re0YΤ t40: .a2 !URV*䩞;-lѝk.^}s+a]b9dq @ DA vjBI(l o9$QkTp#T05JK,]qsAؠBN]7Tv\0IMddv xw^zI#M0]3VSȷf:iV^wgVqfMb9XdU z@FXx7䐚 iB P "EnP3hcIਣ<#Qa+!$A+x@T|݆21cLAXa&0 HkLQ%b8bA!B9 6ė&X)!AL$ V @\B0PE\1!AA_TC4|MCBGu[et䲁d-P[#此 id `Xi؏`bX@ dB,\&+d5Z3)hBRB)<p  U^e4B-lW]"i XZSy/|%[bŤM\].'TE_rQHL=2 [s!2 @F T ъ< hd H&@Օ'hBY&X b}'\!VFlnmm>(B&z^N5ou'<qg,ur~huVJtJ wQ'.JeiD܀J5 `, $|B)+$ ڄ \a"@vKffnJDDYUa4h(zh(\~w!V^6XͨDBlhٔҩ J(&ǸPn ``ЀdA"L“Ƃ/#أs HV)1)ݙAŮ I^)iҩJFTHcx2.%A4XA$2MH2\(L iRsr@FX!JJ͈L!f2C*<%"$.qx(-!Ҕ)W`^k$ jOBfSvyRF,I Z>jRT@ ܁)ݔS>TԐ\a,4=ɑWn]M&ye:lY^P5)Z,[f LHjLUJȮOQkGvV JBN*HPD`&$BSB% TNTA[)y=|H]X]v'~-RZ֗g@`2i(pX+2Ub^e}F]&쎸ğQAH@ PH|j] DAEҝ.颃VD' APIIMIoԶ*gHF,벆glh!Jz| .m%CaE2rRoooWo!Qnh/-arněLeULG%,l(&R&х cԦ0o0 op0TRF* 1VJΡanlO.U08l0 ].VAp-0ƹqAjWίYt C(N,1ލHF)FӄS%D>Lh\X(8U T\@trZe&0EhY !pʲ"(3Ę iY!E|gUOD17N C%Si, C^ںXsz:K ;;חDI 0!D%ފ SADX.}p6p Dq{4H4tIөc1@&@׳=G[1^1ƟLmouR񎞪|qP5ŞmA#-$7<˘.,TgTO5_UQ|+xγsTܚȶ5C$J[1-0xidM2tWAQW9-|XIEt\eD]E~x٢xS7ApljHg^8uҔm 4BO'\T!%]E9H-x餎`wYbG Ɲć[-Ғf8JC(N-IE4Y VVXE5z[¢=mWXmBri9_|dr{:#Ke C.n[Bcss4+% _hU{[B֙r] ;_AKyyQ|:x{g1kRaD2H@u]G"y؉\;ہΔn7g9kV0u`ywc@Qh,>YxH6#_:7 N#iPXVL#'*|0!ÇyT *}y|7}M<h;!gW^ivR1wk-˷$r}Pۻp-l{ܛj"=}=nSZY }vU7_IG[ۮL^vBف%hx9?OW%?:IؽB=~(>+~ 4xaB * `T0d! #GAX8 8k-[JH-iI5 %Cw4&YxIsW_3|qbŁ#&Zdɓ],Z5i8Ok I6}t& 5E `"I"AxpReK@&@r'OBJL *^lZRN;3,ٲת⣆)ҵ+"U[O0{l1 m4,CVc lN #p+7:遡{i9pz {9Ji|r랂.<-KJx.d>4LA L,2d02 <M B ÎR! .(q&hMő~Kɹc ctd PSJ0RK,xB 9ĐApkJ,/uL0w5AkAӚ6%B:]6!;z ):~ Iixjq j(TB:AiɢJ!"B u 1ʐC9D^N.;ȳVr Xsz5DX&ْMV;#>=HޘH5Pʵݝ]juYG{@.$ԉ隂j1z*|>14yY27㸇lB~Bfٛ=6y;IE~m6vrrPۛ)kx}΂+UB'~s!ڙ) ;$j2m_m~^9ٽB{?~ӠDy)XMZ|qyGW^ ?Zxo+{iAHձjJ@$ˁ҃nI~RɁ$\Ysd"]C+t64E>2(wV_,iY;A!K(8&hd"S2EbiF)l1@hF!,"4' 2 *#Z*Rk`tfeab*؏ў'G/pvE/HdRzb.Ofn z`- W7f Ej |v̉*h8""<"rhDncRH* D J* Ԋx0(p$dX ^j i EЇ譜H"\G vpޜe8ΰ'O א@0H>h0pLԪ'1L"$(qN)~6ÖOkX 94cjG ~fQ8es6dF܉Dg^ÂT o+\FbjAcMP 1d,L"N.AFB{n˴p c~tf#~Be<P6im#"\pV #辶ke,"o7+=q${D v$PݶbMh\rē%|rL(64%n^  `nN)"*r+e3+7a,"31$2l:(6RrjgvKBn p°pdhqD)`"2ajhȀ Z+ pSS:DJk+ Q+d#As49#Os,W53,ђeM-&uCi*T'ik@lԌ]E6~G#Tpn+h1S;[P(NI1ԓس.R>h44Q352?IMr:nqhg6"lR`p+VkK|NR h3KN22A4H-{ Rb7lI QPĒJ,KωG>L&.7b'`M;v *l4O:^Gu@2? 4QEQ(hT%I $fb'T˂RPB8O]Vl/ A$ߐSum* _ Z VN?W ={5>XCX} O$U Y{[~ 1vr pJFGrDmqeLiN;e5dVm`WiHF24nޮ`iXa5PaװHCY%RH 3洌.*9[Iv>@ S$'P rFRv0;'#Uh,^mVMtVE(zC` n!rl2NiEAj'jjC pkL$zJ. VZT(SS[_v&jPi@O ǰj @Eeufғh!.hքbisUaG4p/(G"U[+AI?6@@vbR!+ oix 'H0EMh蔡P1O;Wh{wyi9WtWC-Dl*Ʊ> |soW~/ \r~4xx:VF"e]5""l؞۞Ls@4zj{O#,ɛ7F7}s m6is$/Ep\89]hVo&b`OԶ"ukiQ }&=989VV؈@6g`LHQD4EB]J]Y/Ȇg}1cXGe9s?A׳Td-1?Hk#rŕRۢ/k[T$vٳ冷 Q~s"Z5}eRD +a^Zڽ@nf!39Mnc߉ߥ!{>Z5,5zsTװ=S[IA-~'4[Q,Ɂ,B | C5=_yGԃŤՓ~闰 jl'#4Qs()I~ -ok6tk|r&"Fȝ Hg[Zʊ `g?b;J2s~>\p:R-[p/~쟝1i'&s|, 8B 4Aà LȡB x" &jTX"5Zl#*TشIfLN;< !L9sD;4iѥF"mz5kЪZZ,k1bǺJSʚ5 #Xڰ2ň p6޽| ؀$GNLyq9p(H ,qTPcr%Rd#@'Zd]urd L5Q}eR׌I&WYS4_'Y~  #pt@Yƀ0D!2fam&(h"Fb8ba4QD DYK=)FRR'd f.TeNlj)ߛitAsW u ^|n *haa҉Q&h&蹒> کxФPij2N"i-zEJ XF?E-H5W4CYP!eQMb*5԰.'%,n]vTnU\yrm$\poRJ@/ZB{*f.(bgW_q Ċz` %$r kԴ!*K4 ]S rfM<:K5LS'34/{/a=qs^yq8Ry]t`k\Ӊ5@ XfF Z%TZ) uQp~{Jw˻Ѫ 1M!TA'L˗[3D\*&Wft.`,ԅ$n)q$IPo[vgy՜W!dh3^&ӵPM]^T*XE$QG'IH+ op2~os%gXL CӘF3 h9.”nH+mpFB`;j." \u53Li6I!f7bw* Q#twE"E`$3g@c:1Y@p#(1H2PAQgY jZYf! %Rݎ!4y" ><!^F G`[%\9}|_8h4ҩ"% F5:9i] 'V&1taLg2&BZ:I9e '35*Rw]RUU׼N-ڪfs,OSyY5y*@XIjy!nk2NrĿ72s@Hd{IǮBL[[4T6m5lIAspI;61m-!zͮS'֒&axܦFmMKezdnmZ% &Y@/)F &5:3,f1ֺiU<-y POl2g*̠_؂kc ~F|ΰS)J(60ت0llf4 PEBY&xMIӕ/UJ @&ZB/ FA`22RǪtSNE[dyvO9Y-k |Y6ivR; Hl{m r5TIp(5, A)-I wӰl% t8I 7y&,/k tSnoQ 6Jutk%Hq' $9A z t[ntxCsvďc 'TF\F[є@QBOAHm4\''g:ɀ(rƤu9*I2pldemo9%`EN{+O.\G\u?Yk ~Mv 2nh?FA>KCvE, p%@.udKjXz|LkWtpr;=Rg|&p0%0€~sCG.栍W:[~$3&{n{k (;Iԃ 4 z9k F̔{{gTq4JT|;qҒTZ$Wr7B)T,ga}Ca"FW!YU}.RCC.hA` *]7$kAfHA=p pA{Y"9C,` 2pYa2%:6Zh}etZDl6-flІh M$L(}.//5Eg"U-\v$*K}~A*aA,q)pp'^[Sa{`8_gk  Ggfihfn3df`Ԃw3B'g%)γm1wyhOg4c-sR~BwDr\D=piCR>A#p)cKv'M`^ޡ9Ĵp^xvkeX.!-a[pU+Đ P p fx<R`D9,Жb_QrSxsrm yGX;V܇( CBiōRCx>$/hTU&}E~҉IySeSX0 ր@b9vEVhi{簜oqɀJ!*$!rMfn8lv 8ڤ'a xCIr)fC6"m1Cih`6Uhq7*C*gB~ı}Yyi[S8q ` ِ S@ `N pA?j"9ՠ% 2 z9MWnբtYI`הv-!Dl=Xrs .sD6R#NC.V$ $/7֓ÃVۈ/VAUiin^vM.0@ ɠ9p:` <fp X U`{XyQȨdqiGuN3#lH)_(>1!aUC)%U3sՙCih}%z!?:)) 7gwMkڦŠ9蠅Ж aɊ):` v"gѮb)pv ˗G 4ph`/!HT4yǩqF(*hDnA):"?s/baBZ6eIz[SJWl"|Ǿ-M")|-a-eGm(&Q> +G!G5ݻca ʡLyZ#hʪ:]ʎ]]ͺ]9;,p )pE@3v= r/܉7Va֙y+b6IInäd3ȝifŠi Ԁ @A ; ڊ3=Y]Pwc,ߦmlp=`4 GJhkɋ>l(!n)/=L'3wp ؝Ka%~@ : Fm !!kC۽-1kexl]觏 IywWCbRnҙ=#n)dߜ1y3'#6'18.rQ(Zd/C0 *& NN"bɢ>R5t׊>yToB:wwY*zЛ.nZr6jmxh7|߷9J;I=c]0?fp>aY0AkK㋟0 ҐF St~h56uA 8``@B .kB 2~(RH*LTQ9rܸdj~9SJ|TgOEh臎BB CKV[ K%8hh62a&QZ)w^iBd+3p<:IkQO6VSmUU}sTDjT!D QO,dG_aaHl]g6Jir*6l!"erխLrsCS#&UW{n -`J-ѯ^D <`8:k Fw l[, uDW!+v883BV)96y$~2U7Bh!o̞>2j+qΨ5 aL/<>J?dl 72PQ[ry<%$ژ4ʫ!̜zොcitbyX\QR`uĒX:N І0'@CZ!(ߴ$Q9wƒ#`zREm2Т``[\"а978Ҹ 8C*Bp_aVe¼flL6*CcZd@;wS"k1Iy;qpI1uǀld:2͒(h,iJi6Rx%Gr/.?h;ߡ bR>x c,fA bf;cd)1hBDh W0QQe) A I䐈;άHcj3ApkR2A*DZڹst`K[ ܈}%]N2Oa@DHNH%SB,*k4 QH%,?SR@^IV1pQ@jtܰ1׃9 rċ!C|"01DD(.'"D$X@G2=9cHzv۪ЈGА庛#AoH╤LTnwKyχ~A'+D@.p ?oi714FUp%LwS89hDH`Хs8ʣ*3-s S%xIsʠ=5Y *jI R  P1HʭitI2yz>:s C>FѨ xXC6͹kn0H0 8QN3Z6UD" ZаVKM0CKr. +~k=Ħ؈y H4 B8.hqYRܴj#'\!TBT;.>HBP뻿CFdk(0i8UFO@31H\jMFMr;P6F<v{w,yG|G|~Dž~H0@H 咱ERʼnGLLHTX[.ȓlT dIlIDɉd hMɘdɓ,ɽ0'+@20+p1@b`H H(-( +K%! ,^I$_ͺ'o!Cn#JHʼnݺqư#Cwfɒ3R[Ŕ'cʜ 溛O3dhѝ:k*]걩Cw4i¨gfخ]~V0`ϟ= s;URhåÓ_Y'<%dڐKKF z1lغ<);Y}d}_p bQbōNe6B!Yv 3@'t uSyX~]_xK4 7cH:^|Me_{MA NF ; NTQFURִFValj6̡VqMT|4iKsCQ䖋qHyk:J#K9hZd=$i v2 5l"p8ȭeNpNV=CG# n1,$8a- ]6FxV|!g:MG Gמzիm v7J&>1>\iKhW.=eMsJvTAgr5FeR`R#hɱ - Yu6$kZv"ih:%nΥŭhkq sdHmY{ A6]*?[]^QFnw1VZTz[P'umc諯'|Ј~+؛w+.V xN!{ѝ0ug9lDqi29lhxՆ7$Hj]Ʋ-~_)uxߵǹ./<B{Sw=~4ond#ACc$[/J-G1SwGUkddlǚ?a̶1pv+f&CVwsw p|e |AxP0ttt}J}!C&_v&vCvtyvCg.qeg??gU7zg{d8@ `wVtw ŷ  e$3_UjSgp!Vy  [ZFlhaGe=d=F{fm%czCDTcCkF1~Wwy_ P`{P{@3B(h+dlg ttLHxn6 Ő {'8b3j Vy`hf FCuk XWr&z(t(4WeXqwHpv=~tM8Le tpX{(r2C?hW w Q~VxK0RqŀSMQ s b4t `H eWh'^vsfLj|8_Wkrx>̈_vWyUG6{P (n&n{rpfՋGQL׎L8H.0p wj j;0SC3 b/ hcH8nh?N"E׌!)# v g&x5 pfp{#r7`;h|W 5 5)`Jא{*uoQVug)IhGfdg[w?ґ.r ty w 6KG9gVW58pwjhRYy才A (psM;RfmsgGC׳H&.FiU|2`I k'J/ZΙm5vR?y[{cJ^R^\rzjjh抛ۼETU6 pipk3Kpkj: Hٺv˳ZYBv8)NWԪIv˨Iۖ!3^@KPLukWp7a;b|UպU쒿˪ûݲy,WCm닛)bêX  [Q@\۽qJ el!,3u/jQdT ;<> |n D5{*TKlkXC©f,K8~ {g?{ǛKg f |~O@;ȅlȩ: ĐC|~2~Lɛܴ8éx+?ĥ͵< ,P5PN0ȸ5;z DE|ݒ `s nΎ%ά@~<5@M\|ˣzϡI[e8 lT|h- m֜\+ъFѭ|l*pII΅o ϞtlӇBӶ Md bZg8VT  ,a P#I Kkz xFXfȟ^}/6]Kf ӥ +5o]ӀsM 5J0\WU-mzz,d N+ -kQ2t yXB,G; کHLگ-N|hVFg#xW01<33~>[v6F^#P7H r `抺}+S_}d9DOZ4'[M}np< tQm>P;`~=$Nڼvm0QgDm2M=6␌Z٘WZ{4UVptwM?A=F ţp)٠ҹ?T?:$nzg:CףHiGrny煼ݏ^wpWaJ %^ HʾNdqQ܈ߤ9 %P%;P˭ݳ>EAPc lZ!마6(f(Ý^qV~4n>pQhyrK$yz8}@anLlЋOP @>,Ĩ[Jff`{:XvOw0~+eLT0@^]Oqo#+/3Kqmɪoо-=O.~@H#oħoX/9 $Xp9 Ͻc!{!FX:5f4Q`G]=z)Ud2!HͤY5֮'ΜA MVcIb5 QNP lB 6lčHLB-+qVeL8:b&\P3Cd1&A N~XѲ);̜I| [V ձiNvcLBJ5ZnUXaK[rڽۈӨ[y%L/NMM?Yf}Vo2>{$G:LH%cOZ鵞΋6p˭z #"@,n &r+:/h;ZŢIf*,Ƚ2lH"%\:4u$h5o 6W3 bM)"P* ʫ08-M㮼:䕾Xlѻa,ƝbÑ1'q!V+ˎD2IzԨЂ1(L,\ B$ 1#P 9,냳PB|3.FOsϾ`qJ@ SsM{)'Hӯt[n#%/ev O .K¬JUzY,4ruĸlbNQL\ VXacTYB uh%kho-mYts)eՒ]R ެj+6(T_%x [s-NTTXφu ʜ 0u{[쑡%id LFm\rYJgsYwY11l4V,Xhu xxm:`sX;=qIvN{[LmK kLs kq&nk@}v*+`, Y?طvɠ| t9-tg]w!aTv&.vko|)t oœo3o\w/YV7Ѩȑ{3Q4<=|Gtc!&3m=p{;%q Q4áe,;&49 ;h݉|\犂vPq%W #87!դq~ʸ3$Pɶ"ča,ԲLz̋%P,lg_.,hq t&TMu!,`SA h X7Be!tžP̭x4]ϴ ܈ y=bw%,d8ҁv8[O/PsmG?Dl* `7 X@BnЃX2<2T-O +EC{-<T_H]f3UՀDRFy 8\sZTСƫÉYp{HNcXͰ$)` |%VX;\x;Y Y>阷s[xjwskBjphGHbCۊ 11-r޳ ;@K`866[i+,A r(p3H⒆𹍁zQ?<3kHep??.UH/!86czYi*U#5=W \@*Ђ33TZ>(2cػfXB9=؃4#D#P 3DOZlD|D{+(eB! "?ۮVH%؊[8zP!K(;#6("0C ='(C`5\CD @HdQ;CxJI40@$HCDDE9*I'IĘ0JaM4bPPB&,3`K+;3 GRt"$@^|^F`-5UjHR;TR(RG>#`Ft TpGz`(c +h7̤4"$YS\LaLUWvuᒆj K{X~5@/ Ņ4\zG)MXcMQd݉Ԏ"O}@M6iY@a(ͪE `FHR8 DhV0)k6jveX?haSـk‰hizOŠ[{T(dܲIȞlU3;.H= Ȁ3;Ǝ3 \->|%yor޾h&n (P޵cc*UpT=Ml(&J9*HR qH[ gH_opq(+rpphZBkTm ,bp ] _aqmO ;UqpIπ `p0FR) +/K)uXu,r>]Pd.b sn n )f(<0{Tݓݭxai6M=/s(Yn'` s.?@oE0J HO4RMpQ&ǘ++-OxxF/ ]u_ڽ.v 6nvl}hW#*ͫD"ljvhUoρpS7 ɮZKVǔX,o{Vxx1uIHaW*/܊6Qbc%Ӷ? @$!%xB Jְ>8qAnEK&`ȉwot{.Ȃд4e9{r/v{m0ΚB^uV7&@?w{dpBlFڹ%h"Ƌ6r.#Ȑő,Io"AL %p2g|ͦʕ:wZ5eʄ+J(`ǖ.jRRZ(P @ժhPō(֮e\*>h.^ Mn>z0a8hܘ5|I)T6s+J@DHU>Z . $d1i NsǍXcː&Y3&gҧSg˜u0E! SRZ*WTǒ%Q>oh\]=W_7^ud 9`fiLHj -$R1v"Q=n9$aRT\!xĢA)0DZGM3p=čjt!]PTRVb{zZY6&c 8l_ J;-y ֧y;8jhˮ8b @6DvXJ0ԾErǍdRE2<6t):ķZqXV^|з}ܘWrlgb'ap0(TpBf?_E:தEvt >8Aav64cogtJ0F];j4EilzZMVvZ 9x߬^|0'x5 n*Rx@E# +.uv#Nk9H5nYPpx0qp`=Ty+R /yON񚇱mқ{w=чOʂ?5dًW @lyd}A 5(gB?/6߸*!1vF0jY|F82X~uaHX^$YaOZ(PHޯ:Cp &6^YE%Pl+zz.y_2I&1맹!ai(`b)!UH/R퐇pJٜ2\;FpV4cFp9oۛF`;i' 9iNtӞIwĹXUKn*6kz+8;b.`FA S"N63ⲡ 4.*A pm Ŝ3豵f4cӐwMc/&q' fH(: LoNK&2Ϛ֡>"k 2"mq^˞58'H+'%DI x^U؅<HB)+B*.C9]3hdA(U-QV]F蘎C5WL=QP_}_=V\K@]b<@p@ (^^hř%̵\LQ (el ꩞mU"!"$S%|+| l%(4A3T `;] V ZD "Z+Y_5!)<@gHR]!ԟFYPK4ʬ  f S$FA4@ A`A#"%\B(Sx'+6B,Ă=U($AUeNl C'z⨁b((G)2.肬-_+n+Ƣ"-Bgy/@Y_dW |E]˴%O^4FA56ZAUAp]AA"xB(c=;?@.h?nNDSA# j:$>($qMCCDJPXd*j"HN[@\}lJ&@Q_p˛d FxKcMO֙H jA$|)u6\J$QWᝓ __%`n\%~T-m=Ea&b|7GفLЅ\gj&$36d%?@Uł)4=!Ao[pg%Ze5 eEXY2/Wuj'8I~Ldy(zg2NGa f@ l A,B&B,0=UnC3'hB&8$x@ @B=Z"<XEdwE2 C(Ra׊td g@jA1&NVi%#ܠ|T=d^LLLo@H#adAA"|jl2h$L xfꚝ@ 'Ԃ&Fi=r*܉jLhh褆\Qmc,ɰH!|2&Mrd6@̶Ep  !`$`&ܣ&pBk @DAp†r+:x=| wC2ù$Z'`M@NZYM-dJ⿚jf,Yǒ^4< }@ h@$L'ܣ)<oN AxG 'Y,u+,H,P$C<8&2'2\DX4#W-5I,2G}2Kan:@ ! (])+B,`!HrjXkTAF@_⬟`:.B>~rntnfn]fPVdu®(}(|5 df4^xdT܁)4.$"!<xQ VY^5plC.;,sĝ/іoaa/,aЯzReajPXŠ܀i"*V@B$x%!A`pۀ @ %YoI|9pFh w/s~ bs5.\QLwPn-Z'Wl.o|@nD=9֧֗@ \@Q#N!1/dG6?)EGed%fd<;7tbگ9f}E0FIz^SѶo@ PooS" Lrrc4(wOT4skRt+tUnF$nd~aw<`6pkKv5 m7Ap52$P qp('H(KtnX x!ᵆt7,5fcx@h$Ũ#eigeVFd^ UrQ (B2d7ȸpwUE[DrDrhG 8 \{_<ƛ ^5ǩDĎ-%? !q&v(<)I кrR1 z{d_ctZWˤofYpJ#LQ2t͑9t={BAHCHr9B{[|v9܃G6(e}pWc GL- Ƶa<}M=]PU\x~Cȃ}kBE଺}I7wY>4  4 `D)Vxć 4lC*T0IRE 4tf „ ƒ,eIӥL}zFG(E\$J8ZuU-Y/Zٴk kVBlI{vq_2&Y}3X+@e&J4(@^ 2dR vbN& Pn*0 AÎ*1d`)ilœIںC*+O0˿Ï+!Lks9;ih!!:BZ>9lPA;:DL|EgrC N!HF`H:=i)#'Q-9h&*),KXb R001sˠ)3\M5!ܖ[t2HA pF>M Xj(RD&Yi `Kkx @º(DjLeU-I10A,4cʫsJؔ+ŠdˌYgyLi4Ӏn!qM! f`9уtzPҘ| j( r_ތKCK3ESA.TH_";&ʹ*T>> 3%Y3d͉~~0=om N:Sj4X  ygEt8}nzX;aQ=VUҐ;!coB+YWq r&Y@˩<"ݿ(tg|\DD|_Y"`~M8P45Li*x>`Y=A e 8('@6^e$ ;^ʸ=|O04 } >:*)8A#d@) R8nRlѡv’#Ccai+Yq ̃d{A 2\0Za e!㆗قxؑHA|_N1Mt"H8܆K ~5W c.]GM% SP)""FZA3P&pDXU" GF^9F$gRs:c!= icRR7(rRbŰi'Oq(Q v44ӗT0!|SZNpX)Hq`3?[BV̺9F L8ɉ9sAi_&٭pt ٓ@*h6Zr آKPM:'( J+G? ҔA0iN؝)eq&L7Ӛ^i@ͪN1ig@Poeٲ>Y+TO [ƈ\69rT[N \p%uR 6bDz. ,K+9dޣy[GEj|3@!پUhO܀`EeASnTԐ嵤N{{5Y],# rI^ 9ý-fdXJf8Z81D$%XHY#]&1р'w[5R~p=5ۜro .۟X+=7mBp^vH*k9o$ % >2*T@ݲO^ 9Ej4/Nz|@O07\ c?ȮK{/O{f8}5>KPoPI7^Lk‹Q@bu7j`FNDrN t-S )N׶C+LƒP /.n18 ІFn P P B!8DBPjqC͐2PkݾhQ¦&Hp ]9.BTB/,$qc]onGS خAP@ E- JbQjg6puqFbY٭缆&1p#Ղ\x|mx*@NovKg&G1}m :}0 O\%!p1(2rOӔO n$078,E鎢3@o*@ 1 #\o$N$g%% +֑ irA.MQ'zw6(uQ(99 o`ro*e!#_H0v'R Y"i96e*`6 Q -l\jTҐm/i6&93K# 0Qx'A (c32nqFvQ$2*26S*!'EQ'Lstir(6Ͳ֪ `-s-Q` (rS:.K*#s9ՄZ0:V:1;9tN(SBSp$3)C*_ɖLEF!"uD\Q,Q*lN7S@$H.%ofA]J+8@(Xa .3#46uB6X$ Q*(Ÿl HF>)2jN!*@0eo FG"26s`QH$ 삳4XTAM,#J'TKKTS1CLOɒ2EUT )P;(k#+]k68D0EoGRQ N CJ7 48I?Y2UXSC5K)Tkr% US0VP$r2sE'lDo)CvuhMOeiU8ȡt*(Qu?smEoKR` @[9UfĵS TAK]T]('1@T^4OV0M !NNRW 6pNj 5w8oYStV-+` dMd\meea@dfqNC)L hC`p?T*V)SӀq'vjipʴx22Tf Ȁ J\ ֖m:pem\\4oӕo[Ng Xp WI)NwpQb)n& jlrP6>k h3l=?MuY ^dvr8k{lO,\wWC+Np~V;66y3hD<LrR9X9`"5(Q wؗL"lƠh~ٶXv6RvIrVTgv ~R񝰖Ox8>ʣ(z2Ww$7FhLmXmtF1G#l}`0Q_8 n+0+vLfMoX]WmVxf^xV ?5׍"`QnEȸQ"Wq'L' 7 x9Sq Y+yZ9g9}xAo9qn)G`$Ȋh/9+D8ltT!Smfi*=9hv xqy}똗` `٘d CejT,fN!9x+Ï`5D+)xX2Ǚc$[՝5.$+%%k9[l %U]CGIqڣ5>4) RWR]Mޠ:XʷcFZ^|'#yG9myľ,#}X@nࡡ臞_)J4%nV'gGɉm#*ނ9=9VþR1<D4?P(*9C޹`9lr빪`>I_O6U ???o# H>:1^k. )?qaQSY`>N1 uiq3#`58dP=s=B%j1 Nhb&Ĉv*A)Xj:v'#ȑ$K,ذ@e-Ԁ6A"DHleǒ -z`%FPÉ-V4+s"C28J+[9S&6ϬEpF꺆S*Rpxp*z ).\> ~AcBd@hg=pV6`@Kݕk>Fmv'HS oK)S1A 2t/Q`dSNETW^块^Xm_|!T_bXc7e_`\ogWsi e5hD݅>RVw]6搉fH"%hΌR\4-sA9U^X\pk64(H9%VW yg'_[`[l 8~5] &Wl)IgYl6ala&uf^^ǁR|ЈQAAmyQidNq7cD14դ DIʺdNzfH+VVd ZpE_}j+_pVfF 4rJٖˊ&lNbɥ-rLc;D[QN@*W$Ŕ>ծ#e(I}k ] M3\Bxae"]( r&=(ZIx{nء/B5Y+鉈\ux0ML\JWЭrPFZg(YwJ=Q8rNF?nc9NN_ro`dzmxrZ]ނu[뎅.MQ%Ab1dMRA8ߑM# !\Ct67hFFň&tC??i`d^@ oHdCugE!HJʈ|w Ē./N"@fxޘ060;H!@B~yH|$s[IK$#+P̅hb=9Ӏ†7xic2Ř)?|'Yv z;$y` `x<v0,R *G"ɔ9(,CKzzv$b "Bt0ĕIh[ڢkid^bۉi=v3Z68W%@Wb PaH!/q?Tz\oڡ=/2i-[đ$0&6-I99lBp?.idB.; 7ZEf@ Y ;yK 0fO G۔W1m&eoy$׫ab2c%¬~Lעjb30{%rXur@s7Ls&7:Jˣ7*Qtq`rp ;I+b5'/RU3zOIIGV׬HUkK3%V8{{ 2 A v$KvZs00}'4˹+]:I;. MIq e)Z b'FN)凪 Kg^AYP*Кmtq ծ'JH7Q aȸ[gb9Ӝ i@ASd_%oxsjF@=.J..0㺯x0 viNfWU9yË>9+kuɫaדt#RIJ79bikXK>/o1Klj̢QuV|?LMFy:JsU `(HU0 Gz pzvfլmfVDEg*"hIoE7Hai0%1is4Wg Q 95J܁ yiW7g^)"! ( န@ )pZY|}l!\yl!/v3V$ ܫXɗ -#]\eX?4oEDǢ&н:39Lj ewQ0 Xɼ̖`AN9ҽ;7ی2l]Åˮ9J"{cI3*QM Kŝ)5(P7ྫmpa@F,}ib=d.˹'.`P Oy{@ ͌)wPZ@LJ]U bدȉ5^u!=s̮Igf{7CMkȺW@k\-36p햲ߌ۬ '}0W\{=˥gxUip BM" P `Ni ? []?$c8bm*|ImbFz>lC97$@67w8P>J?0*ddz00A67 Al"  `̴EU89>ݍX:J*!y,_Igng26zĂ"+]`إ3XEn9> !^DcEXK!+6wT~ZUU َ# bPT =>U)K~ʕz")w:Riܣk(^oIG^%p:>.K˒Q4 =+MU ٞ N Zӌ`JFS4_MC( R>_ -W| \J?g?YyL }^{r?+Δc!u1:HƤ4'=< L x!$3_-N p ,;ٓM #.@쎚 0r-L3OBߘ6S+H&=p.<96KE^73joupx+ѩ#IwiҠ+غn"M ^P{)x@[A蔞*ZXB@7n`1B >),^L11C 00# #jC5TPL K|p΁=}PCA*~p΅M l3g  tjT~A xYK-P.s^quh*<0S 'RAt"-). ɀJ*X:sL(JĄtzBo1K SAJ…8LBpó(,T@* ^ hl1Z.`kBO)wӭGeeODbJ߀c1R9Qڊ*V]!k3L /\Ad!UB4чܒmaz .Xӷ^@SXp*TG1Bn#5k=vD /`$uXd1[6wd>jw` aCA>*Q8k@*;  48C44 R:T2(bQ\&jBli-~Zhi8cIѥfߥNe38`oz]yGۻ6ɣ:9jOR!-!#Ys7%as<6 LVJFLRh%0C D1FkBitիq#JIzK~M SFK  +Cz`sJNP_Ap WNYn<,HGtӛ YE5PksEʂ si>Yoj;hAB"#Ԓ[ϱS'}VҝI})~x ]rP7UmM+{ Ċ|}Ӥ>f+N/fc:Ssֽ^ nj&]" g4CM:htbN poC1ء$41WS X(בa2с';|kF ]xt.x[Xe?uJȅ5,1ie-~Y,Q>*ϥfi OHHݠ-𠛰nGKQ"@>6l#d# [?'y *Q^XeNضB"!xjp9H2zcDjE:3C ܫ8)* @1 廯SXA ;>P&*3tl*c)kz[X1؂-F@B^mQhmk)*ЀI+5*+i\!'3|%$St>/>,ˁH'sB\iDj8H$Э&-';0zFt*G?yhcfP9p*?̓GyU/N$`.;$DUP ixeֈtPD j]x^_yU; )@rC p:}bJ .~PfΔcWtӖhgBߘyo0Fځ<uȩV#FQ(ԉ~&7-LzRpō>64udA{NPRﰳMc ijs& kiOe6GurXG"ҘPmH 뉴/$(f0(a 6pp򸐫Rj*b*kg*VjI]WEk<?7%tjr]~i~6+bbd4ua@-P8.c?|0D|`CF[PZcMNnv;%Ys'NSF4ǡNiZ#\.`/m%5 1Rߚl|5C)$ݒL3-d`,<و=RkQI5Tn9stR2턭u鄭o 9ÞN]?a+e؉\n ;p:6ƻ 2v]Eˌ ,9~끒B[~(>&bӕ{ZqlG+O_;H+/Ylhw<)O}Hg 9Oo`E$~0=EU/qζq0AkRؔ bPG3F?ac14%sP)0it:D(8A nKޘ06 y( r(T!:F̋hH)]!^s_7w΅*Ք&"+eCh% GFoPv.be '^< amظF.cU4}m I漽$ޢ88}qP4)kPb1H_zCp$! ?q3~\/AF _I? ή}2J$OISj]Lap9P-d1&2f:'PJĦ69ipd27ɉTd+56(N% } 4p~/meΊV[*6je`p#H_IӤ^f6W5rqb9]$A RyQ\+W`~r%58vx:n#:F(q05zulg;EV"GӺVj|nwi38l)W#V򯀵ijWKb|b%越RoNy"qnn4`A O}^3 [ٞp+*ݲQT pqW͔Mi+7غְD,~E>q 5+Tzox7Vμ,zl$gq<|۲vs߀ <կ V0E;YOt;eb8@"=ݫ1Rv/͙b',2!cEk~Mouxr5Z8Y2ys?CY0% 6aٰ&&nB|l4sh1[Α'V#cmnƛQ_VJOɇ4_n5'b Cϡ>j(g񓍫&wl" qɇUEd>g:rh,6܎Unjhߴծ1IǸnDvF< c۠\9!6gSi'9'ZlkV?phsw1M4W^ݧVFQ Frа4rKpG/}5 _5 |p _  /.Pg"BC+ ^X<,].1˜494_~p⃞頷1u;΋<.^ C@fzwC'x=6Fz֋]x#nf;Tkk%j땡!`ڇi?qKF1O[P~(ʼn}WH-jy_|[{m\~> a{8vsfngqqC7~C${8\cTng|Ww|0}@xw}'  (}vU}mGtgtp~6hW :~z7B/G_;6hpwl ; P{g%[dn{}}xǁPC%tJr}wW MHpXxs'tw2 w~;y` u%?::h [M؄9si|xnHltօw(LJP UjtGo 6 qx}sg2ׇw ~x~鷃: X@(( 5R:h(J8v{M:s9r*^#(N&o>vȋՐȎ Hvxxl{È~~@50Ōv g Ċ8INd"iӏ`sXn{wMhhphxCHw(y0{ĸ~ƈ Y icc ^6H},FyclcwX5h%74+)jհlɖ~M'Cs{P4Ix| 6IP~`xe HBYy hyXG My T~!xvpُuA'fmiHKKN֖Y  ؀t x7Ho&Gy퓘 i  0 Ps`xX} {4HDM}dojeh(Dpmwq)yY X}h^`f5 K  I؉ڹA 9IZמbbݦ|ሟǟjr$lCg9P ڠ#4'B {)Ex0pyF2 4}ɜ;*>@hB:*GM2YWɗnXl'kxiW~"ٜZژi cej @yqʨsn6u9|ڧ:tF)stPe}TꈾH١J9EPҺ*7 }xNgS!9{W% ږyzyg:gMIz) Ùm霒d Y*ڥice*Uc Щ 9_ڭs(4 &a j*:S"orr Hޚȓ N6)Ω9 {Q [Q0[v(:{`N9fNؤ!+{Zw)gw+D-[yj(y[:kaX5ٗh&zE\IMO046^G ;ntZAb{)r{4(빪fgu92pn*0-{& i\]qxtڨ縹{F9岬46P?WR˟Dvr xaDdqwMZUɷ{Y-˴M{WPW0( HB{E9v[fMY J#qB{(ˣK*HZJh+ykS k(ěQT@F;}Ct7[;i{lKd )¶Ac/Drpd8ML℡<졚 {* DLE|ǫ{ktQxŦ+YԽRЧ`(ɾ,Gelv[ܛgu8Kv5zov,ŻIH-#J0Ă,Ȅ P0;”xZXܶXyɲY{\'Jd{jh~qruH7-@˽@u˄||IxΫdLj O}*`-ݞ`||o~gHި @J`MǻU{xF>Vܑɜ۟ rs? lqժn.j3W @ i]}a0#%%p+>{-̴04j1Yv8'VG% ݣk+*;ݫnrIhMmPޏSN z>Pc]%>e=*N(`M-s~?Wy~|&7LZl(n͚#^,)N۵~yM\ <}TMq%,JVb Εw$hFsU~ i]^^?~,c0v<}wzKm?Xݎzʣ{x^Eչ#t>hO؀Ni8Prю-J|ȱz+ί=|*-mY}H;/O~0 =?7<:k+>Э~.TTԝr \ox] XuD9 i kpn.#NrLZ:ˇHG>yBoܒ?.~>Ov^x0 æoώ?,M+K?JOhC9 $Xp9 ;w%NWqň &GI)K֮ͤYZ̛2m֌gdʄ MVh1f-մV+SU4 W4l AY %Td q^R׮FfѺ_iΟ=id,P1R8Yb*IF6p$g͡Scͥϝ5̠C=t,ON7Zvm6lg9p(q%Nʥbzo=wTNS1fIrĊgDsgۃ_}cm@^{-hKƯ[n7* n8⸂ [碛N.#Y-M;=l>1y1!/S2! #&]Sp6t } J89DCb红*#Z;JAA̵~(B;yq"z/>4Paɵ't2J))^RB޺K0SU22 4cs7ߴk9Ƙ@=D[TFy|#'1&9QSd'O?-&TZp7 MEuݯXj̴[M7kk 98q;uE kۗ;%mֲg#(iZkm5} wd5)R2vu8xr@ &՗_9s+&X10ԩ Ԥ&vGFBGڌ𥳐ce#*ܿL-e.-dĐ !k4`t_\ģ]_%6heitG)ZV뭑llF^+OmF08b1e+z=T%>]iz`X۶@NoH"|{/&ɶ,M %n pmv6jnֽ_] p3,1 8CMzFL zd{A)0 "Spъ@u+kӾ}%߼@$(A\ _p 3"#% t`d LjQ 0 rO9|2! ѷ2ⴋnPؖ %6172/\'NjN 'Et/H #8-rq>Y8F<Lc`hpjYʤ!,Yڧ!X t…+AF׿4;$V<犞()WQ^`TY,~#kXQm@/3[>`}tܺ2Ґ9qK7mw-$[fHPX=0H4)h;о%{.ڭDD -̣X:K){?i ӫ?TqX, I@BCZ=[*6+ꨋDC)1R (I,7> .@ċ;3:* -|,$(h3 5\? K!8"! @`>=6S-\Ђ; 3;ë$kӃ048t/)p';ꦁpA?[kjX'Pĕ L!غ(hL=@`6z$L:H9 H-E0;@Kb9Ynʯ4;KЃ2@7p.>&`kF;ZX[5Fqܑ5pDQ 8s̵qQGDV88ȧ3ث%#fvĀBDxD@h fdY\Ⱥ03TYH`Al9 TuC0EK>H#=b`>zfJyD(9!=w…JLx ;3+zDBJ:Aȯ(EtHDŊB 0^tKR Lأ pth!fA( ĜL̡LΔ}Lsx!;.ѧS{,,63q,H$MM-4B눍FP㬹$_RLp4(X#*@YNiis0%AĜ 4*4 1b>ӡVOOZδJ)H ,xePM͙IlzUfPLJp:ł% H1оcL̑Q0œZO| ,ԩX3~j<*J+G|{yM0]H1K 5?E̢5E |SI4@#X%@P_Jp7X!DLk}{Vh]:B-Фl<3Lou4RR%-Wq8D3)a1Uf̂M1K=ؕhV\uf(,#HjМ<5WE4Y FeYG#җ0>cM~fTxmbLE;סǢMkK&PP,UƛZ>(Z%J(=ZFe=ۍGuh-}A[MY]ۦL76J.\M:*ĥ3=;, (,MU}սmMeڧZu@ Zf I(7S#0;>=[Z@]1&:sn=^( \^R1Y^93Ջ!Y*]RMx?ԏ}߯߀]=#_'^*J? hMx䥍_1 OcR~u%c5[HA=3hC#.Zl \$8̈[h2f8$LA^FF ifƅPcX:yz"B\^D#Uɽ#+s.ޫwg_-0DPi[PIHBwd1(A0)S)@+ [Q}vXn`쐖Ik γ%Zf#vF=J'ZaXBE~Hz 5jfa֡h?͝mF3KjinUO;ܻ& !xn`!C@'`<[%ql:'YTe"@p#`ȖiG!P#D m&=KmR d< B XEU x1"΂"{>ihOH)@f н 8vK'H]-]q4Sno6Əqa6Rle`ldfb loJDqJl֣VWMMwZꙉ GߢEZ$`Uu ݇UnBB2H Ƚܳpރi O(?rO rn$&c^ )(G b~=] ,B?/QpWPM:\1;9Piw(GÙʀ _U<*xHR{glD_6ݿ> ~Lt>%?_T/x^u+?R rtYw=}ܿqnsJ^'O=S}ʴhu<smx) v p@p ?ˀ 0`.%cw szN7yt 6fP7qApㅂ7iVX [%wRXY{}QSy'sf|52`H1y@ PPv{Zi(|zzz۷w8Ɣ({FxʮR%!0g=c|?=zR{ ȫ OДMM'?_.|}G ?6 " !KI+w!Ĉ͙h"Ō5>#HF,i2ƒVl%ʘѬikŊ%Y_x+Z4cJb5) RJ PA 4pAJ @C(X@6-$na $h\=` 7xP%>p1$H8d Р4 &;@P@lParw3p҆_HxČe2o$_R䕋sC5:G,%:ÒN){4NjݺC@@YS jP Cԗ.j CM1e0D! cJ6+Nŀ Ȁ=%g~ha.sVѬt W®>WqfAePH8.* B2hOXO ]&3CX0%pX88 :XUybnUwl9ins!pQXfRЄumdZc9ڮg]2# dg@ 0JEiZʙLRN! Q&I$ f @`9} +wHyTN'YҲ`ү$wY"FR* 3/g^/Ҭ5s!x͏#^@/K12,g*rӓ醴+Da,zȇDt2Jaf3;hDZ?iix(*Cъ^t$R"d`B,."%_((nC74'Ӆ'3U-֢-VWi.LL5\2<.cya!cQQY}eӾ4Yb0 VD7bg}b$St\9[ h;Pg4LBb=n#L$$%-Š) @<(,^-bEb,FrdGGdHqGa0n!1B($Y3ŬddOf\6:=@ GS:  Kbdӫ9ЁU<^VADAn `AAPA".Lم+DD' hB#yF &EPFփEu$mb'cczd$ _قeFBLN2n&gg]Y< E`W-O،# ͐8!(f@@W<"$~p&Anr&H \vBS\'/ d7lN╝ gTAf5UF̕afb'HާmT~2 ja #^Zf"hYa:&h4.h}5$}8A@~\n\bFOTfE|m4;=Ҋh ggdgH #D)L6LC3+%"$?B$܁n[5`dt]{*az$hqD2)y)g\8ʫ@ņ@fHk8 @ ,B),,v;&hB)L AA@ |4enldžȖN /ʾe(}Eά3Ip~lZKZ c)˃kڣfe >-V@l##&B,&)B)<<>@ ԭrB-{Y,ڝr2`ICe6ƍO\,JcV 2ȃ8Ǽͅ(`[<j HH& e$<'Ă)h#HAjZ X,0N= \Lti:5DCH *LWtB0EeI".ۼګy:LAf @ (ZbB))*FUl#Ԃ ׃ 0 ǰ0HԄ 6pWⵂ*X q lV 3""y1M\K~ d@$) hNi$!|'.+`ۊ)d m#9J C!g{M, GC4I%[2`2Yoy\'r03Kl +KT2FژY ZLN$̈<jo./!A@# A D؃ c !Z77/83$1%ߚX³'L`4lƋ_@ K$ARp$ h@T|)`F/ + DAC6]r * +8y4%:SE㦯ʝa uud!mNYSP*m82ʢMV&gOjWnݸԀqc õ]6tLKo^#Guq0w`aWNsHP;EdCt2 7ɫchj6ЋMqH |;EUÈB'#cj- 55H]7~up +~rGï,9s#6nZ4N<ҊP"ROh!zY.~l-lWmnq'€׵F|up7;xߧ԰8W0 Nmt74i&`H&b6ќ9dٜbMI \(#<"0Lm5kmA'5uo+8UiWd W94XxdaOyۚ1VPel2 tr\q(mW7f iAiWkTm N3MlFs@|]/8p7OU:(C7(cWw;ǘw X#ITbx!rW pnkqnHoH;/:J$:"gDLM@`17S4^o9"XtxKU kc%ezQ*:rނY,Y:It;u=𺌺( C6x_1bFDNWux 2ɘ8Es[ɟ|6mVye 2?O7×VngCW3FP|ŋ #Iz:W˧Wj/ٳaÆZoݼ{lB ,0?4PKB,L 1 Nb7(zG[Gr9QJCcaFch<1q*!\UnTࠂ,ⵁO* PsN-@ANf!gB Q#$ÂAHhDKEi]2W'hfOm%J l ʂֆ}t/a+$,Ͼ2Ygoh 4݅0\O&$P;( B.A4^vIbݠ_%q6Rz1^|y}UjGQW=8,RhXK8Y(k㍻ , #Y5<9sKTudD(C؀$-ӵFZizi_S}U^} :.x|J&V+,kob߮0ˋ;jHS~H {N;5?-PB#&8qC3PH/?͓nk:Л哩TSȕȎhJY c˚+`ML`b׻΁m j0 )[TD"$!,<̗:q)>hGKZhOJHn&5JҔK%+ EWr%j [,X ) fbLdJ[uhGc3d%dجAHoj b ar>æjvIE MS!r!Q9q9Soʨrr_tPf1Á8 P\ycu ,;%,r^0G$d6ޜx 2@-AZҢsݬښېI _)Vu2ASfk_c ޞjMŤG))if M +q3[1mn2)]RTx~%砰$7I +so~+@hdՁ"_ڢz`풢c(HM@\8͋zz"vfmW'Oi0&/C+w'= 2T1!_'QbH(dV*9_p2m`MVZ0F7*51ta2sgn Ҟ%.piG3Wd9q d <*| T`F;ڧ[!KIW_˝D'4j KI.*H\{U W8k4k=xLzgZSn]&]rFK0msئo3cv찡@(4O$m-4x?Ĩxm4z#U8 _؍K WםHMj ?pv u24Y.ɯ/pŦy8y{9٭[Ft?wүզxwT]-Z]Vrd?^'b[ӺBdQxwJ2XCS5u(5}FGz\[=$pk |`HpI\o > >3yXY.`bnox L ~VrlN` AT{"pi,)osb$FMBO:uvoJ ʏ8w cJl=pp woUiO` oC0PmJFi!7RPFLu%h+dc׸C,/Y* #%# OJ2nZ|, XI ̞/"h'-'/fP]npIoM.HMD*Fq>lK"=Ԧ̄<=֮WoߑFo@ lϻVqܶ^&&g1 txq8|qHJJ (0lMlq #f%FȽP׎EawEp2A1۔ Da4!%D˵h*i1!Ͱ F%q""Q-#c$/,%xѯO/.L2bx͐, a(HQd 2gDp%*j́!~t%,J" +,0I IR-KU`n 0ޒ8ҽN1<쏸0.P ?3nOX*(V`y 4 8Cĵn*C0Ӓ [K5[5I,asjunCQs8[8vDor@$kBYbpI*J;.1@pۺ ! }4T>!S&5p,IaP+2.ԏJQ./QdDO$դBh vLK M#mֱ e/DÌ O(E_Q[5SR-U* 46\ϕbG?l,<]QBo\v@4`"8_}Dng=˚ `DNGGNF %b3Fc-vRZsZu݌'uLv5-t$remI.^S;Off.hY`hЌgN]mUFĔŏs`MK.0`sZ!v2!-to "Ol!mum%sV|nS jv.2-TrLV-,P6,)Hʹ8HČMiζӁI<.I DC~oV2η eZGM+K3VmoWjx"}6_ |MT167yUJ.oo798U%8<,qMb?:MŒ|A.3Ya{ """|˪` a[\7[AQSHx!nXa X_#aVox$%BpVz3.O;-J/S{ ]Cvà 3(MN p ۜm\  fC6vvɨjW8o|8qwq߲aXkS!4?` xؘ-ΎKQ0M۬r+vbY;ar8#3F`|@P]]0M''] Cgn?ITs;-ыqBR$@_.x8c'M^?̎+ )㚋Gbc{`S2(v "(Yܰ~mVE9ۙ9=-zIU-9SKKRAxqq[UE0ʢ{iGL8/W*iWtik2遼@#i< GHSZ[zxuNgxAuxN$'TJr8P-T_K W$7j-71ac. qϒ5tSNیb+ 8[LS%DvRS躜9$]{k6 vە;pu88/b @pu'Th}FSy+{7UQ3jnNrk .APk( ` }ڭڃ<4rCҙ\򞡮qW֓<`Թ&27wS=;v`@߱Yߧ7GŌ.Ì(W"1<{+ɀ-W!=r[ƨ!Gm' eH2@ǯ6Kt;w>5ͻ.=_/%|XMaC`ՆVWמ!b xHc =\ȍNW^jTauM\-3xGn!CX$5gv>8lč)ף֎6]j|Y, "uuվ o+8q p o { ChY1b„Q3i$K6;vLU|  <"6lx`@@/=4RCxJZ!*J|P"X_˂ ֱbU+[_7| "ּyz ֯4Tmņ j5 K #jxN1b>8ɢ3cZY(1 g y#Z¢uV,G#KJ;reKC!<{Хۻ/4fQ@!k d5 X\ŁX7xd_o!WWwZvXbT-Va H~ )#ޑsR*W問˜E\@ong#dmtT:խN{./ H-&w|r'Ex9wK-Fy2QBU'28VųM !hE|߯WL #a.,I#rI "Os=wn)t\ɵM&?yY8A wsځxev "τtQGH:)1,:Mtø* b$t{0;8_1 W#Eqb@q:1# J|**gek[#˸3mR{-V{T.9#;҅1>τJ~4PU( 5*BXB{@$Q71)r'V"˫ds-93pgJ0sF09tj\wu,cICoTcGmk8 \\aLJ*3ĉ 'Tħ iS|Aտ}o F5QW> & T)c.yֳ(GI'ƤXͭ (+m3 N'wRDE-uxL8>[*U$"2SK̓ BF͒7 YʆXyD`i&:ë U`&0iBX,,e n(x,87ӳeמ* 0 K3; º*PW JTm/O[cF :!pŵYVx`^2|u9n= hΥO @Ÿ0.;e껸`_L0gha 75ŶpCX J"Bim*T<8+/.e,bng׽!(XL1I4n2mELV J,@A@ a92Բ/( b9iWɴa*4Kx!nͰYHC$0W]9BQNqkVOT3 ů0hTqt6Bx]]ko -\W^i BJvDt'gD#`'M9Sp ٍoG+k+÷Vm*$ iN}歟w+'ba‚ ?\] AoE[)cN֍)W!vh2 .T%4g|oPNsn&JB۹h|*n<4w+Ҙ 7/m9@:t`4bH@|#<NY֏*{i5`u!d̩ԒJ/ mOKbK,2^Yܳڱpu|t!o d n6x&"5W9tk ,S (zbA{%`j7vz>oSM~!9^ص- $fvLzT:!kv3|w<`wI=@}!$F2"x}}.R^x9fWg-5K$cSq=pXg{Za^w/7YzG~!$BdxzW4fXT#TL`͵-")dtH%P*p#!1l=0 1HmP7Hg9`p@3nR?pU%Wtxa-cnRPU؆Y.k7_< $MTsAj@5uXzt^hV"y؇|`d*"P4ffI5P \Jax 2Cx0_'n:gCKU@WTXHwNXi9?@_[Ad/$ r$Ai6Qϸkiׄ)Af``3Ily(x?Ѓc7bRdxcRh99-ic$W.%HTX5EUoucG {·"Z5APkdg@:k'MYfpVufA=989AɎBp21z`gMiւSЛJh l8R){hcb>:qK<];A .uw4(Z&[pwaL/H³w?`s\LKV|$`p M J[>feV#gqjl ק7gZӃ8-n"?ׂ]@S9 !Dj*!B瑎eS/Ghy ҤZ*%;l)Z Q`@՚` `HM` ] gM qOT0]׍0ݑ=]9_-]'15b;Ҽ.:̱7hYs\`Y6M;->`@Y-@t ĝԖM$Ykń.o/#=4.WGh9QCQܨE9(Rv׉M>35:M5N4RZa!,WE mDjp `~c \PjD͖{m6p}E܆m4W4WsE;i$ $*v+x+I4HK-pNA!/EsCfZ,1D[.c͌RK^ Ȧn σ#N8\23 F|A"袎"(D)QF*jL4)QH#v#*70I@OQiAJ?KU10v&H  'I;x0Ó`]r/FiS,RxkMv͘]&T-)@+]7LHzc3{(xسtzK=2 @)T0ɼܰOA]#{7bUBT#9 cޑZ`= BT+|ia1;*godM#ws}m]%[ AȴF7uhNn.y8^ &1}zʁ@ Bz;\b hD7Q}-](,cEA&p)沉q LV'(A 2;p (TLJ`49j.)c2H'0RNu."м!#%, y*[cT QB̐*{ _+MSm~($D5A pm@WNy}K٢̽4841'!E(VgA GY4HxћoʃD6fYnpkC R1vjl| ˊ97 b8/VκN`hT9qF54 `R\U5֏N(\F0< ܂Y #"k-qV|is,Ę!e(y51AKXٖv]Rm"kÙF┏ 'ΏwÂ6]WC$U&&+?LFԇP;qh[]w- Mh_1d\ONe!XKTqu6-I~@WTE#δPnwd&vSd'H_y@c*S:cKnnP.ȸ! n K-ЂJYkNxH.!X2!c&㬛8 7kv Z;#<[ K? X I<Êq0ʹ"X @#3ԣU h:Y+ FRŘB(9 ;(';x[YhDxdAACA &-c,(`r 9  Y* '+,5=TQ74b=:CC偺= kxayG`  Ad N@1;eH|zsKDM2?5#E14"2EIXX9_EбcH0::fd&Ȃ"q {$R${z[sLFXGvAL"{B0$Dz,YE]cC#ŊӢm{Uђy :/ ɑ4ICSyziv`k%乡$;dHL[Dž !yD̏˼1Q"S㬥 'GK" tCh@90flD+ye'Qj0j[`eHeHLG4&aLǤ6ݼYZO𑗼aJMw2MH$B@bt0B3NIƅXYØ'  ĠvLiHiZlȆePQI$l!ȱA{ܛXJLQhJlcŪʱ<"Z̼5r@&!N 9pɏuSґPC=3S1 ةĭj8 wCthAe@ `Q@ooTHC̘,RN$E/I%*-aEϰ&%C'B$SZ S P498T0 T7;Q<\yޱtXlA@ %̆d[L G{!ḶT;;Q%Y&%6M]mU~'|@l@E3 N7`֪c=V SV QVSSc8jid j9V3pl *NQdx+vT`t% UUHFUP[be\\aV0cLp@c3_'8廅i~ukڅ\P̃*ӑ?I?CK N^I/³EJF#0d@?HeO; + ^фV`e\I3e;A KPP^K0A3 MH$mZhUNHb;"1fj t19{o"JȚ&nZiu?;K&SYydc HFad%`&*ph* @'.p1 2*1Ah[fV0;"K`eQ(fNVc-HPHD}\nueM@XgݦgMZQ?cC9s[ޕ#y _]!~fhôE: d^뵖޸&Y1O`O 1x,Ih~ZV<`DZy@ mH_P@9=m=.@V06׆ֶَؖmצnخm66mf~NN[sF!nmy 8hԂ+13$ 91H3++J`0! ,^I$\Ӻ[ȰÇ IHQ"3^tحǏ =Bt䶓(SIˎJ~d溅l\ǮOvݶلOQFS8k9Ugǖ†VYb5+v[3f~oT4%ǯ߿ L/…ѢD 5Jb˘3gpϠAW*Dx e>[GMvmsW3sOw>jm lV v F[g #oc&/Sܮ{ ݓj͢&O~4z85ѴP6&܁mPE>eyӠגrkQksE@^1 ghiG"G}͋0#y.X7O}͇OTE!QĽGF ۊif%7@(aItaJi\tD؍ED&Z{\({R#escM-DSo$=eQF^u#`9}s4(D QEYY<(!zyɒKij~`6'IUff`F(3>b*n5z~g6Tkʘ;v71*N0CNv%gO^Z`}U7\O랩71V#!6pz_JuZkc<$lr}z^Xs􉞢+on䞫nh:]sBfwko_Tiv(OȋW-cܦ '. loZɠt z6g(ԥm\[]]qZ&r<-7w˫2x5W͏]9̌V˾FysHx Lo-;e_nNz>nݑ7[Gk=촶5ӾvrȆ>m|7uzئ{̰{=hvgl?LW+i{|S`7|WiWe|K|:`-ULjf}Z}u-Fwxve uyS~p|~CC2F Ā 0H P tzbM4/ W eC r怺{:H&EFmƷU J\>Rw@03WF a_k`(~r{ vx-P 38Ї5hhad@ @3$.BC'C&[veEqaVx'E{{W߷pn|Fh؋v؇0}X ~lGJ 번AOՃwOJyp}Zad]I7tȀIwpS馄h@g&ȁH /}苾(H6 vc4v\96P m Pt7qxd3ǀݷMXEaLajWXIv 8tF Gg 'gNX%Ekzy ~Hh0oLj11x -nEJg.fR瑫WlDFh\C,ubpMtjpWȄhx  ]X|>@9~ݣ~xII PizS Հ'E֘iYia4$ r֓϶)XlCKVgK\t[6 ȉQa 'bIChU9Yh @2A2:`9t  bp|v)n\l iy؏xEYGIYłzT 񙔷@ 0 y)QyzAvР=yXً KSMi]%_V>ʂ&Yxx'FvVVh5C J 9nÃAPU0ТQx Qhbii\G{n9bG[3]EOe{9BDs0$EC ڋSʀ$k~ Jp` d i  dnZUq0w jm8zmTzO&{{DW(?%`_ ~*r֬fV:Cp:a @ Ȕ :%zzcrJꋺ_JzhאݓAU WL| ځ;)fE~`3d ʮZeZAگ9 vʠ*ʭziźqe}DR@e\mͷCCH$ښ iGyd2JpU}d .aꪱ#`zʯ%P?Z1F&x^;= BAυϽlxn`H`B DJ蝗ξYy Ob.PHkic<S^ECN&gþxs,ýڍh HZ[`nOd~z^v Na,p^'QDtN"w,6_8Srop`);nOoe~ڨm\E>Kn}|:jK'zO}]-N;Vj]V/u{eUtUNDn躽r>hn_1ӮbȁyCkP HLR+U.Nn%S4(lEk!E$YdIsTdْ91ͤYwܙO>]Y͚FSTjiS0YvjUXF +ƪ"=i YheCZd0]x.-ZjʔD5F \p 7A16L|p"Aiԉ/m~T1Zر4*R8u hQeVj[RǛ:lȪ\^kX&X10r}w/_y 6 (Nqd6hAE bhdZnA#>$벳6VRf2pCͱD"I+FrEК;⒫ӫ/SBI3𫌃T!@% 4|0B0#P-eCN;'6GLq<.`qg;0bKǸx!<k!40$Tr$l2PB '4* mxQ0]SŒʌ D D7s)NOkqиv+kEY1B|H%R ?,S,г@[\ oUWt| ĜtN^5WXcF{HGguqjQ#>%AV2q,P v2աR^0-J-.%Z&tMT:njad]lXF Wd(&b2VmHoR' 2Jc Ji"XՊg ס,ͽp|AakZ벚*jŸ/VQ;I#2۠hݖxfU#kN&8=oG`9ǣz,W#4CE[|%[ &M\ (d7he=wx*]QWsC3o'= EVlPxfx݂{\I}|$#Ӥ?G nx( -^\C݄786nҼ T^) fPN]%^#^ Oh>2.X12 Y-0n׿BML) T4"hV0[dI0h.atMߊ dx ҘƏ:ʑ&YAG$0IL0D:Q dd#*B2pTZ%}z 'EHD򌊙)G6p~xv~%0 ~)ZĖzĥ.)DJUXf44Q5y$EÙʋD)0GTIH5xx1Z{>73@x VvV+lGYNqLnm idvDe/l]Ej?^6"Li޺c©fZ>u1\@mu+ d'ȑBNt /yWb6%jj+6Q1lv^w/{{ Ib k[&-وm+P**E\ʣ;e˨>uF(\1v/42 ,Of%, [V!~-RWcIz7Jضm Q_'G8n# 2*W94r1 v#GptT) k4E ~2soJ| _Đ1ȁȸ4VHٹiqw?J 0 0Y4;<9%Yi>MSҀkPA۲0Y*kX,aC]+&%$ u?),Oi`c1r@  ;%4 D ,0/*p.@Xc  33{ 2ܨEH3(k *U9SFUHf| L#d ֹyA>n#TItsm ?mRQ!Q rnoL˸LsEW1xkQ}3Uz*IX4#u;P čDuX6 Ӊ2% ja$Ɏ= MIW"Q+5ЏH ?]T?NpT+X(FʞXu=M@TR,̖ C! Q\G\s¼S כE,5BS-KɍuXfRpBH#-R;[= 5U mEZ]%/]xgm1-:%5Q^Ur!&i(sT*} h^OBDz3KĽYL$Uu؆%<)RFpDκ_P\6}H_گ `%`A\d`z@'% >^@`E͔~=fLrγm60牓Tu8fJ(=8%@1cx3$fS%.xH3_(F)X {y>,d0pԾޕV e3sCNsLe8\GUK>Y-cSRd+/ ȟ`:+ߦJVGvHjȇh%PQ6a5e ]g]g}XU`8ƅP+t)Q)wU.HHQFtD<>f[a'`f-<6jiXfHf`P0=Ѓ=:8,#@ Hj(e*UvdpU Ɖgaf|犩:bch^@sry[2.=Aj֝<'NBKrDHav-D@iX镖[PسIR0Si#(ՠMc3Mo0ΜkꊱmgӮ&Nt+Hnl۷fI`~``zk^cBcXwXicV=;81()'i#,S߂)om7 bnJL>  Vn^鴩xLm}7D WG,O R,q86;0 Ed ز-#S<\(Xo6DPb%"k&h ?\ԑk2ǜE FhTkE?  ea=1ö\`I B?V,rc<B&-m+'֖bmms27cG15ODJQ%QT)[FhC tn{u'q=RFOD9ȃ<($I E ȀS>yO>&}gSW+Ou]urW<1u܁g@[u4I'{2fh%:܏VfIHFnnuDFlvk~,?xvyk '@[g`im,rVouYg8T, &N2Mz4>|`Cj/(d@y')B ? (7P8/OgYj,ئ`⢀"/ z3IH4pxR foՙ'cgVžsC=tTtL,)Ir)?;%y0G W_ 8,ؽrHwz}WKFP4(z)ju$>,H@jPC ( I(G;~ A*OVC+gxʛ r1%j 'O0@$A 0WrH⁃\!Ǝ%[Ҥ;g.޼뗯l0Ċ3F-v'f2fk׮Y+L0aF˥K4ԣ={Xcq$cA ~EhxHC(!I3QV,2B3ss*'sH,} /W"B_:@뀁Z_uB\T^%֗J7Zh96لef}jńVbjШZk&1hrp-To%eE5wqʍq,4ǁ x@Gu D0v+%95ݤxJ a0y7uRNm2!;hŕZ\ l'rIS͂ rء`zAsbfN8fXf!~v"V̊,⋬| 9~Bcp /0R DyÑM%J5YPV^骖;a@@Q1&jW|D6য়vg Wz5`fi1Wc+b"bu3:jY8ヌMjfj\c˨H^>*`$L @+AdBMMnjԘE0 64ȭ~/L @0NzRwȥb<,h7UZL uApxڻ2` 0% Z`.;z0ׇ=I+b&LN5[\DRU*(!_Yfq3'd4kN Tu y μxG%D8Bv9I09ءD,r/B{ƂHG>Rۣ$>! j+/TCO/{)1r%$+gvҭ$~%MJ)btZIAmwQ MzM,6(1-3ᜪ8[ywHTzƠA2Vd&O"!X ʦ*b@J}r-Dֿ21JDH3j$MڕHZҕ+e)*@Wg*aQ4; zc2jv@'\BEvF0fԛ'lɘY̪Vx"vkXAIփ2N8hZP쬈0i=%&p Lq,=lӠWB`M ]^\`dzMB~14d Up*i; Omm%inu4-pC8Ƭ9@SV0׭-嗄R@v`N%2V+LEcij$ձM}r%. 2峪 6$Rz|i!vTc EkCQβyՃŰR"o?$^R#ȡxe^Q2I:+g8IHDPӨSI~@*&?ֽW 2@ xW3+/P9bj ^xA vh4F8ʑuoiIO8=tlp|۹^H:i%ax-*AzS'Z E ~mkyMQh%KuԘF+,23ܡsУMﶷӁt ڻ =pHnwǻ촸-[ ߥwĀH . @Pq:.c$*E{| lg [PϠ|5;|y0AD-Nώnvppt#ݵbӟyRF{Y5/[`7/nxgO{_#\Q2w7󸠀@9Bn- (hd3_I<)L]|`tEmY\/4^]%\'C;;$ȁd4n5_ =1Ɔ}aݾ0f8hDśY׋ܝQ \4 sLGIU1=8^ QlSTaܡ4F8|/L6޵ }`-4t6%A@h"q , Q%_% ]./[AF:x_cq%_txΈG_Nx_16N.J`'Ă/' 8-L5t=6'B8%µi T 6'"ԉbÐ`b*B+¢)Ѣ/hAAbsbD=@ZpTGA2Kt`cPa0Vڒ5E`P mnAVADAf@ A#AgA\=6lC+$P%.cm@9Ȅ}b=_ "FߌB"5$]g0Ba"EZ?C-p[@xԄpXM@cAY1& _Iv@.m3N tPJdPNvxVQ\K7bS6ef xD `AA"\B(;L1p.46&$B$\Z)"4,ecPA)]&$)b`}&/fa&-#6& AB._eflIe 8j5ALk&0IđDQ"eR6%S HvB+.e+C7? )??)o~Vy>eg #J{Vi^gե׭`D!,?6&& h._e&-hEz9䄂L/ɤh(k؈(HuD Zoޡo S@ A @H)6У#n;jXj,D\AWy2`UT)^f*n)b+i0ZrsO@ishuՌH 6ɇ> Δ{ME@ kqbHB)3|j7B,6* j@TA4Aj%j%|Ca΂ڂ-k$+A,kŚeJTZ܀2*(x4I#mTT4WdAZX'ۥ1f()/.~٩^qer,Nb vt0d(JJsHJ@zԱ0s3f \PA$0C-B"< @%AynXe\69\9&r]^2Ȣ]b> DzdEt@GQ-~W"˧WD@1šFot<S #&<6@@; @A#B6Xl:L#:`_д4O<?S!-\BFdV?GuЕUZ=IuԎKLяBi4㮶0pPAhi _;X-Xڃ=%aYc7c;G6)Gdef[nXcShWڪ+6r$tX`16VG0Qc(+6v @p1d_oBʖ\w_4hvx uWSlqq6{6,7|c&w&Ў5PIьJvBġ`?$7?rrs6Z_8`vsx7ڋ3:x/x SP E6N-IjЦaStJ1y99hKkwrgÎA`'ts"Ʊ F/6os8c؊ox o6McSx9j i䷗tuA3&W\06YgLz^+$s-p%Fv8CO:#.dž/9ލUz.hq nIp)kDZIx\zܮjWY<,"2d5l "%Ww=q] l XbXg(AF+ƈt|*L*cJTxNGf@ZwPAāgC6xQ" J - ]OHgʦ2hl$|f&֍C+TOVƥOP h. X:v5 m3_ThWîj` {ٟ}@m;ڳ=frsh̳̽$<<|>fZ!I=焉 &BSM0`-:ړcP[4PѴ( ,c)ˁвEr匭ws]xhbPaA.1`tґiڪ8m{@QWXQzs,\:z>9yC~pT;,D[4AhM4J\ .K`leq:я4:Zq qޜaXR6F o*X ƢDEfP9 tm4WXPڂ5c]MkzVUth[-˱b#0 RDXK z䙀jbC,r2ŊJ;QXZY*ܗ NhOHlAxO}𵘉-hj2  +kP Cn3*6:*` T01 )2uG]Pz3S 9G{ +Xd1ʣ, ncSSOӾPe \ׁeGL [ns{h"uNDV.zؘ OH<)>sYL]iܔy ٱGx$c)D)^`S5&e աja /@V_ Juea܀l33F˚! Gee(5lg],A|@3WЭZ2J;?:rW3ӻkXl<ґ4Mʄp;K>H9w/$J+o+\*8 FJreWrGL ߲ d N#p$Ph)!n&]]Z`cEʖjSbOO9\n'=p&m88 ҢPu-Gt$ <<˼x&k+ĩh%o>p FlhY6)x ? ^h liVah/z!GoڢЎ &n:LeLn/$旬FG#m҂Ǝ|$XPLIDkzp o,1SO]~Mc5lItӮ&(kG X"` $P$`n GNLJE)V1|)(mݎh,N78)0ֺ;j姾L]OD5"Osq5qt<qHk D=I qF쥠&'~LL, *nL`#փ& 7ivYB>.hy1`f66P#CqH$5\iRLPrTRXr\RaRUdD`TZXF1+ #w (I,G --4fI'f *Pl + 󘤲T,N.ZpB2R-#l@꧊8Q!i;0/tGM`2!(MQư1'83@h)tN)Z+k`Il` Ξ| pvOA-y-S3:*E/.BSӀqYkns: :qlr1F,8ɗb24EU38p(8 *%K+)TJLB?C2 ;?,ڄ@uS5r lA*$H2SBlbӣ:4fryj;q'q8n*^ʁ#(~ rD^'o.0w4(ʙh%-%?֔ I4pJ g̖KSKҫFK%/)LрV4הM 0B+OsD)m򴂔EKvZ֍<yҫ'lH= R+55h6˚@J-UaHUTAik#9U]U`9e՘h'l%su&XlJLFtP`TX@F].o27X#p1iYU(QoGo Բ6)WR/+?,#R" H} r]J3 ̪Dt`% l8C >+PGS$ #hSNyN}r؋*"44dJ\[ s`doy u-s|6ЯBh9vRWsU&yu+" avjrvoBP6t7veձ@ >'nb 5npNHoVS A;H/Eq6^ÖHg/a,V0 24 j\4(3G7ESLwėNOqoҴJ#d(w*=ڳWG4Osb-e!lN `7L4yD/j(Lz zg,wr{rWjVq'kxTNuW=@FX*dd{*sI6kQ>'2?+ `bMԁ `57]#4,LR<@Xzf$0[X^a58jr(}b;N?tv lVwFF +no|I]@x7ic"paɷүN8)yH6%7`W:i#FksTD)3UwTW 1KnX`e0YS[V @@gLNY)0XDrUxpe V} 3457jw';ocotTD(?P=Z v9>jQ nPKeXb- l\Uy/b:oIz %"Ԕ!Q +B 0wx~|L2V8Nwyo:1vH<#lU`YdO6hrZgh*͆Zp#!JACÜ  !N*"m!c&ralvo(ۏ 9B EjQŒ2$"Ҕz OZKO1'&GgzL&i ʀYd1@'d Tנ 9A)(IZfN:lBbfbb1VJPoA Eѭ Tl"9z@CLFyO=՚UIvɩ@ ʀdZ9Ԑn^׺←Z֨[g,a06p@ TJ.ňqFqXJt'r4OGfT2SE3qs':*-X*(*#T̯b}/d.^(fYأA؋2 $PoD60B ͖EWdGZhI)UÝ7N~xEnw9m/_+mBeх:*Tm_kd!y]v%0EK)&lRgJMyPذu(-!ZZHYЂ% p8MėJ1u/>A^4 }roOtKL׿O * UJ81h(EPFk'*[d(NO m`3U"!mԠia m58[Háw~`ir!gLNX}(1Qp2Dx.#KB,i,}j0KQ) тRfErcT@M&Zl$|3#wyb,"$If>,YR'>5Pls9RjL *Jxo)P&6'  rA~)`vK(aL5&3;Ȧ5=/j:fWcYN؜mblH5! ;"jV$!DtR}d-+6W@# CZ-$J+a>A8@ҝnK+)ϼ<1cLk19e);M4iAC9mZ[pUꖫvڔZMQՈqEKa7Z"?z,B:ؑg(Uƺ l?L7>,^4@͇LA*)Af{279_%B"\jb NKcm*;Q*ꕚۜu+$ .@@›Œ4bFb=_`DVNT j y-fxfKT̈́Z$Dn%"i F"%,- 7q89G8V&^e2UG&!eȝhzy+̈09蘿\c4O2|fmv(z y/(H]@6$!Y x7$(p4V<ĺI [ćx3*D'[[]4_lVrWSd__>MTeLOh*e0+X_s|YXrfhs"@إt-ңKG]o/Dopuf.Z&öHVdòk+Ǽ6&b Cf/XG*:Y7Y. hFn9)J`g'^pQE3MNz_903km4)l8/MVeq4v(.trtklTkubh&oWfŨWm*sG*9iy햩.P ` `As'( P+4$襹jDN5#]vJn>:?4=9`BFf"՝@'k)kzyqijlǁB8.ZuZyB:q0 %70 )' 1+ۡcKY\ ˚XSF ;]@s]DKy'br7fMIe/))S_!GхnYc觷@;{A;U_>[4vA+.l# 775g'A0p b @ Z'v Qz}[ ۥlۚQ3+r[V]Gn3QN˫[ )*M1/t9uA{u0HqJ*4Nd !дAA0 H++cgZʋl+3z!1(#J y/9?36 njRe+{6:k-ejd^ʢ)XB0 @Y0' ̰"|3$\˨5ry?ܒ{Kt) |z{Sz)@O6-‹KdW _RLT>q*\ !s8 \h| `LZ@ s,u\鸥!qMS~%s!G.JbAr'];La@Ց ħo+x٢dA0:Y@ C o57cQ) W fIZ{oUP\C|ϼ:Ld EșW$C! s³gj"v5eOMn/,j)Āk=dBf#pb_E5ѱZL YAY`y  ұx& GJ{uuv4<ΦF6=mݤGYHd^\F_ [Y7ʥ_8* v+NVuZˍ)+^C.^Ci 6Bdw;.8|]Q5КD~|#C$H"P 2ʢR$K65{ۅ HY =-yYP@z%dǐE g~`Jۮ@\H/e]y^$Q,ֱesݥk!8}A";a)Ipkl4T5ev+ZՠY? $Jj3==Z4ɻ8i kL @ l b̭+,l~z^Chݏ!y;^u5wUWG{2W_>У>z;!;KYLcRMpOdBw M P->l]= JQh 5k-1?S`,+/#F!yK(Lwn@V! kA%,VhQƌ?|ظ%GzܠAJ0vŒ7,("gȋ+3Q(PԬқFQDZ3TTKn2Hձwl!&T+w9u]6=V˒'.hbR`Ҥ۷=WNi͚1K%FM8 ]scHK7~[n)|N!1 ,xpD ol̝:VjM?Х6-dA;$ns 1fG/SvƧNoan +2H;8jywl! N Klq왹,3G/t/j"|gHa0CDlig243ʹR 7b (6pHxRրK!"ራ@ 3 k·8R"%mȺTr0ɡӀpˮT礬2(P f?sJ)R!s8:A&!PK:1iCHvH=;0 N-l4Gbsq>K% ,HbS%v(rB<K-uˁD bMkN=<ڈ9ӻmU%=L6T'owﺔ訤?($"*lpS;f!ƚb:[m9U_+g3kVR$ 㒷yy.cA1ΖM4ҠEi3`بvJ%v# .Shh A6r")9MJ`x`Ho`4?W" M2<*AQ0n[$xc[ZG4 ^A ]}51Vvڡ;聇Fc sxC{VHԊ-ʵyõz",8t;1+vSߊTxn༌Fڄ;Re3M;_XI$'Ѐ*I8-p 3Zc0UbZrurqwA‚fa#hÓF񐆼)EJۼµuH!}JXMkC60b!O<"}9DD>yRҳCpV"<΀o8"$ã ȉ`@Crы9hV:YcPv8'a*D! [lA#cҠijZ~TiKYh%K 8@ @$gL*?REu`TOF&!DZU7PTר0;'vrNBDF]F$;jPWJFc3GZqKZ q`[Bœ2<(GyMdECrFXN-'go^#6U\ХoXQObR'ty,>Bj:W͎';cNœf1ZnVܹX=̜@/H<3@H/`Gg,!`xc* N m':^QedՍ4_3LHxuү&65k*30ͲuJ 5#-bM|\uРCCˀIc)]B숴*q$1d0<1IE-H݈B!JW; `/* ׹Y(Tw[8RS8(4뗋#1*#vcc:4 mw#Zd'8"HG:We5$"Z\Upn#:qd$cԈa T[լfVPC!ArKΣ'Ò WeKMV`zxriY cѓ9R ڙۓ:3tx┣_yvw=]y4;Cj1;0-H!)d ;b[82c2Q8b6AK,&>S#>j؉)zmQ?H616`R&tPȼC pr-u?ks!{Q1;_ɝ#̆d#? !M@% K9'A(#AT} ;(+- 7ȨZx o:ҹH& A=B)@RQH+p\kS5:78LҠ{vß"&@tF8ӡj<<ɢC&F?<~IAǢ9@Ɔoj¤"R8'3S|T  th{KA,ЂKPዄ̆Y$[+>"saL,F01.D,א19¾h hC 6>D>A&mDD& ĒjDP xآG*TTVi7vi] H;h'd8Hd`j`P{ ? H43Pg4̞ܵ*!]DnSxCd '8';e&()-Q>Ĵ / 3PnB ]lI8GQQor*JR@$XR%V,P~hЙH`NPk߱P3H4]N!yèkP;uV-a M!x:,* (fc sQTTKMϴu $U|'@,pJ1P[i،f ,+\yUlMDcƀ2Ef`4Vbɻɼ(ٚf};6(L4-Kx W¨z|9V 9\F5 Z׊Oˌѐ4{E'pWM,ZWAK1p-Kh\i膴VB`àE`E{}zC`5ɒNMݛ:CP t\rD/T˕̙u ԣ5ڤ|IOD՝uUZ~EM1 KD1UDim9[\ +P1:c}_`+)<$\s=`˞2%Ǎ?$dTTΥr oo6!%]])))x)420;R_hi؆`nH9C;D.^^rcxaX'@)]B)ɝ;̔!-+-Cl2כbšռ! `scw50F)0)@`)96AcB UP:aS0MDIV؆i0VO DЄSVn]2(790d1x`(,h8*^<"V?1bh|e4 ,^k\ĒQ7Yee JU-RILc r'@`3ZO~Nf2<@dVmhn5[;`O IZϐ!vHVOH|pEȬyV&?,u.C)T%&eh <C'rOX` NJf#0},p|տ*Rm؆}pfnjV;PP` Zs^XO$h],.T.~XBV봆vVk&k~\B#FlŒkdžlkNʶˎa6볶 m!>r2 2Z* 11؂+'00Eؽf16*0n*(nvЀ ! ,^I$\Ӻ[p J讛Ew2jȱ#G d΢mTi˗0M{9M3aZ\ǓBx=IɣHZǴ鶧ۚ[ԠA2ʵ+E4s ٳfn9 ۲d16)<~ p+^X>q͛˘yrJڽc}S^zF~#fgԁnzľXqҥ_^Ewmk>mJ=A /(r(Q#Fӫ_o~";FcwixQIRUUIUO-`t;ՄSJeH`RO)Io\lםw %y'vd{4GqI$ߎ<(_2٧a7! r)E9;V %R6gZԡ"HvTl2S7,Qx9c|nt#|>2~T6)OS1y\OqTΕNhtNU#BXr[WcY} R6q Oi>Ռ/$aN-$xYzAE`f#资e)I&訒PFι皋.:ch%~:ݖIdgͫ k/!ukO!k  {;̶Cfjcda;g|ƴ.g|w[ 5XdhqD+鱣£aF5TSUW5\w"LuoMӌ\d`*.j<r#tǥE7ؼ,\s~7Mtб-y@Sø<zd#u|u]޺W7}Zkt63E@lpi>#o>x筷!/7g 1'ዃ8}I~ 67+簾zꫯOb[ŠHwR@</y&21 ~m͉F\K‚| ٧ڰ]WjsWGg{<` Z\y)tLj#Adq8st|2 &}mT8xCdBPF&cI-P"I,Ǔp g]dL%k _ PЅ*B vWS׮w1u@d!˘"$Vc$+@'I_kT Jڙb.h `T3>wy̯4GX2 "4)͆fAQ.pZQ`iwrS"M:p6D>E 3aVGs%=iZG%2 M'Cop(2q<A6 7j+没@V0a]tq_5\ 'NӜzDkZT }>M5SUvdHm$uak=RVu-%"]Bկi6ְX 66[b# Q,fYjԳͭNVnuiZb@HyZʪWZxuvDa\Zҫ"\B2WL-;Ung9Uլ^Z^i{?&qEz+_T`XӰLMl7~gL\k L4tHRufE"8{] ,D1Y+CAE/i g2U\WYA9α[95րY8r$lݼp5i3 f+g0}&/CҘ+oIZ3{i^iI Rxwgg>ZZ.-p*Ar [>}.,9d!+Lήti3 Lm8Sիn^Mn#iRH[lж3wlXT]1-X곰hAxLw34*qNF1xZUoZNj:m.o%fg6xakUYK+ qS|[_ko8Mx*Мe){=rHJ0%n 7괎`DY+CujOr/0QHw櫭3N,7O_; 6豟t@;HI>y)k~|L%V_@:}ZC~CdfwzWX?UssovtW{N$x{GMD<.0Iuhh}E/ | ˷fͶ[f>6V44VmswH:BV}Tw䇅P~sdH;x(8x  6@@ t38W?7}|xp=Ve?8/"u'i| FxZHvL[7cP(W55^Blj]~_\-exe~i{  >dp 0Xwx5XEL d|ҧ Bexzh`E ַVftjqfpD Ϡ^:h`k{TsjxDYdž| a胘W`ȈSB_>[Zİ  \Vq/qG5}Lj,) XP1P q41h)|HXeঐ0w|  ɈHgyDWeC9\ٕ^ٕHjea,j0ي@{ P T5/?mA)}  IEYUVHPgh}'S R>c:hTh Y)Ajat;覆15<U`P f^m9}YyJo;Xz&8lPXpH; ЙٜiPTvٞ/ٖ qi Q` ?gqTYI`WeXyXљz9.>gVy๡YtoPy+Qn  Yc)Y JDy8MՈygs4Vx ʙڡJeH Ȓʼn p -QPrJ6vsUHtp|Bdvax`GxPJGIzXyP`T~ h x^(ʷc=:bJk z 5pnjEs:v`0 `{G'B:} ~PIgJ֘p(JXmun٩W Jff9 e @ @ J wy {sZ[ptk|ڇ^{ժ=J z25)V ̦ԣ$eX֮ <۳ʰ @0C F{گ: 6۫ڕ~ ʔEɥՅFc' Uď++3Ecꍜ^'qZlK˖.{|,=(reiH `%5s{%,ZG755\\XyxgMG^Lʰ`ʳdlm"1P*PkWƶ @ .=Cz8 *7:"V&N*{]&4X\bp6 dc,i\L &s P9$ q\4F-]=!vo:/ٜzBi՝ObۜQ? M7Ş7 `쯨<9|N0EmԿ6:ЉTd$oMyWd9DP]팡:̌fȇT3( u}ۍy %P7ME-c[y V8-C-Z׍>ٝ_ O]&8e-FJn z|M:] #PB^S| 5j̝e H>bi &~әpeM+U4^ܯL㹭<^Jol NޒÆhZPVwxm^IFZc^Glio0 }x7^ 9mR@#I. Գp$JYڍ|ݏWBcK\ě~f)k]Ϊ,,mv߯.?`D]]XnHFi=e?;A5FО~X>׎L5*We߇LͲKx~JD ^0 ˽uu>CcĘ] /T Wf!eMX -Jnuw0%n;0.oK>7A^ [3/EP`nEWMޢ8S$z|Y; dnP^ؖ ^›د[h$m?C/\z3XLNHIt+o^ @^'$(jL@o .pe9owocc}CjKb?:ͥ/lߦB%1A (pA 4! >|ؠC?(B(\9QNhK,M7YiMbAi&YQH͡c;QNzsKY} =aF%KvYԮeVq΍[欙_^ /^<OtM e̙_{Z8u^K~2]#Vg$DNFAd捝X)ypMF;mܶq#mcvnJarwo Y_oBi xց!ʈ6@V*6ﴸb8;--R'N?;[gbWv=s޹pjILP^cibJ$71Tr(0"g]*d*ˤue{\\x=J!B )k+%+%Wx`ЈYbA]<_E(-,tGx2*TO.e q.Fՠ e" J-TOLQ  'q"@,bm+%X L )OԜڕE-'+8"Tb/v'\FX%>8^C/vP69C`Nbhk!;DdĀ@dFF%)YKRQծ{/ŝnIKߡiH'TacHC(rX9KF+[#$m'WZϲͮiMRp=O fe.pD$#;*sE0Qh kW,q[`j)V1l BBba UC܌nխnw'u@"N&Գ[3qZal!.c$hW#I/M|"3l;;()^M X'4 Y"(ۓP!b <:/ÅSIȡp&B"{؀X2?:?hS9/ؓ x6h3 s'3u`PX=,H4$Dd(\0}r@C&Abc8\Al桍h3 B( 3K _8+ b b @ˋACk;.p<. -pc7;ùñk)!iJy'eKj|DH6#4Y H3+3iB0SU-:-ʁE@IlȆt90$CS_=)H>H2E'bTcܑClByFɊ ar)}7FFDq< K), YE"x<$G a`8E 镍HDj [ 4p7H.`%$Hd># I`#+JFpII Q@aI/Dp!r,BJ+1J"K *|+Ј+:H;@NbK\@1d4EX>H%@bD@ST2C r, lDC,LV8ilZ̔)R?($:ţ-l8$J' M30ԼͫJuIR LX40Mȁa4Kc6Uz;৕FF/]и̑AFä9lz1w$ϊ04ETS,$x,\`ɊMXf(PXJX4%X$aDP "U*aAQܱ 5ѸeKN<plOI% Ѩ0-đ_'(M>ҽĎ:u;ihOLE.S,` NdQP7%!8˻$і SfE$}I(\Q)s,PTtl!($1;B+):0#\ 'u(-M1H\S@U_XULp>%0$NՄl_]@$Vq%1MAVahA`¤`V@VAQB5/mm̌ਠjHMBL=st]W1O P=8,Ȃy5;Y5-Uk)4lkT(Lp48!Mm'N$U|ĉme`[Xr TI,t͕c̐M YrY|`\rY&ԩ$(WQm݆fR)P]= [`:e8QxΚ[λ۽m$ )\V̐^5L1L9-}JM]K._1H>KwjX%q7_XgvNC=N#0Di0eTNUgEAXK/̔aR[-' cN 31a]":N\]-2ANWHf^\pp3gqFUK@D ;A04Ă'е$QsWVe6~6Qf.hx_>)=jV߼\ǒREv͕Q3i;cwIO;h8 `;!#( Dhc^S6okF8k$NgxF$IT[ ܾm%l5gM$s5TlKoM Z'Ȃo-0GD<0m޺_ӂFPSjmmVmYh`BMړfnGIVЄ^kčЀ#=iA&1[D4&,Rû̵ @2@& Bf ;H'Pu@p⹈p 0$) gg`G&nB-qh YHTฺ RS Z$YKlg~T\K$Hջ62t'UWx Q:ˀȀ HT}p"ys. ;_.pe7sqs[Юk76qLt rY1q%Rꭵ:mLa5to_`q(zGu@(v8Ls pup`gk6wkb/tFveD a ebOT9>GH01Z9-@Nxw @攂ۺJwr'2ȃ/Pwp0wPpwH,N[9rַbj+x.`a[ KV}x.ܘb7v8wd_&a@ "Obq YgFs_two!?I<`zw_{π  H1x9+NZToqZIIn٘wtsan9RIz^ݲDyo߶t|'xFxF2gec_">/Nk 藕@@ӟ 9(/a'ZtxQD.?*hv4\EA %2 `K -X3lv 9A b0$#2CHQ` 9RJS>Q܄hԭ9qh)>[޲%/Q'{M67f63f0_C{0 .`~(~`-Ml}`lU*8P6T.As$15͖HA r Upg\3YժW}@*6T8E/=x4;ER*XcN(l\~zѐ {sR![P3<`Kb;A`pJюvckFV9MWtLo!: 5_:m7M VwKdπ]|4_kvEQucw*|/B\2StmO/TZV~+r('p!(V2'TѣiP!Dh+gRIPza{0)#$II$2Y}%Lsant^wb^vyZc#}ǽqBHT]2%h ye.aM)$,WpYUefL!/.%؁hA!B(Du4B"A,U#`Y/흠=)GQuXɄm]1])Ž\u_~ـޥ__DHhZE9lMuaԀΖY9mm<#L$:H7a$@&B-R  CEė R_[w/t5~`~B!e.}yn ElZ@x4LGe@-hǩܫE< b AA8AA"6˵ dA&.$"'=)B=E9^%YFb2 .ر ɛ&♌Tq@"L\ D/)tںQEu\fNTAJ#\DA @ |%.l<1lC7&DB$h)&4"A4=cF?^=<"$% d>jJ`A"_-- -'-DN$a|`yG:X $9%b_@Kfh}dM6fyȞ@e93B#BP^gHASL9Fc+h<'?$q4B$ā*a[cC#C ͥe%-$/`aڂ|*<&NIdbVe*؀R.r.ihAHxӟ@ XlZE]\g  `)h;CCn%.U QeŁQ )e'$W*gӉrA0 C_'-D)aQ$D[d~¢X(@I`WT΅iaI^ zEi]ɍA1Cj2iB)-@9d'l^~)f9ڇ@ @)X)h]3EN P (AӟY@l쁢2Uj6k3W)h#<@@'ԨNR?Deybjք.*gFi1ȧXX$F"k0j 8k1g kJ~h 6jBIe@NP al&\ ,H$)%\:tXJJl4jM̪I'|BD,zMMj!uKxO]pc-`€YUI^w*ngQSduEMVFR'Ur[P ٪Z ɐ,{~n~cD${|̠77ASnEf:sdU{唺jv)4dKW4(/RlλlƹW1YW9U \}CODx G|C-8+P<L!c҇rz>ƂOX;^3Q^Ta,&5|W>1[0λ;H-XA`Rw.,ѿ%z%=›s4= _UC$ X#|lׇʺƅ0]UbE:_Ĺ}ċXFgf#ŹޛwU)Oۂ'X$%(K7}bL淗=[@3ĵ@f GxW&l\Nb8D D 6HP@`Æ 0a4#(#h1ELJ1v VZLk2QMg1}If `l)HA.F*F,V秆"#HBhq r0`Bh0*)xf0 g>!}320|}SDQ(&V\X))ImKqQi>t4 SQΈ5b#U02P2䅘!P>4ULbJ%p*["v F$K~ "TxfZ)&h0̭fs+(X"q 9iEHQ1&kP4! HJ) >AnxUl=HBP5mRJ! )΋S,F#uʧՐFI73)J~}Ö@@V q+FFNe/ @2[ .s%  H %iy¹t\P)">K!<3ɗU% ϫ:A\iXU i-'s)Rc4[]4pe U%Pv+(sX.3dɔ 84;FeS486&9ZvZ-<ϡu8pn[ucDK"խ6(RQޕ׌|Yt[]%`79خ}` 7ҒSo&%Coz%0F$'Y4@_!ߌhP 5@%M7\6i h   ѥBp]f4c,gX-Q7shxoa YQiKJ=2l%U~,)C ̕aPfka,2k(ԡ7W4@^ TubЬ1#'` Qb$Dh- 8ho{OZ\w~ӧ9jV=!m9f:8=5o~Hʺ*xȋQF\Rd m)䁴ڪJxQcDW)JiTY0ƹ"w;e'KoλA'Up3AMr*Wp@I-8azF$a{;&G%ow@% l^]> <@)H0{sI/t"-]̥(Mov@YFzVſ$6 ׃&<;,11.mw;f.I&"d] vU@LO'&X' ⚎#|- {{12D.$onz@T.،#V8.d`a%@exﴍ۸cnzۺdc=C,1"p֤ʞeOZ: njb$D$T 8oD>jp̲p,Xz.+8(Fq0d0z-ufMRn @fP Þ R Lou%0nPPD@jE$+٢3RR@P C# Wz$M솈*O AbO3o$#C^eNBrBJ4ji2&"r%s$[HljF8 Dqk pG d*nLYncbIa / jО.D} .x#EY/>w`X*" DgQFtH6HO([(ԨߨKĢ@cl%Z{H!Q@5?,z?)uI03Ҋޯ/"EK=0*#7 0:"(&WcDV6HRtCB1=`ْmX45 `lhѤ+&-5)b3 d HU+RՊ4,Y( ɂ6407S#1O6i %SlR8$TL&>f<m(Kdb4 .+oáo` gI:RU蜔,+LgYB+5_sU 1i-?2V\G4+`l 2#  (li-vtjr=6XdVpM|+mft&3l7L.) ˰@D ,q3sdMd3@I 3LQuWk+2_7v6 :a5]|wJVbwqy{Xa T3X3h5:pH@1Ut-s{eQ+TY6{/P^6 tn!:7a5&QhgduY/X vuf@;w!  o+y?8 Ǘ,߄z3]#R F6.{7Ps0S >%7c!qLO]2 ィW2a7` R8D<&kؒz(Nq8exj@!4xNwSe0iw@PSY sE fU"3rx>;LXfJ|˫\=֔On)Xj`\}yjY@1r.0#| Uks u uI0Qji'-EKl Rz CA晝), _y9;2DBI g]I :~0F@C4n%_-rR0YϪ5| !:ԪQtܐtjwj `@9wc:虦n8xc8NB4p4ue9褪z|9h|YX†*M<;oqB/8[Wg2\3)Ѭz3U[]eOכT#쌌D>o*jt#l|`; z cPm;>tKwj3X7_s)u |Q n|c+J0oŜ 1dg>X3(~-vYq]ՊZ|g˻pUV_^-dYH",,6]8μخ; ĔA(}9,(\vce/.?R=T)@}znsB۩keɡ 0JK^k6jzh1b=w/&ثܢn;ˣ~,I«51\O.Xif3>rvAV])44"@:81sKLB۷==JxKbo+JD=Ұ(TMkskg@T*`~>* @/!Q#^Ak$^%ґ_ˣuVÌR S37&P0+iE d sv&%G~Ԉ;*.?2}rϝB⏙9-!>T}si8BE 8D*3kE18siRkJ2JaZ,%j~ 3gNilo_o`B_(Jր9/ŀ?58r m3SLDX݀tʮ;WÈbS&h!Æ= V32 #I'Q?I=əlԩOB pS9dp`;zaC` `V\rՅ`kO%d 6X !b e,<Xe} a~qY 5[kmX(uUPAmpX uYhpfq$!3i\tRtJUYTxSPCz=U dՀW)T ]LX_UaU(ai!_dT"m #m/fV4v[8x)8AjA ADHS͓E)%teƖĝM^&yc^ShFAl^p\] mZU'\J#gn`Mva*@.rev@ˆWiیxZHU #PCCiXb4N2l ݱUD3w}x҄YF{0AW ߧdwn)vpa&{!m@f cUcY]TH jx۪7(CM\r2G. ka4Gs-R-#8-¾,XHYb2q\ tiYըFO*gTS@6UhXRQOl B( j{K cȓXBl A l6zr3J@/2 Tf,@#QvqH&Lr"Afd.K|1|M/ZOЄ&[Gc>O. tjbf#sѓLj hp@mLS)ez7ndkyMƾ緊Gd$}C#T&3dlH̱6DZcjVʶ"-iٖi..r+!$UfU5 ݖU ] a6I)xT`<Ϩ m` >55imØկ%HNHI#c12󪔍_r)L+H3`ܖ(7ਲ਼|V^%ڡ:ͨ{RmY2cSq' Km/NPeϿTÅizWe7 VV͑y]zU /܅bW|0$-`1Zi',&sCf/ܻ %)И|*Ì@Ѵ@@!5o$ OE{לK:Rc wnJs"V. PC `IW̔*|9JblT%@ 't+D,I"0Gyȉ$X89 7ecjrcEGi*D!s5<4k`Z3s9izDJr;=EWFݞW|>2^$B#jɺTL2EWy9F* X6: 잢-nARor7K?h+̝emÄ\~Qq#WEXGrآy#[MX\z,l82,2|Eu(|*Ք PaU蓁 9r&)_6lOPX3?_N^ c.YurI=ƖlY@̱Y˞ez{u|,BLp=^'i'3u{|g񥘂~Ե]x87^qHP R?n9t[趼@a %g 3Kv x'*AlR|n@qP`@}$}zWWNu}ts(r&s.D6WB'Nxxy0wb.7uOaWD":B'Z@`MTh LHzg( Ӡ@:g&v cBF4l70lwQV>GHZ\fc[ن/?&V`6,x;00W=u&xj*N5Kb1B>&JUi CaK\zQ(jgx将l fqOTI'vpwznww!qUqwtBxvI62 (h=\ev`*|"WBTDCQp< `ZhKYi9FdxP0 ʸOxsXtxvh:'YMw1##OCN'Je]3\ 6@!C/r!mȎ4V81YB$RQDC=$-% b'b+8&ؑ!)ʘ O (i*IkFgB-&Me";Χ'To`P2eE7I/v8mx=x(m#m7e 0R}N:DAPv0 @ Lp 'pA`R,xh|9 ɚ)v2uY xH AxNqfw/g"~8~\`aru=ԥ=I>lUАaf pXɡY7WЗة܉,(t~=Qd9AStp(1<)//l"P%6c%\ v UC<?m yS}*Yʜ@ !Aіoy `3,fzɆٹ 8ZrHt4M'H:N⤭VRk};T{Uy0:xwf zȦm*wJCvڐyp `4NpW2zj"SjUNT-MnMI# Z9 HT"B#J#b)6"b/;hP5"Ǝ0tx%pk mjrvZc]ɚwf Њ@ l))@v0ɭjo:*1lj-6m3J #'MuAqLzW҂S)h|V!xXi tGi,f(Om5ewۓ.:uc '0vN$!ATgZ9G<۳]KpvFdi4J|Nk 燋HYYU-NdmlU)&rxUemk˶`1Hmydk0 Yc8dʧ@ p vTz2ۨ[:>KH1Ү+WGȷAƯ vr;x݇rjĂdky 6˿`AOb(}-!XyUSgJ;ĉau[>PI:76;UoP{SYO\7~_{tvkJm:w _لMU6 X^%,c ofo<7:_<,:*UkW(CLě\XFW#6M,psXZ,0U&6=4P7U 6Lhai\ xIp|1Zwbo IV2}0 @xN`^ 7 dxC'G"sPL*൵1ƒadvi aP}*I-g.0Zq Ҡ Y =kϊ, YiM`y+^Hj=拂D:Oco xgm6牶!NӾڗ|2Gp 92L39H!d 00fvTG@@kA'+~(̫"΁ 8tAMiT!$ Kl MjL,5jl1̈*खn˭7%nj1!R C jS/^̑H.J7ѦF/,QEC. ʎa1 ;AkWʤ;b1ڂpY݈'EKzvjnFQt:$nqR$tN$ .4X"X8,.pC2pR˯*}ӲRhP1QHnSnȥ@Q;;(s'>1);9F('ϰ[UۿW鿼Kd:M+@K@:32_*s2@Q@;@clj BA5TD{A8A=a{'Ȃ'A ;ADJj9θN'΋*\@B(1-o1Cƚ4bъXٽڲo៖&8H1AGTF;+C8AS0{ ,$h6K'xLܾfUhJ"-D3HQ$:EDPy4ۈE++$jB#k8#ӈ{Q;4*C++-,=Djصk 2R>3C4qLX4KwGx̂xlLJ:_J_01pZd{B$O b 10,`pEZ.rFIF4IAIA@9b:)3,bKڛCI>89ǑĢLyDʤóUXzBinvi @ Ђp#J_!K㚝+ K,Tκ|̿ٗ13dl%)7V/c"0y,OQs 6h'$2 A9i8*3i_YOk'ځ$JNd/#Q˒Β:ۣNn4aU@pB]\[slTF=r`y|)O$'xJ1ԴD9HVfi J wJp1e%Ф[++#"o1Q;EQ= FBA nZ ,Ԏ؊I)AxDHT{;GĻq| !$NG:ԥdJ3Kp9jx4Ɍ Xnj9խȄ`6;91)a2AQQ ɳ A"&<~, F$е5SD&`W7 $HR)WOJ\Ui9#!rZHXS:\X$df؆OX$;I fXdEB5ZFB\Q4;]CD\AGǚVy$#\#J|uJA%GRKt(Z=ݳB%̕cJ[`C @۴-:ɲV:43[; @C!gd#R}Ϙ@r\#@%(.H4A0D_5B92BB][POP`KLOU6d9]90Aa;Ч1ވ,Xb<#dOYm 006 *C ?<\$& #)(Q ,`Kʀ:]N@xt-1x]|h_I'4(v[yal;-de 6F^c =%_.50B^̌^k.XHoyrflds>grVvNwxgyg;c>;!ecT@vf[Rn}&}wx.fnh;iyX hSZ*-Жv6,fi3*N`! ,^I$\Ӻ[ȐНG>IH"춭Jj0aI-bN6-&i@\G;xFM*uӧoǧN=cgu֯_Rgөh:Mϟ-wNźri4!@ hJذÈ+^̸1*3F\Fd˒ہ:rϟ93 ڝc$=/9zqM4lҠ NvKþ^MȮn3SN| ʥ OxÇ<9ӈҫ_~[BJcձV69~*7GQY>յKE!Z>5.B&-kzlsnQP @vz_P/߳G~7l"sW /Ƅ$1H' Hekk49D1"7kIRd%1I`ל[,k8iW91gq LF,Z2#tv9.y7 QZ2 f6 ڈnB1F&`cM GdlQd(ܙkTcF79cԲtɹ~BFIQMf3")-rJ6ԡb"fQ\`9i#jQ# IM=gJxwxf:Sd㮴%+tS^R}7X?zTm:PddjZ sW4d'NF`KJkXYTFT) 'טFvMʃ#*ږֳdOEYH26hR' ^RT8+=vC+ZvUXjU+K1k \ȶmЊvW5%!Nhθ/WjEkuv97fG\%; HhW=M/z \7KDmZ߿r1a茷Z茢d)!d Vu`#XH(Fcr=20`&c!шad՞Pmwl׭dsɴƋ1Hk$>LMVUk\-h%ٺTdr;琎}󝭌LmA(0x3E-\K_5X@Ùns3:,zN;@/lfK-w(z 6P驞vv^ )x YkiɛngFa 1e)y_wv46 ӡ81&"j)JZɢI/ 2ꑗX[9p;2t`uGJf-֛6]5n R *pNvYa:_ZW YH>p6mcKZ9ِِcÒ)3,Y|o69Yy0%:d(m?[{g\)_*z! y᧪QpUP[  04ګК=㘛|*ccvk|ݘI:G]zTPa &LWc}઩D X0K 8Ǯ zڪl:jo: `J 0[) 0بPf3HdaY*d  c1ȫz\խB `_58k~˷ ɴ?+z*JTpzZf`v Q[ITPjjLK v9 ; x1h˲7S؃$kh`+@ hwJ ~ERx$?>?y %[خ {p@Z9 <苟]ꐅgXZLD( nd8mNf٬#lkr[l /.H =܃ ;F=ˎ{Y2Yt[\k7SNU`#GY~ޙL_>6,. dfPd8 n>%!}nJέ`G݃^XL~޻Y;(>6->ߜ1ߡR ,?Hp71Ɔyg d ;čչӌA]Z۲\~3.kn /=*P|>hT;1#4I*gu~̶ H`^~Rѽc6+d\}Xp,Cɽzq6f2\,N1ن74/67 6l*p?urwy|ceHYt cN6~oޮJ"Ǡ9^g֍ vT4/ܽ;|`xiG™_ 7-SFa >|(q%T$^XŢ-qquXdɔ$,kʮdK1aY9̡ӧw \ў*wS|KNEU9U;{f%{٘-ծe-˒%uյK,XۗV-[Jcĉ`\P?p%N&VqFh}9Ik$ۢVVܹJE绦M*]ָ^}3G9ѱDqG ;o_|iњU|+V8Aǒ)W&XGIѮ(͢ (BYVc5)ƚhm³tP . sj3'9198ꬑp&bQ+<"o3 Lsl>$ `!.DHҶh\$@d(4 CސP8p#1zsOWJ@a:fF]rK1|˘'HBJp TL.?rUXAZ 8{:C:mE*=ϬuA[T6.+\xa4VtR*5LߋL>m2?z()J58d-Ym5X,% 6ܐJvuCsLNs`śӲN`h]Vnt 8si&\.Ï3@*8W_~n6k 5*a.ׇ*-.ޚp-Ni*dՖen e fx@OK 'H Ԍ8_wa67jJXk=A3lhxNVߕF0fOr{r5^*6m.YuPN |Α|)tB=IXc+E8vYY=n.H󝐆t5Hᔧ<Ҧׯb@c~̲mt=&akJWe|1Է',lOQh7\w2`bbV.w|{@(=J"Zjh-Ik_tճj7# *|+rCB]l:ddԲҝ 0AbI2K[|D4> Q"<)Aũ((x 4p{l\G,8:GD⠡%;%J[uL, 3)rS2W~I L S( 'S հJЅ0712ъ3K26%DBZ'+7?_ sYwaec2+d+T8\vv.> ѦFpғ4Sd9QeFj:+/:&d'OTWq f"&UI5R(B/"Cf5o.,Q $% >)@v4"\E\+5t[;NXS`\ Mf\lW.0{.UI%S縢#c<0UDݴ\ W/QC O| M6 $;/ ȣ<**JP4K _5 aKb]X<֨[1d[Yq,Vr6ІP2ZV|{-ljFVS2Mn7[)] dku3)WX@sHGd[:,l!W.WYrԑm}7"3pm0,9,ZULp=F5:Mrf [w(8)]{`z\X+_"vF2cv' vɺr_ c"rCx!cӘdNQ^l+Ke. FWŌQ0YgbI&CT9 TݤyR jJ .>l:aqCȡ SxB1P-ԓk;7M~Js 1#L^4ڐI\yU]oћ\}@.#h 8/ wS: KA S4A wD0D>/sqh{NyDz>"ycc">"C~>sě19+z<&܀Y7HipK1繢-?ˉ4\fIЃ084 . 6{2(; T*i9p@:, AC7*%XY`9@,(D&{ ؔ(87Q+ ?$$q҈{Q :eS)407p<)($ЂD14C $ 2{@7#e&$к#`c8?|R A3CCD̈qAMْs3&J #,!; 'FaVT4444[t&C[ ,CIC ҧ(;AF,flF@(<7@4eqÊl((c*A=9F>ccFc#ĚD[UEv\;N2b1ZD)ݍBE[4nt6UXIz6=(0(1D*/(U寠e&^.c0e!֟cSi)`A*fis|L3Uͷ̭T_!{=bӍESxU ;01Ȃ&p@ٻP]Q]Eh3^)R艮h+cF!dc7˰Y8fpֶ'mIVaeL^kcŝAD4CSaN*آ6bEzd8bi}P8Zo(@@E4DRLGiPC %PC} -fP-~[A A8ara!ة9yiZ45.*i p`Zx8\0h$pc4*q jqp?jjTMC"?R՝x2],8ՐJ+Q:PkUrADq!vA̅;̢'X-g;ѷC1p,f#lͽ9_Jw(mg cÈjjE(d[Œ~'~@*P@n#+L€.WU@4A|)YFIM:]K x)wv K'jz%@@PL'JHYvfb%[a:ֹ3` )!?@@\`pNB LziP0lKy<=vL.jq=#-/(R2f6UъZ>9,MFCT]GhLclb^5d*mҫ:D%);b^d O r1냉TzHP.$+YI'\V h;i"w@&D 4K'l/+)VDf(f>"ь4 X4R p"ެ$V!iYM);R` Y at 88k$jhv0Aw N 7T-4 (9Flz8>RqPĚl(e 01ҖSEZ|L[L,cx*HA.:)Q `D!1 uJ+,) 9{Vau3pV hbHP]SM3Dvt Cz<Ұ Ƽ( zз~/}ErTA|}4ft\}gc=-r cXB @~q;o0 @Izm@*EI1[5:>/~Ӭf{,/彏_r3.gpigYP} B)i/QI@־s i| u@ҝ6JՈ㪀AY aErP$ $!K+;QmlyЈVBF|1)moXIΕpd/y ^\+қ}jj G7KjvҭMP);J0rP%.H* w&йȪ.7ȳ`땯>x9;E3n(9."q9", lK +rP/ۂٙsHozS7J~M g4UlOd/$vD5GVoհNQ'!9@Gh"ݱs,)L Xt;SNgY ].vkEh_M-ښ:ӸFlV>$L4n0AEc}2v&'__/0 S<ω.B# |`Q]eabe[صTjB򱝢\]EPU<9\5UIDwdǧ-] 58AA^Z69@N,JIpM HQ JP_7L"+:!8 pHB&&<3l.B)hB)^)<LA (c @ BH}V_D}5ԝ660 0t.7/Y8`&h%Z: #6PdD\ŘdpX\t0,YfI8(ɥ@ Ѐ-A<'<)|')H  %dKAāL9$>|&h֤}Û Ɯee`61eQlʦ/8 Ne! Y܆he#r#XLa XnXtD;pqLv|ten"x]R$A @@Eh@<(+Ă~j!D@ȹ@A#̂Eg!h[t4&O5 k&llʦ6%h#1PT ^%qrV"O@$ԀEmG]u"?>@iĆe tyG)  (/H>BpH^f`a1(2h~[@hNbc`.aHknhv#W0(TR`XiJu>bUdssK%dwVQmrO̲|1N!V'$j'pi8( N2"'B2TƂ9)Nب5)i!S j._TK(ģ~ضPJXPxLd*:GJh ) ,FDрP#/$ B"dN4B'5ٰi}!d6n("SfqUꆍO:"J\, DIǝT-R P *dnX 8A"B)|B$PAe D 6(2ƹp,4Ȓ )iR+ Sjold ݬ#bM\Vnޮ.kjzg- FT (/o0|oOE/ ]LȨr/DuoMcn d$)h>Y5&``n=HGZ6 lYS:+eXb0j0 B"Jk:.n̲zpJ (Z@ ,k._is&mNWffm R1@pH wz#nM0:n c fH GX qQh+Wfւ7x ?h!Y"'":0D$/|bb2wjYnr3VQ +,@V%-hQ8A%E-1KM2i*b dbn4d QRg\5?6ˆLnns8W > V:k:Z&oNyβ,K>s>V-d0[hH ǭ!ei#[05oj/t[jXrȨ ɠ7}}[ִ)U)F hP%9@RӤ2SS?uijflPfJ4DAC%[2plt]kXNqᔴY(_ς0;M`X\Z7jp.ͯZt`D :ü9Ib44mi֙5Jd#d[e[egvovtl&\YBb1\LtPkLeZSP,'B3aUc^E|R4*M"b5ƙ4cf\2\ChdecVo9gws7ܽP uJAɥs6o~McxԜ4]Չ4ńB7x.pJ#_D CvW6ieTrtvn?*$8!=NxvLe lЎO _ w'!wىv;ˢC5tWys5t6w㙣6M؀ss|:􏚓AZv`1Np3 k[B0[ A (05,HDG.ACeth:g{tsBU$jOg]ɷI$ClmG :%|ME99L33OG;R;}śoQ﮷c}{owG.Ȑq#jSQ7ͻ\Ht\_xԀ(cEۂ'!(MAE+ēbcƇ}GT/FgHw9tN4=p̧c K7j_ Z%>)޷O'_C@=YTT~@] q/J؋}Ny=Kڣ]{ѨS <ݟu3őRu;Gɼ[AyBY$B K&!UD|ٛכfg?396 ~y~ 7<6Ng}4H(pA)F|cŠhࠢ%r$I*h1Cȓ-[&LD;e$KP1/- &\RsK6ujSqSJu*oXbj kał5{ٲbٶmZ5d {ЀC 7fq>hp@})W|sf|%0p)lAj>"v. ADH[F9I)W 5sGD Q5|2TGF5iWqWjU۲i}kM>[rūS! ,XX1"l%в ,42H!Tp 50!BˀB9@6z, ʳNM2DSsGC2~;m4In(;4Jjι-}})//Lл a܋0$ @TjkU6iăCqD^C({qpHKqAhD$OK|8(,q_}q@k҉tF;xFHQ*(85)ڦ_T (DIyo^n@AJB"gc7 ME ')EN[ 唕j2O+c?9j.sirChlZ097g,2/@#mD)f"po<0u^(p'_)%.UESS]i a](c ^9(4 \>dErݠc"vl\Hm* B禓a hny3LOzjEZM0bZɡeDc8sĬ-F<9Ѳ*bB:޴5wcsmZ_qK!$poء|<;ⶨGd-e/26^͚JQ!>1Ȉ +2$s^qhqgIYp@Ԭθ"!JW2f  5Noa# ; xs8ԩOAHE\B/U ҙ(3Pu*ͨyG1Xs JHFRo֮vjJ&]-%dS\&,a͙B[AToym e `:6NlsnZ!qE `F[$w zo'կs`#Ĭ9uPXzK &+c g4脎EaU䌾jʷm[NJ;ڼ@9W 3պ۟gʭCH/GخGpzԿyoV?g  ةBl;]œ[~T) 2 oK_Cqo#hx\-rAPhZ&1@ GU$[Tzת_]58>_o2Xثػ+A>!ڬ5NJ2\+N/AI4͘c7F#NP,5/j֚8 &S@N` !==DmϊpѦH7"&OPʵȪr`v\3Иjnj>ʷX0`hI;~P FvJ #܂pгH2g+Ѿ /Ɛ#0pN sN i`   `Ei^Njj vz`  B*RP)'&q>PZ Hkgk/Zl=g:m7 Gp" @`"J^"ʃ* pNr^h ofl \  B /->Ÿ qKfQm& Lq Ҟ/!2+#V[e"̤c͘a#?rz'|P%` Kf 2آ)1 RZ5:'DNt2)Uq)_)1۴2]b1Ҭc = OP,h7j& V>z 2%:adXr&"Lp971|Q)5T+2\`D b~/44o o X#m3,Jw`Դ c")aڒnx7m:~Mb9ɤ9άA)q; Fcn`^;wI? =GP=?0ܳ864K!c>SA":H2T,s.`n!(G&k99|xJ:5G;;KOet)Arq<( H4>>`D 8I)BPϚTPl 4Ô  !Ɛe*t,(/)-hMyJF tq4H#QN&rc+YlF"3}^PRN>r #V-0?.nM W")W{1XNC.TO.PO%(Zu1#gdA5[U &7j[PIZ Qڕ4U# ml T_Wu$W*NM˄(WamOb,6E3R52 NYUwLB 4IeQNpj}wEFb3L*"JmJ u 67gTU`j*4)l)bWVEQ<]^c nV Y^SmUXRF!H 3uQ[ä[ I07owRRG #EJ ݸ'|ʳK?MV7lM]vvuE5$@~P#\\tIwA$(4*[nެ rWȫ9ol .l'D$Nh&;v." 4s,[3̘jĆ絙Lu!dG#ܩd |HGx5fxPeRTRQ~X#(Zze@\r1`*d|Qhaj @~hێ;Zm*iAD[&"nni& 5hPdqB%)Q6Pt# uYhqM]~Y8bf-|`i9J5%S ՝^}u{Vm* _0$\Y5("fPDpiY)X82&jjV Y H#iz/D r5=Lבc'S-qxD zH 'T̉:l:@*V^((W=a |pº-Y@w[wvǡVwbL2X#g!r-,KX",ՠsGN:O1ݔӟ[V\qlcc]s a3 KNJ:i{15ljxfA'FOPR$C =@Ah,]k[.w3.u Y:5v5lKZ2+.@7D/maj76<} T GL,5|ojԙ] de#D Y ENP,4'A`Z^Z6t ,1|:DuՂC.w䗟2z *xuF mo3"2ܰV x)فTtթY8"CVcD%!qsEM(KB[dQ1'lʸ@5΁xeтeYK A.}WF ADLHL֊&Ivͮod%!N4@j<9PLZR.P\I4 3jKr/vU0pm#˺<)lj3M{Pf`ɼnm{Ӄ8` Qg"Ȩ^i= /} i*$b5D8 OB TAEd(*FL(uD%AѧױdBt&{IB e-7ӲMɰzbɂa 4q06C仛$Ra҂ aZF0TA)B3sK FI+[_O\9JL}[XJJ?zT)U0ÎpB&|c1Y̆mc21(85KCLjr498ʐ>$.]0i`-̐ݚġw P(sVѣ2+Nih](p<v#J'4RU"S쩼Ǝv&֘oERod9i(*H3&>E9D"S T|;T,PfXEtNr=\ uN\KL݌cab@_>,KmbB$[/l{;$iG".;/' 7Ju~rAL pF0NVD{Ø3 &.&-& wEظ֍xxQ3g5/֎;soMЉS"KGʱuTJhN~W9Y]4Ti7_;K+a)gW< PY T5gH aVK*9xg.0R34Y߃LV'<َ6@Y7#>b)u;Jh"!>"q#$Ss5d[DQu`ykqR'@@oYVTW JpKhErl) `RDD;' YKy+ C=b"5XuB/ $8C4e*G~dZGjih>8 0i?vl&KmlՌp }ءf K{`~i@9GR+9ɓ͔~ R#,VCjmX^R"T+zO211YGO@"$" pc3 `**m))pMq!@216G^5Lsia9A]α,A_=" E]#"VjVd79Rddz$;J%1^hApp fE ҩ\I1Fϸ(bEz) .awר}H5Y%Prwt %$1%;ZRCv=V!B7*.Uz*iQ#Arj2!/PR P`0UtEomH| E&P5Fb'ǝ:A*&Ư}"m 6 @CR(eZp'""izoDk;UNeJCood嫸bGcS=Swp ,KEpZ}sJ";- ĥu㊈4j2y5c/[G{}WG(#pO^AOj1 pBi)TZAl;e .j'ftK ( EX|Mrkf P)P;\rƸ\9R7) >y%TbJ5;Kebr8 rkffff+~kgKZ$[P@E\6۸k̲I81Xx|/h!f5MX}Fc8GD47d7eti<Ek ⲁlfq ar<; =/PսCwɨ  ;< 1Ū6@e򾠇Q;>gqpi*@dWWLA9zZ'[Y˭96U8@ƾ:J,EmpܢI)qSM{9}tp pcPQP 34K߻<ܝu";705gl9P*𺟼;)й(`\aiYT8Z7 lD1U>`#O)(u7i v!ɧ>̀q ݞP *.Ҁ q0 +?<坈bAjV 䓾tCt5+Ù|Tv>g+;úWp$cёil_>)P?=yɖ`!EҬ N3 -(lV a;~>NAK頌mh}YCH.uANJ; νz Qq#% {qK!^\Qi ,l]PUwGɀ ̮u-;&Ҡr;ϵ6\>k^K@'zb^$i?l%T\F3ָR%-^,AJ-W~ irĈ49R<9|`SCj`RiɊ%.jǥL¯A6ow/@=Î9gyGZ.1#.` zCc!(Y2㌘9'5Q\MX6y `Fvͷ6놐rر~T]u] 㾔i&bI*( `!isH$/넯#`NP*E͵TkO=0 1,%c8É ax̖|kT3N4O?51EQ^CTgd+/Jɐp^NAX{kSɧr".媄jY6Nl7/i9dǪv> ܴ`.데+䲁$u"mziԁ[ 1P.!ČOAKT!L#fbL$-ԋ3.cU%,,U:[oq}sk3٦{ߨwNR6d}$<3͵j7k4fy뇳T;56[< ;.K# zO8d3?K\qJs\T]Ts+EFtw)9Wu$3QO#:fԜ(E &A`w )ew+ٶ~]yl&9Oll6izfP BfЂ8!icxЄ>38ƍy6&A 2WgIp VP9RI'e -p\nds N)-!đyax0mZE 1b/g3|05 vi  DD~+5qQ D1T$$ꜣ$npd X@*JP:93&cR`ς*_a 679a<JQ^b!q;$q|2&ӎv shFkt* Ie)㱦9+%ʡ`oMnrZ9[ǁ4F=ʊ<$͕Đr$=*Il>>Pw@ ;&JCf(ħ`<hU9$_XA Ibs3pkh꘩LETd GJ>kͲ:Gs<<^ҭ"SfMШkF٥T^(<d^ v&OMT0*4"Ba܂u2;~;Oܭ=Eg DYf@W%͎uݿsx)&bQ7C,tl|slbaz*-l^b'0gBUZ wD+`Ya 1%a$V0O⎪x5,vWYB9zȹ8~Wǔ}_"e)\"(Π&II fH%&#eN7CxƓ)CK'hF`[;$bXCZO NAƀEb5rCjLUtI'AvJ,{:xRC~)9)yGAإ[e^v&'Pc#{bg@WcF0"a"&0 b /n(F9Wǖ&H=,Gf:(`q$T4:-jYLsldv™p揳B̞'fwL0ـc~ӽ;o@,/#KGwjuǧG SҢ$N g {W2d>$7>;`^%K.cZ8d?D݋-_>wl%=)`AHz"ӪcPr r`W[ C= ˽< (Y0dk>~"+O3V%NpA ?+?s;eJk1H)R#?Ɗ&Y@c5kS#ޱ:+4k@L*Lb7v5h $`0.;A1 @ ďrc`+PDHv BS2ҷ+4EDrqx#佲p@$ٚ7YҒ1x)x;'92 ("*b;hQflx+Ăj+c)XDcpD3HI䠠p:yD۷&1 /,.y]-72{2~{/3H޻LHZJGaN A* _\5Ð̾F\@ZYb@kno{js 7TJOeF q, :`pʧ|$GB{L*䁝 ؀ۀB,ԝ:ȴ,$dCEuqxcx&(6.ȿ$ך*2;@HQDQ̒ącА &Idcf́AIO0HdptMr H Mܼz"`/.PN.$,  )úPKv-$DdKEx!4L !x1<2K@GxD1RK0St"[i(9˼n^\% L*]( X\͉H-+0MTܤQMU$2QJvʁIQNʒO\$P 4&muq * R "K0HAFa;=H#^ZIY7iFlX S?S(LK@15o\DppdQ5G=J5 LEwZǓ`NU,N TMU K%Cb 'M3 KN dU-U@Vd (X异+/= lFdxjqe0W[>U DpHĈդWI5JڔFWw׀؁ ؞'i7S؅:vSGW qj75؎mcYc ْ5YuX LՏ ٞk|D>`X\JaIY-"*֣݂֥]Z3h-imr$! ,^I$_Ӻso‡s'[Dy2jܘQ"ď AJGR?(ƒ2m0MFM&͛4oɋW݂wdku+[~\Ià0 1([nWhӪ[an}K7봺xb;&$<(MͶ#ɸǐ#KLq&{\.(ǍBζl$[=Ѩk+1p햖͛w}m_[l*{YΥ)'>ddR[x7%Gʲ-7;okv0=TUUE DQ5`g q)gBre Lqe&O!6rXo QH8FjU5eSO#5Xe%`S t;B;X8Qt r(%z("!n^eNs"`+.vcxx^sz#|"ӠFG9Z2 2އӇ2ݵ$l:ߤ\* tmZ ΨEy`6`gdnQ\GczU/]f"EŘD^! :(7d2TkmXhhɄ6邤*a8κK+ ezF gq[sf[55L,yU-Eٻ =ǩOֈe-cmir/ʰ\2XsM65[S8G;*СkФjμL7jD(jd\I*2Uq7hvTLWin;ZPW7x7-z/l5_cO75U["-4FޮsӨ3͠S7Xuf-;״>Zΐ#9ncI5}q՝/o7g>;Z*3ưl148F#}+4饛_xǺ iv\3CWp$x 6 I2wET^{a>=Nڒ>CR岟:RݣBCǽAKk2"$fވ7"-uK&`E$|Axlm~&K%hmpx`8Ҍ44q8H 9*".$H0tb6'(.0e4 (@ ȸ HF :kdH%53J7uqy e|$p>uA"hj)v]3j'nm`Y5PJc,#aFM6_JMvaIT(>:Mv &aVU˘s Gn˙M$qVjT 5~nl5AiKE/8E*#yr4KwA4-8t U: LN.굎Y(%Fyt4g"M+ITuj< ,R)ej e9 1qAV<+PzR5 iRf SF a̪^7D>HH:oU*_&9YC5gVnz;N+` {X DW>}TAo6ᵲf;ݘE$vpִhUmH6|m"w=!|=ޚw;+Xºɰ5Z1KjRt>ާ (?vfmx#k^h+_64Dk74ཝ1dNEq]9w#}n'|j׼] .$FGkt]mzgXXok֥Wo^UwŦs;X$+99cd=tiF Z슴@^w5; j>|ǷjRYEn xb}`$?jh|V>-9ɿnrA]{i>TIj>[*~{i%t ɇ |#|'})-ByWmH},FDzB-=Ɩ ŀ(QiGtwcuweF^ '{6$F ؀70y` b:WuHRZoB4$'z(xl,HW?1H{C58w?"ŃrnsD(%g~ : M(7}v 8ņ$U3cc^TbޗWv cg.(Ævephsxv?z 8$X   6PhP xw(u]T5V- ؂l;։.bxex,&5chȊ8pxg8E0 ~oe@V[xe(:Xn#*xl6uu%xǍ`ȉg2Fw "[莸 cT.Pq Zoa`7Ѐn6QPip 0 6閄9D8 +xVRs ŗYwwY89~ Dx(:a(y)ff `Vkxc3Ըw^d3suy=9Rj؟\jŹF  ŐDɠ采։ڙ6 U`c`3y 69H[t)Z  [miyma;JWӞ Jg|WHڠ=ctxNR @j5ߩ[] p MsG-pzhtVI[]% U T@aym3Yʠ~ctVVڬ9Z ))i*`Qp]*_ 1xpg*ijJUu_vf'^Դ^YV- *jǪxʕU:}@yںfp G퉮 Z ɮ&ۮZB?բ ~k.5p Lzr P+  qIJ bv@jDf"re*''o P?(Mx:Yz8ŊfGhzE[[ ;$ @V{bꭳJH&c{2Q^ȚjQ_k_k3^q?TV&rՒ=A0Iǘ͹pyV{ { ˽ʴN"wPR[KKZ:幱숀0L e}Lo^һӌXYڠE+ ӫJɇ9: ![ܫ+#;˾Z[ Ŗ + +Ķ;1z릎coiM?mޠ `- {{lpȅie/Æ۽;k݋QZq P* XNPTp[v p sY ĮH|[>l{>xKbĻU|WŵŚpPm{L iD>.SpsL'| qR~9@N0Z0 ) l";8ZZ3E|Lq #:|D*DY 4ALƱ 8\C d_҈GUnL M̓{*{#P%?Sц|;qx [eſ;z]}&:ps uq ϱ|C}T- =ulK۴+ %p?`{Z*2I &-=|8*-% ccJɴ:+˔@=U@CK}FP3؍md2RMTy^`O@{ç@ D[.cD|Кlj몎}mbB=MY5;\8t AL*QMq\#Lǚ[^_Ѧ}Ȉ|1@Lx3m>הEH:ېfmY Tu }ʽ^:8tum}t H睩hC.\Ƶ}Q>}:wm z-Ryo N [-0;p)ۺqhjUCMng ӵ]CEߖR?j%Cnm~M]L kĺU{T^ν {_Nd75ܪkm$5v5y;ʓ[T첁.ՖmV䍃N A7*R~"l 坞d.,pLkڍ|н/m8y.ArzPgqiĮ Be[˻@.!վZ`>"Jx͍f]6Fy@@@bVF/5Y5Wz$rLN, 2+,Hzg+H |O4Xu 5޳l-\>[ _/"᣾!> Hkjl/U^shD<.|/e&oph.RHb,fi ߷^hP$Jlҵ*[iԉ֭bŒd֭Ȏuݻ{y{W_%WÅ'бcC^h^dY!݈+b0#hۦDҵk^1g֬u3N-;ҥMF**%Zz|˘8fMV"A˭.{$/xn3„#V5f|'Xs{HHN@Sh5^sp%]bp)7А4 * 8 +%X會ԪnKҶ K$ =K=<" tO3ACr5VzeL2DŽ啖PpBZ 6 *{Σ@\jD80p"`it9 #w'3%r"<2U(S)JT'ˌ:)%1˴̔d&7<(a7Գ)srHB ' 5QD@D[iѴ6@TA0ɾ8Ӽ:m7'{D0QN|*(HR@ sCA !O%VLij*Ś"6Enad(x)Wt1.؉X:#C~&!Rԙ/8`?4&2vEJMdM9J&?aA (K5+Wx:PZ3ԋg:Ǻ $3Myn} ^IDʠD[З6iҴ5i0Q\O*J5ish89 G^IgYpN@ňfW;ȇ5d\T/PROJL8 + 5E]"ے&rES+ɢoUh1N(?e݂(NG 2Wz #ч)+X 2pTLF5)b-QYVTRP 8MǛ-BZwk@3V,'EGͻW\ [Еn~*ܞ9.Pܠ1W@e&t5491|H`<)@H˹Ρxeq[ڡ ʦ8 aSc\1Wt<_<וu[#k3<6Pr-Ve5E( :a3o7N&V!vX*%X鱮|;f%,$QA)?OTm 2iJcy0a3}ѯž5ۺRe>3`@*$¨9ֻj5sFJRbwx/esƟ|QGťa B84#\m ݦU8H^n;6b6Nx}ޡ*< ~4 TPH9Wo7FXG3| KBr!, [xȡ7\)wiyFfNsNYss2|ЧoSmssU WUFT}b9kYĚ\"xWub&d Dܥq$:^ |@c+-m;R+!a<-y08v/-ơT= SynH3W,{Vj$ȅ_H;u0;P02)&B`8>ۡ#Y 狾m>¾o󶄸ؾb.9 3TK?XP@xs&Z 3z˘s48?N脬cQaSfHIЃ0,'&0⓻㙿A*93)C|dpԅCAA`KDy67<B()7@+4?{ubbC[JcR043H48D0ܧ[;;;zF, 48i`0S5 YxcGKG!D8B$|3!ǪjB =Sl?f2:D;\۫8۽0H7H.$HKƎ[=l?zIéƨpjĩL+0i' b`s$tTG[`7vT?xlJ؀N``H(#8%0%hHX%<J4F7@4h),@UrhH#8a{sIzxIK#DɕI c(C|tģGGKK,зA 6 0B&B!H2T= @Y0riK¨LC礃4#@I$BP$Ij% XLtLDILhȈ,GW GrLR;GT ͟x D" (CbYK -8PYb-qK2|l\Jp>H#!Ԙc#,'GC6rƄII[qlDO+Jɣ(] PMG.%¨LP x:QshBP5;@!u؆TR J(7#03몆:I.9L} 5!EO'Lc"$5+ɷ.Z.%BY=PN1mP<#>{9%9:`SlmP(L>p,%@ '@Z@Hť=Ûaz R'c6b2" ,yZHGV͠89$=MUWhS4S!A)VS: MYXj8Vڝۛ_VlE(Vo$Fjh="LuUi!us){]f-7{~WY|۹͞MXZ/EP2Չ?| XBH1buSKΜ ;露i`R(IV#؁ -cTt>QꝠ%;ZAJ9kzZ0Z(U7_(J_A5]|]Lt0ӥ" %&8߽MSXEa}S͂Zr[<5aր,Z)vʟT}ijYk!= 4"L)U7р(]" }n8-&8.X12F0TifGXAhQN Hj*6XDĿ>P c00椊=hkP69q(`]5PCtJTyljey}]FXSW/:chnk*OPCaL)p@0Ά2u!#%@Eic]`i戋i ؚwG&`ΩL.ofėI^oKt잸lR.B#vX xD{ҮPʪ],πe.X=벅UIH2h8 0h?~T@{0>a/ o.'Yn<} yFʬ<$H-תP(d2g @A'm6;ID ~ s=H KpԻk4%gqFq+o'oօaYd6& "oKM&5dyJ#,˨P>X=W-0c2( @-dπt;[n^6sYmWFvNqoJl7lr$r,7%_#-`l+Uj}6E f!BI],`EN4E xπdg @jklgxv*1, oO^yv'wN"_z'-w,2}7MN7x6S \fw8mL=+7.@ eG0w H x2Y5ה @jty,L w S Ouňg眎w u{K wtzZ*x2's}է+ +6^)2I@%7w{ H{g  vi0y@|v@g\g`|yyt OC (J`q!D*J|A6>h H7z, VȁG ':Lٓ /y 0ULXy!;Tv+wbߡ+k]0[Tْ&vѥvl^bլ/́:0/NXܵȑQf2f6GТE'KVt1bjƅD>Fm8?lؠb Laፇ!N8rE 3jYc*G|C/yqsgO*0%:<$]:5 UL]pBf-ǼU;5a5HZ\% '^|9_`Wh"dPVYf=~hAVZi L0Zl>pm gr1gP- aJ4]LwIۅI"'^wSL*pg 5%4Dv [,M-RԔʎ;l$d$CcZb^ 2XD6t:36k9 c4Y[ FsTMptAZ/߀-Kf]J)۶m/tMD:@T0dTQ찠ZEr81ntTx6;8dQH(fQNH/*b]d6;8xG)F2g4{5.'L> So7=-Y/\q b%]iSS7\;b6`"bF $A JЉF` ß D v@K4R44Wmcx`3>q1P wD+DYc\'єp֨ BX/D Då0PnxP"ѳPD#&9ts {%ƶhn̈́ٗ2:! i\`.@ P$ bȂ4LcsG7 ٌy>0&4a l* L>FM'MIPNY)MB2H'p5Xke؄K"Z :#H)T ylZ"o3ఽg ݧ.C)9V`r#J 0D"@%anuWtl,}5AIZk֮w-;2/`* Akh ]5Bː6N9BODok^Fc# L3Ђ]|6x ,<œXx0CB` & 1)<-ugY0r#[ˬǹ֝2Lc|4'Jb[Lizqyڀct n T>RfF<)Il`4[SMAt0>`(CUťxĨB bIq4>AL^Y^dPil}y h@C[꩞^1[Yq|@^dBy(@ tĚUUOU=@t!dM p&%-a}\=<^ޚ&2,0p~A`K[YR5p~`G  ޗ RjQ` hEyD2p4١)EA$`AAOށ&L Zu-$Cd `!Ǚ:Ᏽ05`!ga/d` NIb b"FdK8X($ w &[lQ$FPUBb$D A$&(UA-H2_3\B#4\#M+6n)=!18# 9 Gc}DJ<@V= asLv4XRKD@JMd)nBE$5\,0WaB_IQpB-$7x1HJJKF#LNVMNf.ܤeN>3gPB Qj 4@4-m q::eI4=MtLDneXbv\"?IpVIDT)V59  A$ܓ)\!<`0Za\bdJybXA^dg.M\.hyufg8@ qt!Vkd1@# aLDopdb JI@ K O5_PU]A8< # A>_Bٟ_(#>y&)>竽'$ 0 Cfda~$j'1gQn`iɀipHOv(}%F](i#i)oyD܈DYYMZVAPi$ s!@' cx2i2J 6b'zhnM, hSht LVDڐhuFeU+حQA dA޽ AŐ51TǍj2*hV..d>Ҫ{ @ 殞iꯎ?2m!ӅDXjVxDuTzeMANR$B$sA  B@\\I:ƫXރym*4)v+f ^#P5OADJ,;JP !V&wc9 Ⱥz ϿD>BhC,b(оC-*⫬hIbC4TN) Dm)\h,9Bё-@U׉zd%[XƺɭV+Il9e||nB-P4AJF.> mҦ很fd||DugBI/pQRp=@n0V ngiJ&׼Do(ߢY$`F-E' h7$iރšbk됐 K0аZ)@ippmGTnķm"0#n]G Iiot )^ph, .g`c m 0B1( 0Kg|ҜkYfq"kU(&pJQfdQ+߂1Vű|87qq& N f|Fj0 a^-uO"G"1-$$_Vn%#sD@k!2Ξy_'`2Ӻc/Ѳ._F@n1kR֔,l[^Tn?41ڑpB0h̘s_2:وs Hֳe.{>C ?wn@;%T"0$gwȠC/ $F;AdG{[A&9i:brJHuetfMs~)N 0wYO#-]L@ EE%'v'8u jZZ4UgVw,HaÑI2\ʭ_'%t p5 gȵ.ӵm+!5a_u"VA u (6\tU[5VkV^`F_pvxgv:`쨶\0NeTt\5/mu^@iylXGqW])xIdJKWNnDZ6buӫX9t74XW|E+ FZc0b;+ {S{FwEl{FMp]rmtn?-!"1'k_V4u1qbؙf#MUNSU9v?N0X⁷4؂%XtEXÒ:b kόd F8>kMNo?. 'oϹ)J`Iq llK06@Mė_uVsXq{" C9ȊI?)288)ACG{/ nɠ/ּ.%X Q'[RϭBP0Fe蘇xag*i:ʙdήߊzf:F2xmuT,%;o5T*zɴ =d" zCo*۝@4yzu\¨WH'L%B;sEx`0:gIas ƿF1<۶mW5M^+FNRBh/b%8sk jpg'DC$KfÖMkN9$Wmb.;^|67ɎR@=R UnS̔X;`=Y%U_]@_;]Vcy+UAP]kc789gC4y_Ώ.s 3,-QhEkeCaٗB%o0NO 2Qt~Ӛ]H|R,*Ð.ۆopxH 7o.Ȣ|Դpn.؂,m+$$JI5G^PC)x-0 dFM('Fٚ0TJ`C,IXHڨ P7wUV7IҐ``gHLaZ/<g阮KLӚN\nP(G?*'2). l`)>XH ` \6<,xUn! Ģ o^0.Lp~( /e)BdJBnQ4 4 R !b7"U -%9fzsq >fkqR$sX")s-v*E*ʬ&J[,FH2 `_] ` ڄ#:"dHD`lhkoR Lz;ͩO!߇!mr"RB"@  qd#N"iB$lQR2A TD,  `i\1]2)T2bnaam*ns=+rrp5;Jp[vmO0Br-1ÚP>l1h*2cH4s3EsG©"4qq*7)ƒ85R66K-7 KR-W3?YK>@.!qKL[%*5: xgv>QL<+b7Ӝ1{nlbn" MS}h&LP6 r( Ԭu1pM4RsI35bi{KATpLu,QU6B%0gFCn2(gx:U E71 @a%4V*=K9="46JSH90,쳫p> cf0\(7u#WEU9 tKK ^SB/ȀN@__0Xd'ݬ`l v 8֒2$Bs3Yf80TnQ>!`E7Զ.N?S=V60S]j(kgDVUSh7]hCiri9h ` 'gV{,'6#d,6lpj x`~ d-#[ޭNLv])S.TTu;j$Hniu"VvٞS4NjAw @Ӈ.s(oyM4i8`Wnkvrww+$_?P[xQwO'N^v7;r0gsTIz]R^$ ⌰,Ɨ.@̷S,윐} )(1RHtTkb6bwLAawN[8` s.(북eo!s$-͂zu^7{{#197z48T۷b 8x:3lOWV(e@wJd->5>>9ގ׋7׫-xxApN]aȮPحҔR9%!N·,jƀx٦U64)ْ1 ֕. (@SRy6WLALKayB npw2֌K.ඕ}9(PR1K-UIn8o" Y/Ftm)›q/9G u)h;9 j@ o邉ڬxdB JNr+/y.0%P $PIMZҶymndř eXsF뺳F_ @,S%; M2Ioq@h5/(WP]d EK00 UL?ڬM[;g#GͯU>\e/x/ Rv)1"D5[(BzDNڬn0Ƶ[;o%m7k;doBUwrlfd2moS=ĸApKl]aДҘ&RSͩM~֪:Xi'n:x퐬+aцAѠխߺ囥ۥQJf,.`>j>Eu5o 7_[Ÿ)ZXXUU?:h(30'Jې2#!3g\ldPR6 iϋ}~G>~Zlf}d\㹽ͨ)o>8H ;Մpx$ÏdVl}襋76$Ѱa.tP'BZ^yLs\R~f'Z;zK-LǘX z'4Y槚[y&;Rj}~AGp ^._)(qG>B&FKLٿF80… 8+6x9x,ć*J8yEɐ,K`&4cl!dN*t$DYА1ҥL^|P! #N&Y*ƌ;v." ڷoĹ} 7.ktb|ɍ`pn>jդM1UYxPX%;{V1ңѤEg 5\:"EӨ,ؐn<`EJ9v34&QPٓK7dIbCH2L4DAplZ!ԩVO*G']%kZ@caV] `p F8Ia9dYYq0g0ySi&Fƀ&[ #djBEMh@ ΍w(TQKD$E O$HdPPNUA>TM|܇Z5WuVZٖr-BaVx4NVe eY%]I%)m+b1Ζc 8PO1re g4HK:S $K,w?mQxx4H_z ,qHEnV\Tpf5ga'<'*)>ftV^L)]kAlyVj czAzV^ZjAk+v\%Q90H G3SRE TQ5B qC]1S~n":[MJMoi |A pZfÿI4k2^ '1 OS$)Uk[*4KlqDZhQEY mN6xӪ/mR7 vfS[K' =ddcp;Ftvz,]P(Ikz CUCgl&ί_sy )@AmYTcLS /뤡jF$$w (IFԌ*[ lF@M7rNRqlp+\(a)p@aIg k{.IM7Gr FȔ8O}ׄY e9 MA 'IZ$E= %L*՘`)PA lG-`bH 畉##Qx>cÕذ1 W=Ebn#*⼋̓QUx,*Y QeQ\ ?1P`4J%J6 naIRpG< n#A<5L)7#GnfRCd,4*vɐ8._,H|6U@}czJY t*V O MAUH1I[E/u,.yKzq%/ ahSc#i-*X 9Ml1 7oζxLāK/_!˴v%Rap'>HRf9s_(r4؋N'DtҰ%Ÿ^-}7QR{`g5ɥ=M`W0Xh\y e@Ј`E~W1]EH&&~Km]2[$`| ˪DHC$1 |eqlNd5`R0> b8s&Z}b3^y#=!#,+QS9L;@FCY7D,IN[}e(R2aɘM%odF|P.uA1 k1x:QÕ3qZzЈ9$_fwzD>-}(kizʎx;*P'eԣrhj%n PYC8')Ue;صyiĥM'<5 ;=0CDB ɲq pG3G_L6[dJV5 4ݬPƄAWç/o/үov֥Cqe?h{_`d=JRh\N \y6LVKa)p;+Ƅ _KswSwovuI;q{^t**[ӬH` KCna斧D%VIiȲm$ {)A1 4TAʜkkҼv[NpHX4Bn7l.MQԀrGF`"Ir[upymZ2ŷk&&M|a'x P W4!0r`r'w v)?"?W:QgӠ;;Ti{3[yEYfWiE&<M'Q>g7ue17Ay² 7OU=Z 5ng|9,XwaU !(S&8r&P  "T48Xg0 <ȃw/Y\' 3W nw6#9*6UR.$^8 `W)1"?2rk?cB\)Z .Ug6(r'ׇZ@x5:yQ]uH͐ OT")wjXdhDvw>_r"qB X)ieT, $gw+_8.Sq?o8-M)`` Pg(cq Ђ,W xwPB A%aC‰XY,BI8#ǐ搲ϡHE+勯uU[.!U[($v'T+.'A3I[yp퀂p 'WtǠ(FIsHy.1VM̐ R :RvաC|tK-rr_O&vq$#$!]G>I1c~55P7CqJJ3 5lY}M.7:DyElIKi S 1F镐5iGSْdC#r>'v0H{X(@b[`+s1`(Q>uQpv v P9}P uM  ϰy YgɃ1R`f7T H&3 tCrRY膧աArF JC-Xz+ =FPun"R=`H΁=@wQQX y<4x98ۗ BYVjisn E@1d{'G)R03IƊ^oʏ}\$[׫1v{Z1Ao 8\E0QP @ VfpbP 8y#(g}Zpx +(8~Ly]_(FH<I"SȚxKWʊyxUaʭ`G22%Sb{H*"SD>1s.pxwX &\` *wJb C9Ql#Prtxxj {b5h4yYq2+>3/#k#M5wYj?6/V`'ߊ%1awHZvk:SfQ>)W `@v\Vv WlrA'PJ,cp1KaɶzM!is;Ld] aRzk"29m*t%epB,pe: ~Z$k, C*'a\Z\W'J`4YYg$G\{ h?xz2s1r1;:ihF{,~CѦ?ڒT?7F+@ZGLChZi[%{A\ g!$7 R+WTF |gjUeWUr) \znW3"B/){YʺG}A5o{IhH1h>F+Z8KL>If'!a\6=[GŨKhAps|ƵT˥kMs`\L|%M<ȥ^!PQ\p8R($"`S%p$ &E Ll(.0&7P[\5=Ny%f\lhˌxWJi;iuAut.%gy[! r_CSFEcuf*rx44A4Ƿq{\.fE WwF rSЦQ0[P4З'Q},\dGKTȆ쉂<-{y&+<- @v;C$g=s\I"eI!4lP#ƻy%ݺ FzNVQ`q4bh+K c|+/m*6'^!-A uD{L\'"rgq"iΘGkyZ$,Iܠ MYm 9I wg[ݔfN0 T]yE ]ȭaVBy,L(,LAŠY"6X1!܍/c(85OI2NLb)95  oa=y` .7 Yྜྷxo1syĜ au=sҫ%GXQG'jX#D3UY5f, xuvAΎZFnxM =K0 bUWZPW]^ml=e>nvܾ:Llz"l "32/$HR,/蚬ӓhzPiéb@E"> O.b0 nEAzPG M] k~na KÜ Fl.no} .FUuSQF5ތU,O=!KվbCF9 S+YDz"6 f 6Hb uV -SP P[9A~=3uG̏mqO @Z g"eXw qS6=u+qL-#dP\A3IX,'Wq5B1 >?@}XFjbad,_mQ7le#UyFO.uHnLU #O=@?R5 YbNmk%^~U$b@` S9U׫n#.$#!3s'X?[ % ,b;X…wDdqa {&=A*r޽ ilj"ĉ,lI*4[54gϾ3UTq]֬]V;Ti͚1K%Fm8uW/A}[… &`#70ʕ3FرDAq_ͫ Y["صŘ -Q`pkذR9&>LcIeٽvX"-'.\`c@Su4ҦOZV^ :,3fx뭸4KlԐ2KA6 ,(@:08tM5M4 Ba#KG~,"N\8rX9'O~r ^&;Dp z̰Ė`Lj*>9/++,P0 h@rP/&bA-4RHA3 ?PRDkL90.m0 " PLɈ.JUhlqjƃP5GwHUߌ؅a8bЇ }I[I|(DB;AĎ,x) )U!רj|Ӫ9ߥΡ,f-hPR0D@K@ IMY+TBUxttHVMōSփnm "͢`#~S6fp C2Wv`u%QZi%K(BMvs^9:\>0@:i fKyF;fi>`)bTꤕ]ÈFfEn`xΈ@tt ͏f1RW>!0kQ\" ]3Y`ǁL0yĦc?vZBq142h#qq j@2@L*i 1''{P WLyuo(Kl%rD(gfCGi/4 F%KsDBQy\cnfRPi4G  x&6EΌ&)'1 E Tt,%PzFVJRIBբз03@kZ:Epԁ[$3/Bn=XŘh!Ǩ4Fi(S/ a)M bMPA#kx ̥IP@B $aZ[s{G3)UnC󒆽J}5Q4DښN 4uVMڋNJ^ LUjum8`9~׊V7 O3ibV CwjQAP1ġ Ɵ=lswSJk$LG;Oww%Q$p0.&mc'w,pF|&4z, ⭻/Au>@^THygL聶-YyoE +ct3xܭpc 0LO/Υ)1!{LޱZHl.H/3`~ IwϲNY[P5| _x'v*';9r >'ҺKb>33ZYN hxw?ϻ??s.XG@Y+Y@;{<ʁ T€(*Xb5;[ɕ õSx(;, ۾d3lт3d$L[й>cBqȰ') =?aXr.Gk(:xT  ѐ0@Ȧg//P( : B,lBD+B+ %DCR<lk$[(b#*E=p0;Bۗ+Eԫ(*F+b1) <"WF*:;Z)\1 g|AYۜ(c>k,ķ3m"5 G2>1 G-:vBvlFGnG'G9߲?~GJYHL ƺ JCZJY2>[ x0i88P84IKQ)55Cψ(ě4CC<$Q5AʤԼ9|L}~$1z4J.,s/8Bɬ!,r( 0t˺@,$c;̕:R²Kh ǡ>C̜T+0+I?wO-PJQǠ8K) :XTBҴJM=jMO໣ŒK$24(F$P;L&SŴ3N;%2kj;ЄVu ZBUGdPWbZdHdhW"$B[`DH h MTXQc:TMR;Ruͷ*&" 4Ճ ?^ S$;F UՒU^XۀuY XJٌ]P0C1$;D%D0ڣMS]'+rZ0WQx6<Zn&W~}E`Ф,l7~MH|P)mQs; #eAY%V7YES #p\ǽ2hi d]Vg̍'IljVԝ8MpWj(ڵ]nH[ܝ\M $WX[X\^۴-زH^ҋ WA-+ (ݤ?d[)ܿY)k֟Ҏ} dee߱ق$Ђj5FmV!T]e ^EUB`͆젆M B36#u ^W ] aqm6uSa>b[.h')eb6E)b(V+b'V- 2.Z$4EX: cdq(-c+=>61k-19pJ(! ,^I$_Ӻso;~之H1"nqȱFy+8Ó(S\r!ubʤFMjmN纟إ\hK*Uҥ2“ իUcǵח?c +ٳhL4p%s[3f~oS45È+^x<Ɛ#KVLѰȈ &mʹ#Ư -ZhGO9ėE /۱s_ʻk'7J\lV ~ :4>Ĺ_޽Pwwɮܜ5Z+[n8o ,ѭ\/M=}cV~^ Q\*N7gV h38]t 8!a]wnmIwED\R7 {6|:S`} 曐3ܑHW?:U .i'|4<&šsEňݔ]vbDuuTf.padz駟:&5TcN)WI$qT?1#2؎SX֤'^6.Ԍ/$WN c"D٫JAi\Wю5:.35F*5^h`k钑S~-WOI!k*:ThnTe fP)I0_rjqJ}U,޲tq(#2ה|MОsθ m˖jmWkrZ=@}D7R37{([tNOZ5K^evLPQ^$*5ً.<&,os6[ʌA#. Ug7m5t+yP'a Mdv6Ki_\cŕ{LKǺo<w֨zECWO\Cm9Pcӿ[Ms44J$?anhiJtMvaod;7aXY; {AiqI|ߤ2K]%Lbf<'ڑdFf\DIN=X)AaR u/\q9582Čh;wFə/W4h=&栵EZ6qcb ΄35Sbw5QOBGٵerog bnh+$LBZ#)B+5*.iDҍ^\7Җ fZSrn4-Oz#ZUy7VSJЩ5\V}ȊzZ Mf4ZެE3QVhl7V-m)L*I>Jܑ?<5ޕlX[Xag,&h=i񂶴ʵC7ղk BV FOJY4: .Wr*vMXuYV@mERu!*dbheoiFV4j5W+9b8 ~2uA7x|V`6ÿ@-9`S|30.Nj9ٮ?t]d}vIJ'Q=F}#=iCеr/ihBvTP3{dŒbv [_?K4i;`n+T'_6#ؼ+p\B#,S+NGcܾƹ5w MZuY(&sVQc+8- _ၞ8Y׎\QC4)Hs3vd!]rq/nR9ZĽ|y͎%.:1Zoz'pup qV9.h`ϴB-Ǽn}36o/sTư8-{_a~4V-ue+v>Pzo^s׵D_FU̦_So+Cٻςlc>{@&;pFAhdq#`|ɧ|ffzзowEV5T^Հ}׳](GF(U@Lo5l8~qg~緂CW~~yQQPLP7dW]4giG 8u ZXGZ!2Ua{x^(z(Cv H4v-ww*gzDz$&Moh7Vuc@Ch@ T4x8BNGnbQ|^H'{΄F°EV}a8˦cXj(7wvn~pc֗{'Wp>g{hP 86Vrj/jXs(OPUbm|}in䣀VxE-gWnX2,~"SBA0 P P=E8XX֌hXjgK)P٘(鍩&(({h{hŀ 6ȇQ` : yn y]Pa|h^dzF^!8H%g(7V׎)'t)Q0qp @w5i8vQ|qE9YٕJI"v~5v?IXZs`ԕ~BK-gc9fQPU` mُ<ٓ3t2vYfS-Ey]|ِ7naUQ=8"֘V u FN NZPZ`` 0 :o%fMIy}ÚItף.[lI4rS%6[\I .cP)f`p ) FxUI\։4eq.jR)bH醆"){™Y`y6ʙf0vp J-ݕ9Đ5)QΧS֌ؕ#)t3swF Pa-0 /J3JВ r  x*٧U bٝ#hL Fr}&XiXɂ8 Iy` c*)M`m(2ExϨ٦p pq v#|[c`  CZF{Ҵ F*r֡HuK^'

NS kJ ;iK"È"6/l2W~i<cx# P'\C* F6M8M1 21{챳<7O0DLF,Ȁ9paeC9^zz8˩iCcp yTUmL[w3Oǰ ʌ{5Lc%P**HS| 79 é ^y˃A|_\ʳ7UaKU~-Gٝ݀D \۱`;WFlse AǮ}@h]&p>~ʙy-ܼպ a#YiViSݪ+yaԳeGH݈C4%Gא$޵r޵剑}OuDb1 k FNH^%9,ΙĊɣ ^t].?jbЍ!~Km넕 sjwn'{Τ&} a cM-Mۑn}@T:ӜKySTƨ.}8ijSٗ%CԱ6۶>Dz7B>7,j=mמ+}jjy ޟT,ջLB)LI}s,ˑG2 v2% UЫX e-ߺK%= @,EU@x`\ /f-avᨀ9+Ht/5sm^9rhyW|EgOJ'.i]^m%!O7I 7]tX,_yb Xb!Pv4)D8/G%vn2/_jŞ*,ǿ7b ?/~֖o~ ȋ`?$|.xxyB/^BV#N8Ki$(AZcbńM81bdfУdהVH({b W1eΜ\nݪճg+OzP 4hiQFիTDI*aRv˘Ff,1۔1jX56D_>Wذw-ww%O|-[Ɯaͅ0⥘ouM$ܑ-yٻ&N:yT(ѣ $mBSRVj7~ QD+U̎ӨZ XLN7})Of۬303mA b5>s-> 0/хy7ۉB9.9RJ*bn j8`n*+dpU,L 6i/NQBS`7h铸? tP5EiZ4F?~H0k ;̣-FBI\k^ZT1OU*5C@2-YtH.l(΋rW%ӝhXbuaX&^pm읆%cUMc=[?ԪTA & orH3"EH衝22,դײ'f{BUQ.:HU1,K Cfb<|y\ywUU+? |sz25hzCuGԏew+}Hi;QCΝ)+-c`1+t{^qCUe2FU 7 qh=tb~E쇿AѮvBR `;*Yj$8! `WYB'k`<} J}1Ϳ.+ehaag-) k2y_ :Pm8d在qP#~QЂT.Ul ]سPy],At%2a5.4Yt3.XsI8<ڐ|ThA2U*#RZhdY;o-% W3(iI[ 'ֽ}q{X(K*WMW&]- ;-X]BcAȄC: (E<0+O'h:k.!f2s?%vF4^ !Yty.6 kd2{ɑi-WJz7KTā/X1[( @UBj- Emr[#Έ]AB:w7Ѝ@bإuGYл! VWf==L;|w=i{i"ЖU-`m̨@Hb+&a#]qWaf'|JyO > 4Z1$9qjb'-|x5m>ŭUd -Eс|4L)hB8_2T 4`3B*+xefm>Ӭ5ýmjw>n˔PE֊QT䆲qu<4bԌ!zGG{KCCJC@emq:]}GLQ/ͧ&F0XjǷ Xpo q/lk=z͝h`LXf vHpKr`3{|U9.V*ժ{ɿPq0'. n%sƺ0{T>$w R>797>Cm+*:*@ \@s(33W{zj ҳK2'h&;U@3@C@ث=`@Oz@d0-x \89;ABĵmr,4+H0Ɉ kȆl0$whG_HAG:4H,ɜ~ -\ tzB:T$FldK1ƻ|dK<8C4 :|;j Y-T}1@rj\SLj* ٨*-03KjH/Z0ڏeR54.uvZXX Ks(-HچU `$ی"תXp[hCj=29꫐qB"j$əYUŽTmR(G@)0N8!\Ѡj g^MI=1]k]]!M>ݑ]B]K0^bPY𥾂Tt^U"\B[8ĕ 5DuU_(LXդ ;5r|`Pj0L\Zx2 Xm E۴e@IX`@>@GY n n ^@ Ĕյ`_"(C`+"FL›i~Tmfa> "Bv= s*$eb%Ը 9¥AQL2NAB=45rٽU^ C]FMfʂ,~suf`Xze$ 2I.#QRUZPY?O0z"e FdKbeU@0cSYB;#B"cnMeVfVEQbvu`V(Lx$ +h\@jK3`iKMKsY\TwN7Sxb`[fUb;LAjX1 > D ^~ `\[kRA=h-3@iikufOxCmF];'[ EFmL`k{nmccXcq#2c'@f9VPx/Cx90 xU/={33oS ;MPF*sOv:W-<߅aZ&Q3 egDw}`ہ:@<tQbR cCuKhQee'9<(`\πO^EXi>CSShK@<(R/ѰuԆavookp%!wZjWN{9]Gyԡ Z69KL5>o>`nb;6"ohQ΂)ho#HD\* p{7聼?-9vwpnm0H1("igWHvyprgfEwvz渑$.zDz.{C:{~dP''4# {{?{8B@Y@y$?mS;q N@\|wa ,&„ 1$KWnݪeW+U|4|(QJ&M冘##4hΜ԰BU!B5,ZPJ &qb^)n]-FnqIgfE#Lo麹."'-LÞ|W':.thN;5P¤D)UmʩU*Rq#|CHL0*6w+ Tia$5I "f&J \L}1“R̗-[ ڰ k/$] ].sf̾rI0y! pX(̂jqM<=Tp0 #2 ,z!.:D]' ;b?LQz$464NQl)19԰ujP~ނ\ n7sG@ryK0|$ խ^5զ9 1w+ w0%q e7A $ >.Axns^!D@Pe'lBw;LB7Rp 09E!@$e{c@'nJ_9a1kۢEa#׼ Ql+aQG#(@"5`@pR1dH 2es@E dQpI%A6:;*BВ@PIH-R31a#a#RDtF:LsO͌5>͌Ωǐd0_8V񐄜 ŵ;H'(IȴcO* Uc)Yr+P6XDo ؠ,DBY~  X@ RÑ~0ɴ vMqӲT|ڢ17Ic"`bj2=oTG?..Rή~$1EO4g|?n*^d1+ ~h')J:BaD  MVi6&E2!괵d#-R/}߰0vFƚlK.ꖷkoؿᚓT\F, *\QecUԙl+oDH%(! {WE"r +pfA p,aHDͺZ2⍞Vl2aryϮ2AbjGz_UcN=2x>@u9G rLJ^VBWe *r,A @ 4/^`8X7` K^(@F>6[lR0+6cAyӻ]ͨN<5\ 4l; TTTQ.)DztR@G ~6Bke, T_=bAxnl f>)  ; +_zF8}rfZꍺѦ0nL7rtK1pi)?ApQU<`Bdf9e=cQgRGݜSBTʉI<1]V~" ܀@!$YL eZ~LQdW]NS0y_X$x&fTN /$C682t20ѵ1gv2!eY|&iC$Be=Jj&lk&V .?Οfz% f: gZxd@&@J1۽ߥU=llkml>%)vS>Wa9Sj=#IW^ZSR v! EdA"B0HkT1>k'▖.@SƣjVA2ߘ`2H qCu-@QPazW\yT@ˤܖx,Q « 9bfoګ&NE>5Q2/`*暓\:em;o@R)@zhdUUmx}@XUȉjgf~:p}"pևkVBC8@Po'fp*o$ŭ)DjkŔ"z)r `k^ꥴ-/NW0@WY >X-*i&q2 +4iN"DSyc1n&wZ~ZaAVpY@%kq?3n"rۆ9~*050##$Oo$/A%%?(EA((WOuiʁ怌+op#yUZmV%B5PѦCm!3" !3BL.Z$C3%{S`56טj~38)4PZ h7N\z?=I-;=..!F2= A{KBAC?s4KtR8agz8'QG{O)"I3[Ma" 3E錎햫%n5VN+P5$FACB;GjaC/4WSUT+lUU_5HHmn')p6Za5D10Z̮jn03]^5_ Y`va'Q!%;Tooպ[%d0eHx0+e;Hd i7Aj[õx,3;GLNQl/Ygzn9@l7e1j)ĺQw z=Ɨ@ ~ 7kYI8:QǾ;ta[FPzXUD$0x6pa'2Ƞˆ*ThQG'NԢE-fek$q&MJ$tɖj5ziRKt5פVvkV׸^kת^VMV mZŐ!;._p8( fć"թͮO-ˮX(n`0㠱", 5ܰ8́ DK NCT〣amn3jLȷ2AR(&+ ;p‡3N#r)%, <=m1#4B>XZQ:"w47wls(ԁDEQrXWe3UJᇋ3.ɉ)[<!)ī9̥ܙ+gt<[ӗ ,Z\#ruQ11RȀ|_m[ W1DH" [Sh)Z .B s(#R,曣~Y14R'n2Fkaƺr9M`PlxY D<@"p_b㹭y{Bv)FrcHFjF>/}ws~2`bQXE_QǬ,OZ~! jns\V bT |!=mDuk\ =k1w(܈F<  Xۚ<dT:CjK{NJH> 5 D0 +er3s*ZqbbrcXL4pZZ)3GisҬcWVda ;5A=b+SP_Ӄ^é`K&s∧ ^5 ~p>Ur2PoVm`a>eK3iי(d\`c@뺞1Uxwr=_F&S"Id2/E`3U+J TpBr2M X=Hl@[~%z|94ð^9MïsA] LnXbŴPzw> u11B̆zX"rC")"oǔNpriV+Q0[cFn{ٹzcl`b8Q*^cmAXBJ(&FEȧ[OK()&ekc.: dIXi bvEj8I%+y]uJkW~26bb(Ź3O)C71-on<\Em/{6xn=Ӟ+[pOuc,锕˿=#$0353 nCer!m)$.G|3 =*35S~S2*4,9P+g5Q5FFBt678TaxS16?AHB#kPP@vTLSתD1ܨ x2 ` 2A@j6G? 32HGa)qM/4HS`+%I%7&R4Q289_K+h04]cRd5GP;↕@ ߱ ^4F5Q )"p-YjQ免 UH?@ɥF8@1rS,u֒<"+TmVu9K9Vo&CEj50Y3 "G,` Ơ 2+E~ޭY )a-HBjQZ2lJ ]@l:5µƒ;S4Lnh0th_Cr]VATX Qa="d@ b1=sC*a װH-HdoD?~ewA`E~RlG_J#mg55|JhWu؊vY_j1y"RH@."V 2rFQNʢmifMnmFbm4PS5"$+rFn-#T" ]32q5.R«JKCг6Rp;&a# b(/>A>CwsNzŅ5ԩH4+UpT4(v@?47k&һVz9φwKI`?.zjm@\jGO :}% # ?GmNvÌ}bI*"vQ,k_Dԉ w81G#͚1?dϞnP{uQP@^a*]˛>+cXzz0}kN@FPn;Y>mLX:DGX}g.DdS2p/X|gkx)rkH c_SC0Zzpnxϱ8z@'lb}Wά bwȨǙ5>p(S5ORYZK({.t]D?Õa9'~6oCZJ5wB~o5) 1A|sD]:VNBga0f#>U֥7lHM+KՏ" Y=*؂OL^D;kE"V貏ф!_y=5xb`7&eMEcQ&V't)ftn[_{Mbי%5"fmΦ%EcK# +=:ؤ@`꒨ҥq E`1f-IclhImF!Y' D hu(xx*% 3 VF!ü܏AƝ[*i5QDFԷN %xݖM(\uNKTL{UCRwċ8s׃0sBn2kM uEǁ ((^by%[u,Sh4} M^y#P %eJ9FbA;SA_~6w#gBcH#i CBM_%ӷw2<s%2x sUKK8P޻bbÆ ,ō7T Ĉ"4hƉ*T1H'FÇ,ZĘ1cΥYĞAӜ9qt3kD)ѡׂ 5jΟTU&mfǎƒc8 ۲`!Bڽ{7޽0`` zS԰aF 9X":{ ]kK[B4BoY@ %X3tZ.ټ \…k!J"#HH=j8) NZ´3;{Lʴ۷*?]ͺװ͢MV[7U[}W `1a)X ,v@cI!fUfEDq $ZAW\HjVlkƛ)@)v[q\=]!u$RՉpI)D.I0TUy@YQi^J]c|mdVXqXbAc1n x b @Z>ue `fu+i 08[裬EP[5M.'lKe ]e =tcJ0Rtޚj)89yߝ~rj?b *PjbP 2f|zje gkݐC ukʻj䎺 r m![,;re9 _67n#:E4")5AnF',d0jD W!+[th@MLtbu# r& hqs<- DAt^4hsLJy^w:j3?4=?q$!eHAI lp/91!Dz9'7 (׃VJ" p-2 ByQ咪(za.QC XFgNbLt贞(ڬ)ɕGpNf]`n]׼WF.q:y T5YOT# ]/ʍmnsJTZe-AmI3 [5.B#hb 訆-yM  7X6NCYj}8*!ٸ^`_(EZN K}FWKm-rqʯV.eWbNlE_f_N$sɤX 곚y4w:i#κxҡ#n{ҖRM_΄I^}[SHWo Ml's{ߣ\d3.wQMǺʽ]6&~] ::˔WTA!d<|6d>tL,:vISX96%XKlŨ4a Og(D`M s]2ۭ|LF&kh{XޗCU/͝ 1>GSȴC6jC {өP{DY9/}3E5jxH%mA80k1ϰgmm7f:E^X[4mXv劓 `gmz!cX[7#W;̢cnjޜTZ؛t@;Ui~9f䲬ːb_Q׬]*ǡPLU[aݰ{1V2Ϝ81)pg@f:5vc̀jLKgyF~Kvb{^׉Y<9Sy";UQ}Cn/n;|@sP1j ZH׼AI#vp`ULVM4ȣ7' G/g1pq|`vqg 7T`{b{$_5>koCEAQ—g>5 uYf+?UF+&>h٧}3*6uN^yEH ɴW) ՂF5diNQz6z0 ͠.a5zQ&d&x{_RB06wӁuK4(,CEN$5huP1y67h}QpF^@MEhx ~=v`Fu$Sآ-WV XZ ]V.F1LuA3hqvo(vq*tX=p+KPqwC#$4H_6V,R_xlܦz{ ?Xi( (HQGP `Mld0 O\7lNaaX152/AАFYs#e z?^ĉ%#yҠ ! =p6lvw%W.i'BqAV96CbrU?Ž#Cr$=#68@+6=@6:bhtoEg9 ny0] 64s9UꐗZ})Bi({- { '0`f3c0 3JE9ʝЃV<+=W{کx_0 3rSN z¯U7/S=& ׷~KΗ;܋ߤ jXA8쐐vPQ)%@KTX&Ux +?zNIZ Ye6'_͘0*AZ!g5l[0q'k|+LZ6wP-^;nnyJ jQEP[S&C8г0J52Rj!1\%vQ Xŷ6ce\tUEr1¿2uTr{·ZLZ@ @ `]5+ v"ޫ$~AQPUHwRp 5XSLSurqoUxfy'4TSn*`zjqQ?5Цw"kwԗcz+ ,P2/ M9yh4&=PJR~Zq`q P <Sd@J+$Y"ÁR[C3X{ٵ:{|K$ ̇H*ͧQ˲Caj;~;bD|) Ĥ\1k zʧQ$ MD mѱٓ5y2lPmK˓.yj'8)2CS+2 FpԦ&KKaD]2 2 (L=: %%ՕƢp Xiw ۈpi:&Mʫ=U5fX|˲ x}H`Skh\`v uop1 Þ([/ypFowE0 EYPRY@:7 6MvF[Sn{إT8 9U(A ޙ;㝷r IPM%@FMchU8ڽ'\^:.Z3L inkMmMױ|uZ4zƊlKV"%1=w~H B-8ɗC߃L䷬oJLڤݐb 㛊F=t FۄF.ˑaeIZ<弯Qq/|/æn"lg5g E{a̙u1%~8v,_( èC!X4!(n#,#E~Fh^ދZ0vHtu".PsI˥!{Gǎ3)0/Y^ҿIV` }f[a+8Kݧ;f2l: Z w`5 N<SPt[wTbh[҇.Z%<%‡8bá6 >o>nٜJ H: XLVRXx  ,^B)2^ȱG%MP1E9@f yEK(dO-Q>4ARK?LC8s+ /^HF emZmɖsuϩË5Uą ]i5k,! A*jH8ШAT?i AcJ&9^nqI#?2DhZB,B op{hU3wA~x,!o, "$r- ē`)ü\;'0D‡NhBKlQED,,3s̚B҆, 5Xs 6h-v  h88嶬r|H%〤23s84i&85@x=*D?mI95 Ի!Dˇ A,= +8l0 '>'z KTCilA$& _X$1 ;,Dj 2ukP 2Y!4X{f>'qN7~b%."@ MuLW h8{6kp (hѧ!H#Rz, C 5 -C}/VxPpfl! zC;VgɑXԠQ6!o>Z%AykupGj~L>(LNݖصݨ|i>, (nz͗ttb',-1\LI@Vp9fjKhb>5U1poN}2{9E`ʈ6xfӨJB- %gs;aށO?!?Ղ U12qjaw؉\V<-4|34e҉4w=N *bF*F hځtÊP4T!"`3!љG>F,Lu d <\ǻc+h/*vT)'Rcִ3 hGI BQ=d sN'8VzK5!h7$|F}b5aiMA gOsFtIQ)IN"=`մEmK+xH yʢ^6pK%S%+Ye(4=D) *p(J8UB$*Q*L! J\&acH;q O AAY sdfΈt@h5MmD$ @iIG95.u6Mjrg蹨3Mds>4X`:) .CscE C#j g,L3*) x XDgh/ Mmj#D?NAUUJ A"h@E'lԤ kNCP@DF(؁`,3X )KUkR}:K 8@\IuwD"߄9)ãÜD)SʣO8rk5Pv0J)pPVh])s[W roOpuX|;T!u\D"(HMCo34LS& z~FPT ,xfITT6$חV/}sZaB>D(XlCjQV J(9 +>[<8 Jfk$N0$C$#K,oytvp\fH6i;].߫y;϶*uFzI`rD@7}KR8-K5 ps9ɽ貫Qゃ+U8]R>Ab ǂ>ʙSXskKbډ ʉࡪpo Ikc?A'S@ ;42:jȆlkN` ;46,3s cE<%ى[1 0x1 ! ڋID@+󱨾:B3b@ψ>` ]>->h; FwNP?o)ͻr}B0*q!'dD.HI ˰HX1w L4wl!(}~B"J;J;`3$4jE#)FHH˹-3;tI,H/Fk Lv+JR'4ylġ$O͌ǰ~ M4 $2B33*ռ͑j8ټڴ9N>0 4:7*qyDN-I NO4kD DOHʯ;h K$O DPZ8$%jerR Lʍ@e\s-4+IL* %'MޒIC1 Hĉ&DŽLwɝӯ(OS$**է3Л=83(53 I T;I$O]Ѝs(U)&>H::2Uld4y$L475*:͉#|G`ODCbӞ@B=4%D M;OQ.KJ+TYM]R okh/YQMRSMl7-zaUt/EUaȱBx CIɑR\NݶmӚMI3ffnܩ$J< ڭh7?S"-3)P7dٲ껫RVݪ.˯_mK,eӪ*&i9aLф L0%ǘō2rd37ƼʗFn{ͧsΜQå#S)۲;Mͻ>LR-?ʶt2_5t׺wCny֜Ix?}q*WMk}h"YiDY<t 86%vywsH v߉ z,΄M5|W~4SWӀ-Rz1bYDXt4@Ņȣavye=<;*RCz"1ʈ{5xc|9ig9zEiW/9胅$T`F(3-Yd5H[VN7f9MɢLB#q 5諰;}:Sk hKp5s^`*3Sۗ'yH睜;VSMJ#4ނ;MڪuVukʓ:buUVӄ3ݫz SN$^\`8[ۛTZ 2s12zquP&,RZ:|4 04\4@i vbò-U#P>=Ւ#CdMV40ǼU@r(SWf_usrK3u]Md5-x5/M@~-.2$?- ,~nEpw+V_m mu=Jm{Or`jsoÍrӭ7X3.ϊ|fQ#L*Kl5j;W Sz1D_ ݬV߳; Gڵ׶ɋσf?mE0fb=Y/P7q7ʵ"_B>|CC oײ Vyd@ "H?h<qZdj@Z* VE.2pen^.(A jA6No=!F6L7` ͗FbuW ~߲0X hXIZ'KGIOQ}õs2^ +^( ?F ]ymtU%1mle|u~o3gv^kF @z^sոI?Ty֍-X޴^}̑[U%:oH;vX8:[iOsr m>wz߲w=8|ZMTS=|V}|}VU<Χy}o`wvb~T P X@{FT7yU{gx6gtcX#%ruS:x}7K|H}6y<ggHSInF*~``P #D 4;S*S<5SSF688xA xwĀ@(t([)6W>x4Ic'Kewc%r=BsN0 6__ԀOCy̧ji>}lxw҇msȀ} n؃r{keK8r4Zi A0p 01CWS}&7CE$Ksq`d׊8hAXӈO|x8D<_5n B)cp8bԌ|h$Erm,Ew88x[XKEEOnE ~y(Af` 7cpYUĂx|i8qHKPבA'Z&GI[g 5Nb @R E鐸(q 96SvuyONi HIRŐHrSvcnk7 `NZ7S^W)%HwijA18h'J@Zj)7I81F :RJI9aրwٮИu3%] ԯ>XOIkDg(/yʨK*֊~+6QW@T0$j]spJ?മ PwBuLijD[ؗ}STuʴ7y*IIP+:Ds5i (P P05aK1!*apu{ZTtvtYWݩiuu5WH=kL-HD X 0#ݪN0b@*g uJtvjg[Zv {+ \Wa[V[OQBP (<["*? Sk@\FLtO75$Y*8sp|g`Z5c{OY #LAt jƷn\ *bP%7@H8 Ae!5 W5{ `GD,l9Oܢ$ _a|FHL:DK?hLlo+-\wx|<H9ßÑx +cţ|a{6"wd{ˬɋ<  fܼ?%AWGIMDܴʭʯl  ( xxIǽ81/I6xĠW Ͱ)ٔ׫?ʶ噢Rk9λa݄-D`kL 4Ư =0 p l˷˼ Uू\ f%ʺ0h&UeH:({)b짫A][m$$Ҽ v4Mӯ8@ |-$>/S%Nj+pZn \ cd^-j|lZo#N$^׽ ȟɥ< $܄nmY ȏ>uWQd陾a0 }+O͐/ ]Һ`׮>R]o<?/l#P*Tթ]Jّ캠욽CMOsxޥ}?a[}A S:QS)h  yt5?>MN.6~J Nr.v\JʥU?ٜ n t"{O%b? |OX~;?$B/_?7о\U` p5Xֿ(j-x_(/%]o߲}|  ,V`1h YcaÁMhEzձ+!a#/)S޺U˥VMTO># PԨ*rI$J*WLeK<:4 WLYh14N\:qͥ[wyߝ=&\p\n-f9s Gv1C^kPaYˊ ִ8~$IkY)&Nt BG&])*TZQ'ZdK(ٵl{^%/y境+νkNek;{Vp,iNKm |dk%\bIrډɷN89j <8b " 3ѡK<"oGl;=q3H̺ϱI!)S|hET#@LKZTI$"i pB&L@"*FNDQGp̑at#'kH$35Rr2h|rJ*{!Ӗ"-V;K^tc$0@MM '6>PyN)Nؐ-h!U"L$Xq?2.h|%NL풅p$CCTꋨj)i`dS&dcN+ 'YSHYRH< e-T| Em%+Ӂ"+ 1$j,- :ƶ9aQ@7N@'6:|dj"jH,djSJQ婨4r~UgZR`#`63vEg&1Z>0%b8 9H$zz@%&QyT.r[TNd@eu%*I-N ٮ X9 6(>`Fa;BS*VP΢54Uؚ7@..1IUvFVv=oڤ# o7(Uk'@(%\OI+9xx^ `x2`sk `!\\9!rCq a\u]bp` 1F<^,@Ay7&`ڲη(#S@79ժ,:gP.d|2|6$a:VƲf8uVM]Tz; lXųKmӦ6FRz]`{DXko5|L#2V2qmk{$Ak)?x{#'Pi8i(bCDj?6ѿoI꽽SD+g&2I‡9R|>-t35J- @6 6k:CGp:Ci[KxFO`dɑCoGDa#$9%l$uxP&"ESTX6B~6[XkK'5^"7<{\P *Fc4GiUA1x1BUFEF(DClIđjÐFp sPL -yދy )\G8ŵ)-JG{5# $iiFZ\ȳȆEP4|'h-DPcp؞9(krYIr6|s0xkcSB CʤR:4<=A٩+2bwUQ_J JX=)0$=UtmD@5U bx%{XNLơH\Zܧ5ܔM\5Pr:iY >ͩ$𼹊% r ]fR JX>@,$ DPSAun\؆XK;@mޓ;,  ;_ ]-"5Y3D\U_)j_Vz+_x2-FF)eR8`G7)x ` npnm0M*x/ƴFaN=+ުa]$ VaӼe 6_zu:8u#t⾜bSt($&I4 p >X=AcmZGnK&IL@XPlYokOw9/q.9pu>më́mμ ^{m5g\9_m>?1KU&f`f_ EP`]ڽ\C3:ҋi8KȂPkYL8>[@EU%LK.2,mrѶ12/3/ꥎs$2}7\"mP9-'&q@7;`@yBfMG90`'HV?W_NzWu;0rW`<('Pov r"1 oxwrw|`]p^\`G.FfT:;iK`:_ɵLnji^vlh?ς@?Zjl dFVXOI0)Pxo\FREi0W_H9HXT<`TV0u#N@k"Yx_Uf,2[]䑚(ijGgaY[hEf[y4S -8 s%ܛv_Rv?kDAchK tvSOFƮikf(G)c򖶧h{_{-ZEJ\]ɦ1y`>3oETxg *R,/fm2|/L >(XA #Fph8Ə$IPt%,Z 'MQP2xP%'2d`3E&bB4ֱiBխ[׬3_|1k !9\QcDA DZtƒ-kVjײm-ܸr^kW2at.G[/b{j+W.Znݪ.\"A<jU.زcoؠAC }`@ J(Bl"<|wo;^ ʄĊ-jc<#4bb JD)/G(PB)HSLD7`5Vc I-֌%Y5!89Xc5-_5`Ȉbb/AFYeeYgFikGζdm IY@ iq*|mU$t1gs p&B| wI 1^EQ4EKd@!pU$1! % FzR";Zi&W_$nAmaKi% 7 6ݘGP֕v 5x/O d28 _B4(|80 [ND"kDT~r' Qq,ȸz5L3l} 4д͈<w:tF'Kt-<5 SS]XKִنak/đ[i]BC1NK&%w >D4_PD"$^x"GX+Ť sC`t[AD겑ou^eAcv,bÜgkx+hxK 8 ̊ IZGBjsdO{)@!!i߻70 zG "~sG)P LЁ@@r+l TL Us.! Z0GX kp,# [N RCU8Lڋv2Y4D$/3G,D$R S4XlK['mnpS0>#(A ~ձuGiO% i9Br-HpBƫ.¶n/'aىr:a@}+U(E)1,U-kaKrjKZw6R؜8'| X`>qJVd|tBF r4`t;@]g Z0-TY,\K'eЅ.tlb׽c0|]*XJIHT(c} 3e>39fO ԟLQ9 ՊTPzn86U6$0pPQ"t[o ZJBʵda(qv\+4VXS OE+bWg􉾴dYli旷ϦmK^Δ>L]Am4 IB((ASrUh% @@``A`HpntKf\!CO.ve^Ϋd{_\؂Il2UEJV6-&myDTъ \[5]rЙ0EP+Y AD,yI3b)S>qq \P7q m[ert4PWXKVrzFS%ck/`zʯ_*(@S\eMny6묂8+ 3 @V@u !;ADDtK㘄ӵ9IBԟ$K"Z/.FUmJ\E$P%zOLi{/xVhv{dpH˶4]a* 3+TATUܠ(ؠ6]"Zc{Cs[|n@78[:+^;E +1qc`-~qӝ8C#6'|8 kv9@-&)xl [[;9?L(?d3A ;9nG6utot ooFaa޴oI7g{މ٭v;>;75̜֒? `k J < xAB#nA WE}?:ܐH=`u{ /䂎9^ڱfŝը A0ǮE߳ q@ܚ%Vu QCY<Mr-ի5C V^y`_˱f^*D  ]IK٘ ._ >@ ʠ[4Dl}aSA Q9XDZ*VÂ9T)!YX!8 !d!_!a` NMŝƸ T l$"2o\$n(4{<;>SK5~*|]""&`8DC> #1 A2.#1 C33fP ! d"z7گ MlIhKi=_n>a"')YRV!-Ŭ!Ձ`eEZBCZrd[H@i0SjI $j%^JpFnd_Z`%܀a4 bj"4fЋs|$ʄDg>a)j{~p,CBVfBfle)9YlXE%1hੁ 1|[TpW dƵ ܆rNQ8sBg \٠%[nhohK5x @,D${^{j)~g9CBA~Y'D]X рK(} 3eH pfd@H脢F4mn(tzh$ao$"hP!B9%fTPW3<'Q v!i[8Y,6)d(F).(>p:,K_0t))j_]R_~f xš"jFkXڊM0 Tbj9c8(c@6ꘪ*MZB7"!OKAzVg~hA@(1(kiWnڳbŴN0Tfle1pk&$To =Vndi3Os \GY*e\S:Rji 옭<~՞ĆH^dv+zl,ɚܥdUOel@rl*X˚y`$UG-E<"B-0X֊Ԫ&҃^-fm4ʵ0 d؆-/Ȗ:Vlٙz#@mۉ^@ɗ\@ܜ'wLlX,>-&5n>-FRn\]\^d~n$ٞ5r+Ʈ7F_K.;ggnEX(͜Vob\( ~no.jn7/#}@N4~ bUR-U W4,}B.*=<Zg/`\0OB銰ʮjpOnۢ0#vh Mb譢-5j)D xm A>p6[QkX u&4[)ߗ?Q~Y7v˸a&:=@iYNBUGB0}&3H4'jt)3~͆A0BG߷m6+X!~MΨdE|4HIJ`<[\]ԅ`$=wz'%k54O4}u~j,rPR' ү53;%UGvh4C"/yxrj)5TZ; [dKko4\L'4TáN/t2C' $@ںv'Gb(:>GT;vj{PX 4AFtu*-}޲y4LÊ͔©\=Z1&j5ضyuo"]+ݥ*r'#xVc*c%ДuȭLP E\DWr#r~rh;H!'xd-g[w&!'556l˕0\߷'wNneIPk\l|x_.xҪOR,l+ܱv{GCRWt3Ģx0%t0H,^oN(djx]}8#w3~^ ӥql3 /IS95M2NClbuǨQXsfw-vNۂ*x"@!y\&@ßx!#Xçx7Blu~wnnOhN]l$N9+ [q@sDoKs٬3v9$]-,É'X°'+;@{{{a[4 =:n#IV;]Iz jyѰ٬Qhɩs&0K',o9kv'زhG<ˁy-yfFv%jIɏL:Bʳ˃;kGq^zIoI{tRQBⰙo4=pFOO"EAy!\|'CEWmTx M{{ˇ7`Ѯ Gn Ϡ1ܼ&~-A#/ޢ$% vߺ_<$LB"؁$@N\DVö}þ:Fh׮AX b 6LZ5iS8vX%Hdq +s丁R8<8P @N;y(Ϝ ` )Rd*T#UTAà ukĖˀA]ѶP!2R葤'YN"KG#*I, y &9s9wo:3}ujզ7Cس=5__fM! JhF>A,$+Wdsf֮Cv}gNE^ӨR9jZzU, fӖe-Kj+X0&C9C)~H!ŰU.&M4V[qn-lk8*(z*砃N^:h+jx [H$IĜ+ڪĊz9lK,:OA! bp)('x* \T2Ė`4;F+NOkM}kFs\hLJz94C!ljN*R<ڲ.S:뜊hͭk  + ur)b $.lIH2Of k2lM=47Q_fFQM57kXm⌋5Hj*^UW^K=Rcm.٣H*Ҽͨ* 7+9BΠ`]}l/yZhP~P 3@`Ą#t5}.7 .!V{FdX\R%e2fé؛=itRgVX׬s=qui3xHi idA.EF[N?ckffټǍuDhqGNm*b+rJPמZPLX+Vhgi%(Ox*^ '@dƉz֓ᦲ7쭨{!a͈qLPr'fR_ M.3K,Y8*`%*PN %²D<6D" oXCC& o }cʒȂ#5 k&c*E0tnUYm TZ @@,JW5V4  rָ8Q`+Vs+ 4l,Np UTSq8y#tIsDzZG%Mʳ6ΞF SiU CUo2ISe4UNp%(w:56kn{FưpWb.cwH^Ù6;HO O[/)짤fVZ}٦S2|ğM$" ,*[v+eT 3%6t/l4 rq|1v[qtW3qlaLqz#ɪ@esfcrd8x&bOMЂ^fFlCF4:b$۠3@TbI&O[ɰ&@}o$*eծGn@2~^9L((x^;@8nh59 kcF6qS e[? h6*d L[T+nh ,2'ST[\ӿs.87;lܫ{4[:t'GU}(o63=PjvC\/mHNuP2QdZ狀v5τL7AJ`W!;Kp=hqZznq=L$mĊ9sZ1}f~/X#k^eo>\OԮ,bآdBȭ*R0bK\e=Q2L: fk| \  hAB7~` BFzJ+\+ @:trBXNPL̞%Y^02oNj-oiO.@ʭ6.F,\p F5p ڂ/b ="'N*#ZHc 'b ؃z~T@iNBTW^fD(HjJ-Q"x|@l!q Ƈ-  wC5>qTLnL1P*~->8w.0,~sjPCYb=FJ|Ǔ >q$` \ͶmĶ.,HjolBl fAq:q|IZ6c8"0; N#Z=L&8'!oQ u("1Z~f%LPr#A$XF2Zm>Pj\*ùl Ol l` !K'1c41R6nj3to2DzP)O%G.2'O*Né !#p+igh<24,-7H :qےWr#L2*@Nlg,tmü1 @` A*bv2#4Db 2oS@c Gbp)csǀ0 Ih`S`!6/0nyFL9H -s-H\dvj?->O:`:*` xAlJF"S2mJ=IEpO:?>cĴN-Lс4?h1T6M355`8EMC`q%^:J4:j.*44Np>3@,ZR@_Ԋ^r0t1WKF?/~<]'F)/@4Mn Ps*&UpIaIMĪ6UQ(:LK9 K7,*H\5*Dh,:l ana!\F7 OY=$(j94h v $+'1i!hJOocdKdUPdTvHث$-4ܲCO)l lr־vvz?ȪPP6h5$tUCXU6O"- kYrk v y2FQUxl9m?Uup64VGAn-[;9(7+*`jfm6R`XpM qc",uLq9UC%6$s6jt`%겖k\ٲbґ Dn6vkv_wAdz}6<@BRG7(f:ZB_*-z=zNRWr)We^|SW׷}ND\%rФZaXaa7v/hӖ. OnunH(T-`-<:Xp3\I ݦPDM7Ўaxd;=/@t+̹"/t)®uvFV'swڼ'}w{(#&t $I)jp)BxX ,#X!$|n&l_-R1L7+m, :l)YV/y{Z=8BBK-=nf4á- 0YM7L@!j}̘9TC@ꮀc$SlcYK?sAnH\[\)fm ʌ,tz)SsU h_S Mu(HVE\@Qj먠+nRTab/E bᤷtle+:̔$= by)\}(zCE|ї$:b~]_x9C(l]+ 8䱬 dbvțLy;yԜJ~%zZ)()#S9fJ &]79\Җs9:Xo<#~.]z>85[< d3y|暓wsCv_(U)N)n1@g9R7y2$|2]ve~;ގ$ Lv,)dOR*YМc-|n4!oQ䊸7ҕh 8|l\<vXyMe_&Ⱦ-r+lVi9`+vM+SGnmDadm98? &[ˡ9E=ļG;7 hW{W|sBﶨ͏i۷,6k転'Iadc0=gpPEm5M#ϔc|Y~ ͟ѕ͛3,NT] x$.;%0|9#AU| ҉n1t"{8 6] f:z9b+6!aS7 C::)V<2՝p(ض$_JMR#%ƗȀeU7A۝&-",]`ѹlKRZJI&,{VfjƕmRlSe2o$poSzr35G~c^KZō:ܝ<Ќ?e}@\-VAU(m#e&|{DBE?KMJv*/ z&n"Xj vM…:T0‰+V4-#yd`դv,''<| F3N,zdqСr)up@0˺4` \ݰ|P:TōUA^(x<4r ƒ+/_xKn7*n  2Ma#-fع4+Hk+F|(ar̛Q!A )I*Y9N;U0n,MbZjV8Spx hײujviY{`;eE ^|Y6RلVP,fR7QQrY3v'tJ-%M5]HeN=A\RVL`}~1[8׀PvajׄU%g}jVPxyX]$gjWi0DP~z Q,.fkO+qiꫢl' M"3HzpFz[P7g1PBzlҬ}Mu%-e1ǧdUO=sϲV[WbDgFh_!2>5m 8q-ٜ咛{ #Եr"~oZX'vvx3 ~mѪD^IͨE0jS`AA^嶒iT}.g9pYbP(0->S\XW(4;B E[ܝ;$&6%bMG6@ [@y/|F>oL4 iO~i$ a2K”- *^Zfqc8 fZg!mJ Ը:>dT{P*Tx+𥢹QALuHf pg<|[%N!c"~"!ѥEn'a$E3Z%[A *T P.:\d;Ah KJj2)/ɅtA1 iC(=a Dd!'!!> b RiU"RsZ顮̇Vix`UkA`x,q|r%n6zH $1zWzR  iuMG7a!A{Xu+#/c{!5gL>FE?DC\5>~Ǡ OY@>HG!cf l7V_oȀۂre)Q'xuhYdvK:W't`e`rQ"EZTyW2x'^%P,ws `>f` ~uesP9q h\O#a(Ba Ȉ،#%&(P%x%5(,:Kg2@聆x_jozxwQZ>G"VV""@irNS_i5'PVP Sb@sIyyIVm $&)ƨ,t.a15%gؘ:ɍh)"{`jE9T'~uGt|8TxM.":B35.ORiy [` p)AODi pѣht \Y898̸fјiAZRqeuڨyR1@d3`_tEߕ9?TTau"Ms(/Z(Y8':4 ` 6%hJY!y6cyAK+g/Q/ZN KBѓ%׈9#GT'!"gTiwY4#LJQG/7ܗeK3l7di0[POi @]7{J <$yd0=56ʝ'n;:fqc8u:ՀJ:u8X5u.rj\+3nQGGo2[RWQ5 8o7Mc?Æ`t CjT {J@\0 sXZUȐZApmDž1xCaÁ>ڒ>z$;uc%Fd,)8!Fc:a@LjQ -c؇lGEe鈃d:l*aӡU"&Y){ ֪QPE #9zZ<ن1Ydޥ{ %uu%l&a9EMSbʬL3HۄG&Z%qA8LT#k%f+[v6'&Q[{: U0<**Bk?:o5aL}{< *Nj.bNT,riQ}?]TO;T!6q>c ^Aą;߆ bn6Y;"FZv?aȕ\Ak޹D+zJUčXfb4`Lr?PI ŕ)W[גw @zDebt·طx†ń)O]@s1,\Vb?$;thaq;>FrGnH{*PayMOx}Xlx8 \lv*L'iWE|pdzeKw\y ) * Ѕ~% <Խ1{ 'ahbZ`V \ɖ1M SR{v"%K_ʼn1,T)H`"1d)4Qc+{\(lu zL,1—5bq)a 7#< 1΍b7Rl%JL7ʡ Aa֬(|uY!9Ձ6 @9hZ@74@&jw}{#0=w V֌ ` ){ \Ӕ7-;- ?}/8Ji=;kZq+MLj3++2@Hqj%*Y;< Bp{|%60M_\w қbYh @8bؖ7-aX> ԣ+/QROzqY &5\Yߨ-H\wG\4v@=,Ӫ v2) (H)S0P9VL}q.9ֈVgԲLSo EvvH;9G?o*iEh}#T.?OYo+OI_Ib Y2r0tNC,!#ɘ SS|*?l -wVP:OBYra57.Bqx}_OT~(G/liIO@.tI ) j punt/1ZzN%BEKTbj pu١-]LE!灞"dnwKͷGˊi#H'4ƒ!ɖ-iIE(K;T:v̗%1OP/l*hl1 ЪPl ;+͵H[DZ{maL+j+(7wM A,1x;.uQ.H':h&)<"ZJ=ܓ GoC>=`M.,HLQ6B5E4RKs( TaBs#ؑ(w^}v$NHaINfqr&rJ+ϚD3JO d?GE6$>:E( . 0vAoEI3Q[nך,5I-5JKFۨUF AT iiQŌz5Kx ( ֧a=.Wy ̨+7[\ K<]4Mh|5T G<  f1# rSЄʼn" />U9,I(WC>j]eh`Ya*KLϩeYz+h Mt:wV<<@ y^-V-mKnKˢZ#m \C&䐩 ;lY\bLK6+]t,2iZnӀT+TxWE+]E./+ Bi@yKuDs2(-{k v@|&:l(?HgSAD"+G D8rN,jAnsGLtS P1 `XA> @!DxaZ{%@؜וJi,7T JḮ ;i^@>%bZ`AMA Zp_b#KxF-A9bod FmX$]Lj.oI`QcAʓ^tĤSiDٕG:2Teq)e2d0Ă -'v#6̑Y+EPr}ĥP5Rί~0@*-5d% 3׸i x@"G]+XH%=p--1"Wծb&*5]H+y? 7]z!B&IQnML`g`!07*a[b:3Ti*HPMn*#=rl) ґ$<[4ʼ<,*( }@nA$ؖ%nDQJГ^ǃ25,I}e!Z%8vBM:'3+D閖”TAg mhRMR.dsы@4Lp1l1lr=aLB}2&n@DIah0޵,^ ]~װӍZ{q_'@#Oߣ) %nLӟ|\H\ÁlR}{t[u۴i3L [ajOzјݚ^s `IJ o7C2Q##%8R̸3'/S,"חL|Scb-n62(ɖ9FcSxFS:4\KJ(4ԣ-HznK#H#c,n ;OɔZEi3[\(0c<.%1d#rV:*٥Wfҹ7vjVfNz4牢+閈4x\Z}>Fn)}#ȵ(wEb#J5N䅻ھ2%^[s9M@7^{Qg LM<][]a9+/_<9иtQWF}rrYI`)*xjؕɽh=)ck4>(82ȸc4> -k*!$4яJ i9UzF#?$? K ’:-p18h /ދё \d1#AK~ +>-Pڡ0Џ<"!EC4zңƈy%1(1P=+Pl)`̙T1d1@C{AY*\|CKz@Cӈ ݲ(AnùC\Ah(n˹A蹱%p  ,22`G3ŖZ7d;U\VG | ̲a x>3]E^Ea:lcd$<|+A FіA6ljd6GHlFƒd H(I$qW1ulG3*Ђ*=" zG{ &:g/)/[:ۋ JE7H<diA:bMAA۶AD\NDɿdv G2ŔOO|Dhy<|L߳Gc`ϐhV( s*#u3Hפ`z BRCdPI, c pK6ƲP#I3 GWʖ;)  *(vdG;;D\~+TQO[7b(ZPN$VH$ [ ;R0ЩX0p5l*,.4$d9cƹzPdή` ="i45BQʞl5N."""$2;DITIP0GiQj@[(B8QƊHUЀ զ, h>RL͡sH@QT e-TbNN;՗T+Nd1@5Wـ P9sq(hLpy%8"lGPl[ [ln nHXnjHh Tһ[0ϲ&q* P(bVҼΠU2 a?S4dNU#K9W&%Gmר51-,l ^HeІeZmЉJlXl0۳5[ol*W,Nۥ#\Վo õJb\{*tƭX0AU9l8\%-+{NEp]ו (}UHg}pu(+$-05PD^eP'! ,^I$_Ӻso‡#BGE޼]8ď Cn(T%J-]Iv#!6sϟ>_ I"͙Bv۶MӧPl2*5j/rzSM^6 ٳLnk  `f˷߿ L6r8ʛ"ߴ86Krg˕1oLoDM?LZ_̠#{JƋyn'&مK6+L»P[m񡼅sZwX$; CV[Ow y,OT8 RTM+Ye}[T`7]%g܄0IsAǀwxg8w]GFQz Ƥ{5t|g݊HLUB =uVXԐeeeasEE #vbp3+f֐~(zMD )$PxMAgd (#[9ML֩V v NCYr!\]V`F(dUfQevTHbYp+UZgU1':zԚ&&HڄγD+m:ޤ* )`]p·5bh.H櫯N<V;"j6 l;Fq JbuQLoW-2J<8g5d7\œRirJ8U6iKe5xee3Ͱ ]p\sՑ٪Q'OАXRQRjJ'}曛4POcZm:s:_36ٲwvKQ{ٷn-Ry%[L ^2$Ez1>xdkѿƴys~9 ::N _\[MîfN{ݻ39]ci]h2od68U^HMzXVޠw=co:9#/T·s~v49@  Ͱ[D ɦ%c 7f"Zᩙֱޛ$5181{'Zn k$xc 59h\6 $k C?oC[Fˑ͉&V%*Zo}ȞttF壔]ӞXr&$^QYHL}f96Q5fIAWҮs(JWң|2`mS*yK&7fQ C-QUČp:Qk#FjMy5s` XJLբX[A7[#4&swF^׋M-rYXrO{ʲƺ=^TK\vi[ e׽X|u! g=5,zC5 mV^Zܕr ܘsi5q^wG{xguPWzj~ kQنV+nJ~0:jZ77ΰ9aP 5G1U;wX>FI;(-VlW݅wc(s9d"f>Ɣe(? }(N / `Kj\+bX̕8*g9z4Q߇Y}3GN[GVq-`suE! "ۆ)4w1Ep|U朄)M+}L6C T@XfWbe1e|^ٻ.WMi2cilS:32sl&wW&_읅- iOސ3={rt~ wnWX^̉n _ᖳv#.m-o$oɕ+I0_Uկywob=0!-`%3Nx'r6kHciW/muݽ%G`u!=؎]z\^m9[[9.{KܾƤhY{dזv\=qO;2kϰ$靷&<˺ OaTW_&};$Md;<_wkWey(%Nlg|kf}GrX5an]7~w 7~T35YC6[ww4US sw'hL4meCՐWW |ExX}h v}JcD55vHnŁmE `fPt&?gh5[IwVtH_8d1ulSw?&BhdֆdHHp^ 5 4Xgz ]c(ʣ P sU=S?L|eMhTԗ\nvtgƐseGɤ'ՇT`$ hGiKodDWv}3 NXv䳉_؉b]3P ǀ0r=o;m5xx7'>kʠz}dזhsi[vѥp M|QhH7NxWfS}HI5eQ荘`8sZP 0POmkw1H\w3XIm'uTM X2%ۤi>ɈR؄`y`w(S"ȉ!hA` p j5ZqtP(k hxi'uZfGFL|•|YyɈb'6}H'^؈4h>c4\H` APw` ` V5oPq&ytTgN}5ր9K'ywyVy虖Dd %iN`l 7(3viUN͇SI6xĐGI>ɵZIIK7 IX!i֩\ܙ9i]5QZ`` y -HtǞRH>iٔTGU?3}& *X rآ:0G=b  `{oe|)j̩w$7uxȢDٔx)whixMFEʁ`IO IВoh)>8:ZzwCeLyI9ʢzFtQ n :vJepވ h ^ZjҰ~W`RG[ vI?9LڃV_ǨMRu#ziEZNyd|jTJQpWPJw( sX^\'m謔Z讘%-5Uyʩz9[dc]ip0BI:S@Q@T z +9F hZJ):(HðK{_z(gWdF`+ڱ {&NB IP3[8U`b@  >KS:ZҖ [;jdű)ibPd+f h%>07Ub0v` 8P{HL}Y[{dG۟];iX%GѺG 5[rP%>{6Kly  uXr\g>؟݆(}bJCllXil[v+uI'Q`eg2pPJ l 7s[GǀI&+L GֿfZۥnຍbŭtM+ZĠak%>YIi ;\24l6ڂs |A݈P k`J>> N ~PVnX.ZH|q;p c* Qi&%vD ֱhTَ BZ - M݅ ͑.p龝> U ࡎҡZ` #< ^džUzFI3ĮH2BcV=QKcWPm_~La@&gHJO`E~ްc} `?cej_ !%o0ܜ*/O7\}5@ ׾oދ{Ȍ趾vϦ-mw \׭闍_ 8b n U/VÇ $HЀ"1F!CI*ThҲ;8KM7ܩL1eOUZkžJŒn}.":3QH Yeِ?+o{ ЎLf%H+t^\ƁFK~U󫤞s*ӂ:JWGV+rh+x!VŁv1΄Fds2乬O}jB iDY> z)B 39rw8u+Z'JhW䗹̯UcCRǗE/0nDC Oo2HA ^_l@6X qz% WBn8 b;iVc584YQisb#y/mG`%7c9rKe82 4Z<סb#l&XF3#  ՛7q@D',PE1cRX?5WYHR-c|Hl%S^,@'>*L'1IP M ȚTZpbkʸQ#D i\$:u/)&OALeG pL[9-ѰѸǩ4~ó qIUlgF|6"G+ohU)Ɏf2 KKT@,K|^yY_'a^ KaNhG)GXYւylb\Qsku$U b59vMhfCTA+ e`5X- ˈ*wKpn3p1i ›>1>b wq9۩fYCӑ$vz4[ k?8ض;M%0.陞 % ebiB@KXY947Em^ 9$bgxcdC8 fgAiԺFXE|쫎F2#8:ǞQvTwt(4˻B-L>9I'DXE :<"JCbC Dƌ=HĸJ{Fh$:k,DF >6oȐ¹$ɚ\ZN,28ʜ!bRġd@bV3SPNL2 |$±#5 3)K[,[2|2(22FN;D4NsI p1H9x\ # ) Y&u;ͫJ` Mڬg<͏D AKCtH%JNڣIΛPwźcwP@UI[ /c0MS0KhDPMG*O1&"H}+"^1MHFi|)O-'Ѝq;,s@ĉDML@RiUB1;(9:R/tPnSH1Ђ(p-@(͝OXLa ÐҴ"S=34PaPaDj%:o ӌZC'5GF9=_2lȆ/i0C'x&&V%e OumS;$u8hgj@X]]d h8 / 0P$H_͖C277e)CBV:uĎjn5@)֢znتEUuh_A)'xWO@N- /܆iR5*@ HH  WE<[~1fPA=8% DZi`99HTR@(C^aPdedM%b9"dٙOdP-_0Xb[ub7UegeD:bkk>pc3305NW"} RRG.X8$0KЛjz3^[H;ȃDT 6a ȁ ΙazdtUU KD0!6 z&{|}h)hu0z,-Kj=rCs b9.cMcLJX=@,mJF>!TfX?NRLHW.*x.jt8j(^vcaI֪j+Ȯ0*dEh6hr[fY ;N<0DX6B(9;ZW LX4#JF<~s@8`f&<('@u 8bӹRoXM윘KDD#?_SnpTh0M _ M }Y78,(瞉ޚhmL(>+hgN;>fh`&C r#H"XY t)GgeԆ-O\pϢvBp 6osU~s0 ze޲*vvss[`;Y!D/J4CQ?tt8`X=$ KVmXKQvG[2uuQ5vUvfuvn[SfwphN4K$v@/710wruWm6lވ@PKG}t?wDA/x ˚g-ub3_vG_怍WDҍŸдFP׫3,IK y@tA?Y_tvf`m.2#'Kpft_4)x&FIաdJ K[M^CV^Gwp)WK* p_{~ F[0|Ku`w]J 47*JLOJHw!| jIGoȆq+h`Ak 2l!ĈZfQ2a6rq"a,9Rǎk̘&AΜhP'П(-jh4Dx A"DZP ƒ:jDiFЭ[$NՒDQˆ05jӤ5k W)CyLArZ34iޝ=ZuӶ5K5)'cO#NYfQn8 -^̘r.A$"Sr%1Ӵ3灪AE!}J?l`T , ]-ՔZW d=AoUdA >_ia%42%rrPr`!J`WZq%SҊ&df#t)0p <ۄX2 +>첮RP 1Briez8c9i0k^\IّK.¬x0 &I:z:!(c! Sxi[ .[i]# ?$ĩj!rHPA:@7g z "Diaȱ( qƒC`K5dpж};vr]h0D#&].ݝ1ݛӿxV< 8 9r*DUC~oiT 9ua{MJ\P;@a >iO!Fw11 MB^ Qے=}⢝l}vImoݼBKΤ& x U&8HS\u594 `,*b 6ְ0- r CS@@"_P < P'hlb() P!0y4 nEk;"C}:۸׽)V-hq^@ < )  7~I @#TSĢNje,QIDLr#PH R ipC"؀ !- x ! 3|@U28!70yH47Q\撈9¶uE]SHe+f׽.F~!Z"G~[ND:TR}tP1h`dJ'TJ%  a Bx L@JSaI\Z@l,}[߀4Ln-$^HXcsKgN1*F~(D.br-W xZn*TBVZJ(7JDtӺ+^c$! 5s~+=Isvlr)LG4 IY|?M_P )(HuR: a|îNtNrKǫ_E:$sgM@s Lv[a [XPҮӊ Z!!?FaKر%Ui{F4}ܪjLj4#TrL D l=*ˁ " ~P\ Lr_\ tI>+(mɨSl q1^njM.b}/M㻦h֙i~C+@eV XX;`s*)88›?@4 L\%A*A@?Ȁ | ^ ^%\E75t;3W'!HQKK/xU?athY^Ỳ2a;F3˼uY6LAjp*0X( `Jn$~.(pEV@ >Qs\Ӛ>oc;ޡ5aYX5ͧ(qOiHDlRY5sGȅ׵8Oa;%,eTu: :! i@gj#`0<` $2 .Yh\pܐGav!6o, PEiz ԟSꋁ5'XV2uYvSs8m.jwUSdTr%6ZQ.://G\iIosI;1YȎs1.MѓLF7-Ͻ^bT PL9)ruOy_`e*`YX=|mrΔ !/Q.nqkMk/_I,鹍T4TA^H]깞CŞ1 2\N8ΣGaY _ݢ0=!h;am_yߥ@_(APH 4^D-ԟRiXDU@ z )>^Q1UX àQ&\!|L@  :W ‘:AL@ Hq8E8`\l'À((64,iM5ۺM9Xab8/ACbBZe5 //cJWhXEʼnYa oU<%QyX")"(VA-+bѢ8 s /H|/,SNݍ2{E fDIX4J#5&QEi#77b|P :W" S9@8ȃᇃT$>Yd'$W"B~et14B<$DA4M3FH^w$A2յ1T9E (Pl}0ĥ]SbUxN;LT0%=S ` `\EATp|W*$'lM[5&Z%9LpҷC\e]Zw!_ž-]9K!Lr C Jj}ly'yB&\I&ee2fQXgThf̦PE]"k l'f1J=33xD&[ `G4HeEg!` }N5Qd6ހ'(Oge %X @ g[ 2%:JWg\ &&l(Wh؁>Ib^/~ "B`Z"`rv^/@%j`vV&w!xy(~'  4S. n2O?MilVf`(3vi9؄[)ȗi䛆(N#v$({|'$Ej@}̠`XC@ ] *m }[S<-Z([P mƦ'AD^T"▦*Mt΢R@xd4zh2%*^K ka{LQ{tERh]c0!݅ր!ՖSt+^n4EEk^@pBګkD&AG^0|i.$A3&BlѿHF <ul U@ԅ m,[ܠNU`?kDA+ZЪ%,NѾ0<.FG5g.V`^k _95z)L5VURg~=Q h*`h:AD\,޽Z&.As`C4@n6mNha^O 熑 S >] Qk&:Yjʝ-Y.](k_g! B4D@/=N/ 6`1`n/X~oÓDIPƏ-\Nok&PUHR̮}>XYH ,l1d BpDHQ_0qF30?` ˲k я:5EO ' VLS~hv̚LAcJ/x!Gjq%sqe&ľ1ϰpGlVRPV}I}~˶]XHo"$-%oC+&&vZ-2Uvat[}Kߨr+qL,wO=_Vʓ0͌"5'h*ޗz` 4Wp&k5sH@nxDdu3~3ҋ)s*:+ I@X\ų7i5Ƀ{ ,@ 6D YDdp"4(P @!I,xeJ+U< *Rp`AB U,AC5ZARK0fFTZG&NtU1<:fK'Oin,bX%9s{8 6|x5v-Z KFe̙-G.ghG;6ݸZjҤsS)&xĉoTpG E yr#xA 5)Pxp=>9ThQLF5 VeHG'\jkU&YJ䶆R|K < Fs ¿ :lH26,4H#4TKb !*m@3Τ$cjʀII:/bˡCrS҇䣯+Đ8 d>˖`P,Pq0Ӱ:;q A,aJ Q[c`M%1Blı(1,c!]UH`Z tipR)rʼʽoꉹʐ$ P |>DlP@]tYwB,DeQ7IOԚWsM;iF2XTQoTS(K [}╚5*j:u'>h'\0:#ʩ[J=20hj)x֪o4.Uy\sJvu{pCzO^_3Ɠ ӄՆȢΈǓ+N)$瞃ɐIupM>YeR)t%X0+0pI)@\ƾe 8bdhn>N8 diNiϨc̱@3끸5 յ q2 )(EJI y  [!FW>GǡlxLe;6d#JaSƣD)LE)tB-NO "ؘ3jZ9^wT kXQJ~\2uDȞJ IX9$2`d%˒Pzr<@O"*SYE$ hÜ&'%,v@DCe Q ݾ.5L lTN܀ 7>! UUEzRBƩ}/;:_0PQ,T\}zFQFکv GPB4f0%TnVhȇ@PZF2$vNu![jTe0Y>:nr8UR *' z b T h`UjZ  Bjv,+1֓Є=Q!3Kud;i]y14d nH༄!foǾȊ*^ BgJ44AhDM` GN4w{Vc$@G:sz{@I\kIoНÒI]w#v%,(U80I:zR  l>5\m.QB2hБ^Smp1ad^ ׺X뀯u TIr: :XF5?Qz' Ap̀87KnTd.(Pk ZY&b|Yt'xUO;H v"ȆӼ5HCyJ|ۈ; "Bvdψ= c"G$4tcRBP lFIW- 1ۨM{})-Uxʦ&ͪV]k(7IɄ_9n1_o}*Uy&[Dy֤ T*5}(hJj}pnt#Pm![PJQWuoFFkcT`Y Nb;<pDo'XllӥXz"uO"{ޮg:-%Y.= ն{4"V7eM'7k` i&C@ +{e]\Js#pXSɬD{' HH%` تj`6@ %*)U1@[rl@ oqwvsꍄqpͬ %*j<E7+(1m0)$KK{&/qb "efrYVnz * 2`x #R"!rRMX#m8rְa6 HRR% " 7##&@ʺfvғD rrң9/'K@)@h<<3?s3&D8Ck0!8q/18'bf$Xu @ ^@Q7Spڑ#N S rCk(m + D3DM2N1!K4EFeE #54p$Jj Af'$d먉%Gr/B7U # hKgL'~i1 ` Rx4P43k4O_E=g FUhzcQW3&N<zM?UJ I/Iwo 7H pQ^#p(K]u؇9/tk hA4D0CE#M0+݈;)l dEhRobf&, m&4Us: "LŴ *``EH1C%F#<(*.vl>yfY Lu@C`2 J k?0[v 51unr }Ո0(d,U gM BiuXS̘QX4bP/ɳ fW5RHM:kKY+g6&lD ù._2,y?hiʃZ* L3Zpy,ybIgT)(@ur~:Pә!t^?*Y uNyڃ!DZYx!ec]cc-UITa+9:;d{@,&⢑􇉤9j)@K,šI$ʗ"~7Iu3nXB'xK@&Q]2!2C;_!dy8W##&*e#ɳM{N;cH;@R  ) yK 18*)[GS>gX$p  !1^[ۺ ȄOI50jֽRy n̆7nB=DqmQ+w'}y;pA[3g#_{@$Jaj##ū׍>-]& 5E$Ix4οO%" D>o'&e[5+wtI4Z{*[.{0]n 赤k7lD1 k~0"Gx#gQNӭJJ(HuԨ^ϯ7U̅M_^I^-U9&rv)IDwwHMws}fLt*iZVSD/_A)j~#=@+ND/U0RAL"AqB@ PBB%޸#C ?|` ;z# 3QÈ9h̙3ؼfzdA#Hѣ-N4TY55q\z 6,WhdZ36ZFFct |pQ! N0G9dx\:!o_Р 86X*`bu $mߠ[`;o6f:+=:"&=z8DU[Ś?^fպ7׵5q=f^j"'/چ[GsO"tٔQ' {Bf<,[%fCfYf WmYÞ\twW^svwY؁{" * *Qvhf zSUkR oAm3,ѭ<-sK6Bf%v\3T7Y;&{l{WXLV5n. U*,U`16B )̰ q>4rq7FjH.k1+Na%H4 Ɩ :G`HݭSƱ*0R)ŧ$ d 9> ŭ\ɧ T2ND= +m$&UwaŰQ7aDΞ91F9Suj_4a vla;P7c.`B9`6}CŴy"^2׾OqݜET yA7: [T/HGY^.s=hֳ0seXTB2uwEzsթC$"aQ^`[fgCz 3Kx2YP%-ت>~0C=gBq6GrnXp(~Rq!|7{E{t=U i("OUS(iN$ugv4hԮз<'is5j n`i, قcTMD)y O8Yyk)~=/Cc#!] atV1u=u*7{WuhpFc,= ` T j b?uvSw"lgff ahDD7]P,'OֱmIAa%giJE==6aV|0Q$"P*&guQDT'6@s(g`0Ff !@YubpF#Me!-bfwtw^}}R@'ࢆy" O[98RA8WXQ'Q0J];@]C*F*Nfՠ@I P M`RNPT`|xPQPc?ZdBD@A*/$9[6 k$ Qxw"%"+p5CbBnD#e#32#}sw Rv Â!NKZAQ LQ'4G @}qY){AZ9.ښʖj٭GZZE0 vW.A$r'sX=ֆ 5$A*2AkEeR u bDFTT?9Nc0D p4b*Zt@  lV4OtF1zъ*%z8K2Q2'Kn~CQzUP;Uc7%239jTYFr,eSNӠ@S:q;mѱ`i:N{vA (K-I]Ɓ'WP$3\:!Qdgy5ع[K4U'*濄Sn8:s+M_SNpmZr-a1!+د۽P6vIaIا&J抳&zB:+0~QiM˘chNQ/t edbS9i,}h P3f,`4 :ab"+,\Nۉ w׸&& ٧'K>+V#׀7PRɓ|8Z#(vQf? `b P jLZqcZYoOVl2 4yRBHuEo~)\d;A8/֩+Q}\.TahMٛ`G9d32Q mXo#_\bm֛-B Z ~ \G;[jEHE!W"uĥ[4 rr%a]QR,g>ٛ,M j79xtp #9vAऍ`",F @c$ۆ#kHAe9/ [dg 1d26ci)9WV#Dؓ=?ޚM SěfQ S X _f]&g&: ~, d2z:~%ʐ  Bm7;>"8!O KYXxc:NI䓮 {w lwpM3PqD:} _:Gc4`HsH Wm˼*0t5PM;1㜜?0f>I@O`-ʰT?Ҁf ffV~d$a_ً#.ȼ.m~  EarAHLǺ j NY1ЇS9Y ?WvvIfϱT OQ xRgs,tqXf2Bz ]O}H7m" _{qn4ƘF8mj:>-T@uE=D^c0c ,owfheoO5›*#2Opeyg;mm.jH&K;QB@YP0aA7"TPD,ހc=~ň#4hPG'NPѢ%̙b4iSL3lIwEMuI)4+gЬZv-5]FV,ث֬Y5{6gդ6YTYxoF$tC0Ʌ4<@4p(Pa%_.IΝ=1DhMka*n\80ڛ?pA(\È~ؐ|D3AKaD-BS TJiT(OB@>.'|R#C73+OҌHPv(x㝕H^ѱYVzȸ"w _4'j d+`Ub ~{]Ses\ꞧv6:/ q @{== =6mӶ2" j>3p*>{K9ۚ@!@+X%D9LSdHkAD M<|$KB2Bb1BΩ%$=L|$}8#,Ԓ0$IhC-0E4;B94 @@43x +Ђ1@~AdtA"24ĈA%F<΋DI!H::|'/ٵiE,,1 UtW㳺k )xCC3ƨ9eCedsB4 F(5FmFI/2<'3~j=fV8xGڊI|t}srlȀLcQj>E2 !7U0 e|AH!{FhHȉ0pD !@;Md8KIh ( Ƞ[KJ|l23E ( )`'!36@3`jhbbpb dMt!CS8D~cԌ|ĵD:Ri$l8ص҈.񈞤:36+cNULfDN| kCkb$S2jkm&}Aq( (R>/?:3Tm[GcHN|g{` "E?+P*`R%E.u/V2Meh3bHPd@c=Q؄H %cD8˚ZWv}[ۚ* R]yM 9  6gWT3I%R!5,.4mK/çTH&jQe%%^\R4=VSƈ)`\pD BS%!ݷ h\T][\%)s]Ƽ]E]4 Wʙu%W^)u (-+0E%%m* *+P ! ,^I$_ӺsoÇ#J|ȍŋ2jȱď CFƒ(Sdǒ]0clRem8sĹHy@qIѣEs"%IӧNmХݠ6mWfoS45殣۷pʝK⾻xޭ7nР KpP^,rKkba˓EvC2ޭdu1kd%.gEWn9ђgNUO-:֢[]0h+ڸ4mݑSʔN>~ƒ>7ZziᠧXsSKV~\^+ݴOSԥ_֮igk߿{rGMvٸA~jrbHF2^r[M6&9Bpis#a;ʡY-UHGDYJd"i(Nsb7>-zHx]$^`Ttn,l(,gY _= &.G/yGeUyL FٳH]ؔ$E)ƍ6dBO D>%DeX)a%/q=aM|>bCM1st3 Mr-a5Q:]z6DQc7N;hP}vT򆧊N١up2xAԢBH0fK4XhʡĬ(VFMDa6*Jf]Rym`ߜfҕ/^ҡ^:|w꼠Ͱ=c^U,dZ }SA ^FVh jĄ2Ece+Cntj|HCjVjk:ŬrUGK僰3cyv%KZbSWV(ՈBפXǪ֖mn͑DNRKdS1TC=ށJ_}- 1R*>jP`YS/{5u87? F5C 5 B[9R0 aǜY~)eL^58F/`-ơ1wn~EMMcG yM"s"ۘG!.,Y5az-Kjdw1f-Z \5hM[9js/9Џ& 10:ev܌f< p쒠x5 Gwcž]0UCvw[|ʚַa%7/>jk\'Ŷ l 3ь"hfo+վ Nn(#;Pr& nZ#T6W%=}/^268p3aT06F6%Zk Q3m#dVdG9r,,׍ߤCpyRqY@oXoxfwgK(x5>=ҨH>>'+׀_}xd|1ҁdhֶ8A` Xhq%FP(^V[0X KЌhV0x谈=GXH8LUdkuHyhGh0見} N`` fns/{t~yƇ… nHo6㈏"k&d ssrh1iBlpb992;=i` A)n8g/(ZT<ׂ=5 إ]ʀZgYYfYĄ>&9 ؖ5|5Sv9'x G PNZf@ Ai)GKiOB':}iصhlxfJ)lsiPYa賚kOA_pFY)ms89)Z  B鏈ٜщHEiZMQXY˧vx6wçկI׷QH>NɻOx&YڥjZyL5ȃ$K;Q[ Ղ&B5Ȑ(GK˭5sШZPgițW˱ZŘj]G۟'[7JN0v@0M1RMGä3yg[&Z忻 jU9'ma,(9HS뺄uKć5hX ÛT3̻5lÀ$༹ifkڣk ∁bɬ[3]t7YKEG4ȱĹmj#l Me S=VZ] / MZ0chw0k]Ɛ:>=ԿlsmʷӼ=lB ~Ӏ^܅mؔkhLʷ͹TTcOڽO׵ddhǀُɌWY\`Yv؆xwwPZݱΐãYM$|k pǭɻFXEu @ p p ;L,P Xޡ-Hacwg:ׁLT no8B0i̚ˡ8@^ π:@]Q KK ~hṐ D"#~)^m -^/ 68$ؼ6eOEEK+ʻ䕗N:Y*^zh&~C>UiXjN qn)^ p y}4Y8<Yb^  f  #]f[fmM#垎-g, =k΍Hz2> 0W}IpN@. m@0ˍVXu-LTH}Ç] F@q. o]>ڳ?7dfK55$ ] })$v kb^iϱ_m_@(.&^O^F摕 ok | ~Y5`j}c0K"6E]$X d-СÁX95nwЍDg"A)SK[Å1 Yf1h9uol2,b ;I$GC-  rH>qLz1tCg]|OptHaAL-֔)"Te촻; H[jIm-I97(7p8CeQvű]tH 4:aU'!M;T]ƪB+-\j2-+v?_{@0UX(ݢ +d6JO]HZiE(D۸vHq)DQ[vOxcI_|{i`j4`y`Wb!ȸW6a Y-␐RY&to%ifu+C\tgUH|O(R-69&i:^JOX`NXV&mpkm8LBd+Jnd΂;&RrhExCr&\%?Ӥ]s ]tztuA=U u[,i7[/zh0ĉ +DYɒ'hKv%.рkRa6p@,xdmBvsQy:$BN pCE}I]Î:0>= QzyXҐ[5Mw7IJH@|YtQXSGrI2EGpI{ jy+'LfϟGuMrF]Kx <&((ZѾeGali-&lڥ#PB*ҿJm$]3Ħ-aB0 2.گ A 8p2@U.zTp{q=UV1̪^JVH"zXU0#֌gmӋ <2c-=VYl,` 2OLg'/ie7QjT㍓c2Tu_ځymY5 b jX;*8,H%E[6 ^XH e-!3r! 6w&nŠD稆4f%gƾ,_ӻ^~GD|P)Z+5k_/]`;pbh{.Xmma TdRt,`F3-@|g;E$N>mJBR Y"6*T1x1X}6, E(G7P'8H縲5lLtCEtV<꼁N65SV30%kfzk_h ~<62 ;{VΦjtgp)V񶹭]og`;rC iģ!i5S cVL"NzRX&M4UW? np vrWxaژo W򭑜+ɫ9i|y|SE+Tq DD@F8M;)E?B+;_]Y>@pzYlv1+8j LP6")峭3̥\5 _W+a;Dp0c޼陥3b)=ocZ\O2[;0Y[*PHK;eN<{'KӾB>L=3SPAkA{; ;PKAD+/˛4jl*9/S=jB\yň#-ƒB k3.ԋ€$8%0*B1H;17,A8CZA:,vwp4H-'-@t $k\6.D 9Xɦx7~{2tI'\EDwzQ,336-\_+Hʄ"1;ZHvXxfpJ'(YPX[p_Δl\|a.g/b]EsߡLHۚ ڨ b 2^uɍ%CHv7a34aY%SPdR4^ PHddؓaM)l\?_$VbUs(⡍b-#ŀ\e ҵ"sYTE6 5908,)᳃K<^W">VPuEH)0OcM[Ȇp!Hᣡd!dd_'^TG%hnI(j6 FƖ gը䚘_tNgH(+KYNLg*|ygb]+_yYָ4FHha}uxhJ JpEp,f H;Yyh^AxpSMV旆i] s>g J|黈^Wug׃SԅxF^aj2889 j 1ħBɍMRmIX=, Uֱ7!6h*9xSSz _!{юM4N5hx]NL$TLzoJm;HKAqmd\JX>FPeCfuxAOxX'H\ @g~jճ2LM*PCQBƕ?_ʮESegDE̱oV+1"M>'8 b֍i(0́ N/:r1 ҝC;xsdd ^pS+q9\q_LqU8.`-[3+"/0T܅cFB.K-R8Gp/(HU@jȆu1*Uг]K ;KHq{I>HFj\CD&0-˜W(K[S~. ]~L$#u',H)+Զr4pUC7:>v;d<&q|4 vJ߶@tEos?wG_LWy x yڥt\ݥµ٦à |oKUeUuc4$9/7. TiUG9f~IISVPcp:oWOElzy],tCM,8 rϞtgzީ /Ou SތL D抏,[a ])9k@vҫ.&&N5wqy_|{N Gݥy-K8WJխM_3RT'GӂhjX՚v5+,?~c&xΜEs+fH"FYYkiM#̘2c̜:wР.(R]v!hRUj1^N:K_Q% ٱj}r(@Ç%P/ |C4Dx dhPBō,TXР!.=h&B5|$IvܹdԦolZ3_䔙ƈT,:&z7Z|R/[yLyRb-bϪyQp3'5:hRG-QN" UDMU2 *#V\WQ%UYgV[?]rم^hba)-2Xx@ P ,Yg5&Zd k5TfmQf"b*$!Ip "A#a%W]vGށd$b<wJ{3gS}(0sͣBY Q*5 Ra0\ !WzXcǰ\!xW^%xka+؋1X2&ezpXi8h  YLbDHH)wtyG>ZHN&!Y' EtNGv(4d$D7R /8 0ET4{Jhjv#n̂W ⲍZ@=Pj#:vg>&쳬 yIK,Up} .Tp *pe@*0BH^Cr2opfQ I%r'7#\F ;8~FY0 26Er&S̪h2!1:s_6̫waYќi`5)d6YeMbJ|SPAFw)d@`C l'xʛw{ORM4A%Nh/8'8qQ.@u rB Cfi`:Hh{2,1\p dˑ7%lTrf[-WK %gp,08 @%f& gT@B":),,hy+Gƒw'i]K"@-+T EЁV . OBI@" \lF!jI'EN O.Qs:#B+Us8]u1+%"8Zo6a5#I4J,+;P nÓH ƴn8Y({Z%ώo.\3=, rV41Q#$r /*g`#X r-Gnw+qd .p|wS+X[0DĢ(Zs@/o~Ѯ%o5bjt誣 0 )F^[[ OuhH` a}OD _(A 4_=_  P܁)4V=}F.U9^H_1@E!Yރ)_ZE% )N@1Zi vWTF) Am$5!A <@  @ݹm&T2uFԃ9(NPEbͥ́ xEE%-9IEj1pqP_a}Pƈ{a. ]_( Twc8Kw95&bl;:" C0L/xa]DnZ8Ubj.NԌ0 lTnjTӮ*pZnN(mU6DqTSXJ[,YUtJRFL.͘pgBm O@@jVTRpiŎ m.A3(>$q(*~ku^6.T p b`aYʆB\!J$JÞq@Oҕ|6P,(b II'z@#"=Ts5#$ i3eHX^(f)r[4*#R +_W۲USl !V m!W\,Ġ qփ5_3f4$bñS8VY.9;ZpBCYC*c;;EejH=7=sJ\lv4%kFCWHt0$2C#"G?rFcsx$DZDĘt)PJ/PKn'40&% ۸Dt3C\#lGc3VSI[B9!.^?|T*Ms`Ų 31c+`?gF^MF.z#_ikYU;4!2szXFZlVm?mov p_ޒ`/w<7G@tO"ЈeXds e]v n*{k{7.XSݷQm۵y5@C\Z*aaƳ,;8 CsxT @ 0iA"](g3t8zBttjsmNjDZsHǖ*g`܎o_xdq  e^9? qx5Z) Yh @n9z4l o5˛ĝ'x7hGOE2JaK,&"?5OzWz аR.p8,ikQ@c{ ^048|h|/HӺ643;4*XBBi_G%(lx˚5 S=eN3|Cy?ȏ/쵱PMe}D< 8I/g7tD;x29HC |h}Vbufu 7|@GD\W׏1#&LKOxɟ0d}$ƹ|C_4$$p A #XzDaQ=nGw$o{++z 㳇_0ԟf~%>L*FD0 TQbĈ>iI,Z8cIU0i#UydZTh+9s3iִygNq;ZP׈- ibKAKS¨R]ZVЄvZ l# kٮ-PAsQԭ[n^,X/k(X @7vĉ (|8?YE4A4JN;`5 05Jbt(d073񄕓A.":|pऩn.;ƻ,wJҚ>棯+",j @ !0P@L1]- 0 !2P 90BTJ<1rgcTF1z! wMA'|F8a|.NʙJRK.jl/R3=6r8pu Xq0Ce_E%4҅pC . mD0 (TfsQ}xb *ʘb26aKVes'hy6j} 5u+׾rӱ:5P=+0yC,߬#,R``=C>P3O>~.M!,Hhhx T~myek6(9(vڞ2j*:(r<\ObA/NAu_l~8 .6/em h`]O7"&5# $HÍ~X>-hd}jM ,QL*Bc&tYkXQH‹Z1 Alj4YX\%4W>_ 9l*ӭ n$Eo ;eX)~Tdr`)XAt@ #>c>nRZV~OJh=^-X:[0kmeb;AJchxeH ``$8j</sـ\*EU!~w7`*DIAfמ#Fz_WJ%T /t(p~PlW"Y}"~&rKl:W%T u+LE t^ TB| {qG\Un&UVo)ݲʗ{ksN8pϡlSnA$BQ^1|;'l*3tIhR/ vW0Ӱ=~Wלrf|ʐz+C۹UgխʗYZR|pzf\ҜvР{{lы^ 2yЋWn fh)Tf&;\+tO+P./b8@Of̤O@xFn/t.D̦4Bԏ,cn@"Pf" # 0 F DO N . n&`/$PMKn1pM#*CpbL"@hP4O^0b6PvNݺklxLT*-P: 2&\ Cg ,ढ<( j@6k`PBF$Yp @Fњ/w亏^,^j/x.U\(n8DU@.M gWl\_ ʞJD'dgn1)rQr :Ɗ͈/o3N 1eHCˏ"j E$1ѯ -@皧T 6z2:>Q6 *"`^gb iJ!;*"w1'7y6g ~#30FNR* 0Ur0#e3۞ll-;6@0s=?ި 6-` :!A7R??gӫj/gt7R 8}A3DRJN 9 QB:KtDtDuP2.20bEC`!6j8b4 @xa)@!H+(7_)szQ+t͂s.NS3`P: 4gx,K/tھ iCLgg1oM#S62 #EUm`z`tcr3*OfRѽ2R^ӐV_O&]6I6J`6E `0RL}WT^%#o=)p V O +<7??+['dtISA2o3j,R@ܕgv!x138@irOj74N7Tzk1nOʶ!>RuJnnnǕe#`OPf3f2Z3Kqq3{Vΐr^1-Hz?so!8FuҴ v g{u[wtn7e-ꐥE0l5Bȁ_p8= CRv7srv@،G mұb<4Xx`4`s}}. bb3-,ϲwXNv+WWL >p-X^D$֥y_*hp/$twa'u(x{I@j |.(y2zkyg3SeOk}TAzXxbh"=>E>f'(+nƒ180Bx9gSXqQFhVxa"mYj6Ux|YDtoCo.ᾲbY#)YwYù;2^1Soŝ+NmpuF2ٕ[UzfփTM@vWs֘C2 )OmIp:†ehr8ZوB˴LDKm'vUƥOќ5/k]q:jx7_*gK(_١P9'(GLLpi.W&͑3XEz^q'I"-Yz[}aTY=ڭ9'ZM~N6[Zi@ 4_B&Cg苑U BâreB/zxUXo:ƚ}g( u3%N)B 2cg.xqRа{ٍ{%Qu͑DV;'ٙPMBP}b6R`:#B"< 7: (JuCG[B~S".^4Rk$C]!ݐ\n%e6.&M{9fbܼ譾ٲSJsEe{%}se"@q "4hPb+R$ر}}zȑxC *J| 3L^2g<"Ã0pA@fpp@$IN CvUd9h7X|!\.70ܹv?K7˵lo4 Ŋ:~ L{qEDfch͡Ѥ9T-jk~ {5fxD3j#Ȫ"MvږW23zO?9dN.db'Wu+#~J,`e^5aOW [9`b1Y%CM4DZp63Yck kņblЇ 8DF4G!g\JѱL(L9-Kx^S)]' uR?ـ%DEUUW {eɁ`6Xa7 Vf 9^[ ҁlA `^ATepBf\2K5܆i7ߤcxڇ&c4c#F8#܎^w$E&Y=5$DiuMf A9 Uy硧zWd xU"u q5x'sf&,{`=И]Ud!z5=aIPl VjЈ3☪y=\sxzld.MUg@T* ?=0eWa1+o4le$~DnGٕ.hVe' ":AHg8nvڑP&J 3yU$ˁL`t}< %Q,׼MR%7usֳ=?+C;G]st&sfY@ /X[M()ш-H- Mx=K0<"74}!Tjz?÷*PVId8fl3R3q:ҝ/MNr%\2JNj; g O! "T`@vɐSiB7o8<"`bbgDOK;ױǝ*\2\T@?`2p%1l­´DZCB Q@OvhB ꍅ;Xb8Hv*spE PԻ b\S&l SLsC)R\Ph GKыH Z%Ry\nR*p拋2`?PSN(%.B#Q0!}5f1Y  YARS ۔VA3IU[YG׭hTRB' RPOYX_9pbU$́l#tt]T]JPSvOBg|EjZtdF0BMVS = o$Y2`$}㞢(r6CZNNE&$(뱸l%4~5(zC'a {" f4g~Vӛ0EԇA'yS0Q>1%}p4oRH#JmQxpnkbX%.AA 򅪐f{tl/"7NqNf+tszH{f%(8:b&jbPd͘~PuuV%Q? o-up1~Q1PSSJXq qlP<8[ Gh| f!l<12RX1X}Xi,&TDx8'neqc2cި)2?cJqKQ^VP6cBuq-ѸEMR"kd " <(Guvi|p NPl!|"Uh"%8}_ҏؒ2ƈJ4!@!,L5YcL‚2@2CL-$JFP”DҨRiyFA2fp wbNdyTޤ l9Uͳalw#}x#\1G&1lkя5FvҒ3nɈUiP$U@FPS lLV3FWjm"{]tlMTU p f0c) fyTD Ɩf0c!y!r˙w.C#} ƥIkL~yC*'~(v d6e-F'zb4-RAvXk%%6 c^k t<ZGUB9[PcP"аl'wVU=(O9 p%94.xG:^9Z$8iޘ+C@ZGdka-}&C$&orK5Y%-+ aN``0|$c$ PNv0vӺc)("r BcǘJ{9ER@5@4mz>h:&#EF*-eRxD)z-\4g:a2(E  qMЦM@4 gY) J-w҃7z{S\:GSnJFR%0ZnRLH@*gCJ$X)z@"+-+q+%fvbӦf` ngf0q`h09!v|V"!B;>E+,Jȏ,垫s(n?>ƍ?:8d!X1Mm-Z*[*q/Ր*Dz{f fp vb`2 3K #= qҳ#z7Љ1Cے@4PR8HՍet{wJͱV-(hE7s+fPvg9f kp'5R&zf[Nm&_ ;0;-(;5`gl!`Yv6H5Np!ObkZZ0`Lh/qb!?LJ/(:Bؘ,7YW:Mr@f@Ŧ-nAY@uPm - p<Т/<==Hȅ*I ~Ì dqzF(z*">zHe(A3QBH8:i $CIJ-B>E^H O>q ǰv* ÀZ7`Αb[h~O뙖|xz~jW+*QsxFMiğŶwmWMr@ `l5o/rBs<0="|815&ia)Ȯ5i5AS,'F0q\g#Fq㎮#MCnM0Ab{r b`Ԑ ZjY',T"|8-~^AXHEPX+Jh"3 ݌B- 2$ /XپM% )OhTЋhuUDf,Zd1RJh]~4H'9% R(2r'ի} `i9 Zȅ}̑w')?!I_ 1?9C,Ȟ,'1jɔ*Ή[(_a'5!XјQB@?a ,F@~\F=zD!PC%O#SS&YĘ!)s|GXKvhbӬZĞAMj4U]zVѲj5d+FXZm۶uxPUWF>@|@p|A8|;=HxhҥM_FsaOC<0 4A 8F;A ܈Əի\R 5|4y唘4AŌ%[S*DfWZbPv*@kBK[z++ R+A4 1c̱ @Q4ʹ 4Ks3 F Mj 7K@8(2N;D! <ΫI BؓFi @* fQK*p0+ +ZЬՂ䢰 gE$1  4 55FelJ yLMfMĤI'$ e%8`F-YfD3ώ1ư7O,;3O@=+-B!^f &xHijQ,FXnF59! ro]s'FذIh7P6|K&YaU$V¥&19  NhT@UF4Ђ%HwTSK0!+^)h! ׈ мEcHt#ٯ5sg8SDr@)opH pl8Tg,Yu,U%)A ~  39J ;\@F-8N̢U٠QESܡkPkY5X1@7ސ$"ҡz& c MpE<$Rzà8{yR#@2r "C>e)`IF)afwb ЖR4hPi $)IsT6OP)ƀTrhKQn(D-9́s ITP /EM"U2hNs< XȑI8hF‚`rP$)yO哅P?eh ʳb CBz$v,nނ3 b#G'U";o8OI!I8X.L2r<@pZ3=j?k Nh(NE$)RRNlj&UC L({MF1[AJ!*{ ,?vַᒉMd0:Gvd8J2_vYPD̏9 Z< I*gOЎV5-STMpkAPl=ny[hs#Hr{'Hch@LQ ]j}$)9*&G* c:IUBsRDX} g\HIA /6V[{soh6sJeNbX$V's1e cNeTF5"$'j{5Vʡ &PL]p{u_K:RrXA2ׁx8Uϸ~coNofxdv+A233&4#;#TSx5D+^ IcR/yYN eɀenuI!A<% +n=B%l0A$[mk&6CzKx;thzdIIjeZq \BM`-]^ (.Jv/ 5ZcB{Jr :=cDndv FCd~^mb`X608EX:V?Lh5\9(Kb#8=a&+63 6lQs>!Ѫ껾c 5%H B,}S?ZG,+Z, [.J$MX,%溺+4=t<{ŵX (@njDk z$bBeȊddHL=/xCMʤӼ2JPA\8]*+?«ɿ<#=)3VQ;X4*5)eq;ػ8>N L{77BT:tMC;=٫"8?K8XԛE EL)aLADȔc \Dx9aH9dkPeP O ) zSO-D-`+\] 46.%ݍL,Ԏ@'5#^! QB 9*%4LG0&,C>@j(E*ULYMӫCs7!( YO<<~Mam(K#0IOl Ls$TPJKU@WMUV `y,R1@hsJjPX兀X5Se([@؄ݏPDHN"ሷءSͤSֱB;@UACJvTQ$P:,. {M…ojUZZe腭ڮeNh8 ۲-[Dk+HA0wˏM &uL ,Lme(?&L X}u+}N3HZMф4ؕ]XILsM)b[r[mO4ݺр Rk(M\I8O3”>H\ !ŀA}Lʋ*hN]eZ^͆=H}Ԣl@{S^eݪ`% `!pŰ&aS 9ېfa-Qa^-U%0 ƭr"P-(-*+,bF*bFs$ ! ,^I$\ӺsoÇ#Jŋ.jȱƉ?v\GrH\ɲubT9Ɋ۶y:#}IQfVZӧP̹R ؀iK߾ʪ]v,ڷpQݻh³亻Kx"F1ߢ#WDIpmrjޜF/73ց \Jm\6aU]qLv i;/)_μyɧ5r>%uNi &l_Ϟݏy-pumwܼNSh`mv}Tl2Yr?ᆆUGDprUL'xřFx`3hShv/Vy@PLCBsUdaFYK840˖br-.G%_9h/XԉLc3)̀w%{y圀Ω Ec^`*WsVb-]9&奕ܞun/,%%-Egfs&smߕvjg9髜ΉR,$qw\`8E%c׎9{2Wi̕~gʚf3Qf'+/*M5rx%5l'I50{,z +n~;p9Ăo*Ѻw˳鱮,M̷,u3J$?]%ai-in0g_׹jӜߢ;(ڹVMAѧ*I+(bXPdYM/ub-v0Y&]y׳lޔy:XܤיݺL,}41~898ƍ;Qc;yK3C3wg~i-<:75㧯>9#.~5`J;-k:Ї/<󞧫m|gPftM\3k} ,x+/w (E:{#La k@ ։z'{Xu]l9+띣̢Xϡz5֧E5Om5F6|vBp+c4\|iPG9Wa2 b␿ b=(rcI|sLQ]U]@pqЋhǘ2r%l:ꅑt쑏}#8U0 1%%e쒲dװIFTLY:i2~H+ʼ(q3lG-o >a _ @F632c(P5j)lv&*f+teZِ{}/|!#>I Z ^IŪ9,GN &g.M9NQ6*d7ʇN`(*6^%F8<} UH%?\)K]zbt.S%J׺Ւ+n&T nh-",CT|Ӏb\UYutѯdmjj &[a=}]l7v1Ws;eIuDpJڼ1VgT&f< wZ gC+Zu(e.tVD5㩚7[ն)6͛{X*M>U+ 8h!< [%ah:WX$PLU`hVhTV#:׉'jzoMi@׆c pjXvEgW0jvQWjFXiz7k6D' 8{F`UHwPh8Ӏ~j7}(Fɀ":8ygvиnW5Iؘ("gdp3h8]xEO 6C玪(  =qxo/<}P+8 );xl09ȑ6#)s%).pȵ7ʰA'pbqhju Ѕ/jȋw8W'EipVLxX *JЄh9rp ud薠txdYfk4T8W}dhs֐y>ba%XƗ qfDIyezӔȘp0 @0ɑlmpJו o_xh0s!陞iЁFq^tPKʜ )HhX\rG p/Xj骟pCTpt?gyȧY8;8 計t'zך Njz` %Hsǝ*\HɣZ\wqJpu1 =*;ɕ] 柲yJW7ejp 'yY$Pp蚮%p9J{a{u⹰릶xzi+k}~ukg>kI;۱i?giGT:媲+h{nO{x z Y'FZK& ՕK`{6ttxVKkڠD8Z ˥?Np;wOX%ZK툱ut頷;i[h۴t7E z}$:y " %[$P嚺Yʺh[%P?O P{ P *:J˯Lx+1ֽCG~=D.)\i~:}tLExb#?d+[ h#0-KK< Ġ%lMji}뷄Y;Ko 6ȃWf@DjzeIc a/\&[N;?CLGLˍMX(Őgk {e )CIƾTLETj !?xǃ˃[ qlv MrƆ: ˺K%@7{bďTKʌu(ʛe ze\l)h1@Ζ<K쨭9ͅep˸: k':\~>l%04H0TU0qNl^I*̦+^ &릁kGjD;hzGϖaHLԷ_V t7m۾Z m7жS@dc0K \diiuC똤=` 柑x vk$['χΊ\MԶL T0=?8l7>9.\ivLCLeTjP>| 0~\ы Kn$N;mɚǽZk&  -LwM >L0z1n @ځރN<^m[ Nkn퉊>| iYꑋ:{Zl˕^a S_ W O#MK@c|^Џ %_~)+@ /* ۞;A&[؁Z)͹  .R? V\_aH`m:m+{ߥ9PT@Vc p} *T[]$Zoslʐo3~` sa / @_{2[<<"<A 9ȑD(Lܲ%NxQ2 $d2(dٲ5B|yZ:8ͥCO? %Z=IcSAN ]Nqln+טa%SVd1i&IJ&*QWyk_UK0`jL @ lg `B9Q*-6jǹgd+֥M&{ܚ5'OUe~ҥLYufWk`ے5;ya*cR܎ ;x- h@"4AK0"Tk+2h1 ^z)7M,s:k:r4G$"ƬxKfr#/S @h ƒ&0Shp xA ?(֢5ضNneIJn|=wѩwdd*隲J9Ҡi.<#C-R.@'rʼtU۲KJ( \3 vM6<dK;_B10ϴJ Ř-QH- Hq*K1UΜNkQDk$%y[KktKUY6+CKĸ P[h`X`AW^7Mς @:4؀dT *Rي<?|Zlwۘ cT\|騤$sMn]us:zT+ O[+)v Oa/~od-Ƹ3a9nt3dPp*Yhᥘ&CoStF{jh"**fjv5EzL)<$/`48K-9Zmm`raF nL (ApN%yrBQυh?O H_tFP_B})êV L0)Тo1Y؀+$oy˃  4 Vf2Ӝ{1 C_>%8G?p9S >WU9鉢ajD1A 4A/en$Fفq8l˿@(pL$(< zɂS XAJ̃JPBs&@XBTBvl6T"T#`4 P|z)C "G 5sD1e0HE$}Ilkv9 @qmLI1 6F`2xy0Đ¹i X )Hꑆo5-8r"Q"w Ð KrÝ݃CQ)M9@9T+R) OP%44Hb2zwȅZ 4$;!0আ +:!+:_VquCZ„-Ulc~IJ>伍fb0fCXIs)ްyGbT-ơcxSȘyC* ՛Z$z@$Xw: [؛KB)bѩ}yuSH1 1;>$!ۋ(9c'4L?$klr:٣K*{3#ӛѲ;11 =Qaؓr< 6S#{v`m؆X0.HHȀ;೗ A+AÝ|{GCҒ$NBz-Ax&&DReQu@E,x2U7L $cP`ScMF>@NE4B=ڕ't TY-;d ɓ=NxbpBۚ7(RBU_HCH0^BҰ|T̓' H<bxzHV ",z+CÅal$k΂ۺ`P_bfHR+eխan&\]`+y4;%]> *'6hx8,ɰH1kHaE^2!W-vTi$fk3 O Vu`W I,0Cx" 1y⥮*͖ϮHNӾƙXm@v׶`kCDV\޶8nֿ&p18RZvSXN(Zl.9jod$yjW9GbFLo]o $ V\ :ltmfphh/n-Ru&EH)@p9<%" jqqht qgrrA5BrI$z\ўFUWPvJȤ`pkKpf2du ADb*@#+qth5sE$qAt˒_\t!tgDUt;2afa"=x* T~'p1 iE[E.m_X=v|g\^uΑ`sb7udeuK CO@4DvkooHmc]rU[5*ˌcQY'Bc#nЕ($wVm>n;OJChz1)x(Vg" ppx(/I;MKhgvKhkotlw"H]H@5ٻitу0(xr/p)1x:Ȝf {7{#Y{\ afrVĤXT^F t=s| w/)~'pU `xЇ8+[0G}8uMW?]hѮ]SfPBa2ԕ+~QXEb;G_0AʔD2gҬYΜ:?p=@C JP3PϩUr* JQǓN2,@S٠Eb(9(4BӨ!Z0K@{ tBvp1}łMᆣ8pr&2 Jr޹c`XSr(x1ֶ̺#&:fe#*fb۬AkVoϯsH М9F n DzRwF)<nM'$?@BZ~ nCzCC$VgʌH}E$^yn,SaZ&Jyq'KӍȮ/4&Kʘ8`<QWod/x ̒DŀЯ^6P[kOc|pp';㙷xGT>A'եL:h`&>׫ jR ph?qO}iI3Ud? ' H@A@b/{^Qũi|qU_y̶]ީELU_s 4@qupE-{xTD˴WԑMdi@  ` @$B- óAAALY^1 `m)`hLݝ,!_׷MLPt( i9ɁJx\锠W$tZiA_L )eY]E!X݋8!L H!%CUa\aNJPh >@ D}_ `x|LNMR8Rl fC6 fM md\E0V%Nc&&DX`'"d(&ޓ0Tb{  P }.!`f0R11@$2V(a..4#M+M#%HT7>0q T>:& ~ bɣ=SܣjD?ȋ $ASbdU00CREDN$EZ5b$Y0~˄Hj|K`ut嵣{#ЈN=ND(-ce@d(#10S`0U$RtLq`r&z7ؒK"Qve|2}E%L]5U,O⼐Pa&%5,&c`0TdNDjDG\dh"Stg~bXhjRZKK&v%V4u`C "aZapS2DȍJd.g>F\VtD}uj(fHBHx_ԉQU4N Pf!$ofݧACjj\$" j,&N WrM0_b @:hyԆFK(։Nф md}-ҟJ mfo&#R*k4 $Bb(F(HeHfBUǑ&.)Iu[P]V fKɥ<'^ޣ$ AfeFrF }*#44 BĜTY 48sz.&4Xh Tg~!\"$Uxx&K!\"@9Mjt\- dHe'febF D٬N^WjFY+D "j*j͸G PΌ`dhQ*a1PX+[z;V6!kܫ嫾O+*EM58pΌfrD j-hBĒba`\NPkR94؂h!alcfqp(ĐξW,ĭKЎrDl^!7sܒXAlj'I՚vJJ;~,xi*[  ¬% ('؂4Pehfԭl*ۈƛiPmnz",yF2崌PXJk0@Vқ"UXhMXNZb(5L̆ځB.Vb`O1/ȉ*@.0o-(6Z jdroQ|o4r.viz,IK0Uc"%l2=P"4LCۆ"܁ȁ,k +% 0p' }l,iH-Y6R&*R@bH] nH`TN픭0H-CY'ɦ/ jW'ݧbk,hj'kpy?1S.±ql0<@xgE1O|aN9//V r$[SF'd57At*kb`b'{2ͦt5r*۲2rb+q[/..\`u}1eU3!b_p^5_0%P @49})ܐ%U4 =+YRqZ>Ņ֕A0Z1L)su׍UE ֬`LE=ot4I 8%.L7 .ݍ+(!Q*.*} u/ QpZbIFQS^P@!y*b^ Ww;zUd袢@@}ý'w׽==[޳Waļ ^Ix k0(cv).[xwW`E#/SЄv-@9>Ii|ߤ~ 1>~8qF+ aB *$ZBa Ah cF,XȐVMjƒ+4f̘%>pD*XN7n!B4 jTSV hCΘ T)SΛ7ՙM IMpw*TC J4ʗ/W(#G#8I1v\R5#1E@i׎ A MX7#1"Re ^&M8u 4NDVٷwVE{ڵmz# ڋl `j! %1*1%,ˬ9K& ;d=b5p!l-x-Ljk8ㄴ&99VK)ri 4n/T <{2>)|J JZpA09! 0 CBL3" Udj`(!p͜Ro"PwThr#Ȣ"<%Ob'eb!̈JK-O?d:O1|bAl?b\Np>/tRF"IkR`5ZEkH5TQu\uWyHYC[q'kd"JXi|+hb<橤]0ve&w=&]]j̰c^ !M4@N!aI{zq֢j2֘cyavf嘙Zȝ +M  OtWhVzi*Bu^G,THk܃"ENjeG!Jm;א[7玞"UHoxR ӧ \p8tڈ0qT3Jv9GAIÈibIo|<giK'-XJ#yU@Z]LSK6[6AD  /`^rgXE2S]"dy5y6:T2I79ҡĘ9Ĥ٢gN"I~+*8YN~Okعx(9pq c4F'`R[h-n k`QV(D&n $UUy#Ie(<= '&KT&gh9@zx3dP>R]₴X sBe%d!j(F\?U=~Fk{Yȭ}\c7Bi|x"Tb;9OfRS b㿝o<⊬uʖKբOv`P1lEn3}fKvsNXYk ܳ] ܢ88\M X Ĵ%qsiqt2eW՝x71x 8 -A'"YW sy=CZ*k*#kZK'  DyA%-h0uj۝gL0|Ҕ1VFҥȶJ7R;ײhV0˹B/$md&Swuhܦ^ӮDk$SqHBiո*E ܡ|q\d `TA3&aeZR:(E9V|kH[,}@رcB!Ms 5BB/#ƈ9NNSƲcY{Y:`Vpn tqW8x4ℜф({v,w:h3n3ck4?yU C8U;MWzϛHfR[{`< ;@/xg VSe#,z/`~k^6MsՖ!}n*L!vw} =E/kڤP*.k3Xxa9$쓬 y.~~ΝaI"Uj WÆ*m4Rx˗F|4yV˰>1//?%ϙqͲ8d?8lX^fuڤȉpςĥMsfD N OESgBކ7϶/޺zO{ $" O*@`% w2@Nr? 0|Hn܎qP@Wn?Bǀ4XFPngJ$j쭘Kz?^#c ,@`0,2~M-8҃j+r·n YZ1ɥĐk>c.H#4#F$$O?adi { #Ok,N@0pᶂl ЇC<ϞnnVlKn>QvqzqS6x/QK%U2V#xbmn*jHd!L\E"]Fב3͈('ﯸeٌJPl(kD Q P!x,*mQ7 xd#VS#OϲVVlA$G|Th^&'%T`p /dDRY(b(ːF -jl@!R +,9jm,VLi##W-JZrB$1./ snK2,T11 q٪kgzO3ū0UD &!lFSbgsLC'r:nr` ~V4I,s(L-ДRt#@SGë023@ 48>I'bi#NnJa0KA{pA{$gnr?F9Vє/0= 2wtH*1OuFO"Y*"bДrQ\bPg̳4^QSE4RbvT\=GT"JuKtUq[^5z0Nt_qr # 4C'Xc,q$ZdXYƺ6}TfeU71-r5<]Ud'q2hwD@|&sPJGO<ƒde3cEeDduGId+Ϧ! vf"f6]=f"tV˸4^1h%[.U@~5<`i2_` K+tO( 22^Bь)o_(4Pn\n]zlf]VcRKwpł&`E(qW&113(qW+Msڔu r FM$cZow0z `HBF &mB0(wMMwixMo|y699KPN~Ur'j# t끫ڗ^M}IwpH&l.j1~SQOHRz3xlGwao!5v7? w&-q4wW3n0`jK؄qukZ_bX;fmj&IŠ z[/B?y\k5JqgQN-*h6MNW+8:4`h+2x[;d3K(}ۗ&N+Cs | laJ yrKa!6xIjhpxPy;15+Op.~6sIX~ PYn8ώrL73ehs=G_t$<` @GH/"oqKw#7ih!x'8c꼹/i5Ne&ey$@b0< .KW1U+8 `MAl~M`B hWu'*jJUW&&xV/ wBfetEdYw-(VPh1#%8_ :!$ onިxsSCv:Blb¬1 3XW&T@t1(5z`…˗8C`İK8h ~3;\njĠmS8ndU7Ñmƫ<q}5TҴ,&Zjl _"$NT-\&( MzeFme_[CXR,E:mgD?Λ6U"xKy)$y̚e"/>;yEfBD0J[^8{$}Tv|iābyVZ̠~z:q.ev35;O% jP9O6];XW8Xv&˺8@Rb^؛"4_5C7¾V}^% @jd|@YO&Z*U>=O [yJڹۢ+6$|_d8o%!A3F*U7X9\}%d@K|Ō;F *C\c9ѤGxʆ@-2!krᒶn)\f id+iPaP}{"J7eQRo }/qBdGlI{fb墫4 bFN,PB~Gг1' V~訛W|]g.;S "Z_ۧ5/vZ:GUeAש 8%i mAD2FTEL#YQV0p@j۶%DzӺr++kyӍR7`G m9_ V~K4uƧEYT+o~j zm!.ZL=i W$5sĪJ7Gl)/gSUMfZS7CUNut6𒵂H/NBp5QxF5΁w8{0*ZlA< ^ȣ/by28@=X*@ŠҽLx#F׷FiU>M*Vi(7$"qS[kDiY;"r33\հ95tT#R)-91t Ոadh2Z s5O0LVD, qoxKFČ|2HY9rPź/S wyiG2vio%xtF? ٨*4 7Moc@vC9j5 ރT{=1d1CX47<ɩUD e69ʈx`C!*5"FUJd^rttA*ӓt\Fҧ{Y 3SpaOGkڡ <7NoK#F"}lbQBrkZԶ6O8ά$ %=9="6]u=WLIURhE[ *6v=Ȃ,1Vz=}Z-Dfhqj<da-, E: 2t|AoW*wd sN!31|AOϔYWhD4T&4A ZÏ;AbKX'4_N(QYkj[Xk1O m U8φpͻl g9~3qқ"ч;,cc“rAƉtS5N,J-eT+RՐDa]e#1reh [+@BBz-glb/ۉVjj# ]5lS`m\n^J*!hG;q 3Ό ND9C5\Ջ)hZآ xi"^6 SsƯt""h݊nbh%ɰ(VVSK[ sh^Ӝ>Pux} >z~MϬzc׋ +|e7N9孱klFv?cLlNdI)bG?(&Oy>]| ZZe2Ýz¾`eP]Z/uBap{a'vdpkc=&LSlgrGzq2"vQqTXlwRXĴ0dTS!yws,~'b G@Tv YiPzB_Rvj&HU5xא^ vf-WVg|f%31lumvx$K&Xmv,sd]fs+rKܷR,A@N;iJAN@N_@Tfgd}!Zeeom5e  ]x ׀v)aPg#m%$1Fg$ua8 (hbH!mXa>Yy p .MN5X72XUbU^hk hvQ!XBQe8+m3lGA!>">)~_xxRv"sݘO'=h` PT@ P @ f =L_RBBjWxB{{pQzK"FAbm$苃w.Q~VXEk<$#)Pب2)5كbFk?i..d4H<}`hx(w(0[ɕTbiD–}*9$aF1!77x}E%bїVϑ+'1i؈YV b셈ZoLE{YpVI0QlV?gTryٚqE #gRP)mgux6lfs'R+Be}JB>1ިihGi ^bX  5YpbxU (vw'0!28YMڤY8?ly=sJ3men "țĠEMFL Lz\*3<K"2\'Ź[V-S2ixrYwJܰ6ŬةJaK_ `iy|ƍ ut+ǠXpL·kx :| 7 3L5l7,x|+,w0X9.WcZU+qI칦*/:N\\ <SŪ >Ng+]vpGPy;nIӭjv<̵ǂ+BR0+mkͅLMȓ{/(#hإ]|3 ٹ6% @!rw7cϬ<7#ITrЇ OZ(Ƌ41[Y隕$GD #M%D~Wʿ(@$1C>WtL6&?_{,*̻_,B%Tˇ Ue$I_} cm6$–(Y!҄AkQ*26́OϡSP }e%{ܩ؝(!ѩgCHuH UƲ"M &ӫڬ cldpќ"t۲h*}!v$*V\xv5D6+XMDؽHWM(^`jmُe } X6.voW=8!iW"]m&F3WbH>35CLѺCDY1}Ԭ ]W)cP P)Y- 6~8a-.Ǘ qGv*LN>)P^PT)+x }a0}K5]]bc[LwqލJ(nNr İقn`YӎWڍ<@ 2[_',~vW֩Z)nfm67τVEc}iط~؁xzEr.4,z{d_r@n_.Iھnz3.08îg)3D<_IMLkLTvە"\S>`{_YQfe_#O%(+_"m> K*`)8??<^f諾M|NRWYϡ_b/Q}H. 8CqoO'驭bp O_Wb"Q0Zp(:$y8#ɪb_@UmOA>u@oدŊWŶkվ W D 1/<.!̰ÄQ,F2͈ fjMJ`kH,hhC6JŔTj @t8J:9CA:f t/<%:Ϩ8Xs>KA, ӿhSp&B9-0 1 m84A`(ECg@I*m4/+풬4JX@(NNS2+&4IM+!.j,jjKۋ?l0ل3Y朳N;?$ ==OQ (PoE*\| GeHR))%nSTɪx#*?4U)Izz$JZu-\lk>.A2-5UkcMji7.2og ]U5J!`~]Nsλ]E9JB?an`AY΀mb[5#<{dbA,2 "멫&S# *mR.GpJ$6 [K -(i%$ UBfA=qm"@+"oZl 3 .$I.RфI O.\q(ll$*)%cj(ʯE W3+AӢh2-4BB: &"JAY VDa_KHE!F%L:k!@?YْMRQDE~Aj \U~9 ں βx'(?Eb0qAhWx!?eRPSNv{}y`Ω :[Ob(qV84@'‰0kIOO*z^";oא35Ks_0bgN#.S|S.H=Uw߭V/ a(&oyGlk\H8pyHm-T\-SWf3^==/+*$T>ĸ[ |`*.-Ȅ#LAQUe J2"tЅ.F :י+>2!<{.5cxB GAZ%P |cQ9ܞoi kOԡ򨪼[b2Xgռ 0YbB : ^$ν@̱ eɀvmb^܂׾-6s '.$h6@JhG p=:m(!ce,ey ʖK(|% \\,P8ױl#q?@LQ= kCyeNgKD#sx@i)FP݂2 l-2DvtO!۫rهx_#HNBT^ju[C#nl7{2ͯMVS#ŋE,"$EG(!aAz!RM,C{UxpW@UqxF5ڦg6w v'$ru d,F :8|<pq .O˖hf ?}lsW{hYh* $ȟLp!$p?+ \ + |h&1@0 +("dt܂-*%(! ,^I$\ӺsoÇ!ǏEn2jǏ#IȐm֭K/t:ܭ۹˟@ y3C nӧvFZTjz괯XlسX]˶iۦ1ӄ6i LÈǸqcŐ g|v#fngBX}M(ϰ?_|ymrۢK\okfʉDE9<8kw3P@ٹI<[<҅a/3{YŪ_ϟwhY5[7MKx75Jh]_ ucHG]uׅ(v$[;1UPZ;Wh=g-b7wkhVVha BRde WNE8a3:v[YvuWUyc[VzOT3օeb|UN5]wc:Z*IFHSchA c|^QJd%*$Ze-Eع̷*Y-m ]"w㗫d*j& y2'5ʍ&k<l9:n\cƕNǺ$o[m$LWŢI7THH3:[>@Ѥ>XΠnv6,Ѣ`0;hl;[p t{*[vVWͰI5Th fs 91׸"Ү.Tԋ^}0U56׿;67q1B֭w^} +@;389/ZYv+x^”:+i髻m˾u箭ǣ~ vxZ <9kΦPأ[X11tUfY}0$B 2fBM[3r#@!pdk5>0/8Tь`.>ٞ>F4u(O:uSF2Lho39fűX9Zl0Bqc"Y cn›'9I%*hEW :a'H\͊j%/NHKը9!^)o ; ~ C-tT$4-" TYվ q]&8=x01ǟZI1K[vtU+/t~ꂖ',sK_m<.ϱdžs )#4*d͎N^&ÙM/N౓tsߋ(סCv8-~U)|Lj?:>hl-A{PFVˆFH7֌ѣh%CmR3'-Rp $@\ wHp`rA%OIԲ,HUdIBPT{jʪʡ\=$Vlȍ:|F7*&ã_H V.j4P7 xի@ZKUOҙ]RVgǟ2T%6z7ŸS5Y"ԳjhE;ǟ rZT-ժW`XiT`M|ĻM@*l!r$Zs'ůU/)wjۧw' ^n,DBS|Xwy%D%&}cz8oZ?& ,\@ ĂAeжO'A!DlJ!4jOZhF3|iஜt - |{\rt kZG[ޭ,x{P9zs GXϾlڈ8l{jyk#}m[i8ւ y3*C:vvV4?n ծS\Evq?b'R;&6R/wg. 8Bt.14.y2nYl`SJƠt>Pgޫa}zk-xv_2cvM̷srkw_H814n$AZB}xUmK?[ϯj ]p.NY(~$.vjhq{ E|GkU̴Z;rĄ H8zii{A|QPU`h  V}&!,}*{Y.tl:€~'u :$y ku{1+UGSpl϶L('%r&v9$5BVw$wKĀsi`(x؁Ʌ4}J';Yg;ZW5@ð Ճu3&C85qa#[X8MHkUhm#cՀ Љ`(4'Pgf@j X.LsKXaBV5F8( @NUDŽCXq(Z؈,_c hmx(vl\EPx 0g Ii5|wjXցpjv6_v:wRUQE{GP6[Ld @( Ѓ~fDwEx{M6r V֨ō8n(yigHj 7fUǏ ՌzDVQ:99F(tTpYѠKg 8u8Ɍ>5 5]CmYm%Hdwu+9@|ѧsrxVc 8W QՓxOQWhpU pau ~r؄XX`I\lwbeY!Q P%К#P5I}k(e3h19y5Q>{A:PmT51F]d{+GY{7+GF`TCGƙ_^׍it 0#Pl9xsnx`P> ,J2xF=5HVIBnFgqKr&UutLpg h?+r5> H9Pfy `9oYxǛӝbP :mV zyzWHLW qT ZS11V&X^Y]%mc9p9<ښl)9;Gj Ue0v8d2PtXZ(ҲP'MuQǹU䙀'sUIy~>*W}pUZ|٨zqi JqXy+za^̚+>gz"Gr id&Fv8vZ @)I(eקK{Y?ZU|tqʠ^BȚʤuY{xLmi JuZl]ȉJyWP 9ڳyYj PISZ >pfɝ@:7>u,lQPNJ{٬w z_GKۙs 1{3[6>뫱9ND;x P gs"?^ 9?YW MEחw(y`Uȵ\Քfwh jKEK*v]؍2*w[ zУ*p? CKǁG; @P>OmkD0x PڗLc;꺮uY-;mn%rt{v[!˻8۳Z#P*pvuzPpebhÞhFZ}ô D[F̳ YƛS@T U0 l}鵼YØK]LU+l 炗ꥇy{YYPŔ0ۢ' ^lǤgukA̻ܳٚ%`9ɕ}|xH[g'rCuZv^ WB啭{5NU 09l |˳\%f ̻6=\@ܿ |̀z@LWZ0ugdFm 笵M@eX5π)y=&K=C- [y ݚMє<|p Cx\luWwKK׿@tϘ Rk< r>M- %DԂf~nv|wNEMUs+Ţ -܄-)] ]sw Yl\Ǫg˽ܸoFqZihxl=ftܲ$Ϸkăҝ Lͻ ܣJ@< @ f. |MuԦ[ߧ'veZi=-:LS, #D Vn@ ]ݨ؅!<@\xO սLޗ˦Vʈpf\rFTS> HBy]%iQ>J^WZM[ <3[ pٝ:Ϝh@@$bHTZhXh;gEK;J~Ԗۯ%ԙnfI5ԹN^Nӥ~ꦐ%B ڭī/i-Yi$甾1ަ[*Ƈk̎J[S55n{CB"ff?Jf0#OV.&_B][3p,|)NRTg~yG9WkNuTPo Oy~*vo#'OEk95ܺ,Z] iFp (O `MP 1C7ԑ%u'V&ꈺ&d6o*Do+>`O( bZf*fJq l!s' ;'CP2 +LLPpCұNuD8**H7ފϿ˱ H ))A$,rȡ'K`*W,A 2 EM,H3#E2Cͱв?'ɺ]Q\TsLКw]hRJBѠzOOiG|!.:5U#YMRYaI6@<^{** _> 0DVٵ%M L޿@C>#ѹ5wġx*,--~MW=NS &UTEU]X`RbCJn *H@:=XL-Z(/Q[:Nhjtyg̀Ј&h+LA 0:@|1 ?~ekiHty"[.kn[ń1d[d@ ʭIjips}01uNF^'yH;k\ (6cxh[Wmk4@LEoӫ7+{P1{[8,g9ORkά?]_6OˣqAndDш"w-p5E*6~mn6=[yÁG 8Bvha|1QIQ%n~M$X)O@¢C^#md;q9l؄GUQlS HHLJ|L!T+%.|ް%X$Y TA>%[_$A)P~2"CT3d UkD,%`͌:LԴs`xc0E M4jʬ}T;twJ'`demQ"/@A HhIJiJswV@pB7UkZ1hͯ1Ż3gF+Ta Nxcn=+$2׷ }]!kf|KF6YbTL_.sr}9M~F$" Kd(X ε/l۳s/}GB(5i jdبO 3P$Wsb6[Y6)>,f{+ʾ6cAfqB 24 *ĊSjڟw}}؆fM@0F/C o J) 3PQ %8 0 d>^# j8b{:db6˛_&g 9J!AAr@qΠ#w@oa}؇}hRH2p YBB/$vC2=5pëʈ8  $+leێd*h*ȁ$/@+ĵZ D(0'PSBF#ŸQuX(lWLH8;ج{:E:(C]aܝMl(# Y0_:tF_AbCF'Flf6C)g+(8tTGd4yďl̹|ԓ('+ichf`L'D[<\sCxaȳI l8h A?88 6p> 陫 S«>u1hxL6h?#oQiOPO0V$ dHNPƜas/)x= K]*H!,b,@;;`[ID;퐳i̓|kڳD$!YGZNz Gru̱ŸO7KIxNZNpQi;)LT& O6DZ`Lө; QiK1Ȃ&h,jJϑRfJȃ2RYMpBQ52͐_}*u]d K%ى+Qij hDLN(jB D53{e ?q L>wPU2)xZ1[%Q"ϑR_x2$ 1%*èX庪s'6rC;d>8}YԞT@UkiY %6 Z"a=/SfI47 u3c,{7tAU\B `FdWM^`HI@E֏(ۅ)\ uܞIqmߝ՛E$Q*3K*C!XvWP00H=7(.9[ O*S*xc} p0Y8^UM%9'͈h\ _ Al$ߞF m[ *\o[Hy3B]_090)'p1H>ωTMO' ͬl`歲 ṝ@ zIaCKܚV-m\;Ea=˶mܗ,?#ƨMEE(:#uDh/bRۭ D 80[NP9 #HYcmӏ%xӚBBYRB>QC5MLN=Dn*4:dˀbP R?p.$H3Zbjx,Qe N02ltc`{IJ^Bfދg^hΎt_Tt$d%Ը8l nܙݣW.qG mC,v,-$ jLm@XGX4XDVf?:*O"M| 敽;K߉ՠ J]hkl#d\;b`aA H`½kiaCdr FfD <E)6~Ȟ.*TuXfHR(30$N +dGxtymkٞ`F4kRbh y昈9!n T6(1i paak )uLQ.J&$WȎl6BQ}wofpR3@+cojjvS F̋v"b1hp OLdV\Ƹk ?D^x9Ҏ gHn_[lo lF=XH\rRЃQXgrN:!S*0"AF9b=yY ghE8…"5sۭ(iqFdz(dQE_EgDʀlZlmtL,l#raf`yN|+H\oLpA}"YuUyVL$ GJu&g8߻s5hg/_i3kD^aZq'& r˶x"9Tmnevwu`~,_Ma> Ɔ/ 㴋 w/ax_P]hBvIqvht(B4Sn#qqu.JO$y'DHB A|BP).,@ЂDPF;gچz47x!%@z K8dxL( ܴbsW;; l h0R r}o|u'Z(H @uY7t,h 2lDk%^XQF6r+ׯb"E(LJesȒbfNv";/_~ (R "|P0`܀j*V!Zm+ 08 >hk%44kAw>؛k1D ?0~)eqF6s!DǪ&}4ԪϽkCc^x6MhZFa7* 0%-T9\xɤ9Mv TF])/nH YK|ρC_h(An\rG՗^{5^1h` xI4M~9@pA'*#ixk6[nn=~#oYSQs 0' I]ܑ+ċu4awywLPCG^yL9ez7z'S~eT$jwؖbE|) j#(Z!f1r9#ԐBd㌥#䣏sFdF$e4HV^YNYn%xEP`h j^% * '~@Rm 4S˲B hvEwX p:TP@| "LiլvĪZ79i D9 hʀ\Q2)K-_k exgP / U  ~E<²Z.nu9X̓I8/6PP08q%@LZBFᘛ8[ } +JnHD)ѵ+ %_U=TmytK=@`uaՕW>6"FCgݯJ-Z SDxűiV8dp] 5珹D"p9&1ѓyfIzNM }f,;]c@iJҮ@y$(ljF gzKb'9QAL%Dqi7?%'#ӟ&-I.Iv0@K <,# G8Jn@_5%C\urDg5{u R  \RT86E,},dC? j#-5OAig\*38 [;put1fUWI"$\r2fQDj.M+_W+ /bO:;(Lobq@rx/ƣ=JA=u .=P"ĀtA> /p'g:J\涵z.t{#[7G.ݑ"pbY;oFoH59]ڊ]2d`^)ol.[a _rl4Ooc3lP86ZxѲ]:\$9-[hzƃo5f$ 1?X @$P*^^> MϩQ=áVAr PҖ# UՂ9Îܹa5P\VZ&wŵ61e\oN!7zRs?@C %8!z:015PSG~'9ʝ;J+ F^QbPqdx:AWx9a*lby 6@ r0C`f.RR?RD|tfC>XsD6}úC8̱GُԒ[~c4pw|ۺ~et,@] @58~ ">\شN-KO Q-"wp-DJjQr; #sҬ%I[V\I< Ր9MYСZ iV8A5 F JF҆Xi؁`aNm|}]Ǐ5Q Dդ Y`ce>H1O9iJVhA 6A@@'̂ Š: ..𡔌Y`O0`*X,MR8Jx) d8SA#1ْI(IqL o"1U"~T,"#c$& 9R} XKq!']4_ b<4A'iAA#8 ,ʢ;Ċ)WFbJ/ R!͋ c_#` # tʅM3. 4FcTS@VE6NYE8SՋb @4A@9fhT-߼;0}>#u?fI0l-L@f@:A$^BH@M3FUc[\ͺ 1 qׄ\a bJ>FK"B2ddM#D$INFI˼,]X?wQZRRVaTCZezE=@bӃx,!S]M(*aIcKFY?%9$,%0(Nbf/ &Qdc6Uc 26#KAe5SL T|lV]&_@$Jel Τ9\m&n2(n&p eq&rcƎWP[HJUuFcVe`fgM{v%'Ĝ'Mت⊁̒pXbWhaicFfG}DuNR|P` K 'fa,Xٸ ?)*BhL4ܨopag&0fɅ@UJD |<ᆆ'ny""dh!x‹HUU))Aě6WOHܩO鏊2)2*5.JhjgZ 0=y@ \rjmTL:BdԊmLoٱw&6[k2"mP$jf&U ~% 9^X@h a9:+TyBQk'jU0Lk5d+.Q跖OZtm?aƫ4`(Jje+Eq5iCvb^xHgLV؁ VFz"XB(?ǮMBQ1+q bNN"[N,11؂a&dƅTg*3lDFY՘a ,H}uZ'kbMi4L-"z-H)֖jmB[Z,4ce-1(1͞KѮ-dܧk`j JV hbҺf>]I4H-x%ZD-RXBtڝDoiپ-< ^`3]am*)~$Ÿ!`Mc!nZrc!%m VLk$!Oܕ-"ȁ^])ѰŴ.bQ6U-ՉbHb.WhU:ߡ![ ܇nfTX`㓅r'@ kfk B۪$d@TVl g T`0'[.1}-Onޫ om1; Z@@KDLaFbp6Gb Ȑc@$L+|$$ 3 ?" "/٢ZC1 v3c2'1'|_ W;`Zfe2~&T4Ҋͤ{>J< -EhJb*YTA|^ߛJI[u\(+atu_:M1F}vVjPGc70 Y8If}X2%2g~ hMiC $\XCuG?0VA\5 \cх^/_+- 7`IS-tɎ֭bNbv>OwhW\lh." {#P8|w~Fv*U8CZcU49Ix 5D8c48_LKHVK LfST \1Rw&͸%>M8ݛf7ЅU"P|'}cfhXgTm,o$g:8=ITCCw2|6bsr l>y{7zʥܸWi9 ڤ8~Ӏ$Z^NՌ"98lm]Oy?y^SzCsw[ pwZ#*)ѭ*y{ xXJa ^MLAGWk @ Vi}׀ k9FĔ;8@C=o8/, egQ@_$\tF'{@?C&=^OQ=[Vԧx5lF^|1HE 9jH1bD NӬ`ĠY $4geJ+W4G3iִMHx Y (МC z4(1Ă5mjkդ&<z5*T,zlX*J%톲(ز-\9pxp@Wp` 8AJpuɓ}[\,Fh9AH'hsjժqD ID!x>6|8"'ZX8cQh$Y:LnV&k>!UܣHH:}ujվY5X7ʖ8vڶ'7v\ ԫ0{@8L0`nXh8D#V;15P T6/v!N衉ጻD䊚 茌%q[r'Rʡ;J)J*b8² 42 )@2:BPA*h174- RQPLQE+6PȡN㢌*N:Hah"j+b<.R0klHT lCEl b MX04P=7C4%@5"UcK1FH`KI=N)TqRu(VaJRbnVt&()ĕע~RX/+dB,dK,0t4xg40LB =9)lE+w}t^lH 'f_%x.1hg盶 6VZqxJn]Ub =ӽ q9oPQsv&Ԁ˜feТDP7%Gp!Q&Fmh~{"WΉɎNz;)<wUTpˌHmOL3|s::Z+st T(=Q&( nt;ߤxUhrH2Yt< iI[FuW7L1#8ʵ&rKr\n~٪6P in`:pZBPC3[x pjD.ˆ'':sXCIyL*\pH,f+ b" I@2LhV"`xi@*PD$XC*DA { 4:;S Q썐!wBE~g)3<ƖX=bpc2.u-[`#R!eπXTjQZ!-s4  Mkr Դ,#.Eb IBPD( itnHf.2pk3_" BWY ] < T+CUĩ+pp7dnt -n<$I. IT6UHpSrajjI2@/3xE*(=r^WPVS2 0(!C1U׺VحȳhE/ڵ"!!Gkłp6~3|lOB2ШE-%1 UhM{l0O{B;m)Uʲ#Pd% ?Dԇz[F*q.(T+K ^# uaDb Ny ;5=JVq'[ zY`!*"|z? K,8?l:Z ?ev1la c@D'Bz*EpN<\02!C;$(45Ȁ%/  Ѭ52̟ Ek*BtO"Mbh > 0[d.sO-%5kwm˛ޓy5fϡ):x{c@^ 'WYMFb82f`*iXf2"݄\Ƭj{1{Z\!;uPap@9A&[g_ P^J3ƥ./̫ޮAΨ*vC#v( 'φyX VcOyX*x z5D*NUp@gdګ}cE-m!km&Rp]rq˜(4ms ׊;J69TƬ,F/Ls,"*;'U@ {غ  |}"x6{rL ?\.#dHݗA+ I4\bӿ$NM抅:ayd&8^*QS.(\/ >A]:+ #,R %s"sdِ>$V(n۞O^BPd#k\HTVU9&' {%U. xpo~A!'c E^.jl Bq0 @gP|HgFu:P\fb$~0'ā$ nhbŏUP$xp q}//ߒOh612ҫ .EΫNVΪxhi1H}h\EZ4 * 5RXNTN"Je=q&fT nz ı$ /BZO,k(V;KNEؔXsrą,R* .n!oভ|]e%" l")r," -Tf&BH2 qVRI%%}!&)v'+WfžO(ESIF0/r.jOdefҷ(NN8)#"x$/A"n +qo01[?(x2_j60g24N @@:'03, CR"հ{vZ9!#abcVh8kb%#83%&A:s ב:Q!FF(0N@ll@<.=e&=[ +b׎5mb6(}qjSR {v2.3k/O77E t [H7 -F%cWH\uEVH澸 r Q.@: U! FMzԹgJL1848_9x(_NDxŞn?W21\dj1WUX_7o4Rh^\Nw,s uZ5+Qg+CVw^xς`xmAAA ("ŠJ`:PnJyEw=A/ȫ-q2XX;7@"77Ypԇ1.:.!#MpZ'G֧SAl̠ f9KywUnV2^xOs6qkͨṅBuZBvhvIs饐gOߘ\ 5꒼9e;IjQm쀣L&m^P^/Lq&\Rڍ`e8T1X23Wė(`y^*]@ѶJ` ӈ]WC]T6 0 :AD9]?j#m\#:s"Ue]d?_'ߔB"|hg2)2/`QRlB7.ϤBu@6%v0ÒJƹ9. FgsIC{:6#V)([شC(k|s[ϪW5'_KMBg\vHq4\` %!D9`x5bUZSmX-xɛ0h!VWJ{'B5Y4ٴSYŅhPblڶK<%|,y '̡ Le$AmT J/G̻&m\3V{JCۇH[绾MX4U@YZ+񷇬₝Qq6<(` !-a9$aqmyNU;z0bpkbz%?BtP}џгⱳ #/6 Zzu:P ڵ[9^LI!aT%P; OYa/s)h<'m=۵M bPF]]TdhO ac5sԑdcHsu+OS F|Aa΁A pᐄ3b$&wI(>[г7}%f,DG>=υD)4:A*6#bq%zK5[3FO!UsK&`%R`"D5m>8Ō{ 9_*|ק'<x CS&*J ULVZvCVx`4aɞ Y,4]⼉(΃AԾ456U)2j5֬gY/Wu3-CN, McFsMNؘdCeg}&ZȶiI@-AA54@5Ԡi[5ZuU9FAJ=1'ʨQtxyםEǔyb0z1'G9́< !&sN]*4CY 2EbZXأLadB2Yg#FZTkIi;ZlWmωTOuW[+VXw@4d-fK-Ψ,^D+mPƾؒy-i|W5s4^v\2K079`]% 4L(bbBvie~# 9\|y.Sٚ\ 7]o yrqrC5@4lR:PX~@-夬:-Ǫ̞H̺=jǝ Mߠۧ:f@c5o+p1>!f c1K c M:(G-#T:3@ 3B>S*Q4DtF:g, .aνpz"HYSN9%v] Vw[nCvC0 ]Lߙu 8S˜CbB'Udzjncn(S㈚ 85#KD+,g3tpZکH8ƴO55Y`Ȇ5N]uD!p4ODڦg1A[{ !@ } rr F$fL&3Q+!M/["4;|UwYfcVF]qZbH] Vp3& ]w"2qa}^(oaW#.ZV$zqI]9 ,pcS#IcʌXr8(]NOж] AOB܀ ͋AvVy J +]yMVvwx >Ro)c"Q>P3)>2jM g U<91vyAъʚsn%t$̲KWz^+6Թ^_SvӓzǥFʕp`= R^kXNF6 D`ԷCYF{fra(\mR8ZZyV\G~~o>iAW{9e}h!4{cGvK|vLJ|Nvv4H_1fCUߗ0GP V~6y~27]&dkT3@+~jU:YBH3kTXaw-6dHMִu6{:b>xvY  }}q P0Z}7/1 dUs(yg"hs:~@Y$:"PuC *XԀKHo;y:%{E8z4urtϲXx-2օVE#AWYar@Շb}^(V  0 8 ( H`9D'DSY$AW,ʼn?+DUvWXpuAW;KXcZ ( 8 Nö 0eaX(׈à؍\yX-"X@d,űFqt-BpM+AWۡd6-7Qe̴,_[u]1h$'p5`QxbI.VgN f ˘gBa) yɥ 0sY#abB<,7ze28{Ca8δ#_ُHSsZɕSȄL' rfh)jYlnY dS =i=6yї~9 i d]-CTI Kbvis%FziXq-&4 cǔI9*GYjTw"f0D I=ZCCi&) ةٝV่ qJ曱b铞%* 1+X2uG%){ 08cQ2bXxHǃ!C" jUP$g{& *٢.>ktc]WG#>?HFK:@ X p3 t4)j_# ZiqpѓC(6۷(a~ +J领)MDI7zYa%Aij#9M9@TQ; 7Rv$AYy#@ iۋ@CODOatӰz:|0ׄ*Xך鞀㓘QfzNǟQs tZX(pWu^Y%Ѫv?J;J[dU = : ~`"Lo Fr;&;LW^bIHX4Sw+GBq:[~4{~jќQ͆#lÌpZ:ԹP pAL!#YP@V >צ5(VUio+$L2+G tO-28n,4ce$G1FI70 f`q qC,Etm!gBh{,YFRŸ+EmG=KH]{ǥ=ϡ_߽|,YHڭڍkHi @lɖ|x!{=΂=ʥ<ئЇ8aC-FMӞQQ򳉖"CvZQ0M[XђLm=xVӥ"vVz6N4x: 8 a${ÒFVbNKwB*Z혾d(p^Ҍ/ެ)l7ZWpmWc@O/}m#f0SJpglܘӅ MGG屬rfNQNCv18jWeD#sA Qљ:_&T0nMn]6<ҥ3y \ E!8=$Ai +FꞧQ{hhvoSje/GA3^ۯ>p{=c:蕞DN\'ҋ!wMb]M%? Z{FcZJQ^S0B-3eA͓a'Fʹ םPGt'׿MbxNTi>219śٙ2o{uUh/  2_Iڢ})|ˬuw<|~Og؆mgUV%OVɅQLXZDHYr3\vD%@BC!&BտG#\nIk yąm!gQVKI|@8(BDK\XqC8 B%MD @d 4hD5mڜB;YHETaҥ67hꆛ*tbtQ]a%zCаF{&堡ƒ } pߺˠAV+bD$NPbF;f"V0(_Ɯد_={hh͚UK5 M2t8Bl55#S<9|@TyN<}lҵ|TmW/'Wfj-k燴ƕ ;‡a*A[$̲l4LAJ;-.i$m6*a:-$߂#DX b/"l; kiHxʂ @ HLjǮhF(Mޓˀꂫj+*Ƞ"13Yh!2l,4b\+B2hÈ$C@DGCrŊ1=X`jʞ3ǠH 6T2O+(_u:?--A2%.{/Lau@?'1p&u8_ٳ?-iPBiP5nѐeRRTK)j.'h,OS^,A*!D2S =M5CK-\r WVL(Y"+AjuZlsnUB@It\p]m]MV<'0=xFv.a:(α)*929ᇟ0, cKu{ N"1n7&9Wn9OnՙܚzDF7Ɣi0.f| _>PᲨ?ՔPK]mk[7;n2]1ioi\4dF| /B rbI/Uל^i&c7=>es;.Cv%8W d Af(Cӊg=a[6Ww|烡BXkGy#HaSG) [ժcu@^m sV /-t҉%<\0Bq{+A ǨŰr3<`5Mm~QĠ; Vd$Ύ_9j")* |Ka2&9ȳBh1rgƚ$n\":-$t)"vqe.~AJSeSe0XY }j$B^Җ$t}切5c]f#X[",$K\\sQ.(BqӜ'x \ <4D9N5Rډ^4|EWsn *4 8*ä,RVC'r陵[).pd81b5$ךǮ ^ah41my0m^$%Eܛ ǩzn .$v \\#jӪZT%2eAbLĶn˭nWrETAb*߾hCV7x(d K]X7.fダ+B] BXi r,C)իN׭| ?6pڢOJiM"RΌtD)%_ 5 ӈ)a$;޴$Zl=SIMA8-n-I^xҘ#6SjnV O0Jrְ/(]h+* XE #atOJ據]3,%rn~!H5Od/CB/WZ,RV1|x\r#.= f #ә6g1#m)`S̢=-fllI)U2ɘL11yTf غj^\(iQݒnGrq>߯N... ~*/:MeDpS|VQ>FkX@4y\H}l=!wJR|&EĄNw ۃ.T :ҋ  .2ɛkQi6ḗi>SUZ YQK@H u?Z!@VŠؓ5 2$AB%콶( PyK)B%*(+DQ)-a@e˸2,C@ȓ @D8CDh;-(( '$(@08&HGĎI'!  0Y7́A79cY E[%tďؒ)2Gb4FMIc܄Mdxk jF3jC<1 :*oG>B؏$AD;}!َ%\[;$*#X|][)H?0iwDFE{ ":y*h ja6cI2eIl@ɔN;ј< HÅ[Pn0;@G !D)H0X KG5 /阩ʪGJŮlGD쩬ʩ0! \˰DB\J߰Dp"˾˽ L+؂4DTL,oB*p! ,^I$\Ӻso‡ 止7n3rƱ#ď Cɓ(I|(nbnQ̎ع[s:@ Hѣ4}\^L6ȎѫFjZtWXb̖ Y_=m|iJP4< LÈ7\xqǂ]~l)aNʒ'?̹Ϡ=SnH0gER3װ)k vF1v([5.^Q.%\#J:%ƖЛ}*ޑZjUs׉}{w/q84!!HAl! U4mT[:~U1nG~2_'wF)Y$USFDɉO$K2pE:J1?̓w, B8F3Jc=fqe#[ƴƠq,נ2RȋbB*\aqsR4 8MtcpXVEڮf'ljMjxLg ;q~-*= [⟭|eN$4Pa^i#Ԩ& ]XT.>hXdYM(fm Jk'Nw:w*LʦJR+gK'uKf7hhlgkŽ+w]_s?8 =#"~t>"54dd|q4H`XQgA0PǦP_ݫ#%'\F=}f>v0kj56/}[jirW8,gK%DSkyҍ1fTw{Mpdo8ff/o58.[8qex]mS&r8*A ]ˬ.%^];*ta]ຠsэlk~cS7Jc q+:}MTKy2i69^y pVNBnCeeQ$RE,}:85b'NՋ͋#}o(q lx&N:;{2_ӆc{o W]uevpJ%|;s_vodSl 8;z$t6pGpgH07c]y}wygC"8H^Àrl?~qd0/4SG^mw <&.)PH8qnFwX2jo;ƀ]CSf1MGyifM)p'XCi:+h:H?~نd/~5؅wSI? AX# .PW*w؄uXO_EfUxMUht}(5x4hApg 冡}Y8yl~hhZx0o[u؇xHF `X ЈHx9PjJ@wfJ0H,uc`v4GoSBoƊȔ](upmp:dpX?[цd;38Nq9<((.h%ۨJ`5:ȎLZ2|5ee]ez*HZ60x~7M7N7ƃ`ȑո7'%LwA"_ g鄏'mpS؏pFyuhx4[uFh7XAvxؕTRbوeY5keQar:)0 7~/C'XuyĘL)>3H'45:lX\ bHw@Nee!5kS#+~tYGؗyt3Ii蠜Ɯ dqMI~yؙ9pe7PjYК[&sxGc_sCY6u6)L~B<lp}H)F֜L >x0S9귝)ّ ۨ{(*Ye8 RJfHEi5J" >Fg?V5VCyLuVMd7Bs ǥ)b:d: Y7H()eKu ^LGʂIghsB1ا::}>:ݗc@hę9DjU?i#N~yȀ ʙbhxkj0 @l :V/(hh鰇īxR%8l0o(I¹HM@Ԡ6U:hd 9pɪXceu*HBbH:VCux}j >ۯF7H;Yk [Av8J:r46T{ ^ʱaYeY?u7Jl6`Z;˳HЫ'^;_&uwմ>P4G kSWںw^di%S{T{f 4 /$5 5'~ՊI:psp)8 ʸ丕h; qrXxѡ!;%$I[е; [h/'/x'˻;xܥx)< +jc*GǽX0#;;kb eT q*ՠ٠y6Fy>ꧼ 6*e}G[y 7)dQk+4sUIu\ @iH>$eTк~B9>6ƸQ; <[5[}ZU1[G;_Q/JSLX\d0 c̹bjg@j5Ɣk ?J^;G̰<>G[3C {F3 \L˗lVlLƴQSL{蜟L \{ʩpƥTr*s Pw9|UNltO>ʼѻ,~_e׌U ✭`9 H?ЍN{j;ǵ dʌ B*>7{ +ĸJ =o#G M ,M<3[?BQ=sD ּz%DӸҞ[ayk, Ǽ7˵/jђK}V|`ǯMM3 [];?Z=S;;>g ҙ\Q,VڷYYj$}p -``HH8.{춲sĐ՟e%ɺʯRByI=$GW ́Rڡ]ڡ ޹ ;Y <0ۼ dIug񋵧MZ`Ϣ>Cj'N'V~jhQ(.HTtmceL M}7a]  @.hkN=ύ PLgӷL{F?͟HVQǔ:\1)ޤ * ||.V: 9իY A~ ` @,HXKHx{q{[c0 8 ^Ɂ,+z'c.k'5- ~tl}?ݷX[  ۮ% 07P7(Pbn`'Mnܝ b` { ݬbw;h~|nZS.7×Ȅ^>^Վʜ@P ‰~(PMWnq V;Y€7~{Kf] `Sx [bobb!#?%_<>L y$Z0.9;ҷN>{C_ % U34رs #0Gm?} gZo[^toq/sb]<EC!6`yn?{O  ~Sͤ'vs*0/ p hЊ $XР0.\CzUW5nxѣNJy$I]C&4xX``* ,X ((PذaQBP05UY#h჊?(Bʕ*UXӬ[&%s,YAy_|Egpas>cȑ\yeÙ+Yq6ټ}עf ص@2]0!R?$Ҥȑ +,Hy̘4YM!Cgϝ?KQ(J40uR_}uKB+[Өӭ[ˠ t3̑lB* l3= -tIkJc d# mƉ,88KJ ؜#`:j;(R)BLl +rP¾گY P@ l0AU\pN43 tB,\=;3EGoR4:U gRf1$XpXz$ cl&<*-H2Lb9(!ʢ"N"fOH#uN0B=O@}gP;{:LT 1GRzK5ݴO=-QcEbnpQ$U(YguJ*s~ͲK/*L;(U3Nri.jv%̳w\ 7gtyWkShJ7 >xZlQ%IYl(/TX7~d2T(hv8MbRZR[t.;3 u\Ȇr=:b(b0fԔQ.3j`kiV>I~5(> 4,8ŻYh$Y}F[IKL\'r|}7㏳pTB=W= %^ls]lmvrװ wh7,AW`PLЀ S2 (k,E ¹q[\fĔB{ lxG5 $_I'Nx ;ݑw > <` c-Yw3YIX F6JVE1raWCqCsԌHHs!5Xk]Fd`N(E d!؀]\h,M z[%GT=8Q£}IGR$&1㧠c$KdErE&1/z# <`$M zA;Np10eJJUVa v@fϓ4Rm AK922YC_O2F5X[዆T+$Ϥ\jjVs@z$gѵm0eCA lj4JyA9vN md';y1 ȈOrlN꾄\*e G)kcq4Dke+[=jS#f@Rԑj^*d)PZU|XIVUTNЂuQLsP`Tg?۟FLueA9SZڈ/2*dc>C;ʢà`4ˬJ 4+ +w[(*)ucX0-?|:Upt=ha,;#' h6)@ d;>C!*S1 SL {KPDEfA0Dk,@aFdC$DD:=i3'dGQiHF pBcŹCX;A9>OEL*^9 $P S NCFIِrFEl0i_-uAkFPDK2H8p;`X YGvDDx*,;> H>2ܩlK90H5C0 ]60I {!$FԽd`TfI $KPC A쯕4O *3Kߓ˟bң&lG,?H,JǩǬŮ>|M"*AiȲdK FXJ;u1s,1hBPc2T:Ƒ-Kȃ,xLȀxo8OL<$a?2kJGUDM t?|Z4ۄ724rJIgTfXC`)xK锆O{yk ɑ\A,H<`IO4v=xaR@$2 }{.oXOlMƛR*3t%.b'2*5iPғMfI4H9H }1@N4 fh弃,p ;Q\ѣ!͗Y$ 19'(%C*RTEU>${øٕR1bɬh& `ih6 4p7 2()V8cx?`2=m(h_PN9xBMȀLD _ԅKөT!5RT*$%ckQeRT4$UmUc7 hȡC`LP'hd 8{4fC7)@#@&ȂrtVwc zh:F@2pZr- Tu$pW);yTz1QW}]AuM,mM .MuAUZ%+]>ٯ?RJp>%pt}ʸAA_XA mL̀ ʴYJ@ RMM_!˔wפ=ڡ܉z3UMUDK,QM4 ۱zr'-D=jIXJu.$0Kc ěծ[Kh Uo5ܜYd\_rܟ ͥ S T'B=R[TCJPd`zm[ݵ,dU.~ + ʶ*կ H/U]'`E[FfpRpS#؁F;^Y(! qI[@1*hU%)[ &e:"̻ܦ̋@ j)*_Wb>2_ӽ;keTuC̘ ;e(7 cݱHc<jfTuRp(%m&xj`3 OfPfRH,(JQ0'#saTShXk gQxOֵ"\ILU凾bۀXI,X_Z9UvUwRȌ*/)֒ jf΂:ivfiPX9E x^أj?^DΈl(f4 g0`(ZYj|&V+Bkjax嗪_;V e;ӭk|%4 w*8X/^HضeWFӀN$pl'(YkOP0ڢ-VxA) DXti *ȹa. S텸mmP+4i'e;\n*}hofw0 &o h6Z2 @'hr`19;1H)2 I\(& IpĐ%dF~8%N`qh>U8fc< @.%0ҳLv r VrƳ+^1א4 4- e`GNWPFeX :C4%bgWhs(tBJ'`ʀnFUBH ORfP>anr)(9 * Teo H/W/Q[4_F={^s^D`׵M C^sggT]qS,ƺ%U{n0hFcra&ЙK/&)xz./$pπp*^XS )3ه}!m` b/ve1\xy/ؐ$wNس{RgVyHy FcP`,6"zUCzԧWzg.=np0\]7G}r!E퇰{>h`W/|҅ڏSt~|z]%'LCLgC.x১H*`}wV`8/N=;.ܹjΝ{!Ds'Rh"ƌ6"GfR#Ek*d)K]2u*VL͜9_TSYB*j4ׯ_:Ek&ΥnժZՖI>xk>([ڴjaU,>@K,s9Dxp@_ *TmqȑS2 FpQG'P)M% 28kk zAdKڳg̩d7ƒs $MԸK2y\3Yΐ*.Q:9m TWn% Vn糭|s?Л^4`gX )ȁ4eU6c]VfhPgiT(D)k Mء Ahg E w#1r=sAULYg4`'J>QSyѐgNzXiՁbuV~kpCjյߘaUeR8e6a`aJ8EE1 '`Cf\2 12L#H>dF*Y5 ӑ>6dL@QJ*LN-e{F]f,lzכq8  zi Ap 0`D6z( `@ :)4<*rF*Gꤜt3يkQ/%U]&a D@ Ъe22 ,Ys9Wpp-p {2Rvnr05)WT P.1v4 1"Ymg -dOI]r*0PVY V.EB3\ 8sY<|A@ipb,DjK khdPuX3Ea {N'=Qn+_ܦ]-aHT1t.:xl(&VE5U9__p`p@xphhuP:.*PhnG*TEqb17*-y$AUâ٤(SZ Iדl{ b`Y%+&}Z#/~?lSNG}id@]wkx]- XA mv(^rh74"JeBh0~|[ wh$Y0Q`@"R+fC \þL7V^60:%NS? 7 !s2 Ԧb*QXh-XЂQc0G>pDXœ#I b4ǨgJ2i$ѸFSb+/8Dr,gTU 0hhiQW7O|~4:A!&xj)P#P(PPr`Am걛¹qrn / t"%)1K.OiɟȁDb#t +:%>{S3tzl%ˆj-QyqLMH*쐬*1*[ԬɊnӔ$ŢҨp?w]֠* LdYrFWhZF! ZBЦM9y#r;K-rLL,Iճ l_2VKOEpq,VE tLE9k8ՂЃh픪bYhmz'W05M{=$){m~cdȿeq_ken.zrkl9,gY6.eeqRם _$x7>PY{_ I+Lt[VMl99%XXDK̸:u :v2W 4,j awmC6ē4[:Ņ^LAƒNrc"ɵiM/16Ѫ|sS@1_tBRs YBm8 3$8'nHizCqiOS{T sm҄Aya*?27@_c907h2Э2J,j@D0 NgP1LձnlSPîqzO{eI~Ia=@0,Ƽf`uqi"̂)9|E7u5<  DMߑ1~\_%UY+Vƽ hg$AX^i4BB0 589O0Q[H;ȏW*`aޓ ܥź!D$ƙ0JF lhd @čSJi՘` ?uR ^dn9 ۘt"#[`MTMa&fd p4)ֱ"bL C|Lv (Z?`$a3:cYL#a5``\7' n(Bc9*Gb ГRu|!aݣ/{c[@Q %3R,fD|у`D@gl`0FFA!CHDB#*):JITrW$d RL >bmYi  %NRn 2O:"P3..qK)Q`hR%A-x%#XnePXCYvr%jj1.ޠ[$0f%^%A $%0c`fSODIbr |FdzDU]5Pfz"!tmEDff!j=yƐY!lb>f\m@]%A ?d`gFaY sMA_TD^gXӝ؉%8J4L4l%wxNyy.90I(fĤ| <^lgmlo 2H!]ƑR\M^aI(,' DVN 1( z$Ag)VĎҨZ[䨎ƊY:$NܝδRLM`.5, 5&F%U@\W(~ᇶ-x*)"i7"* 3#П8+ QA§:2ZT:_{h AoI*4fƺ ,9dDc ~Hv#Z+|$YdLf#]VZʲ>N UDJ-^+e{E\`^ Avh f`+8daU^IɫڪtKe$$AȀ 4Az6DxM;b1u$U.!ܑXi+a\QIؔl|Zlf]βK^aQY*u*UAA84g@NeuZ,(ؚضMic۞m`v'mQmޭ-BFR\N}:i~0i n#.+\Pn)uE zpFv!L@r5X pFnT G.쾨̮֮ }EHX- oZa*Hz˖˄Y XRet/) M QUBf:$ `m2mml$6)يBY:p_l/mF5A2v#1R$S4tF\LYsz0iP*AATpg^hVDkPP,7Gq ֌2u *ieL/^Ί!ߖj1 ل: D'h ưp @&AU'eni!S 12&qx|l2191{'Xf nᶬYpg)3k\orypmYRBIU!Kg'>LAA ' @gl*<"pXKT )LM-?EZ0psy6@)* bs^$aҒenyp?IјE}Qs"^TfkH_\S8IH)# `_ 6cu!M.c;amo Աipzvh'ufJy6Y9x}->m3mS njںu%s +=jH.F%27F!ru_7YLa>3axdOHip|z 4ՊUg|G7DXE_ۗqtx bJ'W (^w PX ;;LP{8P4wKt7(guHfƈ|6("BerJ% U]DjUI@{\k^s6af 50H+ v9aq0 ]@Ѱbt@TRI }Sf[ujI PYSlmCrvBR(ꢲkK3Z5Or2Ɂaxc3ߌt+25)˽:H$68񟟀7ārXq{ӶS,[WWTh\`yZEM{rAX6R:q;&ȃ+L9LEQSFO^y;< i?x\Nj<8>9 q%]MKZs^|Pmq2;2JΗ{[L>[c&R߆!h@;u×x ,AfTEUIx*n]= /4WX 804H'I?#v;(^OQGoA:GdH .qx 5S-Fl6^l}y0Š4X` ;&<x %T$ _% Kt29p' hPCZK4a`r cZoT!%U`| Akٶu˶B#dĉ(QrJ%I|aÅ U͚0!4ɓ'fsf͔%k,ZhHC#}5B^}slZ[m!3 DB5rDiRdr?tK5LU'ϣEw3 >AԫWsTQױ!ڴm!}ˀFp~k/%( 1dZ #f6h,Lq<3'EQhd+M2H{2|ph7zc&(p:cP bkR 8', JZo=B+OL4Џ˳?k@/h dZnI5P0S1I44EYlm5 M4sFp|Hzȸi9H2ˠ vZʧ+u *KtK0:%@bةLP(j*NK ;: H-FRF_SRz/M9-@G`KUՓmV#3:*(Zmxrv8KtKa`r8)ٰ٩v܌*@:vAK qj@;3(\b 5U$1P:5ZjI%_n_:TZo %| R80rn,{)]XLSj>~ز"@|Jֺ/Ž"tFQK13lqͤJǙըFc]^篭J`JMeWCmR 0yx'zÒJUCm{ ~w}+t$< cC(.ehm'ZR"U?blaI.4 fyd%#A:a#*yσ[#lHb g&,Z]C]ftP$QLt ^mq28]s;T'Oقg FhYsƂ u^A%/4UhTWI@Cm`kc`LW@L##NzWjmKYWDIxXJ>zWQ&T0&$}@TQf( #j;3Y^+@ Rp|sRE*H5%2eW{:ꋡm(}%Lu:whG0qU,2+1{5^2fa2c8/1}*C񹳾HU g ɀ5S<`aJS投j K#ZClډЍ=1bXx][Rb\'eڡ)%>fI+琳~ζ@\ ը²dthS{@AQM^ n.d97R2HoQԘ0)6x^זWЦ@IܸkXxp,JU5,`6ɉY\NYіRc}H׃q3Z:6WČkN)˵F-\;`UITD>b2&. s @;)z:H3J'&YTsgX5]F-,J-{# @0Q8OQ<%9'E9Ñ4 :jR"pe: ;Һs<raR݊>fILa ??32@m6S@ lA (6Z#<HB(CoC .S:0qADS'GSEۧE]$`E@r@@3H#~#MP8m,HJ'P-H8BAiL2M^PMMJ_:yB;+P=v2P2. =% yCo/H,:PtY[pe p,efbJfQ@ ` |7%Vg%SMJg$`7~8U'+P3OO}*n<˳ vjcӓɚ& 5! dS!Yl|I'2]E), J9AEZM,2FDRRd%`rjmDasDyqK?d>b>+eH.+)r$4E?Hv ݀e_IBIO6-,eR ƠjN /@sAiV^i9MI "6DVq >l-clcU m&&.(v lO&odmH+Sddv`+p EPA4$W|+^|36t&s-sS\%u _NuH. ( Fvql1v(mPe=KvMR&!zvfJxh|!A .6%ԯ}!~1Wj{/WN sm8',v/UbJZSPbRo٢xg_b^. ?v XxXWX"XYō?b{K.1bu-}*QWi9Ç%C~x_u/'@Vc+CU@ٜG͔ywZ coBxFnEGXegz0c&P(퍡ŗGR TM12Xr s)^1ꔶ(#JswaU-Y1:YLJPٝop҆Q%5xHW`:Lf"5YwfTblqqVzI7Aie՘TS5ҷK/QB8G XYZFOwt,Z @pڰi-v<$:=jg>dڛؽPвICy<Ȅ3@Z-2#^QUKz(у@I 3B}=K_h5%jL8W"ϧd7U&36[g F*qC3`@Q-$040} A//sZ+%߫Sɼv~$lWy=1؂١ōZݲԂT<+;l׻{qeaHzW^=A{nBN1,P^{Ֆ;4iUQ?:%,۹Ģ}p#>@ p'a1>WUIIx ngY[3P; jB` `E)M:=̮欰&k}CsNI^Grb@ J,"…* `qĉQhƍ z$ȃI kHBF'v< &Ys<{6Nl)-i+Dv zڨQ'ZŊAv-VXq5d=l /"7ر{}}zC URHHRpDb6&NѠCkZ?Prٴ9#)UXE˗2d8Ѥ-iyZ5U4iR1lQ)TbUюM6,ٲۛu uu7/߾&\kdoJtCD%tPB. WB aFfy6i%6jqpZgeU0^d"j+6c}A5HbJ8\L2p <$ Nd9P6R4S^bAҼHD: =.,#Y DsP2B0;lD!|ɖHc|)LB4gbAE# cd|6eQHBG 6BZGE>` #ڗ#!|F.Mj@@esTHJIR64f8|v2P=AQoq0t Dp#<ɬ%Qk2!]02ֽb#-c.`wF5N\W4-yH:ʍ^  Z#65dYӐ2HԹN&G:F">;V83 N@8U4(hDz '" hSgthW8 H{*HjR6M,4T3\ÀHv xx4p u.:gP]Ұs!z.n tXiǐ;a (^h:E 嫷P ][f,+VR6D%^L+D(ySE87rj Tb!jMO2)PA 9['rX"U/};q 3,c,A "ף%0w!N7i'2xª̓-@~cC~'#,GFBu1:adHLAob`ӫ^Vg`W* b(b􂲕9QtTC;1KRcR/nE @2/g3t@.[Y l F4x#.%5wj`y9ue B=ZȶQH zj WZPD]Ĩ/lbabUqiL&UaCp+Lb@bp Vr<Nj$+Gۀ|"blYe0'(1T]2z*j/UlHCRX]YN}(s&Yw>OBD>r*E,w.>"6# aL#?Q}Lp7)'od05T.nS%vSVTd )602}v'8Ff}0x=U7rmurm&` QA?zfmyb% sm'2  gZ"DGGo\Wq'9UwGii|F<0A=Ae%<֧1H*i75`xSZ0'`vpMAQpeQtE&?ByEHA']I @ 0K'w#Y׀w @gjbGei Bp5Hb7tH'(h}aS) {' >`$7m&# ,;s Csm6;sm>. p0R{Xg( Ȁ .% |ON/B# #wa0f#Uw7!= (^@茮D)$G XTQ[ԍRQ-n}h8thUr"Xh(>V^ NN'1Dvv%b] O6Y=`#qh(*s9sCvM $ɗ|i$'fV30 \445ɘ6&8y2Uɤ'4b_oQ Ȕօє70bGv#7Е9ߔvYZB(,=q,@I}zɜ{ɗ,8>8T )EWȕj L9)R#œvHx" YX.T<;bZXi #y}YWלy x~)i+IEv0K`s$ -msiIRٓ渞LIIv7VxuXhviJ*<#10c/3bà*Ù(*/w)z}#mU@uus+U0:, R~u8k&VwuCE(d F&S(/egZ5]Ӑ&6Y:?bYsB"gjʂ5ЦY`U 3 xAsfrW*FmAdt4 SvkV{X*dv$%A#r)ʥ)O "Q==1U*;*8g}){5 [3fW cU x!rJجEÞ5tS݊ߪpiMJ'*٩#=ӕtH*3`Mfi Xl8YPD+KmKꬕIA 3h(*;5Y^A'v%H9*~+/ @۳C)x)iH[Ɍ9c9HzZ4q:L; o4ѺEt3)p+i^rx 5(C7+!M A:= ok ɴ{x'GW`[$JQU{[;nQn⬫K@IT= !oDzyi'j3E/ [gM>%% f "8 "7eH[͹m۫:qWy+ RZ4jDM(EC'ju/utJ«i94?W,lҫI̹9 R'J{mtk 9p{q>kwD(#\ҥ%n q##_ܜp}"PId W@c@?km C:IxRqLE|$Ac} dHjGԑ CoɪR7ط8ɚSN [(LK0 ]e X}̒L >ٍBuH/ٳIY=Yjzަn*}U.๧m-bN h t|KK .v;KUq)p>{,!1} /O (ujGGy*.6~㱝<^>rE)u A ԤFP)LM8]nQ_*zG-*N:rqQP1:zd~--f?wG(u͚{-QpjAĢ2^=*1V,g.(ы".G:Y^F Aq[ y>ߺ.nm] *] PtgNu}ͲPYke󛓬sgЎWL~C )n].Aq U GL-ctksj ;W 5>{햭B#X W.0O,Gۄ E6Sb`HoIqbq8DZE2-BZo݊ZѝP;':=w٨1< \` 4 $7a"@7NwӓRBn%6SM@)F][%&/ANE^DA˹1=lb母Blb'8kĘ*T VB"ho!ؓ1dYsЀ0Ajmfze5E tU!0vl?@Bڕ)Pn@l c|װ5mX,2 7' ]C@8 󞏃-P! I >6]گɺ3RHp{I k'\BcBDtB0q.ܔе6+ D4LF*PSfkhJ)Lndkڕ w*/5 ktޚ;;۵lbpʲA}tmUku- Α<#Ȗj S>ړb.j=:bg ngrbjkϫ4M0 @b'Ohx:G "C3!iKdrIߧn3ͱެ948ϗ9[y6}tҭV42M'-X[5)4v|m/ Mv#kɗO>ic; 쀌ݛ4\x^o~?/θ觿3Oy,FBnWvٍ ]-i/:Y vAp y(1dc™>WE1Jǧ$xEу^0`"2d{0|~Fj|*_q>0~@Y/͇H?TcЀHAEYO(4qcBc "cvvJWK/A b1zts4"Hr"C|IT_5"8 10JW:Jtŕ Wt](tы`:fTVc( <☹-»=Å!H԰/r\5țB '=9I Z*}G'99NǁE|,)ҊUb X@-T f%bY{Ζ1&i殄"NN=h.Q @>SSєC+URIw3G3g;hI:.[LG03p")FSJWv %T¾qimCWwh6rZ ^E..Gp!"S>pb*Si5tgB<ǥƳWtdxr]t ="zm!R[v߄[aAn6r\n%(vH-O TLM+k\c1v:.-OƦwp.[6/ C`YqJ6R.\H#>p]j`0srٰ\ cb.+3^DgY9Hl?7IUnVF$a mic%"Yi+c&2 uwxn\sqI=jn` jE"\4@@ ^*a\4߫l1{ˏ^1-GSyTȱMrJn!r)]5Nnt{\+kx+w [n'`/|@Ý@?vpC~q S2]rKYȢ\@ ܅g>rr>h#[>4>G☁ zǏE7tߚp:ԣ4K[B"Z1xW].~YQwpNXcޛt5.pӭnƣ| vA?IF{_:BG6g] J@Zv0P`GMM:d9S'E}iW>@|g|ȗ|n2rC&>vMt81em8w s`IXX]vLu HyFGz0 9@\gf@ hܤX} >(5>Mcm.s_R6`!xnvgWs_8x'8nS~CnIʰN9׃KupGFI9OOZ@`l=!F}ehY8]xji؀hgv>d|LJ|fH~1`%e}r?`gfаXɠp{t HY8p٨Wp w?Ohl]K>{h>HI#D9we؏VWCjF&9\;g~2Xm~hXqvQ 9 #IpV (Fx W. YƆc+TTncs ɑgat)M}m89p燕8Ũ4eiYǖn sI/yg;+taߴCI~h"7S@R(;ن{KəW)4~iՌ yl (@ syvIP(UTe)? i }59D z3{I|wRZfe&_Ity6b`頩 О zII;Zr9-OJ 3 @Hisv+8ȜJix^X :alDžDN#$ڇ&j_lx+d)Lt$ym У;ʍ~z@J<Cڛfq @ F Xbx>T +~A)wWsfsl4@H ]9)fȘrzL2z uJ6:8}d5p c:4] fCzz 6 [D15>ʪNҊ҈tZ57P5M V (]HtvzؚmcHibuERMaZf&N窪n/꜕Y*cSLwx~:0v^Me앰6I XgwΩH$K8Uf؋D7Qi%Ib*ʲA7HZ i`)Tw*m:;? wJNUchb[ ׀ /ZiXVW\ۤcf:;VQE"Img&b)r3m*uq+s˯uzx\Ovp /8LCޠ˸"?T6Y/WgXؘۡ\3-+k8xbk7zbCYBث jWGTC0IGX˘.8N {|Dp84׋V6M˂$DYyա3XՋh[Lv2aDȑPMVĶǾ&Djq9zz@O5v DdWD\ iM 3$8I~8\ile`GѓG~.],usfz @EhhǴ[,y|fc{eAGmܑĿZKpW\ z%^Pݎ> X̌bI l$]ҷ@ i7ٮ+fsA,z:v5{5<  - ׆|ˢхٱ{7Y',^)vLr/]cdcLl x˵P 3 ?a7P؅(0zh pWlH `V@`RU [9)w[jdIV̲1 €ʺ$QkFL& }ׁMz @ؖ :؍vzH0cI =[٠- d8~V,GڭUN<}؀ dȏ\{~N"w bKrmmVf Q]ҥ\H'=MӀ-܆70ԭ׍]g-Wޙ `m,+=Wߊ{܆[>j ~uV`0l bhܶ$h*&ӎIpWPن s'rXߥ=hEգxY ‼Q>~+m`li|0nedlUo3-礗P(p煭0(sف3~5^mq ㏾&CmS͙*hXZ]>H봙8+U:~袏6r巾~Ve6IFrNe%{ uȾp9/*=?m,N[ ˼l޹鏴R-.bzrUM jE`L[|r^ܞO?Ǟʮ:njs ܸ+u2xh{ޔ.heDCm EJ?SN(LU/W`ԍܜgU1j 9] jmO':+pLzW ޙ{D%ι7m|vybە!뤏p޺W \_wm| c,N5M/ (-`Q B`PAC $&xР#h#9(B+[ii-^Ŕ͜M6yO7w&Ӝ9tI.MSQJeZQYf׵WaB+Vlٜ7%;kZzҥ Wy//rKać!n`ȀoMUٲ-V|s>`@U#,0D$*H@bƌud1(W ӈ˘qӢjΟ@ *ӭm]K'UyԷ-6goҤ[^̫p2+h"2*E8̳F+RmrdͶ0ȢR$rP$n%^2kJ˚sx:;;$K<&4ԓRJpj>]K /J 2|0dBH+- <6HD&JDB UD) *db1jtɹk ڪH#NIt4u<(y2kJhlǯLSa /0uQLduuVtU sNpjVC=MSaA 5P>Pa(U)N^Rc.:=*B;%n_~;<(*_9 Kf:[sM00^[MR[tp8DB;1|6Ñ4Ojڴ(3n"(•q ;sbz>R%}S#:}Rݔ:X`jE ̂i=b5O:FBYK6iSv喵S䠄u^嬋-h(=;驠&Oj:)z,~a_)L$.k\UۮeYjy;nsB7?I)u[Zÿ{E3@aPʀ[r7[ЂX!Ux 2(TyB8ĕ(l WA oCQ'D Ni@ K&*(a59`t\""!D/u Y* >@5~Pp06 Ip O2Hd#{w(_cp>$X,ga}HB14N.ɼt^GTd\iJ\ȕienl˖T^T!1+a)Icf39HӤfvRuM =ݴ7N$۔slR*⶙Q'O|法|md6rf r'(RcT92?(J4?@{wPcjOXf 7cv4>@zQ- B2IILe[[ ~U;g.EZ-ebDC^יj'VD I\A8&[Z.(ņ\8}!q*\<gWG[!)M.K0!(9Ih7W`uU D9]|P9rjHCദ89Pcu#[Yeu&7ͥŝ9s#EȟL>'u7#@Hn\r`΂Ͱ4z\}J9"-Fa51 UpEqK85YI~ږi[pr'ͻk!*Ʈ JNjIYrp+21WݴC`9XISz6m BhE D ^&rMӪ |{Ջ:3lsҨ@븤3*†0hjA퍃 ɣgG:. [=:`.)V'a3ADSdtbެӟJNlVxki3yg񱋷a*F`*T8lh#*yPݛq KXbPEϽ=O4Pa1a NXfpg+_tmoWGg!+]e8" _Zz˪ʈ*(!F1=Z6+9p-!>I A9;U8M4K{ u`i`VD8<;'; ?KkɣSs<⢼Pd!۵S=RK*3؍!ٽp|uP_XB1Ȃ&O$t#CAu_hKA ( YhAKH?+m2& %&tB)ª«K:Y9+( C1l>pdGFZ[fK22x1H[AJm{a-CDDSH>-GDbx<dLDM q?uI# )&)߁BU?Wܵłŏ ?\[:+!Cc@ܛH yѡsh`u =3D?brs ;SD3hDGr!bo8o :xG{GnR*{G#&j~?s.yuhTX=7r-@p.$HAV8d̡p?*i8VH2@8ZHGu,9ͦ|Ǩt?3"\5\Eq.Mѵ%QZ=y\pĜ1pLtOCADLRC i^Rx>'1@܉4L_HB$.'\'YJ$\IY^JKAr\cй\͝.nϕł ]m,x]%H٥ݩ=]]',P<|e>U˻DLX<0$@NjRaHfNPxDNDJlDj\J0a-6"m_}__x\dqU_dY 5V6:/i2f$ ]* $11@,H;یd(2;Ui*FR6qY0\qAwtSaV^P ]Y-9e,__(8IbڵLef拀fTZDu!:mnݒ,.RX$UXxNB9 p O) h@!jJ:^\8S (c4ĀXf]_ܑ6԰ }]i> ؀bZו1`ܨ=(No)i.$U[̀;x̵ըCD("@ǻAPY$h[ JduMe b^E&kx63 `t8Be5 haeڧQ~N"Nh.2p$hdnm{Ӕ.^3q6CqC(5~q_b]f@C(*!F#G``lB~jȤlYO'/DF2*#:5e)iki9b;'T`<˳K:4vqCqddž<@To%MGO7vgH ?g?J2K>oU >s1u&2]_o phbݹ cg*,KvX,4j6,s[,CoȮ4 + %uuWwpgA$#R!Z_Gk8uZIU6Ya81bY', {$?i?sd :i[p+t yH#Qb(9({_-R1oz R3Z7_b-bz %{mSuI` \ KeWb  e{`:ȁȁKj|8(R$ЁLy |Gg|&Hwъz(rop'\khѮTfPB] 2\K!ˆƌ1##.[&$ʓ 4@ ,PЬiӦ 7as&B`4Bl괩T" 9Tp!Bb-kl#F$ +rDb^3l ΥYϠAfbĆ'n1ȉ5gbÄ glȞ .fqbFa $]Р gC Ӫ71$M>])T&r7ȩKȉ5LroZ@i Q_NJm=l&y+UE_N* Feu& :x8dghviAn@VчYka"o p•JK1AWshzI]wMw}y\i@{Y^YqB[A$\QEWN,>3a:x'A(bV؅f5xhjX50cF3H?H$L?$sPK69IPJyYmUXaa@&%dɧ '[XaDUI̜srfg6e4hC&¦if؆vu1N)wDV 㪬*v:J=VZ`X Xv6^,hŶm~z-ϑf g VD 04״vMQ2Go\cTTX ;MԥJ2yqwO U[9q ^M!LV,&i6xUas0̟ |9+4hfу[ -Ҽ mX1umFYk&W ~;J UoGRsz鴡/C֒:zidBZz㑰`kMxDϕ1y2@xFoY"Y&,yI-B׸*h! DĘ9o1lk5#2 bd{ F :l \))` Tl@ Tp#Y@/9Sf'NͪQƇ D)qMEš0Z2h0[L KID%吱j3U*6"oI QhSIG*W-o@8[z't1S@gQEoʿykT'7j|D@*>O1e.ob t]"#+i}m4 Ip\Zk+K |%ncÿ>(La좺d'QXݘq,ojH¬R6AТQF!m/zZ hDIئu*l\H&4#8[p̸6c ,\usK E,v^QrW+L, ^^BۤF`@ZQ[Pv@ $` 'r#t@ V51:LꏀR$<bu$IRe,<-%߄b#[oW>Nq9iMˈsM_`̦e15`n,6c9߄(pm2RpbFiU} `cz(Վ( 1,QiS㴲s _T,Hq_V#7iȝjU^l`]/opΛ ݣPefO-ёl3lէM` T$D+ƈRO$a$]Cnsog9F lF*+FaSMxx]a)tDV7a G%]", xn75u DڅWlʭB9^mYMl@ @,r-Jᑅl)^[íԊ*x"AéEĽօfd@ߩm0V*x9E, TKENIGLLud^̓DY Ue bT@V,n1`pĠ- !؁  F8F MdeD m/LU6ŭr֬QrŤ!Tl dPߑ ZU@Hn-\ -S41!PEb@ $uX @dRA& 1=@yx̡E Fv ]!LB"$B!jAԀ \"x£_bd98L!M#A_YFDOb ;%N RPܫ9eBA@U^{ZŦWv-0p*$Y"[Z5{ FnȁPԖ`ʀ @J9Pcpcj+v+$@ x6䕆lHgO Vbibk'\%+ڑFa<b)ڂ>%Q.lL쑈BV^zloj'uY.ZN 8@wv_lSIRf@C -bm|*2M֫8ün+$Vbfhm>D%ĀD_JL.Y],ƖnlmQfa\YlEޖrk*@, _HaDW gc`9ȫ'f.и0DQ<UƮSn.J^E؀V | k-0YYA Y|nF:"*L+Z8ky}ncL".쩎lkh2D(fJ130*0(BCWV\AVRl1U$hu,o|Ua KP k/$* V n INJflj-qnf0q8qٯPM]'pSmDJekV1&. ]ܾHuTpEp}0| n @ *t#?2ckg&a9#1'wopH !mf`>VE ,7 1P$(ٍR?7oʦ,͹&D(Η@y܇~HSy<5@6'52LTZ饝9k&vOqPq/3wcĺ+,%/ sf|hP 4BWݑtDG,*f2m)4It ͌4ct4tMw&ۤFxPJ(obiy 5Q.Kh\ʮZj5TW J2/.L@@1"W#X5~ PA] ! 7{u^+Lu_sԙHjN P_!*l3LH 3/Csfsah[iG4vEEYɸ ˮ0~DAPn2GoLp{NR.qΆMs@0pt'xYwcv+ w{!1Mif2۪wFW 3j;0k[mm_ dSO[f] Z.3`K8N`a8b.cwJ׊8O)A teNuz6h/ހ$s N3b/ǀy P@NG)u|d|M}]N wT)(2c9J9r噏x*ADJlj@p.tF8MV6vp}cpLmSAc6G"47nƝFjfTsk aDfRutr8 lƢnz T:UU6#nŰE8 GrTA,R~Ѻ~%f'μf9FhF!ebjZv#S}/l,Jib5k$-4&lPa4BTX E6ldb~2I2$1* 2-[ gN*( /@!hR@.jԧVxB*T԰aCŃymڴepA"h {K(K<qs7 #($(T8_% "?8@K`2R$6p ;崅t ?71P AK&Q7KjcȸdFz1騳'VdAʁ0+-+c+˷./*(e DLM 3NrȬ ǰNf35A7h EHx TDF[x}J+ 2Ehu9G}:QV%v:@j *p*lײ}M.z6LcJ/DsM6Զ 8l|p D89uQD5x$tk\Ӻbla0KsŁ VS[w H2`c` 2H!+uOeЀeajߗesd"bBǘ2zbEC\N d V5zamwʚoA}"dlEBgm"L(Tf@Q [D{ʢ%ՅE:bTe fZMt Ş@2HA3PE0Ah0Ha(cj4R`Džkb H5JX&0G^vUfDd#iehK$'+Ar">YE $1hbHJTtVJ3\w.qv4i3HRL튍EbRu%$$Ӭ5gt xyqZe=˧R:GLxzXϵs>k>]\ 'r@Ϊ^@beVZISQq%GG$Reo|cn@y$x%D%@-pMnzכY*@2<c h`@,EUrTj: `ӗ hu Vo}eQWy눰 c\7Wl^c؃T#mgL~pFsrBLۤnud^) <EҴka"ڜ`CPfOd"gsI(zLcl(]WDt?JkTmٻ fȉt^套FetNnRv/J '*5-V~`XLp>"[~:xŸE p%_@\\Ȁm]#@s|yG.clD IG$!s޼2XA l-HH&*А=*x8|8iO%(r_ 5M">eOs1q(͕v'a@|z勫[?C64@hEєԢT*9o'8.%b,;ݶCu rQF2E 3tսn??5xH|lVwV1kzoq>[)C b" x1 V?'Ai6ч Aݙ[ۚ~wұ_{ -h1Z嶈9s:5{x**-XQjOߎiY?k ʓV9s~ݵV NȂ5xƾvFxOUa2>9g{cpNK+F`|41>.ȯ80BV;̏T)g@^)iPݎǖPNN -xcdFoТbX@?(~qK*DzBþZ1q0 nF1J*fdI,)u匚w 5d!v/j @ j,>4 V̎@?jq{*o[|#.6o~p }ˠQz-:'~pplA( "fUb0љf&A1Ey4^/@ 䈤Yj 0O9s1;Iʏf20@r]Eô '\ m^B&6*qzI6̑DF$&nѾ l'0&pI`b?IR)2\ $$O28.\%%81&hڰ qm'k'{'oL1&x`-Vor.2uݠLB*+lP+ꐸ;bed!bo,{nn`r `*('q.-G AA.̀$鬘DZ]m1{$2e2i 3^slK)Hp)Ns *k( i.^Vaonopעc24X%V21\ /!:EQAd;%"p6ˍ%`Z$>@>]2B1АYSӨPX(CB2t?h7kfڏ/T,L*2C-@>cDE!-P%N$SCIFGBBQPHOHYa|H-9vT0ԶJ'RJoȧVg @c`ccn8H"sLx87UJ9 dr f!MOW4<@ bI26h#QhQUJGqQ!UR#Hg,Ss6Ef@Ls?kt0NUJPIvU"L*: 3yv?#bX8[ؤR jIY5VTѕٜM![w'C5hjGxG#5S&]3q@+l]<54Gu^gu*2<,'F(4`_]fafWA1'hbiPb+f a[qcd˱d5S=eeaRe3^U)'4w~pCkh`B2*K ,#i 6ƒY㸢 m .*ᎾΤk΁:c=v1d 0Ǣ)\Geuoׂ@4; ,^FVg= '-^i#!mFj4꤮L|JHZW3 '.`iP]ftP7l6c_I/kw05[}G1KEȕRQxյW3Ͱ)$fTsnz;Ib6*lr #w|$)U+$` sT5 `~mab Yޡ2;UZ&4"vd ؀{wsXo,j833H6rRr(axoO{2gz{_7Ú5X0Bz2:꺴QV d.3[, OgibYjA!\nv.G)_z{8<;A{-&$wLdGV[ľfzagc1¡s٘V?7oxĸW $[Qu.m}i BW.ӛ[\Mׂ<4W[B=h^ op{oYU|i:ʅ~BD 5✳ۻ]<^Pviglj|U_;o`?@7pV%H/yS Ā<AQ!Y/af:;#&3Sϛeܽ[`vq/ HV5{k-I/J۩I\qz˝鲹O4EOŠ zX8ZK}ClLQn`w|{a5"f)^i7=I ( 4 /V%EܽL14tEXEI5@F}w= ~,HZx/8FӨOLΒC^ +zXL\S e2N\^8S-8G̔r1ޭ`-4~x?ZfW| |M]/-!Su{~s"Ufq:8b:|-ĉ+Z(,ƍty*HKz̅ʂZj) 4k,@zٳС%TUT( 8m5*#Z;z 6د~AnE 7ѭtn{P(X{# ؆ A7y KlI{ڱc.!Ȏ-.[qI]#k42w.fee̙6khO7%+SZ*]%{ЫK#PWS%H}}|0pō@Yb 4K0̢i%vh! H#jaEfmn+\+0TqtO1Es>Z5pX `OJyUX>a^bGTOA[C{xP@]i_ 1jf`dtA\1aqp2 1!k͇(H"Q&b-*bq-gY@m@#HYi1#z`NI֗:XtNY \Ie{DW}dRDj:&aDŽt4#MY@"(C"zRH"bIRK*Niv*'jt^^5V| Te:VWʕQKT7,R)\Vkm| t A)4 A@")TnĮ2ʼV_MR%o"p^utZu2M Z@8ArKY+|,ylst%[2~10N#PQhs"TNH "Ua jM[MkqxD(Ñ.S3zuަn\W +jjd_g{d@AN)pML-I#O~Ci1w1k9U~͸^?yݟ #AԴ9-psЪA j.M(BAcv˝EnךkPѸd; o^Y;-_΋KhN nZT"PtE= 4kZ7P9DSpd` *PY8Qm ":hp 5ЄK.V8<…9^ C ^χb7 ]BPeNܟŷ,~YKBai1^ xf%ez5gk N4 Gkd>d>x/,TIyT_tE1Ň'JQ҈ RUR,|Ng.Y)-q]>_ D23MjT&"dK"iIM 8$#t( #"u Da$׀v&t<!me(HRN'@>VEPNg)" _"*Q(N2Z !$ &5WMmzӃl4qBa4'US"e+ 5pO}VQUʞ%cT9B&]TeҪاfv st]Ɲt':uю4,7)" 2 _e>NqS v ZD.YYkz^(4Se`u|2Ud.Xj¹~%/T1͈vzx0xV8XWJR}TŗCV%U\IV)'`9rםPZ2ʚ/% \MXs+TcC# (ZYCc3⡊l6~țD))ݩ`E8RmW~N{nޏJVZDuH?~{x~Irk|OF šл /齝U@_P^LKUOԩ,&> \J~e嚌x;NzP` "r?+v1"=r7>疤{nbI7 [SCF H}z{Aa'n(DxZ{V{gqW7\vG'G|GxǁwHh5Wn7}6aocB73)yYA~~7F$_8~tzeIz!vV '`V&Fk whV\%|R!GP ,Ł,5#^P5qby t*03/C=~BV6htŃPj p FlR8T؀U&R!_ga(RX(jmHoH6(uux.H{H}(I*B"\#FQ"{sYarx?(ÌJ8͸UKql)ЉYflAHȊw}!lxhAF!eTv׋(JMg_K`?'v8PIjFB$P?U{JHHiܨf4\ARZ8Ž-%N.}snXcsCyyY>O*^!0`bzò"<[KYIkUyn X!Ya5S6.+8}fHSX&XcUApbi.;='%=RcUKIyWuL2#S{ѣ[c_5lzWX a ,ցo95/:54 yxwj*(3瓀9nqu`g ijOFP2O6vYC^ə5&f9r2 mI}X.'3)s8IۃOJviNquӹuu*6hFVۘ\"Y?qZ rI 穖ɚǞ cICbc~nNɟsTJqj[PzLQ?nd٠FͨQiC"w4Wl\y"+Im9}}뙢;0:K‚Oa8ڇyq8 {E)2zcSvU1qZw饪88"J}fzɞjY,6mDUt*vt#N!5Zv67YɄڔұ#_AF5p@j]_ s9dȩ]<,2V=ƪ7QpL$y NjM*xQ*-|Viaj,A*I!yi#o*YC֟JZ{fugh?w/0 <&@.EPc: !ʁ簰BZS)2 <hW"Ps9ziCGi?J,K0ȯd5+KI<[[p B;x&LKVtIXZ{o]˧y)Z7BAUү5[{Vtqh<qW кŎ;j/ѴAJ8W{[o7]畜Gk%`OdQ;xq3A]ȰC}[$pB2j{ț6*I}EzfGX `jj.sQ:罢tRtI=PEl뫚{ YtYp,@jFyde 0Gaucjqטu,YJ+Jq{**T0l ۾ +XYrs5jكd:â*HF6,` ܐ9?LeJ ŇLc6VpS+Iq[kka^CxJB1&/ .0qpSmχ =:д> J'h=b^,qYIn0>L$GuyQ SmQJy[+0=cdݳh 4}ьAtM y"/q I d0m؇=ʦGX8', @*ZMϾ{ ֟T#Ms h%Q}(l :]/"<#}5y {NgʍD[*TYR{9lޝ-PŹm5 ^Cmn?8)]}3+ Θ(2gw*-]" @|0e(  "(&~▒*sYP~p!35.Z0 [W<>8A!ʱBF0Jd$=nTb=韎%iV*a]^.#L 湊4T^GՖ"$;ȐX5FN=lC6l=j fE(Pmc %J}pܦ( 8;;@5o ;P r C8cQquZ00,ٝu|{wT? )!6so#!}dy{p::wr ? N-y?w߱c06(ϨK UinjYVA-8ao Mpv?^.]~?4 `OY<J?*?*?'ejC:o~AUo_^gOpFQl ng,taܹrDxק&<,QcF =jR$RJ-] e4( r$U`TFB@TRB$5* A_XE zVuAà @V8p *q8uW4aX` 6\Lbh7&F ڳժ hNfΝ,(mZ3 8lIqȒ5gĝ[5|sgOO/Z8S̓xk,2#,~%kvڶnRpȘFh)&_~|ċ?vL~1g?1uF+bI5{md$PRI7 77߀N$38;9:q:BRS .( b /I%h+/RQfLC)(> -<6-%f0DW)\ d;p$IGzˁ RH/1Ⱦ̱l 0pNUIJ .Lp,s(> W c 郐jNaqnBzO?g T)3 ݈*bu,v.( b8tSN;}/Pu95Ta5K^5̕ѨsMFj7%9 0 9Og?6ZvBC6(mokx4=% ?x=^|R@UfUTPz(2Y#]wHa8 #& N1Hco;FgEVn:8Do0*:8 ֪9 Z)P?1=:饙iXjU孲֚4;zˎ,i$@nC[z]d֨:7I$MRD8e1 gy+w3VFzsU. }VXoŵ+ T(a'>bq&(9nsu<&5j2٬.18큊r7Os9H-"JֈWy~cX%Hn;Y-e&r_ƂpxhI<@Chza4~|H; Sυ!؈pbHI\BLNd(D"#F.%[[hĚ*Rf.pø샏c(E=3X @S31n/ VfbW;=x6@YdB\| 1WyUJ;" 81F M8F[\ٰ ?  'pBWBAeBZU`C@n/|!_/1(qMjR(Dt2lxdzr6 Ns:!.jQ \B DВT@M\=@ H@emɫqIbdǖL)WAA =mi_EEf.| &p@Hau# )Y1F5&]c4єi |%=of;49L Bf-Ta D4 O0*\ I Qp5FGָu \w\;>. kuS. m=*Ԧ6 |<$](EM!HDx%a Swˀ/zietʸ7 Dw=gwrBt|]jxi[(\ Yh?x OB⩠4%pum-;2%6@b,y%O{ScLַ^%!,z^Յ?_و _7~bJFmta;-,w5&Q>$&?k0H`t[s89̿[ / =0Sȁ"P%$4<-Pt1XA3xA3B2(P ! ,^H!H%J TkZuFC2Fȱnj +)#D\yrcHbʔIMjӶ춍O#UJϣ+IlǴҕ;o|GURkBɵk׭\>JΜ?M[˶mV⮭WH>J'8Qu"-Vbށ: D81km:o[f F[N&#*/$jR=iD6jq]&+SCqpN8+qgSLl+,u&;ѝyM%B@ vUtk2:@y:jm)Gk7-L/o/*5Ts½Z!ʔV젳p[q)%wTѲ'.0r<*7v+y#4Ӽ}D铜;_OT?)p2g]*\(ݎU1JcL],Ǟ]c7ogvS| >@`6_䓟lj-ǐsEQdM t:լ[}*Yur]{T7Ħk {9t<{ŧ5q7w/oZO7J^,]ulwP@|x9QDEf3k鹾>q]HӡnK 6bX45h8mAL;^ S Wj؃ geP|p2fB| "0 sG?!"\:HV08Lp)h p]bwta{!pɎuaPGP4XL8@P_LŇxD$ Hd+ @KKÚEpQXlձ6ȘЌg%nkw29ػc1BGCFӄ| L$%IJF  Y3'ȏ#!E^ԐFJVh EV77`k\}'_Ti{ xnz&cMV(}d:WIue6ֻ#V!'Qxűhq `'6`\w&[Uf3>Րr3 ٿ,w[-򒧷e77\zwc}Cv 8kUZnِa']Ǯ 9bܹv+=_OiEԤ PRnvd;yܙV)#y|T:[89[þ޻Y՜z>-OHVlSGx{1ͫ܆9~rKe9Mϝtgڧ#2'e4pV]-)i׼k= #{&r+wXVG9G^8w~ |G~v&ftQveFy6~&~2~7k(&xa2[`w`': ΖqH :zɀ #Gr{XPc'|@|?Dnz=R Ő 7~ QKWgׂ蠂+p-'RU"K8HP8Bs$b?(h2UB8{K]Pc|U>{7^69Nis}ut7#c(*=G}Іn`pxs<腽Gh'\v`8 ،CȇIȈ'DurXQiS N4J`  ;X$<8htK' `'eyPYg~ukհFOj"flUll_ׇe,$fUw،ЇҨbvHcؘr(7Qx,]w7c|v` @dsV:a8y Ѐ`ȏz(fYӔx3ae'v/xzW 95(ن{ {&÷*Sq|Z e]bH*;xuL IMA9xGyIO) etnw as**QBɌ7h-v$W #ؖ*r^{Si?YxX ЗslGAY㗘V1QdY3D<U rFyǙ0[$9)HGHiD Wn$ɖy8v{c:EYh9Hxd(*V~ GbyF9wX֐7zy9U*ՙ[Ɋ&tBǞƚ6U@ٖ8|; <5 1 @@ ɐҌ zusdzAt&:'P&k<ةC!#È0PʼnBԆ枪b)cpS(]={S* ^6T`2q0 {)Ai8y㝟9PE%aFn<}ٗ٦TcBJq~8*"By\xg~ X7,@(K]4C{ N| pC茮(#| !_&9c, um5 zNU6=R<=kzA@L?PR~~ W[a/cӪM~I$ s-vkzg(1:=׀- ųh֠ߌ N_̕OMr|՝OԪyIlڼ܂(t? ZP Ŋ%#XA -4C2tE#VĈnDžDrH ^;x%41DXfSO% Q?{ SӦɔtڋW/]`mUWV^aoUˬY\XE![oh(Qy_0X!B ,f̸AGad 8ȑI-W!Q®:6i*6Htuިē$Anl.iD~Ӡ͝K-ԺRPOu>5U[+Yjٺ wnݻ(۷}(|A l3rB(10\2j-mҍO* ŐRs& yf:ꆺi;f<+* Zk= /++2ӌJ%pp4'L2m&ÇL" E[&ќ!HRƣr$( 6ʩP<4kEhR.)JZ+K-.P1ͤ@pB+F:<--O>4AE2܌-wQE Rxs4å0 JSue\tf*/ߤX]WcVr+>zX8 VLdT l=:Mm_ٌn܇FPt{Y]QZQG* _}a%\S` UNa&uֈ1J0xD,ŐUHs2PVbe%V5"_ː9FZg|v<ݠ#W]9Db\jZ:6Ũ:ϫe˺ꪺXE/|ʲVJf81dMf;?h낓I#[m >ɻoʏԚܧ? ]+ӥR^R_#f`wZVg`|v7n&#A |Q+Bv2 T=Dd{#Ad"K䑐E+X1 M򈖼&mǂZ DEF‹@fք5H4E\05 _HDwKi Pc 7LBxᤅ.K 2 d6>(r 2 rs? b\(Ks0R@L"ͨB rB[& j薶D"VB 81̸юroP G4^r^[1TAՓn&R'! S֢$:+s<iO- Jue?˄Ք1( U"쬻=+5 irh]1׏ , +w>5VYF#(UULyٟvCfuG`<+AI TYUl@[>Lj ַ&z;qOr(2׮ˈG*5%Ѯ)Vuk}0̪W-nE_W2%Ze 9AB0lՂn9zF7& ; NpBkj5)fC?RKf_|i24|έ!GWsU Zr.1 gXx)$.LW'lD.8qw5}$wgqUrT,oa^À=6+Ҥ/~ȝDg,. E2O@>8U1Mx@8wΠт̰\JCR4X\@OrƃcTEg ߽}K_.ONӴWhmiEcZ3Y jV{%Tk ׳: :3BUc;j lf>@0XAj8[K(;>>L?cѐJ {s?:ȏ/802i#A4 H׫l%_C+_TG$-Mx" {>LswZPO@B۫A[ @D +_`9ƛh E\]lE#3)4(=0hQK:i, Pb|n,غڻIJLI7#$ت-@Q0 dA!D>HiX8UD3Ђ p;# EҾQL1^ .t< t(Ns@9#ˁfkd2ϖ6lsČƜOO*q;FL*m*yB@ќǀ ~ =*PpMP9P*(N p0x1;Ҹu H <'B3 H K!f"M!@$$DRRH̅*՟Bt4ѺBiP x(XM7}7* t9=)p7̀pDZAeG,Ea&XFCa]&QtBJ^Iъ7q\LV?c P77 Y(P (`rE16pK>i ǀT9C{`Vu^eeb,aW-4-Ŵn 󲔝t"f*%"$%? yB(~ Ȩ;T-*Nc1. (@ٝgH 8Tc 'We(^`8X gXa`,U6cZeX.K"]d) 2 O6:M%.='le%&(($F7|5OE[P~l(d]Pdnf;~=*O1)o>}KzqfRK[JHvaw9$_ i7 0.ghe- S b\h8MF*"bx"ff EcX<~Ö.1@N XiƎc9iu13gbϿ5[jڡ h啪aĘ̩^*wQ0|a΁5kmܛo*P ۛ`kz lPei~Gqi϶5gVh좶\|Շg%F zjίcƬ(Ӯz[%?6kBks 20>kDK $8zI$ "OT} 睥yr֗>9O9VxT}jg+O$Z$3exe$-7Vֲ".:rNȍ<-o~uz_ p?ZTz!g|?9ǯzfƪť*sj--$W?pۿ}(4AÈ ?hA PC r8"A$) J-Q8iV0bfάf͜v<'Р@Egl޼])ΜRZ3֬Fz2ab*k6ڴ~k6\n +6v[hغxC+l0g҂1>v̗IMxlr3ПG46DX@˲gӮ]Y`7nbE/Jc6LA *&j6B*P+zj _͞Mkۿp2\#~M,cIFmmVZg@!k`!V=*,PCU4U5r$9YdR 1iwN=QI)%\' `cG}jW XX~)昆% c >dIX^!mZk &"!faC%Qvq4PC>$?ja2KؔS5>cL6):SOXT_}[%Z_ay lj&fpB8ۜ'{z-m@*p }CB(F=t5`:ŦqD G&ΫRzkV._;,^@ fʩgzaj|Rk-%_@ hbv.iq6{)V1F#̨RUS,0@D0zX /UO\14Ufc!t~c28QG̍n$0 )͑F4v͡U?ud#FWt!:өN."' a !ςD-8a Kxx? AqY4+4CB8-Ĭ@ D!(GQYD'شԛqdr>"NyWKBѝB%>w U "1T}*ϋQEgrEXͪ-ս̪+UKU#Z'0;4Qf3(ҳ_ pB#"VQ2+p "0T7-Ʋ*YS~V'UNF;=^ojZ6LځLۦ["fhj-!msCr$wԄ~ x2ǛlY-laO\ d *T0_j-BSJ;/ki h9ֶ(t> o Y6j4)^b@p[uFtMYSIQ zP^lR AD(> K bB& ^S9P*6OfaWV29i * 3 l么:T ,\ ?3CU9%2@]>E.+ p@>2 w,!`-$5P4&$Tp ~E=꽜y!Ӂa {z / ^tүmk&\2怯C-pTΌVjməٻ̀v3a|hb!p; Id 4AU${qvU^5a$W@'$ U}Z32{;m >~ٶYr~(F{3>yaFjnh /]BƝ)Ma N ΃ȀMȂ0{++T/(/epQvrt;~2k ݛg @l·/vs89_\(E :>t r Gё.w4+Y]aݨžU[^]|١h)% Fܱ8]ǰY9tq_}FGdj:_ (Ļ8Pu(A GěyU5T|h]CB ~0a5BŮLt]`I*eth ǽjߩ =@ \tX&4)< a  uDJE`k..}*"Άɸlm  ZQ ֠"*b## 0Q[ d"l_)ڼt'00)bXhQ1P+bj-E.2ƚ/bơԊH߆8c@ L :a"R̥_Ȁc8zM%Q9jČ، KcrA;";* zv#|Ca@*T,cAAR_𙐁,%m%(#Z͡1]E2 o34:H IB07nBBh@VDՍnL%#pvⷔ@P@K'E|W <->e(8eUZ|zdąeeNv] 8I%#<@\%eIo#a_nI`ܜ(c>&(@(a e1 R7'|l1_8iZe-uP%-Eq/b0&XJ0j@hpJ⠟}'!m^n2.#BHFLF`^ @pc~('$Gz>QÖuf?F|)YUݧA,bQj&邀6RƑb/|mm FQ]c5ҐrޠI $@F\:dhwh'Bc $G {Ja)_P%i"b!~\d#D\l1š iip!̍qZ#]>!o!J֩"6 S#|t y͋%| 8$NţF*)k @VFҢW Fʖ.>VX*3VY:ql6b(A5n nsvjDka&ֺ\W 4&j(^f,zhJk0.a ,aMxEi2 1!rV5(bJfEjW9tțn0,*"Jliy $;^DAD *:xJL,||+ƧW-"i̾,ΧlV,gBvxe5ΖFЮ%hyJGdWu=Dq6mK=̰k 5bAx)FU.$\얭@"j (z{:$-ka n.B*T QfGd 2O]F-|n*n7"fte d"/xn rKu+ ~RnSoɎ $ńNxelN/m,nԡn ㍰i"b^nHY:lޙF̤ېc\W\ؒ-mol| -×mݚ,40"0Gq'rb 2nZ ,] 'Tn."!~3-$boD,p`0*0G7W`!;J%&UIk7!lg8 #j,NqMo_p . o2\s%hFqhVOl11tl,@`WYuCuͪ\Ʊ_.mHbH(118Í43*^3Tr{C ~<Ȯ7  (9hdXU;ψ$[L; $)++6x1Q`@> <%Lm|H" -X(l0DsF,DySS($-4{Q H#NM>&+ТJPRJ /3+&L#| Ϻ3jM۳j(( 1BC)DG}+\ (tوuQ$y/=^2TP *RK=Md2(k Zm-,I;K2S%MP-[ّ!hT B<=X#H Bih\e#w6#v#{oW{qj}t钐ᾁEUȃ>9$36MVKj}b\c/.;kKdK0ʬ8*XiL <+mly!D8yZhS<㕗Ew:WYfe&뮋l9 {4~ /;Qr a>-$,J#S|o'Al7Uj{XOr;id^i?s\̜R]ou(ִ#;gM#;GmU[$Dy,Xq$a,aWm1wr;8Լ<.pBAoq"(Nr ^M׿uXN8 `]Jv8P\v0FK[ HB&iqxtBP4 C @"lhr> VEHh4C(FG5E>TiETƃ t;ȅlO|T=@!{WA9QoZ yKXB E")ďi'$w_F:uD(cgRd ۪VVVPmcLK]nޛH@_rp2-4&4@ P-ar -<"b04Fs伟9ωzSt;(ϿГ4P>U!4l2%$PƲӼ]J @CIt;fA b#Ҩ4TaR;ArKDEH70L'Y_)yVQ,lU HV<&6 bjʙZmCկVe;t e ch!yĵvP8Hsu4Xa IbuN³ =TL4Z ڄ7ebO{dف ߒt,0t"|^j%*U!ܶ(J.R`p® )BtKccy#üb^ɠ{ ihکZ8WMm\TҨuۓs2!|KsWK?.[XTfcY"*XP|D夶ėXf+޲8X/Z;N2XF88u`U )%ny%>S8;F9f!f h6%?/09ts@Y)|sCUFMfqAAGlV07*#44C(3*,D̓zfh*YTN?S |),gh44iFxt- -yH騋Hpv@jp@/ AJخ3ⰊD@ @LDqz "Uc Y 0C&U:u0q,9K USМM,t۬fMV! X5j"Y? a 1UQQI`'/ X~S8J-Kӕ9-T3?5(U^Ur_4 )`4#=+MJa7X;FlwtbP$rc5UdmZo/~1RC̵Sa]e٨f4"Ţgv6^gC6BV{-iV3i+53VjX;EBkk?7K7dZyTZQ:SA줟40 w%a[,fTn3iҘT&pyVvǫ Lkh r!wPT!}TWuFaa,375WslY78DbGZMctH[IPcR pW @:B.Jt1.t2gcp:'Szg-i[@={0YCWi}nod~OW~Qw4RN$Dٻk6LG+K2bJ?r;y(yP<¦䋾[*#5N`[.$ O95PEDM\Q۽ŭCF(skQ/ @oIwu?eUp,l\LDAX0[j58 /tjX s\ͼ\O{;ƺB?z2-lyw{ 2ePdDHұ" [ʫ 5cO"ٟө@%[ Ŝ^]H\ݜ~L /]Sn|\U)pX qs[zM\]%==1H{ֿwriioj5K0SseL\dPUDɥH^e5j.Jۯ 2D6-]λB)S\^cIwS._3V_V"nHyS ww'{40+^Y~M_]bd[5c_[,!bu?xaݴB@ z@Y z1!-ZjYlj:zx*J<2#R   g<{ h8zʥ&?,5TYX+ LzMyX[={صlo䠡B ڭ{!\#jpB%رh1bŠA-*9͙yf Æ KN+TOXF}YkW.{@&Μ>h 6T%*ZvYoE Zmo]y^{oT 3"Nd Hfhd@)ZvlwI-śo3tGO@P@iK#8%tQGuՆۅ ,ꭇW{Up >$E_b0`1ȥvBRZI*QTaF&:JRL>p@#G:̍4R}5I4VUybTln~$xBP\z1d|O:!唃7 -X e` &hf+ I,XX[O6'J*p 4ZBoF˫^}=E`WTQmճz&L]n+QYRx~!#'P'TB^|1uCxcz#ոVVA}A}9{UғPef-n:g{:쯌Mv?9v7/O{dlAE)ளMpeQrkYd6y TC·>8! Q҂ ӈNb~VW *';Nm mF9Esk-Qu<$D ΘrG[44.^JPWoszuȱA,bE<ͨSI(,B-P"$ Sx)/pVQ#ƶrs\^ȗ$UZ~s ^K iEB~͈ iH@2n+AW4/Wt7,dJJSeWy^$5E "\fM S`Ŵ1 s&#{s%ME(zcE#eDI:2vnjzTWO$ 3\>o* IB?^=ʿ\1S;ۛxQt;N`+fn ޴0I'Nq4h'\ȷ.p!IP HABDMQT,WLC'NuCV]VUdgX7VB'g=JZY)6u2+V պ5Т^3 Da x-r1T}f@Elbۿf:vDNZͬ6k2uj+#$2:Jc)ˡh/lkZVm-ġ}H{NlrDDUNdLzYfrd҄i]Q-Z1%U2^LK[ս(l ߼կ`s_XZkƤT6ĝnFikNÑ0&5lyT#$(WJH}Xy&uZյ,nojc86(~[si<, јCnnd(sKWm e)oVJ jNw)])1ue~[f3/r؉WKta2u1lB.l\@/Ow(U1zཊpwirҔ -Re>s@6o qmsL\mCA eB"gwG3:9@4U7%Iyjou9p$d$uMӝ6` @ >u'R`~wW"wgu'RI#ekgނ"Jҭly!W L/6qG>%W.0n`+& U) 4}|x?OZA襴GZɢWWwng~|5!nayP\s+2,;ROg{۰gIj衕J Fqz]axW uC~o7k_S>z엾L Eu$vk}8X?v{eU F1Ky=|u1ugfwq} y !r"' x}=}wgz!v 7~pR~*^%e4~~|r"${aafig9hp3F^a|!4V2Tggc y9j_`WDuvvqf 0Q 7sk7]a]w+H":iD(3ϳ(;xba$nİp  !!x~`:ȊJR,P6h|AƇtC3{8JP: I+2 8Y a ]y yyz YǠ p'+7LQxa2 U4As8ENؓ?iG<@eXiסBWufxL~%}9 R9])` c0diZY im!h,ȒtWy~T 0 \Yٞ] ac 0 j pd^83a©yiaN:|CBn3e>o&η6@g Y+zҢ]5j9P p:^q.jPlz}؜Jl!Z#KsYqL[[#,)0(+9pwz6Z\y `9 B:#(zDLʤNZY;{h YWF[jl*| *,Flv lJo /ZRixzzʫ] AZqʨo DQ*ZĩJ&$Z:dJ`y颱:W3zZ: `o),)hjFvZ<`% =`Ƙaڊ2#6Sx/ Z =d5)Jj*{A: hH:sV1Q*ʊ(跓iiܺۺA[3mᴃyXqϗ[E0"+; Y ڲ-2K6ۯ5ǤhQ@K|爴ͣ(x78\pE x1TFI֔Q% (; ij۶mp0Pz3#3t3PM5闞e:b^b='lBLgbD#0E>Y5;;,[۶+˺s uK:pz¬ׂ[`5O.Q{֡Ei#D9GS[Q_1 K݋@w yt7)us#19Ue!NtRKB@($l7(jI8}f5: 뺮н@lW|93$,:\aTy';D[aÕSKx,a]k}tG4.p@+MPʢ!kX,ś"L%LA=p>qs /|m,sy~#˱ek(=y܋*~GOIxq~UU* b;\\^캡J훬#ܻlb1]3 #uI)7l |B($ eA,r l,L-x\ ,N%elΦUj }N m= ݞVM ƞVi췢]2Χo ⳽{ǵOwMn1 ̪C ){Vl9No N[ (>{N[)SSN!,=atR*"5.~6ƪ=4^ qm~rݖA~xdVFΊ e.))T^V>,3,%kτ#ӵ=~9IX9*L|l0N6wJA:0Y4{=>w/"l9|ZqÃS@\_ Oi^M}^%p~ *~; lO.2N~s=BC_0Kّؾ[ R?,%ۜ" >aْ댺c~8OMȓiF$>@[LQ+*5ܞR@??eg?7yyO9qk»(6 i@P*_qO@ |NB ߙ;wDz]QcrI6OYx@8p 5m~SgL( JEEzTh0hԩ4\*Μ%~UuXdUWmYBuހq޽wh )2YĊ-dh-OYdΝ=lY44k/bHP= k}DQoرǐ#S./ c[rBs"A M m QÍ<iJ%U8x  FEZ)jǬzQ' ;O"=R.k2TQ2"Zxj2:-XL3&p4zG_MT;!**N;m<}D?UkдTx% ҖӪRtE2Q Šc^W7uF2nu0WXUS:vbcqkH S-bdD]T \KW] K+Է^)hV(`,0s5{u aWC6#mY<7޳c>.A޺6Г9Ȁ*cYFo*Hifݝk硛/ SQnAvҾ!xj4&\7; h$H۹릪Z9ooHyTܙxzv%k Pb,ߵ5 <.ϗ 8!vHi'Vi/U@ՊP4@AT|,x“g<%S(A P >x~ֻB#fq`M`ٰڗĥʄs!qii6 Y ǡw ~ FH'/%Z3'ĉO e@XQYERg՘xу~ (G1U"lU+3yXFyMKE.JՐ:JIF7 \ / V0 0M[ψFíHmdcEBc4%(! UҬjt}zH5 Y FCLA< IVܠ0)cCPE(Ba S#1`R Y |fxk,bC=b/H QR4fw$)sy?C3CŗHܰӝ?CPQ yF7MCzKXfTϧ *W5$" G.]B_}M;<Ʌ㞕D+Zvì',50'8! ARRR  +ϳ#Dؑ4lfy(SpԷj+/T7Ե鼠)`m)[JE4 k\8'7x\X4׹2z1]V7W2I oCoS;ӫEoz \BUЂ'Dp%P AJxjĊaۀfDʷ`vGsXtHJ`ӕi-똃Č˜Uϙu{Q.c?nF䦖7ㅲ!{擛I5J.sy`"ʆ JPf6Yktv)yYg9t ohtռhC'or*pAiNsz [BNo[(50V!՛>jZ:uuk^zfv=+\A *,! ,^8 *L"J"FBJ<: 6M;u A~\Gɓ(MsKxɜ)Kxr̹Ymͤ 豣G6k6mΧPYRjʜ4ojV6%ٱ\|ɶm۝nҌYHuQ.U˗N >XX~ODpa;vX"QQ{N:}QkIr׮]žM;V.4jU4dIv^μ7mמ>wwM;u#{2{H3oN9 $h0 +Oh"QG r-uGlWK]v[NNzMZ STd/Mm&r^X(Y0#y9x[We!Jz-Ք^ zoE/|PP)~Jtfx40swfa~'6n n.Yd֘3*nG8RM%fZ|ɥf]K$|1)RN_Vv'U9cfGi Gg!:͢m2c3<'9'^}e+tמaIfL&JfVdT3!d]}RP^ jUTaƽvX40(턮 Hv諆vŊl4>|Akq1j1% nbLF0kn3[jK0` *0=l%TqJ\RW0 \wqqH!K S4ul$3h2e۩ Fb3ܕHA'8іC Kw'IlĹ:`~C[Y"424:a2r@7N7ޔ A$7A!0?4i#+>2L[?ᛗaƨĴ~]wOA !ĩ 6'Rys=hK`蠞p72kX{w5Cq{( 4Lg ʦk>e-0j4َnHt]PA ix#-l>hSZW@txHa-hj`&8YT6GSڲ!M2F kxC:ͅ#D/v+) IpBe!O_"!w=UCq*%X2d?>QZ$Ly{czC!^B6RD[4q(d &.r|#خH҈ @@3k]cUҖ b,Mqa~PuU-AK} iF #|F2eNT8c=>j*G̦60l@?߄Y8WN (! #"`犬cҤAnԆH9TisFL?ā@ŝ妡Kg,>hC+JcU?G?[L&>4ě)͔RF O;9jP (GMz1qa!AV)j׆&թR#נ٣{|XJѲbYBn FJ,pa>W9uIMj^c,U`(}(F 'wsh }-K^"mqO٪֌i<ѵ2e)[2i%Z9fo-o}{d+~;xq*9&D{KDnu]n׬ Md{Y~6hE,4a/})HKc귢Ҁ_!cЌD\[N0o`k8q#euJ.V~x#".1J]JIeRabWqA;V3C\ +-Ȉr{lI5aPέGnAq%@jm\Nu>%P_a @u9^c3y=q m`m=2~h >#F'l-;ifҽ4R{ PFpR0|u;PA VHU Gg1 Z"u=|QX42[l>ڰĶ[F7!i02KvD7]Qo}@^-%Uw* |0e!D󈻛*gqn|7A.6'Cw1'3dւ]'y0!7\X\ p 1pGkU[0 @ PbsVxrtT^X 4_ZE+A`8^O'dOfrP( lkp؁vxU P |H#y fEuuRR~w9ppI]7 ll-zF9Tih9٣aٟgn0Qv {)pR̹t fpu3%L8shVYu .*_E raX'pʋ=zi9sZz٠ڛ:z嗥7,\ bJ*u3Еu G 5$*ʢLMɧXƬ Ơ ) kحN؋S) J54V*Z*,'e %@07ړp>uH3y0J `T*׬rڣj + oɐZ S xۺZ*U 9Ɖ8!p12ʯ*#P>IiZ/u k 39v ٱ !;c) iw գ)l fm>p *0#eCX*(`!ڪ FDJ 7X8 WYhA]AH)yYkʊ|hd`׋=*gm;X *mنwȩǷڔx,GjΙ)PRjy\fM]7l)1ȔKrTr֎;Y{`Ye;i|{ë[Ax5˼+\[ٛ 7Puyc0 dh پz.*+rgs m  Kܿ^y+zz w %x[!iLk>`:9t܉庆5lsv0,ëh5lɊ(vYļ˻f+&۱Bꚽ TX%`)Ukƙdw 3^[[dԚj| ٱفںޜ LpY W\Ue;N",= &܆-7v| Ȃ̏97*l5}lx L͌ p gΉGʦ ʣz(.B(ϮZՉKHzxn Gݙ#zбК nȐݣڼo8rؖ̄w[ pŦ$ݷ&Η!j .ҙe+ k*p鈉ۉ ̈ntʙ_tœPuK_\+gGYR-_{C[m*۲ f}ֽi Xևo-t]/itܸj7 k<щܬ6G=Õ<b-Lg)ݢ At*S p ]l}ۧnW)ҌGp4=ܩAxH8ǜxØYFCIyn7j,y,Sy q޼}{l dg -R}(>Lu]ө׮ʩ~\KIDCS P BaKeܖ  /Խ64ζL 7.ְvaN|cߵ 0펾M Y,x 0.d5~^ئ_{£}V/ R ĜMJ/w 9%6w_bx_}˸{p?kM$O\tmW(]+Bk:bZA`Kd ֐ ODw@N@w 2<_+OdR?n 5=!k1wg jmkq/Lsuo{~D IJJpTc$?2턐d  gSU߿prց_|b -dpa1ĈբXD>hؑcJ@ɓ$UdE!TD tF`g6s"= 8PH%QlѬZ=V9tiծEڴftK'Z…w&\aĉ-^1bƑ%3;ٲek5oPgaD&] U54W'ICD(J1URMuӂr`Q"S}IuVuYkmI&LPr6x8 J&Ԁ[*z^fc`j! 'B 3ēZ ˵\\b)/ڋ"MƜ}޴=_ ]_`$E4Gma&\|%޻dlTc5cW?&"dO&)'wmeСj;cqE1SHp̰[T}ƅL!=%C@1yE{_ '7__nko^67u9_~b.SbLmDZ*rAR*'`Ns\Tv 8@40:ҕ *,[3Yb] ~$4aib!a KLB2$ MD"yBa4ez17 1{@|ѥojv)^F!8y4-')sQ L  PFǛWGdJaT9…HD"a9Aw iUye29-t$~_ÛGE+R )oad,4Grݸ҉'b@RGl@8ـ9u|I'T;!wBpO3 > Dae WEBd\n-o)L/^|bԟBˊmX0P)spT K܈j%~ul {<.G$! ")Nno[n4qwފPd>z^[1%@?-w42+hTB g`:v@ \$,WkQj% XReD4bQ }h8 }9B4vdT 4@c!ó(C|O%  vlm ch*²NLX˷ ;33] E[\ M[zCtx^ {%ޖG6@3LӟR)Wn^M$ ~[!Of61FlėdOh\Ct.n/vEX rЄXu|2(9NsX;aM thVJy((!* Bm"[ﳽuB"ŮqGJ4`;* CӎTOn/-n5[3? _s`9'=2{1W9`AmH*l3 Q (@SCS mk % 'f8E#miCy8ٕ^Ud Ó,c5wfł}ʦ-87@/Bl`VogAp ;{%3<;`[XYh`6'=;h6(-b>+rᘾIpq9YS{9 У_$P|;(3*P8ؿ"0? 9`j8YPN@2TB f6 P;Ӑ"A!A2,/UHA <ssēAAβ Yȁ/rB4ѻ+x&ԁ+ )5d"@ ;.ni(ZK@D;0ݹ6Ys' ;;9\Ƅ DXrkÃC\SD )GDxKРR㕟aNc5O+؂*8E}<4g 9k:Ӟ#ncVD`'-06@CFj3C:4i @A$ĕF +ʣ ȁ~jR w|G!l  i{ |,?I:yS)AIhҡIt7*Ҡj"5BBJ)hA{GGʬ50K(rPr9Tѡ;  е ؂B㽺T;HlK8@LL!/&HL`ntHw lʢJ$:#\? N{JjAMDM; sMhhJ\"uHP;YD28H*U[K@δKi:6d6Nh L+,c[N?$ O|/d?T&` 0hZ)KӄլֻHfhU 1pNbX9΍K`kL8Se$, & L #&(e& ! J(T*4t/%%.JPuhV@$6uS9 cNi[(X/, ׼% =D^n&arM@OG#lJڡ۱-5ZXgЌl` lOE "(_bHQ]dѶ5`k=^}ĺ`2~D N+96hc7n/nצp tk\`x96ԗ`1U_HFŪ"H-K+]MdQsXv SJ5#_ P yLY]Ğ,e Nӹ_Ĥ8W`5c֝ {@7a3Ub "(iZ1JMnfo@ag&=gt6^SngcrG5&gYw- N4USģP:V6CmYCUZuUFd 9EiPa`閦\јƔ.uFT>U>`&zgv/j~&j hh.:_`R}z[aNslPl< Q 1<#ZsJЂn!b@x߄, tx(fZ U'83U[amn;`vZ2ٿF#Þgn^Ll@(ͦ P:F lnj0߁@iP`i-iDZ]'m9]-9\-~ w _C UH7h!Q ?JNש *"/! 4@rU_OH/#?JEdSh0aa.k߆;S/>9sp[47#!fspNK8asi kl#8::q_kioՖcPO@IK't3 C ɆlQGwWG9wZ2O`P(9]px{j`ɾsbjY=>gfD&-/(R#_1*/okOEj2w9H RmL ȨfƿwXz7&L)hxh+L ̉NE aVv#?*p(XP>0P0'RhQ#Rc-f!tG-Y|h#;T &MZjv4Ӛ5-j(RЖ2)TaR {JUjZjm鵰b)k_5)\9I v1V /` F(ŋ/n0f1 7\2f,6`.m48@ KdXa톞9D 9dG&N# 1N|Q&bYRE&s>{*4.zk^]5UYrбcv/2Z\ݗ^QX3WNuu-{e`݀b5Y yA PX *Bg%0@"֚k!n-cC=o \E5PF5 sYL!N$Au5(لH}gS5B7{PǕ|gQşYY H`meu`z I]x _~րՀabްJFbf x#k iШ< !YB 7, TLYy]vZl,5IsNNEd* 4jjżXքYH)-{~:b2 %Rzif@kXةYjEdDM11OD?`<4Df I0lʪ SZ{d߶[5.5=g;//l$|8op"o뢿Y6ck $1ܪ:qG5 vڙA%¥(#2FK_2[f2'NӹS4 L7*2J (ji BtLu?M!2P `X麬jj(*QS`L+C 9ɌkŔ ̫F+R[jY)R>cRrHddV|k UL hO0-/jSې HRQl[Ihq"j[^miŢ\96pAl\ac c) crDZlRS F̦?N:^5/}Oɫ~ULSeeU*jp&l ?!7J`?@B+v*(7;"/\d,c,RqcIeW\d%\rV Y}IS7t7_L".!inj2S$!7lܒ^bp# 7FJ {O>VOLhFdbl5y CC ukq:0Xj,2A=ްN̺~VU"6O\"{ͦE@bt81hSaَщlEA`2fSx})͋des+-MjSz/ XpGM#'&@1|_+pN8ݶ`,E(A P_AA ҢŃd7ۄcHznĬ}+8JV$dbޓwMu~[]['ǁ}w/+ 5-ުY%l6iӈ[P*\U</xWgܻ΂mЋ^ż}K٨^?CZe;D# 1p]ٯ۰R=D eTpUaWtY[&AEbQ d!]|mTyUBy^\#h˶U]ahݱHn|Y0<@Ax@1]aH DDH^`oS^༹[   P9`|HŶܠV$ E _h_=Uaĝ?)QAVdp50%P o֩_)B@,p[!G|UA̍cM~ Q^|]abn N!:i V#<%fcpHt"ؽ DXaAPQDaؘRdb` WA/FmA#_{IaqS2^0l$bť4eݍM!:FUGDtDlUmFN^H[~pǙN#v^1iϛNgœ)ai&ធ}(ybAg(>&{NgBYMƲAUV^jFpjJWJ6# E*vr^nZej[$ݥI)Z'ifhN 'n,cFeX.*'>}BFbl,BdD~†kFFh*Qj[NcL͇ ڪ$l(u(iNn0gAQpq0ll2:TBȒ<^%!qD+~@ ۽j$ϾLTm;IB jhk=-~x P"Pfl"uEB ٚZ XEڑIAl6GςIBB El`lܑb.JmCHPNajlvf~}@įo.Jؖ@<_ @D"B#1mǼJv F8F ZR/`xM5-9ڤJ~o:(.e`@**&D~lmt@eowU^0aIݭ\J%cw9p/@ҀdK^pN.aoPPMdR hcF=X aٺ ob1c:DV.6+\ aj ɘfᒏIq~DH OQ<,qH&lI!A–qW-d 1bePFerhRAP7rm^qHpu̴G-h&X\1'OP?'`)+-ixM, LTU02Adͱk8@sDlRid"3L333SA"\BIK^3/VH_iX4s8sE#34 :r; < + >?211IC36NqG ˜D@3GI f XSiũ\wZo4ZZp K:s_at?SԘT3vO34Pα@kIFB}STOE]/ cZ5ܪa I}*XsB'4VTZcJ!]m#-i5_u}J%,ם~,ϨJ&P.6A i 1eSCdmd#%x AȄF' B,p6a5DbZ*J \{ \~ nt`6_'p| *DPg@lt&19VCOXMQv覀nAFswwXzol+ZO5\x\~t7o 884x?'57=К|t<^3clvg6 e@X2T'4BAl"(\Ʒ|w8Frm\*H7;8Mf܀h3T3?#nr/L_%?jyxaد;kAB05odY1bEF~s j`$zM OJGrs-S!ad:B:ݩٕWTՁ]Py4B#B-E:1:"愾6/tTŰH8w0Mo3FO{OS::9m@fn\ѳgOJ s{芷B _uސN||khb$FyKyt6Il}cFNȵ'lgFT{|fH̫C\Pzh+8| ~l,\Xezlq&}|W+0|'(5VTo6zܑtFٰ G<~q9_SQhD"[ƌ҈YAsZDkՂR KT-$FC$Fvd4+YtRXLhִYS0];y3WO=_} i\EҢ5 jZBM!@V4AEco KEZa٪(m[m-k^{A`"|p6vɓ)O\Ef9 $U ڱ`h͜lmltIUG EB#Yep*EfC{d>.G.5,TMJuk2|m}ڹvo|}7h ذblj/\@H PH3\!1A$!DĒH,ijΩFU2S\[Bj8mFq|+G2 zK\'zO> TtJTSjL-K0Ŵd!M4hdYysK&Q!Z+g(#;2Jhď EDsR'FeGu|eW*!V@L=u?-[7W0jr,LyMkI_ѫD!&<k)Te5i7lc:Ddٍ7ne\|\q%\qK4+@ l '/K`UUrNx1JX8װjb7[VmAĎh6Bbpv\T|FѠZF+ $}ڿ=`λkw^V\c@j sБfe,Ėդ5-Fo[~Eiyq{t%r&ۺT~wKt>\23H.K8.2S}3RH(w6xˍ%XE-Tш8F!(ͺ̈́y7"LGֻ l/?Sc$G>{>Y@(dի3cyԣ s 12[@'" Z_>G,&%k N!pE(3bKl*E':NH4CW$ẙ-?H\Rۥ {)>=SNJ [ Bi!5/sMlni&ŎN-7ކ'KdDd Lis^`<1I;ɢ/oqB`AYH1}(E0v (ۏbtvd:6T.:jbLm8ɯ}c('̞Z:jjJK"կ#iMov]P2(1h g= ӮeQrYd/BBD*͞\L[ fox(/0 E\E(- OtRP-Xk^,@oH 2)!T#,~DR88`FnǨĪk/ݴp "/bK6 tCZQ&1*J˷ .-M_PFIUiBkDjay1'M#q帍΄.|4"OG L1CP11hQbֈlptޢzq.@!/dGzMd*"X Qc>Reekq7 *el> 9'.(lM/iP.cDZD2j1  vqr!O |Foڏ2V#;2$$o%SҠVraHˊK(AQz2<~8^HœD)ժ2n*)!R+/`N|`0" Dkk8n-=#gs(.!E^2&r&GR48"P0ۑ0 `:*UP)@,2-/-탶@!} =QlВ%55h6#ybs9Fr.Y%ۀOCB6.-U"1IExa' Ԡ4 yT*B=8LyRL1: RM[5&ޔȴ-V봑NBO4AsHS PQSolE5I#uR.JOz5tA48EA!TT'JT-0 C5UwUEt܀)H4nEN7@WĤ8NS XsXX PKCmQuZ!-]'(U[c5SSAuTӕu UCU1^`nDT-.?_4uN/ K!aiFzY b &Pl/$ZݮcD(@7ud]@]U]5f=tfInv*)ՇnV4=h!?i Upqjq[[\;u\)lOlw9ߵ{|= WcVاW$z37Fo>@/F bp7$IqBh7JSt0Ls7WX6mi|Jl d)#Tt)L43Bnu-ZfO+ P+ ig0 `w4Bjg0V(Wc;6ېq$q$7d-r91wlW9s> axUL{@phl}[w}ݐjZ~IM@ a~wu&cMRsĀk #3K"&e/d(I8{"<.62WnY,PWUFͯ+iW>Ahv'؏x:X{se;;K2Po{}O<*00,}'wz]\*&ܭeA5|{$F3hTTWs;E8e1mױ\2 oBh]zȝ#ȋ\ץZ!H~^eJ){96T^4IR{ 7gd}ʼlnظurͣ'<#)m25xcП4]׉h;}|ś'}9jVCsu_@nÜ2gY\I]O *1=`B:!kX:.w#4҄k"٥ &گSEW7n{w_\Y jI]sݏa]9׍X߇࿂~],VO~Dt=<<+`XMO]C\},]o}j ^뽺݀$ f.9{sKuڧWu7]+\ࡾ? FP:6^mY+,;u-.+9 aJ9գC_گbj֟ٿ^Ngu?=ڗ[ݦ{JFP:kװ {1ĉ'"1F]^2$HT$KEkJ[XMJ̙x!g<{!臝rrxf:} 5ԩR`AwPW?}={D l۶}7.to@ޟxl b=PŌ/ur [v#…::čK'%2uǎVʅR,.aBy^zhNJR=Vx3\qByNޑ'V EYܐ)udUQe<RNYeBt0uiF_amVd zYR$kT5Pwzu't%[pzh.(O"P)PDe4BK.RFC~6jEo Q~ +2mPyފL5T,ӟeI֢aznl_jZFKCɫ.0|n126_#/0GT/rK-B%I50F #l>,gV^ƢiNcXcFF UiC.'/8#N\s4Z\ 1@3е}ttRoJ=5VsFiә:X,Ecw:_=ЀRmGd +|7E@C}4 nx80~C#31~0BnM>\R^zk :; hd w ]ȳtGf)ЭZ=*Yְ io35qPq3!̗>h«Yq(L7:DA]D?n~Ir"ʁUD]|1| [O{ޓ(02j?(76.o a<# Gĕ2,$4'H=8#OPs99"[E(y:pڋJ\quwEz1acU Bp_ o3_A/q4\aAҐ{a `Hcۄ$y@URYe&Ax&Vi'2|JBV2Z,[EƖEex9-4/70t\CfШGHjJs(iI9fx 'vdNLcr QΓ]=Oڅ ]9j:{@:#-,)hM|W8{<=A֏zlm+GGZjՐ4RڌH42j:ٯ6MrFQ(>TJKviVX(Qdj +, ϐ_}#d<걵f=Zo{[ir+JOեJ')M46+; 0K YtKILCRʙD^[ 9j[=cŭXҵ pVN:,*ʲŮR7] h̑z)\e )dey<{!j[xxkK|!į9y`(0S Fr3Je/{Վ!eu |F|(| xGxxЧlҷ&M#QDFT7m}WDySzea~,qa=~9 *K) mFߐŔ{tQggF|@HExxɷxG'h7Ixm8?2eAgu O 0 hQ(G(X~~iR(%O2L0TVv_@CX|y&lyNxNPG$:TXYW$Y(\؅#X~"Dch9`*)`6E07 AunԴG8|xg|)ńK77!'?EFT}DE"H!: F~s@/r4;u1h)fU L_UHe8kvtЄHV:]G(,8[qvKxE3xtwt'QkF'x/Yx | (2qtWDQ$"yyi+)Bq 0&# #~xnaPrΰj'y{6/p6`I7 ǓuWAv:EibDNXA5II{!SJ(;4X6p2_I .7QGh*s-9kypysC9 ;Yxzy|ٗNݷFdJiyƂ,G%}zX4I sofo}XВ9xvPpͧutHAYdw\Xudy%1xgrXeU2=%uXRk,iE܉?xɚu3z2u4%uq'I];џ!t1(.63 ʠ ZZڝIw! vIm&(z*:~ _P)8é;xA樊46@JwB{,JH>9'\6lX$%IYĥ ~t`@Iڟ臩[p 2 b%sJՙz:*C1w!Y :($0j)I zچea꛲m? $؍A0 ` jCgRZ[BG}JСh:*'[jȚZ}i,pӚ&lUp WV᪪wʝ>Qڤ :y`t1KHfIF٢Xfh+g,=XW;=#%pbڪG0 k%PpgdI;R"X`h0w'ƢgYT'qWF?K(TQ*A w4HnQs뇁Wzeg๏}Ũs[z+VӐFr['m\6Tc~2Zx pn'9_GMfiV뱴>5ٵ%ۚaW d2;l۶(K^J1kY¹JihA`VE Q8w@z[g9lk[6y+XH k +w[뺯JA0[ %ԻbLrF׿ ǝG\d \-<5 )X^d>БKlxdR* /0,&U7d9<- %a'"vHQ~J67J>@ȄD%|ŠZ\HK?x`l;'eõ gƆ-!'uܯ̖ǚ`,WTȆL"Kpſ{{sgk]“LɤikIe8:ßܲ\Du,uTbRI\SU˽lɒLżbd${͜Ɲ ,͢7enpʛYD0 γEVD?PSP m< \2|5<^pe] =;'=-Iڶ ` ʰR]@?SKK^ Cʿ$mһhxҝL9! 6dbw, IOiwmG]= QU|M $]]QH+ 7/] ""l]s0:mwy'7޼h]ùLȂث`zTվhkЃƜg@598}.b!ٞ>-3C,NM3Z˃R=GUU{{9p}E[c{ :mqӍ׭ p0`%001)B+9:pޜ 3p}vcuŔh|*Q=LkqilR[νٜn Er]ڬK\fvgΈ "[$n&1kb|5~Qヺ*z;wF^$ nĈm$!$5é9`#,[a^yqQqycfv(emiK?0-AÑq?}|~e``>iθc8'^GsfR4t5R鞎ҡNdߘ=^r0*Ñβndibއ^z3NPBc8ໃ#8>8stpWtu ̫v[P=#?kr<hD=׆jlj© .ؓfpBDB7HdRPRqH u$O,iM'K |`WzvQ\#8>DŜȋ;юRQ۾ U_/SJa/fGikrq/V)W5*4UBUV]^[ٳHE,ڱν g\uVwZc>5@`QBb-]zbCV(E+'qJ0MDYhǧgy3Ο={z(RkK}Mmh׌_Uṛ +]thBCzر֨ڳjY#kW=y770aTzt ?w("8Jh B 4+ M4 Tɵ`&:駟С Dp͚jE FFFv銱G7;H2򻺚w@;@a@fHj) =ıJ&cFATRG&yxIUU]tFe8lb\˲]D:8‰ lx`.ä+P,VmӣrT_~mR4DxH$gkA|oal&eB8VeX2~ər2Ɣ1%\B_8aT؀Ѕ/ eІ7a `j=ma0!pMeE) W\ E.PLT gDcոF6Za oB8G:sG=я9HBRATB ! ,^0p(\a"b 3jB% 8bƎD8:FKjӶŋ׭us[@F[l/ʴӧAٱ*TiXYݺThѣ-u)ְfrlӞpa]6ܴq֬3fbaJB \HaÃ-ZHy3k#*ǘIU-Ըkheˎ鮶۸s+z]Wͯȓ޻W&;Iv[ݽyۻao:hSݻw:q5'OԀb|B1CdQFfG@W'FVTS2ԄUwԌTd'J0~tj-BJR*k/U EN#[koWuk1ӣ-yLEk\Yŗ_Y@ lV,ll C{^!%!r 'զ",fISF*𷯚k733R[$>s6ְ̲5.kCꐪ՗a&Bd&ek!"_Q2RMs5߀sys]w@22.1"c M\砃[2{72v~w-Ae .h0]݂`l (0AىfڞHZ,y1}U_^v88r=[F j‹EcY9pf䵖.w[+S0euhNIJ6Y-뺗&H#):C$ÞQ+b掘Dωt]h4*veZ)WR6sPFuE*tw9O!HB 9HP 1E'wHM<#?)BCd%sL 4l]<R**`+Fe⟱T -k?{E,.;Iw B`̌r>e6󙜘-D˖C`2nMf㜒,zdKwf͞R=Jԡ򓕰QW2HWS(CSˇr0Ú(EAbTPf!%"a hխJYz<{X>ru6S5(*/tX&㰦\d{z7H5 Tu(լ2WVMW(h@j^J*moF~ahW!vqئ9+X[ΰ?u2&e+r4d%{P#D2A^1pC,:ZFUe23Dc8k#gH_KԷL16-nA²Ȱ7 Wxx|buP kwG\V;v"|# _CTBtVzliki$j`;_5(\aC`.為Z1si4+7"&3Fl|<:~/9P&aB&2"_b$9ɧd2 `8Ei/gg>l]+2l3ɽ=f Jc}ܦ}q@Z@84 @'>9ʽŴ1}N[ QkRZ^;Lo^y؍5@}*e1 ՊDv b%bRC?C怡I*a mD':A,nGq>7STvݬw9T2* +CxtOq1p&y N/BuF xl! ԂW Ml'BL_x9|sxzrM6 ve09Lԟٟ:܉zz@{ 8Z<3܌3\7PV|Ƈ|3Wx\|ϗa]Eb H]7}_g-~7G~7,r~|47B7P5I0V ' RP{gvfWNFwD||hąY?Un'fs6΅t\HJTaCshrWU&8~(l’p&t!hu3q Yfg_IxKrPr2:?bxW(oZ\9],: X9ag ew3ާPmgo&G귇_77pԃ?vJSrCwP|wPfnOH4@ha\]t7J x6HʨDu (Hlu؏,^0|x,*@&2~LڦDw:dptҠ}W(nP`aߨ G:w\ )93iC[skG\c!`*u&(01GwH|Hh%Ph5!c5MpoJVRT XP` "`$yC:) ߈t-)KȗX4yfWH]u}w3pMY(P(GQuNI08Z "ו_)w g jl)eՈeqiC& (yȅXL@uXg`( ؘUi%Gi.љd%<'vRٕ E ` ` ǰٖIa)W$|Υiڥ1Ix U4\dqt9p)Y0ٔ  c52̴8:%[oj`Y>` `$c0Wgn]oƠG*Z\7bW  ik#( m%u޹cɢ{ p~ccŐ#ml MieC Ǡ @ b f٤NwRiVU t߸a`ffJT`jXu9sեiס>0Ax䗧zdc* Z2VIBrn9zRH`3 wMN` j8תg UID+\cp`EŠ:Ji@V(F}ht{D _FVDVԤH9R3 0 bA=3 w{{|+XU=  iE86Wt]b;I(?Y3F g'˧dgBJꭃx!ey/; ?r@TBWIkK۴NQS2\xIV8Fd$`KJ79+3M ԹY!pr; {D 45E6 /X @W8Cɚ̾V\1W{OkX֕LLʧdyk?Y o KIqE pPl%#lhv\j65*~| p ͚ z 0ɥc f ě\jG#<(E5X˵\Zʧ 9l Kb,d }:晼\ah,h3[7?0MY ֜4]4 4 Wg7'5\<~>Nheɭ*at e}pW: % ]Ѻ PrZ 7k]VZDMQ.E`϶k:]nfM]aX dn&5-[ sNMulܺl;B# _Y< =荞IPDNM8@JĴE~NXd7 p+ɕ8qdκw6HOTc?ñP ‡(`!C%N81"$8p @!E(UȑCdΤ M*1$I(EI :t@\2YĪYZWsĎkYdѢږدq[]֮)S&̯.rU8W1&f¼ONVXd| 90/ǑA&][Ă;vI?hY`…uW[a9Y۸(KȱH1iN`ɝ4q#EԆ R]R%Z:lٺi7˵sʫ@k4K^#pBa@1&K 30ӌ4H4KM5\M6D2t7#*!:j ঃ>@jtډ$(P ΛʅҪY"F>k- k?5Lsζ0[rE1 k P,+41\u4[{-j{dl(B@Pkz<΀xvjDnɛ̩8HAH"a.0i4k::Na`P? A)3Ce{^|TEJ[ R4uPEUTSQQU[}${8L©Ie;"J."%iy3o[6]뜴EpT&>CL] kA.C;/{iF1\lYH > 4`8vV|JЮb3f`UP5XM2Yjiof{/ .3AI#CѪKzZ޺h{SoH5?j5U 9 $b \]FŻK ||k -^l1>91 PM_CiM<`\:@v؈p'7*V<5I'޾üEpc(>>g:Nj=QMBg4 hN?$2EX@L&lUv`@,]PG51 ͫxYosdaVC,\p ~c !!:i|Zi.-,u$p"-Ф7R,fQ]`[Ygd`ѨBq WG1! d!BPIħF5dP-l$'c*P.L2@IdD#NgF5)y6-h N- dxz/}dN|\1L QC>gA*sfIilc L](NwRvM0ʻsR{bIjB7p{mR GQB 4>/KH*i~UK2M[Ć8e:Sٙ)'Vel p ePTD4N}*}b(̡ڙ9I'6]"G+9$\kk][q\]xn`P Z#svڀ,w< `1Vy@pP3,5>ֻ kw% 7/Fd>tI|m}5*}ŵ ީvTo'\-@_3ϥ#9IhRH˩*z*܊T{" 0xdhA?BQH};HcK!J393a/د|J"l W2IIJTp%Z ' b.e3:f8[ - p,(Kھ͸ƅ- xB#mk}\xB&qJ"c 4}J'UK%ЈK`q`99sUB$o_y-t>Y>-'-_2zC0V4j>ڵ)SB1fTmiN{,Ld2j=%aIXL&&$54x}68!^*{.n,0{vW }kiW1Ic5wMiNs|ހ5n#P&5_ [;<~7b'xn[aX4^DZDs+~g+DTمrWx]q$[ĸ/yP$<$ 08 $<>Ѕ]Gd,opw>< 1dץq6%f8#j'nY|N4l #ϓ YZg?uޜyo &>uQӀ˂Ӻ`:8c 3޳8zj0okDhD:``j>ı竜r> Ӿks+3?5K?Na9A@/BB4:wj; T"Rɲ&!2#2./L,=;÷4;QOHr i8VIHD;0'聩x{18DhP  >zHL9>K)2 d!Gй%{ —QU /AW$3,y먀 L@[-[K@\k@;*w@Chk$*p1h1b uH9i<(A}[њDM0!Ĉ9&G?x3;I7$Ic3u(s8LuhO̚ ;[?L i&CMb&R-(ETRjR =XҺ㾵\(^}(Y`Ja֊0' [,ȑdUVg LV01َ/'p)W*()(϶G-x%I9!#*Xmd|b0R劅EUUUjMb@4M-R/X2 UY-Y֒%p2 E0:ل4=˃Eٍَ币)ݢ)8bW12,c&h,G =մ]4$϶R@4=AĨj1(U^u<=Y-']h\?fӓSX,\+\Y+5Zu$0o66V 9T]x[XV^ )^Vߨ=mYaV1dU<_J  0i7nT@&fm-oVlTuv0vֽU@1pm*Y?e3.pa8pF`e dH p ;f *7P d &f攖a@^qYƆlXoɈit.UlT6d 0򩖐 UWp>ݞc:hs _G}U9/sδLq#>iXl^F׭X  wb@©Kh*owoQl ^TuYoO]*I %v%nvjӦrOkx37z|4)RSh{wHt$rk֒$X b2uV[J3P#"G4A$(J| f6`A(<8p @BJČaCˏ-~h*HZr+؛K,{34n$B ܸJ RYL&М@.l0bh֠1n|-ZƒK`cċMvsBb*< j퐖ĉ/*ܺw|ɡi fC <#PHTU^%ɝF E\pU,ؙg ́k&(в҉0€FgzF+7h&Q :.! o4R)tGJVS L1imK`c >5x1A(#V?mn(":o{)2kBRn'%iwdCIvY[rWAݪ`hØ0f Lv2}oZe/\r>v._S$'H8e@P`k>Q׾0y 3i M5MYѨ"2%$Qa_&5$UY9RT֩8 (*)3@X;= Dg*S!TOPdb)EXKE4 \ ג:S R4,mi6_ tg2)4GΡ RMOʒ+j)gݳ_OuXr5 S=VIਭvի촠hwGngDhQd#А+p=Z#mP^A6gxn Ko*V8)îLJ*.6;U@bSE, Ur;3m]\mmيy%pG3h ܸҕIi O/K(GcAvҷ,y$&L- 2)Syd0mHEVbٚe&]^Z lX ^ GȅfK\ ?pF(VXD uêW_K7Tؠe|lw˭ooqxѻU|yp4QBY gl܄t]` 0 <4x:ۙ*8䊰kQqbkeyo6')Ђ~U vX0pb"b\(Ɓ;d2a Mo gZԣWM# SZ!a5%}+[*/uoR9`T豑~%&z(0Dp>['r5E!.nqZkˍ\!}=fBV3AM{W[ * mbTH9d#^%`6C:O.mh=÷iB22CC 28f)TX/j}z/km:P_q[Iξƍ* y#'w{'7r\13^,l1.OGur萏ƥ\G%~M$O0'K:`Bwweb@L e؀_@9|2%vhpXiej7'lHmQ O`_SiU>crrq*^ % %t6  cǝxB7lgaTC0h%'<$y'hN&g7 |glƿ@=i (@w gr_eb*f" Tg "HD29L6 C-\B#g=dN4JbKm[y [k& *:-h-,ҩ]ҁ]&?8g~4"bgVBшFjoEdWnzj⚂Y|Q@䑀`ꩼ)vWnR|_$/bPv] @d:.#F`dC5 C0!viɧqf游i4p:^a )њ* N YJ[ (^/$-B'agad5T* Zl[Y뵆&ݝvk.tψk| Pk)%[Ke (  b "& B\.,6Y5th5Є(Vrl^jS6l(,"F, *+ڬ J*}:'&[$(,TY%oVNCWL'H%nPS.ˈp,,X'(+(O"*˺J(WIwZ+fCT1"g-`% sBDHe"7:b<"D6f+B%\Ӟ nFbq rX֜J~W+mWqg޾q///@0A07lg>/5T4Bx^22-8-b5W5"6/t$,x7*0l3Qs ײ~]esPw=bkʬ@A0ۑX,g>/J'え^453޲L˙[HK'tI4Ja9l;~5R\ ttנۢ!F^!PQt  !XT4ԪB#6lVKO.`ƷEFou4bII3[9@5Lj&QdXJ]_`nq,daU.,Pqbbt@]AdA'T1ՊA1s44}5i3i jjLJt'vmٍno _`G" -3wssbtse!X'؂4PD̂<8G"zېX_7Tk_k-}oQ7]Yro/Kq \Sl/;tcSwu\wS#JD܂#vδ6~njo0xJu~s]ΖOyF27+LW$v SyaPw4uc["vkyWh\jpZcXˏOnH$q' X+^Pv݄+wt;v?a,'- B|ځݝTk$fֹ|9p잘osRW+79?J;88H \:;0 ~tǃz[񊉹Kk~jVc;z|cj¬;f"+GoQKT!;N  % \S\ U {;Ղc)Ɓbg/˷<ۋ#͟tb͏ZJ)xzlrl:!{w. ۠ gDej昺摣WadۗFatŽܓ*LBtXK 3:M =퀅p8@ŴVE@C~K> \!2=s'4ɫzSڗo>׺Q~'*辠FĘ>D4p &\%TT#E'.PdHIXDI%QxfL11XaNI,YhcKrĩQNh+& ZVѢY* lXbr+VW]^m .\sҕ%n^q; pZTMJ"@bʼn <0bĖ-#G<!ƒ yqhы .pAj?4d}2bDk1r ! ..gDkt<F&]*SfUkV^5/yaiW] x`“| r,7.ӌ3@ALCm5HR6,MBRhD\i^*&n:.xzHBĠ8a**Nh=+΋F=r/.I ZlQ U 4lT4PdH k K@$3$NF8b:6c m:NdZKlS! Xf1ƈ#K;Wh.:ɣ_2Z:>k>ր ݼ-J|Sm֛ユ;zp4 c8w& -ǜ}7XV>[,{̀Ȯ1DBt7 3hRl9,@򖷼 +p5"7GT(w! /J+_Ԥu.}|Zd5IL_Za%w߲j Ks3`mf Tbt2NG 3ӸOip4B0i_j)c^5~~S&Ի ݑ>aH<"wA^X Ȁ#JPŊX ^&9 F̕Psr5c:@bN5 ݆rSh ?_n@}βe W'FZ*dJ咙 c'ˁf%JS !cbe+] 1edhC;nHeA~;aI s孌ЫQp١cI%%d779ƹSE+Brr"<Ș vXjjHpg{Ј@H~ 3s @5QAHBp+T Q2 cGhE=$Lf'2iIIPc)^v X`ӛn#)oB@5C}@QJ*<4EybhWVfRYK֑~¤n5Dr*-r`u]Ks<`TpyO7!OUGre!5'e:x x-*iK{ZJu)YUڧm%l)WUvi~%kt($IF"b5Uj]b[=ZwIь״bToYWj5gUʰ5|\ s,P4 dAJfs=$2yuriedhwJc2bjFTe!Kcp&GIL6?`'#pYhk1Wu O-Ѽui3f4w6VX; V9ȷݴkh* =lƀŕxE]fRnAu{f pJpk\7ZNAW@l-#j^lgkfk)B`=Laho^=sǘ4Vnw{~B2{npw -7<Ȁ2-Gy*.^h89UND>rZY$76]QnӉ尣SjyuJܽ~U~ DA-\,Ş$NT?ΎI^vQMy]\`!J&nġ @d&<;M, ougoK%5 Wu͛Zyg7^zidu=ž.t0<xa/<^նNPpKbZJ4FXEX/9^r gj0S,e#->PPbTI'bX"!8 r8^]#O4&k1coo.\@Il-r*`PZb"B ; E vZC߬&0x8T5n ObPb|o mbJ1$po2'9 "tp$厅q&_8l aoNp &ņ2 q.̚H(hVZ/^PGxAė1F\hde18fQP2 BTtTQOFb2f!"EcbwDDԆre :aؐ"+r-,%N#9d!rpJ~lgEklm?J6hJ%$o'{'F34Qb{"OЫN<:ްt2bb=<>q$mR2X\Z &{~&&)D.&9i@ KDnR0 LªĂH4S/2><X0~D3N5 !nX{1l-1fnKZP '*N!y .";.9:̦j ;ó,S<dz<͓r=C= S!Db>s6c klq$j%ZH$H@@.A!0.tB'th44| 0R,",Ҽr#Dɓs4fVmq LyxyW6ڭ.@g2O׌"+87}[}qXy!m,w y3ǀۄ8UW_dw,x9l-\.~$|FKy^"cSsyutcx~}pmx"i׵wYe+0SXHF\\BP+P T Q"13H7y!>UF7}mԛ3elԉ7? /!աwݱP\t`.9 ߻7={= z~~[9z>+G#>m@vu o谞7,1ݧ_^tCXe~+R7,7bg~ا_5YlZ 3>1i ڶԹ~ 7䁧u Ym~J?}i]gAsyaWw[Wc*p8Bn1D"8!̙>pa‰CfNB{D cP6xeފkJ:BL ERH%2whi1;敭#Cɘ<~8G̠Jۄ]+B}őQS](F,s2MZ8_j=rnGVriNxthPAg n7$8e 8Ggҏ ' 9!V9'l,˚x)_k;%Tjj"/Y<}9L[89(8ِ9 9E8o*:AwK#~c <=:( ~_x4ui\d h3&p3.Ac#$,]p&L8@ >h)#Tx:"eW'>W#0ؖ6P_zD q1:⒒.ωl%8^)I7%UFP(@ DA4pQL<s_ ɰ<$E?"͒dC]lɫΉtKAXl2e-0Nv#2cp023BHլ5INFH7NO])yJt,ZZf'?} E阅ЙCяY3vqEidI8)΃toAe:YUL%r<{Gtz.o&ti>GkED%Qr*S%Uvt]HgȤ^9pLӪֵq ?2Ř:2gA ˈ. k"*Q lL]]FͱM|"[˛,9e̢kd+}_JiMѵ9Z<6!B9+~ lRz)h4oJBZ3@3wsK,L) :H%BsN]y]* {˝npvPU(dS xRr!A (zbep4Is7Әdž2QlŦ7#aWFwq Oȍq'X%_ȑh !ّ#ɑؑhMۧ)(.XZ9hhfAؐ@Xb Zjq&[Vh~R @"I#H&v!LCsmɈ n)ZC4jZE ECNP'!IБ 6\yhHhI$<+щHVd8x`yw&Gf!}Ӈjy﵎, Q)#-_V'a\feSG9҆!$RYAygGÛi阕q7ą.9ZXҜ9+?IIgm 5@E,p .VY頂깞ٞLMF9h3xe鉠؟im2Z[jX1iC V8MxY9s(Rن.!w%V0jnz[k5jk!1ܱ IFh *ZǤt,Z#Mr2c! z:Z֣ysXmmzLʨtyWV]1-gC& / 8ꥨZ1JęI<ب6ڜrZbqЩUCa ʬ `jҚҊCV1*&ѦF f:jE9QwBAPP b iJ] fVrts.HAjmep >`nڬZJ}Xd> Aؤ:wh,[HW%)Fu7uJ @ K" $ " +Hw Va7W@^psa,AKAKڛ: k{Zh/ǴQsDd%SvGSg3gQ e[1<2z8tr [$F3x {k=Ŵ[ 1[Y'렋>i5+#+z{h[:y3Ye.Ok;Gk^SZS }yy)K*븪1\~<fpKi/̻B~[L:y ۽PI [Vr;t9Q+SNH*O>(xӛT g̽f5kP2WXػ%dV ۜh1$l%S(, s"yYPI5\RՄh5CƆ-# ˾N[ﻤ Wh@LwW(04׌Bl5xp YЎ'Q<0 {LʴZ+bL"E33L;H9,Sttń\WpɯI}5QCvѯM  OPokҼ%|%o̥ 3U;3CMG^0CuQӸP {yM ~‘9C'H-؃M؃Gx w-ĐSIz!z @ դ'R UM#  % 9\b(='Zkx|pM??[IHJ }zݑ~ϝ 0Ӎ `׍ =Im  m ޵  ա-ڼa x_Ǹ8a wl:S t=8!w40Hw~ p &~)~F5 /N=ݵ0 ` 0 <= ;K  \2E>}؜`MKnw`N#P[Y4eE+I`mcoq.sBFQ}>ۿ Jp` N 2>p ~ .@]ـ]ܭ^CӀ S` }& .[."kϽQx.nƮʫL"pJ0   .Pޭ N꒝G(n.ŭ R`^@nBo /e.#O%o'o*(_c09Z79W<_??COE:I_MOA_GFI/=Lf\TTE! ,^`C0`XȰÇ!ƘHEBD9v,d’ɗ/c4ji֬[Ȉ+g6j@ hQvޱ[tiҧPߩ[իXi:wCÊ֔iԧL]ը۷D:m]dxEv-\ U^yJVMҦIwn=EviBLӒC3KS BsHwQ,Td˝?"qU /feʖyƝ}˟/?}wϿ*+fI5syK|;9KYAFYBZbƋҔb\ k@Vk;E--6OQJ@%LxIŷ4@15% b*HNZt >8*~x k=:gi8b3,kJcf]5X$!\@lv ~ }+TW7#^BM$Iڢ@(\92jjXO`BpXf>f)}[:?:!$;.v. 䟤hE\+?'d")'D%vhE*`ܠ9`c3 đ!yTxɔd‰[d /vc{ +"ЇVɉbJS4qqe! /b.2&߃ѴdЍlܜvbf2*g& =c2rv0xCHن*E*8KJN")dņIF08;A Y0"B>N|l +])KXZtF7z\Ʊ-qX<&2A'9ᒘFIMDC6XL8d΢Rd OvӝLZ$VWp|ނz+c"dRCjE2ML9;hEeG,"P"e( 'fd`.@טplҫ^Z|J @"NH"Px9'ܯSkx ZM 4Mf:۹xs(/EaXЈ~NJYbCA0G;t! e/i$+9i1DÚGའ: %$ٲe@x TkflvL9۹ΡUK$ n Ch%>Aw*4k<";vԺд9 ;77/4{Ԭ˄Y WZ 5ugk,`#е P^HbSYlcv;H KpH%&NBT'QUs둻hSҭnk\GH-oM'ǟ6Mp=CpwvʿY8p{HŽjL MD NpB`R"ogE3a dH lUyY\W'>Ћ.+&]&u7]g=ߠwuA!CY. +c`gv_CAmiK($A6ہ1|! AA;).Y }s5Mixғ FuUu{dV{^'eD$'ZA;#qeY`fc|!Ul%>&BxN>0*P5!)7ǀFՓh~̅ygy@71ioH-u$SoxE)^wc{/a瀧5(000f vx|J2fZ`!X*@&y7GH9x*0))`U0ha ?n=-?7h,N4DFt4iqcVtJoQSHVMjZjW5 Xk^3 P^6`A$r8w *p90}&XUTL9@)0~4x+~CWtV8i2jHFM-OYL33zHFL&3Td_?R 7(Ћ0q(kf_(G`?@UT֨Ѝ)W٠ @y-i鏅v9鉣I \^1 م xTɐY q雿O3$ʹI ٌ@Th;x'\X҈i~=gs깞]ʼnI0]X 3D *Yw|AD5_a9 |yٛv_$)hV˨fgL@ 2QaӁ((䙓*s=js@f4gGʄti#IT X^T\*^z5UeykJ|csj Zhâ-i.ǝ2 x!-%!(R5y62&E*cIZ! i(Y^lQ 9#ԡ^YQ)< %jțjPqP,Lf`W %!OzʢE: Q5x(sN~.J*oZxtʟU.O `}1^ʚ'kڐzijjְ,fA`#esȜΉlyJPhEPvP('EQ-v i1ۮ;!EYUc@ J `UѴ*Op@+%|eff`![ %WPJPKm%ĠOՐD;ivzu}n9KcEu:#Q{Pk pɹUۋXGw&lˤ@i` `; ± ['VKq g¥;N 8ȓ<[jö㒭'R B\7D|yH\ٚъ,fޠq@6N }~jiqۊ Le6_[{g雮@mP5|xw|Wr?D2tUDȓ͉V6 ddMGZ X۟>>j_hYHQ鮥eZJڮ^toe"xzHr`J!.΃gxWN9 ?>>:qŴmXx38sv>@, n?!O-`%+ p|"X>PżC {1gyCh~B_`GoK( W!ToV"e  >V @$ZPq>#@IN={wc ̅-(޵^|ʼnر/Ғ:_(oIUnn_/ _IƭϮoAƉ- &D A ,|`0AT1 Nc!Kll*N<,XjdY;9s'LkA%5eI%cZ.PsMUY:e5VĈ-d>"ckVaq֭ZjS"" &<@P,nc%P9Də%D PiԩQ(0@!dϦ-{`6H!D)I1f8"*OMGtIլ`b4LO=4 <Y04(B$T 7ܰCAKOtW \0@7 R(GRw.! '[NIb;sNlN)rR.˫̲03ՔvZMsN:oSI= ,(!#Db,2#Nn툍)X`Ւg,eJ&)eZnƏ(b}S8Z d)O ٳA tW P2v׭z4mD|Q|[; ">`6Pq%|,I|NZIKܷ> D([z_7IeVǿ-K<E^,9  fFwl/}pxa x 6*Mh$?K7)8 7xPdH@'\DLkeF $ 5!,PuPbE`Q\D]Ⱥ¸Cx;-ft<(LT46 `2BH9!uJeR S̝,{%}dek+M ˮ=n'1RP%&S5_]'ǦL>%K1 ?n*Sc%?˖Gzf i.YViOo^cmO$̪hުR"WA"RGqZ@'%I/i7>Ѩb>wDۧX;,/ vq/0_|Pr&K ua DYK(Xm͌UBȖ&!9AJj!~1Ab"l(0tsg[l1yiF'6e1R7"v)B$3i]2i eMu*YM'"*= H#xk \8+A @,:{1?'F6|a4'ӄ +K,=MYx,|##tլv5 (Z;j͵}=Dl9lvTymm6mq]2#ݳn@k.K0 vn2"jޕ_]">'|pT+ܾ6'k`a g58 7 [ێ>AJ*#ȁܼR'6=g4}Z%|J@;FoO^累wĀ3. MuT '\[w.RN(("{+XM~򽿰zmk $U$k;>'(?8td1ԭ49Zޢ\,X~j;E,QoYs{j=#53=wa8C{r#):ZۻY cn>þ(FЋ)cqM"xBŊB *ܴ?e@OB%.7{0ԺlD=c@߻(FCc -{;A_<& a%n* Hn{Dii8UH@*p$6H{G-XDN )RE8kGK&|)Ċ4W@[E1ڳ=廌4 F6~87:g-=;FبFޢq3i+pA%Ⱦr4G)w_;GF+ȬHd4(GP;HHz{EHD81 #2@F;2cF<ɫI)!0ޚ o cɴD ' PHJ{*YpJ&$EJ&JyD ]K‚kQ0$C2\K Ayl!Y˽LfL̺+SY`nlQ>osӈ-ͬ;HNȫdʔȁI(/"JhԜT$ORݼvڊ(TW @VRWEPXYE[1 Q] &Xb-ޚBe5gVi[S n 1JpqMr=WuäTvBye7z}8|<Γ؁REHh[LCgڑݑ>,cY-IbIiݻTY<o;L @ HY𮡋4Zxz PEHׄ#ڨZV,Z/ծZ]5ӱME)H둶*Cy&һ +mEFhQD-rGIѼ ɝץE$uR\ҨV-Z0݆}0Ue] NMߋ ݰQ]]]a J^╈Sɾ*YDGȟF4J&զ]4-E2RZUZe$_U0`ʑĘ15)6b` η++-ӂB0D^D,\N8\ ԙ8_w{OnUR{U U\_!6S-0;c,j5ڸ n@{kU^m^y,\XND[UZ7O\Uc:zY d\ND6dH+a|d](NLMnxd#;틂 p>VveXTUw=&^a&4f?M({?N#vdYKP 22%+^uCfkA1ӁV~e`̀Ue4^v1coIXEh.E @ CV _^h#f$Zfӎhx(h6.Nxd'0Vve(r\d.ia)&hT0D@&@j*DjK6\5 S 5%i]Qӈ(3#-n~rM|ͩkc4ΕH~ÖPD6,fTklJy;ֵQr!+5TY$™$Nd(!7W4=ڮmۆIm.mb nn(V`U3unW ~ـMn`6dk?7e68=Ck(lk^qSI W@kBp6>pfv'*V X q lHJ\4. Z Oq.prh"g#opNQr-& ^*X*OWE&2cok]K7qqoPn_iN>w?BP"`*on^ќXK_/MeiљiNosPs:~ =Ou\u;rNur&^VF Xm3GR3\ӫœ~:Kw7bOw?RWu:oir<<́;•EI+4(vJ*,,!']ЈQͱ2 ɁA /@ nݙ='C6N=䯺*94XչT ,5ț_Mp?#ٳq#8 HXO\S޹x&z^R,Q.Հ cɌ`s$j -}'eL,X2N2cL 0 `@Ly܅2E f^% 1!`UÐmZ",-1T#hX0!e1ݢn!)>ћIP|HMYIXbbPՍqtTƈ̪,a`2y-$,87+\3ꔔހD)ArZֵɝ+bD4%7kpJҀЬ@Yh9YBM_\J51cK\P!SDE9Ŕ)Y0+Ӫj)0{BWJx6I> o2CSxS.V*N5\1O=:3Q0TĖ(.qSoP;atSkCRFo8ՙDY]-rAbgZ19z"s&{ʞTL _EFB`erЃ $Xlم/*Y%AB Kyu(X:{v cE,m՚[=nDo{ WU=vpI [ p4`otB]%.δۻM/= 4U(p̨;ؔ)@[KKE鰐6u؟iPpm-{ 4YHs<s=;T4%QF\3;)J;=-+ pWt&=*jy n!cWO!@HB-al({ nMqjtUEI]lg?Pf0$e4 H:]ϨN}npqUB7xی l{G WoN vڈ ?vm[DJvf,.ifF q@rJީq[+f]n*u9Djnƿ<@PBp'τhxOX֣)K2bymveko IdGܺLDʻ?d6`@V /KH.IW,*|ᐏ|ByZ]_(ycRJ 5cF˴T%"ƽ9;MUA5-_A_MUn}ŁMZ]Z}cx_i]eƑT`_`ZcH"`A- 1p8=I9e[I֍\d]er UKMa ̵ Ibыcx@ PtV"L%I8 asH*(@8a VJzL~Z]aO|Mu("PY_TIUbd*fYA%?a)`t X%ab"_!cT%M2mj!XN%w&bGBv1&nZRs>W5& )J$|h d>s$;-_ngWE:E]xyg]P'gYK ayI`q&q-'shs:g !HCB.P(ibtH_j(gk gĦ:Ie|(DM:e`u Y# VĀ iFeI6;c?=(iM~T,)6<)M[$ ╮gB #ly$SiqzؚC@ ĀzfH@ iXҐD)X.w*Z)i:$u ݌DdF&Zju)f$m42I 鋎+zY+q*<Dfe@)$vFD?PPBin(&^>*:I%6&WVaKy46'T`amdjEfGJ'BS/s0YYjү3ԺZcd2o {,N`* .plqN~>dkPmaqhSEY qǴJmz^l6n :;en1701$2 P@!@l ";.dEYU. o0q(Si0*Tq +P-7Q2r,'P#Zhqm1Z򒉱Ē&12$Z-gfl',qQNI OK3tDkLq0[]WĐ]GJAZ ^t 4++CE`cڊ*B`Fs>:{TG|HWI+r 4#vNNwBқ> HN7gRE7=5T3H5dpUVw5eXhB" J'=[vN+T0]]}_SٖN4alv-;u:@Gg pHcu0+e30tBfOH+$$gۍ[4jS8a}k Pv*"ܶ_ vo , pqkDr'd7ws?w֖XiVgv-B+|uho@ V@i6k|az6{nԇ,̢i*nSTh88w!Tq{DHB< xT x,0kM8eU9Gē_mzw+˺R(*io82UG18sɱ's2ΖO7[4 {DwO9>#\_l9xtD$ C y97)v~kqg lGlE0낮:020z{Sq if3*\w DʥTӰ5WK]:afSkzQjɔ:E 7M$ƺ TGLUH: ;{; :Wy.4{¼u?9wwvM'b m|oz(2$v$"@zW@׮})G.VϲkuQ>zdxiC#yL~v:0êϲo8cs7/}Wk vїU 3~?Gۜ: <i[gG#&a8ڃQi>>;.A/c羮7<.  a_6tȐ`mU5h#?,A%'Qn$28` @N;ysc60h3?aeTQUZVV Wɖ*mڴ_rHs8ȐE"VĉӨS'Z !0`7vXqdƺ||avtITa)Z#L$]ޠemfY gÉ PMF*[Y+ՂutQv_[Cq@Po-[ L+! %c~\0b So8 14c?ۥ4N[Zm`*墌z(h6@94-T\+斂Jk:ȱP+l*¯E *b,/L@,S$d(l 6lGOfӉB2䘓D8h$K11I/ ,|CHrc2Nr%2RX!{,hl&0[K ;c,?7߼uˆ*0COQ$n9B9&G"i$3KӔǩRBI - 5 LmPr`tUք%+[oFyW^ c42!gyah"gNнݲ tPἝp=*)r t%Ի7!±ݭTPԽTU`0>ᄍ=J8Clh"ۜˮZ1kH!Csn[KfB4)6 \ZMiEEƟս%~.,2< Lj6kR1aJs1x}5kk^b̻νw{AqsNjp-j7$_製s|]I+0UW0ds؛b6'v[8±=lsvZQ! ɞ-;#~CTD:FVX4`qHYU;@Z׾˭%s׹ǁ <.IA)P>ɚRC< ~ Xo0KFA z(aHF(bog3Q! .23t䮇Cxu~/\QtV*Ǹ2qqPGpANRi(o27[z3>WYRGrq\٥iʲmf͊`^WI"{C}:*zu"еVf^4-n mM&PFѤEiel&ģX/.Vhfũ*`eDaP冰Pm}c% o da$(@5HnF)̦ծ 0 '0040&eq,"k9LoL4 f0fè t_Hmyib. p< '60*Zg Щ*T8g9f cpUZ&*dMnaޭXlO){1 o6`p:>oB.h8c9 TApP%vcV,Yn\Dj rxzV1vͷ炂 # "[ RQ&2K$Y%Me121Q΂` 8ʎr퍌ޭ R 2ҢzD"-ůj9Rg f"}$$Q:mahsҷ'9x¡F q,P0**ì##) :`+$o;,_r-Hq..跖'(r!(/1$f&:ђ1?Q*#ڪk/+=)AgnhB>P. `jWrJޒQ35U'.hγ6k79`CS77S8qbBӴko2A> *SMO䮓jxo%' S<ݒπk5Rd6Qr>u2>/)3kF SQp,,Aͫ%d D.4C7t<@Ԯn6ODs(S/E3R 7{S*hl4@q85"'B9vԡg3H A;HS4I]L4(*=C;JJ-JSEwbJ7Laq2@iM1N(O> 4++O sA!f*;/tnCoRd i(7s:/TݰTTT?1*Nh7BIV1 }PE:;UXKSKaJp՞MStE3)-%U[r[-ĵU\]WbV꺃@P/4X_uuB;4[eT0%<^D, yV\566"dEBtp^wGX6Ja6fg %LmVL9wVTwa{S? ) M/200*֟ %bjsdbWSvXu^U.slYsf6Z SRr42V[ 8Ӓ8539w6)veR-^p5"jqq6W k\Q9&mll3mgBRJs?DtdbFo[\\7]4V;lC) irv| w&^udvBwxxQWX4ɷ"mz{H[tZMo`jQz]b}~-{צS%/a8`8bB怫B3SQs5>C sTSP{U{c"ڶ|uiB6>xRlׄ0~}~~urMD͆<􌗬X/˖˸r77Mȏx\F@T'"ՐVIQM6 z5",ɅX^M/7yQyK!EX]c]F&u}& ry;B 7s`.9nKYGyAkMY!!x iaagE$] Yu9}kCDhņtvʍCE'Ѹ@! K@#G⏄9#9~ux es69q3: [s ɡođxjsj( &C9ZS nV$N:#RYϧ7eW6kAľ'w@Se}z( 8zq}:~oJ]x 5(oe ezzփQڮ֡ZAFJ,;IpyfO !\;v,:*ۭ3Ni]u$CE*H%9a,v7-pnWc¶;[xgJ`S;>[Z۸{>@gZ,窳^an!{ȂD\ө!Z Z69ە{o{{ۗaT u:dkM|!YΛ -;E6ܤ?OԾě۹o I wc|i|;7(/`—yy3\ȷqɛnpoܥܪ|ŭ ų;ƹ\B|Ƽ0AAXۮΡ#<w3Mۄ%o}ϩd|o=YB%w^ҥL4*囈v}!Ytkea>X |Z^Cj9B 4 *|1ĉ5X1FLH$K$2ʕ+Mx 34ḱF U"V bѢ+*,RaE5jhJY5_\ kа`>6Zں] 7֪=W^iӤ  p& 2A$H+K܈sGJ8IYz3mi#H8n-tN{?ZU֮Y%;,ڳq}\-ݺw7`Ç+H;r9043hэKOyi֮_DtbWoIEUR=xmA3!\E!4Fgtub5`uxᝧF ^GB}g_~1Z‹S V"VarRu8]5 Zv%ngXލG"Y@hA{27A娣<⧚~M8xtB /0[A`bI WiI*\j8TcNcU^|݉j+f@6pѝ# ){.l $4 袌6:&ui.`4hYinbnmjbZN#fg"w"Bh뱕 ;k^5pE;m7X lҒ /x1Km"ZY.<# S[&m$E T4)N{Dh>-ڨŵe.wR S8.(W.[]9+4ͅb@]A,>'ҎtOOZk7µ_[ kTY.=s9/,wuKhs~xyEk;4/Ly$YS /7Y 10gI͝env8=nk <7π-h!`eT<=o (j`1E\+a\w6]o{9bG3ez#  p+} h yD#c% VMbW Y`l Lpd|F5/o/lhox `<;\>9"HϞ@'"!: Xk`E0 zQ7aZ2xFG76ΰ(((XdsA,\DIat-l)MI/۩n|"k-m&\Wq)$" f0#xd* 1 MɦC%uӛg`(U:[?w֒wjcC2!9.C2g6I⊃ UCیDSPzi#+oCN"5=h˻' S4z'})Ё46;EOqCM/6tl7M.Ռe TPES-Hp4iB3W*#7U/%kYgjjA}r݆"]=9a*YN} vY'U`&V_@ДG`r",#+ &%i`o>)3~ӵ;Qc9CcqМEvg<}>w?N,&~x# {#*9MX/u' ۀLAxf#Bkzr*ΰ;tTN=vm\=>OO^#|}0K`}qs$1C]~'!F~wME6#`uFKt{!Wx3p"XhU=a5G  ](v9=}Uj]CMRBV؁xdf>"qqhWat҂/|dou%08hD%n0wH==LH QRHYzi.zt^Z`uc{wS؋&Fԅ~gChxQ>#R7Ewȍy'}fRmRpZzߠ x*ȈBjThkm3r>(bC4@A@EPkvmy!qGƱQJIvFtgnLjN.jbr580"v}I=BEt%I树؈F%qdVkX{+3^ a"*97kxpp#)B {LIgy`bY wFZi A[Ȃ0ɘ1Xc@x,j mlS=Yڅ H` 駚ӹ" / 'nQ_y1r&@5ib*ViRC$)zn7 mV.KBD)aĜ&t)iؙ} Ͱ%8xKBG!}NF9)mUsS9$AP f qZsAh!k:=b;2n w4|8Ah?x*7Xl1,nbi Ar4m(eze ?j ImmZw p Ȁ ɠz* !bC @J76J=:6J\⣪) `*+j 0 r X0Lj-x'Ut8,XVڥ_?|ghV(hHdNJy  `wz jZ.tӡڎØ2jJvfMrVWG[rx[8<E0u4 #ʕ0i g? >"JpFB 0|$ ڭJ:5K>z ?  P7v G g`p"; oQj(O h ;R eX,%z@p_Pɠ ڲ|ڧt[6Zx]R x*v! @ ݀; ۸G ? (yP8Y4eXF\k+_+뺯 K [` ;;KNjɫ˼kk+ۼ[UӫkZT@bT0T0?"! ,^`C0`XȰa‡#J|p!&(P C @d (ˌ!0IeL9v Q“O&QlIֱǤIFڴͶIJuۺmjZkn`Ê 뵬ԧhڬ-ZmʝKٮ[[UKX-õ'ne*a˘3k3#ǎ%C)RnIS֮_dcWr=,L >|[+ꮹНN]uyѳg'ήܲ;; ?j,_c',ٖeۿ0I5VP-$AC uQx$h`j(Ll/TO(2AŊc'rT54:\sT7_7tcqD#XGXzMדPޔNFPşxdL2"bJ"@(%`=Crtv}HN1WDq"2k9U騩VywX)|ye2QZؒORNE{X:XSʪ%|Kc*J(h@iDFFhi)"k5'"j;\QGQSx^armyXI*$ߪNC b [)ح+9,!(E-rtBӒfkh nK$CN:Kw$r ScqMӌPG5T/XۍM ּ; 4ĵ"uLjzoeʀ BUQIs:(N@C*ȘQc4K:T[oyX#oܿ~4^M_;NGI{ wSkL7Tw"{xʅ/~cw6>F.lQ,M JDAE ҒwN)JQԁ:9XE Z]2^l^; |g*lKa61m~ARy-hQ|,{!)$1gҧj}Y}d,P ~͑]F[Rf(C4)LcTwpĠ8Ax!2AT4!PBK$6E/x̡{C,ۃ2$Q/[\ED([ܷhsH+. 5B\:8q7^By#!5 ͅ)# vCIP% t|BLHհ2PlV'U;HB0ZNG%RAъڂI/S4&4@ʟkXSY3L6Im̩a%:J28gk! bXK;Oõ)C Js? +1+}@%<[⭶h\݊n$Hlh*])KART`3)fJ09=l5_uf:3+CJ5A@*&TiV+U|`': Zu*Uͯ.ـ+o@YFck[ߪ O8 G55 kbOI֥LXxݮx &",g)9φ-mNծ`j >VOe\n thFG?RS@D$p\'auo4^-]__;YɦW#z|σ|5+_>-H*[ xxh`,80;@" Y4%"`[=AbDϟ c_0]cc8ˣ0& G0 cLdRvn*}o|;_φQow[VYkK0f(sμ49fl( bG*@6|\@qB9Ђ.u mADx4nt Inx0* RݬLW6xcf2\Z~WO˰hتnJ_5A6oA~Wwa9TΉ - v@'mz޶(cwVǼ4a;lfSwN=;S:MWۂz k.Żyymt.|نqWN-Av %"YPTy91s.̯>/`! <~ElKoLg|TNVHz׿ZW=c'{͎'S=k03"?}" XP$y߹{ 9扢*_e|q@yU9>HNp+Gim dlMwD :¹Mw j5UCC":)&:+z@y8~>*kE  r}D;Ôڴ^ڪ_acig B&$ wϤ5L#% >U^``8U(FDUdh U(H@. P09]Ĉ[zljt]xT(Mh3i+t p ˬͺ3*PY24)U ;H(WIU01  zz BH7sRdzC-Un@7@tŚ%*U۱Z9`Z(6l r9䂋8XIx zxjHX8yN뛓wS<1Lh_p;G:+08$6XKuۊm9f*"+:۳7;Mkۛ[ceM;u[K_kKV!? :h:8PRUOyy(r@Pc01M\Akp9 Id i۽˵]+Du辁:X8$k(lova5KɆ U \ Pܑ3<fn%?ETJP;;[7[Q<{ A^t j_jIi|rmp PjtJ` _k\ wV?AW`0 TjHj86<ɫ n<\šۄhRSL^7g`]Qv8 |V4 VcКB$N$VE5nhG]!E"YI)Mb9˥KUBEJRM7hO-%Zѡ@,QrZjUY?l5WaC,%tҰԮe+&̝8xʘ881ĉŸj>41aCRgAKi(Yz9+L6sٓ,RܹVWʕW_y/? 4-[ɥǥ[.)tDcQFf*._rxbBsduڿr)M$^R30Zj6m¢cʷpíꃰPp²9֚.9+ ""K[Z)KA8#K#+("*ʌ)T)^kPk7v# 8;DDDJNϷ:=b DrDBD2;:E̠,r˔P5T)ŸH !ͥlϲsN:BvN<$DgOPԧB.E+N:o\NC*H! \PM"V,Vd5`;i)L\LHg+~cUv;CجhCk< QtСo#uI\U.A*P" | jI&{9H!} 0OBx;kasL^Q͏SZٍ9㴥5Q丨dB&k+V P"#DpTH!28zhNn9i jէnU`]Pκ$c[&s6}BM1d>8ۓwBAwx*pzy-Q8U!ÉrAr/73l}2-믿a}Ž\=l(ڢ):K šeyy۴<A%z:PF|.D2 !r b@F6@'%.?_GPg?]-۟P$bl)@>PBf40cuXނ 2`DTf}0- @! 2pGaQǾxsh8Uz1'Qo?Ő4;$0iV(w-mzc opApLt{6GWzZ 9DEB@'y` x@ѩ\OЛC?_e+Sߗ(~Hc'p =ۻ= P[&ڈq(Sc>Os([ [xꋿ H0 ؾ>02 |}GZl̤> 8J+sjJ ȫ8dDlTJm;dRZrIRB˴KKGE<F̉SA ŌJ=D|LLst=tR˰M"M EBMZQMGŘhM)B C $:MTH2K(ބL.D,=k<6L%GABdT%NǿNT ϻż Ltn0r$tL{Ѭ1P%0r R؏ `9d = }>GuMED|;A/+Q}C[Q χtOьV Q 5?KB|ʸNӡlR'}Rl$${ȾW /K3'R4N6ʡ 8m9mNLؓ '1R #[RAj# @à $:یT]UF58A$45%ԪHC1R8UM4G RP$R@R@mR(B}NB`%5$֏E]Vf;mszا։VZ8 >Hm10NA [x4\T /5W~ 'm :DآCZK5S kͮY'\Vް8%J  BJ'xD1uDRyxYXTM-KD}\[^ `  2\H3Zȵ`)$IܯԂ[3&Ս$We] uؽ[$ ZMV1ֈ${3m͢"l%1X}<%?^l,3#iհT КT[9.-EʅUQK X @m,V}D-0H80>I{Ҹ-_ٽR@lH+ #FcQaXEm\a\P&.Ղ H3 ._@_ب%fE|t )EDm0fuVJu@_El Id9kaVfUf6< p3 d U$D|]e`?> _ LJ~N6m"h"e6XeV0}Ŀ^{_(Z!Ya~b bcN_d e.bfg-S"To^ n i)r^\6 F!&bcň@{C&~ibWDc9hqk Hd<fi\>ia5Uۜx+5`L+( SdvaOEߩfh..sU ٯD[X- EF sk'9hdnT gkS"j^VMl mT޾N lJm`mYEUOYtmF;mܶC1nUPl_ncRǶW?5g\H.Gpq6Znn{6d~k)Ex흞a N(Ƈd9NNnVeCiܣ Pb/ g o pbp hfZ4q޾kV r>oE<30lh} (g;A6r>Om(S%& 7餠Vo-b̼L&sV h_Hv<53F1qz䗞p>&?(@t('/+ ,EF?f0 t3%:Ov6 Bip=Uo"nXG~(D^ b)Gov)w/Gehgw0S (u+Sqm0duoO{sq'ÈrEs`dG6m{ wq0%uF70VȲ Qxnyvw2 o]oBrtxfj00`gdm_%5}߀g0OakW#Ty{$"JM}ZWR qzxT/zJcxvﭟp5w5?ay !?噃{W'gt%ĵxu--|RFy} bw|NybpjG*yWnO"zVtk&5 Edy}h|y$4w'g2X( |m]s ,h` jpW%h"ƌ1N㫌FTe'lٲ %>Ьi憜4ur,j(Ҥ.A1oj*֬(r+,-k,Yjמ!ƒ k.<Rd+VnDRĿ 'JXǍ7s|$h$KL)L7W̹a~6p UkY/",h3GV-Zpҽ{{gL&Z~Wː뢜r9YGTReRkl[n :To<TwU! /4aY)gtqe]9_E`q6B{94Yy!_G iQ|EIvTTSA AՄ!nU͉oXQ t_d-V0* :J| Ib YK6iZ1'A@ eT]Gu&Xi6f̽Vӝh_mAX {6Z &*6zyVPBgPZY 5cBҧ&pVy P6[czsjኦspIŮB1G- L/z{ږl2պK /W $7DWPNj{e\_00bt[)jl;xy,ǹ`$L(ۋuCL̛ޜC%2yב?ROYST"8[ tCMpTY$kucPEaى8[60$Ok2$moS.Fw;ˆ ,; L‰H⫹vpi99 :=ORiA#|}>WUY]`lΎ4^-n@]v]ˈW- xiw-=[,:1' Q z&< ekiˍ"OcU>`>O} @5T%~IJSc?e h1ky++YT8[H/REA ^͛'("! GΆ.- u=aT+!-%@@>Aqї2y;M|q6;.vc0Eoy|%,E$D/r'P`GN " =HmXR )AƐM"ˇo+X!8Iͪ,:k$.DdL,Bb,BFi+-'4`)Wޭa"m758bӎTNyD K-fn DHٴ"9IJ|3<cNEF8@~ 09qʞ#}%tY hteݡB SE4 q|rk›|\kmmDWkP.A_WL //O&հ0v+&ƬuTgӱ%ez"ZѸuW 1ӄ ]`a ik)͵sL`QTPNP*_7+1Il=/+b.*1/1lU_Tdk?fȯ5r*yɥ:m䵈$ʐY</UB`0awP_v7;rvCYusfY f 2w{ܒϭV?U `G?ڄ~՞F:ә2%ơOĵ:> PaXzֳFیk9WkT_9ا%៫֌:nvKɀh3Z6n-oWm(=\G]}/Z-g7(k|7ue]N;#YBl-\L]!"'fTG{D/:ؿRC$wD -]:jb\ijs]~盥S!'One]LskշP ۑ bʀ;X諬DXP.oȞ⛧p"Г?_ζ'_~Y B/ W;v}5XCf  (_@e,dEÙU&PYe} Y}E^OQб[ d=ݟ_Ś" A` hqUXNGe^EP]h!u摠m 했| ]R W@_vaؾ-C42ONEi@:^>NM] &^n !y pѓgaSY 1&"-0#=`N\*fbd`'z (^[\"a VZ,_l⠔Lt .vQ/~0 9 d2& 3J#:!4F4>%Xn:' 11c6"+# t\4ήc`S ?#A$1U<#1dA YaaFQdE&eF272]^Tyd"dd W$qG؁$DSJ_E]xbFfm8VW#Y# '1\@L!c1]Nڜ^a@N_ZW&`Ɵ \aaڑRR`2MX\EVEIfFP]`%G^&W&fnrvg؅&} @l= ͤ9C]fnX!Aog`" (q24&[jM$Tj_tvd>Hefm' w2ez:zyY'4CA>|ҧ}?~nVCoh U(E\0\E9vFimLdT`tGrg,٨gXY,A,N_¨h0^5iCD#>9)5jXY\md|fP1'$4"Jdsr>'AN \*jRbzjՠň,B>s Լ 1f\j]C4B+'L°:߬F2>mJbQ,GnȷfIUPA-@(ۇ}CkʫA TlV˽-B HO$&*0l&lQ,,Vbkj붊h z+Zblj¸.cǒF?!wݫ@+^0И-N,gM+bTz5$~rlq->Ú[m +`nSŬk2*m"S&[j_Jh.Q`u*~Հ%nZ6jz$Cfv9_b/+1.+"H -djBgNT2Y|`N.ڧ=o͍X0cZo򩛂z/ۂo-,. N ޯ6+R(4n^ /ф*2/㺧f-p/&p?$vA@b/܂ V]NB& G. /N#y]/V_R*(041ɦY;p~,un5LW-dq')D0z R+P!&lmFp0W"_p1::/q ivQ1r#;r0n*Hr+ב0&/Ns\p8Fbni"P JVAxa%Z1 15oUΥ"s2n7d 33wq4K4bivds8rj)8'9s:_=3-U<妴%p@ :ߣ?{4 3$3$% mi)*B O<4f*m})6֙U4 ޙ~t0XsAnV;4K5?<Pʹ1M^a6\p&3eeM4E+`tU8ityi_DApBUr&Ŧ"WrJgLG1Zc0@t$uk\weBPQ^5RZre)[`;5ל`hid,v- >0dTMlfYYv5'bgcgZ-NWA-jǶ\G]kv_Ա>"PN4` aZ*1@ =RvI,t)u˞V_fh5w3&lyGje\!km`SloG}j;_/{-Y8ڣu+A@uSJ8]ogg޵15W|kno"6x~31"ݪs'61Mk u!‘ m8Kq|s&d+ M'zY˜XM9r;8t&y% hx'5z#7ў9YTpƢz+%/hx窋s,NzWzim6x7 lz6Qa# 72#Mesiib>3x3?sGxmF2hn4@$":{=jS亀{;./8{fޯl,u/yF[h{n;2YL,֪4ŇPb|w{{ϲxO<7| j"ϧ(*i];RՖ6]G_kaQ8kG{ |kqxE \_g'ىhדسUӍٷy P<ڸ~~Q>6 ~sWZ=5 v8sEa7ɹcYD14 a„6tРU cFb9vcgYH"( f`ٲ9;y<@%5iRJ94uӦPVzj[R`Uԫ_j(m uA[kIԭkE^z_F6rS-bM6v@:4x2%T th;sytL3m©'ϟ}6-ڰd-VUyGZUܹm+7ܷv{/Fig?\,30/ys1K6v>4ˤF%դ)^m~jۘm7|+zۍ˺({K, +"Y-:=&>3ș$Nz? A@(KJ a =Qa[QHYR [s b2Kur/K14LF6|:Z< KbYfdO@qoPBIYE]_HRI-R)oʴJNi-J7>=NB{L0C5UlUˣ>Mڵ\]W> ּa+dPf-Y$\8Z. P@k1մ'm=e\0~* I5nMRUUm(x_u^[U z_ƆW"cPɀ  Z$OJ$K'Am?Pk?ŊPK7 zQNYxIluAf6O)Oz-S𧫭Zx*묑큮"[ܱ9jLS;mĕc[E{ 7g!%+Qc2v 3駝(iqɅʁy@X< hq/(X'mAB#XXF-wң)"Q8}1N"0E+jN{%"W0ji TtB]nkL1%Gް~bd飓rAb!B/Np,'J &Q3i,ҷ,.YYO#V#cIcWK"21٥!_>)t$T*W$&f"8MmԖTdӉb9KIN礈^ e$TUZ]VP55 '?i!ɴE P@:!6Vы_#@QN+IERxe{.g1wjEbaA5ԅN-N]vR0x(h 'oZUѾZ%e'YR2] %Bn}0Bx b&dTI2TjʚdU0 6V {#S" dþj1_g*rv4-PVk"ɼn+Lv1e*qKX&w&7\HYHЖn.xD@i +ӦcmS"֧q~yW f7&rഘ>^ٗk.wUh-\F!kԃeەkW3Z3 ӗYz J2k|W8RЁ4vF`0 y 8a70apnx!mL{y%`\Z'[|c [mxT/JM|s>Az)D+юŃ#-ISnt!#LOHDHм^SZ*-9Z\륆qqK`F F3Ht:A[4ѹhv}I"𣏕 ]qj+.hMZqU5YuWiSms Z*74C}׿A)pq_pD=|ï-i#er4VenDޯn\͇szY;o;vteFǝw^F[^3քf=βwP=Qv}| 2z,Y1T+/^@} 7%Xv]]v~u?}_?;C}ßYX?/? mqkE%g;/\;=(_1l#3 K'mro.b݀ZT]/O:(b -Fbc%l%RorpO+̾bs:,z?CPТHVPo(ad00>& ܍t^燎/p[0P.~ ' g@aІR [Oթ oeLΰ ;7r 0/?)8 R3$9j3? w44 @1R+;R?A)B%n*LB1t44%9EvN>)D,Ҳn2?ɔ~iVOZtFU4I4e(S//_rJ+3$tU*D5pOBT8DIT-nPP"Ku\ioQ W)o4XdXt0Gi5u72q=BZIZ?TlH`3YL4õ"ȕb b/`0"`q:u;D#tZy4\0uG/_vn۞AnVyW(.oPz^IAM*3vgKq۰|irB)*w206x8 E$N8[6auqdd3 }xv)Գl wQ/S7騸~L8iQ\vׅoojo،swٸkhvr78g_R8}|x[YikБǍ/wؒ<*xr?y8IᦔC3Qؕ]ϏvUB$ԑ굗GrYh;owby5+mt"2pRgse9wO2,lM'T9Y@;I -tŞ9chn @Zj )pҡ DBr%ЎG0๔Yk'8ם9ǬbNѥ5@etvy:ӎS5Ozo㨡!9:&y.2S`uŶ𧂪Boz`9̧-kPzyɮ1ڳy{sxc-H3dusYrl6xԺ,{vXvkZK:xY7*[\SZmuYWv[<غKMb{vGZvPS.ZP V.lbCU`asͼ:9 ÷OCZb\;MS[{T \BR)+<}ƒ TiĤܻG׻KS֘bi)tHo;qOm%:O|\V($ɟܕIɓc'ū>8N0s617)Q޸G,]_a]iLqƁ(N) >%^ٙ7[Q~Dm=ԧ~9;ÌD˖V >K"?>g˞]W[eLjQ>l>'_/ԥ^Ԫo? "'W7]}0{D{>rM;U/si؝D\A}kqI Ĭ[>D ?~-ё(\[NI"zlBE P"…,&h0ĉ%PXD"c;zXÆ A45g,[| -̙о} 3͝7sy.СD=4ҡ89-GSuT6Y3f\A= XD0P(wܹtڽn <@` 8LQbi:vؐd1~|9 UuJh4K쉓͟-} ;O:V̼[lk۾{7a8ƒ s8ܹ;+k܈٣U|V9zisFvjneUW_Z^=XA`Ymw Gy^z7gI}vKh4LZ~@cHP Xv pfiH[ W@N.gsI7݅d'bq ux#Xe9䰞qp2K.HZǓ9gKUB[s JYh$WO>}V%a!FvǩA]!YYzgY&қ/9'3ڨgNT J(fHoAl]yA7^*N˩ xjA1F#g1Z.:Ui5{tĮel@RaQpŸ*Tَgz |rxc"REolXJ,6G))z9âjn ϚR?LuλE[L5f׳b?"m4)~ŭ2&T (gm- oz`Ra 4 g;%J}#)E»Rᗶ1~HE8y'^ WB^1!h>^]xJ*P+e)J\X'BLB翕DXD*20" HH@c]58F5i )-tc/G"HH*N&,La vN|dQIR@ZR$8Ix\aȐjyIvd%UZD!|XV5K-=0IC)ҐFulc̠~F0m)3M 5 ClvZ9Hx:ʁ! v,A qy \x]QzȠܧQ#l4a U$&A = ]P.GSJ-!AJoh4Gb|!ZDLvZhDæyNs}Zj5.j6'Y(-!Ā6aO40M "NPXBVR(ZӚ(&$9yH?u Q!YQy+'k\cmzs&Rm/.7Yw/fAT6>Fz WpE~`B۾-oiE`$vL؊rAhj 1wp ۍwA\"Ācoi*v׋$+yȩ~^&3;2eZr wc@c/  ֧;aXY,a9@ zЄ. mD+zъp(@L0pIT*Ta c0PzԤ.ITzէ_VִE\o &}vs=N߿nXtc% X 5E؄T(4RF!f܈fmM oD+bDC&>A]Q$QQ0<T}qDwEցT" 7f)W|fG Z旘iIfb馛I2e (@Ѝfl1Dn-",QF4EhiFCit:QNJ2dW #Yy~RcZ`a5gYal&Ev 2tff(g"NZ4R *ո6껠ZF-Hz *T8(1U49VKf 3,k C[d1M"[,c$Ll##/T'!3j*.pHͺѼQB$ LS~Qj#S VXgqZwXk]++rlc)rע&Ơ.27u0-z 5MkJ]$^AK{5ٵ5cu2Xg".nǼXxaq﫽D|7*L+9@T/2a5XsMn>'aX/~ļދ,aKY/+"oZb⻗L%Q8yNpB{S>iNs^ ]6pt!~˟cd@vJ;07DK X<np a"Mi!ȁHpb+^^ QxBoOMgaFl!B;b͆gawḋ* w,HI%*M~ hm&Dъ8OzUC#F1f#wkэSa8GQ'?ᐐȜcu{B*"9QY(DD27Ek 5ʒSZd=N Z1% ei5Qk2O^&2ƸOa▙gCN%)rf&ɚi!Yލ, sR [*&xQ+,!ܒ8+P퓠HFN%43Fё>D*z X"FL`/p1%9 Rnj5{u&laD dk-QS1i@uӳӠ+Qu٫z\Z[WmT]jȼkDQ+V`ok_zƼ-X4~C5h1[A.uTJ 4p*D1Kɐv>ՑdE]kuY"3ZmFZ~72T-%bJFޞ̚Gwz4[/\&&dt,XwٵlD[h@"c x}ĪC {t WȂX|QTWDxЀW4x~1y*r&vz1#qe*\dR',)Ycl kLʿu ]F31ϩ̶n?:V $ -g(}N5$h*І~YF"Ȇ9p[=#"dsu̥iNB0`S&LU{bE-\]3c0H.!^;/`wƳ$ !XܼMPE[Ǧ{%\L61i+!cod2^Z@'5,蝿{հ;X98cR\t^ 8kqtq<CJns˱Cdb< |8[W湖>tG<~SzQ2u3:ÿ ЀUnv:$vv=]LdXt+rd1&| s.U#,sF3Mneu(3^3o9[Ѹ5ٙ 'w Gs|n?5e:}G}շdp }c3u+kW~^GzwgGqPulqgp'h.gQwr;Lw0 sU (䆀6XBŀEL#C?F7tOpu*8z&~ 0$$k`gg-W6q6lW6hQW3QQ:JJrmFF @ ĄTBuS5FtlcTYtoo] 'b؁!cziȆ'$xbAQQelT5gc~X('w 8jdFK9h!t}`62 E_Ugwu ɋwbr~1(E~ȇ6VӨc= ݈sE ۈ@ >L ( OCŐ -ɉ.y蘎TXxt JW @pjX/. [!'uwz gQGz'x{C9@' 9rx -7) S1ْȒ29Xw:)sؓ@%uV؏ uK9YMYO RI^cMWiYq!czU:X$bIReyBȈr n pinr99uꓗz,Ȏ~it #;FbИ/2^tVA-ٙ7U{=r8(a)9`6U0vJXiHm)udP $  Be6ùjIy9r!ytbXu89{FQ}b{āEbrYv>Zxx(z iczO*Dؠe}9 iʜ*~"Jن㕢E,.l /:f7rm5Iiv9iV10m L0M*P*%å楁IZ{R eg9j lڦ+)0¢j0 zc|:8,6NWУ@mr%T t j ]o pwګY 7"kppzMߩɫ髞 Z5A0f ? ȟ|z㊅'}e@`J=Rk@ZkxG4Ag[74vZ, IfYI8 Z`G+"Pp툲)eJ ] 2u 6p{$سwpc/E+I1^ NPIW0V Gx:i hYe'۶ ' 7x{k}ۘ+uvpV0ؐ F{i&AT{_ JFDJ %yFsT 6^tZk  4 Jzu}p!jgϛbV帺jkM۽&QPK Hh0 ÂB+yyB<đۅ| ls7g gU<2,ő!O9IJfyq0A: 4.lВ.T\ j ]ȫ60<  0]x|]WWq\k,]dN5tvp*~؁BEm`xԂ<ȄPjyU MY =Kм?т' ]֮ *@+>`r5) v`d؋؎=ВMĔ-Ձwٙ՜}] _@ ڪ%pqa(|s7ۃGIgY)0`ƭ̊°ݶ_V]ݽ%ّ,ޡl/̫Y޵ޕr)fN+u# @ M n.ȑ=eUIѮ^\r&!.#^blqM&}.^|Qw=66~N.0 vIL~W,O}ѡY]Gb~"NmJ],,gk%>`}ACVܷ@FS  M }Y{١ NE{  ʨL5^8kvgP]gyJ^莾.l~پ.h(i -rxhnp>lmpN,|zP>>~[=dKjgo T^& lܦ %pbӼm0?27/};F?@_ԾE+h p0Q_l^-\6zmeo7B^.0f)6_{ry-dR~W6P,LeFQypr=- /w6]wz:͢:d}'(0;+!=YF;Оٶ]4!wiK PJpwpo>+9W鮚Ss G2*bc~?vo9g]0xIJMyy͛bI9K8ɉh{aC̅s\З>G.ZG/%J濸M@AAP2@7[G•$K^dU6Hmʜ2B :aŒQFA6ZȰv+A Ef$] euML&lFK2S UD +QӀ$WTn0X$̾p1{d*C }훏'?6z)q].񋳥\0H->> -A[}q Hf(d*S%-A+2,`&R`3FJ3^$3`#6$p/~Q:R&D"R@`DnhB<(y[!L,Ԝ5lgС$G#MAKoТsSV;KhTˀ P1+lOc+A-^IhYˎI9hi" ,5!RPS&J&}egf^T:ST;*|ⓨ%V;TblLMMcEb Î Ck؀~ho>QѾ}@!h`Y3% )֔jeq}L(O84hZ)y"">jPaObXf?IT% %w;uE(> V䍥_+턮R% AybrhmcD֝9=p_ M0+F%ےe%dsSxAD>fR*J~{W$CB3U"ۺ:^G'VE؍MXk Ȱ(Ц(y"P-S; c˗;ȍb x+_mc YFީII(mIŀF ]2Z5l5 ]N2E *H]SYj.4pWZ֒b&ǽuvgX*-l?lKڟ&y3}Bx8@kXnvVV餤ܦ>sV}5xHțeͮ7޸ֱJ9)Cbe;z> *`<8v<φp+~q_[ʺnȽl/(?rTWE~ HĻМTE+%ikt3j0*@ellBSu:{Uճ~q";r?p  =fs9ynR=zз"FP O$c%iWֶ_yÞ G #7Oj>ǒ6¼{s @k d@.۴ =1C<;!6/??1Ӓ;Њh>9,\8pxpB+ s, BkӼڸ4ˋ. 8›!B;u h;֛)҉LV-(B7';4D+.$<&'L (o+D-.BkH 4ÑIßXC1l8Ĩ Z<+.A;=Zc{˱\9E5DC4Ɂ" ژ\>JD蒶.MN G-j +Epq !(2kEs8 zh-ѹ0?:J Fý(FВ ںdF[MO<@d,$%M4@CQҼdI&}'.Lr)RP, R. 0N!S3mTOySqtф̹;u`=KB> V C]D"X"o# :TKTLTmTORkgլ+U Uզ`UX}W6-Ё8}LO:7^_7bm]7;V0ԌeuG; JF:k-Om-njopq%ם:J1 rw5QӔW4I{Y1 X%X?cь)M"U`X%˛G>YIdKX,dMvGs09Q^(Lq4f_5σpLX^9|ne~c@.Bd\ X`ND,]sdP`V*fJ^,6r4nf/fuK.g5=gtN7cᴘa F|]g/zZV`;8Z\h)` iVj fO>q.i=i^f")we[\&ܥiTcfLIlOBnbύ'ꪎ4:(piS6gy-f"tW~U=Zb$ԌLXK.%.lhRľ`&Em4B&?&i5il;6~mFz.=M%^o.DRNZvlnnNjn,:2(g王WsfvaF>dL H%nY(ov jb+jnlH%.l4XՀp˨ߴ.m X ouLT*jdqoV,]l.&1ZhkV>qr:$%'=g o(`&κNj–R/үj ϋ쎱q[sU* qؕsRs:[ '] 7ZVk At:db@:oGoK.lo3OsNtuRv9aTO슀-n_)uD[u88Nn]h%vJ3v_^ ؀g_]v[e ;v=ooҏ$wsuNw[wvow\kwrc/LteoNw~]]xhwa6vX$`C D_you*jBP+ @/UFǺt,t4_Ce G队h6Ç(uOx&gx'@rDXuÄ8v}z^?o W| ugXPJ ¥n8b _}yvfR]>Wmg?{~W(Ō>̾Bg|+d-o&w~jB,[w7} uWoZs\~r]s%M"<DFwh gYo1!p'RhѢ@hψqȱH;Z1$_*WU֭YfR(I :wlaÇ-jthPC50m)ԨM+Tp*֧X=zذbt,ؤjמE-ܸ(^}q zaȑÆ c$fpĊ3l1HgRxbb/s&dǐ/>eKb0eҴ'lfFvܺwzPh&r߲h>7`x †-.2ǒ;Wn;yΥ6Q|sf7s+pGoW'Xĵ5rlA\s>GtUX[V Gqwc3]Dy*AX 'ҷjv lXFxdfa9ZJp]U ۱X⌐ϜbI$mT&6jC>OC $H QyEBNeXSJsť99eh"/6wAާxt ~;#{2'UV Z$+le[:(XVZVgBgyZ&K&˦4dQ_" %?@}EbU" ۤD!լWBK VD-zH,&y|brI"yb*q|Ư˫GYŠB,Zl־ wk=Hx5ؖ7ܮSbl3Γ4>{tpBEt"qM?4QW!Y[{̮I\*As6x5-=z7c p\J+ɴT" iץ6TՐo.*J .(ynnK?_?oպT{ne`v1Zc,?eϛJ*.S$W+*G>M)WQZRõwZu kcۖJ惿TSYB3(y4g *t"S" D9r,)Y;E:+_r"^b.鵬3,[Ewn63 Ѐ9hQx$>ej>)`H.2rF-_2kFᅽq}J n96cD!SdYHXK3}Qr`+9Lfr'nvԮ)ZTۥ#?H]0%1FN{|bKSJJ4i1@l >ɒΥPTGǬDi E@BIeը3A4 9 ~Ҵw&S$MjSeBl_sSiYJEHaP UBvT CRu 5DiJQҕjrtcb}?« @=[PoY^:-8$ *Yh/SSmkcICV lKR9& wūM}5` EC,8 Z,eZYvNd,IEſΈ6+%fi]jO 6(s}@kp8Sh&?-CZKҜ(nvSj_+ܦ@09*CmnH5\}hqђvrCNl̵ 0]kJSLb@궰-aTw/T,Ie10|&xF%KlN(@ykW}׶ZuΗ棞V!<7,Kx+dّR1Jr0N/l ˔0/+c]]cȖL v3BRR{cbld1NU/`VWv41h'٠, %jV2lPO[]XjjZI$˰ V;MV<(*jx st nt+aNfI-Wk_9\q;捉 *%o,"cXÛ&LAb{df=V$MOɄ+ wx3McYNr9}ew hPɉvW 3fӈNл3wsZ*Z#Z : ^:-2]uO87~JNw;v@,_^\Z>pi4w%= F#\فoFՏxStlv`yAL\Bȧw{}\/wAD } k'No?M~*1LOݲD< U`_'t.xUD,Yh!_^XSߏX]x_lY m* VE~1VX.M^%q-r) S _ b XMU:J_絜 4.a]E ӣ]zI]ab^Ox!wE5apMXolEo p!!Hār2! : _!%b ~ =Xi%ve `bE'z"m"X,I +!buA"b`'1J1c%&!NoDo@`"|C] *"8R .,#9N-Z:e!<<= #>U2r#,r=oQ Ƭ"/@d QdE-Z}F;&\G~v cX$I>v\ oX+$Q#bQ Fd+.+~PƢE^QER:STT$UvI W亽MYZN'8#3D0<13LH<3'37Gs>bFgݚj+W[ #tSU8CO4ZN*W|;DH@|t. I%"I_n@t+LW%V@NNDEޅ^;531R'4xXF:J(K2 s3ouWW3tX+Y5d7 \5[[õ]73D6^gD<7qRaM3'0"a+hbi\?CGoetuP٭-h7Cts+7<%v/6e*nm{5fMXz/ e~5U4rr+s;7ts9lgIZ6&5vs7dixnv9k;z$Anr(&7}77v~7EX7oR,7{rLJIF *2jOHY9Q3whHRÄ%*87J&H~30قZDdx0Z%t9 &Dt;P]kLyI4d"#9rmCn6W8G ]}7@z.ğ}%z94:;z֤97,#kz$X)עJT?'Ƴ7n73:sቹ'c@i:$0*[즕@;?D.N;i?t!V!ۚr{GzVD?8c {@;{ ~;ibe"s8 #)gz%n~۳'2+nlV4|3 ~̓xxJ^eCw/d3Z7ƻYVуҿ=܋v;QF=O:}9C&)?7s[ە_|O kc =Z2#ƽ{ g~Wc˒Ukux}Jr;{I/VGQ/g*kd9}~_z֫>{ה:4=c: L>E_K+ғ߷uS-+s{;?y u @#DhP[{w/ܸ4xP!CtxF7lrēUl2J54}gԤPFSXivmM,Zl0p7wn. Uqf7v|y{';t S4ͪc*mi1,H< !5H4԰;BB,)AKT\E*irNz@kH#ܓ#'!0R;ɬD-QҳLD1wbrzF5 PER3β8; <V :ElP `.Do&ZĴT'L55F B*w[T1kU[}+.X̳v30@)Da|-R^*"=e+e'LM;S5TʾlvTp\V$l4.ZߍB^-\=yRz4%\*bFfeY1'kgQ=] bAYT| 7UU-,݌ʫcӥ !N?SiT+@ Wԓ,Drܥzye/C#:Q9b$sSLцyU(@&XG'iI^ INL]p^6:jUEbV eFOSpFHj]kQ˷+O—:SXWDu}cI"`&Ccy{-NJdY+Ltm Z5Њ62"8վ$" ^+U޳h2,M-dJ\˾8Y|%\h* q/U+D܍YlRv:M[kW`qo_ W!-Ўw)]$ SIp%4 kx}<~KEU7-v`G5qw;1 eUd70<,[=QF+"ZZ1wY38>^3sfK s`|9ЉP\J#1R tDvfO:#M3y:}$Q)]FT%ڴ5Ezkڸ~'ut ]I*X!&,-fj3]S5kۏж{7k~-aiR TؽTo (7j[Nɵ₿`0Ɉ5 #Ӊen~b^6G<.̹sD6p aL-tK12z&0w,t\Lfo p]W3YNڙF=pg/Q&y׻q ^^xe[Wi^sa 291s`tsg  >Oƙcjg7^R1<)އjru'HC|ۻo{|p` QGRvo(4!tˈ֋Oc/JI |E 0ChmK v@)i/m+Z3Pz2@@pBFJVF P;%Ql3V$ 0~rPw0xП~,[?D)nTb+,+~ t0eK0Q Q1DQ$jd 3ngl,gڎ#*z[ެd> e2+ٞcQʰ&P/21Xd =1 p%|ˬ[Xq(lÊ8 DHxjħNB!~Bi9q1!GRDf!0Q+.-6'+eIeo9Gʢ/J;eO".o%z~ %b*m['7o,(} 9"PODOI/S:Q-;R;]3/A55(w|69:p =d1+Ib˴e X"sͬs,Fl?z@:4%."A G# Q0o0V1Q&D1kqbPrDK^bbk&LaX-_?h'@btt.x5N[s>Ms|B7jI_#TmtDIKMtLSs?0ִt4;2NdGVjvqHc =j"`lJeJ4%0RoK+R)LtSs#FFkT{KTT ToQNWSUWu"e[ó BIVOnUv{+KCX)uu'̥0̔Tb¢U\ut`OAv䴕U[Nn)uI˕p"tQQb 5K XR56DY&U`ZQuNVaa-Nu: brxcX?֒dk^I6T4L_Ź1_5-DfkfO3Gmv5"gux6ų 14 2 MN"i5X%dv.jM' I55: fvZg`aq6mtgN <+hGFn3#ۦDQ$V%ߕD ri{̪Z0=epqkGqqf%7m[y "4BJ3`r=9[,+k,C7X[X273 oS 4zwvDy;k'(@'9nGA[oM[Dǒmy#]sϡژ 2#Z8m,k:15CkFZ(XS:-W5zdl9xz+972:ňz8דڒhuYe{Z[zz3P=2ZQ}RoZKTfV2?c* {( }ȟ9\A :+7[+=۳1KSa_@ֻ^n^A{!*yך Jʠ;I4`Lɻi[(̻EЛt{iځ|v`A::Ζ!;ÿCD{%8a;[!NyDwwҽ9["7{V;2V[DDѯoAq{ :fsf5!}+;N815`Q||\~ۣ8{G3dc\8an!;ͽ[u͓BMKOSԡZ\ĂM]'2wq^qGݗS?o_?__~o^͢,=,~ ĀŠ H~3$ _ЮۘTSRZ`w@  ͜?-1=S'so{Ub@n rf` 9|1i&N㱍:6cHW0ʕ4!LjnL4[^M$;ժAdݺۊo+H,Y(p@tY={ܻ{_L^7x?ۿ%] D@`6[,HV<a.(!c\an! ."2E&XaVtȢ:Ņ16AfcW(! ,^@B-*\0"DH 3jܸ ;tXA$QXɲ!0cI3!.S7qJ0v rӧ>|`c,Qd+q4uWlܖ冖۷oɝKW۳߿ L_Ÿdž תu,e[4Q_Hʰ(`=z rI.s$]teȓ!2EPJź  V2?FR͛˫__߻KSC̿?b^ˀ__"SM5Hs-|DkC 6BE@ۉ(zAH"݆n0pC+\r<ׂHLIT%p ;䠤V]uGx֔SW2ey[2x`)-ؙhW}eJ(bx& 9T.ڨho(K;ԒR0"H)BV݁GTRyV6]fӥުke  29g瞤94@f{vۣ&m SQ7sJ黜z*TYe~7VTk/`/& 2JulrrvyԪVjGu`/,Kԍ1AR=<1K'ZG7ݴ" TW-+f B&P]|lLa(-,wn1})J@Ξ:B;_ZK'.N7x"WnW^f[M K+PC_'wJΑm-i,z ݸT7{N.8GHK;/=9g} S^,ײI7p+ԁC!9٬.{-ʫ瘻'%;KGxE`*aFk󨄸eT0qӓ,`1=_m ^W&ЅONȁ̇Ձh}^ J6 ?fa+Ã$2 ` JU2MOr x`MS-PB bkjw65wC!QZ`QxHLv08I $ȸUZ\(]Xd (;׫3~8a Q7Er@jHT; y?.ju,!s%ȃ"$шN,o@&9U̦6GPlE-1Z\ 7`+_i>@ `K$ .wG?З1H '7RJ= Ed3ik.cH &͎v ՞IȦY̢j'.DCy5 Uy?~[?ٟ@%fTS1Rh,N SR2+zՓ! +<7 Z@K45Mo,4%/;R3Sc=8Ap jѠiыr_0V3rhIj5SNi]g^/"e'=(Q Uu8Dqm(Ft eՂnֳю0dR֖,#ljVwm^w[snUQ_fPMߐܩ! o-8_c;65)#C'obpKGl  wp@9}.}%m5m` 8x P~< :x ֖]&vF&ie7VP W bx+&X4gC:}'gx1(x-!*UXH W$6PWLy5XZ% 0p8x9Ax%G8vU@mdasvh,I+UxqjZȅXLfdhL'7s'wֆq(Wu)H-XXLEOL! `!)dOe8GeiՄaGO+T(fD{b)M'h[8%H;t!ψi~F6D J`DrP+Sx wx'*"7'7Vy6j"\qxesrcx١tsVd @J@y%'eɘɝ;f~=huBn _r4y&0ǚ)WI(gPO iihItgÖz$e Qؗ9h iى٢.ꢵY e` @~N j&! *%Q9|V\)C9bYGrO= ? o x P z@ %I@$}L 3e?0ʘ eU?zɀ~Yu40&Y D:GZJK XS65{W /O:_^yQp ঝ@9".v@989zךݙ z:I3J0#5  F \I- ;]COb:]ZÇbHΧlp WɀyQ pͩ0-YJ Jؚ㊉؃ʐ b= îe⮠9z5j{i*s RjTzw [:k_ zxd:ٱU_d zQ 9`F 3 &{ʲg1{=h+zک ?)Ck+^!O* /RzP[\ g[˵)1`Ea!eVP Ҷo[r;kڻ ⪃ڷ~Y/˨MӸ:K S|1)w [ Q괤[k{┪ắ eZqcx,WQ}[j(LY{魄*< 68br+X ;  1O+O <|.'A*JLfE @  0&zѹ.ʻ,ԫ$ً̝2퉔 V03\7@.jBKM :]k;/Ƨbɀ 99PƉphژklmIz (snfnC kN1-!ĂMmQ)c 69ٔ}} ݀ #th\D Z]."_- Xѽ,^b>掜.z)>݂]r=]tVE}zApّ D.>PKx̨{5j卻Jdξ_^Fd@0d{ 'bc+ y l,0 n]R~'+`H~pQ~la$+n}Ė% F˅{lR'Ļ||!J}}hp,RNO}mh֋._Ky[z x?jK9p \>1y`Ʌ 9/EO2.o?/s7 ^ػ?Q?9}6PPW?`dڞ+IqΛ x5 eգ zy?ќJ4 0on??oאדOHro'JbA@_&l88v-լ $XZb.T`2!*SLY/1\!sѺUV`N;v DMhP&4|(OAvIeTSP-@J5ѧn"Q^ņ@Qٲce,ٶ-Υ]wwa#H8f$\x`Dk(VqAsM\=̘-3gtCWVڴi U;,صƌe{9䭕*Q|sM9w)OMnUޜiT Fʔү`;uj[79tg` *^x}%[ߟg &;T[5\{ 6]fD"$tc%d 8t⩬KN9Sq袣:JFċ+@=$h1(@3"̲OKPAh 5!ys UxQEM˂lW^t_4o5]=1 A -OSvZe [[K2\q \Qzfvߍe]u {kv5_&xKXNy=#aab#v(Ico8IA" <-y\QFWev݅l,WEm gygsՕCK 襐fŧnxzwjk}fvMl6JYIvAυhZo{C\X z60Ģ]VpߑJ/Vj *?r[ Jϫ =2VMg|uumu|vۃk7}x?JjQyyiI1m1bK1*kX){^j R.|'[`1vj79G V0~Cl()m Pdx0ȩ0ȕ[vꢠ#(2 r5J YscCUv81 DVi 4G,h|d$%9ITҌgd&I0K6JRqSf,ҕ^ė IZn"N L_ڊ%c*Gs&PjL j Dz4ou.F *,0sֹWx&q NA*? M'|)Jj CK"-PRdԗWCsbC[ Q>%ReTHS[e>.Hv`&1D\!=rC 6WAMbt+X}W&BxwsZ茷nl!`ŢZ5G<]\Ǵ lpS~ZUB Vch6ctl TredHX"p*J0p;L^/U9%S=zbZ,>ˊ5#[yacMw5۱,}d' F1|zeYW+g:Qp,]u{ަ@E#7e+f3y ˬ-A.]he:r}_3a,2]7iO3pK`լYWGxDI\lmQT-` ;\*dhnIkf8IZ)%6[čr;v1d̚Z]ϻzľ򭐕w~?³;8x @C'ЭNOZ۷EiT͂7uvwf򂪜mk_aƮFwwv$Gg8ldkN7q:ۢ[k|W/Oգw^O{M̕9N~Tɹg52x~־^pE'ө {£I}|[(`!2v^jomfSINNr ~zI? ` C=ЀöhǫGxhvfw.DWK7JѺh[/˓}c>xC0qjX6+[; 3;Lڳ<ӿ@c @0( N P@#̼ @"@ 0 4?@+?2“3",`#|BZbH.z@$;<& 'Ң9s4BB0A.ʻj۰L8b:8CäX/@ <@c\Cr#C>{ۼz DcP2B(l6D +,tG.?1lPMA1 3Dã ;+hQV,$TXC1 2 ;;#xG#"+ ,Ơ#Fc 8KAJ =s۪h,i2mL>9LNXEV&{ō !Su|w )yzO|a~Hd?|\Aۋ̽>?lHp3CC2HXH:ZЅ@vtzĞs8YɞK=}lI(:;Ib3&ܳV94Jf mIXQs4QѦ*PhQQ"R9/@Rp%esRP$<)#4*RCJ,=-FO0ӘS;ѥ6E TEI>;-%P ?7 ԗT)C lEE-zGT1O2mLET ePrc? ;>eUYJ@՚ZU[9[ҡщ^m=`G20tݽT5tSm!}"}<C4kSWm-2(%Spq]6L\`?y]RcP8~х\hҁuᚩR:E쏮D$XXvODNYg=EXJZ&DYA(YنBDىOX8ؤuXZP5 %}ZNp|5P.JE#u,ے+8gC[ N}[e؂Is$DqΒbSTv sLxZvXŰܳs}\]/dK[X]F4!DI1\$ݬ{^0\5F0؋[xCa^5<|46\]ެ O_LEp-rM[]rH_߸=Ur^_μ]Q(`=`ZXMV+x`jJ n} 4`IO=?a'WCaPSN]Ja(aA-f`G YjZ#.Zb2_z}荤V^-H:9 ŵa1>$Y32gPHbm#nWWc࿍V+\@]^rB%<nDVd4b_ d0uJn:N> i%(BCveFvd!e`mW-֤MV^.PpúH&֍1\ def$. =~eGQ?޶l6ZnX^n{0gsFg*:WQ7$gg@n;@xg~f س%[K[Px^vM.Neį*i^fdxi.NJ5SNh|&= `W:  ~RogM@]㾦f jjIjgN"Fvj6OvEcj>jUOb=hжV#>>gd<f~Ң.؃5,VFn4Smm}mKܛPA>~ ʾȕC.9oxoo>`[';6)guk>mn1L-4*k.o^v.GoȰookW%6p9vqd ]>` p鱰p} o(1qӞfYvuq7Io7|+E$sf!"gW42%&ausgO*wDarr^qr^f Ov 4o.+67~89:sof`(_)ߪ?rsO،$htI+pt#?;38EMNsPuQod]nSGm=PhWBqXJ mtGO\ s4#qhw`&b7vSefuh/0'\?,vFutw[GRҵSeu_wvwwJ<|Ey>x|wfw~1ertWx(>gg]fqR@]Lww)z!vw<'&2Gfg'unFyiN8x @ 9zOgwz|"yUwS|G7N4pkyv/ /{s>{,$g9y{wozaG_vb7Vnmh|wxB '3\Opok{agǓ y]o.d@yuHhѭwb5Hlg'+|"Xd@notx\zv $Wo?ɾ~f͘zƒ X!Ĉ!(` Ps 6!I8e7pNU'aZVi Yf"NT'J[/ g#~cF @G .(RydKbWaMMQEQB/$FRF%Yu ߢfΘV)"ךmJ`EQ@z9Lސ$94(JL*vm]}b5%N8o=zU~jf\Y AN֧uޚ]FQ,Dl[UȞlVeF9Uu= z r>z!|n+Q g+m7<=hRLŧ⨪@BWR,2$jrn6<3Χ/ 4mmi;4R65';H'j͵ M̺ Ne,Uk#6:sZ(*-wҊ:I/Q[w۰i8pqĜ{#CN곲n;8;y0ě>qO<*ȇ^鉙GW-/~V%+| <1P}Bc' N_]FPO^1?Φ*BN |ct< MʩB2 Kb]6(؅pPS5@ , C̐B ~1ۑu@C-A%!)ə>Q?g@qY5"łEC#I&R&1۬ ZHsG9 y:CK##9Hqk3Dy=@gH[efW#CcdДtZ:=֓rG{OQQF광 j1C|i!2r*h. -…GTbFR+X0u܄*ʴA(U ;+.fEEu2Zmm[!#V1vk:`WA(ҰE찚=Ee d'[Y361ͫhYGZPmaY v6-mkgz>~"n]t\J5f\J7_=uk{ָpR/ċه ǟ:HPʸ0l}}FW?EVg[G󆚾%0rtdX^*z G i_K^7ÔT 4&c>+Q w^;pt}FfCސQMIfߒ#dL*VNQ\qy-xՊy@f,O|Oj9mp)r7'?vTk!i ;Eh%UϤt"o䏐^dmXs5ZayQ<ﲎ7c ΐ00.Q5޸J:~A;r݃E|dk9_|6ǹ9i:{~^RAzҕn:3NCgܶzni-T3_os&lj=7" \z;Q?,M ?}O1WՑx3G>p?zO:S8W?)0QiՓgzP /^x@/s1{b_ c,גe4]ĊG(=,cLx e|R"-P; O o3C F, 1Es(^p P܀ޛ-{`/9ZR@S fDQ)`x%Xg) : E"[H$a lL|>X*-}ɝ\@Zt|ͅ ;d&nA %tD1Uљ!s qꐞ`Ǒx M!!V("2"9b$!%F̧\"&n"'ae)b̉ϥJ$+&[,Xb! .J &|=R%-"l=[c>.283>dA~47a㝢a@* ڂ` ,^ceԢ9M:֟9<W`E#vLv}#=L 2#oNj(FBV5:CjDN\Fjdac SO" R$vEdL#MM!@N%;0 DB~J)LPyfc[\\IN#X\e4r_˜`&afPU:b%9$Cm#eF"2"Kg2fNg#^@N0']Pޞ@s0em&bZbdo."Q 礀ed=`re6g&sV;Ht@\'hNq(Ux"ganm^DbV-bUء0|>J-gYEwd*~& nC&U@Dwjb˄]:%u9ee8ס@$(z~ Zgt(u4xݍ(! 9ĩ^uDII)mVcR͓B=5fJpV0#9H%ҋ(hzuvĹi@ĩ6luDwbɞU6t؄딂;rfNꖾGx OZ&:tqoj؍*F^JfyF&"*9 .+6kXGD%fLkTޅh~F)ڪɑNǻ. Ϋk B׾0‡~PNJk?v6Pêr+EHĢjZ,&r8^!촥9+1lN*̎h,km<"dflh^mk)~c~Ū*2NG8 H- NllA¡[Vlr׷>'ےϾ@m ihޖߖǶk /28./8t&319[ke;\GDctkcG3G[m#-WttJ3'Ad)t:C4j>M2 5GF߄uqdh#WMӽQ#5FS/k54@1>_5>x[BM_aܝ5 JuLQiBIoi]5^_6`arV2bŠFcMdtZZR@O\zw0S vjjC4IDvb"Զc1dd_*&[;Zi7Zq r7^/i7sj7,kv tXlvmJnwPQ!ep7h{wZvnv|6tӷtaWLlqmu 8YvxX! 7Is0?8F211Tx_3CqOku9mnUFȂ10ՊP "wQx}1_&\~3L4ňuk697uID $Β6<:3NFtCsDy)7験`?yORzWdQ_ëgyoy :o0z: z%ޙ{߯v[{Mxz{&L,;"E ?gQj34`{hC_S\:7zczc؄뒋>^F[v%<dj V(úS_C3L5TC;}4o8K(9fo3;;$俺_=ouw>g~W=8\>^#S>[ڳ= GlN> %4xQ4dFĈ/(R(uؘQJNh*v0a(AsfL2մi 'h;yL߈yiRK5u):SJu:f֥Ju:beض4Ե{o^w90^6|qbņYh\2ņ#nȘF#KTP/j.ЀYѥZ;f=Fgiρ5>Φ]-\tj@uչwb+n8rņ%ZfX?:gMbO& Ϳf;M)sΡ%p+ uA2+}0ukN-袋k.[{1 ۀmܠkh$ct88)~ !*pus1GNoI))f-5 ĜP$R959͂9N;me1Ͻ<^q؃ +-،$YjKI-@#TTpG]RM MuRM7_uķP5[7׺v5W?b 5LfeRZ(+Jɉ5[Kq=6s95wRUu|^9U^[E_V`A+"c[E ږT3ډ*4c̏=ڸuUU`wֽb.9mߞy4Xv>)cIk*Kж\E5eY_z]k6{ZHdvti5'٦.*rn-4o8=._sE=uW/a_ s`o<]IW޴OZby̯?=nyv-ihEb/{>,vB|Sñk[J6Տr1l$`[8@Ņ l~ d`7>1ɳԷ>hcR =H=ML] bE F$SC3:䙠fl`SMj+^ʣM2uJJ#RH+/+<Ƚp#mF4Z2oyH{G>~tN 8}_]9$9ErёKn4I_RRi4%:="7A'&c@xJTiK+])kIyL\&N_xi_2g,13d.EjDIhPJs[lY(2c&˰h;@Nur;YIs X{2sh ?E3MU+ŅjV8;E_j䊪Q> waO,TUe%Jki@;T U*š-c&DD's+&I'_ڍ"NV᪋biFPNm Lj踈ٳ^h" ^S׵n, _6FLr-a {X͂LX2<=f*HiD%& ttxMaX3|.lK[=U0_rMVKlp? ]rQ\97tӝjW0EHw%W y -4V _V+oq[_G"O@w1\}.y `,9pEā<~-n}t,Jf+wiXFƺa֜#!W8U@-[QrbM*TBfY_vֳU4c,ZXzDtj46Crx ov#Kg8J*pA,h*R4XtZLeW[vUZIǐ84S3 =w4 Է(3g&ϟcYh2Ʀ]׋v\c0 $smhe )k8jwtv S;* c-tiI/t;Ns޽z=/0O[>0Êe,%7}Ѕ|gxTsygVk++95Hx(Jo>p~JQ^Ѭ.tBjج2Fڬ0/F-RO cpiN߄I2TGGύFnX\p`P0Fzox0#Xp" ##Όv g NP\P,oP F0C l<0 fv(pJ&|{.$AQ e*mLF b `&(#gk! h!f6qB}*9+PoB R%.Ř,J xp@בi* PzqoX.FY8# gI'T.!oa9RA ȱmڱ0*YL<"i 1wR E7 7!q!CqZ("/|m#ّ#j($$#boDPQV%sN(&&h &&S1'CЁ'S],R( l1)Sk))o$L"n+]?+PR,r-+)RB,,U1"i'+2.12Rұq8`//.PS0+oBS+_A7w7 *x!!O8.3B{24H.8R)wrG`S") 1W#z=U*j8mQP'-]|:S:4Rs55+5v>Ю$lIF==s3d>C-AS?1.Q4@/@ T;$)Xr@A306iBs&tB)BCSS9/N:4(?t(E:7rOT/s;C/_v%r"'r7z4Gʹ)>UBE!SF"@Iq44+JbTEA@g(2F38 &QL1'1B>SQSrS,tqfTOwfXt&P5@ĥ5'pKF1e ܇Ri2!YXс4-aSUSE&9AA[TLTh4?+J4Ԙ``v@^ 8Vy!Q!WuuWy'L#X'j'ab#VbX5Yc36c @JdOAZe]laVR \a/H5L5B Е3P16/ˆ` "6$jM'k$!pB` l1+"aabnbX5o3U,aށDVpEe pS+A< ĀNr @gwV\tD?5TxѩO^֣]3b!bU jv  z7kq"AvkK%K`C`u76nzanwn!{{owppӷdb`\H "8ݦ3~vfjo`Ph}<6]k_#ar+xayxx܉AWGi`!K7m6zg|o{ǗȗF||wXxaw9A@3#C(CmȐ>4Ku- `E ` 明 `@혎؏  Yr 9#Y'Y 3 XC N Y 8 ! ,^  :thbE#Jŋ(LhTQ !Bv8$D(SL ˖8^ʜIsMFrܙsIM5F:uޱ۶hѣEIvXZTiZ.j`Âٳ[˶ZwpʝKWۻw˷߽N-* ݖ81Ǐ L aB&C.ٰi&.ɺk;b˞ ukGr>7գ)5Mzb1o!t"6ȞU:nd:m$((]5aΦTe#䐄@[,;4"=YZiո*WաZ*ܰ`Ľ+̼ f%嫃 Ѐ%`liG=&65bQcǚW"|Hd* xf;kF8a$`b-M2xI6ih-%̎h0VN1gciJS]Cdi#= /{󖌓F5{kK"mqE4^yK]Q L_ ߓp ! 7"&FAf!yuJW ؖrnNeLuoJ$!tMm+25#(`6}1pA +g[Vt+:6uo2MUj&6)JSa6Z(FlwpH r4)f"e R˱(gEqEl}*ES}U )'Em5&ykd-X! 96F5F}е ^t$fFAL ݳźsVr/m۴̩wΈ~Zm ׃#X\|lL|"Q6D2t>;N\1נ9muE4MB?A?L{`wk諭.t9]a)#/48b/:|$$r^4YmkI㍨ϘvvҼA`k` FQ %)ha9ዜVl<}"^oxNYddh޻^ʚ z|'×V|s|Ǘ|||Q#amӗkWb1}T^y7~r=f~r870 |wxӀ d'e6XwnjW P3GUp(&6xէ10]msCu=}\')hv";N0~Ce4 O%=E gW ewEh~5OK|fj5 w5W8Y&sVg'^a8#h'u5i^-EPx wXkD 0@U=@8 %}|8|H03o$ei Y]Hpf] X5p9'R~&1(AZwv VU DJ?Ř3`H e͘}Wt 2G&B /xH X P2Eh踊0Q"!mǨP gdDfUJXh`4 YsHI_fGPQhOt)H$@b`)~W!-2' uF6 8r:t c;ك3 `I9|x[LjNYQvRIY3b4dpӕ"a 2zQ,q(0)jy 2`x73_6@ ֗hEyI[e+MxGCQ בYI9%!zÙal/ᒤ鎦YPyvnt+y pWcXW_`qFYŌC ȜCGO 1N1јiZxMk,p ":(%1y1yٞ+ xpUP8Zc<  dUX > F3 R yGyuy,")95/qK.z x vj %[@VWEnGzɤ@F1QJ*PWyp bکgSdf[ ƈ!0JXАwG[kmը:Jg8g+ wy8]DQXi Ъ骯enmn :AL[: S! jʬWxp _8&Ch*mn8K) e%shʮl Ʊ*VA:Sk: O y}IC%,)2+zCZ[O LjR{ڜDbʯ01&*U]5+9$AJJeeEGeJ˴MF:~K"̹F~[,& a+)fg7[lxj-@ x s{ `xzیX#;9LIS̡۫[[}4$=77!Ҷ[~:˴ N{Ik)K8AkzG1&ѻ,Zu(''J;AV6銔G|;ۻ׻~+[ۈH2f;#S8^ [@pߊtykv[|k&P'l L ~C-&H9|:EpF ¦[e-QˤM\1;ÜxMɻC웊[x54P~kwLS+X|%%ܳ.)\- nL4l `a.Ԕ. / yPS.! Ċ>^n>-ncn>!I &4o|P<0.U@8 뚞f>m邟e7>`_Sa~ōkgj?s?@L-.pQƃ`+ R@ܑڷ o/?ʍGث}ݝmu즟 мP# |Mix_?}`h $8Ak &C->OŽF8@"h$/ /p 9Cq<Z)[ĒqB3ͱn\+G܋H>< T, rw rRPL ՚z3_oͲM~GZSP4P`&LЏT um"%IQT/StT85TQ6SSNt5H[WZhI ]y]wւ])VcLL٪HmXSah+s@u\YC](VN^Ur{%@_/2J=4VM٬Q!G,0\ɵs{H$xCڤ IWDNFV6Sc~fl&o#h%6h~:ifox uj|p \36ykU>=]vV泻2rS^g;EKo= wp[R'f4k_~Zks$NnDtAeݤ׳Òivr/#ӱő_ۆ X[!C>J蝟zZ/)mov|-Ӏ(>1 ,h<%a oE-n᪡~[ԑ=~< xaV&łk]hH8a'26B0f]UoRU+-qʰntd׋RGq(fM;%#çy[ǮK߲7XKg#U3E |`X$Ij]Fi E ChߛKX"V:;a~PA ܘ9` ٥ZwqcQUKlRַdd$â7 QY6~S,1 : ؆9{"ml\UYsJKܵn|sPЖQ0weE/0lg=vDl#фi2΢s?PHީTشyG9ZbTޅu]WǷ4̽k^oV- ACG*7Ot.b^ {oӫ>̭ D3+A%KurVBI=3@D*@˩.ыXe8JzIKYs`7X;USHVrսXCH4X+u45TQ@7dwsh>@߸etAYigߦ;z']} ן߫7!5-oHw.7l&=7cdz|<Ϡi' Z6 8܉g(/4\M1nɉ-Hwq_5sf+pUʎ[H{>ɗMVhR Bv֤ӯ c ‹=jú0?A?߳ 0y":4〉㰲?ky5) n\r;>@̐ҐR?k x144u#+B;9㍅lϙAAA\ĎӽJ3!0j+C A{ϛ)B+,ܿii/dZ$DC:>q@Wk 08q;8õÓz'5)*6;S;sc23DL4M#= ŚCR<&U#?CŢ/T2Dg+L\CzбBh&(ET< ۉC]tn|YB7CAG] s7RJ43tG+BozǑOQ,ilk<$+jFB̬hHQyHH;YJdXd73=ܷSj<`9H%02ӨI#X3#+l3Ļ 4J=k2Јwx\@y))?BJ K2bdPK۰M1҄39 KD\ x:;̨_"JK`Gj4KLmRҜNӔKMllKaͅ>@Đ\ ܎Bri|8<0ZEN BDλH+O\4ȨMt/ɰ&F2PunBM]d<[NTP ĒL*,hE"QlY!!mQ,uQ$ -2) 7ʬ#$-;\$S(=N"k+RB˟ I4ӈR'h56=+4*P: SSU (U W,TBRCHR`8udHUIJ}Om+4C{*MNuOݝЦQ]$Sm RBW}UջHZMQhT4O_E :BWJOdHK )jFKFcV>l̦rpSax$.Nk^e8Fl`1]Uln>igcΛ>>nͶ6CNnVȢlV+`#OFWrӫbe@X]p᡾kNp?j缣hp4 K~tH~gtKg#7On8/q(S\>1C.r&]V=Z iRrtIu`otb6?ڎA`O_vgJ7LOve'J~:vGTWkqe0;E~[]3sNjVwtvwwgwyƴi(v~vcX<*q-.]CuCWʹ7\wx܎kxIww+&)'n5{Zeʻ $)b$GHgoA&zg屷.is橿^y,B;>y-aovvcwp&" C}tl> }1~wbGky?W=F]Eohl|`W5}G_D {R)W}7}iVj~(~iO}kvK}ch~?uo„&l!Ĉ'JT7j#HAlӨ-ZVlRXh2Ѭ M&hv^'PrδiMJ 鹨RRj*VzZaŊ+د]^E,ڴgm-ΪkW]@=AhĊ+Ȓ#lСd(r١EGaG$eKa$4'ϝAw͉ͥk>ZT[ ljѺW.ݻu6`<@[fpre%J4GӧSQI)K'NP "H\q!T9t5;չu]\iǝw~'^aA|m{4X}9.C(ב~HV&`.( m4!`n `Y*[qdXavd5o%\h^|x.x3X##AI~H,:YlPJV6[fTYsyfZiEbow+Vawzz|c;+D(+ 6Qˣ cԔ:UiPjzQp|fii]( ^xzg*AśkP GŮI*$lѴmD)-4=ͶVu:Z嚋o*W/ZFbZ/i ;p1RI+=qŚb9ve1bZ'O`.fo-2ʬ뾓k[R)tf*J_ٴQ~8x)uWǁzUcu[sn݅=f3cj˶oh38 lPd^+N6enxR3>'Wn%k^Mv`.豜/ ,l EaR| {G3޸ba@dwD^wǹO˞fyO>Q5,%$qL%ĢpW!j Y 8Foz:22ܫ`>h`UP w&PL*Ji ;CK^T(0>tU@hy^\]i"G\HqM"hb`B rd!soB 3HQc"URVme6%FpF} 5x2DJjMB0*ӟ$KFS*ZJ'⵺R` roV<':Q9:XƲ5K'bˌo]juÜUi%=ުf5'\s* UuNMut+ 8'(s(;ݹG%.c%MO!R&M.t9Ta(=N{8hx0Jqԣ,AD*UYnNXIH Gh:Ph4)%')&dgAK mMs־v3gYկ-w@uj-Vfw{,0+47g`%̩e^pU| g^\)pp+8MnZ-8q$>d!-ln&OqbR2|s.ʰuҎS|xeإW0m@\6b8ڷ+X4~ɰ#|d]LS֖7ap͓ Z}#v)UӾtx;^6>\9^eeiYU`&4j|Ѧ p{ؾS ['7L}ʊYV2}MyX4 ޞԃ~^Z9YCm5\ZU5@HHdVFJ XCqVqaq Z~ υ`͟Y_kl ǎ@X.Bb}!Is5!AZZ ;iVya  Aa.R !e!P NZѭ !!bgC|1-l:ʙabjbqrա'jE3zbN!TLDfWbh0"nU"1V#CcVT3q:zb2]0=#4bڽX*n*]E6BP7",#IpJq/47CnE=_;^ 12T=rJ>1_+ʍ6.@dAN@"JT.ꖧv5b#1*:(! .KB|λyԕPQr !Qye'(c3dQ%$_u$+FAdLT$eD8V9[OOƞZ*[f["%ve>N^:_bx͎ eb]Sd*dRfeHb&m;c1jR]B]dh58FB ,"$ۼ L̚1/83,E\pAGf_mcr]5R.2bqqh@eۼtHll kt/|gxo(-Yym>zo>6B}gUrs:FLވa"( DzW.4(x>mdƄCyvEezj[n懖X>gz`3qeg( &kZgFE@ )x'&i^,Qڞz>.{啖eh{鋊hTNXv& ²8F}%E)N j%>g‹((Gj,iQ$.)e!2%|vZ)ڧ^*:gF@jBijGC* -i'ThZf$z2kܳrhT"6uȸ JyҦbYUj$&֫epkUX>@x+ ,>ķ+tf0"bLr^K.j(j0j06XklbʲF(̂>Nn*:û«: Zdd-nfS:.ekVm-{} I=V'4[+2*)^'BO=}eH}Nę[eTm`~@a BD~LRR,Zfb-s.-y . *l,fDR뎣k̮l}*AVXNQ 'ZсU*oڝTDꖆ~O docn~o/0rL*'1Ϡ:cʪ)oroqL v0z/20Vo_œ)]Z憦Vqp}0;/epkRoVF I-B. wop60 s[0O&-3\0Uo /oL"1+2b]n ,T0p (ς90Ț"w;%on1,/. C`%^r (#q$rG/$%_rB@2 ȍ w-(23eVb߅IJPo>:(2r 0W0n1*32 C3*K*_+c5,s381y 8%39 Im` ;3ӳ l5`sss-Ct^5D9Cs%[.-l̳F2f3΁tVHlyP+3D04A43# 2E{MQNa4F2j3Í`%@I@0D8uB&cN"3״)3cju*s~(k+۰5G1@iZ#4zE4/\ʲ=$m$z0bJFr"gu btV^ZqVrB`6DdduF>3Yg5َ6O;6Ydl7_2mWnp:6Jp5qu27̡ZO3.s'tKas6vI4vmowhtw7E-Ei,VAw)wVCåL(Ww&}}O7~F㖪.\ 'DnKf(xF19u>D/䬧gv}[}ST~Wu86xL p !L,H2w1u2-8ժcK9wyxK_gˑ2/8K$#Y1)2tWU7~uKmـSy[9Wb9ܸCz'zk_qHS::6P9["޹CuϸjΌ-x)D;!yڬK+uxύlmz:w/i# n@{ﴋ/a_;¹Ho:8\;ё:[MG ӕH(2M;Yۺ7ytl+"R~ ˲sUMs>Bzr+h>iW;a,у 3<>jSD;fo~}ar$E2ثv :6  TQaB 6tCYTdbuQNh*VL0`Ѡt ZL3iZ3wgN;s^PC4zhқGϝQ}N]֬m,!5{m p۠4hphh00|01Zlli/'S ;fM1.JtjK:vlZis V,Yͪe\tqT1)Bq]c Ii4jԭ[9{.zCwOTv|A˟M{kׯaﻭ⚫8#,l豌"(1d#Z;퉞2,ȊW`(4LLIAGqG1oh3T5=hQ!"LxuHKnvLzL@9G}>LRx@B'mNnתb! -iK@a#8Ak݉43xDiiL絤I\p@䔨93C4bH% 9!'2[OTWI3P8@CЈJ愗2Όn1I{gTL  t}DeܞgtdӜ42BFbX#%@T*)=SS:P6ѥ6#Moڴ^ mYCmW5+Z;Z̢%p[s#LSYUiޕMUKm\v1uaΑl4Ch0gHe/مq A,K*BSs2Q^}-l uu^eӭcOd3AhԌRssJ wiWjP΍wp{޽w=x§Zl@"A;ء Np\,[TTD]kz:;7Bs?0@H@Ͻ)L \^<,_3|?.Ͻnݟ2_e,;0bp_P QP ViN Lg]N1BC礦zX!J);"!<F/NO͏_ꨯ4nn`PWpAɁ A@V GXs*.* pGB 0 0ΐP  ۰ P ѐ 9 P  \ ` q q Qw+/3'1'31@G@+RQW;QbQgk1c @8@-! ,^H $A CHAŋ3jܨǏ CzlF10"受FDV\ǎ *ݺmH2扑: !B;DU+u`J,ТfڵiK.ڸxۗ7ݸsncV*^0B!v8eG2glYȊ+C6BtL%LR4 bۻ(JrD %DC1F'I.:س]]W`d!UJ'eB =C\v RaLKPZMsoEQ& X1o&n (7 5lCs6W xfy1 ԦQz_ ej8hvīe*E$Ժke(<Ɖ345 &eI 2jxifZRkb];$'Զ6L)XA:jd8Sg_Z6,ˍ&K!-$% HĂ )#|mZ{^|r\r߮29fަt>3ao" ]4ԴFRp@*aWܲk| ](6;ގwGmX"jmnS:t3&R2p]m ~Sx[,NK+x [\!`^s7*3 ;c1x#B춐=y<0cKC\⸧4 NC>A1 bk.l6\hCihw b<f"G& %E@ݲ8~D"aQO P l</;C#a:I Hn =AJ3ĂK 1WF7)ZR]j(n yh\:Ѹm`kA5!ha?6ʎwԣhh dBZ"3F [Pժ\*W ] 5kDѪ֊^ [X@$W1$+5l^#!k`v]a2n2;lPʎ0#0~`(Tv0m5ڬnUGEkaKV`-JDUW,3IR5očq9X,hνك;]5V)P[ٵ}pmF,['ϳ]zmYU}ޑ͟yumu:J28V ,aR3]I.wI4A7Qt.UOʂw\<p*hYhjTêV->K\3>|E[J"K1(QibC(@KU&K=R˨S+"9g?6{j.4.0 9gb;6(1_@/t[!ڑK*4n(.}hU=΢ʹ7TE~җ&aCA%niU&yZ b ]nr{*Vg]p /4p*(wD#L1 StF6Z s;*7y/r-w`n=,͋s|UUn£#3J \@+Ilv1㡱5׻~p[D$f/CE6I;t%799 I%wžf g zjGzʤ|CqW %}&ioFh~WS9J sB:vZxŸ} !mF%z#U# ؅Uxh W0*h Pɩ?ztZi Ȫ6 'WWXnP:Flj$tiX0@:MmupZy2T #e:>/( :l0< ~&j<;K uGp(^Tu骉PH/P'yĺزl,կ=ZBZڳ=ˤ@CE gV`|Ӟ@-ĺA bkZ̬JI#%mHҰ:.)fWL5,ѰK>@D׵;JOQm6I˖ұ3Lδ\f,~iYK:jVlڦK̩v}zmk|}k,?[- Նm#">M:P(hXM= ڢzL b࡭}] ۱ֳ#x@ ~lI;Mҩfɭ܂hPtAmݧt} ӌAAإpݙMUCU6{`0=݅"A@p+j޵dx-7# Id'4+ ].>02>[ۭN흹ND;DCL_NGPͯT`m希/]4^=SP2g].߾ymXB F P?\O;!>H:=/9}2صؔ;в9._~M? >ϧfJ@QA .dp YLdqE/Q$[44bŠfK1eΤ9͛3ɳ57 E=I.iR}QNJuիUOWU%ˎݺu۶54q(  XᇇQPxñc;r &QdAYzgD]:ӧP~u^o:;hղu wnܺwe\`ŠOO|aLޡ#HˍFͺJ^LӦQTM}MZ|+ jKnBâ";0#<:SoD3QDm)c@"@fLpP /!p:T2$[@LH)EpE2L4w#KunTP@8hGL/=?PRKҡ@ p&1zR"ƈ*=ܒ&/3=ͦs2]KsTRBq-:"4=W.Q<. G!2IuRe5&ΑVZPCTl,|ޏaysoUrc=֌]ׅ>y+0}'͒\d6jmF*ggۙ@NrZ9=$9yɦ" ꫷ 3(EŖfp+[{P9V)vΕnTodRdbFG6YfS-mímtJ׻'!BVVLQJp܁}槔x3[清6ym_DBJ˛֑ [xT'$΂9yt0 ~ RB(ܐ0,|#;}t@t"h3ZA$"XCV4X8- 1'7Y5>-~ Ú'+-.Yl!-F/d  ;GPGž74r<83}cSK,B."=Gr$)J&sIFR<,yPJyKKV25-kI41Ic62P$1hBS]ӤEO7SD zN[Մ(ɛy˞'2=^ JMN^@hIj:_(9XQ|C:)" )I[O_&U(&(s/u!Mg7-vt"ld0P8FEDjz lNGIUXZg:)XvND@/j]kSV&E7Sb׻FcQ:G^EHQ| 64 ؔvf:+W2 HJ Iqkhӈ/*֔+jTSۡèl%j[>6knG*9e* nd5].@\HhCҚ(Q;w-*i+om{2*W919sv)5=uvm0LD#aN\$`pzQ/;b Ӿ Jb i }n:7*B8\g$Lm\Z+#oqec_.k!If30yR\lZssg }cLe :_b-j 0u59u#m arhD00}ZMӞfER5?EߩQm?5=kZ.1s׿2z)A$qs]ˋNTStV`k&qH  ][^ t:KȾ7:O){m"< l \9dkn1X,Swʋ:倿-u ';wsC(Љ^PH\1t5ꡡeNC#[Sw_عG.k 4E ywޙ޻>fᇈu95cN*s*@ pة>yisbn%2|2J衱CTz׃x>)۾Lgʽ0ww8<2mf%]>;+#9e۾ 8i@Or)? {'R?ޒSK>⋀K0;.s|;@K@D ۨ׻3b:vۋC ? йS;3c&Ğb7>鳂?S0*AzAk"D›kBxBߋBKACt+;<CǘPK>hC ڨ,ΨǠ3P '+v*$D$H|N MPDMk4M* щp6U,RPZ:Z[ҭCՠ3(m*-T[@\QO 1QXEM<78M!e % I&= VNCXDUv`ԜOS-'445MTٌS[1#Վ4P*> ;{աUn֗̏INivҷոUUUKEVBLVF[V;EW(-Z}q#s"*h62n}X?) qE?s]`jo))`ן,Ӆ|W48;. )V Ƙ]8RԺYTadՅUؖ@4 ٣I- =eYjY){ŠLًL&F>ُ=ՅE :[e Evڄ!D2r%W}hZyڨN JZӿX,zU0>ǀYl XM۠FDkM-ݸ7R[e'\!m…Ýĵ3Ft6ܮ-U !U^V\M/Vq0]EmbݻXZm ؏MMM3-YԭZtɕhD]X}^+;޹u[C6:SˍuZMߐY%ޡ٧Wy_FݔP3k%`@r'k R  4_`m VE `5G$}Mpٱ"3|Vu#Kӯ)fPWvb50YV33HC^c} 7~>!BcMb:c)dW>~˂I^-n/~^F~dMFdcLFOPV;#KPUfe dA.OOCaMdme]>c^Vc|K f46QcdZ Nfg`i.)޿fWFf4][Fq4 $4N`XLdMvgeyjٻDx}l!/Fd[~#щ<f3γǬOuUN'Bgcvi29h-SKSWIzArfs;Xf b~%F0q鞕i i!bkLQ42yf)56Khzk/܅xꉈfa "GJ.f[ci4VF=dgk빆B=:>mh%lV̫\]LVXǞ>TNíAdQluݩlf}lmjĞmtNN뱾DTek1nh2ڹnnTnJz.~Y^#iVmfo3~T fpevfD;jļ=f[oudfK2vln w0PIW lphn~boXŨVF2Or I~G} .j8r$ %&'N -=&^041a2g8VoU/h(E?6ABm YsVns^huX?Gmt !tx0ftܑV*cIVJ7o34>59tQORc϶s xfWuuumE_a^_P sHZM/iKvk2hvDKhH8jNuUvowrX.RY>r1NpBxTcw;fw viNn(L 瀇pxn)"i$#>}:E& 'dݢiYM; kU *Vb3« @joguoߢ*߂BZȴof6FkKtݺ0:kh|{\őe3mf=ZP/AY4>48`1pkILj*ï-p@XWWоsuUV 7 IK q UZeÄb{1|f" bE몭.0#|_ϭ1kBHG'=(D4Un`Sgyqu#u5<9OڡwmR]u\G 3W~J6`QڴZOŸŞBkuB˘|a0#1nJt^#w]OWre|?<-fzjd\dͭ&8Q `D |MrڤO닒v) ~ ^.ǜYNM#ϡ̀eЄ=]G{TP >NA3X (|+KZr)wRgm#4C0#s;W/dnz^ "t#$m|R (l-7NrགV4ʸmk̜nȐ%"|Lw$ A"sFVIqRK+`BQiT<?wx37gR2s ChJ9KIB2ֆ"D+$jEz\vAnP{]ۥE.]L}p2qU#i{^øOrIc߷l~CRU-p^& wBo :dл[̆a}@+W 5H8r #=^A@Zx>2[߈xG! _g℡6XVK'lxiѬ5<4N`sp\2Τ ;A؜}+|٬2~ FPDNm]ߺ28ji!ɣScA \\ ʑzNt#:X!˘メc{="00 1Zc>>fE?>#jZ#AAB~Ta_'I$.Rda;j$.Jc|Poogp g\t̥r]Th tJ~^ؽ&v[{ |lCxnoglg{pH^]rh}J~zR*`kefM$(fn6(z>(eb&I\A$-hq, ߈ zJ^"ƀng(LhygS>l҂]:mբ)i6<)M)h~H^T <\%i[W"^&fIb6fBW. HRh;%J1ee禖ޘtܧmĕǟ2y ꪂ@V*)jJr,tivV$k>aĝkq$ EvD*̫ڵʪUS*ǐZC*渚+3WA+򩻂kJ뽚"Y).NdW _xy.i"lši<,~gMZ0nlv~,΅H lN..ݰLB]$$Jld#Y8}H^ (6ʟvjj9HGY&[*t-nc؊g2m!۾b--iխmQ-Yx.Z(JʧFCۺ-Bd(6,Dގ*:&mUnR`iꬶ=^XtNnܒ#nނ6kc&-/U>0"]$-nn~難ff ;|o"x>n,nR(6'/5t(YZ.Jm 0JJ|4/s.Z'n(bk}h 힣–蚰ph/dr@ Vp0mpG",qK^`$q Wnpq;H1 WNXb1p\#a֧֋ѱ1m%W%woOqpf1 -YVqI>)q L2-8X2/S2 g݉o1K|צ?-I#2r,Dz2V-%20 HFz23mjp0"Q32S^sl,IritǠ3(ry< <7<=G="*"?ot#0rtizj+ᡸs&jql"M=W4)a4\GﴃAA~t8Hu(tJ;tжt>zƴ#=\F7 Ot!iuue-SgsQ&U4~Mgt5鲭5QRR+4uHu2udUrEE_W_宁#hvPsZ[J5=:uCMP^4dJ$6p$M',IQkϣAvev̙3mu(ŏgJT7y^z@+qjcFs?vl۵t9]7SFM@rw7ᚎrYww cqs6Kw3uu_6V6(A=w߅wHexx19zOuxA/7sS8}_xtgxmm{8fqN/O*w74H7HBW1SCwZ0ne1,y4T }JyדW,`oeU$7JKwi|9HƮm x9P8No7tȝCxCh:%?"գ\Dz.LzE 0 ;L_:f$gԾ#ƎY y]qhW'H: vCG;Y40WóC,Bq䋶;vGS eGظS]e+GIzw;ּWzx7q˰s#\W7`T{Aiܧ1Y;>4=K>(NUs}UjVc. 1^ >Ͽ~#~K>4>~%e3~(A$/^..oHSo74xaB s(aDxcwߩSnݶmRIB@J4xC*hִygN6ehPCedFRKpTG"[4D+/`„u. zmZkٶM n\hѠխKМ@R!Ŀf4cǏ!Goef P@&28(ƨ:ɴ+j6'(BBqƪ/,^e2sFP"CP0 21"!t:(\(&*iK*cKJݪd3"74NPsNDrOIB?<4QM[.CE# RK/Sq!OR6Tr1SPIU_*CXsRVC[uH͊:A_aq+>*V2mvNĠ-2<#ˎ aAWB'hg8=G{a6lˡQttW\W̤VnYo> |> vXwȷ,oM rwEůؕnFϮEꭿuTg}k*˶=U(WzEW\Z6`"=0.׸eX+Ȩ8"%xA!LwB\RԖqUq0Zg?p :6 oϫ x  ?Ğ9*dulv8 6@ .ŲU+è?~/T,bF7>8 X@퐇I bG?*Ё| 2E*ZvvE.ZƋ5$/0}e,T85эlc BG9ŀxD P%ՠX)zkK7H8b]$)0KwB;&w.T|o4vAJLygI\"lAfDĥ'T,ZfI戞f: eF Gkє!7yM"SzT 2ZJQHJ x]ϙw XFh>``5Xʄ*tt4C IpJ,%*^pBhM虓yJ(UQfҟPO4i 5DotO# z֛Q5)|)0Z+Zu(X,N0}bXRwDkDYֶՇp+h8 S`Lw4*&qh-TlEyPN:oTAjED$Z&TFN=TmbLiYݱ'4f]h @4HzY"HZ,H(It'O s%V5k \HS,5iK][^!Ȭ\goCOA߱1SQp ^`Z'DF0Mݽew^Xc 2ZN$b>,0|#s%Voa.ҎxģhǜA j̙"h]vE ~3ȉ6) \ ґFŖ݅wV]Y&P0j qG`QkVGF6|ZZE-ZO"B@y xw#?t=9n]s6a/H*;p0O~w~7@׿`,O"\brއ+/3P73p >p Cp J7OGPG[M}M_VWP i <@ ! ,^ :hĊMH\a8pȱƊ adxqɓ(1$yQʗ0c@IӤ8 T,\݂sǴSwKv+G!$v6E(\e;ڮFʝ}x˷߿ ˯Æ]޶m@0X-IzdӨk6$ׯ9]@fPGTE#]Z6FDxlFy`6r3T(Ru-20r jy:Fvwd8-u h*!w#K2W59nފ'8AQޢ^)/hO+ >]D+5" \d~KX,dXB |@,P r[7<9m>0.ک\^ #qw=x}0hl AҸ63chF*b@Fp j-cvȈT`"PL!5]J!^ )zdB݃tNF7 # a„q|Ԇ6KѹLe 'E\ 0C8TEyh$(AE.}&8+V.1IDTɸ 4k PVMsN"ԥ.1^@C"Jԁ I%j٘b0,fH3\Y;RHI:Qi^MϡLx/(ТҠ]Ŗ'ӡTb#d@S~{ܨF;Qfs`I' eX3\jSno4}'s]x8gu^M3ڌܽsRzc 06&kLHv!hgcvrO&N^YEiOL׀|O!Ȏg?)0KvxW+O},?Ϗwۉ]՛)777)`k3^r}ԋ_Eu7||Cu]W}%5nymI!-r#swz+ă'w\~`~wIRKbPPh4xI8'&l78PEX7 Їq !%?P!7k--g,\~\({#Xo%sabi+6.Q0Gg{9|OG/׃||E`E g MOf],nDž]Љh6dhV&(Rhk m8t'*t{cxt}XxX|J@χqנuQ~8X9(~x~f8R,TxtH6ܓ;0}˜hR({mь(fh"s\~tȆRox%'7MȋOGHQxrV3Y萐8Wi~-ŃMQcLq=q8B9HH^Y;eA5 P0ɈB9rf؇eψFY@9CwF9q痦*VIX{畔Y>(I hikm)ps9vm (h։Ѩj{9~gA!6dș$WG8yܖÚ{ HPbv#Cy1ՊiřǙI㜺xuC.)ՄƏɝjƏ9Y@ IǛQip*`\i)H32jfzO8kzIȚڅD֡)w ,f#J P;^;1!d:|kdYL0gչ}m`WsMS4ϘMEʅ Y~yMbsP*f(H{+؂IXPPG@(@@cJy"q7UxpzK!tv<vMv %\qPۙ^$ @j)";ǩ؊(:;{J%jcYy v,Sʫ@]Zšw*ʞȺ*WjKaa]ڰʩ zxڙOI#~2؄fte,]Z֨ *YJ{H K;"Ib|c)~OڱXQe7<%{wʲ Zƪ1Fj7[lP@ʭjZ!|y+S;?UyX ʵ^<9ek;N*bhV paGA{xxʇ׷~KQKVqp([~c* RK:YVʹmۊ=+gB U -L˙%9JyQPs=)G "hcZ{ɛ˻ΛPKv6YpۺB9 h4euY`k{+;%LKѳшGDՋaчg`Ҳzd.3MЃb6Іj$Cm4\L}M x S ;`Ch-կeҨ ʤ1ei]k= ={o %*t]^6|9\|=- RLHYV [M7ElU;v E*ٓM<pL4"ۊ\׬ ǯhY۶؏]JÝ%:Dz]|D%׽h*E ̇Gq]_h=,qã[,͟\8 ϮJsড় |B-qL~0ݺ- */n-Vʏm~91;^?>Hi>Fѩ`)-Vp=Nk29~"78m LpF4],o,@VMn ÈQVtHܳy薻NpH.C6lɤ昞vo/7l@ЋEϟ Œ LfAֲ-f C%N΢Et/G!?#Y$I})Uv'ɒ.{QOA#.:In L:@>l kaŎYfQ`۷[?ĕ[.Yde#@ihԬ[/&X0aB )NQƞ-e 3(K7/$)7UL}S(P*)TU^jk׼z .[exhRe È3&2CClfΝM~'"l,eԿ(z**vÊzNZࢫ9d0t(b8ĊQFOvJ +=B $"h"qO؀2j6m 7+)9˒- rBP*1^fD!H Jq;Fd OhhG4% kQ|d)I%ĭ@@.*a0r8RaTRGTT/6pZnISZTVe汆"çFAS>=Eo2<2t`DǦ|ڱڟtI)->0OE+/QKUWT=9`Wx"VYk[uE_u"&evX&tґxb =Nk"?%[pWs[PZTwUu^z}cx`9 Vis-HcV(vGX9ncmIݾ Z]_7Sg^L  әg[q`_>xXfxi!i>2>kktd'K֔-NkMB{sٺ?:_9o |v("|bÝE׫qB~몼V-Gla-=,۞:8UVSn6i7OoOG8Z:}Շok]ӴE]#`􄳶MCq; P `s> "[j>΃:IGlR0F?T.mÇnѯnAQF$TD>ʈ0~ S8yƮ8t`FtEkxczsߐxGe1P5/%*3[X% cU815Vu%GTqvdQB2[ (<9mc$sx/V ;&{:O`#e3Sv! IzrVH !g ̥.K N|c29Sx)9d@MY: +@^g/˜Kt):Vfg1LOfC}>]&sPxJAWA4RThZR=\Dm8ƳѐJ7 X̳nD44(͇JRFL' BP23*tsQO}TXk\U%I7ҕQbEYD xA{ӠƵAsQ ɴf8YA1 6=9vb'ǺǂG.bݱ @j:P7k3C<?7[ˋ ?r@ ,Uƒ. ,?' ɋ&8 Aۧ g a:364:+8ѲwJڱJ$B?+A(D@m:s/ wk:)L TIpsͮ4T?k-'5ʅܤb8K$Ewԉ㤌8K4z> %jNtGԼN tIDLCjHܱŰ eO4]| CKOu}RI>|ԀʾD=뤔nQ2<˅L!LPA7MKi5QcݱR(,Kԍ8 ^ 1Q2-ST3,: 64qP&S'mNN--)0P8}ēTy@5MD,N KGlu&V{<)eWm XUZ}Y]ŒՄ܋Q EHptS$EVpR-RViWгGk&GWnV>p!xsU\"u.vIwU`9Tc+z=NAT^fug|CqSjE$S؅}oeprEX#SE 3p{UҋPͳZKS#YSw{R]ؠZC-ZѤm-qZx6:z!Zdٯ "%5ΣٚXSM[Iیچ}Uۉأ*NAMD4-M=rWZsHŝ aY`_d;)ZU^>S.JF5&!۞C b6Rb*b`Pb-Ѕ^(bQe~JMda?06u3_2kc `Td{\wcRDߕ߈*S)H ,&`.V 7Gd]QG^_$Fz0b:&cTNs]+@ޒk(zZCTe#e`,G ~uQLHMfUѻ i%j$*>f~Vb9Y\#}Dc_vfC7Fg 2e&qMe~neVamՂ6h@hۄ'LLr&Ju|ddf߽h%F͘E|MO fޚ.i}N8Vmb?..ZVL^ sxq錤Yl^l^ʶ.f59vjlikjj^io RS$JI ٵmbx>#2,~neJjn˲N|Onsp^Rf:.n@gk@<nIPb.pd܎OVcnn>W6? ObK0lq]qp!q!哺Vuo~pEO{ZaZs8qr2"7.o-f:VZUgh>,4=o3mtm )82wK j4dt6&ˇ''o^Z^G<-o?υZ su#tB E_~t vtȎl`H t3P='{18ZT'V׆DXuu#l8M=v=-R/n@CAqkplvmtu ک_qt{qGwQuݐCe6WA!w[n_wobqXaOPOvO[vvUp/6lwiB `'w-yGw[w/IxŘtv`xS"y_yy/c/ze_"pW`n6Tc{Ӈzx)pzUGu>5( m7u`c|>E %y)/|Ïeu7iv[65ȿw$|Lwb}/}7{_/wwnt} {7[|}'\:'}?ZG-/AoozGFf w,h %2\8!D 'Rlh70fF9l(%Nx&lk2g^SF&Ne:wН. yR8qZkj RRj*֬r+ذbӡ+k,ڲ֮Unݺmr"޼xCWhxD rdG"G<֫04ggDw:ХI?n-3+qgˆm;Xhs֝,۶oέ{7޾3oᇊ/ E4Q'̺mmR>Q$0ϼ6m{w?Y[pɵ ]v!_9``E'uYGmIAX^f17yHPن~i&lH-_ BxANXavDu.xP?Vb"uj1wxej"jdz%OI)|oz[njNTj ;y~:( *1䢌` $ifY ѺHԧzjVjU*N\FAڧrF l&(![ң)Ml*\iiʸD1r:Tz;*nEQ"*]۠"0ж( şF, )0 4|F9pl=4-hyRKgӮAu S]5Wo@9$-/̾kvu}ن"[eotF}7sot߭.SI:.Y)d yJy 6+u6ϏJ .ǝPIRdF:l7R$_֊N|PAN$Ou96J7u|m llA?i1aIW* 0O0 @2&|`4IpG iTE@$f% S6M 1Cli.!sZ!UD&DQ\Ck%oANd"bFY^|$$Ǣ1i7ɐ:M#IQ3*61hZӅŚ ( T^ *\g1(2 SY|3'jIUMeM8$'Bq+Ygkm[JI*~fpv_OW ӬVخ/ kjZVom]9~qE*]A@zK*US XtTUWf*\J%i:-d)"YčqNQR$G:ׯFn`:(^ٝ5k|Wxϧ}kz3JNU̽ haabM*[x\gGt2pÂ@BᴟxK>$)\ s5&{Ek- 1)ZT1~leH,q}c͒BegzvxL6aE=S#¸ˉ\! eq:5SnԜ3 xvg*Oʃ.ZDRŢ"+Uĥt7MzJfqdQKB+t36D^Fug n F=F}7'S6,ʮeLcH>抰j{j*QW544S鮇="^U[.~7og"9TY+lwQTu5pRqx#nS>uSCs9ZU:rb1~b#ʙ5"HNH Id@$NdU K9$`dD1Rd2v^O 1D"2X>[%R.?6S>%K&d} C_?%_u'r :Fe؜!G%#VZq$"Q9\&9lũTae?gc0\W fs@,bc:&\5@fddeNfff:Ӫgg:$K!ij.X!PFQ6fᦐ馥@.lfM/rR5>'a2Lkڣue1Z^zqyKdߦoJyyTa'Mgijj#| k"}ZNuf-~¬e\w(Q(WozxfŁڙG:|f&Z{Jh Va,}4^I~֦mPw{h8Nl'FhVz@1'fjh K*Z*fi\OQ%)\<h U'TW8X VziORNi4ZV`ԣ^ܤě)yʓr!ETeWX*vmM &*_YnX#)U*Nݼ0hjRy"V2)W*@2(~ibZ _*}BTZ+"g)6Y婞*=+϶v~FjG+k\Tj\zֻfۋck0X ]Ҫc !6ظ+R \XjjѡHVMq_*Er),|l}d a_QƬjfyPVlpiyZ> 0*iV*mC|J-XD+9]ve"ZG؞h *%j"d)k8mR5|*e~.,rlf..jgVM1Un. PtB|.芮fa )p rm.fZ"!z-l2ZE譶ªj./qaelʮuo~B/ܮmCn_ oZկ"O"j0 8XƖpLBY 2/o0Lqu<4V+/nkoES㲰/>; &pB '0˚do9 oYOcV.0 EWp!aH"FA*,eyۦ܆ITCWprٖ&2E,rk,"M i0q 1yC.4XL) pnӺs-F7@-s@]JYi~%e[ơsk@q\ȎE44<,m#K7_9]Rjq N3  HIsNīt݌c(R>4;[DEαGgHܖ.2#W0J[-p :?""- Y iM?‘N fP D5-4JRgZT4/e$ZuaഋtWW+?4c YtR?Q[,Q4:5M_MruWQO6Pj.paoau-vFe=\GdG]e;X6 vLo+S O0A4Y&F!k!?&eWLi(~9N岲_ v 4@q-uE\N_r:3I>ɶVmnuvC>v 25x׀2n.2Cp-kD`rw6B 5E?\6 :wmw?g hDb"B08#ʲ7.9C*g[n{I&KXxǀO8*xwvvxuyu&vbwljbV}'fo aDyӋ{+/85;[p >9g]9ndr~9WW`k7Nꕗ@pwFRN,z(\Vc2:ęHzjՑCqYL٦pK$/cXa:*MVJ:y߬{qZ;J@ z71;*Q{3ei2S:ɸmɹ;;hװ;z2~( ʐ&9%E"+E |Ş| H44@8KoT|zeae70Ӣ|ɻy|=ЃV=ϊ9T{|$B{ypux/~&#Gs@lMX ;?VJqt鳖~~Le(Qxl>>37l&_ ˰8#ʾ>ǾQT7,.a 4xaB5t0ZDax89vq9#G4dJXt%I0߽SnݶmRIB@Ph(BKpDTS"zU[vZi c0CG"E U\T֥ o^xuw.THqOt|"F(S>y٤ʒutt3Gό9]gj̹OC8썧NU|8p[{gӪmS'v߂|bOre;n,~ϩKm&<} ( lC*7v;v .8*AC,[-梮00C,l"n%\ӤEE+` UQ/Lr@.'9V8>-ATܶt(BʤUVEX{ӢYs}^]̑XπVz}&G?e)mIhYДڄJ+u \VELW]d3ސEgy{շ{6Qͥ~I]٧BXaj*ۈ%T*Ì1tX1YE"ȬWlv ?u^4c! ț-%kg*A'F4X鼘in9Z2yzt^9r["%Jmg᎛CzU cSD pO|ܯqޱrYif ]G; T/,"XOnZ-=wO\P_.odӮ-t~JJ.zSH|QḞ;]C#{GX3/vt p6VJ'/z9ľՖ?(p} ^"heI,]`: H"t KvF !ReJt7sH mћt@5 !XH%PT$#?#UFȡ mx6zLz#4)hL0n  aS-ݐ r\W# Td C@`aR6 /| )F^/PJUdbMԦ2i L5;֔AU7iώ P5MoXEO6^TԎ\0'8q Da U&%@Vڈ)UKD%I7PS7A[@N]i.XRd5i1Ӝ4wH|2z :U,hP5T+o,$dJ[ 6-& R1pX` ;H4se K]L#vC|YN0wP/0:@IX¢+d-[ߙ^L~u>fҿMlڮ6˘8ZV % _6p/(a*@H\ PY?11Ye}Uu[fʝkgصF&ؐpÈ+^81F9z,q!DhFrn06ӥ:+iحk+V*R$=Z'O9'ȓ+_μsu1sc$p^$3V|b%;T"$qG(PCM6$` hCrq5.`y>hNDէ 7,󩧟jxک!SHFAk4 ld% 3" ӟiHk Y{m܆ r${H ~*s4Z3jM @B %`t>q'FeXOjZKsi __Gh6D6I8-K16<ۘO mkJ#R .R H>YwXjk ';pX-q # #t; /|k7wsNcF<`(TO5ԐCvpR-_9~q%ꓮZǯ5? 4qm߾(& {['9D# Hq#ZѲ ܡKF6q/YK wsr͜a `ױ Ő.>4 X$p li"nlyBU jAB"Fq f#%\6Q5I,WH*9 @\g![qZd{Kp$&1eLeH#Ec4T0nlztI&pq p(M)i)&/^iT;N4.OM@%wC擈L4&QyTT/XD5Ɏf@B"J 3iڹV+PJ٪-)>{KCMF! Bأ NoK,QvIPh̞zNUUE%U(N8IIW}+MYbMzPtzύs`"n+=Srb&ѠDB%Tfzòͮf1M9hR -hs~/8+m[@5tP /ysnwW" 0;܄8bzX9w@6պ.fmY >`a FA6;^!I)Tq- iF_Ym^{{D>N*v}Oo4`KŽ0/8(0@890(\lw眩cn&gk>xm.}<>9HLŲmi8@Y}.l+YDB? x c6̪48ΨNKcx 𫧞yZ?z4 ݂͵Ww^+&"A*8N.f#̪! \={ra:s` W(v,iQQd&7]= gD?Npt&t\u$f9b91'0`qvi]t3g2;:XH LtO1NU "k !ЀjsZ x`HX}x}Xu"GD )yz@rk0( ~#GTG\aowƶLBD)G{5 S?0hSHg)=` ̈́wacg[j#dž`qt/hm`k<(p'ww'Ex]. pW=$H9fضx hԅxcȊ}]}&j(hz*((Vv-viD5M#Ճ8wnj)ws [b^C$:vcFF清TGȎ"qjoۧ%( gz  ٔE\j@AHe^8U葻'@SbAvE@, ](Q$bHj< [نjяp1E2z0(zR )xTMŌUEؕuI^{b &0p)[BIY`w{}8cijSt6)DiY%X%zP T\IYlLeZYΈ{^IaCQ e9m;&;{IV[0x0 )9}¹Zf(_Vʹ?Y17ne?ax)3y{PT{H+C_`w޳qvjE _ z N蜹#Sً XK@QT\HYYycꈑp pT`_@w+ѵ P #4gz"y*&L*gy*ovJ i*Xn2 ٌ1bY `j z+*hvy:٧}Ч4}J~{V=鐼ؤIbU@2M@p׀ (30a ` j - $+ee QE+:yƪ@ZX3 ,bQrǘŭOjoSDڲꪪPigzA:&w{\h ™ ۨ괎J"]d8"e$[ * -{/ˮ3kx` @ ڳ> *G ˊӷLV[[/g~,^]ۭ[ P)Pi 銈ڪ[g戾2 p 0 >&ҷejDz;P v !ظS;is𦩝۹ U:ff˲iKm붬b'R K۷B+ڻHKJKiQ ɋn&_;b [ \ UJԽhCXI,3{ 7W{kn]Dbǩ_ ZxpKs\qu11pnGoy 8.,02ܼVk6,##+^=l A\~CGwK Lnl5Kx;Ŋ갌T;] aLdL$)Y̷Ʃ+3wFrXǮ y|ú|<V|{UӚa~˲ ŊșȺi`;K+i'h|JLP ƌL(ո7pXA%4˱û$aFEjLlӼA,y W i֤WRZ|δ,b7ι`kmEPf;< {8J[0wj WsjF-<@:d1у_{"Vh?,ҫڌ<귐t{z\c}9Ӫx?YB}lF} RJ=-L 朡 ҞAA"[=fzO,ʢīĪ֫{Ӿkԧ9q2tw I#mm,LYT}؉؋eZݖh|ҫZn|=Hœ=6}E#m͸}׫=}۝ԑPak}5]gDͺZIˁw֬X ]ױ0β<`ۻͽ -ߝMm١\D-(Niಁyݮ NՋȉ}ތPޫឍw!.$Ύ}],E-㬽bW$> p] p ֠o=KN0M^XӬ˻aX嫈^|a?lqZ PHvWAk y~肎~ܝ.挮ˎnb>,_n .i=z~Y> a븬Vmf@`0 px,NʝmU~G ]N9^ny,=;,ܬMn^6՜i6 eah 1^1@ ȍK1^>!O-9`//~389|?_ڪ]CEO4H/ M_APf; -U_Wy\8anm/^P덾X .dС>":mcJ D p ‡,X`%K1eDQM3u3L/\ ZtIsQdKN)V*nڵ+:t\YfeWsqΕ{ݹc+XN$\'Vw1ƎCYdʣ/_&gН?ڲQJ2Ѩ[ʨZ[]UM[n[n}N`ѥS$\c=\$J̚5&_ϞY&jNDMvVZʼn[K[~oCn۫{."P!ӈ&2T%B '#m(c j?@2@ ZA&Tr 302LTR*hzA=`1mNF̂Ӫq@to/$!\P"I$r˪,AR.3L0{t>F9lkNV6kًV\[ # *0QE-l4x.'K1ZNÌRL Ul5M1-ZUճw+^y^s_-{J,cmLY Yh%=jڝ.vbMi'ou5^E˫uK^^+VѾ\S9}LA&`dzf2}$i/icpm`YPͪ7MZƔe6GfkyN^Şy9$ߑ{|&ذRCU&er鉣I' w-ẫ3*l"{ݴ;_o~禛 [oƄ6|&pGbŧnQ] ,ZGCɎe RISQUFiJS2͜S 8 -g= !)!L/!, nKL#"1>Mn2):h:A ^τ)ǒON,ݝq m^3?[nxa%:"05ꤝT4<AFIe *?iq]ƺꖃ B5Bu1r[R#`O|&-F2*U2S]/fLJM-9-NB*˞^_GPiQtQK4іT7zK&Ty1UzUm? ְU7Ul7z:G}$\FIRwkź%QT%`hQ,5K/ƾ- Zv*,Ŭ]" qJђ(2WElE'/f_d[@fn}eq' PV$3My,tq_*WsS MTWmm3bBܸ0xKqO,XWvZkA jВOQE}{bV/tav#YMqWzǏ[6sKjt*n[\/nls/Io͓AjGԎ4=Qw#4qe`>[wK_Tu@9ΟY"uo0\v X;~mSGOVO[^cBϑǝ٧yVo_=- yI.^Qr/Cғ;;Ӭɾ&Y:+zR?!y !⢻Y;= 3iA@bٳ%m#?8 ̽:@W5#c@":>L4~I ?$~7 5АAwҫoA $]#B:Qc²a 5+ A™uPBsA,@ ㌼0Kς4AAóCӯ7©Î;®KB&l!yB(A)#˷R!-S=a.%x{6tP8++ ERKB DZzGB,E_>MF/L2d$x«F43x$ƕJ6kD\#IVIH"0>x†$˷Iğ|FlǍ GLJ D$IFJ GsJ́řA3s ALK+9iˋ|w|9K`ʦ${JDɿA0L@K|Q̇$A?XLLT+ ,ⴤϴ,CMj?ibMq{\H[MdˠݔD̜\/lO KdCOj=*L}EɢY ӑN`Q̢K\;>NX5\ͯPZ,•#auP4O N, ur {K]RmI L.=JqQrhN\HQ~ MOa\ :)IdR' (O*ݯӢ -mQ.I)b-O MQ5%6H<S:O;FsSkS}E1T{1m}TIuO"u4IMN=E,QQˣuA-UUECETwSG5SHc$&]%&_-`(Fc:R%z7iEE km֑8a\PH3Nԁ smC@X3k׬yLT׍ ӸW[ݬk,R|ʃF]mX:TrCiESkE^䁏͐ ґ%PCYMeXB{IvYa&x ]P YLZ-Z:_`Vt| Y`JaĬV$]]KYPC"YUmM#J}ݎ9b*=+^vɗ??1|_V2Yex<_3,a%0C'pc]cb-_b8@"'|;Ο L)(`d]nkeKZcZuNKQNYaJՒ!ͩXWIg`_ c璴]P6jgQ}~fy՗FPhpGD=KhuL.uf~gڀmg8nOPg%aNNmkNe]a˓^_g+3ÞB~%TB1U6۾6a@fk%O l1`<cȖ6$ نfhmHؾfgΠ6U LR^]nmK[ noo*M[nnn뾐&p6polĹ01YEWVI =0qAEQVn%ٮL1,Fƥ];qNpj ś T h o j(*fFjn]bu.tC?Bo~/?fd5TR4s ]l[6onjBX-d 3& %FtY?tE^e%n6tZ/ ;%T"眅!ݸI3ۿo޷G7"m9ure-mYZ-]ool`l\*v;KegvpvkkvocX/Y?r:ց$wG9ZbccwRdgf}GhnrxۂGn_oeWqn7 %zfYAnaa|Wv/wЦέ惒yכazzlP qݢ~ڥoz'{7Ų5pNέG{E{. A|̷f.qr _XwwzʵtXuqyz^ \lgKl&hoik||ZG]/nyէ*}wz-}goǿ +uoUI\{ͦzwWekZf Bq kPÇ"Rh"ƌC;u궑lVJ *Wp ‡,fҬ9SE:wTNB-jT舤<2Eaf9laO'T^+دƒ-k,Zֲm+-arkn۵w*w  „ !nL71Ȓvrdɓ)Yt SӧLy(ꤪW͔NCGJj\7pyޥ;w/5,pa +^+V)6(9w~OM.FYvNӧ9 EXnh`p 5.{Gr.72=Y3u,v!QGyYxגyVO|5&cS5:V E܃I09sJ)vXd茸F%vwGghS8mW#7l6I5W8$E*rIBdrRF6Pf(PYXeU%RZea8&gex&M覛pYߜ<&UU3$C H *{1z@^+4]%G*{d^fѲ]x"hZm*9Fk\kJX-t2峖n*ծ(j0y*jd떥ĹnvkF/.-S0ўHdbM?ڰCnO@qb,u+PN,,;ֲ09<ELwVŨ-V V6k4MB5`VMYSJ]eY?Z j/ٟihqAh[@U~uM;佈9EJy= /yۻu 5 7L~}kziVcYJ)|ᅲ(jvlg;,C%WZ" &u,[da"3KɤKIy{_pv 0T+AG X"-}R's K@d#)̄ 2LܠV .. 6|ӛ$g8(ŇO_)AY^62]|aL'l>gϟpVD@͗Bj>4ǰ߸+NҦ'a;k#GqKwy)[~qL[iu*E +ե3}8,GHz<}-X~G]AT<-X=[?\`?Qq`Zퟶ^EPZ Yu$ "Y fDyh@L Ȓ̠ݽ^v!za,_QG@.NDً  b%vab^Q2*D"9Aa"^8Ș"W#BGN"MTj!~'r-( )"m*W+fH,2!%n \b E.Z "/mؠ0d16X#*#0E3J4>!&R&b6n#Yt#ܙ)8 $A9db82D3;N@U6>.cc@d 8I$-d^B6;"D2 $ `f" e6a}LYH#!rJFTN%J8bcɤФRdN A`. CM%}d U%^]%Za%%O!5#¢$:MNNj4aZY'[uם H*8DKd^&UJOie``RTLL&C.&cD_ZZLeM q|d4 6T%&rF%id_YffOeaWrNl4ң&P aEif)F7Rr&i*Tg^sH`SH,<@p@]tK;ʣ @e0d!if2ښhnnJ2*~E*i\\H+Ţ^먶ҽuF뾶jٿ2| ,*‚ZL:N,>I\ ]kC,*|Ӓ˦ZZxob,Vײgj.UCZ6uD.H&NZ'bci&+~--͊mІǦjJkRgpnG˺~PZ-¹>,`.Ng:+:%FDLXb.nH=~.߻6./ ^8`Qu,ֈ/dZPP%.~j%BO(wHn/ᆰIU݂oI# 66R+ONF.pOH5ZDkSbKoApMH0$ {ˇ /1d0_0p ;RY Lt RK/!(, h5 /V ƼfCb+ʪ4lMqZOoiD|"JN 1/2I,Vne [ Zr.q]Rt=pҮ!*'* KTBࠧ-k&.O0!k%-c/K)0s{1O:&)r *dUPV5k|s6˟.3sD9{MG9#:/:2<@qB'Գ]36s3FO8QGEp11- sHQ3EF7FG4BX I306\0nrKWB514N#/nG ѮAwQsCɭ+CSUW\\5VV4 z-z5F-}IuK5I[[35J/]S5]^kPm*`` ,a j(vud5RC6m^'X6fg]ov|MvW?nXtjPkP,u}9lӶi95U0v^s6pGpsYyq_$ux su;uvPBng6^V6 _Oym`_Ǿ27bt h}ӦOY Do2fow .^&_0WK 8Z,ڦ28'jlO8bwA#-`ww_bmήx{7wxC/E9u @D(hx_ys&9#yB 7u29d}T˵S %ex1yxm-98cLN748wcP6'l0l]zzx3of6z8ax$cMv e^v\U:71kg{zhv:Fl4[xAu6u;7sroqR`'փ-aD<{K} IԀde6rp~;:K3O:ј̟=;l;S;~P;;[ma 2ǵc|~wU|6_]Oߡ|cҷwu:=A}Կ'lcZAl+I51|K=/={{Ҽ>DNǽ{޽/sظKU8sO4qte>7=ߞjǣ-.M{Ͻ{(>>};#%B$ }=] ?Dzzҳ.&=󿢼Ӻ8)vV?eno?=?HiD: 4!w S7mZfp8|(dH#Kx$yeJ(XI 3o`qFN6liԉ/aÈ5ziҤј6uZTSbjU*V[nzM\Xcɖ5{8W^n\-!C%Rb8PGhإH-Ksf暛qQgW*5)ꧫu8uÎölݻyMnر/8v noĉfPa)3Nsk <0S"pGkPίB:P$J<o%CV4Ţ^ 5ڱ KhNT2&)CCQR<ԋ#3K(9[k3=MӦ θTP IICLT,;i<{ bNr1d3 Mٜ9JUkv,sVuŒVp#Ȩ`,HSelYۜMPis#Sn|`]["hܾdVtӽ5Q^{̀LXPD|Ք-˫i+8a9.v$_]Rr1:7ËUcTޏ7a'{MeNUzyUiklgt9ЉͽV]SBީJIvYjKkTs [-9 ַ1W'ЛM?oNgVo8z+ocntrq]Ջ\'h0)5?C_^yQnB Feue*o?}oJ[6-~orl9Ip_БtA6'@x АWA]z^H`" hL?)-Wb|(X p+וZRVxwFA 1F/-<1 NbТ?QAN @@A4 4N? a a| ]0}5 !"X[kF2 $"JD(Q!Rd"'.;l U,Є&$hE^j*h4QnD:0]oz4a HA򆆔7 DzCT '*P:I1ٙrڌJ7iZex/'# ks[A $-CS?POޔaMͷ$*Y*BUnȣF)TghK2pu\Z׹.`v^J<<[1v VHX>d#XRf9;;|H+YҞ}ikZ_li;ip+ WBJ! ,^H0Z(\P!CbHBŊ%6"8B9r'/Ф$T"uƭ'ӳg.;-vm,2[ L!6ܸsͻo%{9Rh< 2(ރfFVymz(_^g} Ga^MNՠ8(8.<>eZmw!-4|yGL6y_~'Ĩ69x68m2f)hfihaVWB%)D+u:i'w%E/)12]z&W t5F)hUgh;ݸfk䜤f'Qd,M:ʪk](8cufZQSTX|Z{pjm zmn! /j[Z.]JZHoF7? ʙ-zZ("!$覫oIv쮁,B?d^6 Lm y̪& 1JI+z{)qE*2 PG %7(ZOf p]A1{d9$Ќ^G=NjFNͷU_,OZŵ(Ff7-jEk?.Cgȭ9#zy"ۋ:T]ٞ^e7<1-`D9'.J?oOZ){ /3d->ۣryOOQ{_@5|O wvA\ GBr"ҽvœlO$`o? !9!֯h^ IX%KKaa 08qG*,HFt;a eBk 1u١Z2ЊLZ٤ !t'1ʢ 猦e,xFes(!R$JVm^w񨠠t{T 3"&00"#(fFЌO$,h NbPFPIrSLg:d9Xc Jg!/%n .L# I^k&DAF]E'6: ^BgIҒ|JI6yfŅ= 8)GО`}AÈP.A&+4N4GAj<)JG˕4Bqri2pNQ0Lڕ@<HMjBhU7ڈ6v$bE.Zb]Y7Ζr=os] }e_:*c!–k- öʍ`m^.deedj&GV <[h-.j^yK3:)˷S"}X;uy COXt\/ E$"/\! x A7]Q_v3;qv?և>߭{K[t'^zᙌx$p(d;9A Xy6~z^Q_R{Lwuާw p0Wa7TtGF ԧ}7@>%PmiGvp zz j'{5{wk؅LxWķOaSvp| pQؗ,hZ:bPzcWr Wro7:+16k.(dk`]"KWHp6X 0TاOX'E0Pzp Xx g%^x~`Bcz.wfwcX{lhW:UHvl@{a}A PE p @bWJȈݷuݦS8z3fgވ X(^ 87(#*1(Pw?8Hw&=xg@ӆO;xZLW GFM(j@ 0 Ϩ}hmظf@zW 蘅%N.Is討Jnkx:>)o{(]؄yW )ŐtHpQI @wpٔuKxg#訅(+/&5#'1ȓ=isGI)RiZMP y8C:X XI[Ǹʈ _@cI!i3G fF.iM+y.x8Ҏ|iw 9oC{^3xfhySِ|(P @S@ٔgmAb%?IrYq16zzj;)c?y:7̙FH)LyWqBܙ"*F9y ` wH'i?# Tlɣx.#r9i7)|ʩ7IXI9JS)HP\9v0 LТ(9V66e:IɣlyrACv*V}5;IwwgZ{)d&ЉEwWZ\عd>ا@i UjJ(Jy_-zt&nPէ: (jD5rh{ǹsYw o֜щY٩Wʃx(cj!AYv(w; d6ߔT>z :{yʨMڠCw^%fWںzjꡗYZ亙lwMYmEHPzbM¯jK[{ʬƙҺBٰwRk0ydטqe8$;芢 nx rÓ\u?+3⛿(X'e 7J{ƜZRF9ڊȱ`ب`bp  @i뮌Hm3bt /&)y iǷ`JE˨I/u7[wD<@^ktZ;й x H7؈bWU MEQj덹 ໿J8Kg kDʻ R&+qٺ'ǡɽ_cW eI4/[Uj|}.^)i k|[pz ʻ ܸ *]\\vE9*{k³KǠ 绞B47[ګ@̖:ˉ1F H4LĒQ#J,_w%g>ƞ߻cǪSKp6ܶ-F[0 ,Pl(̋J\L<Jo2+ VYlO[ZW]L;_ʬ|®| @ p T1<Ëx#hy M`ǜɬz\KĤ| U|řl,Μb8Lŵ[P$ ǡcIm[]y Ō: -= |Xɑ;!Z%)#%0HJ |ܺrYr EmH2MO-]Vӓ2[a=dM(`']l,lmTR o ԇ99IP*6nB-Y| MF- ԁNmRȌ*=K Z՟(֖ƋݝƜVYڲU1ĠȀ ڈxHWQP6o;T Dm }܉= ؽg+MWQ*ޢ`TJ 7p>pIQ\5\Qȼ*؃mƍMN=ᖼ`Z[lb0ٙK((%) mn}w@b ymI@_@c`5b6[ۑX[nLi*2=P]}ᑹ*_ޔ#uf|m`J8T@cpX5~>'͐>Ύ՗nYw=p/:\^[Q~Xc_p# <,̐~~_.\~[ +(^X9~Tv+y0> `IPF1o?ʍ˞N@ $_}[nO*# PbqI$3_Qq֐/CоGM?Oo)P`K%\/ )/ ` 5Z ؒ/9LsOn @_NOdc.GT_Lˉ'(v[j8+ΪJ*r֊N/첻νs<ÊA<2=,0Fӏ?Gf+6L {0?x +PӁQ<+DڮD2uA45/ޣ>qSGrO>g6qK6%j2)2'[*2:+z/,4K5uN3FD</6ܐ*P*%K8}2B,"8ܴ/wTLPDRKTeVuqɍ^v(V?Y \i^PD-VR&Ҹ=*oA,g/LDkm;楳S) U̘:_]LIy]ޔwH~U`ra-5nᩀ #fk5S7cQiLƒbQze]sm$}BZJ>8ߖuAjz[GE7lYemE{Ղs_Anƚ;h[abgٚŬj,w9IvdU+"wruqim?H_GӇ2,W? |v4ucWE3*㓷wy=t]JF}*EL{[gEbg>mqGr[gOyӓ~nc m+`@@v D\pFrqH;C 4KtRvW7=-X84 1,Bw8ۑwW+Qg2F·_H탧"J~w?:Q* נ+0d2 ІcD_"( Okߘ8v B=Bя(lR )amQ]l"O1wW a M_O|KsS̏9hMF@Lt(8%Qn Q9mbq+ r+X>]zd5w#{b jM M](V=MewV5mTZV՟n@c ,IbpDʢi(\,3r+OZݽT {`Ұ SRjJ+8VsEN^ΉRz<2MGz Njie`zm, )&i:5l䊰 K0.Ƴ*gZlxf@q?{fc}3n{k ç`rהd4iFS0x0&Z2 ؝}UK[31L]- nzܔk>|+B5H 3H]@z-1 ֺaZ WҰ͖n$%\g]vZ.LiTGGb5PJ1Aɚg}⭉ :q(lTp2>"Ta$UFa&vU9ԦBg 4o ܲ b=^۬Tn"\H"Ut2iK' m8 _xrG Lwwqq—x41:'(?uNޡ@R($5JqgI| w]Q&zIҙ%B}R߆˭e] 7m^Ӱ|SS7~n=]mQbY$ߑJAl^㶇?n-=|/u:Ć)AB<@ DAt -|?擿 <2!Cy8 +Z3ǃ8(h:DR$D x-H=C,a1Y"$7K29IAs((BۼCؑ9ʚR5˻TI@3Kr$D̲H"nsLܒyG'&0͒䏼B˃TLHlW4̬b"-$MMH*4 |B-ۓ ̅|D:@j{g3O8NL?'=)G\&>.@ZuTTJULm+M}`Ua UTQMemAL.BWP,X Emm1}2]^U-W9WOsQhJx2ӐW׺l{mW/uDF Ȫ*U-XK=X UXN=WohAlϻ?ݷa՛Q)X[DI\ xa nmPM `!Ə^WTƧrNvXb@~1db [q,L4Wtb4`v=͕%F'6ob1Wмſ.&5{cR#]PcEװ67㟨c(~[\Sa*% *c9D.F FfHI~F}eϝTx=<)*庮 Q&e>$M`Ðw}IGY6VZIv^enW/ aBCFfe&:dFWAk@Of{R5\f;FaG+qVTNB6aP26vvwv9Lf>e"gg@نfHnogqa s6hX0]`:d膖KćghGTTE:>ܵh%83MiiD~i#i$hiY+@rͺrDӣf:ߐ`j>3Tif&~jj/&j6d.k5™N䷾-i|>^j 6^xXh^SFeghjtFjh fʮ>P@dlnSJ~'٣ȞتdfUvnRDKEiNm^8VۮnnnNeV o&ouH~&uotnoNnX.Ֆ?/p l@%tpC"Ӟf w>p]alpv76m_]vXMqq_im&"#`b O6o~arr+hZpQ+-.r$.s wr4'p5_b7svs<ጰ=ѷsU@pBwn5تDrs][htKncNgmO?sPQtU^m*]HUg+WXHu^m27Lo]u%V6GFa'@c?v~egXgk%zNi:jv߽v_^`qr"8>O? vt\7t(vzw|ww>wtx5kw|wxUbwJwxx=xfxui\'l{wnV`5ՊnyF 6xGPyn"y7vn~z҈xG؊7o[N ӭ q۶D]?v>qPvWzOƤyAOD{E&+pwq7qzƆduR"{g|L{vL|nZbgZ:|ˇ|Jorg7^/}f{Ƨկ﫧6+_݂gzF܋ׯ_ÇNE67L2tɖQfhgJ:-be{mmrvIvrzpojB++F*ң2zb*Nި)evv9*xuꝩ duknZY袽g1Qrl;R,JV[yfq{0kk/{.B]zI1N,FU=F^ŞybUdRPڕ+q,yEPKr '{6*mE!.Wy:"wdrKNw:@/{ }*I&ś_ ,)/^]nkܛBV&oYs;_oQ;Hu_8h[ {Q3Lq4'`$x ڌz,K >K ,I8)l M.]W pOѐ8 #" Q2_S"O 'lPSE+NYwE/" AHC.4ohY6˅}h"φ7H b܋!3בDm JH%+K32l]'XEPJ O@Uq$+a}l-_ 쑏:R9/ mX6MJ>m;'YpsI$pSR&&KS4y$.'ԉBDo,̶F~jyZJC!L5}T#~9?+k Kr?r[2 @6TOG# uQQw1KUկUjUopլ6FWSigYTT^p\[֦vteAKתU~`G"K,[u($UjĹbVIzKW mIkؠgtҪVeҤ_ezt_GA cpM8gMr=ƶ*lSݬvsyM 9l'j;6SC=H(Nke!;_T5n\:AwC| j20;Ұ% $ ϷT!. KlZŘҊ9iAٰ&yqumUs؇ d!5pXF.cqr&LUSMx!f[2>sb#7΍JbFyY@ -17.92i6لjgR!M:ʒ>sjYVo/{*:GJx:d*hڟLkA)85zXMUK{q4x Bm{3*,f ZzTkLؿVF[ mлֆ7i>'ۗEgNZ +*/u#]q܄f'22ݾ P{bW"^`W-`ba#C><#(։njQ;>#F,"+@,JjҞc #SVV J$l0ɪE=>(d*1:. \%ćuc R&@vrSKB:aW NenDFeϹY Z"6n5]:b^eJ%`~9a:&m&V d3ތbe@\`O0rn&/sMqi^.Jb/%A[j2`6da>EuEe@m@ybcHzVf(܂Or gs>gݤ b\'/rv&dS Tj@UfVfm dhbzD=E|4Zf!rr~:gf # 86JxN 瑚jvh)XUUWVɉ芲sB.2azif#ӘN R *'/B'h^*dH`)Vk)ͩ:i֪*m':(*/+62r>*- 5Lj= vb6h*$.," Fyk8ikVf2+ 2k^55KpBb8,"p@*Φ¦F&V,Ja(+.jsk.0`ܔ,V՞lXR. ̪fg*@q ,**LPRN,,m-:&ANʪEA-Ǿ떶+-p'D"+KվR!,t &:r*߮BcgF^:Cғ䢬h.epnWNf+)*--mۺ-^o~/`&&AΞ/g.}+jǂlnoMLn$!eX8t%~n3*3ں:ADܾ'3r3#Q5R#uqξ6cgk'4' +9{99\:tHSӯX<ti>>[ ~@oM1]tO4'An*JYn$6b+-TUsG3JpuW4;5=4Y5F,c4[Ǵ[@-jsݺmNkvkt5m׶m 4/p`G56phUcC9d4XS//Ru85L{Fhpk¶kmq^_qM6-gaw!&C1 7k1r/ճ{V~'u]4̗}D2,'ѻ~'>PD%>U78l5|x|*nq:NKnݽӧ_wT-)S谡wʊ!Vl`kC>e˙7Ӵu~1?_='~jج 2!"24B(b @"B(dt(B#5L>$F m.ƜsԡM H %D # 54N1>M[RRTq\m-+qAI(\G4R%sA'էd ̊|J4A K8;sn9諑Q=#^Hl9yZM9l]lEDg^uOޑӆ5Wzٗj9&*LxK T!2uek>Suⴕ++[n KFdN8de9Նe/-! 2*vBn؝}!p k * Cc=^d^nL#ܣl&)kJ_}7|pm\Q\&)'Da QB `<0+Vaph pQ8H*pt\ش !T C&zx! ;!/e7a,֢evT>,~ld;B2}*+7DQȃr +(JxjG?qYBFllr5iLn=L cQP${-Ȳ,6щ;_GY#F68?Nnq+9 ӜGF6AiޘȱLgVL49Mg^ՄMo~SѤ )P*lPzƳ EB;9Oy c@ ZP UBP>c@OzZ э~4H?zω~ WT`! ,^H-bXp‡#JDċ3Rǐ!FB($Hu%0c䕭lԦɓϟ@ZѣHm[ʴӧI]箪իXfǵׯ`ÊOٳhӪ]# $hx?*˷„ ; ÈV##DIY @^f[ƹϝ͍[RSV۸sǻo ȓ+_μ9};A~hXwz[dI#)/E&͞vV3hlWM΀fX&WcWeWV-hPT E  Tz4`/]#RACT̪T"%VՌ d etRE X#e)cL>VvVeoW&#7ES$9P@K/U*ܮ8eq*5ƥ{d엔'?yUng`bt|Ku xy'{RL&=o؝FVtci4Lꇿ:_+;qm~Mbfq/hDC!67hʳ VF%PWfAG|uVHU sTQ悶WH}@hE@wt׆wzEtF9Pl@P*u{{҇~sSG8dZ|s6% LdVFX9\Á38 S{؍ShQ]WKPXxP4ǘh({8`:{O8ͶI~#xw@a"kȎw9{HXUhU9Ct7)~q |Bs+9!)nToxY0W6I:i쇓p y:r0xV7,gxh턔֎bePRhlU,wؒZ=3 ؘه9drIuIR胗F{%}).Ljk8MVy)3t#7oY970u8R4 IILRR: . OAQQHx3Dz{IgO)L؜KM MsWxӝ*yHxQGנ '+{:<)uD1D ؑYXɝ:| J&Vjx_oiř]i9\ii)H':e= لQW 'W ` #;)GDZ0GKCG(ȤzZ) ׉,!Ǎ{lX:+ A`YQf5æAԡDAt"j[y(P/Ъzwi߃ zQ#`sӨ/Q l6llٍDA)JeںbtI7`wڏ b@jz Oڰ 3z( 3A ê TxtIU> ٬^Xlj!:!֊ʭ{}*U`u: j䮛f/0/A & ( 1ïZiyA+0yΩ< ت7 觧h:۱5Uz: #; @ ) @ j/.GZuZ {> /@ݧH+ 6 S WZ\b;C7f+ٸj;7E=t[ z{|Kfc~8c P[qPk_zNH{%n+;r I / . 0fx*PנIem[ٹ՚{̫v7w{䫑 +K;q :3Jif_wQ¹ԊbhL;`[fvo%) +^k;7 ܻ [ z*ˢ:5 +"LJˀ:ſ$ X[kcpUPX|[݋^<47<ƿ[O|lj̳yV`: &|UAʬ3Eq p] 5 L8l̰}7E0ؓjDʝˆpcNI606W\.l0! 4 AR,J%&e\k̬C;\cq@/Q͢M\3* ͻ'w q0d@>`5).PE&k$- &]3 Iy1ݦUp;ӕ?A=F ˠP }cNT7)0ΰ;Ҽ ce)i-R;4r l\%C i>C ] L؈`L5WM o2 & -\پh[w'd;q{ {_Ӊ\D}mr5 x0 > -H ʽ܃*};g`x9BKm:͌@z7y=аɺ@߃mͺu0ȀfIMMe< 2MֽlJφ8dV)XXeP X~m? ˭:> h=3J#N؉`-ܲɝ @ܚ㟽fLUC>hE.(Ȫ]c6E;:ð~`,\^ p w)dm3PUPjn&1p;'pcq=nFH~^~@ ͔./ΥR \ }T7,^c0 m~N|!Ke亾׽ YÐ3Nx$~ pSIK>cvM ݑwƘꦝ$FZ8Ȁ{x3ي->p @^̵C`9@ o;@mOo;t{x?zϫkKxJT?oo\_8Cd/`e?w] ?jUܩo/yhbxڽЉCѾ_S:_ɏzS ,l,dpF"[t5k-^ SѣGc$YI)UdKټͤYdM9iflޭ[nݶm) 4(UYn`Wa^}q u3 Vef̮aydu 1XM24 6l8#H8:YQ_!$]0cyut:)QJ>mujձ~^{l)?k@ЁHo@܍7+⮜BbKC被C2=.?JD(a\ȅ[n}LArHD)HؒT'j1Ij|PK8.ҹ LTR4Id3c|$9Xk]:mҥm2k7n%H!c :_].#Q^A|FqƁiurɫGrI1-k#F7lL?=YLم}.?^\Xфo]h BFq XbJx =/z9imsO U) Od;_X>MU?eЮv[կLpLB08IuX E%ט N*hA r잔 a+$,aNx6 [ C2j~h_f;\h_BЯ6(hA ,t:~IT"('Z;µ+S gB0 F3T.).4v9 c jT;Ԡ'( gch<S3+i#.XJ$#!I(Jb٣9lB,'9M]td]/]dY !G;& QL:`,B ^𺺾 -Fl8[P꺤js(oQᖜPbcvf0;7#=w([%CTɋS;kӸ;KA{7sȆ")@v膜Q@ChV:= r)) ^Ӽ S6BA8о{DRX@8."[7$@!,(C$AA; +1C)Cp ָ3C3.B0 L)2 !ni<,5dCP? ļM.vj &?=E8cxB 1J_5c@ٺY{["^c68vU'^b&(>e ۍ-&d6xEd>kXdmӄd&kd5dad9[q⢹=^S: V&.:ceeY*cd&tNgu^)pw%Jgyf$f{g|g$' P6Z)&֌SD_lfmF"n. O [a%gs^g^ngw4geMA8Nݿ"2}N0h>f'kfhP&c Vܳ<XY=t>hNx|~|V"`^^:knhi '=N&jeh`janM&fj^%.v)(^$v hxfxYdtkҶgU;κPN 鄦.mTx'YlNre>jhо-m`?im*jS ڮbO%s(l6.(negt>wҮ罹\юC^2lkwU 6o˾lFKUvyK~n&&bO^x*-bU`q/OoӾp{vjc_f  qB*a(F8qVq6ury$A+A%ipqyg&8s4OX%owfY_&wgmҠr@'pvCgs]N4&08`(Frvs@qT irtUBrs'Ks~t't5/s _7vy-+t,VPQsD>7pkumYuDpZsGos7GmFs'aqN?rh5f}ajvN%7nr|vs6poy*9:'4Q^0qbt`uyVwpCwwax`yNqh9wrk' ?/zwY0^> 䂿jyd QorQOޢWMC'z'j'ywOzPwyup WzQ\{>yIrd7zWhş]7{0y2}|zΏZ~8}"''HY{ꡈ7eww}RBa~|a||lܿ ,H0 „J0laˆ $yb"H(IeKFFK0cV*3J]DKevy РB,j(ҤJ shRRVjBպ+XƎ6Z5iԩm'<Э 7/>.#Huaƒ#l'N.>i3К$)R$ɓ(URYxldԫӰwUTSMt㼕-k-Zھmw]xȱ@I l|׳gƌM4$ʔ9[-vNTF 7OXhTr"՜5fAV5ӹ\i_!"Jv"d")pbcE}"j_lvS/%N JdoZuR NU~LԔ\U\tR!vw{DFIb:cauډلJBK+ey&UTVyD]uz8&*P" *0*")xf*0k hj 7\z@Pb9tI~ g/1(fJ[WR'B˓JS22`xiuyxW§%pBVosy'{qoGyQ_Iӱ9%,ӷG Fu{1yDsɘ`[2*f@5+#3,b 5 AGp.'Ұ% K'ˎ[ )6;1w0wXͅ;Z_f]|*qûϧB.F'a4I[10'OG0U_=[ؙ+bw]g[h}/+d2Yx$^/"p/DS~r wc˭;~4Ls]WN.P>rߝpoa"»0En%.@( ,ƈ'AEcAFO.;P=M|kH4;gp) G0`/hщtGIrQ0$!>*,bM'e %]>-~#`]FȢo}ADD _0]d?І7 yC :I bD#Weѱ[8[IX.rTv} B W8\VYQȣHzzIR ٔT"H*V׋$QLjFpfɪz*^&*dtW< XYa.a >@~rb HIkHg^qt "`͉V5#c̈1i$(ԗM,̔Q!u%uY8n>߬eʁs9$ csY̢x-lo^>%/DB;%E(,3͇:,e$8vk:@7) egjr"j mʋ}䮻|`SR%.a{.v]Jec+nR}0Y 8*<%R _N&vmle;ڢf/~"  ':s3Սpv+n,x_F!!Yo$V4i1}s ,Vz6V:VrxeXޙM¢?=$]$PUT&:^g~&hh~]Cu(F:h28gx^(fw^l&G.|ByBUEkf{)vӸ$5%L p@Е p'MHg/(i.)-i>)BB)V₉}(.&h1>jUeIg{6FTVB4nWRS {ݞ QTZN)JҠ&.* ]kAJV*Ra鉙/|$x) Y=_ʦ˹(V(T4nـT'~O *Z>+>kNk^+fR+l/LcAՂH2_jn_t*|]A)YIB폳(DsQTk&.,kuj-0,:,QR,fl^,v~,ȆDz(ɞ,+++k*7"ݫꫣSJ 5,l>jF>-v*F-JN-d-~i<[ѬF5acjU2&I<̰RJi--Vvj+n+,"n*mՂ(FN.V^)UJn"nʮҜ U9 ڹmBA)5ڿ-sPV\n.&o.6nb.Ff9L?<~\/횶 =֔mZ9)ֿRFjB^-"o^/Ƃ..Ă;/0R[MkX+&tЛcFV\1ݼpM f/kQ,g.LR"&$/177(DO1Gqjp+Pnnq(tq(1{1NO1Ƃ$ 0N} 0 o \ /YҌr&}Xp*RpCFfC0(2**2+gq'anF# رw&0epnU 2.oV5a{}Xi'pk6g2܂!L&7#"x88 3::k%\<3=:3>s$3?>8_! !. kf 212/.-$C#Ғ&C6 4KO*L4MGoGt,@n-ttK&/5tU Dϛ? 2'`(43",˲ *z IY2k&$ [5V5]׵]5^]3"8^`6aa6b# |P5!+QpCgWs*EGD(/'W;l2Rsl&S3+v6nn_jB-׀As2gSv/)R,u7u6dh47Ucu#j{p7'8Zk-Zz{7|w]{95]7~~{7i|#xw[5c wPwqrssM7 Pghv(t,X4x3k&l8+'1n&88k$ 99'3_?Cp?DIwt#w7e;4vedJ@8ɖzٮv`xえ8ry&DBbu?z@ޒ3'#,:7zGB=pqB_ׅ&o|ΊXyi/{slU(#j*^s7$x䖂K®;;38:?7'{;y}z?{<C;}ss"@w2d:p?f[pϸGBBiﳬ:[,rB\{-ṣ8}ua8G£7?=aGԀ\|2zkW||wGRlUYjla+';:k8&`B39;=/:'X=g< o}_7)s]_-S17]&}o[,9>O8u"<B=3/?6P>chrdz\fϡ~>O>oB(L3#Ǿ(dn>|´V}5߿!4?G|$$M#,aB5LdPaD)V(B?7dH#I|h%E@_jh8|Yg ;wjhP8@p@RK`P S}V3g֬6?AE;6mН;=,)(sMڔ(ɂފRCqb9r4v8Odɓ)K.)a"C#thw* zrbիY1i׮ڕ*\Ƅ9͜^94AF2u)S+X%w V,ٲg3{lxlP׍wQ׷_Y,S Qv  ,葆 %B>(PB5, |&"θHTD碋Jꬫ8󎧰b@3/B6GD&| 4)JL1ز52 д0#;[S8M.N: :m3ᢄ>N*8UlE^}q1KD@brR,# ,RLqEEHlK.t墌-}O;-@4Ch O=4CB'2#RLsq!~4V%SbI'1XGp#]uł]_ӠbԪl.9../>U^Ye*\~-6Ѹn@\Qe\&m:l+ ߂U^wX␩ =RsAnc[+d ?qa֕ft)QEyr6褜JDݦ4M3bI)}8)$IAbn_~/F;Xοdߞ1.8[?PA+q䟿IgҫG;Q&EiySfaWd(#z$H$wqy!ɐOpuOa/`2,| 0O\O4>o~ITJ:@L!!s2#P^}O6@'B;b%L 0ta uE)'"=x8鐇:K(p̈AAb&iOu)$Xʌ(ddREe.= 0)֘1If5#; eF r5<$"`^sn,c"٭Iђ>\?NүEF9RF alX)A fBm$d ,7,bt@ݘ_ ІNPY8uZ\&4WC,LsՄ5M.1;7);9;NuN;sD vK@WnC!@ Q*T302 MCU9f@Q27J=~~u֏RiI0]_Hi7?Jt15'MmIROJp&Irh&]0{RӠE`6m# qE!jYbա%֤!cY<3-"&Im[KX"(GΩuQWÂ`j$j hyn؃S9t@jj<ޗ/W1ߔ%_}nLĊGGps0PJ~H- {"1eJEIQ7N n7A!|pCb`T?b"+:aȱ$ Qd#ɐpDР5־gp[e ̸I aQ7\@ cʙBNJoF3Ns` ,b щ@|H)חaS4i+ƏF1`iݳ#`Vcp_hٌa72%ׇW&ˆtܳ2 _⸗vB6#c-S¸isɠޯF0Sڶ`fᴚ2 *#]95YøvkrҺFUb-i [CChl !vIK(W~-jle孼;a9X־088I1=#)6\O?JqYf B*S7K'*ﴧW_2gj.X"oG@ L >9#5k$HS1]U/|[J{Uj}vu֡z,_[6!m`,Var @3τ?2I'_!`aҼGoތ˸|72~~]taucC{߯2Yl/ O(n*J'OJ :+ZZ$ \mJ"mooϿ\N>dPE P,$H@1@O(m1( 봪 PWd^pv[Jp0nAIo؎ pK 9k# F ; +f*Oΐڐ`.&:Ø/Cgp W  P˲1*~#q yQWKBI&y@Hq( tp*" k~bR# €  +q qqQY O aBÉ}cj!0rQ$qQ$ǔAM$o%g&er!AQKDQh(;l# R&;@M$- 'k&G+Q$q&qr+_k'WNaN$*(/23F>P40 p%Qr$2PR2|+2111Ւ?PM2qW#&2\(?(///+$J02 0*wS12C-Ur8kr 8#%8ѡ$C4@ %@}&~@ot*;Op- d0387#q9s?g|e1A_x1:A :Kq5VJ辳Ϫ @Z5E'od% F*C?yF+y25>}GA,gl5B 88t'VIG=ORWkMzX9Q;׊K4]TY'VS @uZm@aA[a'.2VYY:c\icCZWe ^I'C5^}09@b_U'_5'"/+4US9u v2"c@bbb 40v2]3Hkc;1TMj[5cA6LHNCd+?הM` FefC(f5rv`PψLs` Z@iiq'iqr/6[6cIZssM$.sCY&+VQNVn}UQoODoowge`WSSiB6UOu2dt8DE7LBX4Mc4kil%TZv$NX7gnogvdzoo"E`}p%C8+YF5Z4j2clqT=V equvc7okWv}/~ ~7j+bwk͖23L7O8Ll6^[W|n>$(ׂfi0Xpe'8q'x$Ӆlw+#$$%D][5d)$҄eK`n!I熧cKo` VT>UV[%D?8dAk2>4c$(M䏙7WEb L&#cV)o8( oQe8$Τ~/c9x7w~AyCb$i9%WEx{9u-e P8L3dzKLX+hh bG3fGA2r)(rA DxB`9\"OLĄ@Tk㣐xY+x4+Ҙ'&q::DG) Vh:uu"i9ba9 /!!9z\ǎuq))R/ÌxX>(XJD`NdZԔL7A(EJS6Χ)j͖ݴYaHꫫHT0N1'Bz)yѰ KthLƔLE Ԙj7]{iڎji▻PȥPU[pr]('`BDC IE@PHKD,d5d썹rq}z٨\fT=׽tn~N8#_(8ӽD.=T7m~3ac{觯Mdܵ8}YJ:C=/~f< Oys 7F.{%-`YL:fߢ6(F0Z󌶤dVvS<@D3v+! |.z19K5q1%F0kr<Fe .Rc 4~۸K^tͰg5}pX$oQ& )RMs|b料fZiגPZS˦6AMo+g ÷l_\';W`w v!aIHafMbMDf@jЩ1bD(4І(6%: Ң\7)XwC*NH&EؖfnvK^}q*v 2V;bKM%,T{>o ,;X;`kdYHt2dgjjup,>SWIMr-Ӊ0XEC3#c]߼O:P:5Q2E֔B3g!kUZWwk12Ou4Xӵn-uatϻtbjӲ3Wjз5_dMk\˘ t>/yI>>͆(^aA[đ3ӅZƖM)o;}sG'}jpYbHN8w.LjӜs)N-d٩J0{^QI\~NѠgE0tONtb 쑜k?|oo)w(<_o۱_> G7 w}(F]gH5|}~]GsTb~ǐ~k'tm~ 偻ow@?#S0֗@{7g p g{%rdjvG|!oP|ͷ8,(x0OU}<5hs8}}8^@)hExq7ƄŇzMͧJPGKYH2؅7}bp ؀uڲVIF{KX| XtH|V{zxo!n&{(^0x \8 ,W6wsubH5In(0kԨHvhȍȊOxoG>T/ш(S `}0 X~ᕏ8m=~s6mSP$Ft(|S0S~HozoCҎ 0Lr,ْHvDv9'.97yveG8# JFY9JɊoGz "9* .ٕd0^6m49]s=Yy9AhEJJhxoJI8#T;|ٙ@0IɴP[.9T IY vhG7;K9p{۱ IpJk*I-)huQ_5ڠIK{0{TʤBx [ (,;+Ы؛[,JʱzhkK봼ۻᗓ: ;u{ XK' (p[9xt۱!K+ p혾JZ +e [&/ϋ`Իҫ%P/<3˽>+ kKex99Rˮ|L{ \3$l,Y\\9<> @ (hġyjȔ qQ~S,U|˼;ʥ-\*0Fd\ @ n)w%qew { | !lkۼ,kȂ<ȌJȾcp Ězq`!)x]9|ħʸ H `%,<<ȶ;prA+@Fz|~؁O] $@p҇y\^ shkSm{> @/C}Fêׂ=zԇ]7|؇rTV<ՓϜ - iM {ӝ=͟} צvݫp@4ҫc59 -x;XK <<ݤ=ח =>.MM+ʕ@-m0S[-Ԯ>HI^.|-R;@C (9+N 2֞#z8}͠B?AD>Fn pxNPRο$xtًa^g>ٻ檼 /=^]S>қ;0Ay];b'S...Ȭ,M+[gf놇k> -@Nt~~H~M^1j\NRj@w~L^xn%~[AJo}  H >tWNo~F= /!Own^){W\> DJy:v?9={*;~GIoKoPR/TV=\,{JJlO N4E OOހ_ c/x57j?pu+nLo젎R{}6^~>ȟ-<^KP 7L`C$JL/VL#D !E*$Y @ʗ?Fq4Qt9QhQI.eSŤN*UYUڵRS%[)2Wc{Y3\Pn],(!_P~ pD)ZĘqcE @ iҲB*['N5q3ТfUNkdONZ{vXY횷kkپ;ݼ{ 8D 7DPqE8>1d._ b?CN?9Et}_˾mZ궧cMT{-ҋ#( κh;*ϤrA/)ˉ:>ӱ@D*@ KzH#z0º /PxP*(6CƶFtDN, s F`4Zh)>n/TG rP"GK4ɴl-' ,QKkQ2SŔT䔑=Zn!Q ]N~~\?֜:j?CH@SYmcݙm M ?4NPj 8@JCQz#\LjO4p5@V8o[VDTf@,r~QVD!Xc,%<-1+)Py ܝA,:e` |M)0' np@0RJxcHEЗtR&OUkFc7:%q`E H*ÑoQ'&I|:~ȯ eq,"#[|1aax@-hcM}|v\ =P3 "l=QR$DL&MvRY-IUQќl%wvXӏ*}!@ɕ[c?a6`08|$1kxa ]ʜ[^5hNSJ96gMu&6 89Y'eRH3iMijÞ'R[bxPPs1lhV'0vSۥHRy&4r?0jS@ab:U,]$Kk*Igȁ((<9:Kj07Ls0-B"!^YkW{pb?C#Fj֙gemk]ZwxPhpĪ!xlHRY8㹡(zYQ-lgw;ۍCwM[N G l+[ئWJDz*[F¶O޾ cd׀@5@;Cq-7a@8Evluq쒖1a,{Ūu%WfR3q{^["x|i7`Y>&00 l̅#WânS-FacBMIF bxC=\88i{&X{[>y6q+ V$%J=޺1|i딒dɕleH&FHش\kv6y psl*A>yk`ۼv ]C#:<`=6| Igɛ. Hz "MRb䬫]4;Ļô5"`'A2,A "WZwl[T īnG:\if; no$=E8GoN0\7yЇ^'}M͇b<P|;}]* (NB4 r4ݽ$CUR#<oS5aС> 2ѺNh;+2*>%HH+4CD@!t>@D3 K" D)F܄FD/GJT$M$(.. \HlHl$,tȆBd.HH‹HD-BLI@FPF4bFiKhlkCAo ?CT4+FN; ĠLNA $- M E5F2xRRN*P5{GB#VE^7[$DcZL^`=c.hcZ%[ ;8fBf[Fa%ḁ#l@eeh= nGqny}WH50DGRMU%E^U9V..)A $e6_h] =Uj`6;j=~U=d&Z`p6yfC8heM-ig4<v`NŽB5]gvFSIL_l١Kȁ 6 P.6ncu>@U>zcMmԎ|m| n k&Nk/X:;ndk4`2^ UlIx^@H4l~o~ 06MFmvj^F2p/p.4 <gnpwp p.p ppFBn43 8nN_NG/mUS_,HJ*(~W,$Or%_rLx7(0Ev$V꜕Rhx pp.@pgm2 p1s8s25p:s=s,s6p2' >qkkqn6?i `΃c?ŽPLpu ]~A(/Mrr4r!s?Vs<vb/;s>7Gve (t.q=hߙ"Xl^oRSLxL/<`Lw{w{AX%o~[/;Lr'u:3Osaov9G=xfxgW (!kcjnlHb$(tx^O7HSUpy7x>_y/'z>:w=0Nj[ՁЧ9?p`/xc'vxxv6p*ygV^݉XynnyS >7p~]Vs_OSHHX|HPwB c&ǣFM.x'w/G}ԏx&O{Z#ɺ_{Yz>؃ծ=P4_agBX'=3肊g)<|>}&}2/~p|/~fs~c_G{(T( 2|!D HHD .QÆ ,i)_jM1YdSΜ93 8-J4'B{tϨXLQb >rj#ذ>Hd *QFMCM7db]+3Mݼz>o_.l޽K41 8`a2f DŒ%^(QƇ?i$l7p!'wӸmċoʁjn=t`q-U&WV,ٲgu 7n26Tl}7X2e Ȃ@ug 50!iG vLwMFhrR!ѡAuEEUJ`VcI$dU[]S&F`u%VY}h :1arJhanf\Kw2GO.c~舍7h;6kiUHidIj*9ơA~ɺy6WOK| &ddyf(& gv:ljцg (QmyeLhЁ*(^GU.QrE^T)ajVHUJ8xa.Jج /luJBg5BvjXz0XBm7C+2DW! rO]Iʯ S["=SVհUё4%\b_/Laf`Md̒#(8юi7>nFB i~R6wEȊDz$$u_fHPv." \Lqst'F\W~{V(ytV9G|ʳ* S'-ywiE_9QH#j [-n}b.<{ -O u%|^YZzեaSX `_QiAxG(HC VaUٜ_& )^ A zE^"!͌rbi@ T x4h Dѡ]fm@[ `.D\^|Eb &"_UaM]ED@AāTG` v J]+!%a +Db+ޝi5%_^"$$Ml Ĵ`  (u="k@V*z,0cbK!_2c<ށ?bkb,2 :)!:954R$rW%sy؁8|MFb)vVM3; ?<Bcd3?c OK$R 2!2DAEEZIl&_C_( )$C"VLb<&= Ɋ"^ccO"""1Ƣ\"a"IUe\%lؚ̍ }$YjV  2+!0Τ1]&0eh !l&! %;"d^$LR%Q#%8uW`(^IjH! fZ"_ Ld] >dml֦Pg+\[ޥadRKEnq%P]6XjeReMgrJv#w&.2 L#KdAlm{.(~d<fR*F#qbac )j%2'7B'8Jȃ%YJ }f(]h\&, ҋf|֦dg6cAZ$hrhn#~5ifjH\#vc2]"2!K-5l&#Lj"$m˜ާvP(ifFjL5n!g@|q億d)6(Ii#f爾fn) *oBɗ~+$*&%*ꀲ*&ī``fڪTf.JEhhewk2*ko$lV*m2keVkITPb%Q+:R':y8}go-L2$`a)P,njʞ!Ƣ"¶),,w d"跚_NYj9Hڔ+1h6eޝi ۑgA$. vji N, 9ڜ Ď6V H(mE+l=l.v_:*yelv-冧z̒-3֬*.qmgxkж\ݎBopL&nL:oC0O/hB0'hXD:.jY\//~|%!ah@eN#((ڠaa oa 2ad>pnЪ`ъol0]fj2^ ˦֨& C2&"CoWq{oeiPj 3k(bx"?FV*Oj%_2'˨#h (")?F*۱òrΪ,qf^pHCBsl[Z+b( 3 l1#s2#3's2q'$!!o1)6k*q8K Y^Rp2LlGq={r;z1>1j? cR^.!`5CBg6;F7?4pﻱnfmV uv&LKz:5h_pV$f>O"3徰2&4LL޴Bt:4LVnm@AGKq"nI/#F D1wW#v$u@ `M\[7*Df]sRt^(i)rYAS`ǶllA2!؄nW&o4R eSvݕ){hD2FjkAYl]ul-36o6g77q/`$`3 \/Oõ&fsPMAOjov/vVlJJ%wɲ%/?M4f{`|"ѷ}g _,܂"퀟,n$۸~uwAz ,##Lc0VnZe7r{5q4~ A7<6߸9[R -J\ayK8qzC6b+sxsrI,0Fh3 hv<$fvxgkO_:o>#毜wu xaY3f\os(gơjE7?:Ғ<׸g:w7`dK8'/0;?@0M90v(v۩\lfy9 ;hz{/!?6WW߮vxښt~Q?<˄oG{ jՎor4<[-|#R$cߏg+|f rE'~) /ƳI>O>>wzԇ RcFsWŎxa6lbDJи,eʤcH#I4ycJrXtfL,Դy ;yhPCyiRK6ճj=|+V,$kJ0{l >eC\m a \hDFD\D )TyyL͛i. 3RuzhФZjU{d,XdˢM-[!V]z{`%j\̨ !c,r|~іzt5iӨ?)lٴk5-|SN.s$l9T(0QC[a#=-򥒤VSplX*XsV[oŕGx%H譗B'%6.,ݙ[O);ԋOuIU?^:U,J5S`帞daV(dY"FHGcvMkx2ޛI\LU wC,PZ7Գ zW1[* j+ >R$eT9DK4bgӌT񯬮Uz19BTkh[euʝ3mV|4/\c0E*jxS?kze3rCxU@t?HB'щ2bK)46(RLp71SjiD#K<2)L.t 9*8ͻ]9M;`J t=d#)YIK>Ђ&IL`"I(VM2uTb)Wĕ!je,v2x^IrRr{J0J 8āiܨ$&K:$No wVBE[_fBˑ5X?RL{2o,&73*DT$/P&f&QNu QH5 !HG/i#S 0՚=5 3l4A.Ӡ(*+QeIsYѦIn|H+my+ sOS^qqD7Ђ6P JX\JWZFիEAWQ0!eHjReP)KsKv+XqL胷*4RDQhRU-`+Z׎֢$IZmKU;J”WuY P휱|EƕDtPW:v/ QIZNPn,eiX"uY94oȵQl{Y?ş% ܸ>Xu)\Q5y#s!9l^-X Ek {:hM =Ez<\LT\!uYa UTy ZDJ*R=oP++,P0J 6v,; J-]h]+yU.^eXW~_+E `MjV`뽐ʚrn`j[%mc3&=A6dS]]tVr-vH`VmQk//!emf@՞`k HQ($1-,qcN ҍ*b4liV-5spnNux r9 ,iI7Lٖ*:Fp%h w@xxwxyxy +.VrCr+P3N8,MaJR?Nt8< S&$[u륁w4䀌V=A){(&#;;B&aD!S[_U\gk< s\s|4h&g]"%N;x|@) H} .jӶõZbLBڍF'Z\*ɕarr@ vA5SM++m\e79|i<#/!I=WS]ww-(ZQ̔_a v`ЭXYدW֛ ItK!\C[}b~(6;a4dwA {"= 7~== XYh^N8CgE5=s'R#~,Y;["s\^[7݋hX&Bh22mvݤk v 0ލ;=v}~&F[4~¯}1|E0~;G C`c2tCwTj ɩB)ĉܿhM8i'<]'#a_w__m&Z~؇wש8?jJ K 2"_E%t^4(… '}zDL*NbA.2ȑ LD0,[T ɘ4 H('!y';r4iRA8} 5ԩdp$F AR"Ad 5[JD'tu7޽|R80`* 812O},qɍ@tT=>.z(cЏ:b$)Rh]vM5ش ig͐-rċ'eJ߼t~]J)L(Xd4E/g>PNU_B[X;!" ~4yPlƚ~)d!-ؓn z:MHa:^nqi8MqCvqp ,@C6XC:c>> IdF$A.ɤFÑP6AeM@eX d^.)DbIfQeje>8fryc9v#5c~c 1PBZhv! ,^HÊCY0ÇƘH⊋/v؈# C ٱ S̘ˊ-pYċ9mr|ϟ?m ¦ѣ* g =]J"j0gjʵׯY;VaҪ 3۷pʍ ݻPJ[w*Xc2 4R$o@&Z`HeDʒC1 7y >ly͛@r+bĻwo6ma!eD.uJyDiѮ}5b]sԵQm˷Rڳ^zUQۍ4i(XhcAЁd ] f u@څ+TxE8:R-CٴbRRo82H!udVw@%kyiM%Vq^PU$ygWSfr1"( >fC!0a)FYjmnu(ӌ.XP*&[*2ǑTfZCf5e5 fC)P'U^*\p 14eykiv։gvbmTl œn1[niv;rWJdo}75[E.Af*xV뛽&H'JgBM3Atm>ln(U,0,s7Hណ}ro_01znw+Q QȂ.q3o p{\N=N|CPv>awkh-nXㄲPkz; 90\By" ٯ`4xz"L,fR'2*VQ[\] Hl`z>3 ѓpD9RϔmCW2fD[,ٲ..ݤf5~l/i4:F>0sxМ NNɖ"aCe8642L'\otI 6zLOigKP&=9Igפ^=HJt=E7l<9xŌ$N0l/!]:z:l1NE{BL%o$jQW Hup/fPJÅ׾ҩZc,/:9r(WCwf?m 0zUaiy~_G*vdlj$ֺTA1b hJvi e}8;u Xb_BZw/_vli+/6yCn ٍ&eC-uwBNxօK]<^2E5$;(g `ML1wa þ*z͔%}{QY&2߈OZ{胘>g<6ݱ,C 1ت\г4ABkiċDY%+'X$2hF򑓤,V]7ͨNSM{gM5k~e-ϭܙC [L(T(A`ڙ&G8 OsFܷF s[n\zͳƫ@UsYڳ[H 0\IiMȧUGKPo[%À\y'rF9yǺ,Y"pc2vHAi5'@%Nko9~VO<\n.u`s\d^3tg9* "E{HZH'tK?ܖNY:빖0c>x^;MݷݺyywiΪǹry|27E^vUsCWo oxI=czKmG^7%`}~ַ3g2/z7wn:68תBW˔ޠBІg|E8 xAq|iзVP}\k~ݧj~y؁~0f-7vw엂'7zbGq`*vHf$l+F|gt`|f'ut׀}@xn'xk\x,(~$iv(xk*k_ghbVfnvHup.<l'R&0Dxx4Ji (GHu _*T?wnY]k؂bx~d(ri#W~~{7;W%9y(L`\hxFup 0)xTh Hx#u'f'~-xhj'?6`uc*k&=HM2MHmrċx(8uXÿjΘ(#j}灃@f,؆2gceje8SׇgEYX 4qiOQ(xy]]w]Džؘ|`6z|DYDiy6JȁWk0牭Wj\$n2$sqvTyn%\IxqA%IKw8 *xzِL?z*gFiH k֗=XR{wv#%]h@`ӇVF8myAqpT(\&hvv ) {7阐ɁyٔFk8(Ȗ0qG%_S(ضKqn)up)h0p\ٞY~﷛9阞9ɘ(kh{$@IxiWiiq nKp"ڞ$~憨Hɗ)SIiX *nA)Wj|W6i0hn#Z%@jyࢆ hfgfnj9z`)'+ɠICET&Jjq%Qnښn$_( XP6Zڥj%'.fkj1ZoJp*Y樑٧L2&h@g{R2T!&_yĪ]7Ԇ^``vY~۹ҋսm؜vgz `003>|T߉z>ӝTG.)`LEk)mW-('yɸ[]ceikno/Rmu>Š= }ި%*M2蠢]aųzv+-^ ֕>I,l=鉦1n,etΝ,.$HX+!jpL~K%lin/n 7vr쮽-^)Fk>lzӪ%h`{&ڷ,n㩮MDOm߫n Ĭ-!_G{"m<o]3o>)}aͅw[>D^H/&@+ q9n -҉nbX(k,sZʷfYjrNE?_p_<POo /~=YϓOO*пw{&P{Hq]I(v]N)Xi $.1sw='a9~6Bhj>ilorۍ~n 9HȺC 0Kl;kC$V(=] :2;5>ۀ?^K TR@vۊLЂ *08 !@ 2ܐC.`ènNγP.j|i$_<G-D8Lޗ49/7EU{c$cV6JM )CX+>#! h@*\"(DR5 yC{XPF(|uԹyt!Cp> Pi#Pc"w3HFrZX.E (zIH'=i=l^lJnsc9Jdg;IK9,1_} $ZI8!c1qyBPMk hGp6QQLYsVrLNzRxoAC03Hmӟa9^@PnT 54L>,DxQj"A `hWj='PiJʡT{i[V;Tud Cp<)x`UI5RdPRRelc5*ZI'Tͪ5M5tg L{nhkc9IBCs]ZS X aԥ*ֱ}(ͫanu;ٍ$|h2+7M)jSպq4Hl[Pio۝|/pw78hX0`JPQJ?nE gh=R3X&qEZ mOb@ &$;2S(x ]PKx'/S2Ʌ2W[+ٮ Km c¸b$8Ό զ U^gHwK01 >Yl]plzUa]G! V'<ۛt-}iPCӡGE|j. akOٌ$'x+n5 \ /[usgك((|pc'YVr h'{5їNW^2Dg^pV wmZ/F[%ZZ-v_W1 lY.%@p| [ _8 ЦF]PsLxu!q'95f\+KnwyjmkL5(ϸ! < o!^1{qg]vuGa7 s3 4sO%,K\a3G9p#JpnMocg֩-m[}|'}z/ow/%_yTR,j̵OdwQSGz:gua4<*l,>Y!h'!3*ـX3 iO3??< CZ/Jt/[6N$V%a%Vr4V+ŏPV Er HS2=SVRm5%XtS2k¦¦Q2;#9,jyMDApVRFG=]V ȨQ뱬WTGW32 /2vJvRW :Bb;Z1Z%t3Aҋ =Adk[JcM݁ܨNTXS!X"Y$5Y&:+ve+XӪU *: ,LWRӦTLfeI -wq/Z!2hHWٕ]?Z=!2m^zr*GcXp(gS602/z8 *<"5;U#F#\Cܱ*955;j/k`O ݊Л}%1 mb(#5-ZQBmn':2Ӻ/b9+wvE`\T4/n⎤<.@ԥ)yu*aGvd,% <2cx\+Ya?aa!Z%^T^eVna6:pYb ڊc>(R 'z90dYAI-#c1fQB%X/3/Qf;+3f32 ڬe;f]'9Xh~heq4l['acZ@T_q֍d(Yc/*IvܯU:fpg9`hr+x75W!|ְ%X}`'2k#Ӊha7V8jF=!^<@)^Q<ڍj;fj@ij~9.e0nkO_H91 2fXejXc6^h3:Ziơv*i4*Â\k$hk엖^`kމChm~m@՞m~m۾mm~mB A njsaBmn]VcsNjcu=feln mfX %fv"+l~Nj~n1HqP>gu voGY2 o WXF.XуhEXGxOq_qoqqqIqqqE?N"Gfhp!&q))O)'r"rɡ[n4>E9@V~@q>/s @mhGo!;8<> AH 1c;fH4G̶>Rgs#x<q?qIuZ[u\u]u^u\`vavP(vc?vdOve'LhfaDŽbvom,OFrpDOts6^.mr@7+qKmr?w؞G/gA{48J>Tm.Hmfe>t w_xCH v׎q6wlnn'op}vv!gDp.}*c8s4OOsO)X#h/o$@8oxXoqqZ?zOYqu\Jr*izzauBڌxZgxl-GyۍaU!-7x 9%hu('zqxO|qr)vo^l'ao_pZ@N{Vql|"Gr!S@42h$'|W|x|w|qDŽ\,=s嵡X?Ƿgnryk4, 0S0d!)?4`bJ2#Ȑ"G|(J*Wd9%̘/1Ѭi&MJ:wbgϘ5',jhQ&Q2S@Rީj1fn"+^Ŕcl׶l*ԟ/ᮭkz՛/z.l2&p d'Vh1$ܙ$E-R.j;WVHDcDj;褥M:«)nwhynݭN:!C~"{4Ǔ/_رd BnOE"E˗85i¾m{5 (5r * (8 J8!RX!/T gUqcA$rubc_%ri5gs*(3U9v="a!YdA"@FN4"-`mMbyaYrI_~MoiIU^X]&K3 u:jc@BX)ACH'_FJ<!&)!)RJ)^!z) #g&Y Ybus QRm-r,蓓DNFSjM zZ! aJxj]k-%6K LΘ댿ݑhxR,= mEE{if;~K1nz!nU1[*I$Lኔ5O{K||!]SPeM.̰~҄ơb )b,aYk!=Eb"{hbWr6 ۷>k]7v,~W>IL]^|5D\5֑Oa)؀A2,n@U:39CvR!E Õ;_R#oeRjuk9Ùi[9ɓ~2u-4HUyat~;ᳳB & ?#[K_BƻY!_&X-8߯.ᙼz7-ןͯ L+9ǛSI|54οFCw|:77!܏nn/8:"ǟ}㝦}!7>tl?fBHAJmޑ1şiDťR@]ߝq ]"4 DDixI̸@4hiFf`lu|D`9 =8#`L B W͋XM]]~_Ua]*٭MYfZ!jᕝW,NĨIaA~8hMMcݡ"e"?M%Y!.In!%,_$YjLQS@כEOqbi (S$֌+"biDEhQlS<A00#5Q_ytX;)V *!narFDŽiY$0]a%c'0OD>>>[5B>@+&BM0>Qۨ")r/!]u'a=[KnLauLڥ"JͽdA$M$K0tH~LQQOڙ \I"%Q[QU2&呵Sc3T:d4> jOK&Vf#d,r%IzEe5ObbN&09\#$eGTD9 N* |#b2EdNdde! cQ.!gʕ%ϭe'$NiBT&ܴf fB(^XJvP f3񥆨ctu$Y)S_sJ<#edD$Ti.v}wax<šF<]s`gw]@kNAJB[gf'(wn(&xZ"( g6@gr^(ReUD$ $b0gD=-v'OS~qr;fsq=JTeiA!'R(e% FQL2)JHjep NhY]inFvO~ihLi^QZh7N&Nili66)U1?:'ĂV$?e)'B]$NqӤ"QލUh#-%bvmi^),i*:I(1ZH)ŖBlG)ijlSt,L(JEklr%:\̒hjY>RΚlz*騞f ӆn=fJmv-,~,oپڦm~m~BWl֭%-mQmJnn%-zZ:AY2knmzlMx׀~ -Ȫ.Ѧ:mhnVFtڬ ]E'n,.-:m(`i-o3M5=/ڧVNrk~ooji~/IZ/ίZm(b[2~7hQ=03mi"D /Ȋ r axΨ/wp&q͔Wkđ[X $ pb O 3š'Z`/\0%2Hg=r-pBNׁcmqqיJ(m0pi6q"RI1Ա\GmjzpM!s!oЊn#CLrW2fJoc6^%12ٞ2ͥ*߰fr&gky)>)zr*) 3Ls4T3#n2#PB3&4s4bjs)DZ&t08A32*,h-jMJ_ 70ߓ X.MA:wG--. w\ʲ&h4GgRRHӢVqԄI#5J+W?tKBc3\Po.4OSiYF?G&R'8:Sի4KO:YRoVf$W( >JYZQ'86R 5J_|A+4BO5*z}2`oW#-<`buZZ]+K6\e4f?f4g5$ kVaY^5_T6'bb6cm#n+ue9Y.qJ7qqYNyrrZjh5T(KwRu1mmrnwd8[q֌J 7vT7{$ gV+B8i_BXOF+tlPKPgGfww8x#x[#.6.L?umvZ,nwSpwk'C4xaB?5t')BtcFM1u$I#I49RH*1fL/3vuM;oVlQRPIQ@RLII)S B IcWW^ lXc^2kv짮ug\s6I&Ϟ׬Ȓ1Oƴ+C<˽(4h$ *@iԩmbgƆ JXiu4]ٳkW^'Sf̎_Nxi壘yֆ<\,CC'{Z-VZ9v^qpܹwןowY|,J , dn'瞋nʬ # 3< KC1!L240h*p9A5nB* o,O4:@Z#%OҧUEdK*M$:  ͨ,+$G2${|M*KQ$5ϰZ꒤TLFgJD6QΥP!(dKO%-h"0MOFPLRxWT+Yi+!Lތr!Q$5=Om4VEլ`s5_֙ s׵RW?!a9-S;œfM\E%ZѮPmFw|z>7adXPnYM,Du^&7^)w~=Si-q3R Oԅ #v!e CB=dDDM>) W_+E;pPe/ڄlˏki"M-v>t(d59cH˶pU[0Y OzO"O+S{ܚƛ.6M?]S{:!\Qc3*O@B'+_hsI虏,:U`=^qHwНʉBsxN^% O~O*{8L9b2F9d9uG'9Ic%.9\;]D7E '>%)s Y D>"f]&BXxĉM6ȓ*o[`R MGB2OX( =l5fHIvlqJJ2щKǃ`WWa²,P$c"˘D᥍J8T C QC\ D2=D%'IB4H03$hIc"h>rB`2'˗Œe%o;w$ɬ Qf[&3)Jrҙ4ۦiK\^s>Ģ`;o8Ӏ95Zj9&_NAΜQ11K0q"krF)*(Xz,{g~Tt1? mmTpRRR>DxfSTS*Cz6u)X Uq-SmzU(C^QQ*lQ"k/uiOEm +?:N~+q*0 %5:m+2[CUm'qjچ"ݬ0Y_M]S$'^>&EֹĘi#+?7&dʅHq\(E}y -[brxn@HNqwW m䥍y^HltO{r}šTeF%<Ŧ!յji`" )1 WJ8T 7aרf 1Dtޗ K.>`ŕ @X\,/䤲MJI8#n2+.a "!]::=7c[ S/%Xmfqf L^鄕4`P2"Ro lUqV+x4Qg[+x#Isr˦lH5Lu+sj&< I.v|iW{ZMp^pwNv'a~wW<+W)Oy\{ܑy#GH_zӟ|:)! ,^aŐ!G*\°ÆP"JdbB"D1ƊBJ)I $ST˗0;|\/^ԩg>>ZѣG)]@P Ϋ=u䈒CKC\Ӫ]VJp!AbD]x[,ҳ [ϜÈ# K(@# 477`!-.(HDSbk;lM۸cͻ7lkTfP }-8Ѱ]V䕭jOr7ˇuĈ7蠄RG-`l&i90TUEKƖ ɝVYJ{ea֓P*`T'~```GJ6F1 k&lxRJtۛ!i"qWS!},v]rLP3kcOI*(:$_4J޴'&6X Z abj*(TnK}u#TI#ȓq[#pY#iKmu֔-tFR{F3\l*nDkXp{ QJcH eg#o [`e l/GX&Yb²ˤB1w 43Ia6<aзJGq*Oi{lupjMmc_-v쓒XslxkY}aEFQBFBBo- =#in+$MDLܹ>U{FX܁%g֏j~;ܱGbYeAH5o[: @ Z008Kr' D[ "sV @չa8̡bnr.Jx2 PBpX438+EE3 Eu] w5B|Ϊa@1Ql˓? q*i0'Yu)4+fQ2EZ P؁8 !"]5lnL%ḓW,e);/4Z$܁2E$(l`"J)6Mm1M(@r6v#9OIE=CowyK`v&Ô%e.STъZt M6nz4'(2aHiJ͉,uL Jթ8韧lNrc7&^@v|v͇|lh8`7WNx8gWׁXRn%@fAf~@zz8GUw&{ywk{M 4x|3&& ؀*p`86ҷ/:WdȖS &80֔ A!4֑ Z븃%{&+L359XIdpI;)OАHPCi#ɕ9M) a `hM =_AaigsYZHkj R`o'c,]L@?yE 1ypyy_SHp҄H@IP剔H깞쩞BОYQ>`Q@í:_pdy`Jʭ :QJ5DAꪮ_ꟐIzQ*&~BQ?˳ ŠfmڰeeZkk +{ o{۵":ԘO@iڱ* j3J)&:u@1KIU"aUٳdQG{1[b埯9PRkyfkȑtIuY* Z^[)e۲y,-궀 :5@uk>JѴkFڵrZt$DPn{ tXSՑ%UĊ? Q˺iڱ% PrLۻ?@ 겒?`ƻK?tb 2kQq^KZ_6P5V"@Pd ÑPxCLۨz: lQ ĄIܲ̿:sjHPQJ@$`zϋY3Whl4ܽe˯Vr,cu|\K;k`8er:țPћѐ@̝ޙɚ*˺"1JJl'[FHƒ^fpɑ0 A^+[1XJT}#~-KE]b% ܴ]H>Q3O0 /n: <\-  㼮<%^ݩ˜.|+Oq]Q.؉+~)9MJt^ñ.m; ^  >ZH~g-Ю*nz~B QM9x"Ȉ*h] +w@ 聆:É D J)SmC?N*p?ҹ^"Wbdod\dhjlld^oK- z;)-/>ɾלͦMKѾPR?J pZR\/e_l?\o7w:?$ -ZNV?/^dPG/߃֙D840d@iWOXzn]!b_4i4P&Sp-dȰV-SBtRHtw$9R2dҬ\)ǥK1ӼM2\tRϟJ !J4 C.eSQ.Y2ULneȏ  [HD[qѸS׮x鸥oݿv &\>'Ӹbȑ%O!&MI ͓,4UtiQCm28bƍC[2_nb$Μ:w9(ФIMt9ѥON+תE5[ ,n$yb_h{ͧ_#߿~Hp@LGtA0V*4VZeÆh5N.z D $fc[4)CSEqT$/(n "<'TRI'`B媓r*;nX.31> üt>8OA:N<|3>)O& Ŵ c9-"6"F#yDm[1GhTgF_é TNHtWJZk]J&* ՏXdUvYf)YvZP:+BV6l[ ;4 FaӈO, O{]C܅'x4qTII)TQRuU ~U mUxa;.x<3LKO06=84YNiUG"Mnco[pqYDJw$vzh?W_I͋Rui ^ [^#.7NQ]?;d)WnZk$[ y憦 t9_LgE->3Nz[R6ݥdJ`[zskڏXkh᰹Ll@>VmUo替{ZooPB·cqiE+\D'ĐJ-s&\z>ĿAmgM7/I:&jZZX!w)d !Ooq#(,Pz„GBJ?)tT0?m 9FG>+ŏOc-";h$htMnT:EjS@5L (@`cF8BbZ a%(F:OB#٘G}̄ 18i{7[H+٤HIm? #e4)yp A߽&`1 nY/~A",-( 7P3XG;:ώpT&X BfS!MB Aɖ< j.qHJ$(yr3#MRT%E7EhΞCL"K42 gH@\6pT~a.@)NA s @IYM<ލofKUzS2Vh8Ix"lH+BD%w P'0TY^B$aPxGK[KUVn(t^3'֧m"]xӖ: ¹(pyW(@vXGpAgTf&J <b[P'": KXbRqDy*N`}r;USIk-Bz%7ڷdpZk8&E.:+^KFlM$ڈRVDK[~M!l-B#e%q; II w0#3cA$ 1`bB$f%9 C,|>3nFri|:Ӏ*6,5]gWmc/ Dq O`,2+\@-(*RL(}R?ZЃNh=Nnf)z F"PNieb9ˎ2\jsc>[4ahϢO!aA.Ko}5$hfY!$`d{ gx)v"Av pm bO|Z@`ZQ*!yBoo}Ǵ5>0,` GKR8=Pz* #$BW{kh;c?9,4PgN\۩Ap}Lv m{IbC+Kސa %@:YUÖzip/T"%#g;Gr=orǃ &eh)Ҹ׼[ 4s@S.zx';39H 9 GX=9*[-TK7[)ג|A>@3:>Ɠ??KB&|6u4DKA(&h$龞A+Y< =9A#06$(*2>;&J1#\bBB,DC$DBD[7˭aB 1B*C1 1$Ðx HCOzj?70:LS UD (ֺC@>l@ #C FaF8.Cr[L <2$Aɑ̈ DxQ,90 G W뱏)oq 3sDG<ǁ„u?G @<{ |GbLWxITHBRcdd$HC{ºƑ02wm$*QIJиC[Ȏs3%[:(_Ed`ǘ\KW÷sEےNɃDӡ J <ʠ$CxHn7= Edw$ȫ,lD Ѐil:DDatIəLc|ASICʖK F$ʞL(JQ M Ίħʫk HN4AN:!IB"\B<ԔIִ)ɳ(TĿ٤ui´2HT9D NpHTN̍`NPj΃+d ЗrlHVoħK; xyyOy# %fټ;P@ %PL0PɃ)H}P$$R и#vO}ûLOZl=O@P>.ÉңQQ4uGPQhE8=57m GR5 4RLpPbS%H'G(uTm?e /TDII4M:?9URO=;<=->=bTdBebwkNkc"mk*]ڳK:aK~gttS#e8M|Vlq|ޓASځFk;޵Y"9О\B.GsMS46E!R-D:]]ҍyQ {l3ޝڨ^m~Ul4M;JN`\U\m5DkO~YMcM6oe2^Zfu>2F.!o9$NroMPFЫ>Qp7/m̻jmШlpb^դ"Ϲo?^"-UXw? }6c" $$Wrfr^dFoE"N^Ѷr^[p r.UtU f,F^3HkgHRo:s `5n<q=WgbMbVr0^/Wى 2?iL61OFoBgmQsےoڥ'1$XT~EwENP`tM/qwd^s~p!'ymTwեxA⏯y(y2s߄x@1Zסd8num,DoKeN-a~7T]CbajtaOayMwiR3^㓎6eQVu٧shn}|깇 O.@Ov&Q_|`'9eGO/~ύg o~4LI",hࣄ 1l!Ĉ :P@L1i#Ȑ7,i$ʔ%Iz2g`A"w KI'mihMJ2m4ԨR~4@WCbnux,ڬhZ|Tl­&nIUۗK4gI/Ċ+*d>MjK}h`.mZ,Gf5۶m߾⣻Hcy3gRʗ/_MRnye=JZXn';jg'nF9@7`G։dEEfWd u]{Zy&PvmakYE})`N2G `d(b UutޅVdmއMmF*ZG22gؓᎡ@BaHiEV(iZyIMVzZJ.vYgaɅY)Z)I5Ogi9֔Ue'aJJt`1@=ʥ^N挚F!Z&FvdFM[x5V!F%պL֪zb/0qIj)NQiSN&цJթX{#F ypiߞZ|qDӺ(U1'QD:rZZ&S!z#plS r ]|pT8z ᪱oqиq&}Lk#|')l f(`OG9_VΜ$O| Vbu!o^J2}UtF`$LPGe,R" p؛=DqAFdѤB4Ή =- PȾ_KaL7?l;ֹ@&&tc((] Kv_$A!&k HWBGd$$+ n%u*MO; (DDD"{NPUT$?i@\gC{/w>x~!.%*\J*u2ECvy/Lb&>Qz:;ҽxQQٿPMsPF1YOg=`-_\ٸ]lȭ ]b\ +Uڠe5n=C9` ՛d ` ̠h ~]%Uja~-U[|` "\:B^) Z"ݤ-"v!!uʐa `!F&*BU.^ Zaۄadb!Bd2\KMpb%T"d)B,c,\6*eI"C;"_/8 #)O "*.3b+"I$8JAuaLTu96):6BO48R<=*m"YIE ":*a3*⹹4֝YݢlE ]jp@塠Ady]B.*yUc\L#͙\m!Sr RMddAd"8^5F9]>`T"y (@~#\Ζ2.بV:W*H[#ZBZ%v<ťc^b X\d` ]XəMsa>SLcBaeZյAFY_rfgZ +cYiZb0fj"ffk lZevef&QަX}v\2d#^̝bUhak>Ȱg2bg'w>_dYv{0I [*(_n ^z׋6F~g;h,M)Qf~zޥP+!Di&Krװw~dZ(A$8hzBzUa}f-)Jx'H2*<꣆V[U EhjiUI(*bs +ijv|ODyG]ӴM*HjWwuKdh"ѕ"VƬV2j}* ύ&EH`JEIxD$v2nd]k%=*y~$*\+B\JQ&'ybUb+jكAEڛk^jc"H=VĭA,SiD@VUŪ+&׬f'.`&*4kllʪ*Ԫ"- [DF|䒸jy,&lN^,V%b!B%|=Gmlk/bF)lJU'-R퟾-)ݶ#6^NThR(2.:.bژނNdݾI&uB`(yɮbʊ5lSa@nddhnƚRRdnPm/fqNX&*NbU-ܖ>(iGb#sAF&VElVo$_n2S9l̈́foBG/@>Mo𲬽xު/(RpԎ.vu@/Яnۆ:a6bmjyn[ { ~`-fpWNPtf\d4sᰧtꢇZ6g#^1-C+ V/Grp)tpW%\ GR p(y iQQl5!:re-2s.$7].K&[ku_ȱUq))U*qCH+ŲFBr27Ӳt37/RE&.;q0k1cMrh>2W*O3*~(y 8r t8399 #X:պ K_V+= 2>KZ>c";ua)NAAsB+tĞ0cD7TL134rFF{IGG2Q56KG@S8W5.W/SrM1te5'PFvsAs3r33!1m V:r)H]UuVf.SrWj^1]srTkKl=G4D]YJfWy<WCt(L4a$btD7?v>5/s`Ko^^u`W E*4k2*kt qP>%9urvfUva 7/u*|PŪeC5vvo7wvoMx簝lҥ:gz3#;]+H7[C7x^]׶Y83gS@ӲwǪPrˉ7!(74sǦq]P:fZw]Gux]Tk7ws8woxw1~*&,ӸC4Iߥ/p{K9~tny-/fWWO[LZ1L ;9KJyf7G4y(ቧl޹y9s~r.DVn%z0)3zCzzoty'mSlttM7﷑:o38C#W+2w!z#32a2pREz絑׺2߽ssw 9>ww#|eWkL~9Uoڃxd}>P\<JsuwCt}ށ$ ߸mWtH" JF >xoHA!8ՊL)dX;^Tm>wgd'gâ$^z, ;):u2f9b{lZV5Xmx!{5;Xq kovN<6?]Iqn[KfEYi5-t=0swo"-Z_{F^9S ]79 XH=B=1#ǹ%?lT%yfQ"Lk3ɱ ted>o4!+8}M?sS0FI|Kְqh>YMN`(ۥXV[!# 9 SB@ @Ql Dܫչ{Ar!J ׹n(2BQLHBn 5B$ FVT%]nsݔE͑;Eբ,{H8.-Xg!x6 P8 O@p?Ox(^9N ϸH^rWY[]^<ypAЉs' QPҕ~)HPyԣ^t/?! ,^Ő!G(\x#DpH-2f 1D&H9ɓ(MbpRBʓ!Z0b$B(XrbeB8 (Yaѡ@H=sf5Xõk=`Ê+k>}VBpʝۖ+ۺk˷/CLx>j0j|8PB >&uq")jl1 +b˞/sO: Nqå*BVtܒ^6r9E{V+w^{_pjIscgA9E]T[kF[EVFR 1rH܇ %qBr:Qus,jµ(c\؎ud@caB#LFn`ACԂ8ZieFvECYnaB!tigpr*'\marREN'H"PRՠ2THǤUEՌv3jVh#iLAUPg>dD0P zIј1jyқXp!rvҹD>9["qdRSvq+U8jVZ宻X+3ZŽŇOh@^9DMfBT&D&B 8P`nD\PON֜mN2wjNg4꨺*bqqJAZSP|"#EKDEOen=`m跈&jD Xq 0-qDRgi F9(`˩tP3gxN\ASUkS<5B.AKM}=P5OvCZF/؍`\ ^ⳙp 2%'ԴBIȊ&c\W7̠ƃvPvFHBcMV((Om/`I/#ճ w^bf9,b,Ë#-AolB KWfn؃1ROܖؖ)0)1K b4u0 趂 f$  !"X& 4Y&i;py(#O0z;[{8!~b?$ߠE/ŗ~|$ R$3#@E ZaM,Ql 6r<8gȩ& f%'Iܠip%CIV< x8J'єGh$b&%~oä8s"dA en RLHx2'gXT STVUŒ .I%.m7uWgV1Y׸5^zVSZbCC)N8Fkͪ$* \B` >0(lc2 ˾LC9{ń>'lhTxv+dԥFLlEݹEhn|.}j.j={e^oGn\b\Z1 l#()s܉@WpC]@"J4{ݛΆNEYf˻g٩Xr Jz- aa6 *g@nzIn \|3xRlx1Gd<;zE~c*|FV) <܁ #$1jeAr+Y-g&MSf`3*Lָ˖^7GR3Yԣs6YI9[yX{n()i`{Ir ƌ~rֳAe ID"DeNc \;Ox.OMvRli0짭F@<'<y@M'b7xn[ ]w,D@mi@;P7ԥ cJX&+f`{,q%H8$p]S@EѶy{d]<#r< @c.sm {{7w^E0%CΚW u~,-E& 1rZ?س,N|;_p8h=w׹]{hNsXgJܾmbUÂnD{v6INfegzGwtSGuRƄL`{dV!Vv7F_'qS|\|'_i_|%OI3ڧMq(i Q}Mv0CIg=N脽'+[un@,\y6c,SX4wbvN|cSzR0vLWiyp{V_dwvw%X_r|ȗ]5F0H}tk%93t]eוpA`~WD(xOh=7b7W J`D_e]bE,"| (u~P' qh 0t({puv{˄MAYV G'燸jJ]d]yuTpeFe}rEa}r_YdD*&5P =㦊f\8Y+\yjhxAv|lt,5  PLw( p{+XXЃevYuRSvv樈SP3(kZ&9d+ԏ_s}?7%`DؓWh+.f67giH]{47EX5cUjǂw0 .X8j9'jvivȍXIq~hzA_ȇDd Gd(wvDOX_i~ t%fcy;ǖ$x#s8=r拎F]y5] #aAHy/9 - Ij阧`_ÈEY6I޸cWݘ9IXfL~I|YdؗQ6IwH4uV2_`si`%PLhC[`ǩn&h$z "z0 P R ]9W^Ӂqo٤NʍٓHher{_VOr`*P7 HgBqOweV硺\ slؕhU[FPI9c 3ZZ uPpA*ʤj j2FexLꤓ jI^I:&׵|wYWh* Au5@u'bn,gE9HM@Z VpA2aW-ժ,FqUO9WY*?Iv"dHrXuA"-^!`P$w-hu";$]]D] ؀ p 2{ 0]@oB?I$HK'qPj? :zOewI *pSp6cpp;t[?fxFwgdpK@fd|=ڣRR/93먢p ($rXPnF!Hzj]aCy|HPS M^'iMYVFN٪ pfkd`?;[e4ddڻedt{ҋ싾 / P *iw eba+5LL %'x뺯jTN+ʟMfQe>Ȭ(~Hv˼URFP|<Ê[=̾uLLP ř0{NܾQ\WgL `0 ;P0 u<lr,69bqj׮Orj+(ˎr,y)Ȓw F齇H ĠHá,ʣ|ʧ|ŪʬS|XDŭʍ;)` ,Ee|i 0 >p̼taQ*i3a R'didPɒk<㘈͢UY;SY,Wb9æà=S ]L[Џ[äc< มѦ MK/Y< (A*5 p{L ˺zq$vNppYSaL.^=|:<@ } ˯}2yĝ؎KM Ԑؑopʣȼt@aP, h w1wҙls8ȴj76X,`‡%եՖE=<@ N fZ\ ^я] H;ʼO|P{A>왐ucNj꙾>`OBcPͬmD ~S4w?`aU. S ݭ;^!֚nɝމ <]R-%جN5Jߑz;(m_-=] ~x #/-M\A):Ym-!<>1nQR:o=F]Af>S_ R_iL, ?_pl/@ 7W7pXlw{_ o iM{3njP1"cTmYHP*O`@N}"i}p kNUJ)p-dpTFݡH 2d1dh#DDJHdԩ+_(O=H˵3 b|Rsб+pO>  ?$F!;\s" HB2T4,F|ʓ09Id=p40!Z!(cfґ3r ;LJBڃ4S,3(H5/sS'2D[4W)?C%tE5#m‰,(-K92I(r0EXU!^X#b0cP)C[J!sy})ة)Dej=14kZjMlk=drJ܌ͤ4W ֝dEDiPE%kKic6AIr'X."c=r"*0WLEPwl%<48#Yӓ+OgbL&\:ћ tr# 0o%$BD- 3Îz^(:Av@tFv2rj7R U-XWJi%?pY'Sqðsd$?#XO l L@8ρN3u<I?L3q;-(wZBpQAT4GDBZ ;|è:mKC@ i0 0iB@,WP2LT$Hv?c h@bW%c dE+>Aq%x@̺FD;A (D_#|dtG<$P#.U2RdHH1h*b+DS,nș+$Be030) a*XD yحDQf-"nC#<3`');'4 W< ( '􆣔jsP&)Z2@T%)ȳݲ 4^&7#R)S%a[04d*3M4Z,5nUCN#5D< k/ښBY  yPt*44}Ԣb,TlSz4 (ԓ?$laᄙd5% υ>UQ7TfRCh mGD_lVtOxQ#dpQYNRrVNnmn܏-TJP\u0C]ώS- IG~hl @P6Ie5{߼d LA2rԣD*RQ4B[ݮ %o{HFp_i*W Q]ށ(!Y:S @J1of= n&Z%nvs4{;wC겤in:_wsn &{fi(Rݹ F$h:&> h.I)(wrd$Jg9@Ex!T K<Ѐ睋,u!_]Q!$3'R0T0ཛྷÄ A?3A;(U5HP`1gv1> = X $M͕[ʓ?P&CKttG˝OuG+:B+NlEܫ1S(m3 Q LSU]bU>IѺUz> y6D;iҞSVg$VQ& בlSL D0jHX p۽ S8T SpW׳%(MVT΁ב E);=Wg4~UFסH]BNJJ Sln1bZXnO ƑZuvݓu93̌=vIh$UB 18lP[_?uZR`l(cC$(衸CFCb'@ F3O,>;8%C]ͅ5ِ۷ ;ÄQ RHC[T۵}Um#p>}6A+ġEپݠ!?M؉]յܴKϧ$5mߌ-]uؼu]QQ ]_/1ݩ'MK 3<_ÂJ< BhXp;MU 5ù%Me[ 5gXiPFb8@j ]i[[![S .~uD|A3썛= wf"|őLl0֠kVm fVl_R.5f݉>lgS>y!.{ꖝ˾4S,L,sB>`̅5A;傈N fq5 Q_Xma.kBvZ_Np5D_~wI`|JBn}ۏiT֚*Ty( ^܏@L^﷖m[4=ؕ_m+#dIK$E~˩ò,$fW>fa#WʳTΘeMm>$5$]^Sm~m~E9b!脻DrR8B@K@ed@ǂ߿Ln^w̓OlZ N>Y`XdV70gm?Qe637U\S;L8Nmtyǧ|zqxVwU|`d H?uS|-wPyڔ 8W%n}%֤>_a |mʭcqq7o}D(`O/fW|ٗ͟Aq`}_Q> }ӯ ypu~)~o}iА\ymck@*h0G&MᧈRt"ƌ+jQR"EjH1$I%L $ɡ:KIX'Ћ={$sMVLJ1T(RjՇ+ذbzH"D#5H:k$H %2ׯh0A~l0bQu?fHx5DeL hFSЈwW\.ӻҶ6T6 $ᰀDh|-C):2ɢJ2/¿".00!C>9叝1yE,]I&UWǤyAP&nMU"ryoeb!b0v DӅeTħ^egxw=fe?]4`)?Z}tTU%d嘚U֓Hm"՗wD(WY!ciQFbF#aȖsuxI_hJ'WNQMu)tHeE2'P=efuz&i&XQX&bmFZz@ѯnH?bE)Z1$^"4M׫]=>Z_I~yfWOjIjDVUT!Y$ɗ꫰ag6XUScXeDAPeYF⟺(w^~)L<0n^n𲋲)`(kmki(ZpO7*0PE,=& t"A[&,2.$3/Y檭}]/UI=*nyOQvNb\Jm}9]}{NW.ȗB{6ym?t({Df*}^}vBݤmb^u ;mSesJnqf U-uzyQ+zu.w~G/HFlFwh2뎯xw. g&xe Q m^YUzg L(%Wmq1btS¹_/Q)0g\Gؕ"r WR5ٝᎶ{3mDrE0[-h]UdiL (*4i YLBLkS8B'2:La"D'QY`RdqDR Rȅ< FY U(ޥdq_⍫Ա8;1298x[0b1}IhJMTĊ/4$o%hy0`b>+Z(L+.W[zؕR@*Q$gs*&sf\0Ld6^91 5P qas%gyy#:#vҝK~$t=G8r)@<`Zq1UMp:~g˧q~~ A{s\,@ix.p00@h95kO϶+/Ӫ>*@xo!KXaa4&%Jr& X%֞՞mrFBڪuO*aR,=|j+ X9YDRĈe~&Wuh٥-08z$EZEmmm':ˬű| Ce)qk.7;oH^ +DnE]bC 3~Uy/֚Ɨ}"{OB7.gH5,ba%&XL,'UY녥77| /LIp=hTyuN-RVVb˜׼0K^TM\\ )YZĉ`ص,ńp@. Z0QZ圃1]fz]]yMġu`L M } yeQ浞yuD~݅HDd[^oq$_E6FQ}1=8GT!LؓI}`̈!]FTeƗp_eWލsHEF("XD!VR`Oa"SiQĮY$]ɽfAh<# E⮁b" X0T IԽU%b|J>K0c-B#+ʽLLa#O ʚCl\??5=V/V^e<"1 sc9;2c8#q'9%vuJo'0qqe1H^f|E\帝l[th,>~B\ٖycO>'ӂ2chZL6| aH>R`U2@:X@f|1hhr#§sh.BRlh]kl[>#'.%h? {Jh1PǗ^We[fw=%rjIEJiMiNh8#S%9V`| j W~XIY+ģBꜢE`^*e8yM(f7]nf\*«jsϤiZ\zBF!U gJra1k۳F봂f2=ٵjYl+j-)ݽi6o(hQ eB)NbZR*ZhA 0V皲dv-Z}筒Umbn2*(rf"X1Z+4 jvbShFl~بhhU*Bvl,꜁f'lzιT 9-f,rFMƌՒ qO˗&J׆빝,^m׺h5fJJM-VRk4K˱`sLKHxE1UOsN5Rת-FP QB=?g2{fU[u٭Bl_=?`XSKA'WgVYEOC?5)s jtu^u&4`t6b'GA_g2+&ML춴YeGFfUh!Zݐaُ։/kcfr'mU*1+2ŃT_=q/JsCZRN77G gpwwwxc,767xt%aWOP鷙7pfH  ސ&iva&Z`omqzkM 2(&xw3o6F%*fwx^B+U'9 Df̔?w[2Ε*/ZWNhB7]%PZx,L+Wɹ8-J9G7[*{ߧ%P%%o#PHu*lkXcmӬ/*qg@|6[",IpuTQKq)4DA,nغ'zc77q; 41=&lzJ/^HK)VufcsxWKJB +F5VBG½A/sUѵFvq{#SÛ;ëwU_󰱻[DHHx&d KV+{BF?_υ"Qs\W 7ч$o=ӽUOsGdj+o{+37s,&H`č}& zgU׽|@9tէOkG֗*~*n|;؛H>S$]3ec5a观O)K|nǫ*~LU3/ ?t?}j *S;{L<+ JuaB\JE "RHM8D+jh#)P"Aa2yRӧ t9P*2UN4uljx¨rNCK"jKK 5 GSxp%M8ʔzv*k VSܸoS] Tp`CKEv{;^XUzE6~7M CDI4A C3ݤv7vٽb3EX~E0 Ij%QO"H&\n]>H'R0$ҦU cxZXs_/]d]:+>U>ԮL=ޏM0t`Zk]mI&Zɢ$iC dCdLCjɬ.ͮ5 @;bMj^}d"Q&[h$NXyJ C k@zP4)˵r}; 0FmvZ:l-Z7cqdS)w"DbV;`ϵ5"BJ1Z@,N^bdGf5 b',|KY8untˉɒ1d0 x-^q,cP97}Q2cҰQPkZI7/-)%&牀X ik";!E΋$ ɅPVmɷ Ph'?j~Dc4δҁ#*AXpE8)"ӊw"ޒ-jHa,d&JA4;$gF ݡ5HlrsHxў qYH`HDe=Jy'<9гŻAD@[B-r-|QԢ[ةPP) u6#ZbrIhĤ .99 -X(eS_VKvR4,f?= P.Q f|@}bCPEV*I+!Ƀ>2A! z`[mӏAȁxkk^ӷWc6$_V1.9(SWy6"Ye||6JW<᥶-0K_HDw\njw `nt'1l=a|O2P-p~@:OϟphOoCҼc" i 0*-|J V$a@a !2P`z=|j/vJPr.OJ`"kqq%$/HdLJHq2qρV^|뙠ɰ`> FѮC-M숉 ?¾PRLejOȧ̮BhKTjuZb!$ q Χ"V_Pl.fqϮ}n&\RHZB뷀 ʍ6ʔJ`cNRcOx-i` )u$N@*rԶR0+,q-BϠ(o(#.#)3^~2o\gXpy/)n+h'5CZpr#hiVe *5./Ѷ"i:j"fe|2U/X3,C`T16Eoz L%psbT#Jazsc81Vq$5S!Љ 19k j:?r:o80sje<<7&=3#=Ȋj0Ʈ&3V?K1o:E;"(T79A-.ciI(I(<4 AtlODEMTI74Ise,4;(EA}*"t<-?wT S9[iL+&3GqhItL;4iOJ'3 F2Gr8HY/Ic2&BN/?b3AJKOt*t=4H4=8K0TfN"4(.W442:STEiD B DT0;`H:(!h}r[_SLuUƤ!UZg(2rHu>*@YYc&1 UZ=ZUIWkjh-}uZ,F5j 5բ K!U)$ xjHCdp*B*ZjiC)IS!5{X=BR$9SPC]A]#vcb#$,YMi2am 93q(QeIjU>Sfmv K43`Lbh>2K _E$i$AR4M$J@+ AVG#BCgV,Tl!pFZPD3S!S>n+ Rj"o°3+o[("e2(rSuB70l#LI"_ ^#_'3(35_AKOS[ ^c Zkm_K ^@ `*! ,^@B( #*$E$ؘ@G AB aIRn0B؁͛8r"Y j USѢ}1S>PCUI ĕ롯`Ê먬ٳhӖ>s궮]U{V߿z }[7,Èю]XպVUF ,`B[x1a/">TB! I`Rw;h޴ɓ'>z5CҡT>~^ڵwĨ3j<]˗?>-!ϿH'cgUifuh& FQEX[6Hنk)p">ahgHwTQNVq]RAidU%Iux#I'Uf[AJFiyjfaXF0p*."r0H6Xs%ݎF5䣐FݑM5%buZI^9X!T d k.Pdq؀Hn'M+gqq3J*^@hPfn+mUN._XM.T=JU vP9f6ZCKh+FqLĪ2 wOk$F$'ѱ+SQ*QrͥU\`.ag,{]ˮ]jmTMn (9ؼ*̰ DkxFd,xMG@ ,2%_2-'>t =nb>y9,En2/PMf ChyZD kd='J~,'n,ww=!jxt2ooxlsS[M㍆/wP?# h!4PCl IR$'PNmlSVq,ERX=Be/GfHZ |qY\4l: "b!p/<`D&.ai **p dx3&q8(Y4;%LO }c(хI̞5áA kYHEGLjHDF!ٻߌEBBr*Fh!+XAX;qQ0<2ǂkAyF L.x#*Z5%~I#^2x<,)%A@ɜx ;YnC֟/Oe,7Y7mM Ɛ%:̟J8FK(J!8BBNd Ⱥ njzz8X N6+~rkdͭR3nlQV"w',s(Ѓs6jsx5gM  ao~c*UhM+&ִ:o\ֈAx]%׌" B)@X4ٚEH9ewQ|GAxuֈK-eS | hHCZdvz Ѵ"p+X^M+pWܸµjWPպ}j@@ baXj*Xl$C)TNn#b\G=i] L'pLgDZdEkWxZ(F ;hgL]t.xr& 9I:moޭD jB'31N,#%sL`iP62Cqoox3 b %6a\ڬm u,@J$4j^Y!l)QpbY7xҍbW$YFe3e2̴i=fZ 2ig & 6v܇ n-b 1 ^gAOrk j !n cl4y'Mfֶ;FɬMJTj$;窍Ӣ|nefXF/rxqk_u8HgƃY܎mO4wH#!kDIb,H@]EӚ>oņ YxAT%tR Sz&NxrOZFEp0|}m_ƃVIrM*[Ao} 7Hs|*)nw ،M C%`(0[[Iqz8Fþ] cG_)`E> w%_ L0~|stIemquM`]l)S+WX djnw7v~p|~vurZv3SJ\0L{GZ&rvKP{kv 8|vȧ.r|H|$h|7{slGhw}0_uld:i79*~=gyFy!;$Q0MXtunR;Z0o1zWvssNY7Nq?vηLĀ\@qwp'?g6z*Kٜ(::I5ԗȂ"w>K9B)&1cwcw7Y뵸*^* P f{`: nR1x+m'tK67a s}ʢ* hYoGI&๜xdna+o lq7iȥYZ०л l[: `{+OHNЗN'&$[ʬx>'z-QJr;@@eQ ,,>QlZZۉN`z,0+5uKnڶo+zTЗrS@ ـJH0ܫȐ,ȕ6eRM ǣ+ ŭ@KWE2}˺6$KkCrFU7 }nA,|xmL\ 0Jga= QӦiɀ?elŨ L@@*?vZڡ, 9 @`MIckwgTeD}wȋ  `  L̆0 Mli2 V ˸CaJ@{|,1kY6Ed/И m ulĈ=Kl+ݦ+wzp @ |ȜD۬ [fo91<?=2"EF]q hǁvP- b?DZn8ڣl۶ڬ}yж~а m v H0`1ƍL +ajn`IdI]:ݲ=޼ufЈɀ,<;dB6úC{pY[:-PQ)~Wݺjmכ Mz`OcP; .ݐڂJ?ݗT'|\Fޓ 1З JL7n~~LPs=V~XΠTY̺ۣ аva.#j͝/ wm|꛾-.fgW=Mf#(B(n"]RY.[Y̴ַp o-а怡Q 6g  |`]0M$ͨΈY,hZ:D]}<nn.Zn\aN\^>^ꦎ p బ ^y.- hJ;&}h"iu׸T3ήZ221~;,.ߚAei<;/^ N_B>g{~T!5p _Jܼ9ͅ>8FvfrKű8X/䎐3p8O^ @ >??+/_N~MzA O P  : aa.`,tvZ#^(qkճxs`n}:?@   ۔> p .__  ɠSfJXʡÅH$H("?#ҤH>{Tq˗nR&9u ŋM^%Iy&ei Z@#|a$#aŎe`%i.d֠ڴνxq]y/X&z!%?7H?~R%@,)AATo?FOZT1D"@7>.90D3`4`s4dD=k!>z-R1@XdѸ\>tS{4j):HT2HH.'uV(u,J.E31q)SM.Y1pC͹._}0BE+GB)(tJ4khO4:1,(r(eRJk.ȥPXT[; -hUWR=LWΦAa5ŰZRdV;Ȑ h'ZlM7=+)3=׈dE=WWj R+&&X=@RXᆡH"bx5()DB*t[oZO&*vPt V[, d4;y$"'%r>Wi i`i5"@Wh"xXlh 0n괲M,|jDD20hCNBrJEkZ+|a 5r(TܮFZu:&K7 U3LDzih!/$$B L)P_)z{= 'Ca1\ ly<$X('>~v %mz ٴI! N5 vK=֭>a1Z%'ݶԩnY4#к >f[bȻ=r=$o TfX)3.r$@ז5jB]gfaǹP'l u;' K^uPˇV,5cn]4ՍfthMk_}O[R7E,2pc5&9+ (Tf? #È?A_VdIV⬓ EuBe;E'&B@x'԰I: w\"G@r&?Ӷ2ۉ8>Z R{Jn8[o]Ą(=S#ixKYTj={= ٓCh$# t">42p1).'cCˍ7 ፴ ?H J7GB<Ѓ'(4|@dკBpLĘADs<{u) ?ˠ Ba+ѣ ֘E TOEIж?iE EhF:FJFWXEOE;ߋ +*H!ǎ9wDF3AyH <3x3I!!G:Hm+F? = @C2"2"H|þ@lFsH P>!2I2(XϚXD9"Fp.D ۜp I2'O)<ʉ.R̿E]yĸ_L@LƯ=ٿ<O@CAD"O44@TDވ"4DC4=#ó$(J ̬N|_` Joy:\MP CNEضb*}Kͨ0PdK(4+6,ռ'L6 蚽EkͧFj;#;͜QS RDS4$2}=:OȺcesPɐ; B<=/ɋ5;tAvj$ jD%IMƇCkj leTV\:ԺUQ%OS:7\UN;24(?ȜնhO?UJӺɭHh,MhDł=C;nl9Diޜ^cij><[YS3\gߞ_EfċGu$C>Xq5oFRonP!0S{oUSk)s38sEh3}k,PX-I n w U'fql0^[>\J93vP acU'EJtWB/wmuT{(on*gd n.N7KCQOs-sjcnlgm4934 xe}B7 \a/\cVHK*Cp.>>dD fK6uQ'!sQf>Xgo־ NK~cu H'pEum>SCk:JС)vxBGhoFpuqwF57_uVČfV*?zVRï]B3aOy]^_GxRxΦnto"͂'xЋygVEwЄgЍyw?|?oCO]ʹ2r;"f_& ytlf?|dtp0R,6_slwx{ߔλr ޡkE 82zkFE[{gP/s G|ItI<ƯdhMH(ʯ}q,|oݳ&'y5\Cʽe^K:?o}t؂8nL*7w`7hJgRKi҄ „ .,'N4xPpG2e$iGV\dh̗yfi3Ly iЅ?}H")T#~ ֬ZrbiH&\rνs[n%R;Ae B}h0ن D1䃋 kF<(˗SjMI!V \6nb^7WVYaiCIkA{.tnDo"|U8r[<c6h{GTA_$g~"MFr ;%m&lU)RdgimJ+Nuמ]גeV"ۣ?"wWz:ٯiKWVtIwحv -2?]OyhQUJzSٷVEMv9j V@5#6q- $ƒM@i ߍ[YZ6''B''0NrfB4XAHקoqpbyx Z\[Z؟#FDEemoP$| +)wyEw/G7Cg3O:Էp8@>7zl:K6#TSS] w퉸v'w2Cny!sRhc8|k-6kUV0H,y$:ƌseSFx:/Ze%KfuO6oōivgrʼnu[1r^WDdCXjnݸe8͢δւqA|ӷbjo@|jmYYQsK;D۞\#WZk5}O/9 m$nq^y6TF+)Cڲ2imILcMՓZx>+嵠7 D#z_Ow҆y5zoS׫^B^Q{YkIA~y |yR]͒=O(_RA7ןl[gC \ݑu_^ʜ깜kO t9ZW^~[jM 29X"5J}t .XA`I] ս = ^ y-`1a] ![ݣ:kRPF Č a|8% !_ :Ryۼ*fn|LrYuf avy!&b"P#>"AoRS)\&r"/n(NzS)b@ q7!ͱ`^%Jb6"'n"x!Q(WbAd 3Bc-F,%Vb=r]c>F&Y70&B&q:@;2T<+V##=F_EFV^c)^Ar(bAdIA T4d"^6]!Е?>QqHf9DJ$C>K>L>%TA0DPc##%]$%J2%#aTZVXU_[de?#Wv1e2e!eJeK `י%H^m%G]J2ΏYRK6 fj,&]!`cfccd~XR ~Dmu dm\qX6d&Y&fF`lDf#>eb!p&V gňef_^m>$bc%Ju"Req*W8]GޑRxV=ZYg2!pH7{OXR}^Z~a(.eߓQ[d$Š(Lub3)[yaChhKz46Hm ]_&sK&hˌ%j!KY∎rC)hr}'x (3]y%EhY)Xh>=X:BSi U~qlE)AeʱnR%[*i_Ep`B l)&*QHPYzql*rz*e*F^aߦ*fp*Q5φCɭRq$-܄ +L^-+ΥM_\+ꯜE#Vk\PbkPs'fjbEzRɽ愥뵞I> ڢ6.*~]!jbzlp,VHXdkIld['Sb`:і 6-YEe*ajÅ[R<U -Һ}*꿪r yi0T8u yCK1+mF'FrV_xy Vd3$FJ.N$-nJiҦÊ^6"ml6o*=n"#,ƈmv~-,m:jȺQQ*D bӺnF/znbzr|n-)m.!0s-uUmUa/-ikN>$W.8*f Tl^Ӧ-:-pñp smO g!cZg+~XNoo1֮ -iV [߉..B)g1 l/ S.=.ln"S, jn 0 Q#L*r'Df2*OƄ K 70)l+ߞjy*f#kg$nr :Sղ2 2!bQe(#Ff%jbnVAQi.wr/޵k |(:Sspq0{(i!3`q,o@H9pAS3t}" WeU4I'R '.t.r2@ UHKޖ*5fpblaqݶDQIY Dg3M;M_e9ð @O5C+)z:qFoMgu[pЩpǷȌUG^cƂ/vkp+a׶msE]XAtdw4>:vKVp66w~$hSdU[mmǶ`lm߶`L(pXÎ7Q[:I3SIu Vt1wlx#[Vwz)rw8?ߪqeg$~~7&̭dh B@2{$ ߶#n3ZY[GF/p$TSDG4{,vy{ Es¡H31w1f9V8Yh5ӨȒGG}RkŌSk5zosףPyc'jR/cZlLT(sAݲy?:4/@3: _{0Mo8vJ''$&0- {szҠqs˵s7p*t]C`RpKtG~LzjNSer3{7aw;M4'o+5/*t;k_FŵfP n ,3YpC+#D7z_7yG"c':7|#*XR5;PB'^Ksdm|+)Z0-ɧ:S;T_0-uw:/<ջok1%rK Yvy{o-շa-G~ʒ ;ǿ}jߤ1ec4ׁLe)ӪB(l6n}*hW #xc~R>٣s>kczr%~'d@Q=_ĥOX4xaU b0U)K tS)S☐Hɓ%ELfL0aģ;<IҦM2ڊeRK *TSRTPflاȖT-ژU%R5{cKsYcވmCQ-!Jg$Ida0k̹SgϟA,zkR~My ؒQ-ٔӨ5vIkbX&L"EW.bx*`^O,u;'P-wү]oV:|mɻfڷsc,T6#;,hl‰$N{CB* o-ܓm6jd[ ?.H9V)YHTz!FʬC:ܐC S%>0FAqRͶ'Rb@Q0qsXLC|:J.P,΂F/RJ P1f" 2kSPkr\5٤"i,lR<\uRHK)U3CQ"MjҦ\iKIK= mI1HT?Y%@UqhJPC%L]ճZ_ J3&C6e93`"ZP0;)4qRҝʘyqe%zWa=Q14]5x=vAWሻHH. mDǔPE>dyVC~e} _FҥCVF iS9x Io͚\ JR>峩lJ d$1#n6D*4{%(?4XnZ_A#l'򭔭89s9CW,/Fot)-&4li";(?.ww]kMJ=J|SL\z aZy; ؈u)_d]}z6oxیVșdm@Z'/!)a9IazWSD G`>F_JA?)B""A(ɅO)sa2 3Ar2,rC xL x' 1FhX|%hӰ4}!FE++%Fyis*HQ&cA"4T-eHydhSA\" 9-|$$bڇI"q{$}I,<)#22”0b"b>]9spbYY3/$Lr0g? b(%0~dPi[bdQ*9C8>jD9Jre@1|/*Pt$-9E6)41Mhr(w|!t&ugJ'C8/)]pzU)X91Kņ=j5TZ[detDXi4s=d_. f:UF 2>{('3ٝk+HtrSV%@\j=vՖ )U.@qT W.-mj eݞW"vtoR n [6Tr=& 6ٽ7xK_bWvQbO铇tCEF)“e{熧jNBE_/&gTF7|b2׼0" T-aUntx[&G3b*#苍UR7*yg \yJV+U)H4 *H J B'z*)T4S%Yֶ{vefpL,W3"Xk;3T2v@KLEB_"ʆMw.0;aI딖Nd,iZ$-I'9zn|XDu0|}[GL%֍:E,bDOzrt 'VڪE-Lb9zmfoJE,| " d}{k4@ay^;V V /to{dǘ/ׂ̮H!&pLm b$$8 bd֎$20oPg&*#hJjiڥ~e'cd mk 1%"htOzP y( -0HPֈ Y04a jnmJu².fhn hjPCHPBF/F O~O"z&v&K1l ĉ/k'e[weȁZML իdYSŐov&Eaj +1q%4 cṐ,ujBTj1gb嬑Qhv!)*b6ȱ}nWm:5WoGǘf# /ߩ0ō Q!QRT2# vMOˮ##!$5A$QKNmRx%-/& rc7 a 2}s.E"'wlApA)ٱ)$ F1PGúr+f,.|@ҿ4)r)wb/Ma$ qKT05n݋1_N"bhC֒4^@0/3Gҕ| ɋ r6k0@r5?h뎩!A%"7 s2.W 8O834Ee&&O5=`40AOa 5 @ 8h`*r<Ȋe@ 8?=29H' ?kX%@ ftFEs>gi➫T%>}v<ӧ*Hߓ^DC;S'>% Fݠ1W܂3,MNA?K*KF4@t K:ȪHRI/4 CsETذ'?ehRJ>0 TFtS3LsTZipPUeA)SBHoO &P94$ M5#FQ}+Ϗ4% L(htTS5S9T{@܈z3)USUOU 4TuJeKo5Wy }FTfR-”_2H5UN UWO Z[[eS+\KN)^@cX=vdKEu-r`aHaaa?1A5(cUc^dd_ [\|Nc[gq>klVq9VZgWT\c 4iˍi$6eۢdF*j'k Pk'xڱ=Sl:ϋVX R[Hn5n_Ɏ RZJfkrpe5lsdZZ:$7d)/wn37R 1l HTtGwk)x}&r^-\y,c7ĦKaEN/L/L{[7s)+ڌj}poVIGUuU,5{KzH,aIwwTaI}O}5[VG/bBWYթ6vw e$l{ꜘipqLa1.MXc>o#/NVC%TӔ]`XcX~ZGy4WE7S ]o50GX6yg\m1wMnG}W) xXk"8P* =Y ZP>Xq32y*טՕu|x_ v}O@J$TژYfL*x֚:m}mZeCZ7<uBZzZM$akӘ[\P*;= ؕ8uoUHjC7Wh!a [K\s̪_۱Z36vumٛM=;i9u`a6{Wٝ'F!ѵ50#bv}ۖ;su WbِG6_d^qW0 qA#_9+\Ǜvpδ5VT{^NY7fMa)i6yw<Mv]9Qa{'|q%sXV]At"DvtXqy×K[7jK\n0g\'$JciH֤ S||>Sq Zw[iYӜT @E#V˗]Y4XYϚM?Յ_aq'S}{C()Td ! J4 sO|7iTOYwڿjѷ:Uܙޡ:&@ ~@6`'J "ak(ϯv%1;aCj>HەѴ J`!$>@, e;"l݋S0YϟЏ5鱎ٴr~./}uH X2 |    #7+/_?C_1K%_K__ag?_os{y u{_ A! ,^ A *\ȰC#J(";0 cx>(:T>{t2͛83ϔ~]yѣFsʴҥ4["5իViu_"u+$H[:zg 8p߼iCݺsXE] +Qd9rc&Q 2k\qgCHƍ?hC'B2m#)ﲁ> IȍΕWXOI~ u^%N<0[|Ë;7ؑ$>F"D&`D9̿Vjzځ@uTm)F[odǜZ` v݈$Bz`1!߽ŗ^套aΡ6*ro96@ݗP~@(hH`k :RTJSINS&w yUW%"\3Xԛv"㎾#!h !陒hZF9 d*ieNz PaZޚIGj'©s_{Z<6'N$*AJIeJ,Qy6%jnfmnr ׫"8_ - ofZg7ѰZd>ZeZj)!%؛F,q~;z#v?B$a8C -9fv \2S@KO( UV^;W+ީzkXnM XYj<,d/q* DjV8#jyK 4MRA]u`AYkmZouY^G K0Df6,m&t mtб߼;L^}06롹FSkܾ{|"'M!`8L 79T#C=to>&07y}c6@EowxD롌\I "W i#~02h·ѝOt3ԆЅ߮f X؟pG<ɣybG mp+ MhH"" *UAt{+NAuEhfN(Ea?=]wZzP!wBrT rg9bs p"XLL QĢ)QفG r\C7L^I+Z1".31Bq,I@# Pnf'W,:Gz3s{'=9yL43uAsa& @DGbwlE噍r18'ՒzդcKǚ;.MaϛtpkL֗x 7h0'JPjn~څz5b%f7;Vrg6-ǹLiOƄ>)OשTt"@"k`@@E/8vQ{(KْWQJGzw,/j{ajk^Z4Iy[Զ "z! ~o{J&ɕ"o_ɤ Sv,,ҸQd/O|2$#$MURލV _7di8!IXct&7X8`M g 咪 +k;ָ.lҪGͶIyגx6`b‡ X@磀ҍPq: 8x-S` l~&hٔ,N{PLY,l h3USm#O(qMnlJg{A]=ju;\^w{}ڬ)ç?{ H&CX{LD`!yGy<9)aŸ&6=Id2Oٜ=9ֹx.ɼ{S׫{;CIiQv };#1<#hBWMs{h_޶{{mwW6RI x2w,ii#||5js鷁sk;xGk(saXtǁ,xaidvbS:n޶ddswJ!$C: h2s|nnv3ׂinX}uxT|'sV8daGxeHY&K ?`+MF`90P{&w*;me$GbIÄc(mnF38|WXWj`8sOi(Oi؊w8/W|F2sB@`FX :!G2HЌH`70+ȸ{d8=H:F:H :X8z7oʗ긆$iH؏Ȋw8fMxis2؋3`M`(z;Ѝ)w? hXnxMQ(H6(:8B\E]bȂ/n؀FhC DDyBJLJR6VyBZɊHG2-+)Mv%PFdӌ?P!)/.%w$Y mx2Lj(09T@Bs4YX5]89$A)SvKM9<@ gIXY隯9YBw/ dfy$dX,H2v)7{~+'yw 88̉.Yd3#Si79x\wvי9LI9/AʚJI[y9ٛ_)#2؝?uHbr~y)eӝ86e(1W iKН9 ɟ ʟFJ4@%8KyXz%v1ڡ%z6 3‘q)Aw12J3:60ݹ=z,؀AـvYL)S:JZK2zZ D9IY@Ȉb͉nGqj(ptv:rاɝvʨɜ,96qy / HpJ?FI)暮jJJJkJz j-C>Z a 0myڜʹ$uig9{ڑjʨ?[ uz֚yH^̙Ip H( oAӈXi2Y[JT`Ǩ28Qh%H[ڱp۱ > (;*5۲ 7觙h8k0 ۸HѨA7`F9:jAJ$P`i 9LЌiz.%0pm`iԙoj۱?w:{:6 F$ ;~0}|ڽ۽0ڸ仸ͨi:;9@BuyKۮVk?`lH،.zI9{'kIkz;ɛʼꊽ}ڌ0Y, Ψh 6ʘ34Êk9Ka >KNB[*?,:/hzX{[\nnJ9;!@ HЧS0>_)ÅJ`Ą̃)̬/?쌃<嫭KڸMLxz}w>І[̶±X8vis( ̵۾[!ڝ6|lT@S⌣,:ܽ<șܝ8”;@,2 P˸>ϻ& =uh$EKpg{mg`ZmJռ+ʬlߌ|l΋LBLH͘ Z@C\ԁ+mp7Чl Ì?J20Pv!Ͷ[\ R9lҒu)ɏʢˬhu,w<=>1ؐTl᫷0jC C-M}Dٜ Պ|*}Wm *_r% ~+|B nklMFkT`y m|ݝ͸3ܽ/NB<-n|y@<Π݁ق}޸DnJ6 ދ1=YQÍi{!kֽ!y姻f>fƼm[ld>~a0hV7`%Jxr.ҍ+)|nd@c6NqS@ɞڮ=JԁNL0IMx)ó[nCzN볎?Ӻ= |>&ޮ92:>ypa:*QJEժUJrW%H%[ٱIԮe[U*V^eԯ_&KdRak,Pٲ @`ϛ7w&]AԩQd: m7`ݛ7&([g8.Pla#;t*hQI3eJ;y8;N8 ׽$J}|wJe?804l1ƒ nx*L`42,(5:CJqD?0DP®hQkQ6 %9f)t $ 4j(2;Gx.*.z/{rˮK b?6bK$J ?ſϧ '$ , eHQ   ڀ^x`+"$m#`VzKrx &4@f#C%?fYD]MJ:bYxw$gv%j{~gkʰ5hpbc" eR2 _}h^ {lS˩&YsJ7)}{i[IyML1[$)7ze(He'axxC PC2Ow+g@|s G,Bܤ#M 0&x4moVV¬&8шF$B6q+AS8VM,b@#BL%)f \b^C;0PԜtw;2k\ [E'rI,]N[63͡fӮhB·'"o[ô"7Nw5>oIb/dM[Ũ?a=\kuD:rNҰ.q$u{ߟ Ghe3^??CE- \mF|N>_ܩW}k\= R8 |= n}I&@ W뻋[c0Jӥg#I$+SҹAӿ*޲nK#=;[BE(@E|[SAދ:-7Bkk.{1S+C3.`K)k>9HJsA'DT?V` ܭ!3N(B#T: s%4%RXAHCKWH,t0\1R$}KEc1Mz:{+Q"jC4 lA;T[HlFkKhLVl9ۭYÃ; D0D`D?cf:IhxB|+{E0DP'O-ʁ`;P[ #;65kf"-O.iDvI(%1,]`;Y|CLRQFSŤ4nlp.9n@..2 @qǾnqG#54is=X*+ڼ 1 ^9EtU楶#~kAT+N4?R-rN8K ?s /iKZYžnVs9;`2c=s'Nӱ6q5₸R$fmsM|&f~Nm/0NwPu2?dFTv57qmsWB$bY4rk(EHGH3!6QzdDgab%?ũۧ>pn`pqx/r squ_1cwB{wt:V 5V pH%AL=>{7s3\KKx~P]ɯpSlm6Kow9'NKt2w}"0]$A%?KRD@f_8@%J$L jj!ĈNhI66 Ȑ =~Yd)"?l %L,IьYj!Μ:w) %`̣\1m*ԨRBI*ͬZr͊)$Iƒ-zRxpr.[Рy ]oڹqѓ'Ϡ n(y*̚1]YCGrySÖY:4m%KPjœ6?-z4i)‡gZթ]o=5Se˺Nzml!U>)R6~K%}2&mEL-ɖC`C:&ToIyHqJ%s%r)6X ).DJ&ZXv 8"wu#`1Nͷ^}ndFK!&iY&Xbz)!Zm܇m6r#BUH '6S-ҤMXo5Wb9!5E@ƗB_\MbdM: FRr$DN qfO$j:&&fo٦i윤k(,m'yuVZ^nX;} -$}.cIF_{efFjh偬AƗ}B?o+J5l*6IS|rڲ.'%HH '#D@ۥ~jKj6jin2KTCԚ+0(=հ"V,.t폎FHXd-ڽwP,6*GbB$5x3@Fw:T۷IkNQ4#ZMMy]0^w)ʏ&&jmۀ@ 됀CP-QtJfD&0DY#tT[[#d_`t75=!D [(Y|f(~>e  !HDPlӨLrYS"cq#>H r8`(1t$c)|/%keH5m#0UXɘ{8?qA  殆2;d-QbF>r K]&t%&L%)",Fyx(.J0ȸ=<4v 5EGM PeܳGQ=c4eUY!̦GOxF+ X,J,b{(4e()#O/1a KOj2DCTS *Gs"}(c<.>V dF7J4e! H7Zyȶ'(b9QZEԪ*%"Č'Q*ـd%#QgXj\\">YeYQƌq[YR#|gKYAJ>y'b{R5F4%}n.S=fItB`").k󔒚Teb\s WfH$9IM!\Ȍ* /7CaS q~4s:t(j]g!Zf& igFcn1hڍqxgGMZǛ^qc#|`j6٫ ymVw23pk;>X|;fRhEUi>S*/|l Ih $ ~ܓˏԝ'D!GUb3\IP}^4ȏ7 M~nD?KᲦ&ɈΏTxyW\Yȝi{$yT)_t6 2]g˱"S55UA9$YU5] D9 tKPWexKD b 0 Va ߱*NJ`߷}NPDZиiM\6a fNt9aY!af& NI^Pe gASM` ]`P$b$hBtMniI^(b&6&1FEb[=| P)bq]"6P-!Q"/" =V0 [ 1:ݫEF3nfM}ν6~p % FEZibc.b"H@j&lngT"bR"ɚ'R`b @PE|4h3AgehFuf/JgwmaC\TXK^+Ng5S ]e"fr窹wDma9S iȽ~ v^Jtʨ%1r>e2H-(D,Mh3#mxs=^%^X'%!P6)%k(i(R#vjFs s FՁ +BSib iyYV̥I #haiƚۡF1j>*\HzgKT6zcoN۠"xUY:'RB(#)j -1h)蕼߬Ҫ *ڪ`x6(&~Kifv#~EiY5 *ڕf"h~NK `VbB(UPbɒ4b&B+Ei~fj% Dl:,lfa ha!y2+I)q+`FQĹJ֬r:&^:H# *^g֏NR^Rk2n_؊+- m+1Ȯ*!T )RNmb? tn6>F'2j5f^-m-ş?im~ߊnsꀢnśL4hPybR-$骠To3+bo%>٦a.:Bo5*2*VnL].o /,.nZ%\J>k 0oɵ򲆵.$n ^c\ &+\m[Ko (g)-u~'Vn0̒ *p2pc/;qS~w?o #o*q,qJQ ."2 q 1Np +q(Vqz!l hZY L/ *#qc2i0, .''S1 f-g *q++7$hȲ&.kŪ2?fuVm01%6122_֩C32DK%66<s&3k?3B35:GB;2q6s4S$!@p  t$gm7;Hfς9('[4:C0 JCՁrBt-f L{4-YNNmEI۩r0Q Lrz`SS{KuDU$3V y5$'5%RыTuYoYO3[]tQ?Am[ \*5\vbt3va'g/)'lbg26kDcW ]7ovsN$fWfo6!8"vIθ6vCCuBmmK0#+g-o'Z3>أy?jHdY$0w.V@v2%k %vrRc5lw`y/8yu-ĭ$N;˷ҷRK~*.1xyk03Kjw28z;xHj焅g)^ 7uet;*48z[B3>9wOtRnx-9?xi8K G LK~vt3l[l8ӯxz/x+?xPy987B t'7=hC0Xv 9mBH,7n*H/w[̾z:DƐlLO`w}9_x?y4ixyo#ܳ;y{m&;{wz$N%]'󷃣:G;2r9zQpķxy10am:Hت{;vW |;W7z3v;7*z';W<+g<:{, ( w-;=r«j>|lWTξPz-}-G.{j+ҧD|"7nrG8軿3AgDG0/vhv2=L8@g($K;B7ejj>7O;i[ϯS<{YC@6_>%CnYs! 1JnO~?i8}?ɔ?Mk JN /0מ:6T:k =yD(>i2xO6t))VB1f$ȱb)#?4yeJ+YtR"+3i֔YJ"" Z)qgȝCj6 J$I*v4kVfEcP?R!G\KHe\qU˶H{e"βBE6ZaH6u U*ՉkzlɄ`)C\ɴ7}[կv;+P*nU۸+f1QNnW7ghm]S^~zhjUر=Ɋb홷eݷG6yr+3:/&BMЬ>jh<,"nRAv6Mαȹ 2t*2E;.Y,B06x,$zrOHFʍ= i( [ M;Vlh/Ffd$ m|B3+i(0T&08ᢔ,LJP<-]|K7 9#"k4+M"?T8D0 'A|8>A*@܌4Ce'0574'&טXKddX.#š:ē=sVʮ+\ťִU׻.jV:Bvc]^~W2*^>sw= T E+F+hdk6.&ݚڵ-M$DL>!9YBm|AY1'Q3ng%Xe<Ӹe6XZiIJ>M&@i65OU]uͦgLŵ\eL淓v{TSڐ e>+orPu+7]$]xɏ E]<u@YbR l^xM0}qq/uY%-%prO /Y-ZN$p1Tdd+ǀ-c%$2W&*m}M\9T])nWbҎ]:^`I ! /DcihrUq~QLlΜ?p=,bC,1I9gt m !K_[D~u%Ҩ9/1~b>5yb<GT28_g9B k\L @Lͬb#U~VS|_YcJ"dLcf *[-c4ӊ7 G:DRKP(vY[#U3˜f+@4_7@ N8Gj1$$J@ %{{xn Y_~&1l4Nml8u6DX+EUj%!Ehˏt7Gr2ccsawސ~[-Mzk>IbW.G*GjkX$#cb'hK;UmA~bWJ.ppc'4OEh37!%ߜ\"3j$D ն'[kk7X=E?}&Qwo\.kƻZ]^^q[>R;7>U'o XuuAWJz[MMĥ%^C''6 J/+G;ʫx(VIϩ67R,\i2cA^cq#_d>~(!O$2%<5C(D8ޮMq>sb6B֖k mQC76CT=@T7kbD; >!q#XA#'T$t6۴(0z~SLoH;HIeI ԜEtNc Fk4((P }(RL2?-0cTDs24EiN4OGMHQ/+4zNUp 36HaVR(:M0M5RHrI4OR"iAa-ĠJU[u7zZ55%(ralV0nR3[n5pPX{RXSuEY 1|!d[/LQHz\[(0[aõ`#)`UKZ(ەy ^43sSN^A^Es4rp Ôb5rawP\uQurbo'W" Ie0IYcAiHNM2PeE%| PWfCPge"bbU`FՒv1nc*w3dwbdKTKe5=r S!g3l#W/PWmhMr]S UqGfr-V&C2cU?Vd6YM1Ik Urwm)Wx+6Wv:t֪n?-7Mv^+'wwa;qPfY`Ax?7sV}+Q;uS%Blj FjA{!WsB\i{m)ҵmp~ a7_2E4] z3GCWWXWU8 r=Whחm'yȂ)_jt~ oiDckҍY|Չ6Іo"nsioPcC~WE Z؉2-xl;a͵WTw MؐЌ+MƇ#x]QҎSw,[%C/yuL6UX֓S>t.Q痒8.t!1[2xS}1<$!Cd eB Nmܖ7==}o[CMɺ%">T~ 3^3 >C㛀KwS^WSI~c^gko[>m>?>{^q^  ꩀ 1> > b ԝ)! ,^ %`X&"J(Ņ \dc C/jRT˗0cʜI:8)䨧EZٳG档H2bT$F>{&MȏUHJi+$I`ÂmZ RFb:Z(۰Q]_utv޿,da@dv4@$(HĐ7j豳!c&DSɺ8Mv=훯aj&ۨS:"K_:ysf2D/SĩkLS/N,#ݱ_Ͼ=[U?]K˓pSUyA=dY`f1$ }ЄvBIPxgDLjh(X,8Ns S!}XP@%cR/裏:]U 3 t^TMGYv\?\w\>8UM1Y9R"`A NЃFh~$VJ矀*(*-YG=u#^ӏ}!#Q~@6vK) ֒j:TU1zW~ 1֓$}5Q $el&&ud MX14RI'Ɇu۵!mk)!힊RǣAEܤ?Zzl)HՒFjjyꕁSQiw\4c 81+;Dl KDr`8au:,(ںl(&fQՒ(fj 3D)Eo_5eO7U Upl۲JYQhiKnad`1e|Ѳ}l"&a1lᆖ͸(S v*3KؒST|Ycs&~ Q("6pAS=U_uSz[7 mhn-Wbܫf#keJs&/yIWcm;ąhmheRǞȝh.Am{U`M WH!$I4VdZzbVa-=[P֗- X ;[LnH"0FA jx 2 *bU/mf%4aOXר5) ԠF%C|a^aL %~\E$"!,xmfH*.y@x9o (Ǹ:֑zW(C+0>9lRE13^<$y`fM,X "if:dg<4[F-0L(Urr Ԛ9̗Rw76<99k$i&’䜦͆3 I -akht8OdpR!/#%?4]>{3)p)RԇÀ)P 3(B 6 (h$QaD8BR:2&4C7^֨l N~Ѳ*spxpkWkπ&2?YPt -QJX)!&(EcԨz!elGQčb7tChqs"C 'ZrG̘zWFә nB1:]xlbkZVVE5)1KFʳ; \Ӯ+=mJì^5sŝ]%@{6:T6SeAA9Iu9B9%}&8b_,v'rS`[S5]XWۼk| R56d"pFRr5bZ o+B. L=L.[cJP0R>ֲR9m!⮕6X6')ӷ6Yh\2g!Rɀ)c19⒟^ʳ4ʈp_&!ٴiF/3)LsC*m}7qsGH摉t PH aȻF^3,=Foy [ txG>iPOmjG氟 l -ӭ0F?n3`۳NN`~v5~a?R;a #[ ޗuqZ 0T{wr⒜z[l}O؊6"؋\xoTlkF4g- 'ݓslXDGC|;wLATol+Idmp!+BψqBu-PW͵R㣁9z`"b\"F4WuLlK\Ġ(v`iۙ3[(H܂a`Z|ٲS`{1!<+ {s7/Bc8مxzF~ 'KJL[ * *z}wW\ɖZ<[ @bSʺ!Ʊ~1!S&PCPĉǺ˻8< O|̇L|Y<͠Eڼ*Lg ^=1gu!.">HЗh'^XRȽ-<4^ˆn<;4>钎JaNKNP^=;\ `!~G$p;,`;l7 JB1~NNJ(}̶@}ǩˋ~耭㣾=4@n6}ޛ= ޞ7.^>ٚp %p~KoMξӧ- BK-]n~0.]cnkXk:lB/._~11srZ /`$=Lߥ&?|HȝHb؞covxH>~+NK~7Ϲ: CF[ߥ r UOVj!;@`n}v?I.oĝ/⼜vF0y?~?BW$|;;lϒ.神O~$ N{730_F D  ,LA XѢ80G*Y C;>K?n̼ 9u?i% èQ!I$RQQgzĂnݚałVi6`۶(~K", yK;V_ P?`cGanK4ʕke.V۶sXׄN}=B%X\i;죳F>.JQLm:Nkh=t002<3A#;/=Ha g8Zι:> /`JPa$1J;O.JOa/e F,^ wi'0`8Q2 ¹6e$xC. @t1v BLMTS@0iCMm 9vEd$"OԸeEnts#xǎyc G>Ps3V3tJlI5 Se_[,f &bif=;ES$ҀpXz: -DwFV٪j:꠰-ЊInk]ۅ.RMna+F(4~%݃J:WjO]J&SAx7WN{c(a¹DY}"o,`n0Z\[Z`R#W:"i:f$T-AR": !9,ԀL[-D p[)Kh2͵(Hb@,si~ٗ狻>3kAϛ2A%@!! :Y$KBcR&TBGB,DG0EB0,1C(C3K0&T$@>`])ȍC$Iɜɐn|C䋄GN\I\NLOȽÓ|ΓNLl84LNO)-/R M#%9P0>C4F4MScd\*T EIP̒IH-Q_}NmQCQE%OIQ RQAIL6ɋ|лs,US=/C,R.%Պ1bL6}6ƉTF9L5LIL9=LgS@SI 8QMTl$CSG]Tp WTlQ! Нt\O&W{UTvlդW՞.%Z%TL{lLV4Tfu´NS-@ISHYMX(R,nuTdؙWQUTMRYQo\lF0TS5MuZ{O*u&#؂5XYU0S?KXn\2(R@@FXOIB}VѕYEٕRYL=ɜ]TõўYZ}-ZMLDbPQLګ5X5] 3U\M]Ȥp@=ŻR۵%VULh%\[S(ORݼO[[m-\ϓoJMWt H]Wԍ-ZLFS-_U?X=X=X-EPc0N Y]Ol%^S0eXh5^M֏ۙPu^v`M\LTvY^IlfD>>W2ރ(=( Z ]1TBNÄ=']lU^N`]L$KOc5V 6ބ>j c<c|YJxBp#FS5TF>0:j 4pAXg=[e[X`M46QMa-G/LRV/,lCfmQh=aqI[Kj@@2,>`douCC0>7^pW`mLpfTM$^6``H8?PWn<@b6B @FR<=%p~b.iDRq%aa g ]}oVZ&ٓML8ξÎE@xi)Hbk7>\#`f#f%Q16^S``KaF`qGq厃.DhOkvTI7=(nqCn>Hh^pn8L`&Ou VQrW\-^t.)x^TL`S7\ՙa=8^:6QHYt^?8v;w;HqDGS;buqVa4RȽ4T`LwLl]=>\sUiRoTWuNZWoxWo<6r@ Ѐ\VޚN4;SbFgh`6kaiDD qW8<ry; G;/fsG@umvqMq}J7RF4q܎q}g6w$V7][dv7t5^?{]GȃG@o_jxYo{jGdy"Hʓ;n Md)S#޼zbKC*,jTĈرN1W\lJQpbN\cƐ!:2rIpQJ&=jh[$ۋ-qJcI"(M~TkWR2M(i.m)SRE+W[I5Z{qtG]$u~|x'$X#pb'DcQEN$q"I.UidaFifH"$Xʡ4pHIsqEG=7BQJP(YNd_v$w4&Yx^zij^RH!}a٩$B&IW+I~'EW)3S(`HzaWhQAŧ'xH\]irBFINBpMdEge%m&9d[n=WB D#OIy~yWffzn.u$NE5}We=IwKR- `!RVfQC `C ېWѪeW z.I xQE JX-BGk5?[ / -ݚ.GA%UeI-N>S 4mS(\"X"q-\5ؐ #B`JPw8 BG(ŝF@&s*ѯG nڪ&`g إ\SH{H=Uwri ]m͵N^7{I"aQ\QEQ`"@C )%?pwQ Z2+^$FUJd5 aH8L+{`:[JЃ!Pb%bGĠ}&{)b ۩ 6PA8 DhF4"rQ} wpA̯VW]5exbAI^s@I)RԸ+|#ǯPP?I3A(䝝֕(L)+㶀Tpit$^WoEd(UP1E b@4 |5p"%pf K\Zѥ:j(y@0YiZ &n823uk) NBkI=J"HDBlM2J &Aķ7 q@"9|O" d!Bƣl%"dv 1kJi= +*f<A1MmК{;,<&+C5¥p2&@s@B,41%pJ"XeeOJx էr*V*lu SA:1$R &%IDI,$]S_3R*8&Ԙ&gjd)4i_z-LjJRI'45"–ђiE5Ϭ,0c뮷bV f$(@۵( ^LFsIkia HΜ):kN2RY(Pj'V݇UjZ kiҫa5sQZŤ\eZ5(4ؼ4!L u be*H"Qi_HoWΤTJQ,-[<]*8SiX," h7s^w&&N(W # N/9&aDg2O ǤqJ*Ǘoe\v{e%/Zp伞(yva2%+"O݄KHV/oŒ,% ¶ 5 p-}P뀡Ӄp&apU!FJrGC<8S:yףI5L_Iqj:/_?ĉ-u"-XΡ!N;ElqSU+M`z#GGNӓ}jj6J.s>Γvt Rx9Y̴VU%AAZѡX84+h'| ;jlpw(G%8ԝR񢠮RntqnwWSB_h8ׅ%5_eHEC/TJrEIn1_-CWuёQ=VO^ԡnSWq,7q׿ET?]x\+B}7;~:{C6liMl[!^E^wۋR3Ƙ/)[!S㭉%z\]]]A UQh ^a\L}G)|u_ٙ u kL]ZD8ID>QMթ"\z8 ea Q<] ] IaP`GbYYr z}\^x ]p|H T~F_RE_߈92^9 U% i 5a`iN HFp ]ץ 0%t}J\NuE|>\8Yb11zPM8`R!H$Tirx a*"Р|1lEBӑ Ӽ^ ^/[ i"1Qc-^ /YY`4b ^iD 6rA-%|AԸZ|y0zh=Ei "&cePT1E2fM\a%RPnpM&CF VmPB%B%L%F7blPN2 P΍l!DSDi$[O"J]P@"'LO"O*NxT# JC*%D6WCLcVt8^HNE+!zӌ%D`Zeh4j4Fc%%4 %xnz4!#2I__JR`ZB.PPcR.e4 Q8BGbU~KHgw~$DoD D$&cV@2yPVnfo2""g1'c {$g&sG pERdjXF˜m'w"}i^ 0=J4Kc[誰J'`] n\6F#QD(a.gbBE u^(eYHEA|)v{ Zfip暜b"%$Li ' '`hF)GFAR4)'&8dA`@ Q n$widduۚ剂Y:m([KEʤԥIvG*"*25ZFB߄"GGݻptjB__, JjE')`1>jV̊=[|h^E_d#%LŁ u-k<:+PhDF_Y+x-V1UWfhùPBN)JJh˯,>bfi1%*+,,"mP+܅ -YHFV)액iӾ&PJ[**#*l,06 f LYl-^PFӸ2SNQ]@&.Lڭj.Ml#*u]cu4 `*]>6\zHQnnåN|]9a3 8ƙ1sb3}Q0h%2e/5^BcDvZ}rOsNR/: FMIB65%0FSL_0|M61C扎*os")D,ɣ]\άV$6JH7(U@U3^ yTBkNv|@#.,`-$tj]PF 6PaPtV/6!nkdKeߴʅKF/=$mbbN3!&Օ5v6mG&N2BڎnO|4uK؄QiKVpLXhm&rQms UuCk?nPDq7/.vxvmwM^ n6v#͘EN5yh֬Mcשڂ2q%[pɢ1j1fA4SrkTtUw",w8zo&d LӇD7BN@0؋nwwpOGs52Yeanx.9[w$l %nC| ;vy'z!0?sG69u V8G+*cO5}9O5єD7YQhQ sS~Y^'5$7zwO3&y;3z 0l\:ylRzܬj#Mmcmoӑö'µC[%;cZ 0Ϣq5)i?HnJK?vI;y <-;osoG zꂙI˸hWoGYJ^np TG`;rdMj79В59o'5 ƫ-j+vrw_r=5.;~W<0rG±k%e6F+a+ӻG?.D[}lH}7j)~!C{ðm&W/7#}Ӎ~Z-G{L9W?yă+4m2uR ?}bC5Mbƌ+N %1bP#I4yd‘X2&3ejyB5r8)RH61ti҇oF"VzG&ulX^5{Ekd Ե@ M" 2ϴtaag*YjD yQģ{5(VիrZYg3m{[Mqڽ`_^jxZGn"ScivNӹɅ3nQs3Ոj ~٢6p Sĩ0;)$Űʴk&졅LAPQA8D5BTT1 Sж q!z/@:h(˜."l| eh-*7k+>*5=`/FJl GxϷ KI4 I !dKkM$0QTL3B3FCk4FmhnHT4VSʇըO$5EK'*R "+z LouftDTS_-)k#R[%g&jMl4_BKMz5K/MVr5s_GKZ+P%)ȁ ^u@ȓK$J )9{-f%vXro,z|)i k-1yI2^)&Dm,X/1AM(D!Mve~UWe1zjgF$K65/.w,xiui^U8%e[Yk~tl0u7(4f!D.[su[kPk pss\)L<\Nu}ݳ>xwG=SHl q]Nm }"}}{b[y򝟨m"sMT} p$9H"y]~7 igejֈ`V-UЂ۪286oH Ov) ¾0 NwC ӗT 3 d"I4?98؊w1riŋ``BK4# EqoNJęPwB$򈽒&Tɋ4j#"զ"p1YQFV:n1@^շMns3d@CA6cFZfy$eyi4 wi^Rciba"9W&X9IDrd'ihi"!-|QbL_]қYSs1ͤdKUSkGW桯k Iش~zTW8IބR,Aa)ϝ0!*M]ΓhDH`zdBFOQ "Nu4[ #Tk՚JܠEIe}<5Ԗ iUZٚS )R:0gʭn;E֟pl˯ضRӨhd2ɥN=^a- * ?k )]'b[QU$!t3"H9 oLŬ:L DSTs`QEⰯ؊\Z=W۵.?Tl|n,үQLTX^]rrk[*v]Qs4JyBWf@\% s[_ iE̝G#gkn2zn`fV9g|#B VG$Sx})9h'q]KU&D˞$/ْO6q=8u%eTe{EP\LY1\2y{]R~-ZgY )f0sJ׹|҈|gL_KD(ՇU?+&A٥qr̬dIv֮& !j>2WSlw3bdGhT7$)v$$эNUJ{dX-Gl{vxUT8\c3]iV_0vCj ^ :'-/~5 7bM7qw :pYT+W.k /30=zδQ芔_f}+=H?wȽ!ס$DˮB>ޣs>{ŽdDugkni2:oD<:bxgSkQg709b4ͽZeo6S{%4 ?~/dY=r/i'ߟYmuieC.r=ֻߘ0- &k$MD#&E"$ moCoknݚDL fDk$pl|%7oYp$F0oΆkhj!J J/F\ϟccn%-gfeϝЬկx:R0In^ȸ#a"L3aq.vQw { dz81 odM`&p JaR)&i2 MR!C#r"űj^###!я2q-LZ+[O`^q q,!Q'l̂'l$(qO")3( t"D#-$~$+-B+`",ǒ,˒2 anR3or RpRq."r(+21M#II0)EB@n!^~`.1rLg=4E(24gp4I3oO*IދVpyF$ L+q+b7RHvC2$,,r9SS"'- !~`1: B& S#U.[*㌎k> B ɳM>EiLF]?o?l&r@#0#&c;#RQ"TB}%DzE'*b0thC3G0vu"3" 0 G)EFfT"e5; GFxTP2NHvC*sjЛT$EvhO <ʐOθpB4t%xt HNM܅Ԙp.)PQhZ8K5S0()&&#Hn" "X)7S*TTu"ZT 1N5WQ!U_5VYZnupR+,/$U]-ڴUN6³Y4uOFOB[yd?*y\wNPL3!XSR+]ݵsSq9s7mCHHdeU.'`gOHLab)vbRy c*Bc}A7Dd>SUYPBQa6QK:'5X 7fPag}@a-YuD6lx-Z פSy8 96 #(!S\A߄qkYG69FXJW]OZڙY!sڅڻ:ZpqյI!w:'.әM+ut!HgVw7Q yך:٢іp(A Z', '~j=ܾ:iF~^K'Q_ϾaW[7>A!=ʹ3IjE[>9&*?U'{A;XS{{=|]E>aGJb0!X c21D*ZbO?eH*ȑ#A< $T,[tY̓*EZ$#R/1X3PR3MvXQ!Ŝ:} 5jVT[I0֭\zպ#$"= @ AP>7RGW˽-k);I8bFZl0lFW+[FX*fz aDdўem)-J876 /=o* T&O9m{d-zR*ʙS]:`@+B0+WV~',M8S,)\?E>_mvNFhI$Hb>4^?I) H`^RHH#%b:ҖKDd{`BYWQH%h\ye,!G^zIPV0`rəIX_M,'&$t}J(Z$eUVUe>E Qh50#&n!'(oJ΄b&`C:L:(ogM9thE  ?$NDKm"NnmM| ?KnK.֮n o/ދoo:EN Y9mO@@! ,^Ȓ%F1`hĊ-b±1$ RPq˗& R2$FKijϟ@dDԑQ*=TӧPIJM=XuN`;6P>sC9p ݻx붯_8KeCÈ S]\1ǁI&JC=,  X# V(CKF}DvG-^aҡD$hAcqƉÈgKINOzËgeS&*>?u^O(eFI⠃i0@B/SFSF ADm [E(opm44@܍-QN>uu(>WyrIş Rf_>9 \S\"Y!hY$y|6!i`0 E QH.,'܋rPcDFW='Ԥ4yg% ƧौTR%X9 _VFhk qDgEEA-h, ҳ*ԚN}XT&=q+t*ne-ަ]**7fq%@F:5!K1@P1P(ǒ%ŤR;'s˜ZjQ;/,nYpHvg GHܱ,-] {m6T%d̯@ @ tE[wLIM3 / B Q]n'gl+geg%FM d޹-L-8E]`\p:[:;}~U(Dhxb\LRN-)gY<%PE*KcDu[7Ui*#H'<0EEÝ~c>υv"6dEQ$((A6πWHIHRRddS҂A4Jz`#A7ʈjl=52b<9kl!Qn<#3e"ȊdP6D?,YZNr:ɹ,1 ;"9W)lgV A R߱+E.tMhBIA DJQo LB5vv4)>>Sn )IulfS u@f3 4ͩNӞ:Mo'8b=k #NኪjibE-9K4yWZůVtd-%rs|k\̍n)IYtRT~ivAC)b:v]MΤuLM+T2tE,X(PڔA򢬍l =v,jMfeuH 9ru&XG0䕯tsMF(DV X)ckS>6u,M0>}{6[LJB03NUf\A>oګӲjloiV2U/p; \Ӹt]L%itW,b796@.V6PiX3jҔ,'[ ?)*8L# Ƥe_'YlB1`p*gCZLо,^ՇG ^豍d政AZL44Lij}sStK=i:Ȅh]g26aW?27ی_bΚRy͈W{UJM""r'R1L%҆@MLηN}:9e9 inkھ}9\2g~D&JP<-fPH&d$'YmэGdf-Q;/2cRrh. n] G:z,`A3Ee^iKzCf+kbot"ԫ亭NjOPH=w3dNs}KyJu"X}w!Xq ~K7ezV1>zaWvlbbo'JqMyd-00XD\1^G؅]xrx He3Ta"观–p5h8E3Muׇa}\`7QFWrLbg'-0# |jS~7T腠7XaFOEv[kDS[advǃ5@H!p7{obt!08?GpX:v~w^|x9TtTHe>yoW7sG_q!1gG& !2,bҥ I+&X4rhX`ĉHTk`Sk@ch0N&=vgu&g(gs!JpчW9! y7uҐ&u PRYh;h I#Ghpyrw⨅<&ׁ:S/wkIzotVjxN=Y÷YXq!p0YHyw@LMU) h=CI'_9PiNvxo&nÇc?VfyƗo EU4ĥ[xIS؍#X#m;GiBI9K,#є)79b4?-phaPn暅|yxMo$&ȂTqFNfhIyFIqhy9ISMHvoН,OٌF M#g(bMcBp?uypzSF[N FIWyTzVJzaHpa2k#>RQԡe i"$g{(*vS4!CafTqxEBMenUQ eOKNd%aS*hL~HazɣHcJr♦jjDx4!P0IFuNI}6P~ L % F$JH&>gl՛vʥ]JBȩ!+uBgNRS7C+ eueY1z kxI[y5JOKZVPoG33~Z v#rHilRPd.'i?0Faק\ u z><hpwv3vTyWUC7*A<ծj Oa6y4keENџ+pLQK7øv뱖{)Zy0R YPFscIs~u5Z`V_M~dZ$.0BA0>c hf ˈ\*m`B7[=jkZ* , *)׺@cB[u;@dF 6yP*J'GKҫ{h|?g;Y%|WV:J0)0(~.e8ʃ y3rczc3۱,W7vB`RX<{ʏ/kRGpo"#0 !0",w CM 3/0+UpX4]\]Lq",7 rF /ӎuŐMsOݲXm="`/+wœP8VMŤlhΣAFpe3`ci`:P )-mWF G/`P9]d4CɎLS:I-b=/ O0ͩ]Jf]ұ%q6+fp-bFÔa>3]RB0{gZ;q-ԚlU̜ݚ]f|ե*l#Md=fҚ~dNᧆ &nX!ѤMiNSޞkrF$Wdp/%~HC '2'&F$ Rͼ*BpR]9ghN\ݖ3,nّ\㭺mq{ƣ\zGq/и>%p问c۵Ӌ{,m2vSG^]}wij7lmnr%/~þ@ a O PW"@%@""0(vJ,L̹n@bӭ%.nލ2#4'l",,^/ /iXX oC %@4PN Lj +DT˼-r+.wahHݏ 2&.纎T _f0_%0"V)AыR|%p`n/̡o]0f陼 Mn dmCqUp3wHǯ~AHaĉ/V(qa\,rvQvrP# 4VYrUI&Ξ;eZܹqܽSau+WʼnŚ<UVbmLnKwwy&LvǏѯGnϧo!ZΞ?o=:uJT3xzfS'ۆt7 %2D#Nh3D:>{2{H.뽶"Q>fDKl:#o-$BJ`@oZp#K %,j` 7nMDNͩT|űD!켓>;\E?M@*tQj't:?S:c4ʑJrJI,r )LX4)`pְ6W^S\Hܓ ӊ,a\6fc=QlVX5Do}U+XKR LOOGBA$)uSwiWR]U1ɴЈ%6LSWDxKeF1RlŢaY^xcC~A@L0SVY[veb5bT4' FBMdԑ XJzWXok5֢fP y43<b3󺢭$ǒ4iaP⎛dNnUm^0;fQ^@*'IpT5hZ:._Xxa]V}FIc찕>Ҫ>tp)QJ^y 7\C>eb%݊N!p@:r-7WSCu/Z*1\BWN;} lF' =&7 :O5#1HB&SȃwBx(Htm%hZr$&WR>g+A*X+ @PG E'jZxE+(vB؟X;)Wⰸ+Q?`-H< Q B5Pw#X{+Sa!# /! P@@A pC94At' %Q:ʿ矐8Egde+]9.MA [8EԭɕIXX|K^2>2bA3LON}rXɩ SFh%XC@+\%>q4 7]nvC?l@teA zP+, 3#BQL&BD'b&+($4`9 X@Ur7q1۸1qm:}A `N4)M$'(JL|JA^|g6.TWCڕ~k +HJ%,L k*D9։?k\zW$EP xqɜi a M ޠ*'%&&R4qsL,RgQAcf@Յ l*Q,UJ|[5z^VnԸ%)3S=әubB4T,nwhN Y kOTRdΥi2+Rr WNжPmטQ-o;׊D\QN4$%1}Vͩ]a_aq?@M$z=.AsS"/~$Nqغt2S\ ˜2bYm;RSIXo\]W%OzE+nI*yH l`.#,a3 q`ugz>lINu_{iL@$g4 |aX aTZjU&&t8SԗJmkf`w-.\jì?axp_O%P0ObџU핹UoĐ A]LW1stDZ脴H+r |j0ͼ5aUNmV_vs ܵ2a L6\bnW6|ec݋UhѩDr}p Jkw&-b~ױt.@<:=LQe[GvFMvkT|]kIe]}hLo#i}l˱64旾*roh|`t{_l:ԡNkdzկclתW*gkZ턩gygRhP%*ȧ@5xZΧy{sO8bN{3Oa݊|v<0(/n#]?/;5;m)ikʶ9#0'B>;ΪC) rӸC:ڭp:+@]S;.c86?<~A :s 3k ˳*F> Ý)+ L#|CT[iK?A. ?C2*˨|˵5;!lDk!̭sU:'8@ fBwk> $CT ;sD`'+<7;ĉ꿴DH$Fb˿_­y:+OE0k$!xUPUr "+,ZDQs)+K@t9+*FcGd:b_RH ӓF!Fë;۫O#1M! zs\u\iGx>3AYGydžt3&Er: |J7ȃt:K@C\lc?dKDLD3\=:dJ4+^`[+B¹:#EK@T\TIk/'IʙJ"Q#J( Zʁʨ̃<ç5ӿhԺD캭$4įDY:B6|Sܫ0{;QELK2K/RLd[ȼi 4J͔?NL|?ӭ-3JDc3TO5M<tDǛ2Q3G{!ìHR-+-STOEݐ%SeUʘAC\Xځ΃Da+_k:@ 9fIJE߲(ƍ-[hTǚ{E (X.hU.bCH\$Hz Ze:U`U.(?,2x:@)`?m? XZ%łP'å 9E  I çZZ_L7 0 H̔Nٍ]DQmϜu^)^A`ū+TG@_..1+Z$WdJtJ'b@+DH5Gb/Ec1.2.c34F'^c6nc)'~ݩ`ͤZCc/6@`]:^i`B̘|e `EDZy9< 5 RE)C2<ӧh}Z@̌]/eZ.\e[._eGp1fb.fY6fdf=87c/cF\Q5jL5dFG״X( gTZud`.dYMЀ@_V$C_G8F`4H%;FGLHhhhhfIhe1c^f@Hi1aa&p"XQå Ht7eL(jbCxcFiY>hL MVk~뵎kJ뼦>klle/^;X(ߨ4 ]u:=ӁD (sjNgg֩ aM)A̍?&?hhkIk_efk_iN븆khnnքj6k6R8&nn.o6lJLLPnv=?4HWV%H4\.j@Us.gFg_ݡD갚#Xמ e:7fcn~ bnnVJnhVkmV~q6kJenjqqooJcvbub}4/g pp>jȐ51\z(ؽb O02j4fiZ?lk^fWn'.o=S(tn6EGb'&H4N46mv'"9|E#urrNAhuGYXu^Vnaxx쭎V`AOߞkjhovoOtrywwwuvyqBws/lwxXz>lr+%H Ƃ %x)0xG6Yxh匞7{I8G{WjW'you{SjS{y/o/G|r '7lx|ww~/j?m]Q7[@_WhO{O_}_(T񖏅V`/VX{7lO@N~vq~v>tlEJtu1jyYx艆}}nVRXeU Kjh0 |U)R7r쨱<Ը$ʄ%9JB' ETRJ=urW7R# 5vEҵ$$j^J2I GzmTHTIP+zAUJ4+fL+&i'G`*TgȖgHlmJ-Pa&%z$DQ\,A8 meF!W+Ҋ{UJ:}$$H 0fI*Yے$G}gxҴl"! n=vh[󡄑K. %4 \Oe];Lj} 2/Š-֊d7'_2Ɣ`=)_hqqp|Zۙr,7Iz0A XHL!Jkg& )!WQA(DTv/+8h%vg'$PCݺmuѰP?6m= \=7q|Xjƛ xЀ4~ 4`iP1dDkb~([``.-l27Q*0*\ fÛ|]ؿtMx $pq:"0_5#Ӹ`d1#=iJ5L#P]8A#N2ryg3jO& Yc.3ȿ,b#nP-hdx3 Mg,ڙG[8)U$p*nG nvC:钞7GZoWyq|D0߀4O)gcp8HD#6Ԏ_=h+vM[I4?#Іp"*! bH <1ꮯz_~eQ#1\i ?`; P3F @ PbT`$ !Q @>)aI/ a m}oSa|K ݈0~9062v!3ޢ$DX4d,\9"Հ ]"VHAOQ5#FAxbb(~_#>A؀%%"Z)OE  d^3p!ꙡ/Gf/.1^HPD$Ac5-N˸pT@ cPXAQ<"%>& Y_E֍LR[ƥդbf & 1F:tn$2d0ۤ!fJbf -LLDp! E|HP%N=F)~ [3.$CjЩ|z\r#0lsJtsBg)`Rf6DJJXP# ALQG`+9hJՇTChC'}^XYfjSPF&L)ˀ A%iޠ% =cD]ƑvQ%t.WYD Su2P!LP 5(&-WGW)Ox hNb)aiC֐*.vi_" -؍߄HY&]RL i^U>Wu$9"*iy*"p)XHHĴdŎ!JT[}XHbńp~ΜUʼ ?(\"5課e6ᘆGHijѤMj]$uKpڴRO^X+Xq|$ݓqb*n_1^:Ҽ^6'ir&'irnQ@ 0,OߚոrAk.ilԘbav+ߟV[Vpo"%{ 1 j1&SF32 DB>ܙ)BbBd}-Fd3% a,a_m'kd 2ҫˊU$/heP6cFVyyIjN/lf^8hn򐻒Uš]+b ,Y$ osy˜//&/ IR^L9oF.Rz/Ml) 冯kJjȪ_ܮ 0Ũn, W$~i2]{'pPPd.J>a)c T& ʝppz0'F`K,o&aM)PFLRLZXE+0 %p0] .vPI?Econo)%`up{pMh,GBз^#v)L>'/.!$qNUؖ p#pr" rF:a kOV2E\3m|o'_'Oq.Ć`-ݺx^KFXKThFjqz {B.0# In;0$K3Q3~gz1~ZY݆*l=I0ILҳIS>X0@tAFaM Қ`~D'ks%{FMK_|uTՌ3}p"B¢4W=0?G^t@1/FvJXTX\̆/Rt[{n%ZD^&c\Jܯ=4 t^YL 3ߴvbB\d v[P4)D]qV,jVS3M#vb.6V[dsI+)BfC]Y47.27pI6?9܎HH䵺lWA nSipX^(~w$f,Msʙ2Kqs /*-X`vhhw!"xAH5+/Z3zFWhu򶊫8a:lN෌G~dS/w~ﶰZ?/If3P6z6dh8B]'Yχk7-Sm ̲,AZaƌy1.Ό!qd%8*.4"xG)5Y39G?IiK0"`9c+5)5{7k ::xt7ʸ0򷥩j*p 90":3;zDδpcVZ8dc-*, *SVw$HSR+%ܨ⎘1͌/z6ntF-3 DOcX\J+Lrt{Y"jU>_ y27ܴ_4i1ggiX{?Nk ʹy RTS\R* 2<]l<^$-ݺ;f217zcοh];ޜǶ./}{{9`ykV{FʍLO֋@vrr=l׮˵+ *(XhBna(2s1fh{#Q}4[. k}Ƀ+-NOOھ-Jj'1͓~YutGZT(RSծC?U'G/G3r Da"&irO%Bx#&5mq#DL/J4iTʔ+vc34p%x)i"Cѣ'ORGCԐGSF⨩ɓ$v &c7*ʔ)ؚ%۶sjkWD%EKB aJ3.1BBN<2D1^$УAIJ.aҜ Π %z{S>G"Q,㪿P@S8܈z*$L;2s>[DMdd 54>*0Ci|>-%22#%[F+EeӠ<J|S0-$2+d;CLj0<3RTZUϖ4-@]QFULju!v*JRLWM##U# >1 c ò]ǒVCӌ[H/94MǕUOmpZQPi1l>;<ڐwMw0*\=O tIsw RMV"}a+!;]͐ZD~7bu6\5!];ΙdtK,rYjl̈5Q0QSNYj3m]ډD4 R':A %;{N1M}c5ZRv{$OJpibkUfu$ZM$Q͓לy&g_\Qyҡ_ېJx)oH/OQg+F7fȔ4zsj02)KIم v3&4$)j>a,}r`w)~[̔UNQ'CzD'9 \ySL*H>4` 3j ؟~$wB+N lx]Z |2F[iX19O1"H rxGdE#yfR$mpA()1xf1*F:sFFus*wG'o0EȪe ZNC$&IH2S㸠sMlj$] ] FdEqQ\L(c>ԈG.]`.7w|dg0^ +1ς>EsT3[g"><~fC Ɖ^e ] >DRҋ-EH$y!iN;U$P*$"lqj-uʊIMK\RSkQhABFEra4*=bD5qe@#S_'x;jr"1>NEh[(vKad|OFvEyiKUdR! *8 IH 'L #T?eVO JwP O"ƂtN S bRbH#-"Q_ q !L#8PbތD[%|VZb񟴏!3r,t1y<&_$C '1G,Pg])!R$R'bh%'*mp+Xr$Ȅ$|!m-3/"*e@£@+&H46R@r, EӢ.w_"H/\"*H_P/XfK%V%}"WE.r#V*L6oac.\M,G ),P!5oPE60Opr8R1]!V01P"TS$36s65] Ε֧Y,p6"84!,M=S=?1:C@=ﳃ') $){@S3A=h6=B?2/B=B0,51 2?rDyvD7_Hd/P-o ZFo<[垃O,vAG %/HB/ u0GR$J@J(S:99TOpJHͰ;6H/%&-P%P6Nq óotrOBOq/8TCTg&spִ֒ʼ0$o(MGR#5N]oNatKPj<,S3BoDuTI5PM4 xk0UeM"NE@Nó `EXG5]$S^[u[uLK.R=!Pϕ-0_5:ʴ]U8!Jje4<_ԑk3io6,DNVZ5aNvbaE"z4m?xhj00RVB^DmO`IYTABAZNVj֙d[3*Ye]ʹWEKZVnV-Dbm4 $,"J#/BMTiּN6kpVe3O3k3V5f-ּʶhMo$p:g!(ljϢn9>!, A9^ }~~ S04Km4Xeu#i6.'pu"SN.kaU%7%7~mZErxׄ3*L(h;1(X@qE(]<#CĖTlf-)2{S.gJT#a8c6O=OQ q8E/xUʤiv7gfaAԋ2ÐiOݸilemQ/#&g+⤸D;%SYxf&sЀM,-f9Xs˩Lk7]S'-{7DyFHّ*Yn&Kt8oaƍfϝ/IŒOq?Ӣu?9-eE3\Uya5Vd!ک>.a)Z! sD4eZM쒉&XQd ٔYFr]h#ӦT6@ cGhTp_Pz(3ںUYMOTْ![h lЬ i11]oṮ%U$FG:5uHkzKQ}'bl`ĎyR:3`^3%7{9"I41oϭ1#3ag`A^nJ6,t{.s:-;-?ZSkZ/v{+7ek\º9{#_SۛUS$N /k^j;\[%g$ۿjb\t : |41p㍃S6:|Ň$,FfG2%G?[٤e gWe-v|\9[fvȉv$Bo!1J=J"@&ʯ<ܡ \m}6?zZxɺVZSs7"…Wǖ-^Gx|#K$I\#O\H ep~=yԎ3*}3x٢i?.H[;]R_RWZ˥q}̷31} ȁ><Αl7 ,}澿;KHaKӁs-IgMGL]Nu$EᶛUU^zbZX}ن` 6xN`a!)yu`s7Y"FWaOr!M҉XVVLkO\wc(ѫrĐBsANK)oR˰™s)Z{ %'3(Ѹψ&|6/Mp*Q,0 r X%pt 4Ԑj A UJۜOT+%8m-Ia!\bđ^]EOړ`f`ybJ-D#KOyV\`D8PagYmx/d&&7q?D`qt+׉̯i IEzd@R1E,\,a _|Ư DAo:Gȳ,24cazDƨG"t)Mf{ ?>& ,YFqŊhϓ[ ЉЮD WS ,l'1fԄJ6uFlZss4%INq{_IE6) :)A{:ZMo LaNqOZl0\{RÉఢmFQBhHGt1Q^ *i*hˌgFjӸS; (3N'qXDԷ*O%la-ԌbSnAάbjd)Xq5Lۚ֊ԮI\O;׶|i gL4LV(ĆҰL%R"]U#UyKAXk=eʑUMjE*UdTNbLms"a b1 UWkcY 5J+KGgt tѷֶs|ֽ) ,b`U^1BFJdq]D8 6y_S4u6/akV88 -^\6e.9^["̂X [}sHEԗWꉊA8,u˩=^ъLxfS D,e=zAPR3r |lW{0TҪC_yG6WJ!@@ x3V6&G{9b ISqq^R Wpzٜ.)"2ЀU@! M:#PeɑX>k { [ڸB5̮F#A;d( "pH"PSoU9C[qm6]7Z#NL#-Fu{\_ߠ%hXc!Oq馠* =S̖QΕQoqȓ *wλҦW <@ |$ tw'}|{ބ߻+~񂯻G<+k~?&/5w;a N@ 4! ,^ D 8(\8$FM@E%ȱ Wdx:TqK.W/8sYӦϟ@{JT͜>H*]tӠDtJ(=Xb㧫W@`ÊćVhӪZNѢ=}nՠ@UӆmU8s >d-(@12`cF3fYag9J zk&8jܒ5D Tn9E*Z9RУGgLj\p<0=ľ!?&O(l0dmv }_m xkv+F^݆wPm!$h$"^;(ngpAQbAF@4lIAIуE niZjk1xayuzfoo(tIP4d'k> bGWطCFBB8lPbZYkm$N 6aW?*aC ʣhw߱!,uU\Wy~ lҦiiFRDif^;دՅ\k' hw r(q$,e LЁlغJ>mIY~*lKAx)b(2/a,4 G<@\$0RF 8ؒPvKf[Ew=4a=*2#v%F@^ /+|m4L7]R{DuZ Jq`PnF砇nnLޝ}:E/lGBb vGK;_t9{zH>0I&< '|zק~>w}oo>|tgxfe^[ q:yʫ@oL Α.sPAxX i32Ʃ.&fHB\e;<2׃{HQ|><&-} c C;@kkJNy6ص,HX4͇ \bg!уrL3G%|NtD5gpr1`kI 51u' O.gCGQzR=$ŗ6>*LVD@L%clZ*fV@ $1fZp48%4GPxe NIh #AzOsʒ,hle*y2Uq*rL]\&IR'HE?=4!A)i67ii{^(A wsdAzO=P aP}^C*=_Y ֒z'Aw:J u"2{seCTWP*br]57*J"Hz#L[5MsMzi(IJ.mi$Qy4 ZOCԥҔP}*L?٫Qt4e;U͓`hl:.Y$(IM9IP+]GZR\R"J00,y΃^Smts*>[>dDJJgΡ.E+(v!l)vv8 cl[+V%34\ $DfR-^iT8jH=m>xUx[C:U4̇L"Hw)Xt,N2Pәmu`g.%:Btd"D[) Pkf׺\3f1SAnp(@ G6ȱ@c(O9ʱ@rj< 泶0e)fʷhӁt(v`nfl(E&ᇲ vl+Jfo7Hi:F4s?*:Mf}EU7@X\a% O'r#,BKl,/^xW8+< |D,NzfbCѐjɖ;lb9 Ovmw%S[2p#ڌ^N~?Cv?)*;<{=Hw~ cEfrs6g5TTd^>G'mgu^s]Zv&Y0U||MsEw56Q0}g}$`lu@~oAVxV~ydOi]{BdTe hZ[p7idF 9\M.q?PBpxFJLFcc&X< LNŢohsgܾ^dV *q`wnڪzxkm|A7zuSwoi SpZP$Ni4H\V|h,eܖRDZeEBx+ |뻴wB/F|nz0u9 Bu@AiioI&͑EKkd0ǯL?Xq V!:RqeCxg %F8YbA]26XȎE8oLjj=߶JXMPIyѴs8@ ‚vg,v5^o >D> F}S?SQ|iػn>Ӈ(a=M<;R^ٙ^_xyB0|-!wl3S'nA~sdžgX6~xE^}j5<͙/0 æ{pg2{oN.vȖ;뽎g~qFoB^o>oϞ^MGi Mg\ X^^^ n0 W^qq%P6%PhFm^׷^`w{g%nbX1j5<6~atn,0_ДI2\аP 0$>We;})ߞ{Gmo.nR?T^ ov/.䇍^v`@.CL {i @;0\ꊄ^Wl\0yRpC=/4-\^,C(xW_f-_/>cg~>u>O쨯- oE­UX`x"` 7]DIWAy0ioon_H@~] h{ uX`w4$PKLp!A BaEʒ%G$N4bdH 0@c?Z aɓ%T@҂-H9uLϝ;%JQ=I.etQFuZuQYԁt*U=ZV+֫AivjYqfs ,$ۗ0~T(aĉ+"ıc0Zȸcl,#ɒ-O Q4J 04ad Z{EZVOnUgiltOe:ѵoǚ^~ XаAei -B$ˇ__/9r8# 0%J@aH{A֤ HBr7r+J'z)+k.,Ү묛.E4ΐ" Si=%%#IRR Rɂ H" k -L,pj)N:ʑ 9庺S.iBӷԠ3.~+H H$דAILԔ),E:ˎ,pUV[4Ϥ4;\V8ԕ<{WfFLУŠPdNԐV[ .H#Ji(L7OK`]0]B uGCaA,T*zU n.tUxVW8N`l6b8XxZ\C5CK6փնn%JU"IsUvoyKmIMcIx1~L!{i6xjÐ7G)3ܕᰳMWlOuj%\e"K;R\#UjN-]Źݝ33Z*o4(톣ai1  V݌C/žsfe4|Wam{Ձo_u !,`["KnIK=sxu;wyR2E|kvY5tM ec7X!HUc2$wjy|Ap3We0ғJe`Ox4@U h8{1w=1NhPO4g1[°6VH'0&+>A s@*[v jdS5<npأ#H`aBZraʉhddhH!|S_ @&X$䞘ʙ1 #4jqsR{ @,0ǣ{3<i4'hZƥ6rlmc[/JoQs^@p*Ӂ}Euq;3xbZW!_q7M)B.na *'LBU0u{iP/Kufc~->lY(/`4k^2,R63fX{G ]i{ȊB?IhK>As d.r }1^fii3 !DFqn}K&v?`$sa:.y3.O X 1}v ¤<$~җ㒠Ra NPB,5)C>Cҫ2կ~ 4@ni1 uٺ%Ma(ȽR5X;x>;G4 0 A~7}t[Ҙv\ё$$OG*}[8eHǽx9h~Zk|׬dt=\r h_Zu'9p`jigr[ g5%_BA"ĺQ+;AX #5e379R4Ʃ3 X=ڿu G Fy|ǢUd)+BW\#0 D&]T@3C^,@"{:&}ˮ=*C&LH;i/h{yA|6"&1(;|xGR2C`0(|6㹌b*(#[286#]F4˫|JsJ/ E.?LH$Kt=w@RG7@1ӻ7X\=?Q;B,II{a'C#Lzɷ):I9+:#̰0ZA3G;˷,TJ}+H“:?ҔL4wk} ӮtL:y*L)ʟȫH0CTN%0qӯhKDzK4PH`Ϩ,]Ԯշs(Uj< #`}+%Џj`IQ: yO?Q$MDȢ(T*T%]R#\sR3"ϠԹ:I,K|JS0R[2I2]8X4P 1s̺`=>|́MެA-T 2m'HFadȅDKuO{ELcTvKx DW)BQSA-pU;>DDs|[]K{Ö(N)*&z₼RBImE$_4~VTL76=&.XnWͷ)<caX3MDnP/HV6jDTeQ4_+# qa}e΋KlCʂ<%jnaf @[[mh}۾m>I++k,[MA=5 &}i"%r-n%lnշX|OflvZefm.P-F)@3H텦I09 @>R @%m!B&(2?B]&l2qnU,h,J(悟䕃֙ner˖j8mrjr?$xZr&e`m-;,ζLBvEuIE*FnNttGDXuOwwtnvGwzEpAx@@(oK'l\E?GE0Vve*OKN#%hg2o[lf2cWѿ+-) <0ڣ#G|6xN?_?v@X@_zGPwxLxHzwz{nJzv@gxAw@Foq>2(Ծv~B ,"V/QZO_ڰr/]?u/kPGq/vg@ttwL(SR}PGzOG}wx'J~~J JO~_Lzg~GW~/{P?}`S}y`x|4=to`4o \ygR 7`a „H0l!ÇSpB&#I=Z(n,i$) %j%LgҬi&MLvI&&JJ+ejtiӨRRJϬZn:u( }$c6M/_|1H7hb )zⅈ/`v&.s+r1uDDrA7k.c,.mDfG% Cbh3RH5RFHj/jُp73*jWo;b2ejk])2Ϟ% lR:5ėQBef%,ij[Ey\L5edMљh$TIoH&Io/JR$ XMu]c&WҔEV]wJ:Q#k{GZwF9K) i. L hf jV J)%Xj\N'* TBhb[<UOdTCdE:\ ݥfuލR QW:}ч{ԁ^&9BK9vPdJW&7-V+$":o_;T1h#OjTKivS:/Fj5)B+ruJU;Q%Qye|ZA_^,`v6D~HRQƑDդ2XoPBn)lwkAڕW $:E0VjIyW~&D/ŀ̸MGDe/Mm6R+䱒N2)t ;7xy-jMZ]JJUlŴPJ!i%uY-ѩ,Ld|6q4Ip~;ˋJ,3+n^% Co^J"oY~$)Mf[ik"%`쳬2p'-jn/X og3^,  [߂IT 6ASEn;J5ʙIhbt[ *u..\M@f hO0 dxD>N+ $<5\v>렽RuB(Ue&1 !!`04b'&kX82*1B.0(@`+TdqP)4)PfR SFT:/dUf.IX!ߌ%G$g#QǸULHb84ȴ$2 H0@ vQV!>(rZ+׸R6IM9?@Sgu򘧝dg)>OsMH4*O#Ao訦'F.ol[9nn.լ˹氆P2! LXY72kN}9t+R<8<>n :FYć yT\Zق glL _tپ m~ -]LfwEU\Hu\5Az3@nΆqnqQ-nSlH:x nQ.I}ь[IPuvS⸃ ߺןK]6-&vyנr#,wIO0BA~w)`! z"XH1DKnm?=Ƈbhv˵B`e(,,, ˁ^%DBK "^AL F rIE) =99qm)tXZnlߌ 1-`teΓ XR9qB$ '`. B! "A Y  ir۷uaF/U #HFZQXޜ%ڽQJ[I)^̝ \tBXB< /]\X)Ip$waIN"FiGg2|'"DA KF#fz)N PQ7ƪ @d@ A!AKl^$\, F9ZuZI)RෂE\KPݤJ\Rؤ#BDaF@afjhB$̪~,4}i*@@\@@ ;&2Ǎ=DdEl#$]阉4G rܨZ+Ң|}"EGh\RjIMS5i"%'  X|wSV\"L)DA#Dd6^l7iA&Al )mƄn6W:EZ>U^AOub!Dme J**y.tHmuLS+py`Aٞ'Ӏm,k&eO4밚jAxPe L!]pjp`\DlȑKB"n4jϸ"*onȫzH*W萀fN W0&PA.oD 0 |v,^ @ @ p h.̚n=Vt GGWF$]yC1āCԢtd3"Z/x10s'kxJy_b#{,c0fjGX X\W.mDGTVqg?OhH3i==￾'  aBM2Tbć )R\(R"tG2"ETJ+YJeJV^Ҵy͖.y ʛDIGK-ZԐ!@|Tӧ,lI@L%2Dj;kٶu6- FkwRI#^$O N( 7X2F?v0%Q$3e" (P'Ozu L8O欹j?BI =X<1bɓ)S\5T+CM5w 'EJ[Vݺwmq,&k*x`*Tc73pGj A|Lb dm6v 3 *9H .*꼄cɹ砃ιҤ:-9 <!sF CLk*(#l<A2sPJ9&5h+E4PSLK2c@(# 9jzq%Mb|tg !dHH$<(UUUX,@Ð<32}uIT͗f3'4α&*9CAS? @ZzRrTP-e0L2xjk^Md;!UOI;7!E U!r7jm \uc_{d/P>a+di" 4J 3Zi @v kQcM s6gi9-}wȳKL "IX =V(^Hn$ČNq/L>^NeAEPv[_Z?5YmMp&֔Egs"ѪuGMi 뫑1ֲ8{AjW9fmcJ餹 0?˂eٸ}MÁVwROtzlr0缻3EzvPkMu^J5K+*>wCC.0CXnJpK~cbU޹zf#g~ձb>zY|Cw>EIU:^̓La]cBcQYJCì0Zśˈ<iOad5$(azƒKBSOmjN:/YcU$tZRCI%]&$)HL O7"J6A(cmc(V1`ͨ3vTaWB"Oanl./B CZ]h8k9æR·$E jS}MƐ(CC-=PYܝEVg6f̧* 65Qb0%/|mǗ[_1'+Ry=6j^sN :,j(&V=qO2TH4D~JZÈإ, Uh 8vNiWe5tt Njb1b0QkNl *JNqғS)M U7I4Y6UYm!=&Ov+E5],lW, rGH'CLG($Ԣ52AJ[|Yҧ'`0# P-Hvm\[=LK1y;Y c$` `\<¤ "| L/9.s"F;1W4MG|[\U!X1ҩlu}+m߻d|KRO@ ܟ2!JմSB J7m]PO(Xy02ДUއUҼvŷ΍gs5ls[JKcϢrͮo0taH7!yGp}gpaz& G^@Sa߶&|EQ/*Xx ЌePxn=Csvvꕶ)΃C Qx{_}6l-_E79u2_d.=g&9F0цt~xSe~7Hl*%c|$5yHV|GEn6$ HâLR +/`R gqhj.r^MrBSgpm. j0 8ϚpDbm /Oi01T cZaưrEᢄ(+:  aQ,#!f fKp 19bq~rG<'qp^0s@Be}-D烇n>{a PQ"z &2jzZk)F;R>G^@񻬑c,nzзO c1 1T k! CqgL-qgHGLd!鐸d"G@"m%#m` 5z"#.TRR(L\ ֜h^ΦK-$2YMra- 2g> ޣ)_RQ%R߱zй ,WR2"g(hHXnp| $w.C4LUD( ` 2k3@11=2K$'+"%]VI%c;\:*|N,'WixS5"62V!.C.x Ƞ@̂I# s$lgR01: K2JᖄG(6$ҳ =(3 ||@? 0=0 @%4¨)j##ԟ..e)bZE H!R u<<&!dF7&GpGs*hA"9?/ڠsB w !?rDcgK!!"FF a34Rٔ@԰ؑa.M 4+{JT)ru=  uNN ܊4,8UG?52%t$qSiN6 2KtbpfldUyVmL jj K oMmM5I%l$3CrZSq얫5-315VmUfo^I $Q`.`O^._GABgz1Y{1:\q9abktڮ "]G@FC=!JOMq393Cz2APET[-` KõU5?N< 'b*e gvB%j7BskWl.s2!U\wMqdzbALEȴ %W$:6vjjp_Q).%q+#͈,b rXl即7ap1I#jk%'W=n {.Ld j6o2`pukהuw 0V4/Ǖї};>f}ĖJKaST]re ~~_ظ"_p8veՉaY,w3taY} f~m߇{d@׹p__0e'PJi򏁒axb?K98S_ʲW fWw͝JL"v7CrكaK2N+oyrSu|cv /ɖcy}cq!aNj*z;:-RW~VP..Z+bd:D)zuzd6C:wI `z j [{?W')UW.K}{O90oj.J/Q;h) 4|ĭv{9?[_j=+UF1KKs9;I"GK: kwں#'ӻYh^3~(E.Dž\ bTQ ao;f|MbN؏]rrFi 8{+Y5S9c, =iQ!|օeׂ"fk{R899<ª{G8*jȍ;a<'VDKʟV^xh7h+9!<~9p5YRᑠsuوgٻ+OC h{F9XtڟoeP3Otʼ} 9+UPk%$=F;1)ӵ<~{ԇ4 (5yķȽ6`lx,3ҧ\7Oχ}Q埯Q,[|oӪS #rKJ=ڗٙԼ!xO}{} „yFwU9>x1;]Su㋭D-7,Q何Ozu]M;s+`2?g|-5$.Z0Kd>ٜPNO\s 94^^E{20lqg]I e&J[t^= #cNIVp멚\BgI9<8RNZ%Q^ cAX{_|'oߚvC{r%u9>i$ SI焱0R}WW<]_a+bvyI[=点ve]FgtN>_=yYI~gt;T?4… :|єĉ~҄FM7b !đ$Khr Ä [lTR/kxɓΝ 1.(.gI4fťLY9eeԧZy #xHJIO)G"ݎTe7Ai70O;w>OW2}LRUդWbn u-0hE $j N݌Z> X%?m֮[@ч]Txr/e;|I M>zvњ'O瞥nZvZw _$_mՓzHwCYpueif4ub"؝\5ރ!7!xvӋ~g_{PXM)u(LQIoGxUgMbh-b-ԓGјWb*d{[1]_1Z Imx'NEYUiE)VT6\g`ԛuٚ>8JdbD *c !Ɋ]AW(IG$UX|8e@tBJ&ڬZ68fr&cK%Av:dh&>.cA.C @;QBbʢ*ᦜePU-L)$vTpl)BxV媮e|o" )I`TʧI˖",pgKBM ְ^"6S}kyXTyF5!37T$tF;eer0s;[Q/p"ʋu֔InyRˡH Ίx? ;ót$;c$>&[XT?.9ȋyf mԦ:FQ)T D"NH0ha^x<0nCY)E$@^Zc>@n(U|ZU#=ӑP S;ad, ; ':8<ȊXDFJ$<~nSNx4ƂkZuǨx2Bh: bhP$g8]Ab(umSlYˑd;r #q2;fv3+Nr*l[0 T~H!FQjӌj4D">7Q(>Ʋr dbn )Kȵ\s )RG7{aʔ!)>(d zE&♦[hC j>"E'ŠfTgGo8s>AIG2v,?aEB嗅<ղVՠ{k(jԙ4t(2uP5c(}nD](XhOd^A~H-ʜiKPA͜}k_W~K%lbcMp~TGZaDmggS"WG:YEv^QSP4PK\ acS&u :&7),r wa+s$n( LNF  _4cf3~X ⯱hRZs q/(K_f g5ki7TR8H)@E*nES፹(By8s?Slܳ&Iϵ`r_i@۞F|oPݝjE(?b!|R`BЄDp6UrgV{ZL -kƬ >e˕nb%h#89/8SQEv|TxQh$U^V,ILR_r5d)ךXCNY쪉ȡCķCK {zΔyڠ:g̞}~nbxHyRH&b )i:]7줘ލql䭾=DnVP/+?#IV0BkGш1pA/E|)I k"985wܹuˬY" QUp9ڭMgzIW:IDawbحkxt4%2}6Iq:ct}7U>T-WIckLd7G~>XCd~8'Lc|Rqt28FG8c*z>8qMvhGA ExEhWߴa1GIo6y/Q|ކNuC\ ^R-+}jzmn6wtf1[g~d?eUƁPPThPA,5u0/`vGcgGLr@>kgL׆Gb]6%)򆩗l`xy3A 6'V~P7!nCZ$Jbwt8:|&CWZ"L#PDbnazSkCgs{ӄ NBrKv8{1q(S̈n(w_S8wwņ)[T7]k^Qv8[}F(sՏ)yX<vI_s,#~siTTT?@)2d a `D8?Վ(TAyRyg?iØH- Ќ~HZ2FH70dr)pxiEi}908SIsN_p#?*)]Y(h05dqSz&wЗF^^v(GVJJg.@9مi ɷ9Wd6SZd_;GxIdBؗyWy3tY`aD|^ɕɟ7qh C7P@/@p>m /ُ2Bq Պ鉛PvAvW%Z4h1jG@r@ET?#`^CF2U9p'Ƙ;':WyP2עU#P%P?w}–sG:g£FtoJzUBTWQkâX/k;a`/ ϋ%&d,=iKtk-nM׺m^+;%6L2>^ī6lt  ES{5TfqA;r1>Ҷؐ¬ad<-`̡s|t/ml8>k4߼߱6sW8MVyL]u Adz h=鬷j$nÍ*8rz}}#3[.hONKVQGeBjf`-M묧;מ,i ó \Nd׵`[a@9 [jχ@90r  ,+#'t:J' tan#پF$d@ #݆k QK^ X6*3!"OCc`*DQ+$'@PR`$6HHb?;̷V #,,g/p&+ {gd43GJ86Kzs'A)J$P,y ز| 9K}j\#o65ެ, IhfMB b~ 惾IInZfD7:LS6`RT#@ CP';Ʋ3HelVV+n'(43Ԧ:5%BFdfBѨSX1zҲ~4)p$'=xL_kຯkFkI&T V̚MVV[iWXȒղ$YE Lʚvju j1TskMa&nD :`5-2]ac(S}lEYɨ⹇8)F3>a&&GUs!LGu2UO[[Zͮ]rW||#UXCfTINpQaÃ{U^70Dk*5+s]EL'Fp0ˇD,,+ԅ ~a_y_ 6⪩%aF%&;FxA2Q>D! g8N뇽rU1'\Y)OirlE/v){k_L$%1@2uAʇ%M(ys9oY˟3ibͧ.Jd&ǻ9Ϻʪ"$d|=o~'x&ڧ̹|ic1ȟtaα3RtԹisNqqT7>SB\ّ]p0*ًٳXp3LJ<٩vUG{аSjb! x "(ҡB8pٱYFĶQ&뭪HY6E(2:{Y5^g@ۆ zx ʬMx|T|YHcawDe+g{uSVF~ɶS{h$KBKrQX~;HK(ٳ*Z*ʧ i1w!h;VJ[۽yny DufфN zF2yu`țYlK)j[$ WiP § pe5' ݻka4i豌S3LPg$:Pp)g,Z$%!| K~V{4a^ij%uE&zAi'J̐zSS )cV\%"#(`CP&7))#꘣:jkǫ̽% nk_ "뛉i[\H!^! cp6 '(zFAɍqX6̤-={ Ű)ˬz|L:ot{<(\.yȊٌQT l?E80"\lYj"JhFȰif{g~VlЀQQUJ&&3jz-iSIY$" %M(mƛj86Myڵbx‘ Vwj'}8] z!@ܞ Jt Heu^{yd 0 p\_4 kRpJDzʬ쓊{InA ˫?]gyegYj̸ Q@=ӿXɩּTڂ&ۣ]yMH^jw0'gVMw Ir;ݳx"bU+V}6[R *SgDhGBd"BPwؼǁ=lr .yxNsܨa: xW=$c$BBڎ 4J,$ [ܼΛC~|T Sr`nxVЭ>ױy7vSdOhT欮t4ɌyM̩K^|`—m}g^@~ngJ>hx BQNqN#,[hOѫnRXJ9Jl=?>iV|f칖X 6X{`qN$?ef_;pn q7㯭Xn}'^.xgj oh땨MmUQlbS^pPT>NG.,ٷJzP,gowÙwgoހ(.zTM7)w]Iy J/MpOOS_oݚ';ut_TRpaߤct`_al߆x{?xj4_tX1o^O``LFNdN;ު 0?oqT_` ;.ap~XɌiaw5Ňh1rTPA.dXpC ElhQl丱 P0d)Sc1H$Y.eH 4pjZ`(@|spAz!0B.H 9ʹ ׺N.ijkĽ2C:oF /ìBrH>ҠH+&laV2%*]`@JpK 0B @ "S ZB)0`D+1/馣3*1GB/K/QHtRJ%c1(BaTRK!&W$Z%!1\߶tj`V^CW^5(*, dL1 uhX6:濳hw%f1a,Ec%\mfC&ɥiCN yW:Snr+E{6fa!CKgps}"v/F{KDB.N۱\@( RpHaaa<6OmRÆ $@d h5 Xp$V.b\ԕ:.QHX`$0*bC8cly HZ6$'  NN(G:"C(2M DHlbD$MmE!Kb9Na FEL~ YU~\ςzo@10L.SS|OX7XSd,S5ͳ0,'ɕE}{mI%\;9a0X0z Ȩw~4gUTU6ԡ}(iZLh 81*bk)E(ZdHސYt@i#uJN9׊\pB.ES_-ah[)@LmjNU?l4 ǁUWw½~u4kVVvf"Yz\5}zJ4@+nuܕQU+=5/U@hipZԮV MhQ@sJ1p$xpY!Y@Uel+e I\Vw:qnLhrTlWbFVZH[ә4Oh/TiWh ?erO{hKt `CSի+@(Afrj4dAB-1ED9IDz$WT+ ~?,"?GȃB*%< ;# qTG x|u;̣=+ ?ȳdĭt{HJ߄{ΝE4˵t\KNK˻9t'PkΉOLb?3? AcBW1ɥFHC q;tS+OcVB#:<"»VN` J\ΊPY4K8}H::uN=9P4ˌNt?:S*HWu)NwNOymJW{ N|h%XWN4r9;X=&РR):4P /j/0W}͐/mVd1QXV2X L|UPW|W8NO}U'XW.XWMw}׸OH| Nڮ=}UN XKڇ֘Z- !Λ9U35:\Y/ܥ 4?\ /'َd5L˽\%QS<<\YY٢ܣ=ڭHs3mWwHZI oX4L[L؞J}Ȝ3[HX[f4(ƕLOc\-Ѹȍ4ɝ\ RgE[܎VE9JWyVȄ^VHwŝ/]ߕT^-ثm%Nhrt <-uP^ M퍹`\1Y/\X2uTt %Z<^-`M^}ҕS)S_+`]$ֹQ$NU$ьޕ`Lr}X,]mDuGl_ÖܘY   VGAuE_1<ޣN`Youݵ_uKWiW*Өݸάec,nݬT~HLMUL3V5~b3:cEsUW9=JTs[06åd5ӎ#4-`qFD~zLaAŬ `]$QJG}ҝPD(0AQ~e]:sW|zMcyeUN`bfcgU^vl-i[pHauq>d1-,Ę^ gy1͵EMTZ%s-h h0:exEw]WxMf'(ׯTQ3eMf2hīdZNH]TfOãP^@^tCB|&x\lNEcLdd95ªniZ'HPKMD`ng-+kvkWM~c^W%``5|a%d?Lpf웆`CY ElclìNN%k.m 2o2`LFNKlOjZ,׸ߨkN9vT >ڀOmUQ^)~Q Hncf;^dŶkznbU  ʶye]oLVjGe9%T" &FL{3Fc]kԾbN] p,6al09s݁IU@S{^Zv#~qr.FRJXASzFj6rL?ڛm>F2R2P`v{ڵQK$brZZNVbe%V7w_NVQ-X ؁xAs\g&M,iqgclOwJέ(Fg ,ى,6@wS݅=wFs\?s6-76leLx>Tp}x7A3uWaY/:toLx1w,&w7rn(9wK^u6l~Mksh'&-胞S:xgnHVGMD$syWOشiуhc%blp0Yf*8Dq #!Dqj" $:d\uKWb^yZAzRȞdziyHKsK Hs^ JhX2jB@诿:V / {d#F&-H?VW'Adt5GW2AK|첉-!HPBuMz2s.])|;DP Gjh(F@ v#߉}Ap80qT۱\ϕW"k>|챇n3DDF4G|lsn,rɨG,K]Pi4N[DVA: ʐ<)4u$š%)&B}s6Ob$lBw/Aq l"]A$bqsǤ4%n '}H f=EHD"=(BJP=h& JPJd"hhCZDIьh&0!RJDRs"e)q6%<=(iz}Yf>όi34A%D-JLe*Gh|rAP(VVjuP%+#Qihg?!ONb0#1BRWZ3 NE) VR{C"ըG{ ҷpE}Rf%KOSdbAK0p9+^1~IS)` 0@`f$OSTk<%k 4JB/) 6n$B ^ZTէE  .%{7%{eƞT_6` c~fFppA] jū= aTgQ,t~$VgƎnk L@qEc,ދ:Zaz ,Z]v5[ʌ-ŕ؈ڸ\ġ-`%*h',r3L,<' %'7+-CkndiH:nu cPKX}mej*=Q[ŵ[S46l[Wښ$ݠlH>:,)%E@rw<erڢM7D1ny#ܞNӣYX3^z~kO7g*b _o/kɛ7E,ba?_A7-:鷮|t +3\0hnCg^! @  U =)EbUY_B)`_=\e%_W* .)D !ܣ}(B),_B}%+dWYz``,8a=A]`*ġFfyuS u UP[iUWɔ ̽ ! LL"uP^%`f>Z)|'eSၡᥢb"Vaաab-ʢν1 !wUiRPI`٠$z [i72 uћH9~BB*J^'~Bv(J+R'b>#/6b.U X12!$Е1ݥi[ @<؞ ݥY"ݕ| I7c,(|By'c+d*#LjL{u_-TE8&A>b*"#&# BȁfMx5eSbBE%[e  Hd5zU %eU\e[* )Pan{IeRN䅂*NeOn}'|%eEQTSRaћ) &lJ$&Ee>VhZeHbI_%&1UAfV$Qbd)sZ=\<A^FDE)x]aeA*'O#+^e|B}%X%+Z.Z\-TEkgl&CK@$q$8^n%N~B:_u+V&@:R/_P' XGrA[e)BMTmBYxemY/{'3|NB{g^$\%lh"eD^'(LpzK(0*(©LAğ6WL&R$}tz%\:6jd'x5'x=tڥשߍ*Yd#&-g{OfB-*ggzfEz"nNBB!"(5K&*P<DЂ6K"Pdhc'Қ8ZTSB kRRhZz6*t_<D§kE=!xnJY~+”'g-BJ^(!܁d.+WjVKD&>%,-EW=&o-&'&$zWbҫ_+rTh?*ec&z"Ċٞڪڪ)X&-'pB$%mܢmJZm"%!ȁd4vAʦB$k2V&}g!,@dQ6*,*-jkEnukHZ%T^%#>r%4r&(,((&,/ڭ-'\% "zoPAdAհc!p2dCeCάJ(Z.um!>[EKoor-&xWm&%+>eTDB)@ޑ.QΚr'V$>(ȭ62o 7--,, p# v/ "XȆ.! Fh8fY/ٓVkBdZ.den궫1N^'xC0iRm_Tf`Wrڶ'z% 0j ",t'0"lATWT"l BSxL !$¿jYdZݗf.[n߅#Zhi>`ޛҍm1-:'q".I0{ l۱aI3:gj j's87ā9Or%W؁h2"4&BA(O "$$p:6^~Ųo") K&rw106t/GjaμQ>2FYq.te4>-Zfo/"s<(B wk78hp9A%GAVkVd3>'?5/˘@ibb阚&(&ݵUu5EMo7v7xƪ.ָ1g1@ [ ,KD }Cg&BU;AGj#(p 5$ @ d@\@ @߁kBEp(PeKQ16w}q;r9U:&xvz'/8{,!SW7)G€[4 B;v9sSxv yxМZ+s~1&g4'eY1NZzv7c5W:)f¹.hzֻ: T@\ĘށJ:s^$$KE86kG8:{'A܁=8ETnAxL;Zթd'*)ӗ~nIXD3˵Zw/G(O_`w;Sy%zۻ N<ʯt#< *<'<nh$$ $A$Ax%uy d $lVv>L *i2: dj/*;Kj VD_G[:ӷM/?-r;7hVhXS*k=}g6zENK#0@/;on\\PA' DcJdҕ?4]([~2'SM)P 6LH"Ć)j"ŋ7b SH#GN2%J94*pSLiJ2r=T)CI :)OI6E jTSS9$d֔s Z$Imڴ=zp[í$?|Ե˃G$NP!3OCRDdI4ItD#T th.}iYv0֬2x#šq d-a4&.:w l(&E=zԩPL(5?z4PPzC*!a7s+W̌s4K$I"9HL##+%&Y<(C)C)HCK|. CESIhFMht|(*64 #1i,3$nRN'9#$I~3*!)0ĻI CH|30(2Z1X4X t,2eDÛC9@q0VD A#EM$e4 _Ku"Vw4X7B"#PBCR%)X.8Kt.>[dIäQ깬@!S<32OG0L6 D2l.@(@,e33Ip$R0A„ 1DC#DOSޅo4TSOդt;r"'uȐ l%,I)lҴ986A.$ lfJ* 02o}Z^xӍb 9MD=EQE+. &>Ta@d۬[A"VcX8&͓Ec*7'3>cuIB窿 TI)z1Vۣ֕؋:L==@}w< [g$~sSzK C[k7ʾq5Mc OÙFiו=׽I c[b"r&j"Y[|`F5fu<]~b.DSH}+\>ka 37=]0xAdCԪ5&LJ8;I=XΡHeE '"L@H1maxR1inL#0%Xi02ʃx&l^#FJ: ހXtF[m|Dח8%VbA)3lxR+L_l R1s}ђAݏ |&4͞)_iJ<=#I=H$'K|gl?FlT:\+ EP* 2ԡ(y5N)t+bqRq`c&< uJq_N:Ozb מ'*e { ~t#Mk 2lPcY>ОfK{UcR S(`JՔM|` CMBu\x%nۻR>=hnqqA[hO0@ARlI5*Ej0)K3y[ Wɨ\iP"fLn`[4)=sLoHq8t-Rܑ!7K.& <:WutoI٧6v56r*I(T<td0o!/NhYz479‹$" r+)`5R3 "آ.)в.fK PR1 yVbDcH7Dx/z;"'0\U9$>pKZY5.d(A"eaiwaְ!xehCœ&A*_RUv6SDQv 2-TB-Dk;w.Z,$<`F—Vnߜ^eѻL!Leч<4M5v)7U{>T_6^ta~U&7_{[t/"K{D;A 2<] :PI#tax9tyzck\fwyՠ-Lǔ1uVz4h@:%z[ti٭w}.7Ğ+y%be{HgTz*xMO})OB.d fHJdtn\cF".Eb^ror0og$LhpRP oq ըڦ nSz1pꔂ#Pc4p"V+j|L(+Hk0~n|pa d DVmifŠ ?RXӆ*T GO6 ⰶd0baI{% |bz# ^&&kE)% pl"Q)ZH &gnc.dRq5PZCPG6` :ÿy>Q۸D qܞ1Yxw =eFB8=1HdBJbHdɀU8 =#b6z2"##y&m&M@1 UlL+  h s:vi)RM*)%2 n*x @ @ "m3HQU0$_ETF2$QWZ2^e2yyQ2VNΦPxOT\i#-cs R**2/32R+#DXi_ ۲-%ѿƦ$ʆH1Щ(bsܦ2/Jad&::堬ĊF h<=W4=S$T^"feʚ4P<4W (T(AC+GD0 $L ^c8OS/@ukYuS3^5ǖQ(=; rPu%IA'hiv(Y ȔDLVoY(sBФ:M 74XS&Jke=Ro2J8$M,%,Dz=v)t>d|D0-M눕+hӇӃ7or*6#R@Ww.xO# ·7Ey_8-whUc#OI,d~R`sQ X@zKȖ#VN6` >et%wu5Y||TW&*Ԇfx]0~!ZPQt[G.c 4#O\9xl{7А5cA&YXs@vr ZG9Pر:DML4H{M@ڨKٓW8NWoGD;$8o(Q$ š"e0AA~nzF۾Gڟ*J '9+ d\5`mnq"37Duq/ V>A姆1mT6Lō[?;;i72/ǃ|EN'ȗy0 6ʥA qO7Nstc ȓѓ8IN\s<眎Ο{#L:qϧM!_EG7o\ѨҊ)ڦ3M 9#A'rb]ݲE!J=re=z̜u(3H6'Wבܙ]٣]ǝR-| 1|%EW%rڄCdu+=d6OM}dԟ <7}ӹn3BU\$sZې7<G"+p1^kEӡ䧻gͱq07[ au:dC}-}'eE(\0UB}r^kC">YcP\E߲ʧ }~AGd-^KA#b怾K3|5~e$>#>u;m`= E³?VިwOӾ':$>5~oqP.\5hCY0Lڎ"/ a˨_W!AN?[5۳|q1iߤW7>”@S Ԅ #8t @+.mT3ndA$K$Ɍ,[bT!̙+_$i0Νw9РC?̉ҥIL')}0jTLM&Ą5VLRF-3TJF;9uaÇ"NxѥP!Cjyse^Xe%O_is/X=;&\g#92TWjfKMm[-i.Ȼx6+KyVңǍMM'Ox}.j2g䣓>~zB& Um ْt{.xqx N\NQfQ:y Taw^yw\L`:awTʸ/mO{b~# Ű<iT|*9K#߅+~2z]3K{+Xm]?#'aЊOumk)l}ذd:f [ a9;xWωtAKu{7˫6xV. AQAxe `ԳX:&)Zij>o(} tc I 0(]E 7A,zG2SA?-!lsJz`v[:W=qX(U᪗.uƘ0)7\A{ZtaxEw)NGN|FMqk5 4%my YHf;N<5䁱_Z] ޲7H6i gCS$&0ku \QA/Djh/N:%Rv򙯓$HFQZk3 9U bqXHLγ(6 j=))K*JM:B3[%2f7u51)LN+Kk DK.7|Hg=d3_ tQ3Mj4h9e(Q sg*Kʡ_T+R,|=RJqJUE,2iL:SUՔ}7hN GSe,n,iR:2(jSidҪZՋ+_F8f` 0+y Pb94FK _ŲJ]g-B `O6fd\Ca,@Q$ւCfQ)Xg5tBƀi#Rs 2 h+JI V([Nj[決,o}Ѩ~֎XTؽZ/*ku'V'(0(r/vW&{#xi?f)ϴ?k {&P K,a #ZgGss[Md)ΏHx·1/|w0;uZ ȧ)b1 +ǻu *-Do3eE3dayB*`%rA4c 3IHFBa|i6~O2PaۡX?A4aiwIvZS7x;O3Q>i(<թJv 7V8i#c[HRua=Α6,eSٽ;w5/H$ۤF9GWե%{(7SDaJH!V 4ԂfO]gT9cV;QwQ&Z=ԷόK/LG -E(Z/{y՛3yr$-5w~MX쭛:@w>a.6?XhZNE-vr&u9v (s/(ְ͍j>'QT2rlb\^EyWdQX/1M ǧsANٟeUb{" ku|Z'8w[>+%{/9pqe-~v/>'*RF|. 4ft1A\7ml6#t;V2t~a(vfH7!x*G*A&q_!O$m8fUlt_ y嗃>V jGfoTw׳DH.Wt5}ɲ,x)PFKBX%v}lTM`ghk9h`1*eILXw)%KXk2ZG=7Q->?cdU&%@` ~1oHyp(r@hw8h`{b e"V|x!+Bqs=s}Q$s#ӈ.V؁ׅ)^x prysZT QXm?&Bww}ӃwzC#xČh,%q 8 eۨ y`o&=og77ze"1VG' %2AR#Oc'8H^s@&gf rPePh(j`c"mQTm4EKU-_$.SR1BA_1E83a7ieh} })z@i`xH9L5FrRiUy8ȕh^d 2hThO`V&4qq{)9|IA鍩K`90Ըfi!1'w "ROt(vsvgNO9&Qg )HY|`XhW2`ٜSЩ|M8uN3jWT"bN^vy0~ }riؘqȊ8t@7Q/ҹz: C:=Mzk[wk6g$_X֡@ w~ Ͱ _ x `y`EAvsiYld'!VpuQEl Ne'UH%XWzZ b'X`G~z4Q9)zZHE&xhDUZs:٨%s` Ǻ*7F v#A$ ϷA%dD#%.I%aMzjsJ5@utX\ڌah) 9Ū"*)'ЬV72 Һ~F3W:B5r\XeOa a_ȫ"j$A/Kx]q&F:Dg띄tKQ5Xѱ@#IH jǚ vtz-ê+{'.0%fQW`E_Y o[;BpWaGZ!N$>hA4Qʴ 5+υLA ˣ﷾TFd"OxdDbe+wk+ZA컟lNၽ_uo$)%> k |e@K_ FO RTc)vLN 9*aw vJ)rd̯%RqK S dzHf'#@Tʧd[(&88^C^e,o5]K R7NKlgff2LIy1s- THLP^bf}eh l,ϙڽ}ˬp):k[τ;Dб ddPSI<`, u\,Vj{qY* Zɭ j ' J 1=6IsLSPImwaΦW:Ex 3^ +xh]*{f۵Zܞ ؅*kϰɅ'xNJOMm=IM -Mmҭ٭ٍ-M--M??@mM=]Nn = ! ,^Ê1p(ıaÅ#F4F(2Ǐ C~ɒBhe$H @ r朐310㧨ѣH*c4ӧPCUljʵWS:ҳh^3۷nD umկxoV~#B Qa.mG8d *jkc@r$1G䬳$SPRw-P%oXqv쭞lU{Θk,uEk d 8\0 < *J/E$cA.!]fă>rwn2W*kŦU<1bp+KQ7>nb5#tBB,o <ꩨVIdpOF.=&5q/g(f'z5[-qohw~Np 7=o,-sm6}x]yd#)6 = @Z }ƛ{H˪x 4`xx8' %86Ê5q]Lcٳ\֬ }&d[NΑe/  *4 rB\S!oy}҂ @?C 3eV )8=T!؆s&GMF6z v1Nr1`S84fnD; 7@8T% b㚧*vq3^WD+؈MXص1Z 0IL`QX/Ya.Zdd EȦ1Ys !yoHJXhʛY J0< &dɲ!vY@hgTC3L5 K MA P>擟%)Mkl[_>| ""x3x*>F:s(eK'9/I0r%ݸG/?Ц- dzF6 jU̫I|]V-i*D~M@`{lNN閸*=L=. ҧDOnժħV.UzspfB lz"a*,~eQle ֶmn[F@Xk[UZ]iz H&Љ_˥?Xssz1Y+4MtH]T(:d+ono[FLmlN0 ܵF tRڮItQT;SPu7w$oTߺŐ8"ؼͩqY&ųe0b:>2oudCk% OW5 p $T?̞t5F|fqYB#dWΙȳ3q>WJ- ɁhB;zȾe&P ,a2OwwI|_R39ͩWӳNiJyЌV2ykK Ўl/7`b<}P& YLm++:Lb+q9uNn?whbC! cS6q3`!N߶P+{<☮ -)m{z۫v8G){0c@3[D}<`"^C=8v'Ob*WT |\<S& 21PEt7dg}bWWwpu } hghYzVy0h~fWucDa{tb$Ѐ aS]FC yg}g"p(x|`L]ng.shWtHyQA8?8H]:L8%%\YhXhggu8@ xhf}L{L1p݇}e'8'so3؂,Ȋgx~Ng|,A*S:؄NX! GrasE%~ukgXgwf؂u@u0(ph~tz@{0 YuPzgH#x$؆(qgXvW;ԌDBeJx%BÄۈ XXE$[e% qӇiw}t(H(hL't@hqXzGɐOI} X Hyh~#m"$kaA{0$ n)yW|uh hjDc$0(0%dywSIpxAZ9O5iؖgtI I 'ؐi}ٛȆɐyytKA#(@rxb*)XW| HA̧CC8)EK@NIipyghZ9nYP*jϩ{pAHȚhA@  Kx~xX7pc!zn1:SJ5bW ڠSIind|isqxP%3JJw'+FP@gfzfZIlJk]r*X9]`ɋyWq*h@@  ࡙tiJx`y-zIOiz<ʎz8A9y@!%RHKWRx@i944;1|n'QSRCPijʦjxuZj s*rJPJy|ڮP {i`]يa`Et IgpB ( gOvX7j&xJjWZs⫿ڗ $~T.Ъ*z֪<{kRB+q:Rik1:| ڨ Yh:vjuP j?+iSDI:QZrH/aWg/ (4 x5 `zNe.l6 {=۳ZyRIc~犔dٺy` *@ ڧZIlKnzxgqjjfThpAsrv{q*$EWJ'%ćC۝"1X ۳ʭIʼ ʖLgSyC gY*~Y[vk9ʧ8:'Sky%[BWA f A黸4پ-{%\лȿʿD̿ٳ˼ YvņZckqcL2:2f"&CKzy܉4  5XT&i{P>ۭ\\E| mW ;ʼn[ܠ\V<'ɧwv1k\(~v#K(xǡ]0* lTgPo^Eɘ&UkYJ@v,cl A_̱RB˻@㹲0) !bx-[D ALzPL$gIPuP #C%Nw ͠"sOo/͠c<8L*dsǫQ*+{aŒ:E%ć[ъtPYbMΛqKd U ׯ|47 |Ɓ"Ӷ,%SR*Հ씸MJ |\5@F@ܠV=,XͰ[b]ڈ:Mw]MppggևJV^9ڴa V}dQ{;==?W)Ʉ1I}! ݬ]%*J(yl{|b-zUl #;׊Pcm㦭7T<~cƶ]IAݦ]A,PU~bf3:5ٍ؎ >^){%Ċ*/;-tM| ӿhM皺 ߅޼::^#Tk8PvΰLTNֱXg/uo*oꨞϸX2%-(#*,#YY {}zLYly+h`7Xq> 🟮{`ޥJfy ؅wC]ΠCMA ٳC:8!Ň~4*B>."II%dK-eΤY&L9ur'G~ mX:C*-ӧ hU 6l@WaX! 6`- ޺ 7n 6Hw߻sZ$@N.xĆ!^ܙF(A )z"U,I#D-4H2tݿgU^պCW͒5[ͻ7o5G&A„#)LDzf̝gР\- LMD=I>*.(?L ($.(qW?Hn @ԓdF$-Gj<"l!" &@ȍ,>C,`h?,0$@ZsA2>[' 9MHsO>Ktd7nЀūFDw˻ʫ Qk S#A)jrT'",S/UZc6)6,M?Qu@Z{epA*,V_IDGIfuPrKazvAÎ]wSjAa05XssbP1g>ƃFe{uzP8FcI.r%]iJғ(o/‰UIi8jɡEd fX+ C5>5 nT-&gyu_,@D{L"67=n0̭-SmUԱ.HfTwQd8k `X ?b_@ 6pōA\ J؂U ׼mw#?ڞ[J0[ՕNa#w[YU!j9вk/h)к@Nn20m 9a<xbG9`g jjЀ5TOzcg}=h yr7|{ϝxNɯLa1|<*~'*D6RފaO/qCd;^9;1;k@|@{ ;";"3:۾ @s([2C:1#> kA@;wr'@li !{3h6'1!?C5:#Ԁճsj{/ ;S'vbB0 @KA)@55l6;sA94CH#C59CS;7C>dTA Lc{a B{`ك?T f;B{6sYBиR+f!D1)W.9AA[B:E^ |=|93_4_\\3Dc<Eare9F 3i0>qCC#E\C*$ /ҧ&‹@GHz !8SGT'pX:'`o4s^d_42Fd=ԋHB\I{e'|lFniH_l>ȼ*(p/*x +fc"&lB(d);HJc8ۦTJt' tE,Kp2l G;H>,m{!Ib{cɿlJI'eeT)pVvڧb+bUYF/b_eI\NNܯc)lVu` >7 B I/,*R:d!Bs<(JT_u>`xe F?!_ٯ٨%,e-ZTh6KVV|D +~bwcae~n2Dc5]=6ex;n b3Hklγ kB?t1VJ`D]\WEy5\o_~v[N\f)S^e ⪶bbwja_Z&)ӐDHA[%Ԕv|?MHS4.6uOFW%]6uel>hߢ '^ZV6/2ئ~hFhm#j+Hx5ڍk$f& XE>UG'Sų" 8@qFJF%s"rP!d^Ȧhx `hdv 6ezUK~k?ߟ.M'fS,FԪoU %ЪmmnynW-iݳ8lHP[=%E%&aP-K)6rm/W]|.ZFT jz5mqMT W6%&hpFeo6]af[k^ZakIhm^ދtح:htHd\! o&.FeO+x+H(% eVfus w˷Sd Ь6ow/g:V[3뱿%<ylh"hpM t{]o8PUm^g8PO)W ^S7U))!.p1gv-({Ou,Fc{i>A^v?qq1Jj h3^0ݸyeCw-~$.&!2w>cYb/mjq)~Vv~2f*fPOhsދRVYeIBޫljvP|b?7jH'w_{Z5K쩞Q'-{->whf.z s'ohFZO\fՁ*NuUeg5- )Eɝ=70II{n_m͸Ywlx2H*9~2}oxxۮߡ.|_͇xɿr߄Pr}9rܨp T(qa⋌/Pp,AĆ F*W\2$!? dD ɁÇJU0mi RJǏI(Qd X*\rIcY7jC *Sҥ$ z7 DG:#!ΈNAoĂ C3zw+HFǎ98\C+FxC#J?,iГ6[69KdR%>p0A.]B,ڴkC̗_n]w hA@fU6ggMVYg~QfYg&jvZh֕NХ^]Lԅ0mFCY$o@ WHArDϡdui Kb Q긔xU> Saj\}a YtG}݇_~&QDĜ*1{=x@ bYޞ>FZg]Y$ij{!SQB,Cшp4LIʥTJ4Z29IJ2I”`%YdoUz'b{ejpJQvWF࡙Y.&(%;ioZB-(2jFO:$1%YG:P Q?T%!KEƆlf2[[S8qyq]5Wmn](#{e][)jPjo7J*N@LEt0]G*Bk'_Dwܹ2eR5X[18"Ebr}npTߚ;x5tԥWK/*-SNWMQu-f} q?CRQԱoxKu,lmOL2D~8%|OѱX_qq_] 8d44Ğ2, `5{ˈ<bKK* BD9,J6Xpk"lbӱc \dep Ca,XC} ()R1ΨTKZrK$%F;1Mu @0klL]6PJ,B=fP;k 4`BФ(J$G([ R"13) PQd1C6 a v(UX\Y25H#C$4覛NjJq<%lqLLt @NZ`|HX`e>vS)9YP PHN^l=}и4Q4MEb0p&-Ӑ9%(J+*|$b(mz44<%. ԡN`(R(S~aJ5#ȁ ۔v6 029N7"J {Fo>tC/ҽtX`መ,b;X2V\!ɦi>t.#5+Ka*Gb~$й(!3:MOT)uK"mF6@U֤0^]d>1!E7uYԠ. & N.:KQ_T(E)2"vMb+ /b|h\(sI|0K{ZԾ~w\\nmv+bc Q/E݀&pA \E>IUJuv" rt>jk 9Я>Q\#E?P0 r92cְmbXLdn~3<_! |`-IN-|i L5a3&Vx;Ρ$~`,c{YUm%i.7%Z `pU =IZM6Ѯ_XL,v\,F|0D`aRJΦ)Q hcqImk-qnv&/ X.MYX&F3ú7L6:-o.dZnUBq2uU#TsM U/CYRI`Fe%fH [ =`IPBVhW>:n{ۤ6.ufg}Բ< h7R.v6zN)4"+m;#@ [2=E_î6ם">5i#OILB/^t}<}m7QWlճm;j[VL"QCC^C=mrfՁK>>(^/h|7wcEL|޷/_ăF?5yӟ=Ru}^IYAW!Aq)ZjPiPiZ9itI{}T}\Aٝ -^} a !%4ҁB&]mB|^-]^ =+darř.9 x]]P-AzJ 4@-AzD69ga Pـ[a bY & "&((aB6bb&~"=!Faz]m")^+6J]ќcۤP p . @@&II Kb4R"*b"ݯ7—Y#Abi[,"(c:J(:7c*b&"b9[?Ƃ)œ֞ۈL/0! A `4Fm~EF86b+c7n'_9B?+':2)d9c<$}+#NחC$d1@ ѠDJK1EFT`6YGB?+**?iI$W8cK%*7%\#\Mz%9#)椸d[aF90BU܀Ǒ !1헙U$ Tv5`e,hV\JXjf,/c}^&LB`E[$je\B]b)%N:K~YA`h@b0$%Y,@$f֘ Tzzz"&Xbe++[r#VB*B*X|lj"m&)D#&onpvcr#) WjRh'ʗՅכE'c-pPAY`E%V!Ԩ&z(ݠ'B||~}'||je)*'~r[mfn)r>~z)~r[r:%@_dٗQ$Ĝ# X!-4#hZ Uhg"gk'kj[fi&k>)녂*L~=bj]rzޗv#&g*\(p#Vh)*蝥$ (AhK%e0QyѢc < H樴YA_ձ~({T!V*qr'")i&iY㐲f(pj*j"箖jWcE`\b*Fj~BÊm&(P^_."VFYN+MiYdnQ#(+;^dr8,+k|g'x*# lͦ,h`jrkR,(@\JÎcv_:⯹$ _TNB%Raj~F+ PL!,^ۡbF塶R׌&az^Q)l|#l$.tzn,*x%$%P%|& k-%R)n_G+Y^Jmj^)f ^ͬ܆~_jfo&T-LAF"*ҬVUb䂢Jʤ%lfo-^%$Z! N¬G'& X.BLa *!00 M,pWJMkhUNppަ^z#AߞhhQB!a-]"F I"BQ]j"gje+B ւn,p|ւq-櫿+VN!E :/_kgp[ íS0 S/B*&h&=#-p $2|0&h1H9r-,lQe{֪<)032(&43'<3o2O2kOBꚦ)hB$LA"wU r.  -C)~v$7R"ntT"P%iB.-(/ *C)|Eq06_eӖ*bcjn4C2B2((M4^1"B2eAiz_io(lB(P(<3eBdw-܂{w-̂|b/6"~{6"1}x̒&z.[Y[kCVTE4qg8Dgo[5\;E,L]گc~r7w[f)*KKW6LϷ|_v{-(tb7B lTAIA B#\BgG~G$`/&gHfVg~B|AV[)ɅVe)^8spZ{8k%8]pˤ$g73>fGAt3%4ve[1w|w',6 yX39gy#ܷA"L)d%[Z: 76x}c:h92˱Bzko_'tSs 0@ Asz郞U,X&bCz7{㷩3v۷g$y]8\!$B$Խa՗BX?Lq;3 Ɂu_b70xqGy9@$B{>p+}F0{sg:z߻ϻߴ#Bz;O9#|q>1sus[nGh{qzd"E'QbL&Q&#Eb %ʄL ˘,ity˜/I4 O.y)RGrhX**)ƌ[vHcɂd%mZkv<&Ѣe F8ՉS#ı3*U 3@ ;%J4G.*i HI 屝y"NZhǞ#A~9H?5}N5)p$w'T)SQ2Hr$wl|vd{_BoEb ;0l k, \hp@ -zh @s()IδXkO\IL)( 2-D(* O@1'"OɸICD$lI*kИҀ4.zbʯE֪S,M"I.2HB`ARl\!(̘p1*Llԇ 2#C3rMIҡhQ;pH~"$J]L%.DP܉J+i,/ϑW$ 2ˬ`4M 6Òr;2EN촓,jY *pBA .p+ N*`p6L *,guȢ(xqZ5ܒAnI$ryjV 28Dv:e[I$;*ˡd%'|U J4\-ֵ݄#f5 P-׉);NB@≺bP7Ď6 ~dXATMQ!rYfhXtr+dղT=v/Ä<.ȣ֊cHC)ĐeVlbnTCQ@iXXj<+V#)xˆxF6*2Ǜu4Ulw kX?)Ne)PEnR5,`@nwebнN9v&%Xqz."CܰC/z61a`3&Pi&l%&A7fcNR;vW$IPGn1 ApRns])jRP(_m 4 145.;zL]̟ R%@xǛg<"o$/Hk:W>r sx#iARjT&R1p2V!"! 7OMJov11qDbg)K^铧\XnD7;U7KHЊe)!0>R.۰kBIz,>pH_%S!. z!0Yj٪d)UdEDM>p ,42V|J%#hKFj DErآoh%(w  ʹƐ iLDԢ6(<(  tK^Xxg4n / NF(, CDKPΣ6 ??Q"fk\iH~(ZĠy/ 2VH)Yuh]>l Ij  C0;O:(<~I֪{B@>p kfvˠ1x1F%d|lJ"4H)3˾N$Ǝ $(rʅx¥pk-HFH!qhH h(TfqJQYqhD")^i9 LD9ON"/~*''$Ch፞Aeh br`xedK%qRC R ׀Q ) 7IH*Єlz,i(r"o""1+@4Ò k-ffO P2-Ve'&' flOҧȦ$/}rJ@2 !E61ۈ:o((J$˄02;N3y@3mR4ӓ4C mUH^spD ޱ&yD4vעo,~X38$u 8)#/sL(3.Hx`Scfʐs4d-Ċjxc"ɴi6A6@4/NvfA}jZϳB$@#)'r9Cs ŸK΀t,Dwh䀡^(-NN(YuQU,k7"h~O5ͧfnD/{) %ZֵFj]] g"z6#p5>ͭWˠhF`w jkT҅vv5&r"kudH e93㌒@5T'OJ+j7}bsptFd0& Sn)%:9eQbJ2Mgoog;=6#n#`w *&k'AhNl3}~)Ÿ4.RG5~$j(@+m*;"CBx!bF%k&z[ $H6_ -3 ܀{aďz17u`l? WnKD%d%otN jѸ~fTeHjLFyOUY՛2:Xh1ՠ 4-T֣b8^k7/eqx3᠓LBwT77en3ᔐ1\٘v$y~*T rDθ(JYf#z?Q&"̎sRqw3v/ C&aŠk'2L[Jc >jiX-wٍs&6?@sxTY6 4 2)iV8sow"3Éri3f#v-T9[O90g9stTXVkeΙy5hn-3"r` vij/{Km":nOO81 r<8xHj:vZk "K⧁F udc"tzs:Zl_8dYt3{xM !x.D)!9㧃z`3F$>[)ЍחK s(E)[q/Uqi*GzWL$($HMGEc]z/7B{[ۖ=Up997>O;`7zxo<` w4mec ?h̻}: [u׼.g;[لL$2'PD0L-\7 WΕڐ_{ul$cTuD9;<\k6+:%f/3ō%-ouzƣ{zɆ=;Q%cX|?Z磱0C(dԞDu[sgʭ\Z-)u2ܧc*#Hkw'{tк<ͧ!Հ}Qq*)q;Xĕ8UrV.%RZLdZ*N?]_q-h/gy;Y]=&XեT^l!;k&kвعۚ&݁Ե}53/At%&t!(vb3QYPSYaZ4k](BCěm8 'Csp^楅8 );'3z._~Օ!t^G_:sb饄~ ͷˡ_SgcDv1HG߇_2GMٞw8s_B5:0B0AQLYx1aF ?y2H1,I*YL2ȏgysΝqcf8IyQt&fDzړ&x L j4y1_FƗ9넣@E`?$;pkQq3`H|Hc^tmSA0ʩILcV:oZ>OdХZai ˿΅TH$4q햷@XR9Ы~A_༂ 5t*bvfГkҹ\Bnk>3]Df$PXCcDvL"MGM5Q6ʢg~gcTl3~DFN։Rs]Li18rxN& cݚLQc<^Ғ&vEֲVhŶvP aƈ+*kZ(v\HIYy`%vATwxw|(f!o{ F] _~ ;lv:D+8K.Rt,&c UU8=OsVL^'~"PEeFyTnjs-@M\OM֢}:oK$yQ[O@D=2vncUK-cL3.2P:Hos z[+ݜy'rVnyޭ p~m`GDm|){;'gt\I݅=6 M{Z6%NWX4b]xZ)ᅎ&Q)K"*oZEIɈtHg\$.!"gwvJ(sv06z/&8A-ysVe,ax{FT8YGs\70Ac>IAG`K̈́JG5@vk$rr^(KL<8n(dHh  5p,#9|;':HrBgU$W,h`ЊT}2u'g[v⒖9N}%i)x2m_󌩴>Y"^2x1ujfD` I@iD7y`Zdnv i)rR$rBZ22s5f(K9n \6Iypwz@ՒG՞9Ryi\FΓFtÅ7k$D\N%i@ *CJrr`Ia!.xea0aykFngr9*n壓9ZuuZyMDpQh)?|i()ipƜFQ""ĊW(uFN0zr`xFjM#Rp+Q4ӟJLtt_lᨐQjO}jk(`qjJzJaZljf !)]~b:H7)v =I3ɭ| y ztxx V'hEZa2|=9vs@u)78nyw0 ۝ :1 @KUWx1#kbnvC:$+ٲ 0k u׉tHl&K@ ۰ધ28DhS!T $- Z3\fKd ^A 3 s  w|'G*U$ ~ ;UڒYjʥy9"E1 ^, [f{&  7ۺ`-srH[.Gr!:?X[, Ż0X1Ťd<7keqj+=sB۷[{tOz&)s㏞B:\6EaH++nЛ6v@z|„PftWtf 6k#}UJm#z* s {ÂXٷ÷kgHœtvfC.&:Ӣ7Qv*\V}xع 9N۴`I`$5M3]ˑ>; q؜}Ct2/כ Am޶} <QQ`%NHyO.N  NnmO@%n')&/->Mp7$;6:^CNN ~/Q! ,^"GYc1VHآ"820± ;`)(SD9C0cxd080Yr$#!&@^ôO=PFugUX`ʕW8VrK5@~Ξeڴ۷uԨuԻN媷9r۴KXpX:5,8۷)ٰszuUĉÇˍ (!(^bNω+*V{w [Æ-+oD1c<b_ev`Ak2jӫ_^3XΚѳ?{@1c>?je?yc7~g55|ݧ^b( qCAuɦ=Ӌ-@/-PC-\EPva77ZlU~pa%Wr%sX[>i!cSNY_yޚY7X` U``ե[Zc~zULň%Gxl)Q Bhc5ܐ=&[ TP4FGp7s gV&+W氘'N;%bpk쯐孹kؾه"(:(.tio3J0 *Aoip $EZVhtR{en0F`M,K^+qo(!` {J ` D?1zC$djI[HnI00M}wUwS]i"w-*grJ~ll vۂ+cd筫V-{LD0,Q3C  ‰.󺫜0@>H*6if؂y_}*ms cw,-j0K‹D 1ĵ$ۛnN?O䪚T$JW ~\L뷎u qSRt,˖ 4 Cg$pIBɵsMWpl.nR,UnR; TgU[MRk$[Ĕy+^lof E4isf|'lgH֌ps]Ǻ׾qn 7FCIw5 "h!7pbW˚p{χk[)|꣈vD؎<#z ׳n|Yw(mܑ=l"mtSrXqFL- Owx|#׉O|! C,}to[9#_o')"|piEi:,eSOxLkdvn{& wwk۷tڷx8|Xw wGs7|}8Շ%~Rw~S#47`KTWH:O% ehDPY xqcP}&XVXS%Xw'kWvxK7|N$)(@‚VΣX!7b9x&@\r[S+A..&phP0pPc88V}`}8|l1|؄Wwnh؊' 1HU8uRgr.H!$@m5d)?x>%3~tv2XLApx󖅅hȉx&hgX|khqhw}g-*,Hz!\Vm%/@z1$B`X|(^8oX(<X`6)UȒ5׏&H"Bi}V%c 9@^ǐmu\u\Gv=  iR35GpTxP 0 pȓw}xi'xRGzi8R1hwيI XMx@ə蔅CQiG.A @m%_+)Y7UYi/0`i8 tIVhzɓPOu@t`RR(w{0B)?ٞ|7Y(Q,@/dyW)HY\ٕ Р ڠ*zWiJf3iq& hP8.7yH?9IIhɝS)-tpri>)螠HA{RYV RL-ڢJ(_qX;VuDS_5Ƞ+ywxjH( P:9Qa@ڨnpCpѥXY\uy @ 8Uz(jU)|%\LQNڥY 4Z**R߂j'$yAi\gYg  i3f/iRPaڙڮ6zgapJʮ 7e9 TQ:JP  yyڢSjI|X! iа'wQ{`(%H=>rOACٴ {aP$aXkʯgЮJJP RXXhzЪz@:QN!X> @*h P P  \ jȤ{+i V^ sig1Ra\t=kC$SG$PG$(0R]@Z{KҼak˶lWKeЋs[;?}@[̐ +keAJS!۷)4XpYZ@Jw~nZ+@d fJXCSWOA `Gfq!Lj|Q̥1{f}fKUL~w<y h\`ڿ|7 ЗچxB&%ѶJkGh5+xQ-!mNϙ* OPQ&z!4_+mKrѕјw͸YvhF$\,ܮۼ fg|UQJɔJͪ\ݢhչbv]S=f^,X~g;+q9QxtuZN˧P`2,- 9̚ܭ`ˤ٠Omt1nQ]ՙkYǺ=F+=G69F)L/t:Q"W$K.LcN\KEMQpAݭ.ک-ipʢ]ٯM=0v}$ơWyF ||>{v 9Rͻg t0Q,W\`6eJ(r-ݥ^|aSb~[^f:N_㭻qK~_w }\v7$XuԽ1\fۼ^`ӆVegܝZ.'Aw]՚|#>Ya~ H-ߒֆ[>uqH)(} .}4i^(Ϳjrq9MQ >_]@+7Ywdq7* (%%uJBi 6}pQɎGi;~P~,*݊"X`4 NKRKyN.<]* KL.}kg,%]^>Q\WqY ok۬ ( xJMfyKͨi@b ]6꿓-3j {yAJoԭYc^a;YΚ-galnJdCD^ `~ yi~QikYٍ΅6˸*{}Ś  ޽xKnAoY[QܭF{"[wA'a# 4 @`.dP #v0 Cr :{D4)UdF 1LG Gӳg!=OIK.-tQ%̩3j!Yb=W]}f SQKd{.ALLXaڷԣME'%KQ9+4WDѹ7``!CN0Ab(g>r5:A')E.Zպ֠Bch9y{E=~Evww0[l P0aAdA#D""M6b@H5bH "Ra##z[ :p˭܈nGEÏBң:H$̦0 s1 3Bj#b(>5TCA 씈3l.)CG>HRKGtSM5>MrQ(SUuU*cFWu:ե(D2jK3>nէMㆉl0v?3AdSCUfyЗ*͂:I| HعQC瓆vܣ㣣ArzfިnA"ݭT) VY47n@a#";l7VI{ۓQNyel_.H /c 7"đ\ѣӃq|=?rHVT8] W52vva@!A$e6#MAme+C8ެN , 4P\pBp$($_ PY\!j>"MM;BŰT{ZĿi F6GFA,)m1f ,PkL\ܠ7Pc]p${SQ[؃Z$.4uOYCCѪ"ujD*͉ʑd("w>AKYLJm@|&52 B) !g+(O|RlR W-{ZV 4h>p|< w Zn_)K&Ai*м3pa @,Þ̫= ޻ݓ݋:=8[#a>aʰ鲳 c9*t;⋾;> (܁s2!2Ñ!,:K¿ Ð `.暧j)z\. 1 = l#8 -zuS 8, )36&|8c8^HbBHDhg? ٺ*:Йx/=03 =76 8g{\2;h <^SHp F8 LE)B4ėh]Ӏ3IH,L,aR<0ʓ=Y ^ m!!1'GTtF H,@C5E K6=ACVJL'p?KIz-GDL1GRƠ1x|̤J[[JЄJ(MMd=2t$k@dDF KG,μA}K$@ ]31Í@C=4JN#JԀ,BTkXtĿ 8ںCݙ2K$MdӤO3!`b( }0=X^eEaF%Pu'@=^ƳÂ]IV# ޽ρUcuaE䵕$M^!e$bN$ H$0S#N#vV6[.],2Y3{RR-[F凮hyJᄾeP%6CFg\\!5x' 0 ]ՕN-J&]$h]6]ݲפ.jj$xk׾eb.en볶fF 8LTfne,`l`EL@zdQYCT0}E \Z[5~R8vIEb>eno@q{fj @f{~mA{^7<#'8&uoA0L^c!ߠ%Ʋ.-ʿzTVen1'nFk6G=oqNf^*>s?s*fNFqCg7o@nt݁1We8δLv[$O̓54d*Kz5CT}8yfZu:FNNnFheOhsvT(*vkv@Wh C7E7tU!vOI']ăO~?ST]uVZ%l%aDFV8:hv{tVvJhsg pvgg'X^@y'PewDywGwρx՞wNA^P>uX=?XC~% bG 8PĈ5 F$F6rhQH!0`xaҤ $6l%L 2-4iM` BeQ=<ѣ* Uhժ Z}X XM$#ANLa;-.\Е-[*zD ( e2n oԨ\CE#+ybޝD@ A /dfO)(#8#p!A,y +[nr& 2+,b,Pm!u"<}u9_gTZmiN\ѵ|Q ؄cf>ԧYV@wAfhH<F,\mcCMTBEEDGq'JJ9uQޔ]v6U`u/ /IiS™PY6iVbuX`ZkH]H %&^2*q@%cfZXrFfZ^}")@Ip(TRc>jBp< uܒLdJ_FRW$KbeJS U=!}6r19pC^5A&Zc^__jYB&&CXTų#Tk'*rl!{3dLdNbQ-|IVa|1.rn6ndGRzJońVڇ͙Vu+Yn%D+|)- u G,s67|>?JJ sD?@׶L7-qR]~r= a+ل[t.dLCH2 rX)9A&ERCZWVp&2$B'O [ (GEKZhrif\1>)i,Q 5** : 7̆dX }*n[T6 9>aCj$ۨE!hnQh @@M$!=QrQ"x?pK_D%%ܒ }v R8]0p4吆wK6uЀIp_:F TÈ&u;5'PDd0:hΤDQ`Tc 4OmF -O8)^Oڅ.d͙.L&Tm JMǺȥ.ڌ7 )Jjˮ.,j̊&&\4P@zUUKqv &-'DF]Zz3Ev h`]*5 ~40'JHAd;xk ڦ7!Qi૰|rЀH{Y&-ipZ5@4j# 4[If :}_d.Kt P\!#ҝ.: i#|.|iB&l^5(mAWi']J~S" W4fҴDa+qbIS$vreq,6GV6p#+3 ]$ఉqAhr-2Z槬X8 q`))ww6=o4T_┊ZP.>9eHGvy0aS JgmP,B kd"o/E&0!E8  !E8#Os gw"i`7Byg] p?b \axi}I-JIAN]IZ vY?nLdޅ5$ME ]E\XEt֞O=$dB)a& 1B!P_u_e">u^9؝B AA FLƑE}E `a^A f uai1^[hD^u`` XEF㴘jTil,+,^_R/^!0v_& #1&_1#2fB%P3c%,#%\- T5V04Zyf `; "!B =\]=b@hGqVK׆ LQTʑ\ YS߅Zba cբ ^]5&$P"LV50b3$3bNrd2#))PB)BRQ!S6%Q2P e&DeمfBF89b^#Z W\]d>R[ܼXiI\I#$0(l0ڦ^hn PQfT&p2)) "dceMZ!`#]# T~8 A,i NY_}fgnN過fff~g~rN(줂R3cBlJ(2b(bn~nj臚()S&#$HB-B!( QiE(J@@bDX@Pb'ŧZ=iF)nf1jz(& e(dJ12()TPf*)cmꦯb(SS+2BB&@6k^#R,AlD% 4jP&2 be\+\9F*&c~*rOPmg^h %pgS.2lNJNdY%:@8D\DzZ*xB֧_^v,k&Oj&(ڤ,læB*k&"lNBoƩf&L-Ċ)Ăj-30֖(-ع!JǶ `]Il,&\-l֬'欫,+h-+')N$Mk2%+Lm)(-f^nTBnf)m&A  A$0ȁ|6ݶlH^k-ML (./&N$&'|B)l+kdB.R/Z( &mSm*D%'P.犮660uܙ e`4@}#vhif6&L/Z/+m(+/B*oTvRd/S/) /+o*en(Ѻrf-:܉ݧ9h12#Qya)aq1niN#pfֶ䢮Bί )Ln+ B2q0',grq&a q*qSqQ*4d]=_m- hi1R- 1׬cB+ nm,Ds/r C23 +%%+&'k2TƯz21+2)0"Vۑ!岧 fbi-A-Aryj0tD_13DϬ0)43 p$Ks,o)("CHB(l63K)Զ2#%vS>%r‚)WȔ`Edg~>-H< ,_0l>4Ud^c!iRBG1o/v(3 {2Rs55^*6B(L/\_h*`0=dJ6Mdv&4K_M!coi2 RG +%hf\$fjkNR5 \5@M! ^jꙂ5mB,B6dۭ Z[2%`hfǵ]ӵF*l3^Wwug_{'&H`[$qGuNB HY_\qG/[eʴF/Qr/&Lghmi2(!SsSkk6mmdΪ3>Kb_ %N6t4#[.Ը-4r(LB"Xp $aJNBVx3_{ԀWpý7śR57O}kls+!|{#y8/ 'NN:;X^"W*2oyE7~7 p Cw3w4 3GB-`_vv$ ”A,#T{_EWvUڹRhc!|dj{.W232\vIvo{! #'hN %cBc$Bj9_ߦ;-d߭z/4uOB_Ikv-o*6+\|KyAĝhr9&y9Wv)MRgG߰#ɟ4XPĈ5LHL2UxER9v$U'@رdJRTSL3i֌ g)SBĩQ#DDU iZFtQ,ZҥD9dd)#'G@JʕIL$-'=rу 6qG)tI ÷Li=j cǚJ4sdjթMvכLg:)#4𠄆O, bE5JUd*+V wN$ǶǷy뢢But[y՟?*Р\gFC,90$ R";n.J&{dJ< #.&IY0@.#E:i:DBt1MBI66l7*'X 7bJDri#N0;ɼOp:/8$(<Ⱒ"P@Îۊ+DDD"dL4ނ+L5ŕD:2D>H , .8+@jl&PKک$pԑW5M1يD$wcҷ jH<:c O}]"3;1$ﬣ:Sh)S 8gi*8ܵŠ-w w#Ѡ;Ў1w;IM>G D4\1Zi|i@d 9P}EdbFsu5| 'TV6cDրJ@ d-iڕ;[UVLStI\M.Qԍ<c y-urjD‰@uB 2MRIAn% 1a%4#-Kc'B Vr$"0qҔz1 5_g&܎Ź$Dbe/.m&SZ;)DzklwU[?}[ -~! ' CA'q+[Ub!ZGpTH3#_Ռr.\&Mskv:P6[SV,ݬ7 NvբuS&q [O Ԇ@ءzB6y@ែPZ0쀠Erg>hpB)I+8թ2.'LPt[ O~q643ґX7 B# DBZB"J\eKu+Sh"4X}k!+w R s % O Q^G .#9LHG2$w/| X1AA*ϣֆA 5v4HmZ9Frr}@.`! f23HD&p1d Y\:* c1;Vw âߺEAZ$L)fĂ7U R[E*01N?P'X0Q[ lŖHHʬOd)k닁`,udU~F8$P\0) b3j %7AB/bA&>PH<=a|WF7@voK :|Es B"`D&>cNUeu I|)" ?J6t5O@lXobP""49#t#.h$ 4[QQ#2 2 /*TI5~szB%.fLxBBMsM?ϩLU4Vc%Vrq_,\NN`u<`Qa#7 v$Q¶Ɩl(FFaBB6T  ec2V F8nTerf/fy"Wrs+"hqa,QzOC?U]ֻ".ːN %o@0I˖ul 1& d<lE@wAʵrWHx5TAzBJCnq6F!rմ뗊7f52OEsYTwu[~ 0QY4L.f,EBTw{whU.'8꼚y;17U DQd{yԯp. .RW@j3^[ThGry {X=E3imԕVeٱjYW︗Y8#j2U@Ǟ+eRXP:  ~[Q[=,SuL"=X \N2G΋qڼ 'WOl?K|qA- $GTYx.c˦`a#|9YuBԧI d4c+`6hHe;Xyo q?)$H[&d_[NːFG<} t\"LYɟ'DC(sg3JO U_|R55Q7f}i;YFd 囤?O^N j@358*V|._rbiCg[ ٔ&pG96 KDs5H,pvSOIb1 UMeg[DԥTs19&"]' HT<7դ?֜H.!=%!CPb%ڶ7L@\P?z߽NAXFݽ֗5YT7R}/ N:c6-AJ%%*6}T+ao:x2=IQ]˥ 'f͐G.s飰i+X>̞M|IsXqNS>UD\p3.,bP~8\ٖ/4]j:Cm 2eXY2V­Dg2E`n\ evmH Fa I T!QI&b]&!i~ JuFȖՂi qjT'\V։v\ҔVzhH!w=dbH"8"}eK;*H0!g ҁ3 *O` kiJkFH#Dg߉੡*(GxXPU'r$:کXL)诲9 .o +nJɑT.LHY;ɻ*{,2i%DmU\0iɽzˬJ"ɴܺ8jwKs9 + 41ݯa"/&16[!>yR$iY]_D"I*Xzm\Wͬ7s|ͳ?n,[O?JUwfPTÏfNЎ][bf޶'x87M.gjk\{'sί2uJϬۥRnVeVp&=yJ]֖:t&G"7섇_-fN劃dN'>3D,Ý>ޯk9&Ԏ}bDd ~MXi;;\4NXr<+R=QHkiȗ]F+ulbAG.d~=I|_ªHߝ믟i)9lp@)6߯+Jr5왨O=58e7{{#uz+F%uE\1v~4f(sQI?EVI (ml3_ߤ\V*?S1 #_ƀ"YG(t6Th ?cr*4u&mv _rCւǁ!O.}D[&# R2f)EwsQV/7VtMsV3THb.3\47Y]hfScXf@?CH!8*wiGب@3g$X';Ci]jц<׆LZPօke*h؏ٸЍ&<TdHuȆ[x{YlT5S>'"\H?<]w'fu~䘌PUhpYǑuGY-D)*Oc·MOP`5|4)|BDAe7&tb!J,5Cns8lT qoK8z.w D#O;9i"8X{qH8.nV]oy B%xg9]ʴa,v۲]rƋs4#jSiHRIÇbit<)<)aj t6uRIyY^y]I|+*<9y`o66}1]\B߉-1l>yB)q7ƞ$R O)g)ٜj3.USU1i6Ihvr飊$񠈩9$4uJZPwy U>7**XJ!Gz12Z}tH]d-UY=?YAjrDd(ɳtIP8lt Yj _|sVoR" jYka ]y pz[Ix(IZ ~*Q7zrb(V~Wsiˉăd~YXqKcJڥ)c^P(mQH#ƁsZ ՈZ\^{Qq *?7<84I^`rUEe eOJjjaJS%_x G*27ѯgW:IzkjnVpP饾<3t\.B)Z]6#{6)+?k!:,ݚXlSTJ(Gwrùizz}L UD8X%fxTkFXZjC< Ru8 aM Vl[}oV}yi\gG=SƅEG:"Z[[^]8UYQ0jfmi3QFy:23R`ʡ[JO=I@_ڑ4O.م[ʃhY rZ7hx6+QM"RE+ AvfL&eF yF$( !yzsrĒ3~7ktm{e8;<.!(d7!}jH{[al#3]78HÞrc7 ,1ЊY)>v;"(~꧴:4+lƛ9owwO\QyY2QkW|@*#(Zp'p;N2=;H7aP8Ƃ,PDv2(ʷ*|;ʅ4J1`QQSjȻ\h}7* =<+ t;Rb—\e(ϚĤ%\ܟڷ7[8sB7Sʧg*S1&ηҚYfwY7s9;M?=8tl{xxdC5Ioyo4La|1O!26 efl5}dF;b/+ {FSop]z(1:z5'Xtoֵ 1zC y纍,ؙӞ̓*!-(SRXXRjI̬ߺ׉GT0M */$FN1 憿/ػ v"id-VY9"W: KZN #%@UC9C9 &395 Hය{jvjLVofsoLZ}HT dV<@= N+yy0'O4:ޛZ3ZV͜ 8x'n] ןLT\΃ >@N9E.D.#'LޯOfAz0r0z`F+Gf-H? ;&1D [hܚwcڸǭ.:U}埣M:q_N9>ꥎ."gV|di{SƆ@ :w3?U9ʾ\7^,`d$. 7=2譆uN ^J ,TȚǗ9޹̉= 5Na$0|0~9nD>9 ӷ+?8TgVNyX)G򐩌8cj7:a.{-1 A:nA y _?7;@IXDw 颳 -I>AvgK3 .%N$A\}_ߕ^PSdLԷJncEX^s.͘^3̰ ѿ O ` femWvհ?O҄ډ;g[3<ųK |J`R <*3BQbAL])&!%M@%QMB̘>4IN7CQϙEeU+քTQ^}iͣ]mNf̝. سNFlW\qUśt ]k+^HC4y•Hr(̛evm:tצLJUuUPE)gO&ٳz U˳ۃRۊCѡ?7^ę[Zs_GC|L22Xl&^>z&Z'آnͿ qͧJK? k꨺=B 7p㮻<H# 1Dh񴱤2*B/ȫTQSsP*rO$kx2eC町2CQQcORa $ZD0L1 uD  2H|9DsR?FRRmjji< Ώ$S̴2J``< hG4:3 )ZeaZd} 5J5ɂ"蓊&6-KLMNH02 '|@5`UI`]Op˗$8z@`jQ x&udΨV V(|rV>VOū(2+1r=J#C~h`y`"Fףk4TJ׵4D`Tf#n x'we٨Qf%UmIO_n M0YK'@nv@oo%'< ;qgqO‰&&r)?37|rţ&x@=/g[C5:sΡʵkWpŠ}CVl@F 4G۷orMk.ҢZv˷߿~ͪ(ayN_`Ų rQ>|xA :4tȰh#8kCb{m(L8o6!D-bp'+vvI`^as,E:BB֪}C˟?[-l?\cptg`^ BR~B |&Pjf(܊ADl/CFoIr-Q..ZupPCM]w_UIݗ`R>Y^`ae5cUGUYQ`_Ry_XXvoAVYYi`!_p!LOiTk ⢨іG+NPgo鈐>M\s^QFP]V9^b%&ci:%`o ~b'idsvg ɩ6vkhD+6wqلVjM"G!dj*T܍5_+ܬVh0Ķ;VkXs)g?K 6Bm^jk⚨)ͽmn*MSuF`s8tNG rüܴXZpEhȨrJ?s ,7ir,|}NFrՆN։7-Ԏ3뵞~YHו(9SL$qsϝR MD?MLx~a.yFI:ޛ6{3M<3לkeK-q~s,9S~,,gpΆRcB^3Kvb C񂧵KF(1"~SJ?% VC7 M3!5AQ 5h 0(NmburHyf:բ+$q5:Hڬ(6o Hpq[t `@ d)RBӟ:ePnj+-dgAUn5k %|T(l̖+Rb'n&>[3?Rt'|]MJaC0k@Kw!tE(N"XyAJf/w cŹF6Tu#$X(  IK' r+K @7(՛D2JT!*bC"(eTF'Gl}C)["%AZ6jh#pHi̔ U8+rq\4{ouÖZvvwk[&ik:61# /%Z,mė 7~K_ h A|ǧ22*D@9ske[AՁ,kau 7xo`3;;@0jkYPmvmG%+nTVz+2ZTkg?u%\\ovҍ;Pg‰N굮hOJÓKl6Gmp|8 Y k_̵PL]@O|[; 7\񸎺[pG 6/zmkƢEqp;-aBUIJ7s]CD;B s+~ w:{pGFyc8zoQ:|QoІVތ't#iῤnҦc=3@car/!RbJ5gs8W3~kp0t0tttwp`~pkabk~`~t|RWk,(燃7~5^$BUgR{h_*8D=AqJ:PN2V (_?7P.xx ' yփG쇆~gp!aznh,xa k!x;~x(k-؃@Hrґ{NIxu#{2DR6.g)-w,VN8{pGygz8xznr8H7(8苴芯~5؋X2&Fb@G6 QNIkc3գVxvUH0`\~PpHQlt x~؏8hn ~Y~z|0W^'U2wdxtimfMd$GH) 8!X9zkxy_Y`9N!=Gcr)rtW<6F)i@/V =ٌ 郸(|`镳衙5gĘِIL9U p3lid@.mu9EyEDҀC 9dx|?xȋkؚYn@Δ| POY `TYAr8I{?_B]e=rmDrx*gEN0IRt@ H YX(ZH1NPaɋ9uP`PiotLYM~У$hu[VgsQJ0 nRUN i&RI@߉H^yQ!Yt0IJLM1p p =:)*M1'oaX\ _{c{h1#Qjn- iras0r݆0`XäzR{Ii@ig.zڮHnu)V-@ [ !X99۪*B*ПEbWʨۚ3zbQд2:d37 Ztx`iΊJ@JFXiHIۮgPhP :]PU{C˴gUg@NO\AP}- z PP IjL]ˮiGQ OJˮ嚮PAPbSx-tHÈ:cN1w dIuP%qAKzRPR]]:;T[a]LkۻMKYaIQ۷i upPbꋼ^K0LѶnFXX ]|!V?K핫4hȺuJ[|:s[09CRZK«$LL].̻]PFJ۷n` P yKrX~ pNZ9VP;Bt,y , @ xn7ë u3i/ Fk',N#1O;waiA\,eKd ry`  P Dlb}+⑶)УjQٶ` E? CU/qk= 3:+Wl1ˎw4P@ʻWܷ+͵,…ȎlɼG:j| IWLW ˻l˔sk!7fŜdWZWppF\PQ!ۿ\|#նSQ^l<a8@ phb;LԱ QZZN?L5 1]D[9 v^Dݤv-!-C' L$ݷ)#J˵\VxLG*ݤv},JEO=eeR˓ ԓm$dLsG{XC"4=V7+AiR2y{{#»nQ2|^jp'}-J*&,}Mb׏PퟚMmu }C6s*GFV|Z 賧_A{W(̮-ȃܮQ|>&~]H!g\1A'_ Kq:cް{ުxs8K\cF|qT;a&N䱰\ }`~Ϟ#b[RPM9I| dU磝&'+u:̭ _F;:FnC f*׵+#fV'Z:tQ凛ؑ;OKo(qsJ@(^`3 ejk0QDպ*Ҭ|X >arA|MKܐKu!S%oP/p>,v^aJNsEVun[˗CpPuLnC 'Z;Nϕb.nal7ϐLw!&EKajun2>&p|g]].[=OAKh0b4O$ Е&P HlΆeśPa;mm+9F3Q8r 0QG?K5{i{SZ Ыی.ܡQq4IO-to=6*1E\o]g|OX1\/&DA{@`7 x^f#ڣ<n 0OćV!BkVz~&ጶH-ɂ@ }Sg )ơ3]yMt8"Q"E5 $y#9 Ⴂ1a&؀ -tꄁOCعs &:,eڴÆ JU nMkab%kUim#XqSdhuf^iҜ!\X޾q!ȑ%O<.>~TR!ПA(4iԊPrth+GbIƈߵQΩ3d˗2cҬFQ}}k0juX"h^S;Nm.YE.:M)|Fo]o@oQptA"W5 34E0C L! S\M-!x 9喃ʦZ.GZ *K=+ Ѐ=#B  :>L3+d7 LA3 5&T,,E,iO ^HFpDA{:T*""k+"J+ZºJ  KK* 2a, =P,S=T@2{LNhdPZjED5>" QdQ bIK-uGM7S 5HUUlիR <@H"r/S4/#hհݓK Q{QrRY;.*q/u.Gg"HWmTXUV}aM0xb+>r7PAӾl)L?e42Ty2 9C7nf*2'W2R8U (+[}`'mEjLހMB6 VO]$}¶##2GT5lvccATO7` p/yn )Bx<-gz͟Ꚇd'Xv7su]?AxJ^ mlr`Hd]LB{(@ Jt$0A:CɃw3m)R  W󝏆;`AK[#b:!Qcbj7A ŋsW@[b ] c$c&?8!B$"m ?U e~ +#Xr*&̇@4@D5/JD"F5Kad,Y‹d&Qk˃W _U+] L@|x~<_&0ҖV!!HIOX!I&o#yDa Hf:Mp.cJӀcS|`3U*ST8XUIJ]T,>L@W`bE )ܔDi2@EDmkC8Mzjmv1dH;9d` gxS?ZAuGOqՌjLVzPЇW r^qMD$HŌf&%fVӛ(eʴIS4gKf iv ,tPTu)B"wtL0R!a R!S'/k7:I5ȯֺ^1BbJ,HʦN縩(lSVYTHe?qj ҳܪ#- ڎdxI39[k]K՜bnK6E.i*_9v+Rb+WdJC @.A=aґFBx ^f0F;1?]-kVE %Y"{H?&w<h% ,@1i@lLG(B9B#Gc@+oa[~*UOL5P0 J/X ^W+F3KZfƕ1@JJE*YIR`dBXV4_)0s ^p0z1J1`ֺVLb^,Ha F JKlnXnv,L.Pw bT';A|n-|G p#U!~/O7-ijS~ǫ?;z`=C;׃Yڛ/([67ہ$3#p؎Xh?)@7C?#?,@?K=g¿򂝭Z7+¬3@1)x=Y !8llI#xmLD!Ao$GGsz\qI7GD?\.F|#G`9 Ã37,`]a ;L\z;`KIk L!,LL 3d'hL|LA?ǞdɃ$4>|ILMlL4!(HHtHT,Ɔl6;銸óԀA۾@FLL O\ɷp̧ILMnTtJ\OO-O'x-;<+T>{<|Pl7<QdΌCaFpѹ,)dsM3T<>lQܲP,LƤOEQ$LPK(R=MdP+eP%lUIF-%<$HS7[4PPgpR #&hT@KO1, 3E6MSGuSEm6mR"%L,$R#ChETeˍQiE<۳If*I5G Wq)(WsȴdLLp!XMK/0X7('SEUWMT%~%U>HLV X` W]`)Rb`nTI-aCC3c>4c>aE$@68}Z:׆^4H[q^+T6*)Vm尘}3P`_j( mĥ0Aq5$Xc 4^<7GV1R#ECF>f5>>@B6CVruuT?$AƲpggpg%U0`FD\>gCH r]cl,gkVphg06]4E$fi6DNt@Bi4 is\'j4N?0KRn΁`d)9(ڶ=Y8|R8kK㇎'D`eV_O`3.iH4|X3fGU\4lfihki f}Ч>f䧆EKh3́((~y9 ڵ>!xVڻdundb1fغ6ýi6N-ךBANoF6o*Vjc=mix(Ԇ]mW~S@8P;j^p X|h#@=; -V0TXeWa/~^UތpFӒ)rN.l^>CBS~jN(j/ot6m1/Vl*WpcjM RM;Bd ]2c3L3'WXL> +3xq@j4q( Kfoj>6rZ i͍-r]-7Wo//i2@k8޵NlQpΦs|/H.9K%8:) go'q!xPY,J?NP|PPsnWP0mou5oo4,dlV[ 0x?mn[Cv)rvr.Ygui$h xvj0|. >WlwVPVqb>>_9E!0F=|pj Tmj'yK^iWΝoa'o6=2?(7=F// ;)o5slG~ j 3d3/cZ7Mq8+uwCrJv"eu rjs.|iV/x{2.or_>~y?$Wx?}%dWv'xx&ܕoo>OkV phHS#&l,l1Z  l` Bnh D$J`F(6fDK`qF*h% @fҬIS$:w"䧓SR _P*R:igIlr d'ذ;c([e*T[2i-KS'РVFm:jO =#S E22%G&S@ FT )]/V.AÆg#mp1cG'Y`)0DH*N6W>sCǓ/SmʕTϊ[g "Xf 5`Ke'3SOiUS$XQ@Ye]I'-vZpk5n@`[m 5B;6F&]rr9]tk% Sւ 6HY8}7ExMwJ5U|wI_|䟁\M8aMhL;|UW*%h^!acN*d%Z)YxQ@Kj@*D%I:)}JVZңٴaOPřIY[aӶWZ%砄>(NOggՕnK ƓPL774PJq]hܙ蚍:+ dš$A>בŲV)`V9r^˔@{][Y%>Ւ=CD\sWUW3[.RJ iԀq"FůY1wƵ= m0HrB9t%':ó>%fixN.zR\)2\FL3rYhVqK`w_ܥ+0pmO6#c5qNW+F7M~:`%ǭj%3&m]nRBF$R#_ɎBj;39+F!]DԲrƂDlF Y-G:Aze|YȧWG1 A3wr@=ng_W~ B0U*Pʴ<09ȉI'J%aAAk@7^ R0ъ/␛@QɣZO}2+fRhLa‡ 4MLx;FX/jh{4a<ԑ܈ T`V9`6㰓}3 VSdXB haa Rm{Сgw# CPZҐ" ]>n|ORT]#]d%{$xBKs!DA3"XP[ 1k`pHJȭ"%MjC0 m ᜨa.)jiC,#iCDž\;jC]uL¬ HR^ 7 9${6a6 P#89E02{<}١lX3΄=\kAtryFC贘3@TsYE2k+ֱ.aƷ%E` ]╰D*B&/MuNknQ/YŇ:e*Ox;O(wxJkm$d$h"ݽA" $P@v '$$B&dB@)DB)B)TEN)EZ$GV$)pV@VH*}y `Rdc02&2-%^EхX \#e'uTAN8J,K6%B`Kwʼn۵AEZ"[$e ~vdE֣=%>[[&%B>UB=Bu BVB)_CbD&afDb$FrFv$FfbF_/yǺ,\N~&h.c1.rЉ,%! ę-Be! p%߻]K%#a%Y'6gڛe5'%CR'vRB_Bx_&y>aa"faN$caR+t)'}'Hr !݅BAKNf5݀mPH7T~줷dqi^X9``ťuJ(v["u"d($%xhy(zzy6fz>&Z&)GB,g)%x"B"]r!g. ÝI1J DkR\H&G2.^`/BUerJg(Yb(%|ba&)B):`fb*)+8"$l $$xF "Jbm!0Oh@8€|EKiAk%*h)B dsj*jjj&<*frj*bfdBz%{*"b*>ZBgٹ~ߨધ` *i^_`P"b Eؽ`>eFBk%u>k:*a *6'D+B*8Dg~+zc.&k)컆b+bnlE'ϚBZS^Xg̩z)uZI>顢f&jȺB,,)0B~B$-,+-zB*h$bޒ*GFάVd|֧v$~A0 <@L@k"֛f.k nj'&ިߺ/.+B.*(h,G)Ƃ/lvjI&*ҭ.-E+n -l]p@ A'J%4)~J`&jƯms,mD'+.2Gj@F@*/"oFf"EkE~ , n#A,Zn BLՉ$C ί0uRk(vd-G,>GZ|1B+p,Evj*pNF+b1wdjV,Djgс`z܀,ޤ}pxeE^ ^!/RW&(1/G'1}.$GnZ1(&Cd'o~Bj*p& " Y&>W=rd(>`$ "2,Z,!Gsn`v*(fB|B,@}B16&qE'/ꂲF;;*rr=늲G$&D ARAprq^VA ıTkn5O+>2y#*$fm^yg*.$}8,NӮ2~P3%3)(-B,߲,#L$3Q'O1aZ;7>l$PZF4.p?iG&h/OtX!0*^mB2t@6"@"$8R`"$?-R;4MNtO_6ftMB(PPۭV3%PisQھN?5lǶ$XicBR2ro7ߖ-&OT躳B\?\K4E]^f`JSkBkiv2[/>6ݖdsl$W6Ocv.Է}7C*\BmSgX aTvjck xTkǶYu[=6m7L;o@rܲvKshW$h+i';7t# LweTwF_uB!+sZx.t[3zG^O2+@{tM7Cy+p'\w"A#4Țr*3644uB9։sRmaH'f,?cx=zvG>wsNB88eA:$`֎b(4wwRx*.7k-}k6{F9w-Lyg6NpGmvBv[ALXMAyxU{u[*R{274&co8hB\x;z@0(yjzH:vzv=#rnK.y[m z-4<.ߺ)tv(lo()Bw*(g'lpK8S[*4243#/2|YْWi8/=h&u)'[s_X8 {E!$֯Z~5wlKWU/S|(Xygg|+87|sB#=='}Njǫ'!`4BGT7*4sp9nآeW: 3b'g'tǾ&\&o[ջ;g?YxĒ'+C[V+O|gs%(Bo?(='(?%XB" !D/PAdT~)GD=zԓaA 2"U #(LJuQ#G -DxaDIT˖5մ'P9osS(CnҥIɡa@T*LY$I%1eWcɎyLkצbm*oIl^4?u45T(NEux(ĵ3WǷj<"<%s$ψݑ#G,v$u^J*N\T1V8DC@y I`mݑfϛ3Tj&]T$:r֮˶>p;ʵo_i7ne Yj,2^[Fc1" b EdLx%Wdcq%&BMR\ˣ0C 2, (/{+'J (N)J<{7{R+$8R:E# )yf)^H$&pbvzM٤g.)j,p"Pdܰ2Fu>uޏ];^/DA+D- 6`VB -|P< ' qTIa`WrXWr65svC9`y('a_٤rޢZl`a ,Ha@ꞥNNa=\? 3 12lcr+\q 8$kHx:LGY[ Q5PiB!PpwSp 50"f&0'wtKw]]XAя@$AkM $2P"<[jtIHRRzrW)8Ćppx0%$ 8D8D$\C>x(򠱞US%)M)[DB9(I ܯradYIДЎ+ɴ+vmi8sJ%B2EFf#%/|1tuq Edt$LlbOW(N_Ʋu6A |.p4 P.yG01 C TY䐺rl#ЦB@Q6<F mj NF CnӦ)UJEKmmsG$tŨCڃ\5@:pAˀ 7 P2!r Vj9wGkFhB"!%!n٢ԲvK][Vж R8A bTЈܧF?@h&00I=VJHD0LAf4`PXXk3;r&E:dݥݎ;X?ab/fZ$y.]0Yxt黦,J8ª#ބ7J!PFF@ʦEF1ORQFd$F-He:0 :+6)YjmO$f 1jpBѐ%D {+SRFL1 %]C]1o$H(AȰ ":q`:R2YFD3z,Mv (B2P6?r@BJ|!'ͱL*p>b,G llpDp~'eB$H8\@ "L Y#'Bq11cQ|.6X)>!Kf "a ӥFĊˆ aRvR'2`50@f "b!703PZ73>0٠$JT"+l="Dl;q<0ՀjTĚ$=X>352$׍5.r26@??b.|J"9!G1@@'ՀS7cb,"d#R6cc0tB$G3I0>Ԓq3/2J#/Ri/r-G$PEe -zt2A1䥠MڀWR/S>YrGNic $˴MКPOKYpƒNT5#a%r !@1bDGr543B-m~^B# WnJ ɢ v/Ja*CmJ< 4LUєE nVyN@PV?fTvWţH;7tC"pԐ! bne7YZL %vffgzJ^EWĎIݢJiF%CIRTO/^2r0^qWF4*j $HaNkk-Z%Q_h@hc^ lVdi& UXBEBj\! -$LٕpJU%vVMjuviW6j@kkCWtV%)sXV mcWv{eNuYSi\e')0S8ܵrvgOg)EFM(v*53h'>53j˄~!PJpF}"L@9O:K`W ~{o f& v0Pe#(TKx+(5<7yU]t"+:o(Rpro낸M'h"Y)1e{/ Ѕ07}箹QZՖ~EbJ+u1:`ceywhoƵ3%[ET#x3ss+LD?hw񀦄4J޺`W!f P]k؆sx}rcmi p& NZHrqz#H$B %35yؓ}B=Qx^5W9YSwxG7k9boKމ6\f9- ny%u(z]geطԍ#׉ ܍hlFC<ԓyB~J Y/\;wkmki mkUB.b T9lJ{{^|#z')wymKx"Ep# M#OD!QMq E4YGӝ1@9 {kjp+j#PCvsFT˜M:J(s5zq7\ "$8א/OړYMX_uB[Vs d"|ۨ [7GNN (P fdB5NF^@&f;;fի w3uEjTۺg 7U[ WFTNr⦥`rrsU*+[BOaOD0cƠZN(4۳&#Hʻ a\xe1I ׵yvLLU3tFEwjH!["RF{u3UZ" }wEI#(Jd0Fzټ)nVcu"z'Q!Y.۴Ѭ"1oJvxy ?b«\]é#-= HM8PG rx1zc'-;h.q*ܥE[&h68FK x>TGtc:&vT^ɓ\*sCVb!D<%T'e:b֜_7-:;AE3 E8#4H%,ʾoώ S;qvf:DJ.sCeoׄ V)ԧRaDj&I)#&쓳SqAY{]ƍ6Z|0|)ߴ]z5MJdfC hhxh AjIkSbE4}%b'O=qC\G-rT0MH z1vI1En [espi2p5bRD"d_TP=5XOñDMdj"`AkYh2фd1V%Q!|^yhSTDAdQFaL!a[(Yk%y^Iآ!}RQZ&fQy^QX fpAfKg"nz٧G:a_,j%+G@ J A[wN[w͆a퉡h%-ƊQ.+l "Iq"9rl, -BF&ۼy-mԸ8PTxZ^{%I$<J*V EK^{TI<2J Ʀ Cty_1+zRL"&3w7FܨS:W DdWG9Jl2cS ުԪwb?u`nؼ&6w'Q# ɑ@__{˕}C݄,8n8;>.k2{y>b>{.~j>qmŊVk4I=Nv%xBoAf59Q6"۽ f9953{$AU{C=_ֳUj /{oĹb{!ܭ@bP N.8M+0zP$Q>=hBcԼ< F_$6!C-fVYė\~ꇪ3D(%@D K낫Ɏ%cx,+"n{cKQcD\ZF߰DDb_R-I)sQٕnDo(j$Vg_k3-MP-Z,d,lz"F)h02)rkeʘFzqh{^"h Iȑ.B".L-hF1&PQ' Qt]JA$YJH0t|]4>'›,nDt$rg"LA6S5BG:H9 _P51fPUR꽁bZGU԰09[)l(L&;$5W]ݪ^ %ܘiǼ`,uǸLjh^!:P Pr*W3&ZNwCBQEđvdl_vNR|<$ֺ.\/|ל;?=yϯn/QE(fn],+u^4S9 r|Vl?*LaC2y/}kTTzpG*骇{9B~2hA;Ysov7t)WYI7~ْmD=9 z~Op} Vka?ߦ:nqY#y m=vvxbpeUX%B'Gt~!nX25Aqn@cV0рp>ffHbA=S}|-6:T\04 倭Pv޷[(0>#qg@胲3y& m&9g~oG_ 5BBAz"AxKnx9*rt0sU:{pJ~l4&Z%^7=!c_pxx$@Xu":{"UHD&r8,vF!MxA{"KH9nR_}xm~q(Lo8r<8XkW7[؊3[qf1 ߓ3XIbl ؏дk=ܨ gNQd ? I@t@7gYe5'6Z$Ð'i|['i[H"׍3Y ̀g22#ݴA`ǀǖ1#'vbXz MT?J+#Z4WsCIyJD@Y~ؠY7vDo)L)be*dEL ZI3%ꒂe@ wI#V=Q|ٞvh]r^ƷbLXda}Iykrh/ii;!j!i*z?y}:z*D dp vB6Wp ?%"}?qA2b3j"Ŗ4j{jAW@zPժcȇꛭ6Ŋ(/CqrZu:cSZ, 䚥!Ŏ%YeUruY4 }<FXOxj[FBٔ4%FI'Z|ʢMIoYrQ~&*o؛ 9C{rњ 6KI)#:xǒ[N kn8$x^"J2:؋ 0xyD9 ? LJzG::LxiQRUE0;-cpqúwk:^_F?@v;;:jYg/z"8Jjɲ*n}5Ѣ tR\m:zښvj{{7'Z=wu{кVd8sp0:k9+~EYx(K[ 7!K僞G*Sb*wz-$9_Vcܤp:;cWchwckt/#V :T|3 = fX C(W??,WG~t*$h"zk"{lLn+|}<AһvbM"@K,[K€ !Rrhv*ݫ_~`˔rdjO!&AI)X󻹥k"y|ۿZ' F}@-uƞJi”lš"L ܹl!#:>:PFJCR4 97IL +'s|Ǎ^InGXi+]2R Πh(cOUD£l` O΢ MyѬr?ռ`d|+hGX֋5JQgΗ<XjHa⊧!`KМhn9C p sM;F+ѳK m`[ ī!7W?<:ƒ3(-p*??`v+x=Y5 w}RR7~Ռ*o ? 7; S3UML뇕s:bVx<x?NKscfu𼅼:Q] 2LՃmո°ZoㄯeZ@b'!YQ/wf= v&ѲŤtKbqݓY@ Ŕk8 Y^\>mq stF lFk4w}G ^J8Ci Bn€|>"vHr[ܫJ>{H޻MIDJ͇""f.02nƛj n7BQ 3"ôӪrm:ebAb74XdltL͜nJ~jL|t=:YXme6ũqڷjl-ˀpt,~w 717F3(y py[=zd淓)a㳪5:a gM[u ԓa(KIDꩮzNݞχ aXi+ኜPH(f?V뻽E1g>]=8@^C"AA//7zl"VXʎL-m 2GG4xփpFmlz NDHJ)rc7+E') h x @44aa#cζjVK)۷LّcEK9ܕ,sэSD}1iO2Ƞ‰$~Р5æ^&pcS:ˊ㷠VktidL@*M$m߆;nnP@L#8[" |o0?@D|*7VC\ '<"݄b0V ;>z eK&$#% 6ep)ZV1^s3|/C$AS:4\D8B$Є'D! Ѕ/a e8CІ7aEHCʐ?Gbc> Nh ( щG|AB*y6D.vQcB2ЈH8JN `Q! ,^[°#PxFE5t#G Q(&H!‚K *cĀM8sIC@v Ê+="z✫XʵΜ>sKt][Yuݹo}÷_p [WMUr >kƝe_j\X^;[6lfÇˍ ӆ! I8;߈%FR44reK2GWYӦ͞-$a&V4wWX4Ƒj<gnL0ɀ("!&!}p'a=YvՄaxaч dlA 8ҎЂw-ސ1f$:rB (D %`dKYrw煗zD#1 fPD@9цYWU\!敝ʅ]o!WGhAڢjt! h酋j_|}.4 T0TP,R8i’K+8 Qf,MkxQwZ RUpe\׸mW^{ס!fkTtQEe:_Zꇩ* O ʊhJdy?{k .ewCFGXHt1aĺ,.s[hlѠ4Q3 @zlueZ^ hOP2l1y 506{5@~3tp +uY&iKp,lc^wI]mwM&so+oIkx&(L>yʝMfy-_w@a s^-1{qǟtF!fuY].Ty}τԹ7Bo < niz^@\9;O$2sDDVPҷCeN8E*{"6֔JLC 20#=e9+!Xy8D)EQx4/â̲A E> _c3Nρax8 &э@%5h+֍E+̐B64XΫ<@4"*9JLR,kדRC %zw{ #8Vʊ ]dI{!3-2? isZHJm$B4~){Cry:L'ׁfĢ eȌh2bDOQZ ]MvBٜł$JHwUŖ+GyֳsMjֆKkX-[&4IcGfRCaG^8vKFS4uGK.e&7 i ^Vrh*-SkҢ)u"5{W_! CJĮ-;4F;9B!Г񝌓^:(L)e|+Vle0q85nƇ+@<gNሔ5GQ &6faTǨ91vzB5 #&. iuw16bhE^;5ᯠsЧ@͈F;@A $$Q@F@BxLٲ% @[N3|l&>ƓJm[{ݍ/Uӎq)͔РЎ C֎MmG#FljMC UrDARJ.oI15#19GvTן hȬVԛD74qncθƩ=d!o@}q{ 6d8ƴ@̂yuuj~Z^{}萋|>NЏG; j!W`{=.jO}g\jwB"LȄBps/a`'ehײЬBaOقvLJ\LX>ȼ5 ic\yҗ"=rhe{5s ]' v5 ɪ|9@B{26Nx@ў|% qs~xڥ=SF{eu|w mQux{v cS2,MiTfAqjҷpTs3w]eP%!DJtfʦ~yCu0'duwzuF\'c7zPu RR^`X8|ŇH,hI6TDR"f5>brCe%vT/,C\y' ExW8\wW׈^"_uNHhxu8i艞 b{"f(5T#%V`I5Vr(ЂXA]XW"] OaOha\'8؀8 ҅H{\8\hH (W؎Ouxw`%>G2GTw?7ɇ18hUa"J#UP]NQYts- {Pzx鈅ؓ>툐 ɉAHȔMY)xOK(P1 S5f@ŁK+@ב)h3d&&pt1&PPׂ#6}198aR@銒HYG]Ny腃P{h^y{)XɎؕ 'nd$@hJM7\U6q9YƇ]Oei/%'BnC u \xu)jA򸙅0YYɉKI:O)Yio* `:S a+m(PY6ĉ%Xؑ}4x}:a!ZAXxaRp({k.|P[!3\DKm:p iNLIdkqXLd˦~ic+XpA,dAl;,8aDA$>JFm$+JY.%(POXoO'*.1Y[&ɚ;4!餥d+nN F5pkJW P+pK4nܯjkkXD{ 7KX#*,Yœ* ~g#Fɝ.l,ОŢkʎc ѱ `a,Kk;WW R $:HLլǫr CAh1CˇzsPՒ#,߷_G5|J c{ұ'Ĕ)Ookny`@ @Sb ӻǪ !8]A'j bH\7׭}*R=&wd0) \{ X0&c-5]5=Ư\qlKq,dy0@ T-l!~]@}n.:I62&oC-->fϕ|`F[MU;fjۈ;дM6}^!|1-\ֻ܍$}ބ}0oǀŨjXP}7b x MMٵ͒˹KP`,| HL`+dpWQYq*.Ǭi,g>&.'ݠ.5Th+C0,=>owjF dD<%%DkA%<{UC}__~d^sNlf&mEmn$na6_is2=,K76٣SYʨFEoc5"fahSdɦ>.fʅ[&hke͞ǙN <_OViEQTNo.5aX]zg`5Pja9A;̙@W5GܜlШmBq^NE)F(ukǕ {hY'!qAO쯉U›1oh\^ Os:, cBAoK˲%ی91j|oIN2X@;Ь~Z~ò4zAo1=H_g_ġVOgA`0` a [Ġ+.nhG&L@Cɒ6d1!@hPӦ7̜'JAKzD4{9,QNUQQ+^y뙳MǮUX:q}[Ǯ]{3gB"{ڕKgqϹKw5tbr~RAQ8d $J+DzYm>W& eY&O ,KsC1r4qۖlYPrΊW4߱ؽ:-m}:,8!4>cTl."o , 4P`:#{D a  j Fiiz䩷*荦r2θuk餠"鎺.;7j =,Jë,3˸./>T?sC,Ͽt0/,3,*jD@NDEWg1GkqGQ#[Er&xL(茐,31uЋ;:L?,@,fSC-Oltop Ct5EpA3ktRILH!8((qd v@UUc\ 6蠹rE(KW:jʫ@ֽ`UNS)yfgY]er{grwyAu J- VO/2X:>hׂHIub8 `z#X (kLY+M> 7> Ydŵ9gs?go/wdmF@z=jཋ/CE Pxa뮇ua{i`2&^ (@"[cPR('^ƶq> W ܏Y5wߑ<3$?h=347A@ ,FfûڌJ7Qpb4<"mG3W1& X Qe @ a8C\4=S>f\\a s1k/ؿ@)Ar_ Tj x6$yDH.9TTqx5)ێ1B P*CBXؔ&2ra pEѲ5?q 1GR?(s`Zx7@[5)ܥ,8m0MT5+%%Sx$  EF3{iC(.%d){8L~hx""Inl:6uh'm5rܜR -oٚT] 3Et)vclvձ2*<qb+fGQ%@7qIL+hFSQ=聥{ ^ΞVē` {XĢ?t#  -tV~T`&t;6 U-td-qLB(:WFnwkCDʐ _^uLPXFW.EY.">hJ|H;Z4* }@-_Vy"ד굥@k+40Dbz1܃{3A1% 4:0.$!ERL 7H.Ml©(B*l< [jb 5| 34C_\f4k[q;2jʫT+,0 ()) Jl,vd@K/GR4J$.G(pE#38B h `-1,0C $_FHȰKò8$s7|쑋6l'nl i̦0iI8pGKY{HJDJd@Vxa}G)@MA{h/-ł\HД?c;'P6e#˫3a4$<ÐKdK8CIhI&˺i0s825ĖL)q -$L$81?,?a ~1) BaM쑭ʮ/.[  D˷ ˩cl \_,Ltd;L=fN$$O.IHOT/ZX ʿ?]5<P ,[Ť (O4N̐BTȍZ֙mٖ\ϕVIi @ ;O,h(}Ua^cta[sfE)HR,]k]^܍EMM\ʥ-]xݥZk=\mE [R^R2`=ڥ]\-`M\E xDLaDO܁ȁ/b-pVp[2V6}.<+3b]^q̩DMRJWܔ+T=kkb˥'4a.S |` :^e&]ѵ&Ba iaV/ZH³[XՍ PVՀZА K~@X 4?+fa)#-Xb[[G[bmP*V6{W8d](56)V:EY:Xch.i-e vhO)q4Gj{: Hetͳe e ˃95eU (eSFF)b&H@e}ʩDܔˋT$2 Mqb,@UkS^^SXh^V>PW JU_]z P2[^_d%PaL*]vK$@CaC'kl#]L^lvo$PTlF'ؕv%@˞(d.<(G (@qmTm܎R EFͬ#FVhӃØHKFM&ΤoϬn'nnh&'֙oo o>o)v r~n96GY^0mK);M=JPՒgz <ʣyѣ48<tT DLv'n,n\%%jVNOG.V_^+*u!w_0ufPZt'HR'Hl&%p8+usb/aP|4L/a3^m? 3 Wg}O'oMۑ8ՀTbV[!u"&^ujUVx ,xo^r_t,vވm[罁/mxwjᆿv/mIJ Hqw3w~k fRehz8'e&Ih~vd+(6_nu}(u]W1's*2bx,O|{y'x| 8yǏ^_a˸܁sHhA>J!<~D(,|@%T<|A!?` HƒE+V9hKTU&knV/5R$ +_\1S&Svl] g6 C4$e͞k];%4菋F_" .ȓIs4E~:?R< $D 1Sjl`gϡW$Q<.!M$B 5Oὤ *S_7tQ@C 4 SWXuj!fVf~hÄW]J(ASD&͈ĈU~=XalZWf PF S$GFsY6r"m 1pFtE*~$x"dB)}DKrj@M Y`AVOmx` *HK#T*U`yU *aYN("^G"U/֊"U?f}AwzT(xVSiXFH?զoSPuQI&9.去k7*~kM ȫP:iSn{5*a_l(Q2jc:dTp+Q(#!񪮺ZiTfZ]>ZZj#rzZ]H dю214LnRNW]"quPەvhv42Ǟ{Sa6畠uWCW@U֨Vg?ö,]6ǕG^WZ9W[Z kvfvk7 Y!!W1 (oXeDvXQTPyWj6eG sJQT0)b_ͶiߥzU68\Hfb/cG㎥Wf\jR8[hb+g\-=+dHuEZWXdd$" `,(r<8< Հ==L*'$*qH0aB LL[3D0$&BZєfC`2\Fi 87CQ k&q >Ѐ#HJ2 3Ü5#O>['IO07a(oЅ!OCcВ -@h`>a#S"s W8)(i*0jyLF3Mg1Bx7bse.JYXeDH *T (X=Co&1D?lLh)K-(SpsHK E3tY,AnU.cX:-_zYͥ4B}7sJ_H+N}(rg|J| تAհxpiQh \gmRkV5!=椰0!ݓ -ꤏm?X% ecÙu>3VV*-_l|w㯧 *+HA nHcO\8\Ή:n{2=9sg{5y Qկ! ݹXϐ%49d(7V* ̜dBzY6' "//(CyP0a{Jlb lL,;ʁ䖶̥w#$Żp5R̍qcs%~i:'#pRYe%j' RfX 9ydF>'vჲD|:kU7/7pnOܶJBgВ5邤u:XZƴEf29G]9ͼ|'զPW2VFjf4:"cG>+`k c`afT]6HoY=܀şdF4,Avû1X.L YlD/2hUk1*rA@D[Yr!hՐՄKI&G9VTd.'ĦD@Dp])~ҟ'#$zI| <ƞ IJH0^XLOb^ge:cb[Vyf'zv)}JB{iFf&Lf|)e Y>Y>AtIb5IKGh$rhrQ_#hfo )h[VJxR;\ACRud&꘹Nʤᠥ.BRvfwngg[>$Ldv§{'j>+&<+)WBZ)d~%vk)Lk&"'%+XI+(xdIu 0R LN@R}|{ y앟gbUDLܜ0,&&֕8iBdbBfX)$|,*Ffie,&"%k&k|VVmR>N+6>v+)Xkf)8^FrM)#jޫڮF^"bsLnT $ DMB۹ ڑ^(h,F (ryN'nvnbv2,gmld..22-Nkzb+|))$/%8$tm^&*,#vH LqH#(TGRPE@$ @,!P.ȁEgF&rvc''%|b|Jp|r#.-wnnf~fnF Ƃ pfB%4+zpJǷ1q2@U^ >yEԯ^L4kbc~nF'&Vl_p_qJ+ qӚ00 0"W-/p)³B$)##A C._@$WDef_Ę *v>g$ln$|gƮ2's1c(X0цzn! _62Rm"p {#7g$/r~s)8/e,uoAhy 4 XM,LzK,,'-/740gJ,N˂)43233F76m%j3Jpp3"#C9s* .B3AF>oElERCs4{&4/;W5Gon73(|e*+B)l+sHonH B*+#^u09u:0!3-]t2M8 +o)8vƬ0(WjQVg@:̚hr[:j;t0r*Ă/v,B)4sX+23&\?6Y6 21_'7J627]GFwtvbs3r""A?h$@4$D.7 %WSfh)j,j}jwCsnrN5mb43..ol϶Z_%|Ƶrkt.^5ugxGH#_v3#+-%x;7@h  Ɋx[8.%`x|xvgc%#z2§#7w~'<6+B+o?wuk)py-Ķ/̶8Fn|^^]ouHK 3/Vo'@TAv[v'~FaF/Htzq|R6oz[s'|z)Lys xr8{9B+0Z&+7-]w+sr~HW5^ߥ\'e>BkˁYJi[zj)^^ד6\]5s:Gt3{+zC6pú{y;mvz_xOw]yaK-{F;8(L\HZsȁc"OoEh/S02! ž ,B1ڰђB3sz)B3op?8:O+/5[^bSWI6'gS`~ bpjR"K;&#剘0rp.2I*UtW#'9z6k)RĔ.'x?0o9IXҏA׵7$V[!뮼¤0SK  PCEllD G;L;l"(Z(pa7ݦFZZQ%O8d&')J3CL)VJc'⼮!jLC IHJҲ:Rdbɻbzۓ#MO4dH"Y!kB,8pQꑭKl$ϳ2lbM1nZRH.%,9a/# R זOڔcZ,?ȅ^rCl Qb(  t 8 "HmwCTqE@[!QKl=)a)Ha e0^v\"02QSG>_w@7R eHn@! zc45)صю`AIdH HQԌ-EBh `;T! UѲF AA,㊆0dqt_޽"73]O*oXi hk4doxp *)\`<@yT+;! AAz`?NCY!ɹL ȕ ܞ =y(!*/8SK߼GrQ6VMBc fnb ř79V,]x1 HζgIdɄّA IHBPL)?{`xvc @ XdG4,{rZiXrK ZFB倔).Am nLi~mP&ЂF+"M(Nj5[rLk(A& ϽRз@- j`** j =#1l[b!h)%j˾EB$"!%%H3ePnIBaxD!rdl@8B8B`} ԰m,LAd@5@|f*#%Bbv˶0o ,zN@* 8`GJj~ B/D2{'FG2LUosfJkP&ΆP*E)YA1:4O abTDѲ$ݞ+$`1+ LMhg⸌ ؏ ` vp%%Dk0ba& 1%@+?"q#oH U(I0_$1-"\k A` {bhn"r:ebjأv$v!"bH/ \ # N)"Ӥ)ܚ)4R%4_*Q @p41Sަ-h+F2"#P+=Ȁ8U/#!--BB/thE^b`B>FLN +\M2Rl -2N373GeXb (.Z.^3jp b&t@eq)glj48(zф 2TCGC3BqH1PK,3#&h؄(septeS#G4>P QSeU>G?kiSʒ TK D!:yH+ A'A%LEY: B,= !gRQ(S%l1MHB#gvpgOqos"nw3e|tPAvRP8s5s.4-_1@/Ut@{l\4-B132Bk9憠LTNtAy(`O\Wc")c6>I-[TUSڮml*j n*BD3Cv)9&v"Yo LQ8qV0hvJp-`+)he<]={=53)_aThRH(Gv(eQ-``_KUȣpR`uhՎ٘Ԉ)llB0*AX' cЏ)_P\S*SIQapgg!U%̕5,#[~YEd fîL.:VXKz"cfHmnlvn\`%)M -A--_274|-Puqm37]!W#4BRnF|q1tm NW/Ek=-k$ŶhӺvwG&Ae;ӀχG׺^hgPlpv"0gha;À1ۅi3.- ha4h\PcvxUvFǘG|H)-Pj=H=,qR}k ,W\DE, Y7rӣh5pU$!"R`Y3ar\ cd%4%gJPCG5yj–bD.!r8l%Ɔl#[uWC-l1!7mS!hx~? 3L*GP8s)rP"ASkGsǹ'By[+o1'fM_6!Q\sH)xxJ<^Y\Cb,M6TūveC (ڈ:~N v4K CBT6m̭1>Ce膋v>Y(9%t[037a)$5(שfE(a)J<{m#p~@"s9DP!o'Z]j"&(# bu9H #(O $ {:t>Sn,{_4rvjٔm$57[zˍ3n{l뢽ɀ=[ȵd`S%mq`}}{ 1PX(8{Y:fYT {R7<Ǽҥ+О.AعXSߜS=Jv{s/y>'r-HSfX+ =Ƚ#xѫ}wY}(:ÿ"M!!| =,׳YI-,nWB޵)/dޓ_nC9Χ98EdQyoͳ',x7W۫B}4]~<¿Y/Do< QW0%EF4%29)@-Nc YoF $%^%ŭ7|&>S*TLqV= =fn1"GR=i=vMكi]$MCDKssqaj4?PNY~\Xr:ە.E!gvPέ;x[QeBrR>?ZSE=^ Z&P&M24(O0S82< =„1FL:v,ǎJn U2ʕ,[| 3JS4kdN4KȐ?KbEcD5*T@TJkիQx*'lدwJ6IC2\6݈ݼ"ݩQLy$ 1ŕ;*9A+[,9"G6'^ :bRI")tgCy #&v2Ӥ K1Ƣ}ʔ2J6DZ'RC5DȥrjU+\=h' ?>ܺh~߿ np&vJTҘ#Yu0`v!iBJ*|)D hQk 94Ei1!<%oEWH$K%s6T)G?7PpJ1]y]]VVJz^fT{G&|e_^l#a XJ!x`(gfX`ZTr %'kiVmX#&`4 Eq\(JMH:Wr=NJvPb[֩G>VEM!ቷU|XZm+tr~&`pʠ().v),ȉ-BemnEawZؑ[~#KM.mrur%G,5ı) wdW_$K1eHV[& W̪%q ILy{K&m7.Nu .TzEwq4dTȤ0P9y[2CgR18 x4*@xj4AFD&DX9S? Xũ PKY*q[ q,X(n>5-'_|Q5O^T` 0 V4׎LN:Du 8*j)fIaE득w5!tFo`贀Rvp3d'+λ$o|tA2M`'竻\?I&LBd2l||g\.[+п2SEٹz-nw]xb+4Nڄw,Z V5+T,ߛ 㲺,XLlbʹfg]Kį'yRDHBm{܎+M;rRyz<:',esgXbrj#_~b$)-#( 'Q$` p1 zV'$t*'XBqx:T?^M_MN TGlDz,&D|47Wq'GG6ʴ*?HD?xBwjfCuX(t{v`#I6a p83W,JytfX=gA3HiYZW\1jY7 6{:dIj#Das*+~Mm8|X 1&QH|FgIFaxwA+tIl'JP_kyքw4ֲVvq-@Ňqpr#1t+;M|aq0SwUfȤ{QhU*OfGId?yJa1&q7 u>5Q*2Xtw7¸0(y3T{(} E63h#M# LJ8|qQ!  & G &s>]Ví#gcQ6+"{^6V;ubD9Wkee8tgS|kY|}1F9e\'^#gX}M)q`Tג/n9:!q1\ \_ّ.as+qh*IJ's@{&W|)Ger~Å^!Cv3N XF@06|eQs*ny1YGxGWL0yBSga87q>ɃBXY9qY&Nci֋KYN{éZ*m  )@oŘ8wv՚snpVVN9`18YOy8D"{?2|;(0Ԩiw{8&S5nwWY2zYVwLʷxY7@ *4]YR8Vj NpB9??aXJMi w:T&1k8fi꟎)ٍҠɒF9"6Bbj(fASֱרW *y>Œjv}96i)rQ Dh%9iS7Ui~]cyBi wJ{ ꬕՄ-6j)y9;ڣsЪBD3q  4u^u Z:63V \@HO۠٪ޚ<|Sȣts0 8z9Du*Ki5AgB7; :4niۥŁjvsqZwI`z[/f^ l4X<N$c7:Xi*yjjr[> Cy;|KIˉ1yQxX{lW(ꨀx[Ɵ@*u:xv볬7A{*ѷBFJ+SG`뺘ʲ@ɊR$ f;s밦 狾[[ h ;g[)k+i2Ȥ8˽Y2o HRr$'}H1 7{Ge;p۰+ŕkz-f5:#;%I)8زqW7ʰe+3 ļsXimJU,X)'NQK&%gx;2ƊZ}]S$S܃ksĥ {šOH4+NŏE^RI@'+0ȾŅ3ɣƚ M;u w+s| si_-W5MH@L^䴢lX_lD6Fe2-:?H `P N-S}4W\\sŒ{.rZ|$jוz cwu| k1Jq"-6ÒZ|= B e5hS+ȖDB… "XPD :Ԅ <~PJ$MBɪUJ-StjP*TS͜;M֜sfP:MRK5UtΡU}RJUˁ~ZM9irhUmWI0śSԨJjaF/"'̸#H"I\23ͣպ4RfhDuj{2kR_WSkS'V)Uu4(ذeMӨͯ ݶM8ΌvN JiS] ?{h##Kje_M2 OcMdtr5lt#Kl92Ź碓n"ű#(&fH(ӏlj&$ʴBL0JƼ;P:,J8 j:*nQE/n,H s !B==LhL?EH qʩRi3(LUK@=nMϊ:9IWUQ!bM|H^d"=Q)OVap!OٸҔ+'fje T5 e{sUVY„;M(TZ?U2|5!7b4fgei$~HϺ{Yqfj.cs1tvecS9t)Y 8%VΎ`cmMF؆ovbseP]'; yO#C'Dr 0ҙ5 V>v~"]e12?ơٲЂ30r!dwѰpW8TP{ M~Gکw%" "11Dp ҹ3Mpu&^;Tv#'n817kW82H,R*[EYK»0I.yֵ*!< )  ύo|4jV|+-{*c">AnIcRXRR'i=&ZQ4㸟J3}YE+{RM@-8\ L# G4`qKVF7["%E-T/K`.Wp3R%A vKBgl5ˀ )X=wQr`"} |M$5Q""caP=|V,gpp:G%fBD$1IբH#B0NoRyЃ}giu<@7ц!݆IdհPlmSOx5-Efͅ@XjwI[> %* JX Kk03"?+.59e%h>|Qac܉^:lKVk6 gzǵB/zH1z/)" uvyx$~69wsJ6MUxp!'NكK4ĵXW_nDHJl`6/~xĺZz._Dr&ܨRe 0m B&۶E٫ )iFc⩀3 lr>wq_H7@0Cc9! $:Brs@l<|ꖌ2Z3ɬiZB*[ 9S0 'H`|">я)6)Q Th%C!6Q\h\)ű>[齋/ĄP=` /^ɀ/IxoiQA.9T;ri>\i2,'E?D dVD\4)dD "L$>)ȷzM0$x/HI VLW4+h㊄`hK`("@En8 |t4u$cxhX81_}xl~RÅEȖ]@A e߽) X1#& LXI q4A' ؟bn !n(sGݸ$Ux ZVNk~uȀ*bdaևEY =V_oW(⅛FfƆjdܠ@і@O-) !VՐD',+ओ9BKTv@OtݖmPe<%AMUi@@{{!ւv7GiViI~taaןoU(]^ixeHV^bnfE7fk7D@cD`P) ^$|EE })f֍iݖi`4U t& @MtDUkT&W鞎yg].z!Ys^f&hziGa/ :[ B:EBpPGC <^xn>`r9A+&E-gJUVqZuhw ZFݟOWhӃki\o[lw ;L[ 1\s!FUtc&l(D J{B NBܨdL\? mh.6*>t̢g"JxYdU d ԁ ^ gaZ" e%.$g}Lt6;Rc @3@{(H+5($%i5Փ޲s_mLrż#]cnfCTo#SW Z=@Pn,/xҫ[Uwd5* Rmت"0=g@ l!65}ސ Y_"yةhoCi gg/*Yf\.lE0p8+OFDdSN/DBKV tl kɤTGS@j&qX;c%yHg[ {2LrC|re0Ju=L^vMxCqsɖth}0mڣ d=Q_;+і\.KLkxZ2@E}E\(ۧK!j]t k9YrFu- :oW10JUDCf#P*Mg5RɨD,8/̄=, ѝ\ gr%ƗUs-+G K\RLj`"oe؆(ݧL" 9x|)l3Ʒ(kӒ|. 3G$\/Ro 7h[\TJ.fʰ- 8I Pe0T W# @y`{C)%s@Z9!\ K,@(ydzoA;]`ʹW|y'\p8Ε_ObaxaDuh|DRD=bNwCb"$Rb6/D06iĚv2,dAlf$CXPNdb&됀J6Opi=u>٫M>T\ppv8*{` ""|s|׀w|w|8rwȀwWr"r;w]$/@y4<{1[s6~#"Or~4Ht7Oy*A7exq`p" xm|P NX|Uh|H|[8 hYHw0(!#$<2gFs=t$P$n6ч8([!'q؇E@e&C+{JL4͌X}WmԲ}N]˲=Íq ^"=;P+ƍ _ }|CR8"Qp2lra =pݮ n{ńdi8$|S-\8{Jgݦ@Ӈ e0hP\΀\RZδ}n&c<|Pݰ`n!1qx)a(=];o=]cP_삎Nc|kPm~[-#v{N뵾;p;qAlw{1 S5%;oQsl_N-]/>W5NH.- 6]M,o~LR[v@?qaapCجD&#SԓIWBȼݡkـ, n,nX.Ȁl6.o>ռ#`݌p|mz!o8Yz(t##/pԇ`}%bno.q.u/p1\GR_=^z pwr.ۨ+65d@!RУ/5a?0!AN~ۤԯ}Xj$XP .H:u8P=3!F!E*d)5^XbD2,%Zd /] bQGdEEQFP 0plڵ+-b G cɶX[qv]y1X!B!4,‰C0ac v>F,M03?R.&MP!3jB.{>ԭe=}:ڴix (ctIQƙmؼCafSSVUWrMKVZ+ضmrX&>/_ ;l@S?C>B؀z , 42Bm43CmD:J6B5r6\ZHdJ.C3&TZƉ~ F樮531b!<h`nj`,"kL2"3 . @: ,>`A+.Ðp fC3ϸ;:ʢ*b*4" $&>2kJN&h"%*hĢ,HI5-M@J=ʨ,d5+kM6Zj@? [;CArdp3:Ј"46QE4FM kUB8怵:RT> yXaj"/_kQ#) J,J#MY S3۬M6 w\[҈#H w[ P}}і4vhzNat:A6{avTGn[amH:Wmb8U+1UU+Re]EL@A»v ra!4KS}p>x<~>nwn{nME!>5x.9bo \50VC"Ѐhs ]GXB/4(!@dpILxÔmy^ږǼ p M)7xT XZO.C: Ox8@I g=|*&/V`%V k%,U =SP<UC/.?dznfe+ֵ$\K0$A 0OiM] I±}Dc)H Zlpdc-ʅ){D0;j-taz>TICICT- 舁Y@7} ]y(sC!H1 gHC=yO\є[ܣW9 f`byЃzt#*8<llXЖ6(CrfJ|YBdzZPiRL\&T!@h8k\x L Ez>E"=+qЧRPk%SԣlSڴBZ.yyHc\I%'u'} 91hƅԍi.$ , ^ 0Skz(f]rcХr$VMhW%m@5Yb޶FIHiVdOp~(u$ڀU}S^`.$֞rتbԧ ۪ŬU3H^VaU-J Rm-%AGmkȔt\W 5aĻw*T-껐0w+R"e/K}T#]/{]LZ #0F+)H78U˯2P.suj̑`v@ WeJ!]O 54]1h]ȪiWp,Y=^,AT2|PH/dEKns@4'eLczuBgCN/(0 7S\rOS" L]Ȇݝs!V䝱c9@ÓLƤ6KZLפ?NgZRF'InOWQ@C=Rgd.W|>0ײưCkd2n}}9Mg4i  (ptdYrMNdLt;n+[/^~Ϧ=(ۜ2?ᥕ,iQYb[lKH27S!bH3f{R9h$Xj `yb2Rgszv:wϏ@1̖-d]E⥏6bNk}i˛AUZ >pNM ,T<x7cNN!k@/)=G]~j_Qةem |}' fkwO02vX(EN#7Z|j 0 X@ӽݻ+8@<@8@C.)VK>K=*Tks71S>{ j,4`? . rp&Ċ@ ,"yx CB@-<@ )h0(2Ԍ@œѫ,;A69 𪎊t¹|:;zJ3tԚ4;@A) Ab,M@OD4@0E =/6dDO%@(+ Y)x; [a T -;{CK6z39ӷ?<*rFC3ۃXFT3`I*dGш6 EN )xE!x .+ _ $1bLFK @ ,8TƆ384=H7W=˼oT:#6&R 5hD tGwQ%@ۃJV(G#GJ* )2=1iB@(X#0Jʇ DF ;C<{JÂ.*]; Ib)0 I%hȝ(8O(PL$XdHWD/'d@&Oooh=O3uY]V)xh8.#@̭E~=nVKr<;L5($,N&W: ʩD,A!X-XXQBUUdAWe0}PZ-CӰՇSEV'`-KJbUbYf}eaPdiUmم9#hȸ)mTۛBo5"r=WtTuMRTxMos ڪ %*M=aeH@ ؃H[8[m۶MXX3PUMӸb.]VVۓmU#xٳѵbU.ӄx ٞ%2(Z4ZUI-EwWyo|Y\D!0صu[]U•mUe]mYX5-^MXE۴5Xea޴}$5EXQ^m[R =Xe[{\ӀdZWWxlM_B#TLe@?&t{R,ғ!B)hHCU\eQ==#LaU[]uծVش r’$u8QZu" _a&ײ}P_ЀPUl~dȁZm@[. yZP%nWK.%XV$x&'h9fU !|ל_[_yF@cдҀf623QqfVo~i˜O `ceHX;{"g=㢎H(ץ>m) na|"ͭPN@4B# n-9\9i.{ */ puPiW%ໝfЗfTDgޚF{llnVĆXVmfO Q8m5ZmjvߋTҶFڅB&2َJe0@ k8m~5O`M[>5By֓:6,ŻbhF'0X^kc hF%o&iol:음;edyoYNFO&.r_s%OVj&`_DѬQLn)Xd(b=#'srcE'@ۭ&eOl%`YVo0o~r9c.m&LOpNp Xp $P'`*'8p6O$p&ڞ_=(~ekP8 rXtf)wk%gM +8W9[4eF5h|M>X'8c)GarWaQ4O)_4thws&iӾks;7W$ps5gs?w➝dkvG9b@J' Xk.'rnnEҟĘ6_Fu'-UBd.$ fn._B^_O ^\U&.a2W Ҧmwoyvj8wӦy7yjy 8myρyy _՜ZHo&z*V{?߀;ƻ4ŀoJ&u~hI5X!FH3@f5<ywva/Ey&TM:vnw-q~vosv8yͿrpF&G+o_H&4t{#ǻmf|ɊmM!eφZtH,O^~E@P-p`.zg~nyŏog+(p?s| k/9n$`;0lˆ;~8Pƌ ,X"#԰a (^h CF,  Μ:wR3!-_%4pAÃPmd_ ZւbŲ({i 5n-ܸqЭk.>c{&)0I@1 @(r +P)8)@u0dج.q%*c)0Qc*o!󹑚RtrႳ:4icSJM$σ Be O>}:rjڲh>'R{#` &Xo_Vb &[VWbd!:([m٦aJ81h-RA)j%ate#C?(P;PaT HLvinhhvx5]P.d "g |}rlE~%A|$'yLx`  *H iHI9%׃XX!qZihbl{cp "FYi,Rw{G~AŌ4*KC|X\>D]2*QבI*ya.LoCQɦzO9oT)S@ H^{yh|REUx bp"> UPҥ+RQսZ22v,:+C]`N4FÒX#Bmh(D*#Q3qCWFU[/!a.PnVlL/‰aֿgy?TSygZ vno䳴Kۨ]*"1y\gQ8E*X/ˬC̒:c֠V P[N栉Du4Vk͑-n1]bڕZkx@ Pi J-7#lںI*;>aݿfEBِ!>C @uX&%&5Xt!kՅjQn lb#ו=V{Rn0EjRsHe0q@}RA>q@ b6"~Pp3+4ͅ4Յ5/`X|QUBfUHA>xjw9! ɣBŅlқFCA`^3 zp BDhjS;۷8jQ+5E D%+J !CVD6 @rfuddlQ(@=1c\@ w^iHmxecޞG~+^0&)ϪTM@6}solJi n 65'0T "cMA @Al[e48†dR%Q3 CZ_GE]Xt3#f DSpaXүwl'TԶD)|U$J"T6 :R٦ZLuC*5Ų%9&Qޘa˜X%{(HR!KvS2*\٠%׻D=Y3*Tw)$ t]( ".Nʠ,nx-iB~Ul]:~(żu1Qј|XcKĚpzPI)6.:A7ɫVڤv@Yf`ݭ"*W-. 'Wdɢ (AF0C,x8,F!OV o2dل^+d='QʥR,(kYYnZȺ'a`JX2sE`c5$#4>bU Su-R51q2qS%(9 y(r7e*TC,[sA ':/ySr>P ޲.ٱjvk3Ԁڥim*碱%#O C'Z*\_襮IXiȀq;n>7}ʂuy+.wo,9 tcMk7 X]p\2]J1F eRQC J8hplB QtƗ-OrQ90h[dKANT]]?2ֻZkXgg0>FcE"=R[*$ )/?JLЛE@Ŕ7#$UdK7}€4PI;^ fA&&` tSeT\E H\QY$G}ȅʙ M9^{DU!D@ ]ƽ=W A  \vaAuv $`"#!>B",[ADYJֵTɐedQ > O `rhEW% zsG BPPkl Y+n( MNupݞ,p"N,f! a-@X1B.#3 YHPh̊87vNM#u`h WDA0Z%U=бmq"UTy U0^R^WXh4_ҥi`B&H"01͚"dHIG$K_@|YGڄ՚DɁ[ABJ$`P>E"aqKTu5CE޽c#O̒vT9&!j@VE(3ifZ(n&%i%n()j)B )B&Ÿ6*Hbfdb-qA(\uD[)qUɎ*IΒN0|!S()_9)@" \DGNN`h<~"#Ʃf2j&h自).()&DkJBΩfnhhg:j+ j) *+֫&j¼h%r-lO*&,@rCmjDa zڣ㝄w k ^Tߑ|&r +yK줳.,cjff(Qj&3nfPlg)꩞+$ІPi:.ګ:m+L>-P3惵&0,0D R,Utuzn $L pv*FFBRr(>f),f.鹆g*"-Ӟ.ӒB+"jZB,Ԯ.e.B!K,OVcu$`Dۑ@e~عOvFavG"+OBd%glF.J.//&`n&+g.ꮫkR-RӞB --$X#Թ@@2G2 Z]88--AZc$Rno&zhr.Һjqo*q61k*j* +_mgBZPm4Xc*!a)ۂ)8k"ˮk붲! &%po)g.(bj/;qjkkO1)2)Wqfqo1*s񼚂,r,̲,j&<P!"O{mAz/춚&3.eڦ pfhf4W 'fnҪ'7r6SrG֮)/'sfrrj)dz;&pҫ-֮)HB@H[4VD) D3he$gV35[Fctn((ǂ/t,j#s~h,2(B9[1:L3;G2B<;)#=+;->;J+!A hldq2&3CDqZJnX"4KK''B?2+Z.+2)n)(t'M3`sPP++K-P2fs>I2VɬA"`,f읒&uF5kg3+KӵZ;-L5\5Ht\[r5&󵻶+L2`` Ou'+Ӣr$& [#(Q:j3^5+LJIq"HRhiFsk#_7'jïzmv+l?r2+t,5LG^?ҒrLo+Nk(rsaG4* $[m.uSkw83fJR AhGzۦV8jc{#yGoB+B+wBKmr\3_Zo9MW)7)%t)29+(h9?Nq.ˆˁv׍kr&;4@Vo9ޢ m:26wb$ks*}/Bs'w(5sxnc*5sB&NO^˺rkCq[z$ *9C8IOcꁝ w/2쟛W Q"~nh"mfdF[9ꂂQ3cn:nۻ)*'h96{%rM+-$й?fSB"S2a{/Oz p w?7gk2.&$SqT")?8rtoN.e!rsy펺vۮK.خӳ'\%xBw$K˫$#T3[|2{!@G|3{3?kw<{o=PN/&hr#A Fȼ!f[3h{׷46E-+}۫n#y43P=~H'LB"X$h' ApcN2:3Թպ3? p$ķ=;c';;_SCLSrw='~$ @~FH~j{&DL*4x % br"D #:d$8v\4@R#IY e)'Kt2+V|+V+;s&.B6TVB}rU,V sNC@Jg*R>t!kѦmv0OJ)#G<]cI@Zyˑr4vF]fsfN5EsCѣTbH$Y $8V;Sኻ!o UvpDL̚H}Y )?[ʎVZTgt&3fR};Y }ѓ4+?C8ˆLR體zU"J$0"4o2M. E:,4D3 5(i5 L)dGfdF`3IJ\Bj7Ú#:XVJancabyr產:5k%YfxsNydqn<Î8躑v~NykI;hc-52T@Q%ocnPUU!>7(A L|"ElP79g}P`WBוD[& U抡ԘWYtq-T@mkû!_! ՎDV0! bR ( :B@Ѐ%KmNGY(exhP0<pБKLȨF%Vb#%)I|bqbBSV"<v n c =ALh"p; $tEVL9넉%20O~>tGhLLє6~` 89"ryOA5%()mB!LL⑖d%+ɻhmBD6\%"^Jpy[Ip ;ʼn"LIn"Zd,2L.unEFH44)(y71ͣ)UE&g9IC9b!^x Y=EA|Zc"dGD"Z2p^PQX z%.͠+D! NP@+$DHn7P%YILEpチJǔdT:AYڣNuzrdȱAb+"L HlF.upm.JX U PL+g*D!.Hz]-2E&֝'y:VK7dLh]aQ_[lRu4fAq8e 4LPJ@m6'-s}>_$J ;JK(ء3t(`g1P d =p$H|YX6mT\ hmH)D &MPL]Tq;Egń!Ҁ'lC# '7 JXѪM vQJZRU@l`[d =r5+PA MBG}$|jJ80ơMI8p& '=4;n3fB )ĔwVcY±Y2J|ƨ8et JP'ud1yp߁ ރЃ`.p WK+p5k$P d IC&g, ̗@*tYqF7ј[S\'XjLm{ ǵl u.h3a>1ܯ?[*w)EwȺڅ(= Fp T@_0l@ bJVf2<+Za.x TF\⟤ Bd"%4lȠk `@ ˀ @ iDc"B#`B+*`؂*')n.a̐BT*20„oN%HdFǬg \pv/ %# Px ˱` H ="Ah NcTM=Vp*` @~g/"#npP  +TeL)i>fЛ~ V%k+-kq,`zP *6MHp &aMڥu i}QL& iΣ xQE 'hR"A|̬? R,b#<#Ԃ bC1܀'{' )oRٔ=Blnn>>& uq3 !e!C0d!7lbkL%KHpr#QB>RXJ2N"GXb8B%%1S'1=p%LP'GnD)#k@N3+0EpLO]fBL!ض̺RI .hU.$gp//fg$$b:"5%3'_r1,"*fp0mC3 44ŭ]jTZ+6g$> !xos#+.j'`BF$$aLrSENᐚ$;WB$TG6R< -x%F-L=M*+>/B31lp{P +17JIEE6@=!ARA9T 5Q {32 9AduHCbpEC?/_B#xD <ɑ/TZTD?# Td@K,Ldvs**@ Bq "txL:#7DtDDM9#O $W4TP 51s+$1KRBw.R3]†4'0ÛK?pg46A+ T+OR !MR:0- "A%U#wCT.씭ڮ~f`KHXu3doΓ S\KXL){~[9C,wq+Cu7l.2H"^d__BvR'pVxzLQ2a'Պ,P;m#@$ *V2|LZql1 <^5>Q!p\V@U"E@3hgbpq`'6di NEi'j'4GC6 U("ܢ'VmC{5%1+h*S!ذehT,1SN7j:޵^3 +#Y&W :!A$7rr1w;X9''LbkEF#6 }%f#^xpm3Ł0wr+7찅2Uq0CA,do"0p/t  ig|nbpX(:bOgWkBn 0( Jk9T 0I#N&זo J1 W7ݺֈ5f7"HpK P5Q-#q#dRП\sXo8ʇLB vn\Q0 #N։3L570ŐؒW|5+?,"TZGRxzEca(7N}Fϥ3 ^0rwXbrgRFS3Rxjp"*Y =e'kR1K+ vјf?(Eb.G8w\pY'/vP88L% WU~ĀcUuUz:K-Iy'{Zqz?*oz\wZP8X+Gd6>bG={,ڲu9!Qd؄VRUUM{~L_MД]%IdML k"l5Ԝ*G9@nтACۖ zG7P1`1c/,;P- (sG89.ı/IJL!! -5 08u[^>](U6gk%\!u |Q#!u]!F$9% ~:}Oj815ZS_ 0x<0/qKp=ZHH6Fg]dA_ s~ tf5%r^.OOIA0 j}GzU_Y]<е0-].S44c)WR<СCIS'ϥLi&J&-ȐA|6XnzIlٚjVӦPr 7޽|k*} ISK-1ȜMNQEC"< ƍL)}7kn e̚4oٔ)&u\2Mu9iH.e 2itV%;:O/GΜ;_8?_x|%UD`$D"pe ^8%c6`n cIii"i%k.6lNF.:'p* 'q6QZȥRI OJ)UUWmUvr7-z*emqw^D֡~n H`"܉kFYgTؠ k`уeh#)d<$k)$TPk8#h6?$q7鑔>!J)PQRPGt]>+ga{5W[H 'bg^LR" bJH.!(qRJR]cY*]d"i $*EKK 9qGf|OBTx$U,2 զhz mW&c&&"2F"Xr!ь 6fRR&kf>_&\j۠5?찬&Iq8SͫGr7DZ"e)Hrrvyl'Vzf=F,q΀5KȐt) s(RJ챓oJ_u[Q7#tߑP.vU=pBުOO=޽J%"mgY/c旨n~;9s훙«𢛎44"LdG}gۗ73|S*V*IGj 6iJ=YRx,"r'La \YB# jL Ě<%O>__ӊVDsXE(at;ȮyH0#XjrZm9wy [8Am"NƷWȚ#T%# 1Lf6ph$`UESH.y"]Ly1.b]F'mT/9V# ֔*ؒȐ ā$!.#O3˱Gk`]6AYJrkH&j%蹚F}<|I*[{F( +=wz]鰖Db5y@1Mq<+Y(ӎw y@Ϥݽ$V3t2$Au @t"tJ 5V (D"If#P\7L5@]ƅ%*T 2!a)l /= ӈp+d,ɩg{qn7sb2 \SDSuĦr MC$rE Ec$6)" R($V<_~ 2$NnR 7 p{[E +T4gDŕ6EFt.O_4ITנ*T?Q=b{ѦAGЪj%3maTeߜsj.q׃99N,l6cz0IL"[\DMWJUsI慒KZ:bTgP>زS[yhDZ 6Wmhk̎Nm4gʩa2?C0CXrw5C$K= @J`"Ж8[oNpfu vk̠tsO|{R[Vdd ao4M6 KٹnrjֻgG[_x$*ܚ7߽Tw^IQEjN9cWH*~_c> >6536bϬl`$F?:Mt@P߸7 K3$nKw.h s$]zWK@>jch$Ѯn07PG°_M[ |$}YpYoBx} i&jrRFn(3}b?'N~!IqD8"73R|{@7|Q%9)LW}uaVeZjw'UB~IF4oWAxeuRe҂0E_Z[dir '7K aywreBXaXX((nW<#!THxG8al+4DW-EZ{RRph FWzEy"1Mra9xhR`4lf\1lr"7@T刈^ah>0B} y[TF4XgqgSvxnx'wV6 a7α R(iWsA76E5(&a7nx?xgĘ6pM08\9V|]Ȃd-eи)G vy&I3a"8zg+ g~X?w*xiq~)!|Wڴ@V7 QI$ꦍֆInDB)nV |!7Җ"YPy6s457|7I33 fDb]#jv376/i +IpH{1hPfM蔏ؔ3,*V兓(|Ut;'x9zj! U" Y^ƜcuGd9naX 8ᏊQܒ`SY;rU9|]vD" 8ŝznٝXWAY hfw7K&tO8=)m% n6s׉1yuI D8yH?uI~}{lOH U^h0Y$j799ؙ٨DȠj7ib9 GH D ?9 F>g W3‰ PwBFzPlyfj7騖' ifyΥkJy>R*0xcG'QjWu4v7yfcgu yAg헞S*pҩ դw;nt7|ډ*}W7eyvr 3:k0BzL)Av%,,e \؛~>GlgȪ.f#7yJ)f\8fziDʔq4itȯy#D^Y18vPVIeWyZ\Ql~ PC[򣱧X6kgsʑ\t)oD|W!9'J `zckZi#[\ipQ!(Ah>19V-RY}Jе `;ڋڄ3vWa)%۩}CXHPג٪5( Lj   )8{*`lg %ɹ˸pm,YJ8RHf\Jaۓ<SB*,ˊcvi̎7uɆÚ̕ 'f۫'j֬AHg̀bFΛWq<ͤZ|] H!tmϊaܼ7{Jt X?ZD,U̙Tj܂{p9-"l fa+H} Kz9*F;!|<ȣЯl>xžf4-9ΟJɗE!IM[UPh[U= P'<ƪ#ݮ5=?H|Ҕ˂` |huB"̱<[l\< S/ \r*ʎ5` =,h'˯CL>̰:xUS~K-ʉIkik¼v#)HÝ֬ȭ'խ[@`Xĝ$k덹*,+ {-&xI|}C£> D͏I%>ND{r }uҋm捫f彄*fyg(N3hnlzͭE9j Ј>n pD*5; M pQN$lTw}Ԛ>qQ ի@懲- 7֕=Ui֕XoQM5۹jjqy(l&qR^L ӈ-gcRkeQcxQ{Οƙ (ܠ\]Ƕ>eA@W#Qf 2jJTz nvrsa (6?K#4JnzZXx>'ۤAyI5 k(#H5lETTW7ܼXeiIqhF!6_m r})6@Ze{# ?j4i~j?ZIynn5vBMyL?\ܟnc"lqI'>} PB 2eaD"<bD#RƎLj*X']iE5mZ8fR={rdT|,urɏyg=]-uqj)L|Vl T"i2Rf()ݦtZӅrt ú5MX6euWRK IwOd+_}jsM!&Iz˖fTJƔi(,Y\ ujׯbǖ=;Ƅ*Y/P/%2͗9ҤoVwfT'Me,n;/*^{(vj.pΧ۪ܶ", 8(;-);?#!KQ#;=hp!.@Zѻ<ꩶvHRpA\DB, 2Eb -О.&x!}<);:UTN(21+8d2<4;=fCH#a(%w3а($%" .2M<3|$7T<Ѓ 6l{G 5#jLX&P2/P2 Tq}2TS=kչbe1nКݎ.9|/`4^:s|SFÃPMRq̸t-6Pm:!bjܭ2ܱ2L]2}s|ev;: ęCؗ/[Ȇ;T$Aܨ7Y`[|Ê&z =Pwx`}oBtp}١k5X-Pi4JcrpHè"茌hLr0KX=6єr# >Hry!.$CgRKӳjx~O:e%"rRVBkaDEXm$UvbH#K4SBJ`B>q%IβY<&z?c%5X\Z0 d_K炝hl A{!7Ѥ')(9>B CO@t1SQ =Yh%MF3'4m/T\TIrSì(:R>\+X׸RS@YٹǦ1Sn?PEL Q"tBFKu VLҘ1YRX8?7\  RMsGqM͗.|mqNxBƟppwy=NqJM5q?(eNq+߸_+<"BЃ~B`cG->ukn@X! ,^Ƒ#F0YpC8"Jᐡŋ 'j#B 1a‡(?DX2 @в͛%a*ȟ$m܁ɗ/`㧩ӧPJu*L:eUT^Êz5)V>}飴S6Zۺ+ܭv>[M K  8aC7jLaьa| d"7lr&͕8oZ$ϗ8] AҬM'otNnpb+_μɝ~A@# 9 ʒÇ|ѡ#a h,gL]rЉ'SgGojU\P=[Sg\-Wqr!tNs`,a]]8py -!ފ+2Ř";B(|AKweGyFH=5]ՅF܄ 2UBؤr~Vi`~Y!RhvtA4CH<5Í(hj0#IP`L(_b&%楘uU : [~eJaJ\Y0 C C)t)HhI4?ByR* &g!\cJ\av*ȁDkxvwc!!*A!/BN)[Tr+n˭Z:9jaJm"-Nvعة[qZ&C᩷^gI 5,f2\iBZ Zz§b\ o#.Ю "F")z)d8fϐ]rm[Q7LOjsj|w6$p@ tB %V$,ҬolfNlM2xu۽\ᦳчϭO[qL7~8lBЂzfsMl,>~>^Rph)x!' GrgophXskl A(3ڙKLB[58@V'{a r:8 !6AGo8d>i1 * `x0蓟щdAp b5ooj"~Է>oH2ss (*ht: 1d8F4 *x@ bf+' $!I$oZ8`brm$_Ēqq,g :юyDc5|@L;|CI=)  25H7IzS$ %W@J9ӂC~ē63)o< /YlOzqv:4Ē>\:TKˊV{] @̎zv,&DA! &6pHȌ(")SLI VӞBhDOʓÀD?<ΩlNSS'xP&lQ3S}h|)VG*VY]f.Ѵ)57`MKf!銎.Ln OŞp0L:z@fQ}*b"A uP=’e\-J>ff8S[_:!] ֒ѡk B%^hķ=U!KݹN-"ŤxҮ44'1%4Y $#H7LV81!pCV_z5{GLڢЃ[ݎpRajQ܁veaHjus0e܆ nB |xߵM'i—h0^vև hٹZYx̢D(X jnS Ð:8k<>[N-Հ9 3s|v,ֳ=h0 s1ȅ ;a I0sD(8ozӠ"^$`rCRtI{QiZT@ H/N\yt# }>֟/< $>_XC5$,2ܥoRkFUP'͖T'@{msawBfcrigg{ߦurVg|`WQ[yW}7W|W*}'#8{' }W? `JgiFNj$B~PSK6gM/PI>uH'Ptk=D`"vqg4nVaasPa''z+؇,H~8؇/w`x& `oriDHdCNyL؉MONRhMRHYEWXD01H63;jt,`Ыv頽ʪlꯎ@zJ {; sЬMA~Z? LyNG*㉨ z);;TzSGiT/PM;Яz{  k ۵ZM+k0_]ڶm9t;;ǚV˧/6XX2ni2*9j<۳C/CIPgwcB׹n0Pn+ ڵc5զ{;Wg[Х{ku+^_ZK|(r?E $<;K;L陥^KMv!y@$ډkHP۠)Ŋں;n@kd;gK{Ë^˼i˻$<Wa]X# l~:bqn%+oS4kKu諾$۾  UYk8bXs0nп ^Y [Л{`Z]nipgl&Zlt@ @ ]/lŠ||`$\]{\]C ǖLɴ[*L{`8\?+jEBlzS4+E?zHKK<$@pi\Erڡrz[,}\T$VX0S1GJ-(ᚮ>t +r0̐@3]кzjpcAo ǩ~X>ۗHmRIekr.Jt&^>EyjRS&:]ݑǞ䑞12ڟ1i+.n|X8= ϒlЇNH-ΩO}=^Zy $OTY"d;nu<:R0^n].+Nީ>XNaN|[/h7ľ|` + Ի-ټo㗅Pa(<^?jNx凄)ѝ ^Z07!@n@/SĽ:.Nomڍ ڌa[Om'dϪqvoH?Ϝ;[?s̩S{*ӰA|N=u*RǁsDLɓ bG\P4PfP1d#A (IQ0S+NmêթR;luałP $b`p}@]yJ A†G`!p4g!eb5g0nLʄϨApg:n sgh}ŕհϠ]C{k(Gw-|d ɂ͛6Hç>}(5Us+W 00Zj˲;WdъU({l1L.Sp L"Քc,԰"(C1EhC~{$#!UBH sQ9pxBj)*(:oJ > Ba̩2#2t2,SMM&TB 1 YpAFF*S2ɤF .s=Q?X5ܤ9 p@WA)*b)jس_-0KL$'3a 4LkpqD08TAk=Nw@mCM4FOQ%,*?>S7b`uB::<5Ur"#㎨rx̌muʯK 4 XS ꯅ%Luƴ;:.BU_c,h'At9M` h$+rـQ ^*L$@*dW!nvR 2Deq@`lܱ&p(KYŽf*0@| aj>bK1i 9l$WxLd˘e42>$kQU<|KdP +EV,o[J#JSds pON1L|H4ōKO}B݀Irg(b#@tI(ùQD4cb]s>Hp{]DGWVj\iaERF(j&MI 4bB:IMCigoIoB VYK,ĺ=(2<4\^U&3bYByH"'p>MDdznJ9|ku-ZfTa X˻+nyūX*o`*L˘N%Ɩ|j:@Վ6e mׇ6mYX5W;>b0=dXzPe\ ]9%׌s+Ie7^)jS񖗽":ć/ȗ!HP yFyp^$ث_=~9Ñ`Ǖap$]mnB}=` b >jy}gS z+mVa 5E>y}sOGpȮqɵ{h Ȼ<@IRw7شB)V_Y%pyqr̾}-Z@ a(spk5;;7І_ Mlxs%.)7!MRcy TOJM [|L$Nv,  7'#`2fZL!t0@ oʭH'#8G#|Ɏѳ}>PDwGHDp@ #5CS `^>GYЇN{8k/`gx|1fV}w'>}_xfK$q+(-wW:[ g[\tAꮿCC;3x<[[@+>((I6CQ9˶ P<ؾ<+A:?P!SS*IAQ2'(1By=k@]ޛ$ @)=B89,([Ell4$8CI7*V'<䧔Q@: (@34'[d@cTB {8'LB B B#P3}ɮ> b-6\AP2+ R#@A0>|+DaA}VF:43%0 )ӵ&DkNĻ 9o=墺B,T44.;:7Hr/&A8/"DZ7>7FD x3'"8KgLK0FRDdzScDE97$"1LGPҕ"گ\~܊BH ؀ JhA܀H>ȷf\Č,K"kB*캊;mIl캐4\{c.¿3(I@FzG i}4ə (L5#=Cxl0`Iı=\MF=KtKA:דI5Kأ3ǺÍ̛ iɐC`J8=EʜQ{Z;,57Li ?e)< s",qZ33մø:=:r_\cT]KmT1bI^0ĂhG 阎eI2/똰s:%HQ]Q]XQeLQ$@xAKjhO-\30$tR ";fȗ;?-DRt.K5R,I$ӄш4 8+8Qs.S[p3HG8%S,PF#Q .9OL90q `=)OHǠȲ$ϰ@'uKLj@4ܬR ޴ĺb.) 먛H>@݃A]4?R FmeP hK u=y#"!Z+U%U"TRJZC5ȽD8LPBMjJqė38`̉Z!͌*.:S.< @C"EO5%xq-N @ 4L{;J~_!gg}!g5FP>hQ] [W+Xb`fOM6Ph@hh@2fijk^6D>g^ WTC`\x$x}gj&j h ,Pdkܣ b^}.hG%f1)-w[t d<ii̶8˵i 8g-6hMC}ԩBP'~m g6n8ᝆ[hg4~ؖm8*BJ3x>e)oe^Yoxd*$0(mҫoT•K<^mזj ਫ਼n~ pfmg$Ai'gĩwC>mhb{<%`#g%pÍM&O^M Iespa,WC2or< W Q8Qksi$kpm(2hG@qtqf6dFCiwqB gϴ '7rpQ}"|,-l&.уGƆL2O@u6.kU΅#esjo^O.Z(kνpdmE̽otttwoN\ܜNAvxdfQow5c7Lya?ͧR's.Ϝ=,Ŧ`7j]gfj?r$vjoʰsΥ%E32P%ǎF6'pl*ri }xMoJV[/ Ҷ2faoUʞ3Hx~%Mդр`VsGK Pr%`yg}m# gos~  $H$HK}8ӿ_f}ނ>t6} PpB A}2Mw31f~r^ѭ'Чa-z䠗g&7r6Go|ƨ!^bV#H|b $?~$i@%J&Ç7jhԈq!$Z#Ɠ*;hYeK_|)BΜ:w@K,2@Pd SbYbba6 q5DAz.H{@(l097|` N~]WJlO.]z j,SJDZ6D):aŋ(;vjL -Zk  H8Eu]x%pPX@ VN5"VNIjOBYT6#U@Pն#D=2B!盐-G oH@R'QEeƄimH k&g)g:0:ڙ]\.usBo ⤕2H(^)ܸ55iM >٣pue7nϧ#N@Muħ35$*$ .Wa"P-P9OlK I\<{řj,*wrJlpO:jI oz7&H> ]зħډcׁT29Q@?s YZd"i͒>]0s6e\<';9?k;<՟kYF Wn( y8:>iM9hR8A֭n^İݭ- ]<:9 _M'چ7|ARIsˏv%9!D+r(<uU[ mݵ4M@rhRZDmDGE $Ac|GIHL\xܠYNE8Q |ʬ[jpAXrxݔmAؖ ШAKJɩmNutơgY J5m V@ _(O0^9΁ߴ_`:[՝TAA&A:ҵ>1ǣ Ku I$T MOFn iDL \*Q\.^P%6fyF_GǴͮT 1fcn"PeJH CATATg(WVz%(/.(!& m*hnV&h1v2ODv Qa,SaDtNxDSD P$h KGNuS-X\VAxNe,&idcAi|ap DN)ADsL$ 1\?Y%Jtr$(b5(n6@̄RZNM q`[ \V҅twD $H YJ&RƇiW,i|čܐ'c6r&#*j"$g(f2e*(yO` /%OGi&FݨwqR[$0`Ijǔh(^`k^ ^Zh ګJ҉AѻEA/#g`"~htOF/PJ&-,",!,BN, +h:eFAn*rDA)NZѨƣ뱾QGP(aZq(xTޝ qȉ!_Lʷf Bw\d-lȧ蕁Q6,"|:J:؞-ڞm! B¾9έ嬒%,J#->":jD1> bOʒ 뼰 nGrfgvT8^V A:0N:_K<~AJ! o./ڊ#.6oRJN/%8/قmeȁPJF H^lK=gDEThf>$:RI.i@Xm/L4v.p [I(k[crxԆ$FJzѧK/NiJ)ŮjJB*/.203/&dg1'3PoPc/ iXvΫeLYU/dQl@4x)wTV턀@{hLRHBfp Z-D,.&%[2cN,N2 NlqBo%P/V1)IJ,)r-B)%%2"-%-Ҫs0ΩJdNo/,&Pb$qy#\NLiULFWA"`<}I'w6/&t@7 S6A?K&/B$0.%2WFgF2),4Ht,2I)-r,ײ%wtG"DiVL풢#0/A4PtVEKR8 9硟5jv.mlP;s#A(ޟ >lRl;o@Gon oߪ2]GRFG)+_4G4`,t/4K)+,6c)..BOF&cOOPw1Yl`mO0Rq0Q:'ZMihgXLPBa.K9,Ċ2FqwZ[Z#@7w@gZOsct;w+1] 4*[t&Fkt 6Hx6aatb-7v),6ar,7}ӷ+rwWB)A5tvgsϊl~ P,mkߥ@eJE}V. -qS&'t;7tkwxuO7*t.2u_w_7Fr&,xH`ya{3{7_I#vJc2C.B^9gy%{h@,|ɖh omSff}VƵ\;$ܰrk@g7*x9FxExsG4w%+9|79wz)^G1r.w<ܳd|*D)R2u +Xj b,VIUx#I'QdH/2iR$+YTɲ O1i^$yx#0tGz1/P010C2pciR $2Q@ڰ>Q1p{餞\9)GB"IFBDȶۄөG C#R0S:&+pɉK\mD$" T ,PI*;nx 7صAj8 {嗧U a`  r `2 oG*URL0&;r)CM+oFlF!:o!o#-N`N*G/He0A*~@$M#TToGIPRxLC"no.  2R A Jvp~iG$ 2nCVf+Nʊ<]0-1 D%a|xKf( rWO&U\MEhL4\M@ oР  0.0RS @$0Ϲb}ҥU^2|1P$Ȝ0рJ.!2,%A1qpרt@,0%T^ #*cf0J 4!梠 dؐ@%@ `DOl 0.cr*PGR ` '~(FZ#\? 0-1kD]L1+, Q[Lu6L7 G !BF]: Rq6 d2 s\] P }j *zKt Nnu6| |'~p,q@6C92,r-   bNa"#/60ͺ0 ={ N1N2l *Fn`6 yV *$hh>&a+ڰ'2O7 CFcvd.άǥQ!`TRqĎ-5n1/ǂi7 #li= = NK/2F`J`LLKM4 4elc2p2FCgEMQDD)P7O7?!,sXB,m4!\4PDlstRG}S0 dI3;gbp KMVMO0*nWoK$!&,(4&:m@& )Luc!s1hKZ}SZ1.9#4f &t#q&q+S.9UHe/#XR*NBLJb)LG) t ` "+1( g!,2//BWŔxo <3~Lg1q&s3qVpZ Zug〇[BcQ3E /ap\G4m 1 ˪lbS*;iCc>b|M!Py FC(nWt+@b23e"NXt#S*3uP6eqeYe_eO֌qf1poT` b9m].%:i/$&B%CÃ())"N.Vbւu[uPW2Tg86yV24lp a'NnZ6ZؕEOD 0H#zoטWv@xe#*֌`9qw6bĵZQrYhԥ}7ٝ'r }÷"~ AjB̚Zۭ~9OzVx踙]1y1r*2) h*⶛ {dž K.kz,t"<WY{ H [ l6K(p73]WTECLzZ9`5e DZ6ݶE)T+&w'`:uiڦoB:s!LC#t[v|dKYmfP 4 z56yimXH{Ě?אsy̔4+/w/u49'@`w~Dd{[%r+UIϸV-fnG;|7ګ7e=8-e{1qNRn;&(g,B%RPs[%]̀Y'b }-g.dڹ_O;6ms-ۣT> ˰Z3;qDZfib9?8}SㅵҙȊIi.+'hd~k{Y_{0T;2^-lO)Ջ@!\p79H )XQ`ť68ت)H.ފWE٧8zuuL'~|O5_.5b\JGz6R7FlŇF9MAC)xV>x{syQ]4NB,Wp({RŗXc4/Tdi #h*!˩ [mͱ5e%9`ccNjxFNfcgT)l+xO1BSCa˼)$PeC{|?b$`h7v{ՍdT ܒP@i+VR3t)oIHb74feG&l}Oom<*7n6hs-c,hc* =':fFJ !eȑE"L1UaYa%U}bW\r^HyՃGh{ǖ! WEg 5IYFHh]a~hh6"LPoHAx Sd*Q2"|ȑNR,PJIjW^~IU8bcbfE^{ʶK,\IyN] m]~΅`m렐)gZV$F(h6#w餏H/%:HB ć wÑGL ]CzSwm]ܙc3唡aWb;* ղs)ab_\۲ rP[afզkԺZӧVG)ȣJDJ%P!<2&@a7F-㎘vKGQ 5$i2ur &9TlUyWbs̳eㅲЉ BpŮ-ns2}FPSfX e;bigR Ůr)m ws-PzX/%,>.$&DQ!FubaPJlɼ>Ɣ_-(&>@ XQ+ҦD<#)|,C-PDK`nM㖷& 1C-_\SGUs[%'O飄S\bJpq$1! zxHF*BWkH0 DӰ֓0G$V)c@M ]!h"ŚFeYa 5 m7/öQcFFU)T8us(‡12E&תEh=17 ; :.f+;9qkֵuIr%&u%K"T7q1&,"[Le&W CI4@k*OK}M-#RٗQ JYkKdRJ;v/ 4`$q;إwHNE7բlg=롗 - М (WkpCqh-1g@KOgz"xWRLJ ~uZ-а2ioDT^N.:ˊY4nO %zvRɑBMi³d%j)xvDYPKQ>WRr%{AՈTD8C #zʘr9aj%Ӌ;&f"KԁMC}20X^*s ! $^fʡQKX#6 ?BSEQh.rg#X R )[,SQg9&VRlSķ.rlSDOd:6;j-]};%y%H"<@jUJGH !!G-#FI{,ruS\3Q֥b0'$! rғj/]9)Y?Țd%$+ф d(߫ ~υ$<Wل$Orԧ] ӅlcPO+-QȪ}Q1eAqgvg\R@\FwQݶxpUbt4&H.&x|-*wOS}m@+292333ӊ"Dd ̂V iA4 {yWQ)s NX&xev#tZ(`6@ytp@ 3FLi–qkD3CO @c-n/&.t.;}\7&9/!4WwN! zyT(َs؜Gy˦2Q#&9.SDc+J;2}JAqɂ-Y !O:ȃ(IV4"[˜PZXz,T %uRYv-+;eH|a;y wN (=jzNpE_G_j{nErUdZQ:+ WWqV :3#i)=ٝ3V+F^8riynn>uf!hyԇO~Jhw)wG3,2R_2ZMMX$  jy*bF9b_6(QLJ =ڪ2td.ѠZ!єO׫P]hVv iwg͹eIL"s*GJ\2 Db+9ioم!s)8CXH>j@P$E)9ExyK, ؓ;LkR`eb5iʑե5E5Vfb'p[LPsm:(ճV:Wj,3G>*3w(T Gd[o*ӳQƷdԅ0o*w[bk&QA+)&i~m$uۺLiⲞPѮ`KY{R]KۂeWrUwM&[]sq̤תݛs aᰝ@D#&ybe*UƫQ1˫_r'32d 6֋gq+!ʬEmm@+%Ҫ@IB+F{Ja$U>p蹚`۾PhRw[a) 5C :JaX9:jo({k5k(/~LHQ1l~:|39 31Wo5B̢,FMw ŌxY<&Yũ۽;eRux5|ͳƦp3 KzAQ׊dX9kălqk$,;9ǘ|6hɗ&^{lEWLQRH*-ّg{zkMb ɝ~ }oٙA/]j,2M.#TTW,-Ȅ&9Vcϑ]1]4 m/njQB̐ͦ#T ^L9.V{8\{ϗ^, Al}5bHv<߰.@KWZ"jX; 2zc70z˜,m: "n|YSec}^ U^t>\wP nʹƌ:9k *\-^l!RՉA-ȵQT̈́@G M!A'IzϺM]-B+ޖ{T&9/QjsGA~D/8cZHȹ&vP֙oܽ݊PquMk_m?]nlƦjAXILcyLQ"A磋^Pv+R]lZ.}`3B̗fOT//ʡX}njW%cR_4ݻin. >5O=)(جxU,Ygղu\?w.mhZ6 0g#ka5Ui7z7L{>1wT fJ1Oz߸-8JZ&Rn(K=D?@h4T$\NR.jBrH#"*H0,,8 0+pjo! KsP6i5Y+*I 2GL1оL+'#3:#%L@|2(Mp 1D˾@ QnMlEZ5KLO Q±T2umQ!DrԔQ8oȱ_/vv>32*|]EӤ0#R.+J 4w-43Kш볦%MeL%rle=!k.D5YhMF#s70O[X]s1M5%mQQ|zV[g߂{ǖjfe"PS5מEڪ6(l- Z6=J9)iYp`YM޷l-8I\;+FÏE4=S5⧖r<ɮvA(+Ɋ 4o+L;߈]QWi~ O_dP\u`?ٶSLQWy78 qi^E,-;\庲+ZDmN{uq)(,L}X% pz淶F#L ;PO3hҤ"@*x' TI#TKl 0W%Mv3=XSHBи?z[e35$OIhmNo4r$B| iZBzH4 "4&"LP,EN^,k e]\Fb&Ьl^"t ێBQ;;&D,D) l쒔lX$LT8]ˤH*3e G˝{܌TD{:t:=h3V U` {3@f'RatI46g &٥DU<\!DJؽixbɰ SRҠYғrB]yAMKs6ޮ_.T.&L FD~fĚָsIE45<ҍx?+M!MB\bO7ܢ70꿒u>TTh#ٜ4N}˿j,J̊πRJP^%I*akK1ѫeD&Ѫ,/Ku>#tIfEWe,;ڴNS;6+mx暾dmv& dl7 ƒ;Z.sQlD$fe#sWѷMT#49&$UmwXBabnZ(=/N> 0Nid9u:3,BlFKr[ !G"/ ocrs?,H07ɒRl]3u0`g9_>U, H6):\q}'vEdq,8fJf Bѹh.1-IY%YL׶̮8b. o#=橯YX{Xbu \-xGN|rD_be5]V^jeCT>iCMTl[|Ȇwݫ`py.jXWܑ_vSک/!5ၽ~N;}au[ $;gm*pՃ,)Jd}gsHg,r['Q|;!XQ.!^R%75 ,T:/?B_OD0AȃA<AAB0G>#?G0! 9i #; oy-*JA!?*1’O4BAC9 2;PI 9QBb'|AQV p72ihz7귨0ßHc[*LxDD1$O,CP kCXAl ` Ż8W Q;?Y`3*Z7PjGb= xTi3F>@k,.K£hFjFO$ClFm&q IPGC?%𑼰BKa72<<Ǻ1*<ݢ L>̢tʦLL$C@0hb q Iȃ5?L<] GCIRLMdL,J1|̪נh̃@J'ҤK!,WKګǔ*伈0L@LLExLlOL@pό|<2(*)xXp@h(>NI?K*!,F7|IO/A.4OIIGnEb @5uτ4K E Ml*a Q\X`'}Ҽ5ǴT$I$弐$$p252M4U5e6u6E83u''<=S=}?$hA%&S3DOUTE@uT@5CSE=B&SIu%NU8-'BR5S]%UVuUUUU![Y 0;liquidsoap-2.4.2/doc/orig/fosdem2020/remark.js000066400000000000000000065346371513273233300210300ustar00rootroot00000000000000require = (function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw ((a.code = "MODULE_NOT_FOUND"), a); } var p = (n[i] = { exports: {} }); e[i][0].call( p.exports, function (r) { var n = e[i][1][r]; return o(n || r); }, p, p.exports, r, e, n, t, ); } return n[i].exports; } for ( var u = "function" == typeof require && require, i = 0; i < t.length; i++ ) o(t[i]); return o; } return r; })()( { 1: [ function (require, module, exports) { // Copyright Joyent, Inc. and other Node contributors. // // 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. function EventEmitter() { this._events = this._events || {}; this._maxListeners = this._maxListeners || undefined; } module.exports = EventEmitter; // Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter; EventEmitter.prototype._events = undefined; EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. EventEmitter.defaultMaxListeners = 10; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function (n) { if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError("n must be a positive number"); this._maxListeners = n; return this; }; EventEmitter.prototype.emit = function (type) { var er, handler, len, args, i, listeners; if (!this._events) this._events = {}; // If there is no 'error' event listener then throw. if (type === "error") { if ( !this._events.error || (isObject(this._events.error) && !this._events.error.length) ) { er = arguments[1]; if (er instanceof Error) { throw er; // Unhandled 'error' event } else { // At least give some kind of context to the user var err = new Error( 'Uncaught, unspecified "error" event. (' + er + ")", ); err.context = er; throw err; } } } handler = this._events[type]; if (isUndefined(handler)) return false; if (isFunction(handler)) { switch (arguments.length) { // fast cases case 1: handler.call(this); break; case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: args = Array.prototype.slice.call(arguments, 1); handler.apply(this, args); } } else if (isObject(handler)) { args = Array.prototype.slice.call(arguments, 1); listeners = handler.slice(); len = listeners.length; for (i = 0; i < len; i++) listeners[i].apply(this, args); } return true; }; EventEmitter.prototype.addListener = function (type, listener) { var m; if (!isFunction(listener)) throw TypeError("listener must be a function"); if (!this._events) this._events = {}; // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (this._events.newListener) this.emit( "newListener", type, isFunction(listener.listener) ? listener.listener : listener, ); if (!this._events[type]) // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; else if (isObject(this._events[type])) // If we've already got an array, just append. this._events[type].push(listener); else // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; // Check for listener leak if (isObject(this._events[type]) && !this._events[type].warned) { if (!isUndefined(this._maxListeners)) { m = this._maxListeners; } else { m = EventEmitter.defaultMaxListeners; } if (m && m > 0 && this._events[type].length > m) { this._events[type].warned = true; console.error( "(node) warning: possible EventEmitter memory " + "leak detected. %d listeners added. " + "Use emitter.setMaxListeners() to increase limit.", this._events[type].length, ); if (typeof console.trace === "function") { // not supported in IE 10 console.trace(); } } } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function (type, listener) { if (!isFunction(listener)) throw TypeError("listener must be a function"); var fired = false; function g() { this.removeListener(type, g); if (!fired) { fired = true; listener.apply(this, arguments); } } g.listener = listener; this.on(type, g); return this; }; // emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function (type, listener) { var list, position, length, i; if (!isFunction(listener)) throw TypeError("listener must be a function"); if (!this._events || !this._events[type]) return this; list = this._events[type]; length = list.length; position = -1; if ( list === listener || (isFunction(list.listener) && list.listener === listener) ) { delete this._events[type]; if (this._events.removeListener) this.emit("removeListener", type, listener); } else if (isObject(list)) { for (i = length; i-- > 0; ) { if ( list[i] === listener || (list[i].listener && list[i].listener === listener) ) { position = i; break; } } if (position < 0) return this; if (list.length === 1) { list.length = 0; delete this._events[type]; } else { list.splice(position, 1); } if (this._events.removeListener) this.emit("removeListener", type, listener); } return this; }; EventEmitter.prototype.removeAllListeners = function (type) { var key, listeners; if (!this._events) return this; // not listening for removeListener, no need to emit if (!this._events.removeListener) { if (arguments.length === 0) this._events = {}; else if (this._events[type]) delete this._events[type]; return this; } // emit removeListener for all listeners on all events if (arguments.length === 0) { for (key in this._events) { if (key === "removeListener") continue; this.removeAllListeners(key); } this.removeAllListeners("removeListener"); this._events = {}; return this; } listeners = this._events[type]; if (isFunction(listeners)) { this.removeListener(type, listeners); } else if (listeners) { // LIFO order while (listeners.length) this.removeListener(type, listeners[listeners.length - 1]); } delete this._events[type]; return this; }; EventEmitter.prototype.listeners = function (type) { var ret; if (!this._events || !this._events[type]) ret = []; else if (isFunction(this._events[type])) ret = [this._events[type]]; else ret = this._events[type].slice(); return ret; }; EventEmitter.prototype.listenerCount = function (type) { if (this._events) { var evlistener = this._events[type]; if (isFunction(evlistener)) return 1; else if (evlistener) return evlistener.length; } return 0; }; EventEmitter.listenerCount = function (emitter, type) { return emitter.listenerCount(type); }; function isFunction(arg) { return typeof arg === "function"; } function isNumber(arg) { return typeof arg === "number"; } function isObject(arg) { return typeof arg === "object" && arg !== null; } function isUndefined(arg) { return arg === void 0; } }, {}, ], 2: [ function (require, module, exports) { "use strict"; var hasOwn = Object.prototype.hasOwnProperty; var toStr = Object.prototype.toString; var defineProperty = Object.defineProperty; var gOPD = Object.getOwnPropertyDescriptor; var isArray = function isArray(arr) { if (typeof Array.isArray === "function") { return Array.isArray(arr); } return toStr.call(arr) === "[object Array]"; }; var isPlainObject = function isPlainObject(obj) { if (!obj || toStr.call(obj) !== "[object Object]") { return false; } var hasOwnConstructor = hasOwn.call(obj, "constructor"); var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, "isPrototypeOf"); // Not own constructor property must be Object if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) { return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for (key in obj) { /**/ } return typeof key === "undefined" || hasOwn.call(obj, key); }; // If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target var setProperty = function setProperty(target, options) { if (defineProperty && options.name === "__proto__") { defineProperty(target, options.name, { enumerable: true, configurable: true, value: options.newValue, writable: true, }); } else { target[options.name] = options.newValue; } }; // Return undefined instead of __proto__ if '__proto__' is not an own property var getProperty = function getProperty(obj, name) { if (name === "__proto__") { if (!hasOwn.call(obj, name)) { return void 0; } else if (gOPD) { // In early versions of node, obj['__proto__'] is buggy when obj has // __proto__ as an own property. Object.getOwnPropertyDescriptor() works. return gOPD(obj, name).value; } } return obj[name]; }; module.exports = function extend() { var options, name, src, copy, copyIsArray, clone; var target = arguments[0]; var i = 1; var length = arguments.length; var deep = false; // Handle a deep copy situation if (typeof target === "boolean") { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } if ( target == null || (typeof target !== "object" && typeof target !== "function") ) { target = {}; } for (; i < length; ++i) { options = arguments[i]; // Only deal with non-null/undefined values if (options != null) { // Extend the base object for (name in options) { src = getProperty(target, name); copy = getProperty(options, name); // Prevent never-ending loop if (target !== copy) { // Recurse if we're merging plain objects or arrays if ( deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy))) ) { if (copyIsArray) { copyIsArray = false; clone = src && isArray(src) ? src : []; } else { clone = src && isPlainObject(src) ? src : {}; } // Never move original objects, clone them setProperty(target, { name: name, newValue: extend(deep, clone, copy), }); // Don't bring in undefined values } else if (typeof copy !== "undefined") { setProperty(target, { name: name, newValue: copy }); } } } } } // Return the modified object return target; }; }, {}, ], 3: [ function (require, module, exports) { (function (global) { /** * marked - a markdown parser * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) * https://github.com/chjj/marked */ (function () { /** * Block-Level Grammar */ var block = { newline: /^\n+/, code: /^( {4}[^\n]+\n*)+/, fences: noop, hr: /^( *[-*_]){3,} *(?:\n+|$)/, heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, nptable: noop, lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, table: noop, paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, text: /^[^\n]+/, }; block.bullet = /(?:[*+-]|\d+\.)/; block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; block.item = replace(block.item, "gm")(/bull/g, block.bullet)(); block.list = replace(block.list)(/bull/g, block.bullet)( "hr", "\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))", )("def", "\\n+(?=" + block.def.source + ")")(); block._tag = "(?!(?:" + "a|em|strong|small|s|cite|q|dfn|abbr|data|time|code" + "|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo" + "|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b"; block.html = replace(block.html)("comment", //)( "closed", /<(tag)[\s\S]+?<\/\1>/, )("closing", /])*?>/)( /tag/g, block._tag, )(); block.paragraph = replace(block.paragraph)("hr", block.hr)( "heading", block.heading, )("lheading", block.lheading)("blockquote", block.blockquote)( "tag", "<" + block._tag, )("def", block.def)(); /** * Normal Block Grammar */ block.normal = merge({}, block); /** * GFM Block Grammar */ block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, paragraph: /^/, heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/, }); block.gfm.paragraph = replace(block.paragraph)( "(?!", "(?!" + block.gfm.fences.source.replace("\\1", "\\2") + "|" + block.list.source.replace("\\1", "\\3") + "|", )(); /** * GFM + Tables Block Grammar */ block.tables = merge({}, block.gfm, { nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/, }); /** * Block Lexer */ function Lexer(options) { this.tokens = []; this.tokens.links = {}; this.options = options || marked.defaults; this.rules = block.normal; if (this.options.gfm) { if (this.options.tables) { this.rules = block.tables; } else { this.rules = block.gfm; } } } /** * Expose Block Rules */ Lexer.rules = block; /** * Static Lex Method */ Lexer.lex = function (src, options) { var lexer = new Lexer(options); return lexer.lex(src); }; /** * Preprocessing */ Lexer.prototype.lex = function (src) { src = src .replace(/\r\n|\r/g, "\n") .replace(/\t/g, " ") .replace(/\u00a0/g, " ") .replace(/\u2424/g, "\n"); return this.token(src, true); }; /** * Lexing */ Lexer.prototype.token = function (src, top, bq) { var src = src.replace(/^ +$/gm, ""), next, loose, cap, bull, b, item, space, i, l; while (src) { // newline if ((cap = this.rules.newline.exec(src))) { src = src.substring(cap[0].length); if (cap[0].length > 1) { this.tokens.push({ type: "space", }); } } // code if ((cap = this.rules.code.exec(src))) { src = src.substring(cap[0].length); cap = cap[0].replace(/^ {4}/gm, ""); this.tokens.push({ type: "code", text: !this.options.pedantic ? cap.replace(/\n+$/, "") : cap, }); continue; } // fences (gfm) if ((cap = this.rules.fences.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: "code", lang: cap[2], text: cap[3] || "", }); continue; } // heading if ((cap = this.rules.heading.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: "heading", depth: cap[1].length, text: cap[2], }); continue; } // table no leading pipe (gfm) if (top && (cap = this.rules.nptable.exec(src))) { src = src.substring(cap[0].length); item = { type: "table", header: cap[1].replace(/^ *| *\| *$/g, "").split(/ *\| */), align: cap[2].replace(/^ *|\| *$/g, "").split(/ *\| */), cells: cap[3].replace(/\n$/, "").split("\n"), }; for (i = 0; i < item.align.length; i++) { if (/^ *-+: *$/.test(item.align[i])) { item.align[i] = "right"; } else if (/^ *:-+: *$/.test(item.align[i])) { item.align[i] = "center"; } else if (/^ *:-+ *$/.test(item.align[i])) { item.align[i] = "left"; } else { item.align[i] = null; } } for (i = 0; i < item.cells.length; i++) { item.cells[i] = item.cells[i].split(/ *\| */); } this.tokens.push(item); continue; } // lheading if ((cap = this.rules.lheading.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: "heading", depth: cap[2] === "=" ? 1 : 2, text: cap[1], }); continue; } // hr if ((cap = this.rules.hr.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: "hr", }); continue; } // blockquote if ((cap = this.rules.blockquote.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: "blockquote_start", }); cap = cap[0].replace(/^ *> ?/gm, ""); // Pass `top` to keep the current // "toplevel" state. This is exactly // how markdown.pl works. this.token(cap, top, true); this.tokens.push({ type: "blockquote_end", }); continue; } // list if ((cap = this.rules.list.exec(src))) { src = src.substring(cap[0].length); bull = cap[2]; this.tokens.push({ type: "list_start", ordered: bull.length > 1, }); // Get each top-level item. cap = cap[0].match(this.rules.item); next = false; l = cap.length; i = 0; for (; i < l; i++) { item = cap[i]; // Remove the list item's bullet // so it is seen as the next token. space = item.length; item = item.replace(/^ *([*+-]|\d+\.) +/, ""); // Outdent whatever the // list item contains. Hacky. if (~item.indexOf("\n ")) { space -= item.length; item = !this.options.pedantic ? item.replace( new RegExp("^ {1," + space + "}", "gm"), "", ) : item.replace(/^ {1,4}/gm, ""); } // Determine whether the next list item belongs here. // Backpedal if it does not belong in this list. if (this.options.smartLists && i !== l - 1) { b = block.bullet.exec(cap[i + 1])[0]; if (bull !== b && !(bull.length > 1 && b.length > 1)) { src = cap.slice(i + 1).join("\n") + src; i = l - 1; } } // Determine whether item is loose or not. // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ // for discount behavior. loose = next || /\n\n(?!\s*$)/.test(item); if (i !== l - 1) { next = item.charAt(item.length - 1) === "\n"; if (!loose) loose = next; } this.tokens.push({ type: loose ? "loose_item_start" : "list_item_start", }); // Recurse. this.token(item, false, bq); this.tokens.push({ type: "list_item_end", }); } this.tokens.push({ type: "list_end", }); continue; } // html if ((cap = this.rules.html.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: this.options.sanitize ? "paragraph" : "html", pre: !this.options.sanitizer && (cap[1] === "pre" || cap[1] === "script" || cap[1] === "style"), text: cap[0], }); continue; } // def if (!bq && top && (cap = this.rules.def.exec(src))) { src = src.substring(cap[0].length); this.tokens.links[cap[1].toLowerCase()] = { href: cap[2], title: cap[3], }; continue; } // table (gfm) if (top && (cap = this.rules.table.exec(src))) { src = src.substring(cap[0].length); item = { type: "table", header: cap[1].replace(/^ *| *\| *$/g, "").split(/ *\| */), align: cap[2].replace(/^ *|\| *$/g, "").split(/ *\| */), cells: cap[3].replace(/(?: *\| *)?\n$/, "").split("\n"), }; for (i = 0; i < item.align.length; i++) { if (/^ *-+: *$/.test(item.align[i])) { item.align[i] = "right"; } else if (/^ *:-+: *$/.test(item.align[i])) { item.align[i] = "center"; } else if (/^ *:-+ *$/.test(item.align[i])) { item.align[i] = "left"; } else { item.align[i] = null; } } for (i = 0; i < item.cells.length; i++) { item.cells[i] = item.cells[i] .replace(/^ *\| *| *\| *$/g, "") .split(/ *\| */); } this.tokens.push(item); continue; } // top-level paragraph if (top && (cap = this.rules.paragraph.exec(src))) { src = src.substring(cap[0].length); this.tokens.push({ type: "paragraph", text: cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1], }); continue; } // text if ((cap = this.rules.text.exec(src))) { // Top-level should never reach here. src = src.substring(cap[0].length); this.tokens.push({ type: "text", text: cap[0], }); continue; } if (src) { throw new Error( "Infinite loop on byte: " + src.charCodeAt(0), ); } } return this.tokens; }; /** * Inline-Level Grammar */ var inline = { escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, url: noop, tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, link: /^!?\[(inside)\]\(href\)/, reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, code: /^(`+)([\s\S]*?[^`])\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; inline.link = replace(inline.link)("inside", inline._inside)( "href", inline._href, )(); inline.reflink = replace(inline.reflink)( "inside", inline._inside, )(); /** * Normal Inline Grammar */ inline.normal = merge({}, inline); /** * Pedantic Inline Grammar */ inline.pedantic = merge({}, inline.normal, { strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/, }); /** * GFM Inline Grammar */ inline.gfm = merge({}, inline.normal, { escape: replace(inline.escape)("])", "~|])")(), url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, del: /^~~(?=\S)([\s\S]*?\S)~~/, text: replace(inline.text)("]|", "~]|")("|", "|https?://|")(), }); /** * GFM + Line Breaks Inline Grammar */ inline.breaks = merge({}, inline.gfm, { br: replace(inline.br)("{2,}", "*")(), text: replace(inline.gfm.text)("{2,}", "*")(), }); /** * Inline Lexer & Compiler */ function InlineLexer(links, options) { this.options = options || marked.defaults; this.links = links; this.rules = inline.normal; this.renderer = this.options.renderer || new Renderer(); this.renderer.options = this.options; if (!this.links) { throw new Error("Tokens array requires a `links` property."); } if (this.options.gfm) { if (this.options.breaks) { this.rules = inline.breaks; } else { this.rules = inline.gfm; } } else if (this.options.pedantic) { this.rules = inline.pedantic; } } /** * Expose Inline Rules */ InlineLexer.rules = inline; /** * Static Lexing/Compiling Method */ InlineLexer.output = function (src, links, options) { var inline = new InlineLexer(links, options); return inline.output(src); }; /** * Lexing/Compiling */ InlineLexer.prototype.output = function (src) { var out = "", link, text, href, cap; while (src) { // escape if ((cap = this.rules.escape.exec(src))) { src = src.substring(cap[0].length); out += cap[1]; continue; } // autolink if ((cap = this.rules.autolink.exec(src))) { src = src.substring(cap[0].length); if (cap[2] === "@") { text = escape( cap[1].charAt(6) === ":" ? this.mangle(cap[1].substring(7)) : this.mangle(cap[1]), ); href = this.mangle("mailto:") + text; } else { text = escape(cap[1]); href = text; } out += this.renderer.link(href, null, text); continue; } // url (gfm) if (!this.inLink && (cap = this.rules.url.exec(src))) { src = src.substring(cap[0].length); text = escape(cap[1]); href = text; out += this.renderer.link(href, null, text); continue; } // tag if ((cap = this.rules.tag.exec(src))) { if (!this.inLink && /^/i.test(cap[0])) { this.inLink = false; } src = src.substring(cap[0].length); out += this.options.sanitize ? this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]) : cap[0]; continue; } // link if ((cap = this.rules.link.exec(src))) { src = src.substring(cap[0].length); this.inLink = true; out += this.outputLink(cap, { href: cap[2], title: cap[3], }); this.inLink = false; continue; } // reflink, nolink if ( (cap = this.rules.reflink.exec(src)) || (cap = this.rules.nolink.exec(src)) ) { src = src.substring(cap[0].length); link = (cap[2] || cap[1]).replace(/\s+/g, " "); link = this.links[link.toLowerCase()]; if (!link || !link.href) { out += cap[0].charAt(0); src = cap[0].substring(1) + src; continue; } this.inLink = true; out += this.outputLink(cap, link); this.inLink = false; continue; } // strong if ((cap = this.rules.strong.exec(src))) { src = src.substring(cap[0].length); out += this.renderer.strong(this.output(cap[2] || cap[1])); continue; } // em if ((cap = this.rules.em.exec(src))) { src = src.substring(cap[0].length); out += this.renderer.em(this.output(cap[2] || cap[1])); continue; } // code if ((cap = this.rules.code.exec(src))) { src = src.substring(cap[0].length); out += this.renderer.codespan(escape(cap[2].trim(), true)); continue; } // br if ((cap = this.rules.br.exec(src))) { src = src.substring(cap[0].length); out += this.renderer.br(); continue; } // del (gfm) if ((cap = this.rules.del.exec(src))) { src = src.substring(cap[0].length); out += this.renderer.del(this.output(cap[1])); continue; } // text if ((cap = this.rules.text.exec(src))) { src = src.substring(cap[0].length); out += this.renderer.text(escape(this.smartypants(cap[0]))); continue; } if (src) { throw new Error( "Infinite loop on byte: " + src.charCodeAt(0), ); } } return out; }; /** * Compile Link */ InlineLexer.prototype.outputLink = function (cap, link) { var href = escape(link.href), title = link.title ? escape(link.title) : null; return cap[0].charAt(0) !== "!" ? this.renderer.link(href, title, this.output(cap[1])) : this.renderer.image(href, title, escape(cap[1])); }; /** * Smartypants Transformations */ InlineLexer.prototype.smartypants = function (text) { if (!this.options.smartypants) return text; return ( text // em-dashes .replace(/---/g, "\u2014") // en-dashes .replace(/--/g, "\u2013") // opening singles .replace(/(^|[-\u2014/(\[{"\s])'/g, "$1\u2018") // closing singles & apostrophes .replace(/'/g, "\u2019") // opening doubles .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, "$1\u201c") // closing doubles .replace(/"/g, "\u201d") // ellipses .replace(/\.{3}/g, "\u2026") ); }; /** * Mangle Links */ InlineLexer.prototype.mangle = function (text) { if (!this.options.mangle) return text; var out = "", l = text.length, i = 0, ch; for (; i < l; i++) { ch = text.charCodeAt(i); if (Math.random() > 0.5) { ch = "x" + ch.toString(16); } out += "&#" + ch + ";"; } return out; }; /** * Renderer */ function Renderer(options) { this.options = options || {}; } Renderer.prototype.code = function (code, lang, escaped) { if (this.options.highlight) { var out = this.options.highlight(code, lang); if (out != null && out !== code) { escaped = true; code = out; } } if (!lang) { return ( "

" +
                  (escaped ? code : escape(code, true)) +
                  "\n
" ); } return ( '
' +
                (escaped ? code : escape(code, true)) +
                "\n
\n" ); }; Renderer.prototype.blockquote = function (quote) { return "
\n" + quote + "
\n"; }; Renderer.prototype.html = function (html) { return html; }; Renderer.prototype.heading = function (text, level, raw) { return ( "' + text + "\n" ); }; Renderer.prototype.hr = function () { return this.options.xhtml ? "
\n" : "
\n"; }; Renderer.prototype.list = function (body, ordered) { var type = ordered ? "ol" : "ul"; return "<" + type + ">\n" + body + "\n"; }; Renderer.prototype.listitem = function (text) { return "
  • " + text + "
  • \n"; }; Renderer.prototype.paragraph = function (text) { return "

    " + text + "

    \n"; }; Renderer.prototype.table = function (header, body) { return ( "\n" + "\n" + header + "\n" + "\n" + body + "\n" + "
    \n" ); }; Renderer.prototype.tablerow = function (content) { return "\n" + content + "\n"; }; Renderer.prototype.tablecell = function (content, flags) { var type = flags.header ? "th" : "td"; var tag = flags.align ? "<" + type + ' style="text-align:' + flags.align + '">' : "<" + type + ">"; return tag + content + "\n"; }; // span level renderer Renderer.prototype.strong = function (text) { return "" + text + ""; }; Renderer.prototype.em = function (text) { return "" + text + ""; }; Renderer.prototype.codespan = function (text) { return "" + text + ""; }; Renderer.prototype.br = function () { return this.options.xhtml ? "
    " : "
    "; }; Renderer.prototype.del = function (text) { return "" + text + ""; }; Renderer.prototype.link = function (href, title, text) { if (this.options.sanitize) { try { var prot = decodeURIComponent(unescape(href)) .replace(/[^\w:]/g, "") .toLowerCase(); } catch (e) { return ""; } if ( prot.indexOf("javascript:") === 0 || prot.indexOf("vbscript:") === 0 || prot.indexOf("data:") === 0 ) { return ""; } } if (this.options.baseUrl && !originIndependentUrl.test(href)) { href = resolveUrl(this.options.baseUrl, href); } var out = '
    "; return out; }; Renderer.prototype.image = function (href, title, text) { if (this.options.baseUrl && !originIndependentUrl.test(href)) { href = resolveUrl(this.options.baseUrl, href); } var out = '' + text + '" : ">"; return out; }; Renderer.prototype.text = function (text) { return text; }; /** * Parsing & Compiling */ function Parser(options) { this.tokens = []; this.token = null; this.options = options || marked.defaults; this.options.renderer = this.options.renderer || new Renderer(); this.renderer = this.options.renderer; this.renderer.options = this.options; } /** * Static Parse Method */ Parser.parse = function (src, options, renderer) { var parser = new Parser(options, renderer); return parser.parse(src); }; /** * Parse Loop */ Parser.prototype.parse = function (src) { this.inline = new InlineLexer( src.links, this.options, this.renderer, ); this.tokens = src.reverse(); var out = ""; while (this.next()) { out += this.tok(); } return out; }; /** * Next Token */ Parser.prototype.next = function () { return (this.token = this.tokens.pop()); }; /** * Preview Next Token */ Parser.prototype.peek = function () { return this.tokens[this.tokens.length - 1] || 0; }; /** * Parse Text Tokens */ Parser.prototype.parseText = function () { var body = this.token.text; while (this.peek().type === "text") { body += "\n" + this.next().text; } return this.inline.output(body); }; /** * Parse Current Token */ Parser.prototype.tok = function () { switch (this.token.type) { case "space": { return ""; } case "hr": { return this.renderer.hr(); } case "heading": { return this.renderer.heading( this.inline.output(this.token.text), this.token.depth, this.token.text, ); } case "code": { return this.renderer.code( this.token.text, this.token.lang, this.token.escaped, ); } case "table": { var header = "", body = "", i, row, cell, flags, j; // header cell = ""; for (i = 0; i < this.token.header.length; i++) { flags = { header: true, align: this.token.align[i] }; cell += this.renderer.tablecell( this.inline.output(this.token.header[i]), { header: true, align: this.token.align[i] }, ); } header += this.renderer.tablerow(cell); for (i = 0; i < this.token.cells.length; i++) { row = this.token.cells[i]; cell = ""; for (j = 0; j < row.length; j++) { cell += this.renderer.tablecell( this.inline.output(row[j]), { header: false, align: this.token.align[j] }, ); } body += this.renderer.tablerow(cell); } return this.renderer.table(header, body); } case "blockquote_start": { var body = ""; while (this.next().type !== "blockquote_end") { body += this.tok(); } return this.renderer.blockquote(body); } case "list_start": { var body = "", ordered = this.token.ordered; while (this.next().type !== "list_end") { body += this.tok(); } return this.renderer.list(body, ordered); } case "list_item_start": { var body = ""; while (this.next().type !== "list_item_end") { body += this.token.type === "text" ? this.parseText() : this.tok(); } return this.renderer.listitem(body); } case "loose_item_start": { var body = ""; while (this.next().type !== "list_item_end") { body += this.tok(); } return this.renderer.listitem(body); } case "html": { var html = !this.token.pre && !this.options.pedantic ? this.inline.output(this.token.text) : this.token.text; return this.renderer.html(html); } case "paragraph": { return this.renderer.paragraph( this.inline.output(this.token.text), ); } case "text": { return this.renderer.paragraph(this.parseText()); } } }; /** * Helpers */ function escape(html, encode) { return html .replace(!encode ? /&(?!#?\w+;)/g : /&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function unescape(html) { // explicitly match decimal, hex, and named HTML entities return html.replace( /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi, function (_, n) { n = n.toLowerCase(); if (n === "colon") return ":"; if (n.charAt(0) === "#") { return n.charAt(1) === "x" ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1)); } return ""; }, ); } function replace(regex, opt) { regex = regex.source; opt = opt || ""; return function self(name, val) { if (!name) return new RegExp(regex, opt); val = val.source || val; val = val.replace(/(^|[^\[])\^/g, "$1"); regex = regex.replace(name, val); return self; }; } function resolveUrl(base, href) { if (!baseUrls[" " + base]) { // we can ignore everything in base after the last slash of its path component, // but we might need to add _that_ // https://tools.ietf.org/html/rfc3986#section-3 if (/^[^:]+:\/*[^/]*$/.test(base)) { baseUrls[" " + base] = base + "/"; } else { baseUrls[" " + base] = base.replace(/[^/]*$/, ""); } } base = baseUrls[" " + base]; if (href.slice(0, 2) === "//") { return base.replace(/:[^]*/, ":") + href; } else if (href.charAt(0) === "/") { return base.replace(/(:\/*[^/]*)[^]*/, "$1") + href; } else { return base + href; } } baseUrls = {}; originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i; function noop() {} noop.exec = noop; function merge(obj) { var i = 1, target, key; for (; i < arguments.length; i++) { target = arguments[i]; for (key in target) { if (Object.prototype.hasOwnProperty.call(target, key)) { obj[key] = target[key]; } } } return obj; } /** * Marked */ function marked(src, opt, callback) { if (callback || typeof opt === "function") { if (!callback) { callback = opt; opt = null; } opt = merge({}, marked.defaults, opt || {}); var highlight = opt.highlight, tokens, pending, i = 0; try { tokens = Lexer.lex(src, opt); } catch (e) { return callback(e); } pending = tokens.length; var done = function (err) { if (err) { opt.highlight = highlight; return callback(err); } var out; try { out = Parser.parse(tokens, opt); } catch (e) { err = e; } opt.highlight = highlight; return err ? callback(err) : callback(null, out); }; if (!highlight || highlight.length < 3) { return done(); } delete opt.highlight; if (!pending) return done(); for (; i < tokens.length; i++) { (function (token) { if (token.type !== "code") { return --pending || done(); } return highlight( token.text, token.lang, function (err, code) { if (err) return done(err); if (code == null || code === token.text) { return --pending || done(); } token.text = code; token.escaped = true; --pending || done(); }, ); })(tokens[i]); } return; } try { if (opt) opt = merge({}, marked.defaults, opt); return Parser.parse(Lexer.lex(src, opt), opt); } catch (e) { e.message += "\nPlease report this to https://github.com/chjj/marked."; if ((opt || marked.defaults).silent) { return ( "

    An error occured:

    " +
                        escape(e.message + "", true) +
                        "
    " ); } throw e; } } /** * Options */ marked.options = marked.setOptions = function (opt) { merge(marked.defaults, opt); return marked; }; marked.defaults = { gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, sanitizer: null, mangle: true, smartLists: false, silent: false, highlight: null, langPrefix: "lang-", smartypants: false, headerPrefix: "", renderer: new Renderer(), xhtml: false, baseUrl: null, }; /** * Expose */ marked.Parser = Parser; marked.parser = Parser.parse; marked.Renderer = Renderer; marked.Lexer = Lexer; marked.lexer = Lexer.lex; marked.InlineLexer = InlineLexer; marked.inlineLexer = InlineLexer.output; marked.parse = marked; if (typeof module !== "undefined" && typeof exports === "object") { module.exports = marked; } else if (typeof define === "function" && define.amd) { define(function () { return marked; }); } else { this.marked = marked; } }).call( (function () { return this || (typeof window !== "undefined" ? window : global); })(), ); }).call( this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}, ); }, {}, ], 4: [ function (require, module, exports) { exports.apply = function () { forEach([Array, window.NodeList, window.HTMLCollection], extend); }; function forEach(list, f) { var i; for (i = 0; i < list.length; ++i) { f(list[i], i); } } function extend(object) { var prototype = object && object.prototype; if (!prototype) { return; } prototype.forEach = prototype.forEach || function (f) { forEach(this, f); }; prototype.filter = prototype.filter || function (f) { var result = []; this.forEach(function (element) { if (f(element, result.length)) { result.push(element); } }); return result; }; prototype.map = prototype.map || function (f) { var result = []; this.forEach(function (element) { result.push(f(element, result.length)); }); return result; }; } }, {}, ], 5: [ function (require, module, exports) { var Api = require("./remark/api"), polyfills = require("./polyfills"), styler = require("./remark/components/styler/styler"); // Expose API as `remark` window.remark = new Api(); // Apply polyfills as needed polyfills.apply(); // Apply embedded styles to document styler.styleDocument(); }, { "./polyfills": 4, "./remark/api": 6, "./remark/components/styler/styler": "components/styler", }, ], 6: [ function (require, module, exports) { var EventEmitter = require("events").EventEmitter, highlighter = require("./highlighter"), converter = require("./converter"), resources = require("./resources"), Parser = require("./parser"), Slideshow = require("./models/slideshow"), SlideshowView = require("./views/slideshowView"), DefaultController = require("./controllers/defaultController"), Dom = require("./dom"), macros = require("./macros"); module.exports = Api; function Api(dom) { this.dom = dom || new Dom(); this.macros = macros; this.version = resources.version; } // Expose highlighter to allow enumerating available styles and // including external language grammars Api.prototype.highlighter = highlighter; Api.prototype.convert = function (markdown) { var parser = new Parser(), content = parser.parse(markdown || "", macros)[0].content; return converter.convertMarkdown(content, {}, true); }; // Creates slideshow initialized from options Api.prototype.create = function (options, callback) { var self = this, events, slideshow, slideshowView, controller; options = applyDefaults(this.dom, options); events = new EventEmitter(); events.setMaxListeners(0); slideshow = new Slideshow(events, this.dom, options, function ( slideshow, ) { slideshowView = new SlideshowView( events, self.dom, options, slideshow, ); controller = options.controller || new DefaultController( events, self.dom, slideshowView, options.navigation, ); if (typeof callback === "function") { callback(slideshow); } }); return slideshow; }; function applyDefaults(dom, options) { var sourceElement; options = options || {}; if (!options.hasOwnProperty("source")) { sourceElement = dom.getElementById("source"); if (sourceElement) { options.source = unescape(sourceElement.innerHTML); sourceElement.style.display = "none"; } } if (!(options.container instanceof window.HTMLElement)) { options.container = dom.getBodyElement(); } return options; } function unescape(source) { source = source.replace(/&[l|g]t;/g, function (match) { return match === "<" ? "<" : ">"; }); source = source.replace(/&/g, "&"); source = source.replace(/"/g, '"'); return source; } }, { "./controllers/defaultController": 7, "./converter": 13, "./dom": 14, "./highlighter": 15, "./macros": 17, "./models/slideshow": 19, "./parser": 22, "./resources": 23, "./views/slideshowView": 28, events: 1, }, ], 7: [ function (require, module, exports) { // Allow override of global `location` /* global location:true */ module.exports = Controller; var Keyboard = require("./inputs/keyboard"), mouse = require("./inputs/mouse"), touch = require("./inputs/touch"), message = require("./inputs/message"), location = require("./inputs/location"); function Controller(events, dom, slideshowView, options) { options = options || {}; var keyboard = new Keyboard(events); message.register(events); location.register(events, dom, slideshowView); mouse.register(events, options); touch.register(events, options); addApiEventListeners(events, keyboard, slideshowView, options); } function addApiEventListeners( events, keyboard, slideshowView, options, ) { events.on("pause", function (event) { keyboard.deactivate(); mouse.unregister(events); touch.unregister(events); }); events.on("resume", function (event) { keyboard.activate(); mouse.register(events, options); touch.register(events, options); }); } }, { "./inputs/keyboard": 8, "./inputs/location": 9, "./inputs/message": 10, "./inputs/mouse": 11, "./inputs/touch": 12, }, ], 8: [ function (require, module, exports) { module.exports = Keyboard; function Keyboard(events) { this._events = events; this.activate(); } Keyboard.prototype.activate = function () { this._gotoSlideNumber = ""; this.addKeyboardEventListeners(); }; Keyboard.prototype.deactivate = function () { this.removeKeyboardEventListeners(); }; Keyboard.prototype.addKeyboardEventListeners = function () { var self = this; var events = this._events; events.on("keydown", function (event) { if (event.metaKey || event.ctrlKey || event.altKey) { // Bail out if alt, meta or ctrl key was pressed return; } switch (event.keyCode) { case 33: // Page up case 37: // Left case 38: // Up events.emit("gotoPreviousSlide"); break; case 32: // Space if (event.shiftKey) { // Shift+Space events.emit("gotoPreviousSlide"); } else { events.emit("gotoNextSlide"); } break; case 34: // Page down case 39: // Right case 40: // Down events.emit("gotoNextSlide"); break; case 36: // Home events.emit("gotoFirstSlide"); break; case 35: // End events.emit("gotoLastSlide"); break; case 27: // Escape events.emit("hideOverlay"); break; case 13: // Return if (self._gotoSlideNumber) { events.emit("gotoSlideNumber", self._gotoSlideNumber); self._gotoSlideNumber = ""; } break; } }); events.on("keypress", function (event) { if (event.metaKey || event.ctrlKey) { // Bail out if meta or ctrl key was pressed return; } var key = String.fromCharCode(event.which).toLowerCase(); var tryToPreventDefault = true; switch (key) { case "j": events.emit("gotoNextSlide"); break; case "k": events.emit("gotoPreviousSlide"); break; case "b": events.emit("toggleBlackout"); break; case "m": events.emit("toggleMirrored"); break; case "c": events.emit("createClone"); break; case "p": events.emit("togglePresenterMode"); break; case "f": events.emit("toggleFullscreen"); break; case "s": events.emit("toggleTimer"); break; case "t": events.emit("resetTimer"); break; case "1": case "2": case "3": case "4": case "5": case "6": case "7": case "8": case "9": case "0": self._gotoSlideNumber += key; break; case "h": case "?": events.emit("toggleHelp"); break; default: tryToPreventDefault = false; } if (tryToPreventDefault && event && event.preventDefault) event.preventDefault(); }); }; Keyboard.prototype.removeKeyboardEventListeners = function () { var events = this._events; events.removeAllListeners("keydown"); events.removeAllListeners("keypress"); }; }, {}, ], 9: [ function (require, module, exports) { var utils = require("../../utils.js"); exports.register = function (events, dom, slideshowView) { addLocationEventListeners(events, dom, slideshowView); }; function addLocationEventListeners(events, dom, slideshowView) { // If slideshow is embedded into custom DOM element, we don't // hook up to location hash changes, so just go to first slide. if (slideshowView.isEmbedded()) { events.emit("gotoSlide", 1); } // When slideshow is not embedded into custom DOM element, but // rather hosted directly inside document.body, we hook up to // location hash changes, and trigger initial navigation. else { events.on("hashchange", navigateByHash); events.on("slideChanged", updateHash); events.on("toggledPresenter", updateHash); navigateByHash(); } function navigateByHash() { var slideNoOrName = (dom.getLocationHash() || "").substr(1); events.emit("gotoSlide", slideNoOrName); } function updateHash(slideNoOrName) { if ( utils.hasClass( slideshowView.containerElement, "remark-presenter-mode", ) ) { dom.setLocationHash("#p" + slideNoOrName); } else { dom.setLocationHash("#" + slideNoOrName); } } } }, { "../../utils.js": 25 }, ], 10: [ function (require, module, exports) { exports.register = function (events) { addMessageEventListeners(events); }; function addMessageEventListeners(events) { events.on("message", navigateByMessage); function navigateByMessage(message) { var cap; if ((cap = /^gotoSlide:(\d+)$/.exec(message.data)) !== null) { events.emit("gotoSlide", parseInt(cap[1], 10), true); } else if (message.data === "toggleBlackout") { events.emit("toggleBlackout", { propagate: false }); } } } }, {}, ], 11: [ function (require, module, exports) { exports.register = function (events, options) { addMouseEventListeners(events, options); }; exports.unregister = function (events) { removeMouseEventListeners(events); }; function addMouseEventListeners(events, options) { if (options.click) { events.on("click", function (event) { if (event.target.nodeName === "A") { // Don't interfere when clicking link return; } else if (event.button === 0) { events.emit("gotoNextSlide"); } }); events.on("contextmenu", function (event) { if (event.target.nodeName === "A") { // Don't interfere when right-clicking link return; } event.preventDefault(); events.emit("gotoPreviousSlide"); }); } if (options.scroll !== false) { var scrollHandler = function (event) { if (event.wheelDeltaY > 0 || event.detail < 0) { events.emit("gotoPreviousSlide"); } else if (event.wheelDeltaY < 0 || event.detail > 0) { events.emit("gotoNextSlide"); } }; // IE9, Chrome, Safari, Opera events.on("mousewheel", scrollHandler); // Firefox events.on("DOMMouseScroll", scrollHandler); } } function removeMouseEventListeners(events) { events.removeAllListeners("click"); events.removeAllListeners("contextmenu"); events.removeAllListeners("mousewheel"); } }, {}, ], 12: [ function (require, module, exports) { exports.register = function (events, options) { addTouchEventListeners(events, options); }; exports.unregister = function (events) { removeTouchEventListeners(events); }; function addTouchEventListeners(events, options) { var touch, startX, endX; if (options.touch === false) { return; } var isTap = function () { return Math.abs(startX - endX) < 10; }; var handleTap = function () { events.emit("tap", endX); }; var handleSwipe = function () { if (startX > endX) { events.emit("gotoNextSlide"); } else { events.emit("gotoPreviousSlide"); } }; events.on("touchstart", function (event) { touch = event.touches[0]; startX = touch.clientX; }); events.on("touchend", function (event) { if (event.target.nodeName.toUpperCase() === "A") { return; } touch = event.changedTouches[0]; endX = touch.clientX; if (isTap()) { handleTap(); } else { handleSwipe(); } }); events.on("touchmove", function (event) { event.preventDefault(); }); } function removeTouchEventListeners(events) { events.removeAllListeners("touchstart"); events.removeAllListeners("touchend"); events.removeAllListeners("touchmove"); } }, {}, ], 13: [ function (require, module, exports) { var marked = require("marked"), converter = (module.exports = {}), element = document.createElement("div"); marked.setOptions({ gfm: true, tables: true, breaks: false, // Without this set to true, converting something like //

    *

    *

    will become

    pedantic: true, sanitize: false, smartLists: true, langPrefix: "", }); converter.convertMarkdown = function (content, links, inline) { element.innerHTML = convertMarkdown(content, links || {}, inline); element.innerHTML = element.innerHTML.replace(/

    \s*<\/p>/g, ""); return element.innerHTML.replace(/\n\r?$/, ""); }; function convertMarkdown(content, links, insideContentClass) { var i, tag, markdown = "", html; for (i = 0; i < content.length; ++i) { if (typeof content[i] === "string") { markdown += content[i]; } else { tag = content[i].block ? "div" : "span"; markdown += "<" + tag + ' class="' + content[i].class + '">'; markdown += convertMarkdown( content[i].content, links, !content[i].block, ); markdown += ""; } } var tokens = marked.Lexer.lex(markdown.replace(/^\s+/, "")); tokens.links = links; html = marked.Parser.parse(tokens); if (insideContentClass) { element.innerHTML = html; if ( element.children.length === 1 && element.children[0].tagName === "P" ) { html = element.children[0].innerHTML; } } return html; } }, { marked: 3 }, ], 14: [ function (require, module, exports) { module.exports = Dom; function Dom() {} Dom.prototype.XMLHttpRequest = XMLHttpRequest; Dom.prototype.getHTMLElement = function () { return document.getElementsByTagName("html")[0]; }; Dom.prototype.getBodyElement = function () { return document.body; }; Dom.prototype.getElementById = function (id) { return document.getElementById(id); }; Dom.prototype.getLocationHash = function () { return window.location.hash; }; Dom.prototype.setLocationHash = function (hash) { if ( typeof window.history.replaceState === "function" && window.origin !== "null" ) { window.history.replaceState(undefined, undefined, hash); } else { window.location.hash = hash; } }; }, {}, ], 15: [ function (require, module, exports) { /* Automatically generated */ var hljs = (function () { var exports = {}; /* Syntax highlighting with language autodetection. https://highlightjs.org/ */ (function (factory) { // Find the global object for export to both the browser and web workers. var globalObject = (typeof window === "object" && window) || (typeof self === "object" && self); // Setup highlight.js for different environments. First is Node.js or // CommonJS. // `nodeType` is checked to ensure that `exports` is not a HTML element. if (typeof exports !== "undefined" && !exports.nodeType) { factory(exports); } else if (globalObject) { // Export hljs globally even when using AMD for cases when this script // is loaded with others that may still expect a global hljs. globalObject.hljs = factory({}); // Finally register the global hljs with AMD. if (typeof define === "function" && define.amd) { define([], function () { return globalObject.hljs; }); } } })(function (hljs) { // Convenience variables for build-in objects var ArrayProto = [], objectKeys = Object.keys; // Global internal variables used within the highlight.js library. var languages = {}, aliases = {}; // Regular expressions used throughout the highlight.js library. var noHighlightRe = /^(no-?highlight|plain|text)$/i, languagePrefixRe = /\blang(?:uage)?-([\w-]+)\b/i, fixMarkupRe = /((^(<[^>]+>|\t|)+|(?:\n)))/gm; // The object will be assigned by the build tool. It used to synchronize API // of external language files with minified version of the highlight.js library. var API_REPLACES; var spanEndTag = ""; // Global options used when within external APIs. This is modified when // calling the `hljs.configure` function. var options = { classPrefix: "hljs-", tabReplace: null, useBR: false, languages: undefined, }; /* Utility functions */ function escape(value) { return value .replace(/&/g, "&") .replace(//g, ">"); } function tag(node) { return node.nodeName.toLowerCase(); } function testRe(re, lexeme) { var match = re && re.exec(lexeme); return match && match.index === 0; } function isNotHighlighted(language) { return noHighlightRe.test(language); } function blockLanguage(block) { var i, match, length, _class; var classes = block.className + " "; classes += block.parentNode ? block.parentNode.className : ""; // language-* takes precedence over non-prefixed class names. match = languagePrefixRe.exec(classes); if (match) { return getLanguage(match[1]) ? match[1] : "no-highlight"; } classes = classes.split(/\s+/); for (i = 0, length = classes.length; i < length; i++) { _class = classes[i]; if (isNotHighlighted(_class) || getLanguage(_class)) { return _class; } } } function inherit(parent) { // inherit(parent, override_obj, override_obj, ...) var key; var result = {}; var objects = Array.prototype.slice.call(arguments, 1); for (key in parent) result[key] = parent[key]; objects.forEach(function (obj) { for (key in obj) result[key] = obj[key]; }); return result; } /* Stream merging */ function nodeStream(node) { var result = []; (function _nodeStream(node, offset) { for ( var child = node.firstChild; child; child = child.nextSibling ) { if (child.nodeType === 3) offset += child.nodeValue.length; else if (child.nodeType === 1) { result.push({ event: "start", offset: offset, node: child, }); offset = _nodeStream(child, offset); // Prevent void elements from having an end tag that would actually // double them in the output. There are more void elements in HTML // but we list only those realistically expected in code display. if (!tag(child).match(/br|hr|img|input/)) { result.push({ event: "stop", offset: offset, node: child, }); } } } return offset; })(node, 0); return result; } function mergeStreams(original, highlighted, value) { var processed = 0; var result = ""; var nodeStack = []; function selectStream() { if (!original.length || !highlighted.length) { return original.length ? original : highlighted; } if (original[0].offset !== highlighted[0].offset) { return original[0].offset < highlighted[0].offset ? original : highlighted; } /* To avoid starting the stream just before it should stop the order is ensured that original always starts first and closes last: if (event1 == 'start' && event2 == 'start') return original; if (event1 == 'start' && event2 == 'stop') return highlighted; if (event1 == 'stop' && event2 == 'start') return original; if (event1 == 'stop' && event2 == 'stop') return highlighted; ... which is collapsed to: */ return highlighted[0].event === "start" ? original : highlighted; } function open(node) { function attr_str(a) { return ( " " + a.nodeName + '="' + escape(a.value).replace('"', """) + '"' ); } result += "<" + tag(node) + ArrayProto.map.call(node.attributes, attr_str).join("") + ">"; } function close(node) { result += ""; } function render(event) { (event.event === "start" ? open : close)(event.node); } while (original.length || highlighted.length) { var stream = selectStream(); result += escape( value.substring(processed, stream[0].offset), ); processed = stream[0].offset; if (stream === original) { /* On any opening or closing tag of the original markup we first close the entire highlighted node stack, then render the original tag along with all the following original tags at the same offset and then reopen all the tags on the highlighted stack. */ nodeStack.reverse().forEach(close); do { render(stream.splice(0, 1)[0]); stream = selectStream(); } while ( stream === original && stream.length && stream[0].offset === processed ); nodeStack.reverse().forEach(open); } else { if (stream[0].event === "start") { nodeStack.push(stream[0].node); } else { nodeStack.pop(); } render(stream.splice(0, 1)[0]); } } return result + escape(value.substr(processed)); } /* Initialization */ function expand_mode(mode) { if (mode.variants && !mode.cached_variants) { mode.cached_variants = mode.variants.map(function (variant) { return inherit(mode, { variants: null }, variant); }); } return ( mode.cached_variants || (mode.endsWithParent && [inherit(mode)]) || [mode] ); } function restoreLanguageApi(obj) { if (API_REPLACES && !obj.langApiRestored) { obj.langApiRestored = true; for (var key in API_REPLACES) obj[key] && (obj[API_REPLACES[key]] = obj[key]); (obj.contains || []) .concat(obj.variants || []) .forEach(restoreLanguageApi); } } function compileLanguage(language) { function reStr(re) { return (re && re.source) || re; } function langRe(value, global) { return new RegExp( reStr(value), "m" + (language.case_insensitive ? "i" : "") + (global ? "g" : ""), ); } // joinRe logically computes regexps.join(separator), but fixes the // backreferences so they continue to match. function joinRe(regexps, separator) { // backreferenceRe matches an open parenthesis or backreference. To avoid // an incorrect parse, it additionally matches the following: // - [...] elements, where the meaning of parentheses and escapes change // - other escape sequences, so we do not misparse escape sequences as // interesting elements // - non-matching or lookahead parentheses, which do not capture. These // follow the '(' with a '?'. var backreferenceRe = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./; var numCaptures = 0; var ret = ""; for (var i = 0; i < regexps.length; i++) { var offset = numCaptures; var re = reStr(regexps[i]); if (i > 0) { ret += separator; } while (re.length > 0) { var match = backreferenceRe.exec(re); if (match == null) { ret += re; break; } ret += re.substring(0, match.index); re = re.substring(match.index + match[0].length); if (match[0][0] == "\\" && match[1]) { // Adjust the backreference. ret += "\\" + String(Number(match[1]) + offset); } else { ret += match[0]; if (match[0] == "(") { numCaptures++; } } } } return ret; } function compileMode(mode, parent) { if (mode.compiled) return; mode.compiled = true; mode.keywords = mode.keywords || mode.beginKeywords; if (mode.keywords) { var compiled_keywords = {}; var flatten = function (className, str) { if (language.case_insensitive) { str = str.toLowerCase(); } str.split(" ").forEach(function (kw) { var pair = kw.split("|"); compiled_keywords[pair[0]] = [ className, pair[1] ? Number(pair[1]) : 1, ]; }); }; if (typeof mode.keywords === "string") { // string flatten("keyword", mode.keywords); } else { objectKeys(mode.keywords).forEach(function (className) { flatten(className, mode.keywords[className]); }); } mode.keywords = compiled_keywords; } mode.lexemesRe = langRe(mode.lexemes || /\w+/, true); if (parent) { if (mode.beginKeywords) { mode.begin = "\\b(" + mode.beginKeywords.split(" ").join("|") + ")\\b"; } if (!mode.begin) mode.begin = /\B|\b/; mode.beginRe = langRe(mode.begin); if (mode.endSameAsBegin) mode.end = mode.begin; if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/; if (mode.end) mode.endRe = langRe(mode.end); mode.terminator_end = reStr(mode.end) || ""; if (mode.endsWithParent && parent.terminator_end) mode.terminator_end += (mode.end ? "|" : "") + parent.terminator_end; } if (mode.illegal) mode.illegalRe = langRe(mode.illegal); if (mode.relevance == null) mode.relevance = 1; if (!mode.contains) { mode.contains = []; } mode.contains = Array.prototype.concat.apply( [], mode.contains.map(function (c) { return expand_mode(c === "self" ? mode : c); }), ); mode.contains.forEach(function (c) { compileMode(c, mode); }); if (mode.starts) { compileMode(mode.starts, parent); } var terminators = mode.contains .map(function (c) { return c.beginKeywords ? "\\.?(?:" + c.begin + ")\\.?" : c.begin; }) .concat([mode.terminator_end, mode.illegal]) .map(reStr) .filter(Boolean); mode.terminators = terminators.length ? langRe(joinRe(terminators, "|"), true) : { exec: function (/*s*/) { return null; }, }; } compileMode(language); } /* Core highlighting function. Accepts a language name, or an alias, and a string with the code to highlight. Returns an object with the following properties: - relevance (int) - value (an HTML string with highlighting markup) */ function highlight(name, value, ignore_illegals, continuation) { function escapeRe(value) { return new RegExp( value.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"), "m", ); } function subMode(lexeme, mode) { var i, length; for (i = 0, length = mode.contains.length; i < length; i++) { if (testRe(mode.contains[i].beginRe, lexeme)) { if (mode.contains[i].endSameAsBegin) { mode.contains[i].endRe = escapeRe( mode.contains[i].beginRe.exec(lexeme)[0], ); } return mode.contains[i]; } } } function endOfMode(mode, lexeme) { if (testRe(mode.endRe, lexeme)) { while (mode.endsParent && mode.parent) { mode = mode.parent; } return mode; } if (mode.endsWithParent) { return endOfMode(mode.parent, lexeme); } } function isIllegal(lexeme, mode) { return !ignore_illegals && testRe(mode.illegalRe, lexeme); } function keywordMatch(mode, match) { var match_str = language.case_insensitive ? match[0].toLowerCase() : match[0]; return ( mode.keywords.hasOwnProperty(match_str) && mode.keywords[match_str] ); } function buildSpan(classname, insideSpan, leaveOpen, noPrefix) { var classPrefix = noPrefix ? "" : options.classPrefix, openSpan = ''; if (!classname) return insideSpan; return openSpan + insideSpan + closeSpan; } function processKeywords() { var keyword_match, last_index, match, result; if (!top.keywords) return escape(mode_buffer); result = ""; last_index = 0; top.lexemesRe.lastIndex = 0; match = top.lexemesRe.exec(mode_buffer); while (match) { result += escape( mode_buffer.substring(last_index, match.index), ); keyword_match = keywordMatch(top, match); if (keyword_match) { relevance += keyword_match[1]; result += buildSpan(keyword_match[0], escape(match[0])); } else { result += escape(match[0]); } last_index = top.lexemesRe.lastIndex; match = top.lexemesRe.exec(mode_buffer); } return result + escape(mode_buffer.substr(last_index)); } function processSubLanguage() { var explicit = typeof top.subLanguage === "string"; if (explicit && !languages[top.subLanguage]) { return escape(mode_buffer); } var result = explicit ? highlight( top.subLanguage, mode_buffer, true, continuations[top.subLanguage], ) : highlightAuto( mode_buffer, top.subLanguage.length ? top.subLanguage : undefined, ); // Counting embedded language score towards the host language may be disabled // with zeroing the containing mode relevance. Usecase in point is Markdown that // allows XML everywhere and makes every XML snippet to have a much larger Markdown // score. if (top.relevance > 0) { relevance += result.relevance; } if (explicit) { continuations[top.subLanguage] = result.top; } return buildSpan(result.language, result.value, false, true); } function processBuffer() { result += top.subLanguage != null ? processSubLanguage() : processKeywords(); mode_buffer = ""; } function startNewMode(mode) { result += mode.className ? buildSpan(mode.className, "", true) : ""; top = Object.create(mode, { parent: { value: top } }); } function processLexeme(buffer, lexeme) { mode_buffer += buffer; if (lexeme == null) { processBuffer(); return 0; } var new_mode = subMode(lexeme, top); if (new_mode) { if (new_mode.skip) { mode_buffer += lexeme; } else { if (new_mode.excludeBegin) { mode_buffer += lexeme; } processBuffer(); if (!new_mode.returnBegin && !new_mode.excludeBegin) { mode_buffer = lexeme; } } startNewMode(new_mode, lexeme); return new_mode.returnBegin ? 0 : lexeme.length; } var end_mode = endOfMode(top, lexeme); if (end_mode) { var origin = top; if (origin.skip) { mode_buffer += lexeme; } else { if (!(origin.returnEnd || origin.excludeEnd)) { mode_buffer += lexeme; } processBuffer(); if (origin.excludeEnd) { mode_buffer = lexeme; } } do { if (top.className) { result += spanEndTag; } if (!top.skip && !top.subLanguage) { relevance += top.relevance; } top = top.parent; } while (top !== end_mode.parent); if (end_mode.starts) { if (end_mode.endSameAsBegin) { end_mode.starts.endRe = end_mode.endRe; } startNewMode(end_mode.starts, ""); } return origin.returnEnd ? 0 : lexeme.length; } if (isIllegal(lexeme, top)) throw new Error( 'Illegal lexeme "' + lexeme + '" for mode "' + (top.className || "") + '"', ); /* Parser should not reach this point as all types of lexemes should be caught earlier, but if it does due to some bug make sure it advances at least one character forward to prevent infinite looping. */ mode_buffer += lexeme; return lexeme.length || 1; } var language = getLanguage(name); if (!language) { throw new Error('Unknown language: "' + name + '"'); } compileLanguage(language); var top = continuation || language; var continuations = {}; // keep continuations for sub-languages var result = "", current; for ( current = top; current !== language; current = current.parent ) { if (current.className) { result = buildSpan(current.className, "", true) + result; } } var mode_buffer = ""; var relevance = 0; try { var match, count, index = 0; while (true) { top.terminators.lastIndex = index; match = top.terminators.exec(value); if (!match) break; count = processLexeme( value.substring(index, match.index), match[0], ); index = match.index + count; } processLexeme(value.substr(index)); for ( current = top; current.parent; current = current.parent ) { // close dangling modes if (current.className) { result += spanEndTag; } } return { relevance: relevance, value: result, language: name, top: top, }; } catch (e) { if (e.message && e.message.indexOf("Illegal") !== -1) { return { relevance: 0, value: escape(value), }; } else { throw e; } } } /* Highlighting with language detection. Accepts a string with the code to highlight. Returns an object with the following properties: - language (detected language) - relevance (int) - value (an HTML string with highlighting markup) - second_best (object with the same structure for second-best heuristically detected language, may be absent) */ function highlightAuto(text, languageSubset) { languageSubset = languageSubset || options.languages || objectKeys(languages); var result = { relevance: 0, value: escape(text), }; var second_best = result; languageSubset .filter(getLanguage) .filter(autoDetection) .forEach(function (name) { var current = highlight(name, text, false); current.language = name; if (current.relevance > second_best.relevance) { second_best = current; } if (current.relevance > result.relevance) { second_best = result; result = current; } }); if (second_best.language) { result.second_best = second_best; } return result; } /* Post-processing of the highlighted markup: - replace TABs with something more useful - replace real line-breaks with '
    ' for non-pre containers */ function fixMarkup(value) { return !(options.tabReplace || options.useBR) ? value : value.replace(fixMarkupRe, function (match, p1) { if (options.useBR && match === "\n") { return "
    "; } else if (options.tabReplace) { return p1.replace(/\t/g, options.tabReplace); } return ""; }); } function buildClassName(prevClassName, currentLang, resultLang) { var language = currentLang ? aliases[currentLang] : resultLang, result = [prevClassName.trim()]; if (!prevClassName.match(/\bhljs\b/)) { result.push("hljs"); } if (prevClassName.indexOf(language) === -1) { result.push(language); } return result.join(" ").trim(); } /* Applies highlighting to a DOM node containing code. Accepts a DOM node and two optional parameters for fixMarkup. */ function highlightBlock(block) { var node, originalStream, result, resultNode, text; var language = blockLanguage(block); if (isNotHighlighted(language)) return; if (options.useBR) { node = document.createElementNS( "http://www.w3.org/1999/xhtml", "div", ); node.innerHTML = block.innerHTML .replace(/\n/g, "") .replace(//g, "\n"); } else { node = block; } text = node.textContent; result = language ? highlight(language, text, true) : highlightAuto(text); originalStream = nodeStream(node); if (originalStream.length) { resultNode = document.createElementNS( "http://www.w3.org/1999/xhtml", "div", ); resultNode.innerHTML = result.value; result.value = mergeStreams( originalStream, nodeStream(resultNode), text, ); } result.value = fixMarkup(result.value); block.innerHTML = result.value; block.className = buildClassName( block.className, language, result.language, ); block.result = { language: result.language, re: result.relevance, }; if (result.second_best) { block.second_best = { language: result.second_best.language, re: result.second_best.relevance, }; } } /* Updates highlight.js global options with values passed in the form of an object. */ function configure(user_options) { options = inherit(options, user_options); } /* Applies highlighting to all

    ..
    blocks on a page. */ function initHighlighting() { if (initHighlighting.called) return; initHighlighting.called = true; var blocks = document.querySelectorAll("pre code"); ArrayProto.forEach.call(blocks, highlightBlock); } /* Attaches highlighting to the page load event. */ function initHighlightingOnLoad() { addEventListener("DOMContentLoaded", initHighlighting, false); addEventListener("load", initHighlighting, false); } function registerLanguage(name, language) { var lang = (languages[name] = language(hljs)); restoreLanguageApi(lang); if (lang.aliases) { lang.aliases.forEach(function (alias) { aliases[alias] = name; }); } } function listLanguages() { return objectKeys(languages); } function getLanguage(name) { name = (name || "").toLowerCase(); return languages[name] || languages[aliases[name]]; } function autoDetection(name) { var lang = getLanguage(name); return lang && !lang.disableAutodetect; } /* Interface definition */ hljs.highlight = highlight; hljs.highlightAuto = highlightAuto; hljs.fixMarkup = fixMarkup; hljs.highlightBlock = highlightBlock; hljs.configure = configure; hljs.initHighlighting = initHighlighting; hljs.initHighlightingOnLoad = initHighlightingOnLoad; hljs.registerLanguage = registerLanguage; hljs.listLanguages = listLanguages; hljs.getLanguage = getLanguage; hljs.autoDetection = autoDetection; hljs.inherit = inherit; // Common regexps hljs.IDENT_RE = "[a-zA-Z]\\w*"; hljs.UNDERSCORE_IDENT_RE = "[a-zA-Z_]\\w*"; hljs.NUMBER_RE = "\\b\\d+(\\.\\d+)?"; hljs.C_NUMBER_RE = "(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)"; // 0x..., 0..., decimal, float hljs.BINARY_NUMBER_RE = "\\b(0b[01]+)"; // 0b... hljs.RE_STARTERS_RE = "!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~"; // Common modes hljs.BACKSLASH_ESCAPE = { begin: "\\\\[\\s\\S]", relevance: 0, }; hljs.APOS_STRING_MODE = { className: "string", begin: "'", end: "'", illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], }; hljs.QUOTE_STRING_MODE = { className: "string", begin: '"', end: '"', illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], }; hljs.PHRASAL_WORDS_MODE = { begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/, }; hljs.COMMENT = function (begin, end, inherits) { var mode = hljs.inherit( { className: "comment", begin: begin, end: end, contains: [], }, inherits || {}, ); mode.contains.push(hljs.PHRASAL_WORDS_MODE); mode.contains.push({ className: "doctag", begin: "(?:TODO|FIXME|NOTE|BUG|XXX):", relevance: 0, }); return mode; }; hljs.C_LINE_COMMENT_MODE = hljs.COMMENT("//", "$"); hljs.C_BLOCK_COMMENT_MODE = hljs.COMMENT("/\\*", "\\*/"); hljs.HASH_COMMENT_MODE = hljs.COMMENT("#", "$"); hljs.NUMBER_MODE = { className: "number", begin: hljs.NUMBER_RE, relevance: 0, }; hljs.C_NUMBER_MODE = { className: "number", begin: hljs.C_NUMBER_RE, relevance: 0, }; hljs.BINARY_NUMBER_MODE = { className: "number", begin: hljs.BINARY_NUMBER_RE, relevance: 0, }; hljs.CSS_NUMBER_MODE = { className: "number", begin: hljs.NUMBER_RE + "(" + "%|em|ex|ch|rem" + "|vw|vh|vmin|vmax" + "|cm|mm|in|pt|pc|px" + "|deg|grad|rad|turn" + "|s|ms" + "|Hz|kHz" + "|dpi|dpcm|dppx" + ")?", relevance: 0, }; hljs.REGEXP_MODE = { className: "regexp", begin: /\//, end: /\/[gimuy]*/, illegal: /\n/, contains: [ hljs.BACKSLASH_ESCAPE, { begin: /\[/, end: /\]/, relevance: 0, contains: [hljs.BACKSLASH_ESCAPE], }, ], }; hljs.TITLE_MODE = { className: "title", begin: hljs.IDENT_RE, relevance: 0, }; hljs.UNDERSCORE_TITLE_MODE = { className: "title", begin: hljs.UNDERSCORE_IDENT_RE, relevance: 0, }; hljs.METHOD_GUARD = { // excludes method names from keyword processing begin: "\\.\\s*" + hljs.UNDERSCORE_IDENT_RE, relevance: 0, }; return hljs; }); return exports; })(), languages = [ { name: "cpp", /* Language: C++ Author: Ivan Sagalaev Contributors: Evgeny Stepanischev , Zaven Muradyan , Roel Deckers , Sam Wu , Jordi Petit , Pieter Vantorre , Google Inc. (David Benjamin) Category: common, system */ create: function (hljs) { var CPP_PRIMITIVE_TYPES = { className: "keyword", begin: "\\b[a-z\\d_]*_t\\b", }; var STRINGS = { className: "string", variants: [ { begin: '(u8?|U|L)?"', end: '"', illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /(?:u8?|U|L)?R"([^()\\ ]{0,16})\((?:.|\n)*?\)\1"/, }, { begin: "'\\\\?.", end: "'", illegal: ".", }, ], }; var NUMBERS = { className: "number", variants: [ { begin: "\\b(0b[01']+)" }, { begin: "(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)", }, { begin: "(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)", }, ], relevance: 0, }; var PREPROCESSOR = { className: "meta", begin: /#\s*[a-z]+\b/, end: /$/, keywords: { "meta-keyword": "if else elif endif define undef warning error line " + "pragma ifdef ifndef include", }, contains: [ { begin: /\\\n/, relevance: 0, }, hljs.inherit(STRINGS, { className: "meta-string" }), { className: "meta-string", begin: /<[^\n>]*>/, end: /$/, illegal: "\\n", }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }; var FUNCTION_TITLE = hljs.IDENT_RE + "\\s*\\("; var CPP_KEYWORDS = { keyword: "int float while private char catch import module export virtual operator sizeof " + "dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace " + "unsigned long volatile static protected bool template mutable if public friend " + "do goto auto void enum else break extern using asm case typeid " + "short reinterpret_cast|10 default double register explicit signed typename try this " + "switch continue inline delete alignof constexpr decltype " + "noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary " + "atomic_bool atomic_char atomic_schar " + "atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong " + "atomic_ullong new throw return " + "and or not", built_in: "std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream " + "auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set " + "unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos " + "asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp " + "fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper " + "isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow " + "printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp " + "strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan " + "vfprintf vprintf vsprintf endl initializer_list unique_ptr", literal: "true false nullptr NULL", }; var EXPRESSION_CONTAINS = [ CPP_PRIMITIVE_TYPES, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, NUMBERS, STRINGS, ]; return { aliases: [ "c", "cc", "h", "c++", "h++", "hpp", "hh", "hxx", "cxx", ], keywords: CPP_KEYWORDS, illegal: "", keywords: CPP_KEYWORDS, contains: ["self", CPP_PRIMITIVE_TYPES], }, { begin: hljs.IDENT_RE + "::", keywords: CPP_KEYWORDS, }, { // This mode covers expression context where we can't expect a function // definition and shouldn't highlight anything that looks like one: // `return some()`, `else if()`, `(x*sum(1, 2))` variants: [ { begin: /=/, end: /;/ }, { begin: /\(/, end: /\)/ }, { beginKeywords: "new throw return else", end: /;/ }, ], keywords: CPP_KEYWORDS, contains: EXPRESSION_CONTAINS.concat([ { begin: /\(/, end: /\)/, keywords: CPP_KEYWORDS, contains: EXPRESSION_CONTAINS.concat(["self"]), relevance: 0, }, ]), relevance: 0, }, { className: "function", begin: "(" + hljs.IDENT_RE + "[\\*&\\s]+)+" + FUNCTION_TITLE, returnBegin: true, end: /[{;=]/, excludeEnd: true, keywords: CPP_KEYWORDS, illegal: /[^\w\s\*&]/, contains: [ { begin: FUNCTION_TITLE, returnBegin: true, contains: [hljs.TITLE_MODE], relevance: 0, }, { className: "params", begin: /\(/, end: /\)/, keywords: CPP_KEYWORDS, relevance: 0, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, STRINGS, NUMBERS, CPP_PRIMITIVE_TYPES, // Count matching parentheses. { begin: /\(/, end: /\)/, keywords: CPP_KEYWORDS, relevance: 0, contains: [ "self", hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, STRINGS, NUMBERS, CPP_PRIMITIVE_TYPES, ], }, ], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, PREPROCESSOR, ], }, { className: "class", beginKeywords: "class struct", end: /[{;:]/, contains: [ { begin: //, contains: ["self"] }, // skip generic stuff hljs.TITLE_MODE, ], }, ]), exports: { preprocessor: PREPROCESSOR, strings: STRINGS, keywords: CPP_KEYWORDS, }, }; }, }, { name: "1c", /* Language: 1C:Enterprise (v7, v8) Author: Stanislav Belov Description: built-in language 1C:Enterprise (v7, v8) Category: enterprise */ create: function (hljs) { // общий паттерн для определения идентификаторов var UNDERSCORE_IDENT_RE = "[A-Za-zА-Яа-яёЁ_][A-Za-zА-Яа-яёЁ_0-9]+"; // v7 уникальные ключевые слова, отсутствующие в v8 ==> keyword var v7_keywords = "далее "; // v8 ключевые слова ==> keyword var v8_keywords = "возврат вызватьисключение выполнить для если и из или иначе иначеесли исключение каждого конецесли " + "конецпопытки конеццикла не новый перейти перем по пока попытка прервать продолжить тогда цикл экспорт "; // keyword : ключевые слова var KEYWORD = v7_keywords + v8_keywords; // v7 уникальные директивы, отсутствующие в v8 ==> meta-keyword var v7_meta_keywords = "загрузитьизфайла "; // v8 ключевые слова в инструкциях препроцессора, директивах компиляции, аннотациях ==> meta-keyword var v8_meta_keywords = "вебклиент вместо внешнеесоединение клиент конецобласти мобильноеприложениеклиент мобильноеприложениесервер " + "наклиенте наклиентенасервере наклиентенасерверебезконтекста насервере насерверебезконтекста область перед " + "после сервер толстыйклиентобычноеприложение толстыйклиентуправляемоеприложение тонкийклиент "; // meta-keyword : ключевые слова в инструкциях препроцессора, директивах компиляции, аннотациях var METAKEYWORD = v7_meta_keywords + v8_meta_keywords; // v7 системные константы ==> built_in var v7_system_constants = "разделительстраниц разделительстрок символтабуляции "; // v7 уникальные методы глобального контекста, отсутствующие в v8 ==> built_in var v7_global_context_methods = "ansitooem oemtoansi ввестивидсубконто ввестиперечисление ввестипериод ввестиплансчетов выбранныйплансчетов " + "датагод датамесяц датачисло заголовоксистемы значениевстроку значениеизстроки каталогиб каталогпользователя " + "кодсимв конгода конецпериодаби конецрассчитанногопериодаби конецстандартногоинтервала конквартала конмесяца " + "коннедели лог лог10 максимальноеколичествосубконто названиеинтерфейса названиенабораправ назначитьвид " + "назначитьсчет найтиссылки началопериодаби началостандартногоинтервала начгода начквартала начмесяца " + "начнедели номерднягода номерднянедели номернеделигода обработкаожидания основнойжурналрасчетов " + "основнойплансчетов основнойязык очиститьокносообщений периодстр получитьвремята получитьдатута " + "получитьдокументта получитьзначенияотбора получитьпозициюта получитьпустоезначение получитьта " + "префиксавтонумерации пропись пустоезначение разм разобратьпозициюдокумента рассчитатьрегистрына " + "рассчитатьрегистрыпо симв создатьобъект статусвозврата стрколичествострок сформироватьпозициюдокумента " + "счетпокоду текущеевремя типзначения типзначениястр установитьтана установитьтапо фиксшаблон шаблон "; // v8 методы глобального контекста ==> built_in var v8_global_context_methods = "acos asin atan base64значение base64строка cos exp log log10 pow sin sqrt tan xmlзначение xmlстрока " + "xmlтип xmlтипзнч активноеокно безопасныйрежим безопасныйрежимразделенияданных булево ввестидату ввестизначение " + "ввестистроку ввестичисло возможностьчтенияxml вопрос восстановитьзначение врег выгрузитьжурналрегистрации " + "выполнитьобработкуоповещения выполнитьпроверкуправдоступа вычислить год данныеформывзначение дата день деньгода " + "деньнедели добавитьмесяц заблокироватьданныедляредактирования заблокироватьработупользователя завершитьработусистемы " + "загрузитьвнешнююкомпоненту закрытьсправку записатьjson записатьxml записатьдатуjson записьжурналарегистрации " + "заполнитьзначениясвойств запроситьразрешениепользователя запуститьприложение запуститьсистему зафиксироватьтранзакцию " + "значениевданныеформы значениевстрокувнутр значениевфайл значениезаполнено значениеизстрокивнутр значениеизфайла " + "изxmlтипа импортмоделиxdto имякомпьютера имяпользователя инициализироватьпредопределенныеданные информацияобошибке " + "каталогбиблиотекимобильногоустройства каталогвременныхфайлов каталогдокументов каталогпрограммы кодироватьстроку " + "кодлокализацииинформационнойбазы кодсимвола командасистемы конецгода конецдня конецквартала конецмесяца конецминуты " + "конецнедели конецчаса конфигурациябазыданныхизмененадинамически конфигурацияизменена копироватьданныеформы " + "копироватьфайл краткоепредставлениеошибки лев макс местноевремя месяц мин минута монопольныйрежим найти " + "найтинедопустимыесимволыxml найтиокнопонавигационнойссылке найтипомеченныенаудаление найтипоссылкам найтифайлы " + "началогода началодня началоквартала началомесяца началоминуты началонедели началочаса начатьзапросразрешенияпользователя " + "начатьзапускприложения начатькопированиефайла начатьперемещениефайла начатьподключениевнешнейкомпоненты " + "начатьподключениерасширенияработыскриптографией начатьподключениерасширенияработысфайлами начатьпоискфайлов " + "начатьполучениекаталогавременныхфайлов начатьполучениекаталогадокументов начатьполучениерабочегокаталогаданныхпользователя " + "начатьполучениефайлов начатьпомещениефайла начатьпомещениефайлов начатьсозданиедвоичныхданныхизфайла начатьсозданиекаталога " + "начатьтранзакцию начатьудалениефайлов начатьустановкувнешнейкомпоненты начатьустановкурасширенияработыскриптографией " + "начатьустановкурасширенияработысфайлами неделягода необходимостьзавершениясоединения номерсеансаинформационнойбазы " + "номерсоединенияинформационнойбазы нрег нстр обновитьинтерфейс обновитьнумерациюобъектов обновитьповторноиспользуемыезначения " + "обработкапрерыванияпользователя объединитьфайлы окр описаниеошибки оповестить оповеститьобизменении " + "отключитьобработчикзапросанастроекклиенталицензирования отключитьобработчикожидания отключитьобработчикоповещения " + "открытьзначение открытьиндекссправки открытьсодержаниесправки открытьсправку открытьформу открытьформумодально " + "отменитьтранзакцию очиститьжурналрегистрации очиститьнастройкипользователя очиститьсообщения параметрыдоступа " + "перейтипонавигационнойссылке переместитьфайл подключитьвнешнююкомпоненту " + "подключитьобработчикзапросанастроекклиенталицензирования подключитьобработчикожидания подключитьобработчикоповещения " + "подключитьрасширениеработыскриптографией подключитьрасширениеработысфайлами подробноепредставлениеошибки " + "показатьвводдаты показатьвводзначения показатьвводстроки показатьвводчисла показатьвопрос показатьзначение " + "показатьинформациюобошибке показатьнакарте показатьоповещениепользователя показатьпредупреждение полноеимяпользователя " + "получитьcomобъект получитьxmlтип получитьадреспоместоположению получитьблокировкусеансов получитьвремязавершенияспящегосеанса " + "получитьвремязасыпанияпассивногосеанса получитьвремяожиданияблокировкиданных получитьданныевыбора " + "получитьдополнительныйпараметрклиенталицензирования получитьдопустимыекодылокализации получитьдопустимыечасовыепояса " + "получитьзаголовокклиентскогоприложения получитьзаголовоксистемы получитьзначенияотборажурналарегистрации " + "получитьидентификаторконфигурации получитьизвременногохранилища получитьимявременногофайла " + "получитьимяклиенталицензирования получитьинформациюэкрановклиента получитьиспользованиежурналарегистрации " + "получитьиспользованиесобытияжурналарегистрации получитькраткийзаголовокприложения получитьмакетоформления " + "получитьмаскувсефайлы получитьмаскувсефайлыклиента получитьмаскувсефайлысервера получитьместоположениепоадресу " + "получитьминимальнуюдлинупаролейпользователей получитьнавигационнуюссылку получитьнавигационнуюссылкуинформационнойбазы " + "получитьобновлениеконфигурациибазыданных получитьобновлениепредопределенныхданныхинформационнойбазы получитьобщиймакет " + "получитьобщуюформу получитьокна получитьоперативнуюотметкувремени получитьотключениебезопасногорежима " + "получитьпараметрыфункциональныхопцийинтерфейса получитьполноеимяпредопределенногозначения " + "получитьпредставлениянавигационныхссылок получитьпроверкусложностипаролейпользователей получитьразделительпути " + "получитьразделительпутиклиента получитьразделительпутисервера получитьсеансыинформационнойбазы " + "получитьскоростьклиентскогосоединения получитьсоединенияинформационнойбазы получитьсообщенияпользователю " + "получитьсоответствиеобъектаиформы получитьсоставстандартногоинтерфейсаodata получитьструктурухранениябазыданных " + "получитьтекущийсеансинформационнойбазы получитьфайл получитьфайлы получитьформу получитьфункциональнуюопцию " + "получитьфункциональнуюопциюинтерфейса получитьчасовойпоясинформационнойбазы пользователиос поместитьвовременноехранилище " + "поместитьфайл поместитьфайлы прав праводоступа предопределенноезначение представлениекодалокализации представлениепериода " + "представлениеправа представлениеприложения представлениесобытияжурналарегистрации представлениечасовогопояса предупреждение " + "прекратитьработусистемы привилегированныйрежим продолжитьвызов прочитатьjson прочитатьxml прочитатьдатуjson пустаястрока " + "рабочийкаталогданныхпользователя разблокироватьданныедляредактирования разделитьфайл разорватьсоединениесвнешнимисточникомданных " + "раскодироватьстроку рольдоступна секунда сигнал символ скопироватьжурналрегистрации смещениелетнеговремени " + "смещениестандартноговремени соединитьбуферыдвоичныхданных создатькаталог создатьфабрикуxdto сокрл сокрлп сокрп сообщить " + "состояние сохранитьзначение сохранитьнастройкипользователя сред стрдлина стрзаканчиваетсяна стрзаменить стрнайти стрначинаетсяс " + "строка строкасоединенияинформационнойбазы стрполучитьстроку стрразделить стрсоединить стрсравнить стрчисловхождений " + "стрчислострок стршаблон текущаядата текущаядатасеанса текущаяуниверсальнаядата текущаяуниверсальнаядатавмиллисекундах " + "текущийвариантинтерфейсаклиентскогоприложения текущийвариантосновногошрифтаклиентскогоприложения текущийкодлокализации " + "текущийрежимзапуска текущийязык текущийязыксистемы тип типзнч транзакцияактивна трег удалитьданныеинформационнойбазы " + "удалитьизвременногохранилища удалитьобъекты удалитьфайлы универсальноевремя установитьбезопасныйрежим " + "установитьбезопасныйрежимразделенияданных установитьблокировкусеансов установитьвнешнююкомпоненту " + "установитьвремязавершенияспящегосеанса установитьвремязасыпанияпассивногосеанса установитьвремяожиданияблокировкиданных " + "установитьзаголовокклиентскогоприложения установитьзаголовоксистемы установитьиспользованиежурналарегистрации " + "установитьиспользованиесобытияжурналарегистрации установитькраткийзаголовокприложения " + "установитьминимальнуюдлинупаролейпользователей установитьмонопольныйрежим установитьнастройкиклиенталицензирования " + "установитьобновлениепредопределенныхданныхинформационнойбазы установитьотключениебезопасногорежима " + "установитьпараметрыфункциональныхопцийинтерфейса установитьпривилегированныйрежим " + "установитьпроверкусложностипаролейпользователей установитьрасширениеработыскриптографией " + "установитьрасширениеработысфайлами установитьсоединениесвнешнимисточникомданных установитьсоответствиеобъектаиформы " + "установитьсоставстандартногоинтерфейсаodata установитьчасовойпоясинформационнойбазы установитьчасовойпояссеанса " + "формат цел час часовойпояс часовойпояссеанса число числопрописью этоадресвременногохранилища "; // v8 свойства глобального контекста ==> built_in var v8_global_context_property = "wsссылки библиотекакартинок библиотекамакетовоформлениякомпоновкиданных библиотекастилей бизнеспроцессы " + "внешниеисточникиданных внешниеобработки внешниеотчеты встроенныепокупки главныйинтерфейс главныйстиль " + "документы доставляемыеуведомления журналыдокументов задачи информацияобинтернетсоединении использованиерабочейдаты " + "историяработыпользователя константы критерииотбора метаданные обработки отображениерекламы отправкадоставляемыхуведомлений " + "отчеты панельзадачос параметрзапуска параметрысеанса перечисления планывидоврасчета планывидовхарактеристик " + "планыобмена планысчетов полнотекстовыйпоиск пользователиинформационнойбазы последовательности проверкавстроенныхпокупок " + "рабочаядата расширенияконфигурации регистрыбухгалтерии регистрынакопления регистрырасчета регистрысведений " + "регламентныезадания сериализаторxdto справочники средствагеопозиционирования средствакриптографии средствамультимедиа " + "средстваотображениярекламы средствапочты средствателефонии фабрикаxdto файловыепотоки фоновыезадания хранилищанастроек " + "хранилищевариантовотчетов хранилищенастроекданныхформ хранилищеобщихнастроек хранилищепользовательскихнастроекдинамическихсписков " + "хранилищепользовательскихнастроекотчетов хранилищесистемныхнастроек "; // built_in : встроенные или библиотечные объекты (константы, классы, функции) var BUILTIN = v7_system_constants + v7_global_context_methods + v8_global_context_methods + v8_global_context_property; // v8 системные наборы значений ==> class var v8_system_sets_of_values = "webцвета windowsцвета windowsшрифты библиотекакартинок рамкистиля символы цветастиля шрифтыстиля "; // v8 системные перечисления - интерфейсные ==> class var v8_system_enums_interface = "автоматическоесохранениеданныхформывнастройках автонумерациявформе автораздвижениесерий " + "анимациядиаграммы вариантвыравниванияэлементовизаголовков вариантуправлениявысотойтаблицы " + "вертикальнаяпрокруткаформы вертикальноеположение вертикальноеположениеэлемента видгруппыформы " + "виддекорацииформы виддополненияэлементаформы видизмененияданных видкнопкиформы видпереключателя " + "видподписейкдиаграмме видполяформы видфлажка влияниеразмеранапузырекдиаграммы горизонтальноеположение " + "горизонтальноеположениеэлемента группировкаколонок группировкаподчиненныхэлементовформы " + "группыиэлементы действиеперетаскивания дополнительныйрежимотображения допустимыедействияперетаскивания " + "интервалмеждуэлементамиформы использованиевывода использованиеполосыпрокрутки " + "используемоезначениеточкибиржевойдиаграммы историявыборапривводе источникзначенийоситочекдиаграммы " + "источникзначенияразмерапузырькадиаграммы категориягруппыкоманд максимумсерий начальноеотображениедерева " + "начальноеотображениесписка обновлениетекстаредактирования ориентациядендрограммы ориентациядиаграммы " + "ориентацияметокдиаграммы ориентацияметоксводнойдиаграммы ориентацияэлементаформы отображениевдиаграмме " + "отображениевлегендедиаграммы отображениегруппыкнопок отображениезаголовкашкалыдиаграммы " + "отображениезначенийсводнойдиаграммы отображениезначенияизмерительнойдиаграммы " + "отображениеинтерваладиаграммыганта отображениекнопки отображениекнопкивыбора отображениеобсужденийформы " + "отображениеобычнойгруппы отображениеотрицательныхзначенийпузырьковойдиаграммы отображениепанелипоиска " + "отображениеподсказки отображениепредупрежденияприредактировании отображениеразметкиполосырегулирования " + "отображениестраницформы отображениетаблицы отображениетекстазначениядиаграммыганта " + "отображениеуправленияобычнойгруппы отображениефигурыкнопки палитрацветовдиаграммы поведениеобычнойгруппы " + "поддержкамасштабадендрограммы поддержкамасштабадиаграммыганта поддержкамасштабасводнойдиаграммы " + "поисквтаблицепривводе положениезаголовкаэлементаформы положениекартинкикнопкиформы " + "положениекартинкиэлементаграфическойсхемы положениекоманднойпанелиформы положениекоманднойпанелиэлементаформы " + "положениеопорнойточкиотрисовки положениеподписейкдиаграмме положениеподписейшкалызначенийизмерительнойдиаграммы " + "положениесостоянияпросмотра положениестрокипоиска положениетекстасоединительнойлинии положениеуправленияпоиском " + "положениешкалывремени порядокотображенияточекгоризонтальнойгистограммы порядоксерийвлегендедиаграммы " + "размеркартинки расположениезаголовкашкалыдиаграммы растягиваниеповертикалидиаграммыганта " + "режимавтоотображениясостояния режимвводастроктаблицы режимвыборанезаполненного режимвыделениядаты " + "режимвыделениястрокитаблицы режимвыделениятаблицы режимизмененияразмера режимизменениясвязанногозначения " + "режимиспользованиядиалогапечати режимиспользованияпараметракоманды режиммасштабированияпросмотра " + "режимосновногоокнаклиентскогоприложения режимоткрытияокнаформы режимотображениявыделения " + "режимотображениягеографическойсхемы режимотображениязначенийсерии режимотрисовкисеткиграфическойсхемы " + "режимполупрозрачностидиаграммы режимпробеловдиаграммы режимразмещениянастранице режимредактированияколонки " + "режимсглаживаниядиаграммы режимсглаживанияиндикатора режимсписказадач сквозноевыравнивание " + "сохранениеданныхформывнастройках способзаполнениятекстазаголовкашкалыдиаграммы " + "способопределенияограничивающегозначениядиаграммы стандартнаягруппакоманд стандартноеоформление " + "статусоповещенияпользователя стильстрелки типаппроксимациилиниитрендадиаграммы типдиаграммы " + "типединицышкалывремени типимпортасерийслоягеографическойсхемы типлиниигеографическойсхемы типлиниидиаграммы " + "типмаркерагеографическойсхемы типмаркерадиаграммы типобластиоформления " + "типорганизацииисточникаданныхгеографическойсхемы типотображениясериислоягеографическойсхемы " + "типотображенияточечногообъектагеографическойсхемы типотображенияшкалыэлементалегендыгеографическойсхемы " + "типпоискаобъектовгеографическойсхемы типпроекциигеографическойсхемы типразмещенияизмерений " + "типразмещенияреквизитовизмерений типрамкиэлементауправления типсводнойдиаграммы " + "типсвязидиаграммыганта типсоединениязначенийпосериямдиаграммы типсоединенияточекдиаграммы " + "типсоединительнойлинии типстороныэлементаграфическойсхемы типформыотчета типшкалырадарнойдиаграммы " + "факторлиниитрендадиаграммы фигуракнопки фигурыграфическойсхемы фиксациявтаблице форматдняшкалывремени " + "форматкартинки ширинаподчиненныхэлементовформы "; // v8 системные перечисления - свойства прикладных объектов ==> class var v8_system_enums_objects_properties = "виддвижениябухгалтерии виддвижениянакопления видпериодарегистрарасчета видсчета видточкимаршрутабизнеспроцесса " + "использованиеагрегатарегистранакопления использованиегруппиэлементов использованиережимапроведения " + "использованиесреза периодичностьагрегатарегистранакопления режимавтовремя режимзаписидокумента режимпроведениядокумента "; // v8 системные перечисления - планы обмена ==> class var v8_system_enums_exchange_plans = "авторегистрацияизменений допустимыйномерсообщения отправкаэлементаданных получениеэлементаданных "; // v8 системные перечисления - табличный документ ==> class var v8_system_enums_tabular_document = "использованиерасшифровкитабличногодокумента ориентациястраницы положениеитоговколоноксводнойтаблицы " + "положениеитоговстроксводнойтаблицы положениетекстаотносительнокартинки расположениезаголовкагруппировкитабличногодокумента " + "способчтениязначенийтабличногодокумента типдвустороннейпечати типзаполненияобластитабличногодокумента " + "типкурсоровтабличногодокумента типлиниирисункатабличногодокумента типлинииячейкитабличногодокумента " + "типнаправленияпереходатабличногодокумента типотображениявыделениятабличногодокумента типотображениялинийсводнойтаблицы " + "типразмещениятекстатабличногодокумента типрисункатабличногодокумента типсмещениятабличногодокумента " + "типузоратабличногодокумента типфайлатабличногодокумента точностьпечати чередованиерасположениястраниц "; // v8 системные перечисления - планировщик ==> class var v8_system_enums_sheduler = "отображениевремениэлементовпланировщика "; // v8 системные перечисления - форматированный документ ==> class var v8_system_enums_formatted_document = "типфайлаформатированногодокумента "; // v8 системные перечисления - запрос ==> class var v8_system_enums_query = "обходрезультатазапроса типзаписизапроса "; // v8 системные перечисления - построитель отчета ==> class var v8_system_enums_report_builder = "видзаполнениярасшифровкипостроителяотчета типдобавленияпредставлений типизмеренияпостроителяотчета типразмещенияитогов "; // v8 системные перечисления - работа с файлами ==> class var v8_system_enums_files = "доступкфайлу режимдиалогавыборафайла режимоткрытияфайла "; // v8 системные перечисления - построитель запроса ==> class var v8_system_enums_query_builder = "типизмеренияпостроителязапроса "; // v8 системные перечисления - анализ данных ==> class var v8_system_enums_data_analysis = "видданныханализа методкластеризации типединицыинтервалавременианализаданных типзаполнениятаблицырезультатаанализаданных " + "типиспользованиячисловыхзначенийанализаданных типисточникаданныхпоискаассоциаций типколонкианализаданныхдереворешений " + "типколонкианализаданныхкластеризация типколонкианализаданныхобщаястатистика типколонкианализаданныхпоискассоциаций " + "типколонкианализаданныхпоискпоследовательностей типколонкимоделипрогноза типмерырасстоянияанализаданных " + "типотсеченияправилассоциации типполяанализаданных типстандартизациианализаданных типупорядочиванияправилассоциациианализаданных " + "типупорядочиванияшаблоновпоследовательностейанализаданных типупрощениядереварешений "; // v8 системные перечисления - xml, json, xs, dom, xdto, web-сервисы ==> class var v8_system_enums_xml_json_xs_dom_xdto_ws = "wsнаправлениепараметра вариантxpathxs вариантзаписидатыjson вариантпростоготипаxs видгруппымоделиxs видфасетаxdto " + "действиепостроителяdom завершенностьпростоготипаxs завершенностьсоставноготипаxs завершенностьсхемыxs запрещенныеподстановкиxs " + "исключениягруппподстановкиxs категорияиспользованияатрибутаxs категорияограниченияидентичностиxs категорияограниченияпространствименxs " + "методнаследованияxs модельсодержимогоxs назначениетипаxml недопустимыеподстановкиxs обработкапробельныхсимволовxs обработкасодержимогоxs " + "ограничениезначенияxs параметрыотбораузловdom переносстрокjson позициявдокументеdom пробельныесимволыxml типатрибутаxml типзначенияjson " + "типканоническогоxml типкомпонентыxs типпроверкиxml типрезультатаdomxpath типузлаdom типузлаxml формаxml формапредставленияxs " + "форматдатыjson экранированиесимволовjson "; // v8 системные перечисления - система компоновки данных ==> class var v8_system_enums_data_composition_system = "видсравнениякомпоновкиданных действиеобработкирасшифровкикомпоновкиданных направлениесортировкикомпоновкиданных " + "расположениевложенныхэлементоврезультатакомпоновкиданных расположениеитоговкомпоновкиданных расположениегруппировкикомпоновкиданных " + "расположениеполейгруппировкикомпоновкиданных расположениеполякомпоновкиданных расположениереквизитовкомпоновкиданных " + "расположениересурсовкомпоновкиданных типбухгалтерскогоостаткакомпоновкиданных типвыводатекстакомпоновкиданных " + "типгруппировкикомпоновкиданных типгруппыэлементовотборакомпоновкиданных типдополненияпериодакомпоновкиданных " + "типзаголовкаполейкомпоновкиданных типмакетагруппировкикомпоновкиданных типмакетаобластикомпоновкиданных типостаткакомпоновкиданных " + "типпериодакомпоновкиданных типразмещениятекстакомпоновкиданных типсвязинаборовданныхкомпоновкиданных типэлементарезультатакомпоновкиданных " + "расположениелегендыдиаграммыкомпоновкиданных типпримененияотборакомпоновкиданных режимотображенияэлементанастройкикомпоновкиданных " + "режимотображениянастроеккомпоновкиданных состояниеэлементанастройкикомпоновкиданных способвосстановлениянастроеккомпоновкиданных " + "режимкомпоновкирезультата использованиепараметракомпоновкиданных автопозицияресурсовкомпоновкиданных " + "вариантиспользованиягруппировкикомпоновкиданных расположениересурсоввдиаграммекомпоновкиданных фиксациякомпоновкиданных " + "использованиеусловногооформлениякомпоновкиданных "; // v8 системные перечисления - почта ==> class var v8_system_enums_email = "важностьинтернетпочтовогосообщения обработкатекстаинтернетпочтовогосообщения способкодированияинтернетпочтовоговложения " + "способкодированиянеasciiсимволовинтернетпочтовогосообщения типтекстапочтовогосообщения протоколинтернетпочты " + "статусразборапочтовогосообщения "; // v8 системные перечисления - журнал регистрации ==> class var v8_system_enums_logbook = "режимтранзакциизаписижурналарегистрации статустранзакциизаписижурналарегистрации уровеньжурналарегистрации "; // v8 системные перечисления - криптография ==> class var v8_system_enums_cryptography = "расположениехранилищасертификатовкриптографии режимвключениясертификатовкриптографии режимпроверкисертификатакриптографии " + "типхранилищасертификатовкриптографии "; // v8 системные перечисления - ZIP ==> class var v8_system_enums_zip = "кодировкаименфайловвzipфайле методсжатияzip методшифрованияzip режимвосстановленияпутейфайловzip режимобработкиподкаталоговzip " + "режимсохраненияпутейzip уровеньсжатияzip "; // v8 системные перечисления - // Блокировка данных, Фоновые задания, Автоматизированное тестирование, // Доставляемые уведомления, Встроенные покупки, Интернет, Работа с двоичными данными ==> class var v8_system_enums_other = "звуковоеоповещение направлениепереходакстроке позициявпотоке порядокбайтов режимблокировкиданных режимуправленияблокировкойданных " + "сервисвстроенныхпокупок состояниефоновогозадания типподписчикадоставляемыхуведомлений уровеньиспользованиязащищенногосоединенияftp "; // v8 системные перечисления - схема запроса ==> class var v8_system_enums_request_schema = "направлениепорядкасхемызапроса типдополненияпериодамисхемызапроса типконтрольнойточкисхемызапроса типобъединениясхемызапроса " + "типпараметрадоступнойтаблицысхемызапроса типсоединениясхемызапроса "; // v8 системные перечисления - свойства объектов метаданных ==> class var v8_system_enums_properties_of_metadata_objects = "httpметод автоиспользованиеобщегореквизита автопрефиксномеразадачи вариантвстроенногоязыка видиерархии видрегистранакопления " + "видтаблицывнешнегоисточникаданных записьдвиженийприпроведении заполнениепоследовательностей индексирование " + "использованиебазыпланавидоврасчета использованиебыстроговыбора использованиеобщегореквизита использованиеподчинения " + "использованиеполнотекстовогопоиска использованиеразделяемыхданныхобщегореквизита использованиереквизита " + "назначениеиспользованияприложения назначениерасширенияконфигурации направлениепередачи обновлениепредопределенныхданных " + "оперативноепроведение основноепредставлениевидарасчета основноепредставлениевидахарактеристики основноепредставлениезадачи " + "основноепредставлениепланаобмена основноепредставлениесправочника основноепредставлениесчета перемещениеграницыприпроведении " + "периодичностьномерабизнеспроцесса периодичностьномерадокумента периодичностьрегистрарасчета периодичностьрегистрасведений " + "повторноеиспользованиевозвращаемыхзначений полнотекстовыйпоискпривводепостроке принадлежностьобъекта проведение " + "разделениеаутентификацииобщегореквизита разделениеданныхобщегореквизита разделениерасширенийконфигурацииобщегореквизита " + "режимавтонумерацииобъектов режимзаписирегистра режимиспользованиямодальности " + "режимиспользованиясинхронныхвызововрасширенийплатформыивнешнихкомпонент режимповторногоиспользованиясеансов " + "режимполученияданныхвыборапривводепостроке режимсовместимости режимсовместимостиинтерфейса " + "режимуправленияблокировкойданныхпоумолчанию сериикодовпланавидовхарактеристик сериикодовпланасчетов " + "сериикодовсправочника созданиепривводе способвыбора способпоискастрокипривводепостроке способредактирования " + "типданныхтаблицывнешнегоисточникаданных типкодапланавидоврасчета типкодасправочника типмакета типномерабизнеспроцесса " + "типномерадокумента типномеразадачи типформы удалениедвижений "; // v8 системные перечисления - разные ==> class var v8_system_enums_differents = "важностьпроблемыприменениярасширенияконфигурации вариантинтерфейсаклиентскогоприложения вариантмасштабаформклиентскогоприложения " + "вариантосновногошрифтаклиентскогоприложения вариантстандартногопериода вариантстандартнойдатыначала видграницы видкартинки " + "видотображенияполнотекстовогопоиска видрамки видсравнения видцвета видчисловогозначения видшрифта допустимаядлина допустимыйзнак " + "использованиеbyteordermark использованиеметаданныхполнотекстовогопоиска источникрасширенийконфигурации клавиша кодвозвратадиалога " + "кодировкаxbase кодировкатекста направлениепоиска направлениесортировки обновлениепредопределенныхданных обновлениеприизмененииданных " + "отображениепанелиразделов проверказаполнения режимдиалогавопрос режимзапускаклиентскогоприложения режимокругления режимоткрытияформприложения " + "режимполнотекстовогопоиска скоростьклиентскогосоединения состояниевнешнегоисточникаданных состояниеобновленияконфигурациибазыданных " + "способвыборасертификатаwindows способкодированиястроки статуссообщения типвнешнейкомпоненты типплатформы типповеденияклавишиenter " + "типэлементаинформацииовыполненииобновленияконфигурациибазыданных уровеньизоляциитранзакций хешфункция частидаты"; // class: встроенные наборы значений, системные перечисления (содержат дочерние значения, обращения к которым через разыменование) var CLASS = v8_system_sets_of_values + v8_system_enums_interface + v8_system_enums_objects_properties + v8_system_enums_exchange_plans + v8_system_enums_tabular_document + v8_system_enums_sheduler + v8_system_enums_formatted_document + v8_system_enums_query + v8_system_enums_report_builder + v8_system_enums_files + v8_system_enums_query_builder + v8_system_enums_data_analysis + v8_system_enums_xml_json_xs_dom_xdto_ws + v8_system_enums_data_composition_system + v8_system_enums_email + v8_system_enums_logbook + v8_system_enums_cryptography + v8_system_enums_zip + v8_system_enums_other + v8_system_enums_request_schema + v8_system_enums_properties_of_metadata_objects + v8_system_enums_differents; // v8 общие объекты (у объектов есть конструктор, экземпляры создаются методом НОВЫЙ) ==> type var v8_shared_object = "comобъект ftpсоединение httpзапрос httpсервисответ httpсоединение wsопределения wsпрокси xbase анализданных аннотацияxs " + "блокировкаданных буфердвоичныхданных включениеxs выражениекомпоновкиданных генераторслучайныхчисел географическаясхема " + "географическиекоординаты графическаясхема группамоделиxs данныерасшифровкикомпоновкиданных двоичныеданные дендрограмма " + "диаграмма диаграммаганта диалогвыборафайла диалогвыборацвета диалогвыборашрифта диалограсписаниярегламентногозадания " + "диалогредактированиястандартногопериода диапазон документdom документhtml документацияxs доставляемоеуведомление " + "записьdom записьfastinfoset записьhtml записьjson записьxml записьzipфайла записьданных записьтекста записьузловdom " + "запрос защищенноесоединениеopenssl значенияполейрасшифровкикомпоновкиданных извлечениетекста импортxs интернетпочта " + "интернетпочтовоесообщение интернетпочтовыйпрофиль интернетпрокси интернетсоединение информациядляприложенияxs " + "использованиеатрибутаxs использованиесобытияжурналарегистрации источникдоступныхнастроеккомпоновкиданных " + "итераторузловdom картинка квалификаторыдаты квалификаторыдвоичныхданных квалификаторыстроки квалификаторычисла " + "компоновщикмакетакомпоновкиданных компоновщикнастроеккомпоновкиданных конструктормакетаоформлениякомпоновкиданных " + "конструкторнастроеккомпоновкиданных конструкторформатнойстроки линия макеткомпоновкиданных макетобластикомпоновкиданных " + "макетоформлениякомпоновкиданных маскаxs менеджеркриптографии наборсхемxml настройкикомпоновкиданных настройкисериализацииjson " + "обработкакартинок обработкарасшифровкикомпоновкиданных обходдереваdom объявлениеатрибутаxs объявлениенотацииxs " + "объявлениеэлементаxs описаниеиспользованиясобытиядоступжурналарегистрации " + "описаниеиспользованиясобытияотказвдоступежурналарегистрации описаниеобработкирасшифровкикомпоновкиданных " + "описаниепередаваемогофайла описаниетипов определениегруппыатрибутовxs определениегруппымоделиxs " + "определениеограниченияидентичностиxs определениепростоготипаxs определениесоставноготипаxs определениетипадокументаdom " + "определенияxpathxs отборкомпоновкиданных пакетотображаемыхдокументов параметрвыбора параметркомпоновкиданных " + "параметрызаписиjson параметрызаписиxml параметрычтенияxml переопределениеxs планировщик полеанализаданных " + "полекомпоновкиданных построительdom построительзапроса построительотчета построительотчетаанализаданных " + "построительсхемxml поток потоквпамяти почта почтовоесообщение преобразованиеxsl преобразованиекканоническомуxml " + "процессорвыводарезультатакомпоновкиданныхвколлекциюзначений процессорвыводарезультатакомпоновкиданныхвтабличныйдокумент " + "процессоркомпоновкиданных разыменовательпространствименdom рамка расписаниерегламентногозадания расширенноеимяxml " + "результатчтенияданных своднаядиаграмма связьпараметравыбора связьпотипу связьпотипукомпоновкиданных сериализаторxdto " + "сертификатклиентаwindows сертификатклиентафайл сертификаткриптографии сертификатыудостоверяющихцентровwindows " + "сертификатыудостоверяющихцентровфайл сжатиеданных системнаяинформация сообщениепользователю сочетаниеклавиш " + "сравнениезначений стандартнаядатаначала стандартныйпериод схемаxml схемакомпоновкиданных табличныйдокумент " + "текстовыйдокумент тестируемоеприложение типданныхxml уникальныйидентификатор фабрикаxdto файл файловыйпоток " + "фасетдлиныxs фасетколичестваразрядовдробнойчастиxs фасетмаксимальноговключающегозначенияxs " + "фасетмаксимальногоисключающегозначенияxs фасетмаксимальнойдлиныxs фасетминимальноговключающегозначенияxs " + "фасетминимальногоисключающегозначенияxs фасетминимальнойдлиныxs фасетобразцаxs фасетобщегоколичестваразрядовxs " + "фасетперечисленияxs фасетпробельныхсимволовxs фильтрузловdom форматированнаястрока форматированныйдокумент " + "фрагментxs хешированиеданных хранилищезначения цвет чтениеfastinfoset чтениеhtml чтениеjson чтениеxml чтениеzipфайла " + "чтениеданных чтениетекста чтениеузловdom шрифт элементрезультатакомпоновкиданных "; // v8 универсальные коллекции значений ==> type var v8_universal_collection = "comsafearray деревозначений массив соответствие списокзначений структура таблицазначений фиксированнаяструктура " + "фиксированноесоответствие фиксированныймассив "; // type : встроенные типы var TYPE = v8_shared_object + v8_universal_collection; // literal : примитивные типы var LITERAL = "null истина ложь неопределено"; // number : числа var NUMBERS = hljs.inherit(hljs.NUMBER_MODE); // string : строки var STRINGS = { className: "string", begin: '"|\\|', end: '"|$', contains: [{ begin: '""' }], }; // number : даты var DATE = { begin: "'", end: "'", excludeBegin: true, excludeEnd: true, contains: [ { className: "number", begin: "\\d{4}([\\.\\\\/:-]?\\d{2}){0,5}", }, ], }; // comment : комментарии var COMMENTS = hljs.inherit(hljs.C_LINE_COMMENT_MODE); // meta : инструкции препроцессора, директивы компиляции var META = { className: "meta", lexemes: UNDERSCORE_IDENT_RE, begin: "#|&", end: "$", keywords: { "meta-keyword": KEYWORD + METAKEYWORD }, contains: [COMMENTS], }; // symbol : метка goto var SYMBOL = { className: "symbol", begin: "~", end: ";|:", excludeEnd: true, }; // function : объявление процедур и функций var FUNCTION = { className: "function", lexemes: UNDERSCORE_IDENT_RE, variants: [ { begin: "процедура|функция", end: "\\)", keywords: "процедура функция", }, { begin: "конецпроцедуры|конецфункции", keywords: "конецпроцедуры конецфункции", }, ], contains: [ { begin: "\\(", end: "\\)", endsParent: true, contains: [ { className: "params", lexemes: UNDERSCORE_IDENT_RE, begin: UNDERSCORE_IDENT_RE, end: ",", excludeEnd: true, endsWithParent: true, keywords: { keyword: "знач", literal: LITERAL, }, contains: [NUMBERS, STRINGS, DATE], }, COMMENTS, ], }, hljs.inherit(hljs.TITLE_MODE, { begin: UNDERSCORE_IDENT_RE, }), ], }; return { case_insensitive: true, lexemes: UNDERSCORE_IDENT_RE, keywords: { keyword: KEYWORD, built_in: BUILTIN, class: CLASS, type: TYPE, literal: LITERAL, }, contains: [ META, FUNCTION, COMMENTS, SYMBOL, NUMBERS, STRINGS, DATE, ], }; }, }, { name: "abnf", /* Language: Augmented Backus-Naur Form Author: Alex McKibben */ create: function (hljs) { var regexes = { ruleDeclaration: "^[a-zA-Z][a-zA-Z0-9-]*", unexpectedChars: "[!@#$^&',?+~`|:]", }; var keywords = [ "ALPHA", "BIT", "CHAR", "CR", "CRLF", "CTL", "DIGIT", "DQUOTE", "HEXDIG", "HTAB", "LF", "LWSP", "OCTET", "SP", "VCHAR", "WSP", ]; var commentMode = hljs.COMMENT(";", "$"); var terminalBinaryMode = { className: "symbol", begin: /%b[0-1]+(-[0-1]+|(\.[0-1]+)+){0,1}/, }; var terminalDecimalMode = { className: "symbol", begin: /%d[0-9]+(-[0-9]+|(\.[0-9]+)+){0,1}/, }; var terminalHexadecimalMode = { className: "symbol", begin: /%x[0-9A-F]+(-[0-9A-F]+|(\.[0-9A-F]+)+){0,1}/, }; var caseSensitivityIndicatorMode = { className: "symbol", begin: /%[si]/, }; var ruleDeclarationMode = { begin: regexes.ruleDeclaration + "\\s*=", returnBegin: true, end: /=/, relevance: 0, contains: [ { className: "attribute", begin: regexes.ruleDeclaration }, ], }; return { illegal: regexes.unexpectedChars, keywords: keywords.join(" "), contains: [ ruleDeclarationMode, commentMode, terminalBinaryMode, terminalDecimalMode, terminalHexadecimalMode, caseSensitivityIndicatorMode, hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, ], }; }, }, { name: "accesslog", /* Language: Access log Author: Oleg Efimov Description: Apache/Nginx Access Logs */ create: function (hljs) { return { contains: [ // IP { className: "number", begin: "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b", }, // Other numbers { className: "number", begin: "\\b\\d+\\b", relevance: 0, }, // Requests { className: "string", begin: '"(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|PATCH|TRACE)', end: '"', keywords: "GET POST HEAD PUT DELETE CONNECT OPTIONS PATCH TRACE", illegal: "\\n", relevance: 10, }, // Dates { className: "string", begin: /\[/, end: /\]/, illegal: "\\n", }, // Strings { className: "string", begin: '"', end: '"', illegal: "\\n", }, ], }; }, }, { name: "actionscript", /* Language: ActionScript Author: Alexander Myadzel Category: scripting */ create: function (hljs) { var IDENT_RE = "[a-zA-Z_$][a-zA-Z0-9_$]*"; var IDENT_FUNC_RETURN_TYPE_RE = "([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)"; var AS3_REST_ARG_MODE = { className: "rest_arg", begin: "[.]{3}", end: IDENT_RE, relevance: 10, }; return { aliases: ["as"], keywords: { keyword: "as break case catch class const continue default delete do dynamic each " + "else extends final finally for function get if implements import in include " + "instanceof interface internal is namespace native new override package private " + "protected public return set static super switch this throw try typeof use var void " + "while with", literal: "true false null undefined", }, contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.C_NUMBER_MODE, { className: "class", beginKeywords: "package", end: "{", contains: [hljs.TITLE_MODE], }, { className: "class", beginKeywords: "class interface", end: "{", excludeEnd: true, contains: [ { beginKeywords: "extends implements", }, hljs.TITLE_MODE, ], }, { className: "meta", beginKeywords: "import include", end: ";", keywords: { "meta-keyword": "import include" }, }, { className: "function", beginKeywords: "function", end: "[{;]", excludeEnd: true, illegal: "\\S", contains: [ hljs.TITLE_MODE, { className: "params", begin: "\\(", end: "\\)", contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, AS3_REST_ARG_MODE, ], }, { begin: ":\\s*" + IDENT_FUNC_RETURN_TYPE_RE, }, ], }, hljs.METHOD_GUARD, ], illegal: /#/, }; }, }, { name: "ada", /* Language: Ada Author: Lars Schulna Description: Ada is a general-purpose programming language that has great support for saftey critical and real-time applications. It has been developed by the DoD and thus has been used in military and safety-critical applications (like civil aviation). The first version appeared in the 80s, but it's still actively developed today with the newest standard being Ada2012. */ create: // We try to support full Ada2012 // // We highlight all appearances of types, keywords, literals (string, char, number, bool) // and titles (user defined function/procedure/package) // CSS classes are set accordingly // // Languages causing problems for language detection: // xml (broken by Foo : Bar type), elm (broken by Foo : Bar type), vbscript-html (broken by body keyword) // sql (ada default.txt has a lot of sql keywords) function (hljs) { // Regular expression for Ada numeric literals. // stolen form the VHDL highlighter // Decimal literal: var INTEGER_RE = "\\d(_|\\d)*"; var EXPONENT_RE = "[eE][-+]?" + INTEGER_RE; var DECIMAL_LITERAL_RE = INTEGER_RE + "(\\." + INTEGER_RE + ")?" + "(" + EXPONENT_RE + ")?"; // Based literal: var BASED_INTEGER_RE = "\\w+"; var BASED_LITERAL_RE = INTEGER_RE + "#" + BASED_INTEGER_RE + "(\\." + BASED_INTEGER_RE + ")?" + "#" + "(" + EXPONENT_RE + ")?"; var NUMBER_RE = "\\b(" + BASED_LITERAL_RE + "|" + DECIMAL_LITERAL_RE + ")"; // Identifier regex var ID_REGEX = "[A-Za-z](_?[A-Za-z0-9.])*"; // bad chars, only allowed in literals var BAD_CHARS = "[]{}%#'\""; // Ada doesn't have block comments, only line comments var COMMENTS = hljs.COMMENT("--", "$"); // variable declarations of the form // Foo : Bar := Baz; // where only Bar will be highlighted var VAR_DECLS = { // TODO: These spaces are not required by the Ada syntax // however, I have yet to see handwritten Ada code where // someone does not put spaces around : begin: "\\s+:\\s+", end: "\\s*(:=|;|\\)|=>|$)", // endsWithParent: true, // returnBegin: true, illegal: BAD_CHARS, contains: [ { // workaround to avoid highlighting // named loops and declare blocks beginKeywords: "loop for declare others", endsParent: true, }, { // properly highlight all modifiers className: "keyword", beginKeywords: "not null constant access function procedure in out aliased exception", }, { className: "type", begin: ID_REGEX, endsParent: true, relevance: 0, }, ], }; return { case_insensitive: true, keywords: { keyword: "abort else new return abs elsif not reverse abstract end " + "accept entry select access exception of separate aliased exit or some " + "all others subtype and for out synchronized array function overriding " + "at tagged generic package task begin goto pragma terminate " + "body private then if procedure type case in protected constant interface " + "is raise use declare range delay limited record when delta loop rem while " + "digits renames with do mod requeue xor", literal: "True False", }, contains: [ COMMENTS, // strings "foobar" { className: "string", begin: /"/, end: /"/, contains: [{ begin: /""/, relevance: 0 }], }, // characters '' { // character literals always contain one char className: "string", begin: /'.'/, }, { // number literals className: "number", begin: NUMBER_RE, relevance: 0, }, { // Attributes className: "symbol", begin: "'" + ID_REGEX, }, { // package definition, maybe inside generic className: "title", begin: "(\\bwith\\s+)?(\\bprivate\\s+)?\\bpackage\\s+(\\bbody\\s+)?", end: "(is|$)", keywords: "package body", excludeBegin: true, excludeEnd: true, illegal: BAD_CHARS, }, { // function/procedure declaration/definition // maybe inside generic begin: "(\\b(with|overriding)\\s+)?\\b(function|procedure)\\s+", end: "(\\bis|\\bwith|\\brenames|\\)\\s*;)", keywords: "overriding function procedure with is renames return", // we need to re-match the 'function' keyword, so that // the title mode below matches only exactly once returnBegin: true, contains: [ COMMENTS, { // name of the function/procedure className: "title", begin: "(\\bwith\\s+)?\\b(function|procedure)\\s+", end: "(\\(|\\s+|$)", excludeBegin: true, excludeEnd: true, illegal: BAD_CHARS, }, // 'self' // // parameter types VAR_DECLS, { // return type className: "type", begin: "\\breturn\\s+", end: "(\\s+|;|$)", keywords: "return", excludeBegin: true, excludeEnd: true, // we are done with functions endsParent: true, illegal: BAD_CHARS, }, ], }, { // new type declarations // maybe inside generic className: "type", begin: "\\b(sub)?type\\s+", end: "\\s+", keywords: "type", excludeBegin: true, illegal: BAD_CHARS, }, // see comment above the definition VAR_DECLS, // no markup // relevance boosters for small snippets // {begin: '\\s*=>\\s*'}, // {begin: '\\s*:=\\s*'}, // {begin: '\\s+:=\\s+'}, ], }; }, }, { name: "angelscript", /* Language: AngelScript Author: Melissa Geels Category: scripting */ create: function (hljs) { var builtInTypeMode = { className: "built_in", begin: "\\b(void|bool|int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|string|ref|array|double|float|auto|dictionary)", }; var objectHandleMode = { className: "symbol", begin: "[a-zA-Z0-9_]+@", }; var genericMode = { className: "keyword", begin: "<", end: ">", contains: [builtInTypeMode, objectHandleMode], }; builtInTypeMode.contains = [genericMode]; objectHandleMode.contains = [genericMode]; return { aliases: ["asc"], keywords: "for in|0 break continue while do|0 return if else case switch namespace is cast " + "or and xor not get|0 in inout|10 out override set|0 private public const default|0 " + "final shared external mixin|10 enum typedef funcdef this super import from interface " + "abstract|0 try catch protected explicit", // avoid close detection with C# and JS illegal: "(^using\\s+[A-Za-z0-9_\\.]+;$|\\bfunction\s*[^\\(])", contains: [ { // 'strings' className: "string", begin: "'", end: "'", illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }, { // "strings" className: "string", begin: '"', end: '"', illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }, // """heredoc strings""" { className: "string", begin: '"""', end: '"""', }, hljs.C_LINE_COMMENT_MODE, // single-line comments hljs.C_BLOCK_COMMENT_MODE, // comment blocks { // interface or namespace declaration beginKeywords: "interface namespace", end: "{", illegal: "[;.\\-]", contains: [ { // interface or namespace name className: "symbol", begin: "[a-zA-Z0-9_]+", }, ], }, { // class declaration beginKeywords: "class", end: "{", illegal: "[;.\\-]", contains: [ { // class name className: "symbol", begin: "[a-zA-Z0-9_]+", contains: [ { begin: "[:,]\\s*", contains: [ { className: "symbol", begin: "[a-zA-Z0-9_]+", }, ], }, ], }, ], }, builtInTypeMode, // built-in types objectHandleMode, // object handles { // literals className: "literal", begin: "\\b(null|true|false)", }, { // numbers className: "number", begin: "(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?f?|\\.\\d+f?)([eE][-+]?\\d+f?)?)", }, ], }; }, }, { name: "apache", /* Language: Apache Author: Ruslan Keba Contributors: Ivan Sagalaev Website: http://rukeba.com/ Description: language definition for Apache configuration files (httpd.conf & .htaccess) Category: common, config */ create: function (hljs) { var NUMBER = { className: "number", begin: "[\\$%]\\d+" }; return { aliases: ["apacheconf"], case_insensitive: true, contains: [ hljs.HASH_COMMENT_MODE, { className: "section", begin: "" }, { className: "attribute", begin: /\w+/, relevance: 0, // keywords aren’t needed for highlighting per se, they only boost relevance // for a very generally defined mode (starts with a word, ends with line-end keywords: { nomarkup: "order deny allow setenv rewriterule rewriteengine rewritecond documentroot " + "sethandler errordocument loadmodule options header listen serverroot " + "servername", }, starts: { end: /$/, relevance: 0, keywords: { literal: "on off all", }, contains: [ { className: "meta", begin: "\\s\\[", end: "\\]$", }, { className: "variable", begin: "[\\$%]\\{", end: "\\}", contains: ["self", NUMBER], }, NUMBER, hljs.QUOTE_STRING_MODE, ], }, }, ], illegal: /\S/, }; }, }, { name: "applescript", /* Language: AppleScript Authors: Nathan Grigg , Dr. Drang Category: scripting */ create: function (hljs) { var STRING = hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: "", }); var PARAMS = { className: "params", begin: "\\(", end: "\\)", contains: ["self", hljs.C_NUMBER_MODE, STRING], }; var COMMENT_MODE_1 = hljs.COMMENT("--", "$"); var COMMENT_MODE_2 = hljs.COMMENT("\\(\\*", "\\*\\)", { contains: ["self", COMMENT_MODE_1], //allow nesting }); var COMMENTS = [ COMMENT_MODE_1, COMMENT_MODE_2, hljs.HASH_COMMENT_MODE, ]; return { aliases: ["osascript"], keywords: { keyword: "about above after against and around as at back before beginning " + "behind below beneath beside between but by considering " + "contain contains continue copy div does eighth else end equal " + "equals error every exit fifth first for fourth from front " + "get given global if ignoring in into is it its last local me " + "middle mod my ninth not of on onto or over prop property put ref " + "reference repeat returning script second set seventh since " + "sixth some tell tenth that the|0 then third through thru " + "timeout times to transaction try until where while whose with " + "without", literal: "AppleScript false linefeed return pi quote result space tab true", built_in: "alias application boolean class constant date file integer list " + "number real record string text " + "activate beep count delay launch log offset read round " + "run say summarize write " + "character characters contents day frontmost id item length " + "month name paragraph paragraphs rest reverse running time version " + "weekday word words year", }, contains: [ STRING, hljs.C_NUMBER_MODE, { className: "built_in", begin: "\\b(clipboard info|the clipboard|info for|list (disks|folder)|" + "mount volume|path to|(close|open for) access|(get|set) eof|" + "current date|do shell script|get volume settings|random number|" + "set volume|system attribute|system info|time to GMT|" + "(load|run|store) script|scripting components|" + "ASCII (character|number)|localized string|" + "choose (application|color|file|file name|" + "folder|from list|remote application|URL)|" + "display (alert|dialog))\\b|^\\s*return\\b", }, { className: "literal", begin: "\\b(text item delimiters|current application|missing value)\\b", }, { className: "keyword", begin: "\\b(apart from|aside from|instead of|out of|greater than|" + "isn't|(doesn't|does not) (equal|come before|come after|contain)|" + "(greater|less) than( or equal)?|(starts?|ends|begins?) with|" + "contained by|comes (before|after)|a (ref|reference)|POSIX file|" + "POSIX path|(date|time) string|quoted form)\\b", }, { beginKeywords: "on", illegal: "[${=;\\n]", contains: [hljs.UNDERSCORE_TITLE_MODE, PARAMS], }, ].concat(COMMENTS), illegal: "//|->|=>|\\[\\[", }; }, }, { name: "arcade", /* Language: ArcGIS Arcade Category: scripting Author: John Foster Description: ArcGIS Arcade is an expression language used in many Esri ArcGIS products such as Pro, Online, Server, Runtime, JavaScript, and Python */ create: function (hljs) { var IDENT_RE = "[A-Za-z_][0-9A-Za-z_]*"; var KEYWORDS = { keyword: "if for while var new function do return void else break", literal: "true false null undefined NaN Infinity PI BackSlash DoubleQuote ForwardSlash NewLine SingleQuote Tab", built_in: "Abs Acos Area AreaGeodetic Asin Atan Atan2 Average Boolean Buffer BufferGeodetic " + "Ceil Centroid Clip Console Constrain Contains Cos Count Crosses Cut Date DateAdd " + "DateDiff Day Decode DefaultValue Dictionary Difference Disjoint Distance Distinct " + "DomainCode DomainName Equals Exp Extent Feature FeatureSet FeatureSetById FeatureSetByTitle " + "FeatureSetByUrl Filter First Floor Geometry Guid HasKey Hour IIf IndexOf Intersection " + "Intersects IsEmpty Length LengthGeodetic Log Max Mean Millisecond Min Minute Month " + "MultiPartToSinglePart Multipoint NextSequenceValue Now Number OrderBy Overlaps Point Polygon " + "Polyline Pow Random Relate Reverse Round Second SetGeometry Sin Sort Sqrt Stdev Sum " + "SymmetricDifference Tan Text Timestamp Today ToLocal Top Touches ToUTC TypeOf Union Variance " + "Weekday When Within Year ", }; var EXPRESSIONS; var SYMBOL = { className: "symbol", begin: "\\$[feature|layer|map|value|view]+", }; var NUMBER = { className: "number", variants: [ { begin: "\\b(0[bB][01]+)" }, { begin: "\\b(0[oO][0-7]+)" }, { begin: hljs.C_NUMBER_RE }, ], relevance: 0, }; var SUBST = { className: "subst", begin: "\\$\\{", end: "\\}", keywords: KEYWORDS, contains: [], // defined later }; var TEMPLATE_STRING = { className: "string", begin: "`", end: "`", contains: [hljs.BACKSLASH_ESCAPE, SUBST], }; SUBST.contains = [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, TEMPLATE_STRING, NUMBER, hljs.REGEXP_MODE, ]; var PARAMS_CONTAINS = SUBST.contains.concat([ hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE, ]); return { aliases: ["arcade"], keywords: KEYWORDS, contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, TEMPLATE_STRING, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, SYMBOL, NUMBER, { // object attr container begin: /[{,]\s*/, relevance: 0, contains: [ { begin: IDENT_RE + "\\s*:", returnBegin: true, relevance: 0, contains: [ { className: "attr", begin: IDENT_RE, relevance: 0, }, ], }, ], }, { // "value" container begin: "(" + hljs.RE_STARTERS_RE + "|\\b(return)\\b)\\s*", keywords: "return", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.REGEXP_MODE, { className: "function", begin: "(\\(.*?\\)|" + IDENT_RE + ")\\s*=>", returnBegin: true, end: "\\s*=>", contains: [ { className: "params", variants: [ { begin: IDENT_RE, }, { begin: /\(\s*\)/, }, { begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, keywords: KEYWORDS, contains: PARAMS_CONTAINS, }, ], }, ], }, ], relevance: 0, }, { className: "function", beginKeywords: "function", end: /\{/, excludeEnd: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE }), { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, contains: PARAMS_CONTAINS, }, ], illegal: /\[|%/, }, { begin: /\$[(.]/, }, ], illegal: /#(?!!)/, }; }, }, { name: "arduino", /* Language: Arduino Author: Stefania Mellai Description: The Arduino® Language is a superset of C++. This rules are designed to highlight the Arduino® source code. For info about language see http://www.arduino.cc. Requires: cpp.js */ create: function (hljs) { var CPP = hljs.getLanguage("cpp").exports; return { keywords: { keyword: "boolean byte word string String array " + CPP.keywords.keyword, built_in: "setup loop while catch for if do goto try switch case else " + "default break continue return " + "KeyboardController MouseController SoftwareSerial " + "EthernetServer EthernetClient LiquidCrystal " + "RobotControl GSMVoiceCall EthernetUDP EsploraTFT " + "HttpClient RobotMotor WiFiClient GSMScanner " + "FileSystem Scheduler GSMServer YunClient YunServer " + "IPAddress GSMClient GSMModem Keyboard Ethernet " + "Console GSMBand Esplora Stepper Process " + "WiFiUDP GSM_SMS Mailbox USBHost Firmata PImage " + "Client Server GSMPIN FileIO Bridge Serial " + "EEPROM Stream Mouse Audio Servo File Task " + "GPRS WiFi Wire TFT GSM SPI SD " + "runShellCommandAsynchronously analogWriteResolution " + "retrieveCallingNumber printFirmwareVersion " + "analogReadResolution sendDigitalPortPair " + "noListenOnLocalhost readJoystickButton setFirmwareVersion " + "readJoystickSwitch scrollDisplayRight getVoiceCallStatus " + "scrollDisplayLeft writeMicroseconds delayMicroseconds " + "beginTransmission getSignalStrength runAsynchronously " + "getAsynchronously listenOnLocalhost getCurrentCarrier " + "readAccelerometer messageAvailable sendDigitalPorts " + "lineFollowConfig countryNameWrite runShellCommand " + "readStringUntil rewindDirectory readTemperature " + "setClockDivider readLightSensor endTransmission " + "analogReference detachInterrupt countryNameRead " + "attachInterrupt encryptionType readBytesUntil " + "robotNameWrite readMicrophone robotNameRead cityNameWrite " + "userNameWrite readJoystickY readJoystickX mouseReleased " + "openNextFile scanNetworks noInterrupts digitalWrite " + "beginSpeaker mousePressed isActionDone mouseDragged " + "displayLogos noAutoscroll addParameter remoteNumber " + "getModifiers keyboardRead userNameRead waitContinue " + "processInput parseCommand printVersion readNetworks " + "writeMessage blinkVersion cityNameRead readMessage " + "setDataMode parsePacket isListening setBitOrder " + "beginPacket isDirectory motorsWrite drawCompass " + "digitalRead clearScreen serialEvent rightToLeft " + "setTextSize leftToRight requestFrom keyReleased " + "compassRead analogWrite interrupts WiFiServer " + "disconnect playMelody parseFloat autoscroll " + "getPINUsed setPINUsed setTimeout sendAnalog " + "readSlider analogRead beginWrite createChar " + "motorsStop keyPressed tempoWrite readButton " + "subnetMask debugPrint macAddress writeGreen " + "randomSeed attachGPRS readString sendString " + "remotePort releaseAll mouseMoved background " + "getXChange getYChange answerCall getResult " + "voiceCall endPacket constrain getSocket writeJSON " + "getButton available connected findUntil readBytes " + "exitValue readGreen writeBlue startLoop IPAddress " + "isPressed sendSysex pauseMode gatewayIP setCursor " + "getOemKey tuneWrite noDisplay loadImage switchPIN " + "onRequest onReceive changePIN playFile noBuffer " + "parseInt overflow checkPIN knobRead beginTFT " + "bitClear updateIR bitWrite position writeRGB " + "highByte writeRed setSpeed readBlue noStroke " + "remoteIP transfer shutdown hangCall beginSMS " + "endWrite attached maintain noCursor checkReg " + "checkPUK shiftOut isValid shiftIn pulseIn " + "connect println localIP pinMode getIMEI " + "display noBlink process getBand running beginSD " + "drawBMP lowByte setBand release bitRead prepare " + "pointTo readRed setMode noFill remove listen " + "stroke detach attach noTone exists buffer " + "height bitSet circle config cursor random " + "IRread setDNS endSMS getKey micros " + "millis begin print write ready flush width " + "isPIN blink clear press mkdir rmdir close " + "point yield image BSSID click delay " + "read text move peek beep rect line open " + "seek fill size turn stop home find " + "step tone sqrt RSSI SSID " + "end bit tan cos sin pow map abs max " + "min get run put", literal: "DIGITAL_MESSAGE FIRMATA_STRING ANALOG_MESSAGE " + "REPORT_DIGITAL REPORT_ANALOG INPUT_PULLUP " + "SET_PIN_MODE INTERNAL2V56 SYSTEM_RESET LED_BUILTIN " + "INTERNAL1V1 SYSEX_START INTERNAL EXTERNAL " + "DEFAULT OUTPUT INPUT HIGH LOW", }, contains: [ CPP.preprocessor, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, ], }; }, }, { name: "armasm", /* Language: ARM Assembly Author: Dan Panzarella Description: ARM Assembly including Thumb and Thumb2 instructions Category: assembler */ create: function (hljs) { //local labels: %?[FB]?[AT]?\d{1,2}\w+ return { case_insensitive: true, aliases: ["arm"], lexemes: "\\.?" + hljs.IDENT_RE, keywords: { meta: //GNU preprocs ".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg " + //ARM directives "ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ", built_in: "r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 " + //standard registers "pc lr sp ip sl sb fp " + //typical regs plus backward compatibility "a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 " + //more regs and fp "p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 " + //coprocessor regs "c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 " + //more coproc "q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 " + //advanced SIMD NEON regs //program status registers "cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf " + "spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf " + //NEON and VFP registers "s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 " + "s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 " + "d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 " + "d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 " + "{PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @", }, contains: [ { className: "keyword", begin: "\\b(" + //mnemonics "adc|" + "(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|" + "and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|" + "bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|" + "setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|" + "ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|" + "mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|" + "mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|" + "mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|" + "rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|" + "stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|" + "[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|" + "wfe|wfi|yield" + ")" + "(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?" + //condition codes "[sptrx]?", //legal postfixes end: "\\s", }, hljs.COMMENT("[;@]", "$", { relevance: 0 }), hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, { className: "string", begin: "'", end: "[^\\\\]'", relevance: 0, }, { className: "title", begin: "\\|", end: "\\|", illegal: "\\n", relevance: 0, }, { className: "number", variants: [ { begin: "[#$=]?0x[0-9a-f]+" }, //hex { begin: "[#$=]?0b[01]+" }, //bin { begin: "[#$=]\\d+" }, //literal { begin: "\\b\\d+" }, //bare number ], relevance: 0, }, { className: "symbol", variants: [ { begin: "^[a-z_\\.\\$][a-z0-9_\\.\\$]+" }, //ARM syntax { begin: "^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:" }, //GNU ARM syntax { begin: "[=#]\\w+" }, //label reference ], relevance: 0, }, ], }; }, }, { name: "asciidoc", /* Language: AsciiDoc Requires: xml.js Author: Dan Allen Website: http://google.com/profiles/dan.j.allen Description: A semantic, text-based document format that can be exported to HTML, DocBook and other backends. Category: markup */ create: function (hljs) { return { aliases: ["adoc"], contains: [ // block comment hljs.COMMENT( "^/{4,}\\n", "\\n/{4,}$", // can also be done as... //'^/{4,}$', //'^/{4,}$', { relevance: 10, }, ), // line comment hljs.COMMENT("^//", "$", { relevance: 0, }), // title { className: "title", begin: "^\\.\\w.*$", }, // example, admonition & sidebar blocks { begin: "^[=\\*]{4,}\\n", end: "\\n^[=\\*]{4,}$", relevance: 10, }, // headings { className: "section", relevance: 10, variants: [ { begin: "^(={1,5}) .+?( \\1)?$" }, { begin: "^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$" }, ], }, // document attributes { className: "meta", begin: "^:.+?:", end: "\\s", excludeEnd: true, relevance: 10, }, // block attributes { className: "meta", begin: "^\\[.+?\\]$", relevance: 0, }, // quoteblocks { className: "quote", begin: "^_{4,}\\n", end: "\\n_{4,}$", relevance: 10, }, // listing and literal blocks { className: "code", begin: "^[\\-\\.]{4,}\\n", end: "\\n[\\-\\.]{4,}$", relevance: 10, }, // passthrough blocks { begin: "^\\+{4,}\\n", end: "\\n\\+{4,}$", contains: [ { begin: "<", end: ">", subLanguage: "xml", relevance: 0, }, ], relevance: 10, }, // lists (can only capture indicators) { className: "bullet", begin: "^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+", }, // admonition { className: "symbol", begin: "^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+", relevance: 10, }, // inline strong { className: "strong", // must not follow a word character or be followed by an asterisk or space begin: "\\B\\*(?![\\*\\s])", end: "(\\n{2}|\\*)", // allow escaped asterisk followed by word char contains: [ { begin: "\\\\*\\w", relevance: 0, }, ], }, // inline emphasis { className: "emphasis", // must not follow a word character or be followed by a single quote or space begin: "\\B'(?!['\\s])", end: "(\\n{2}|')", // allow escaped single quote followed by word char contains: [ { begin: "\\\\'\\w", relevance: 0, }, ], relevance: 0, }, // inline emphasis (alt) { className: "emphasis", // must not follow a word character or be followed by an underline or space begin: "_(?![_\\s])", end: "(\\n{2}|_)", relevance: 0, }, // inline smart quotes { className: "string", variants: [{ begin: "``.+?''" }, { begin: "`.+?'" }], }, // inline code snippets (TODO should get same treatment as strong and emphasis) { className: "code", begin: "(`.+?`|\\+.+?\\+)", relevance: 0, }, // indented literal block { className: "code", begin: "^[ \\t]", end: "$", relevance: 0, }, // horizontal rules { begin: "^'{3,}[ \\t]*$", relevance: 10, }, // images and links { begin: "(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]", returnBegin: true, contains: [ { begin: "(link|image:?):", relevance: 0, }, { className: "link", begin: "\\w", end: "[^\\[]+", relevance: 0, }, { className: "string", begin: "\\[", end: "\\]", excludeBegin: true, excludeEnd: true, relevance: 0, }, ], relevance: 10, }, ], }; }, }, { name: "aspectj", /* Language: AspectJ Author: Hakan Ozler Description: Syntax Highlighting for the AspectJ Language which is a general-purpose aspect-oriented extension to the Java programming language. */ create: function (hljs) { var KEYWORDS = "false synchronized int abstract float private char boolean static null if const " + "for true while long throw strictfp finally protected import native final return void " + "enum else extends implements break transient new catch instanceof byte super volatile case " + "assert short package default double public try this switch continue throws privileged " + "aspectOf adviceexecution proceed cflowbelow cflow initialization preinitialization " + "staticinitialization withincode target within execution getWithinTypeName handler " + "thisJoinPoint thisJoinPointStaticPart thisEnclosingJoinPointStaticPart declare parents " + "warning error soft precedence thisAspectInstance"; var SHORTKEYS = "get set args call"; return { keywords: KEYWORDS, illegal: /<\/|#/, contains: [ hljs.COMMENT("/\\*\\*", "\\*/", { relevance: 0, contains: [ { // eat up @'s in emails to prevent them to be recognized as doctags begin: /\w+@/, relevance: 0, }, { className: "doctag", begin: "@[A-Za-z]+", }, ], }), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { className: "class", beginKeywords: "aspect", end: /[{;=]/, excludeEnd: true, illegal: /[:;"\[\]]/, contains: [ { beginKeywords: "extends implements pertypewithin perthis pertarget percflowbelow percflow issingleton", }, hljs.UNDERSCORE_TITLE_MODE, { begin: /\([^\)]*/, end: /[)]+/, keywords: KEYWORDS + " " + SHORTKEYS, excludeEnd: false, }, ], }, { className: "class", beginKeywords: "class interface", end: /[{;=]/, excludeEnd: true, relevance: 0, keywords: "class interface", illegal: /[:"\[\]]/, contains: [ { beginKeywords: "extends implements" }, hljs.UNDERSCORE_TITLE_MODE, ], }, { // AspectJ Constructs beginKeywords: "pointcut after before around throwing returning", end: /[)]/, excludeEnd: false, illegal: /["\[\]]/, contains: [ { begin: hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", returnBegin: true, contains: [hljs.UNDERSCORE_TITLE_MODE], }, ], }, { begin: /[:]/, returnBegin: true, end: /[{;]/, relevance: 0, excludeEnd: false, keywords: KEYWORDS, illegal: /["\[\]]/, contains: [ { begin: hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", keywords: KEYWORDS + " " + SHORTKEYS, relevance: 0, }, hljs.QUOTE_STRING_MODE, ], }, { // this prevents 'new Name(...), or throw ...' from being recognized as a function definition beginKeywords: "new throw", relevance: 0, }, { // the function class is a bit different for AspectJ compared to the Java language className: "function", begin: /\w+ +\w+(\.)?\w+\s*\([^\)]*\)\s*((throws)[\w\s,]+)?[\{;]/, returnBegin: true, end: /[{;=]/, keywords: KEYWORDS, excludeEnd: true, contains: [ { begin: hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", returnBegin: true, relevance: 0, contains: [hljs.UNDERSCORE_TITLE_MODE], }, { className: "params", begin: /\(/, end: /\)/, relevance: 0, keywords: KEYWORDS, contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, hljs.C_NUMBER_MODE, { // annotation is also used in this language className: "meta", begin: "@[A-Za-z]+", }, ], }; }, }, { name: "autohotkey", /* Language: AutoHotkey Author: Seongwon Lee Description: AutoHotkey language definition Category: scripting */ create: function (hljs) { var BACKTICK_ESCAPE = { begin: "`[\\s\\S]", }; return { case_insensitive: true, aliases: ["ahk"], keywords: { keyword: "Break Continue Critical Exit ExitApp Gosub Goto New OnExit Pause return SetBatchLines SetTimer Suspend Thread Throw Until ahk_id ahk_class ahk_pid ahk_exe ahk_group", literal: "true false NOT AND OR", built_in: "ComSpec Clipboard ClipboardAll ErrorLevel", }, contains: [ BACKTICK_ESCAPE, hljs.inherit(hljs.QUOTE_STRING_MODE, { contains: [BACKTICK_ESCAPE], }), hljs.COMMENT(";", "$", { relevance: 0 }), hljs.C_BLOCK_COMMENT_MODE, { className: "number", begin: hljs.NUMBER_RE, relevance: 0, }, { className: "variable", //subst would be the most accurate however fails the point of highlighting. variable is comparably the most accurate that actually has some effect begin: "%[a-zA-Z0-9#_$@]+%", }, { className: "built_in", begin: "^\\s*\\w+\\s*(,|%)", //I don't really know if this is totally relevant }, { className: "title", //symbol would be most accurate however is higlighted just like built_in and that makes up a lot of AutoHotkey code //meaning that it would fail to highlight anything variants: [ { begin: '^[^\\n";]+::(?!=)' }, { begin: '^[^\\n";]+:(?!=)', relevance: 0 }, // zero relevance as it catches a lot of things // followed by a single ':' in many languages ], }, { className: "meta", begin: "^\\s*#\\w+", end: "$", relevance: 0, }, { className: "built_in", begin: "A_[a-zA-Z0-9]+", }, { // consecutive commas, not for highlighting but just for relevance begin: ",\\s*,", }, ], }; }, }, { name: "autoit", /* Language: AutoIt Author: Manh Tuan Description: AutoIt language definition Category: scripting */ create: function (hljs) { var KEYWORDS = "ByRef Case Const ContinueCase ContinueLoop " + "Default Dim Do Else ElseIf EndFunc EndIf EndSelect " + "EndSwitch EndWith Enum Exit ExitLoop For Func " + "Global If In Local Next ReDim Return Select Static " + "Step Switch Then To Until Volatile WEnd While With", LITERAL = "True False And Null Not Or", BUILT_IN = "Abs ACos AdlibRegister AdlibUnRegister Asc AscW ASin Assign ATan AutoItSetOption AutoItWinGetTitle AutoItWinSetTitle Beep Binary BinaryLen BinaryMid BinaryToString BitAND BitNOT BitOR BitRotate BitShift BitXOR BlockInput Break Call CDTray Ceiling Chr ChrW ClipGet ClipPut ConsoleRead ConsoleWrite ConsoleWriteError ControlClick ControlCommand ControlDisable ControlEnable ControlFocus ControlGetFocus ControlGetHandle ControlGetPos ControlGetText ControlHide ControlListView ControlMove ControlSend ControlSetText ControlShow ControlTreeView Cos Dec DirCopy DirCreate DirGetSize DirMove DirRemove DllCall DllCallAddress DllCallbackFree DllCallbackGetPtr DllCallbackRegister DllClose DllOpen DllStructCreate DllStructGetData DllStructGetPtr DllStructGetSize DllStructSetData DriveGetDrive DriveGetFileSystem DriveGetLabel DriveGetSerial DriveGetType DriveMapAdd DriveMapDel DriveMapGet DriveSetLabel DriveSpaceFree DriveSpaceTotal DriveStatus EnvGet EnvSet EnvUpdate Eval Execute Exp FileChangeDir FileClose FileCopy FileCreateNTFSLink FileCreateShortcut FileDelete FileExists FileFindFirstFile FileFindNextFile FileFlush FileGetAttrib FileGetEncoding FileGetLongName FileGetPos FileGetShortcut FileGetShortName FileGetSize FileGetTime FileGetVersion FileInstall FileMove FileOpen FileOpenDialog FileRead FileReadLine FileReadToArray FileRecycle FileRecycleEmpty FileSaveDialog FileSelectFolder FileSetAttrib FileSetEnd FileSetPos FileSetTime FileWrite FileWriteLine Floor FtpSetProxy FuncName GUICreate GUICtrlCreateAvi GUICtrlCreateButton GUICtrlCreateCheckbox GUICtrlCreateCombo GUICtrlCreateContextMenu GUICtrlCreateDate GUICtrlCreateDummy GUICtrlCreateEdit GUICtrlCreateGraphic GUICtrlCreateGroup GUICtrlCreateIcon GUICtrlCreateInput GUICtrlCreateLabel GUICtrlCreateList GUICtrlCreateListView GUICtrlCreateListViewItem GUICtrlCreateMenu GUICtrlCreateMenuItem GUICtrlCreateMonthCal GUICtrlCreateObj GUICtrlCreatePic GUICtrlCreateProgress GUICtrlCreateRadio GUICtrlCreateSlider GUICtrlCreateTab GUICtrlCreateTabItem GUICtrlCreateTreeView GUICtrlCreateTreeViewItem GUICtrlCreateUpdown GUICtrlDelete GUICtrlGetHandle GUICtrlGetState GUICtrlRead GUICtrlRecvMsg GUICtrlRegisterListViewSort GUICtrlSendMsg GUICtrlSendToDummy GUICtrlSetBkColor GUICtrlSetColor GUICtrlSetCursor GUICtrlSetData GUICtrlSetDefBkColor GUICtrlSetDefColor GUICtrlSetFont GUICtrlSetGraphic GUICtrlSetImage GUICtrlSetLimit GUICtrlSetOnEvent GUICtrlSetPos GUICtrlSetResizing GUICtrlSetState GUICtrlSetStyle GUICtrlSetTip GUIDelete GUIGetCursorInfo GUIGetMsg GUIGetStyle GUIRegisterMsg GUISetAccelerators GUISetBkColor GUISetCoord GUISetCursor GUISetFont GUISetHelp GUISetIcon GUISetOnEvent GUISetState GUISetStyle GUIStartGroup GUISwitch Hex HotKeySet HttpSetProxy HttpSetUserAgent HWnd InetClose InetGet InetGetInfo InetGetSize InetRead IniDelete IniRead IniReadSection IniReadSectionNames IniRenameSection IniWrite IniWriteSection InputBox Int IsAdmin IsArray IsBinary IsBool IsDeclared IsDllStruct IsFloat IsFunc IsHWnd IsInt IsKeyword IsNumber IsObj IsPtr IsString Log MemGetStats Mod MouseClick MouseClickDrag MouseDown MouseGetCursor MouseGetPos MouseMove MouseUp MouseWheel MsgBox Number ObjCreate ObjCreateInterface ObjEvent ObjGet ObjName OnAutoItExitRegister OnAutoItExitUnRegister Ping PixelChecksum PixelGetColor PixelSearch ProcessClose ProcessExists ProcessGetStats ProcessList ProcessSetPriority ProcessWait ProcessWaitClose ProgressOff ProgressOn ProgressSet Ptr Random RegDelete RegEnumKey RegEnumVal RegRead RegWrite Round Run RunAs RunAsWait RunWait Send SendKeepActive SetError SetExtended ShellExecute ShellExecuteWait Shutdown Sin Sleep SoundPlay SoundSetWaveVolume SplashImageOn SplashOff SplashTextOn Sqrt SRandom StatusbarGetText StderrRead StdinWrite StdioClose StdoutRead String StringAddCR StringCompare StringFormat StringFromASCIIArray StringInStr StringIsAlNum StringIsAlpha StringIsASCII StringIsDigit StringIsFloat StringIsInt StringIsLower StringIsSpace StringIsUpper StringIsXDigit StringLeft StringLen StringLower StringMid StringRegExp StringRegExpReplace StringReplace StringReverse StringRight StringSplit StringStripCR StringStripWS StringToASCIIArray StringToBinary StringTrimLeft StringTrimRight StringUpper Tan TCPAccept TCPCloseSocket TCPConnect TCPListen TCPNameToIP TCPRecv TCPSend TCPShutdown, UDPShutdown TCPStartup, UDPStartup TimerDiff TimerInit ToolTip TrayCreateItem TrayCreateMenu TrayGetMsg TrayItemDelete TrayItemGetHandle TrayItemGetState TrayItemGetText TrayItemSetOnEvent TrayItemSetState TrayItemSetText TraySetClick TraySetIcon TraySetOnEvent TraySetPauseIcon TraySetState TraySetToolTip TrayTip UBound UDPBind UDPCloseSocket UDPOpen UDPRecv UDPSend VarGetType WinActivate WinActive WinClose WinExists WinFlash WinGetCaretPos WinGetClassList WinGetClientSize WinGetHandle WinGetPos WinGetProcess WinGetState WinGetText WinGetTitle WinKill WinList WinMenuSelectItem WinMinimizeAll WinMinimizeAllUndo WinMove WinSetOnTop WinSetState WinSetTitle WinSetTrans WinWait", COMMENT = { variants: [ hljs.COMMENT(";", "$", { relevance: 0 }), hljs.COMMENT("#cs", "#ce"), hljs.COMMENT("#comments-start", "#comments-end"), ], }, VARIABLE = { begin: "\\$[A-z0-9_]+", }, STRING = { className: "string", variants: [ { begin: /"/, end: /"/, contains: [ { begin: /""/, relevance: 0, }, ], }, { begin: /'/, end: /'/, contains: [ { begin: /''/, relevance: 0, }, ], }, ], }, NUMBER = { variants: [hljs.BINARY_NUMBER_MODE, hljs.C_NUMBER_MODE], }, PREPROCESSOR = { className: "meta", begin: "#", end: "$", keywords: { "meta-keyword": "comments include include-once NoTrayIcon OnAutoItStartRegister pragma compile RequireAdmin", }, contains: [ { begin: /\\\n/, relevance: 0, }, { beginKeywords: "include", keywords: { "meta-keyword": "include" }, end: "$", contains: [ STRING, { className: "meta-string", variants: [ { begin: "<", end: ">", }, { begin: /"/, end: /"/, contains: [ { begin: /""/, relevance: 0, }, ], }, { begin: /'/, end: /'/, contains: [ { begin: /''/, relevance: 0, }, ], }, ], }, ], }, STRING, COMMENT, ], }, CONSTANT = { className: "symbol", // begin: '@', // end: '$', // keywords: 'AppDataCommonDir AppDataDir AutoItExe AutoItPID AutoItVersion AutoItX64 COM_EventObj CommonFilesDir Compiled ComputerName ComSpec CPUArch CR CRLF DesktopCommonDir DesktopDepth DesktopDir DesktopHeight DesktopRefresh DesktopWidth DocumentsCommonDir error exitCode exitMethod extended FavoritesCommonDir FavoritesDir GUI_CtrlHandle GUI_CtrlId GUI_DragFile GUI_DragId GUI_DropId GUI_WinHandle HomeDrive HomePath HomeShare HotKeyPressed HOUR IPAddress1 IPAddress2 IPAddress3 IPAddress4 KBLayout LF LocalAppDataDir LogonDNSDomain LogonDomain LogonServer MDAY MIN MON MSEC MUILang MyDocumentsDir NumParams OSArch OSBuild OSLang OSServicePack OSType OSVersion ProgramFilesDir ProgramsCommonDir ProgramsDir ScriptDir ScriptFullPath ScriptLineNumber ScriptName SEC StartMenuCommonDir StartMenuDir StartupCommonDir StartupDir SW_DISABLE SW_ENABLE SW_HIDE SW_LOCK SW_MAXIMIZE SW_MINIMIZE SW_RESTORE SW_SHOW SW_SHOWDEFAULT SW_SHOWMAXIMIZED SW_SHOWMINIMIZED SW_SHOWMINNOACTIVE SW_SHOWNA SW_SHOWNOACTIVATE SW_SHOWNORMAL SW_UNLOCK SystemDir TAB TempDir TRAY_ID TrayIconFlashing TrayIconVisible UserName UserProfileDir WDAY WindowsDir WorkingDir YDAY YEAR', // relevance: 5 begin: "@[A-z0-9_]+", }, FUNCTION = { className: "function", beginKeywords: "Func", end: "$", illegal: "\\$|\\[|%", contains: [ hljs.UNDERSCORE_TITLE_MODE, { className: "params", begin: "\\(", end: "\\)", contains: [VARIABLE, STRING, NUMBER], }, ], }; return { case_insensitive: true, illegal: /\/\*/, keywords: { keyword: KEYWORDS, built_in: BUILT_IN, literal: LITERAL, }, contains: [ COMMENT, VARIABLE, STRING, NUMBER, PREPROCESSOR, CONSTANT, FUNCTION, ], }; }, }, { name: "avrasm", /* Language: AVR Assembler Author: Vladimir Ermakov Category: assembler */ create: function (hljs) { return { case_insensitive: true, lexemes: "\\.?" + hljs.IDENT_RE, keywords: { keyword: /* mnemonic */ "adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs " + "brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr " + "clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor " + "fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul " + "muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs " + "sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub " + "subi swap tst wdr", built_in: /* general purpose registers */ "r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 " + "r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl " + /* IO Registers (ATMega128) */ "ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h " + "tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c " + "ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg " + "ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk " + "tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al " + "ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr " + "porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 " + "ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf", meta: ".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list " + ".listmac .macro .nolist .org .set", }, contains: [ hljs.C_BLOCK_COMMENT_MODE, hljs.COMMENT(";", "$", { relevance: 0, }), hljs.C_NUMBER_MODE, // 0x..., decimal, float hljs.BINARY_NUMBER_MODE, // 0b... { className: "number", begin: "\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)", // $..., 0o... }, hljs.QUOTE_STRING_MODE, { className: "string", begin: "'", end: "[^\\\\]'", illegal: "[^\\\\][^']", }, { className: "symbol", begin: "^[A-Za-z0-9_.$]+:" }, { className: "meta", begin: "#", end: "$" }, { // подстановка в «.macro» className: "subst", begin: "@[0-9]+", }, ], }; }, }, { name: "awk", /* Language: Awk Author: Matthew Daly Website: http://matthewdaly.co.uk/ Description: language definition for Awk scripts */ create: function (hljs) { var VARIABLE = { className: "variable", variants: [ { begin: /\$[\w\d#@][\w\d_]*/ }, { begin: /\$\{(.*?)}/ }, ], }; var KEYWORDS = "BEGIN END if else while do for in break continue delete next nextfile function func exit|10"; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE], variants: [ { begin: /(u|b)?r?'''/, end: /'''/, relevance: 10, }, { begin: /(u|b)?r?"""/, end: /"""/, relevance: 10, }, { begin: /(u|r|ur)'/, end: /'/, relevance: 10, }, { begin: /(u|r|ur)"/, end: /"/, relevance: 10, }, { begin: /(b|br)'/, end: /'/, }, { begin: /(b|br)"/, end: /"/, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, ], }; return { keywords: { keyword: KEYWORDS, }, contains: [ VARIABLE, STRING, hljs.REGEXP_MODE, hljs.HASH_COMMENT_MODE, hljs.NUMBER_MODE, ], }; }, }, { name: "axapta", /* Language: Axapta Author: Dmitri Roudakov Category: enterprise */ create: function (hljs) { return { keywords: "false int abstract private char boolean static null if for true " + "while long throw finally protected final return void enum else " + "break new catch byte super case short default double public try this switch " + "continue reverse firstfast firstonly forupdate nofetch sum avg minof maxof count " + "order group by asc desc index hint like dispaly edit client server ttsbegin " + "ttscommit str real date container anytype common div mod", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, { className: "meta", begin: "#", end: "$", }, { className: "class", beginKeywords: "class interface", end: "{", excludeEnd: true, illegal: ":", contains: [ { beginKeywords: "extends implements" }, hljs.UNDERSCORE_TITLE_MODE, ], }, ], }; }, }, { name: "bash", /* Language: Bash Author: vah Contributrors: Benjamin Pannell Category: common */ create: function (hljs) { var VAR = { className: "variable", variants: [ { begin: /\$[\w\d#@][\w\d_]*/ }, { begin: /\$\{(.*?)}/ }, ], }; var QUOTE_STRING = { className: "string", begin: /"/, end: /"/, contains: [ hljs.BACKSLASH_ESCAPE, VAR, { className: "variable", begin: /\$\(/, end: /\)/, contains: [hljs.BACKSLASH_ESCAPE], }, ], }; var ESCAPED_QUOTE = { className: "", begin: /\\"/, }; var APOS_STRING = { className: "string", begin: /'/, end: /'/, }; return { aliases: ["sh", "zsh"], lexemes: /\b-?[a-z\._]+\b/, keywords: { keyword: "if then else elif fi for while in do done case esac function", literal: "true false", built_in: // Shell built-ins // http://www.gnu.org/software/bash/manual/html_node/Shell-Builtin-Commands.html "break cd continue eval exec exit export getopts hash pwd readonly return shift test times " + "trap umask unset " + // Bash built-ins "alias bind builtin caller command declare echo enable help let local logout mapfile printf " + "read readarray source type typeset ulimit unalias " + // Shell modifiers "set shopt " + // Zsh built-ins "autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles " + "compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate " + "fc fg float functions getcap getln history integer jobs kill limit log noglob popd print " + "pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit " + "unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof " + "zpty zregexparse zsocket zstyle ztcp", _: "-ne -eq -lt -gt -f -d -e -s -l -a", // relevance booster }, contains: [ { className: "meta", begin: /^#![^\n]+sh\s*$/, relevance: 10, }, { className: "function", begin: /\w[\w\d_]*\s*\(\s*\)\s*\{/, returnBegin: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /\w[\w\d_]*/ }), ], relevance: 0, }, hljs.HASH_COMMENT_MODE, QUOTE_STRING, ESCAPED_QUOTE, APOS_STRING, VAR, ], }; }, }, { name: "basic", /* Language: Basic Author: Raphaël Assénat Description: Based on the BASIC reference from the Tandy 1000 guide */ create: function (hljs) { return { case_insensitive: true, illegal: "^\.", // Support explicitely typed variables that end with $%! or #. lexemes: "[a-zA-Z][a-zA-Z0-9_\$\%\!\#]*", keywords: { keyword: "ABS ASC AND ATN AUTO|0 BEEP BLOAD|10 BSAVE|10 CALL CALLS CDBL CHAIN CHDIR CHR$|10 CINT CIRCLE " + "CLEAR CLOSE CLS COLOR COM COMMON CONT COS CSNG CSRLIN CVD CVI CVS DATA DATE$ " + "DEFDBL DEFINT DEFSNG DEFSTR DEF|0 SEG USR DELETE DIM DRAW EDIT END ENVIRON ENVIRON$ " + "EOF EQV ERASE ERDEV ERDEV$ ERL ERR ERROR EXP FIELD FILES FIX FOR|0 FRE GET GOSUB|10 GOTO " + "HEX$ IF|0 THEN ELSE|0 INKEY$ INP INPUT INPUT# INPUT$ INSTR IMP INT IOCTL IOCTL$ KEY ON " + "OFF LIST KILL LEFT$ LEN LET LINE LLIST LOAD LOC LOCATE LOF LOG LPRINT USING LSET " + "MERGE MID$ MKDIR MKD$ MKI$ MKS$ MOD NAME NEW NEXT NOISE NOT OCT$ ON OR PEN PLAY STRIG OPEN OPTION " + "BASE OUT PAINT PALETTE PCOPY PEEK PMAP POINT POKE POS PRINT PRINT] PSET PRESET " + "PUT RANDOMIZE READ REM RENUM RESET|0 RESTORE RESUME RETURN|0 RIGHT$ RMDIR RND RSET " + "RUN SAVE SCREEN SGN SHELL SIN SOUND SPACE$ SPC SQR STEP STICK STOP STR$ STRING$ SWAP " + "SYSTEM TAB TAN TIME$ TIMER TROFF TRON TO USR VAL VARPTR VARPTR$ VIEW WAIT WHILE " + "WEND WIDTH WINDOW WRITE XOR", }, contains: [ hljs.QUOTE_STRING_MODE, hljs.COMMENT("REM", "$", { relevance: 10 }), hljs.COMMENT("'", "$", { relevance: 0 }), { // Match line numbers className: "symbol", begin: "^[0-9]+\ ", relevance: 10, }, { // Match typed numeric constants (1000, 12.34!, 1.2e5, 1.5#, 1.2D2) className: "number", begin: "\\b([0-9]+[0-9edED\.]*[#\!]?)", relevance: 0, }, { // Match hexadecimal numbers (&Hxxxx) className: "number", begin: "(\&[hH][0-9a-fA-F]{1,4})", }, { // Match octal numbers (&Oxxxxxx) className: "number", begin: "(\&[oO][0-7]{1,6})", }, ], }; }, }, { name: "bnf", /* Language: Backus–Naur Form Author: Oleg Efimov */ create: function (hljs) { return { contains: [ // Attribute { className: "attribute", begin: //, }, // Specific { begin: /::=/, starts: { end: /$/, contains: [ { begin: //, }, // Common hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, ], }, }, ], }; }, }, { name: "brainfuck", /* Language: Brainfuck Author: Evgeny Stepanischev */ create: function (hljs) { var LITERAL = { className: "literal", begin: "[\\+\\-]", relevance: 0, }; return { aliases: ["bf"], contains: [ hljs.COMMENT( "[^\\[\\]\\.,\\+\\-<> \r\n]", "[\\[\\]\\.,\\+\\-<> \r\n]", { returnEnd: true, relevance: 0, }, ), { className: "title", begin: "[\\[\\]]", relevance: 0, }, { className: "string", begin: "[\\.,]", relevance: 0, }, { // this mode works as the only relevance counter begin: /\+\+|\-\-/, returnBegin: true, contains: [LITERAL], }, LITERAL, ], }; }, }, { name: "cal", /* Language: C/AL Author: Kenneth Fuglsang Christensen Description: Provides highlighting of Microsoft Dynamics NAV C/AL code files */ create: function (hljs) { var KEYWORDS = "div mod in and or not xor asserterror begin case do downto else end exit for if of repeat then to " + "until while with var"; var LITERALS = "false true"; var COMMENT_MODES = [ hljs.C_LINE_COMMENT_MODE, hljs.COMMENT(/\{/, /\}/, { relevance: 0, }), hljs.COMMENT(/\(\*/, /\*\)/, { relevance: 10, }), ]; var STRING = { className: "string", begin: /'/, end: /'/, contains: [{ begin: /''/ }], }; var CHAR_STRING = { className: "string", begin: /(#\d+)+/, }; var DATE = { className: "number", begin: "\\b\\d+(\\.\\d+)?(DT|D|T)", relevance: 0, }; var DBL_QUOTED_VARIABLE = { className: "string", // not a string technically but makes sense to be highlighted in the same style begin: '"', end: '"', }; var PROCEDURE = { className: "function", beginKeywords: "procedure", end: /[:;]/, keywords: "procedure|10", contains: [ hljs.TITLE_MODE, { className: "params", begin: /\(/, end: /\)/, keywords: KEYWORDS, contains: [STRING, CHAR_STRING], }, ].concat(COMMENT_MODES), }; var OBJECT = { className: "class", begin: "OBJECT (Table|Form|Report|Dataport|Codeunit|XMLport|MenuSuite|Page|Query) (\\d+) ([^\\r\\n]+)", returnBegin: true, contains: [hljs.TITLE_MODE, PROCEDURE], }; return { case_insensitive: true, keywords: { keyword: KEYWORDS, literal: LITERALS }, illegal: /\/\*/, contains: [ STRING, CHAR_STRING, DATE, DBL_QUOTED_VARIABLE, hljs.NUMBER_MODE, OBJECT, PROCEDURE, ], }; }, }, { name: "capnproto", /* Language: Cap’n Proto Author: Oleg Efimov Description: Cap’n Proto message definition format Category: protocols */ create: function (hljs) { return { aliases: ["capnp"], keywords: { keyword: "struct enum interface union group import using const annotation extends in of on as with from fixed", built_in: "Void Bool Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64 Float32 Float64 " + "Text Data AnyPointer AnyStruct Capability List", literal: "true false", }, contains: [ hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, hljs.HASH_COMMENT_MODE, { className: "meta", begin: /@0x[\w\d]{16};/, illegal: /\n/, }, { className: "symbol", begin: /@\d+\b/, }, { className: "class", beginKeywords: "struct enum", end: /\{/, illegal: /\n/, contains: [ hljs.inherit(hljs.TITLE_MODE, { starts: { endsWithParent: true, excludeEnd: true }, // hack: eating everything after the first title }), ], }, { className: "class", beginKeywords: "interface", end: /\{/, illegal: /\n/, contains: [ hljs.inherit(hljs.TITLE_MODE, { starts: { endsWithParent: true, excludeEnd: true }, // hack: eating everything after the first title }), ], }, ], }; }, }, { name: "ceylon", /* Language: Ceylon Author: Lucas Werkmeister */ create: function (hljs) { // 2.3. Identifiers and keywords var KEYWORDS = "assembly module package import alias class interface object given value " + "assign void function new of extends satisfies abstracts in out return " + "break continue throw assert dynamic if else switch case for while try " + "catch finally then let this outer super is exists nonempty"; // 7.4.1 Declaration Modifiers var DECLARATION_MODIFIERS = "shared abstract formal default actual variable late native deprecated" + "final sealed annotation suppressWarnings small"; // 7.4.2 Documentation var DOCUMENTATION = "doc by license see throws tagged"; var SUBST = { className: "subst", excludeBegin: true, excludeEnd: true, begin: /``/, end: /``/, keywords: KEYWORDS, relevance: 10, }; var EXPRESSIONS = [ { // verbatim string className: "string", begin: '"""', end: '"""', relevance: 10, }, { // string literal or template className: "string", begin: '"', end: '"', contains: [SUBST], }, { // character literal className: "string", begin: "'", end: "'", }, { // numeric literal className: "number", begin: "#[0-9a-fA-F_]+|\\$[01_]+|[0-9_]+(?:\\.[0-9_](?:[eE][+-]?\\d+)?)?[kMGTPmunpf]?", relevance: 0, }, ]; SUBST.contains = EXPRESSIONS; return { keywords: { keyword: KEYWORDS + " " + DECLARATION_MODIFIERS, meta: DOCUMENTATION, }, illegal: "\\$[^01]|#[^0-9a-fA-F]", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.COMMENT("/\\*", "\\*/", { contains: ["self"] }), { // compiler annotation className: "meta", begin: '@[a-z]\\w*(?:\\:\"[^\"]*\")?', }, ].concat(EXPRESSIONS), }; }, }, { name: "clean", /* Language: Clean Author: Camil Staps Category: functional Website: http://clean.cs.ru.nl */ create: function (hljs) { return { aliases: ["clean", "icl", "dcl"], keywords: { keyword: "if let in with where case of class instance otherwise " + "implementation definition system module from import qualified as " + "special code inline foreign export ccall stdcall generic derive " + "infix infixl infixr", built_in: "Int Real Char Bool", literal: "True False", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, { begin: "->|<-[|:]?|#!?|>>=|\\{\\||\\|\\}|:==|=:|<>" }, // relevance booster ], }; }, }, { name: "clojure-repl", /* Language: Clojure REPL Description: Clojure REPL sessions Author: Ivan Sagalaev Requires: clojure.js Category: lisp */ create: function (hljs) { return { contains: [ { className: "meta", begin: /^([\w.-]+|\s*#_)?=>/, starts: { end: /$/, subLanguage: "clojure", }, }, ], }; }, }, { name: "clojure", /* Language: Clojure Description: Clojure syntax (based on lisp.js) Author: mfornos Contributors: Martin Clausen Category: lisp */ create: function (hljs) { var keywords = { "builtin-name": // Clojure keywords "def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem " + "quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? " + "set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? " + "class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? " + "string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . " + "inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last " + "drop-while while intern condp case reduced cycle split-at split-with repeat replicate " + "iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext " + "nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends " + "add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler " + "set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter " + "monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or " + "when when-not when-let comp juxt partial sequence memoize constantly complement identity assert " + "peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast " + "sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import " + "refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! " + "assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger " + "bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline " + "flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking " + "assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! " + "reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! " + "new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty " + "hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list " + "disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer " + "chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate " + "unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta " + "lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize", }; var SYMBOLSTART = "a-zA-Z_\\-!.?+*=<>&#'"; var SYMBOL_RE = "[" + SYMBOLSTART + "][" + SYMBOLSTART + "0-9/;:]*"; var SIMPLE_NUMBER_RE = "[-+]?\\d+(\\.\\d+)?"; var SYMBOL = { begin: SYMBOL_RE, relevance: 0, }; var NUMBER = { className: "number", begin: SIMPLE_NUMBER_RE, relevance: 0, }; var STRING = hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null, }); var COMMENT = hljs.COMMENT(";", "$", { relevance: 0, }); var LITERAL = { className: "literal", begin: /\b(true|false|nil)\b/, }; var COLLECTION = { begin: "[\\[\\{]", end: "[\\]\\}]", }; var HINT = { className: "comment", begin: "\\^" + SYMBOL_RE, }; var HINT_COL = hljs.COMMENT("\\^\\{", "\\}"); var KEY = { className: "symbol", begin: "[:]{1,2}" + SYMBOL_RE, }; var LIST = { begin: "\\(", end: "\\)", }; var BODY = { endsWithParent: true, relevance: 0, }; var NAME = { keywords: keywords, lexemes: SYMBOL_RE, className: "name", begin: SYMBOL_RE, starts: BODY, }; var DEFAULT_CONTAINS = [ LIST, STRING, HINT, HINT_COL, COMMENT, KEY, COLLECTION, NUMBER, LITERAL, SYMBOL, ]; LIST.contains = [hljs.COMMENT("comment", ""), NAME, BODY]; BODY.contains = DEFAULT_CONTAINS; COLLECTION.contains = DEFAULT_CONTAINS; HINT_COL.contains = [COLLECTION]; return { aliases: ["clj"], illegal: /\S/, contains: [ LIST, STRING, HINT, HINT_COL, COMMENT, KEY, COLLECTION, NUMBER, LITERAL, ], }; }, }, { name: "cmake", /* Language: CMake Description: CMake is an open-source cross-platform system for build automation. Author: Igor Kalnitsky Website: http://kalnitsky.org/ */ create: function (hljs) { return { aliases: ["cmake.in"], case_insensitive: true, keywords: { keyword: // scripting commands "break cmake_host_system_information cmake_minimum_required cmake_parse_arguments " + "cmake_policy configure_file continue elseif else endforeach endfunction endif endmacro " + "endwhile execute_process file find_file find_library find_package find_path " + "find_program foreach function get_cmake_property get_directory_property " + "get_filename_component get_property if include include_guard list macro " + "mark_as_advanced math message option return separate_arguments " + "set_directory_properties set_property set site_name string unset variable_watch while " + // project commands "add_compile_definitions add_compile_options add_custom_command add_custom_target " + "add_definitions add_dependencies add_executable add_library add_link_options " + "add_subdirectory add_test aux_source_directory build_command create_test_sourcelist " + "define_property enable_language enable_testing export fltk_wrap_ui " + "get_source_file_property get_target_property get_test_property include_directories " + "include_external_msproject include_regular_expression install link_directories " + "link_libraries load_cache project qt_wrap_cpp qt_wrap_ui remove_definitions " + "set_source_files_properties set_target_properties set_tests_properties source_group " + "target_compile_definitions target_compile_features target_compile_options " + "target_include_directories target_link_directories target_link_libraries " + "target_link_options target_sources try_compile try_run " + // CTest commands "ctest_build ctest_configure ctest_coverage ctest_empty_binary_directory ctest_memcheck " + "ctest_read_custom_files ctest_run_script ctest_sleep ctest_start ctest_submit " + "ctest_test ctest_update ctest_upload " + // deprecated commands "build_name exec_program export_library_dependencies install_files install_programs " + "install_targets load_command make_directory output_required_files remove " + "subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file " + "qt5_use_modules qt5_use_package qt5_wrap_cpp " + // core keywords "on off true false and or not command policy target test exists is_newer_than " + "is_directory is_symlink is_absolute matches less greater equal less_equal " + "greater_equal strless strgreater strequal strless_equal strgreater_equal version_less " + "version_greater version_equal version_less_equal version_greater_equal in_list defined", }, contains: [ { className: "variable", begin: "\\${", end: "}", }, hljs.HASH_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, ], }; }, }, { name: "coffeescript", /* Language: CoffeeScript Author: Dmytrii Nagirniak Contributors: Oleg Efimov , Cédric Néhémie Description: CoffeeScript is a programming language that transcompiles to JavaScript. For info about language see http://coffeescript.org/ Category: common, scripting */ create: function (hljs) { var KEYWORDS = { keyword: // JS keywords "in if for while finally new do return else break catch instanceof throw try this " + "switch continue typeof delete debugger super yield import export from as default await " + // Coffee keywords "then unless until loop of by when and or is isnt not", literal: // JS literals "true false null undefined " + // Coffee literals "yes no on off", built_in: "npm require console print module global window document", }; var JS_IDENT_RE = "[A-Za-z$_][0-9A-Za-z$_]*"; var SUBST = { className: "subst", begin: /#\{/, end: /}/, keywords: KEYWORDS, }; var EXPRESSIONS = [ hljs.BINARY_NUMBER_MODE, hljs.inherit(hljs.C_NUMBER_MODE, { starts: { end: "(\\s*/)?", relevance: 0 }, }), // a number tries to eat the following slash to prevent treating it as a regexp { className: "string", variants: [ { begin: /'''/, end: /'''/, contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /'/, end: /'/, contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /"""/, end: /"""/, contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, { begin: /"/, end: /"/, contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, ], }, { className: "regexp", variants: [ { begin: "///", end: "///", contains: [SUBST, hljs.HASH_COMMENT_MODE], }, { begin: "//[gim]*", relevance: 0, }, { // regex can't start with space to parse x / 2 / 3 as two divisions // regex can't start with *, and it supports an "illegal" in the main mode begin: /\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/, }, ], }, { begin: "@" + JS_IDENT_RE, // relevance booster }, { subLanguage: "javascript", excludeBegin: true, excludeEnd: true, variants: [ { begin: "```", end: "```", }, { begin: "`", end: "`", }, ], }, ]; SUBST.contains = EXPRESSIONS; var TITLE = hljs.inherit(hljs.TITLE_MODE, { begin: JS_IDENT_RE, }); var PARAMS_RE = "(\\(.*\\))?\\s*\\B[-=]>"; var PARAMS = { className: "params", begin: "\\([^\\(]", returnBegin: true, /* We need another contained nameless mode to not have every nested pair of parens to be called "params" */ contains: [ { begin: /\(/, end: /\)/, keywords: KEYWORDS, contains: ["self"].concat(EXPRESSIONS), }, ], }; return { aliases: ["coffee", "cson", "iced"], keywords: KEYWORDS, illegal: /\/\*/, contains: EXPRESSIONS.concat([ hljs.COMMENT("###", "###"), hljs.HASH_COMMENT_MODE, { className: "function", begin: "^\\s*" + JS_IDENT_RE + "\\s*=\\s*" + PARAMS_RE, end: "[-=]>", returnBegin: true, contains: [TITLE, PARAMS], }, { // anonymous function start begin: /[:\(,=]\s*/, relevance: 0, contains: [ { className: "function", begin: PARAMS_RE, end: "[-=]>", returnBegin: true, contains: [PARAMS], }, ], }, { className: "class", beginKeywords: "class", end: "$", illegal: /[:="\[\]]/, contains: [ { beginKeywords: "extends", endsWithParent: true, illegal: /[:="\[\]]/, contains: [TITLE], }, TITLE, ], }, { begin: JS_IDENT_RE + ":", end: ":", returnBegin: true, returnEnd: true, relevance: 0, }, ]), }; }, }, { name: "coq", /* Language: Coq Author: Stephan Boyer Category: functional */ create: function (hljs) { return { keywords: { keyword: "_ as at cofix else end exists exists2 fix for forall fun if IF in let " + "match mod Prop return Set then Type using where with " + "Abort About Add Admit Admitted All Arguments Assumptions Axiom Back BackTo " + "Backtrack Bind Blacklist Canonical Cd Check Class Classes Close Coercion " + "Coercions CoFixpoint CoInductive Collection Combined Compute Conjecture " + "Conjectures Constant constr Constraint Constructors Context Corollary " + "CreateHintDb Cut Declare Defined Definition Delimit Dependencies Dependent" + "Derive Drop eauto End Equality Eval Example Existential Existentials " + "Existing Export exporting Extern Extract Extraction Fact Field Fields File " + "Fixpoint Focus for From Function Functional Generalizable Global Goal Grab " + "Grammar Graph Guarded Heap Hint HintDb Hints Hypotheses Hypothesis ident " + "Identity If Immediate Implicit Import Include Inductive Infix Info Initial " + "Inline Inspect Instance Instances Intro Intros Inversion Inversion_clear " + "Language Left Lemma Let Libraries Library Load LoadPath Local Locate Ltac ML " + "Mode Module Modules Monomorphic Morphism Next NoInline Notation Obligation " + "Obligations Opaque Open Optimize Options Parameter Parameters Parametric " + "Path Paths pattern Polymorphic Preterm Print Printing Program Projections " + "Proof Proposition Pwd Qed Quit Rec Record Recursive Redirect Relation Remark " + "Remove Require Reserved Reset Resolve Restart Rewrite Right Ring Rings Save " + "Scheme Scope Scopes Script Search SearchAbout SearchHead SearchPattern " + "SearchRewrite Section Separate Set Setoid Show Solve Sorted Step Strategies " + "Strategy Structure SubClass Table Tables Tactic Term Test Theorem Time " + "Timeout Transparent Type Typeclasses Types Undelimit Undo Unfocus Unfocused " + "Unfold Universe Universes Unset Unshelve using Variable Variables Variant " + "Verbose Visibility where with", built_in: "abstract absurd admit after apply as assert assumption at auto autorewrite " + "autounfold before bottom btauto by case case_eq cbn cbv change " + "classical_left classical_right clear clearbody cofix compare compute " + "congruence constr_eq constructor contradict contradiction cut cutrewrite " + "cycle decide decompose dependent destruct destruction dintuition " + "discriminate discrR do double dtauto eapply eassumption eauto ecase " + "econstructor edestruct ediscriminate eelim eexact eexists einduction " + "einjection eleft elim elimtype enough equality erewrite eright " + "esimplify_eq esplit evar exact exactly_once exfalso exists f_equal fail " + "field field_simplify field_simplify_eq first firstorder fix fold fourier " + "functional generalize generalizing gfail give_up has_evar hnf idtac in " + "induction injection instantiate intro intro_pattern intros intuition " + "inversion inversion_clear is_evar is_var lapply lazy left lia lra move " + "native_compute nia nsatz omega once pattern pose progress proof psatz quote " + "record red refine reflexivity remember rename repeat replace revert " + "revgoals rewrite rewrite_strat right ring ring_simplify rtauto set " + "setoid_reflexivity setoid_replace setoid_rewrite setoid_symmetry " + "setoid_transitivity shelve shelve_unifiable simpl simple simplify_eq solve " + "specialize split split_Rabs split_Rmult stepl stepr subst sum swap " + "symmetry tactic tauto time timeout top transitivity trivial try tryif " + "unfold unify until using vm_compute with", }, contains: [ hljs.QUOTE_STRING_MODE, hljs.COMMENT("\\(\\*", "\\*\\)"), hljs.C_NUMBER_MODE, { className: "type", excludeBegin: true, begin: "\\|\\s*", end: "\\w+", }, { begin: /[-=]>/ }, // relevance booster ], }; }, }, { name: "cos", /* Language: Caché Object Script Author: Nikita Savchenko Category: enterprise, scripting */ create: function cos(hljs) { var STRINGS = { className: "string", variants: [ { begin: '"', end: '"', contains: [ { // escaped begin: '""', relevance: 0, }, ], }, ], }; var NUMBERS = { className: "number", begin: "\\b(\\d+(\\.\\d*)?|\\.\\d+)", relevance: 0, }; var COS_KEYWORDS = "property parameter class classmethod clientmethod extends as break " + "catch close continue do d|0 else elseif for goto halt hang h|0 if job " + "j|0 kill k|0 lock l|0 merge new open quit q|0 read r|0 return set s|0 " + "tcommit throw trollback try tstart use view while write w|0 xecute x|0 " + "zkill znspace zn ztrap zwrite zw zzdump zzwrite print zbreak zinsert " + "zload zprint zremove zsave zzprint mv mvcall mvcrt mvdim mvprint zquit " + "zsync ascii"; // registered function - no need in them due to all functions are highlighted, // but I'll just leave this here. //"$bit", "$bitcount", //"$bitfind", "$bitlogic", "$case", "$char", "$classmethod", "$classname", //"$compile", "$data", "$decimal", "$double", "$extract", "$factor", //"$find", "$fnumber", "$get", "$increment", "$inumber", "$isobject", //"$isvaliddouble", "$isvalidnum", "$justify", "$length", "$list", //"$listbuild", "$listdata", "$listfind", "$listfromstring", "$listget", //"$listlength", "$listnext", "$listsame", "$listtostring", "$listvalid", //"$locate", "$match", "$method", "$name", "$nconvert", "$next", //"$normalize", "$now", "$number", "$order", "$parameter", "$piece", //"$prefetchoff", "$prefetchon", "$property", "$qlength", "$qsubscript", //"$query", "$random", "$replace", "$reverse", "$sconvert", "$select", //"$sortbegin", "$sortend", "$stack", "$text", "$translate", "$view", //"$wascii", "$wchar", "$wextract", "$wfind", "$wiswide", "$wlength", //"$wreverse", "$xecute", "$zabs", "$zarccos", "$zarcsin", "$zarctan", //"$zcos", "$zcot", "$zcsc", "$zdate", "$zdateh", "$zdatetime", //"$zdatetimeh", "$zexp", "$zhex", "$zln", "$zlog", "$zpower", "$zsec", //"$zsin", "$zsqr", "$ztan", "$ztime", "$ztimeh", "$zboolean", //"$zconvert", "$zcrc", "$zcyc", "$zdascii", "$zdchar", "$zf", //"$ziswide", "$zlascii", "$zlchar", "$zname", "$zposition", "$zqascii", //"$zqchar", "$zsearch", "$zseek", "$zstrip", "$zwascii", "$zwchar", //"$zwidth", "$zwpack", "$zwbpack", "$zwunpack", "$zwbunpack", "$zzenkaku", //"$change", "$mv", "$mvat", "$mvfmt", "$mvfmts", "$mviconv", //"$mviconvs", "$mvinmat", "$mvlover", "$mvoconv", "$mvoconvs", "$mvraise", //"$mvtrans", "$mvv", "$mvname", "$zbitand", "$zbitcount", "$zbitfind", //"$zbitget", "$zbitlen", "$zbitnot", "$zbitor", "$zbitset", "$zbitstr", //"$zbitxor", "$zincrement", "$znext", "$zorder", "$zprevious", "$zsort", //"device", "$ecode", "$estack", "$etrap", "$halt", "$horolog", //"$io", "$job", "$key", "$namespace", "$principal", "$quit", "$roles", //"$storage", "$system", "$test", "$this", "$tlevel", "$username", //"$x", "$y", "$za", "$zb", "$zchild", "$zeof", "$zeos", "$zerror", //"$zhorolog", "$zio", "$zjob", "$zmode", "$znspace", "$zparent", "$zpi", //"$zpos", "$zreference", "$zstorage", "$ztimestamp", "$ztimezone", //"$ztrap", "$zversion" return { case_insensitive: true, aliases: ["cos", "cls"], keywords: COS_KEYWORDS, contains: [ NUMBERS, STRINGS, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "comment", begin: /;/, end: "$", relevance: 0, }, { // Functions and user-defined functions: write $ztime(60*60*3), $$myFunc(10), $$^Val(1) className: "built_in", begin: /(?:\$\$?|\.\.)\^?[a-zA-Z]+/, }, { // Macro command: quit $$$OK className: "built_in", begin: /\$\$\$[a-zA-Z]+/, }, { // Special (global) variables: write %request.Content; Built-in classes: %Library.Integer className: "built_in", begin: /%[a-z]+(?:\.[a-z]+)*/, }, { // Global variable: set ^globalName = 12 write ^globalName className: "symbol", begin: /\^%?[a-zA-Z][\w]*/, }, { // Some control constructions: do ##class(Package.ClassName).Method(), ##super() className: "keyword", begin: /##class|##super|#define|#dim/, }, // sub-languages: are not fully supported by hljs by 11/15/2015 // left for the future implementation. { begin: /&sql\(/, end: /\)/, excludeBegin: true, excludeEnd: true, subLanguage: "sql", }, { begin: /&(js|jscript|javascript)/, excludeBegin: true, excludeEnd: true, subLanguage: "javascript", }, { // this brakes first and last tag, but this is the only way to embed a valid html begin: /&html<\s*\s*>/, subLanguage: "xml", }, ], }; }, }, { name: "crmsh", /* Language: crmsh Author: Kristoffer Gronlund Website: http://crmsh.github.io Description: Syntax Highlighting for the crmsh DSL Category: config */ create: function (hljs) { var RESOURCES = "primitive rsc_template"; var COMMANDS = "group clone ms master location colocation order fencing_topology " + "rsc_ticket acl_target acl_group user role " + "tag xml"; var PROPERTY_SETS = "property rsc_defaults op_defaults"; var KEYWORDS = "params meta operations op rule attributes utilization"; var OPERATORS = "read write deny defined not_defined in_range date spec in " + "ref reference attribute type xpath version and or lt gt tag " + "lte gte eq ne \\"; var TYPES = "number string"; var LITERALS = "Master Started Slave Stopped start promote demote stop monitor true false"; return { aliases: ["crm", "pcmk"], case_insensitive: true, keywords: { keyword: KEYWORDS + " " + OPERATORS + " " + TYPES, literal: LITERALS, }, contains: [ hljs.HASH_COMMENT_MODE, { beginKeywords: "node", starts: { end: "\\s*([\\w_-]+:)?", starts: { className: "title", end: "\\s*[\\$\\w_][\\w_-]*", }, }, }, { beginKeywords: RESOURCES, starts: { className: "title", end: "\\s*[\\$\\w_][\\w_-]*", starts: { end: "\\s*@?[\\w_][\\w_\\.:-]*", }, }, }, { begin: "\\b(" + COMMANDS.split(" ").join("|") + ")\\s+", keywords: COMMANDS, starts: { className: "title", end: "[\\$\\w_][\\w_-]*", }, }, { beginKeywords: PROPERTY_SETS, starts: { className: "title", end: "\\s*([\\w_-]+:)?", }, }, hljs.QUOTE_STRING_MODE, { className: "meta", begin: "(ocf|systemd|service|lsb):[\\w_:-]+", relevance: 0, }, { className: "number", begin: "\\b\\d+(\\.\\d+)?(ms|s|h|m)?", relevance: 0, }, { className: "literal", begin: "[-]?(infinity|inf)", relevance: 0, }, { className: "attr", begin: /([A-Za-z\$_\#][\w_-]+)=/, relevance: 0, }, { className: "tag", begin: "", relevance: 0, }, ], }; }, }, { name: "crystal", /* Language: Crystal Author: TSUYUSATO Kitsune */ create: function (hljs) { var INT_SUFFIX = "(_*[ui](8|16|32|64|128))?"; var FLOAT_SUFFIX = "(_*f(32|64))?"; var CRYSTAL_IDENT_RE = "[a-zA-Z_]\\w*[!?=]?"; var CRYSTAL_METHOD_RE = "[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~|]|//|//=|&[-+*]=?|&\\*\\*|\\[\\][=?]?"; var CRYSTAL_PATH_RE = "[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"; var CRYSTAL_KEYWORDS = { keyword: "abstract alias annotation as as? asm begin break case class def do else elsif end ensure enum extend for fun if " + "include instance_sizeof is_a? lib macro module next nil? of out pointerof private protected rescue responds_to? " + "return require select self sizeof struct super then type typeof union uninitialized unless until verbatim when while with yield " + "__DIR__ __END_LINE__ __FILE__ __LINE__", literal: "false nil true", }; var SUBST = { className: "subst", begin: "#{", end: "}", keywords: CRYSTAL_KEYWORDS, }; var EXPANSION = { className: "template-variable", variants: [ { begin: "\\{\\{", end: "\\}\\}" }, { begin: "\\{%", end: "%\\}" }, ], keywords: CRYSTAL_KEYWORDS, }; function recursiveParen(begin, end) { var contains = [{ begin: begin, end: end }]; contains[0].contains = contains; return contains; } var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: /'/, end: /'/ }, { begin: /"/, end: /"/ }, { begin: /`/, end: /`/ }, { begin: "%[Qwi]?\\(", end: "\\)", contains: recursiveParen("\\(", "\\)"), }, { begin: "%[Qwi]?\\[", end: "\\]", contains: recursiveParen("\\[", "\\]"), }, { begin: "%[Qwi]?{", end: "}", contains: recursiveParen("{", "}"), }, { begin: "%[Qwi]?<", end: ">", contains: recursiveParen("<", ">"), }, { begin: "%[Qwi]?\\|", end: "\\|" }, { begin: /<<-\w+$/, end: /^\s*\w+$/ }, ], relevance: 0, }; var Q_STRING = { className: "string", variants: [ { begin: "%q\\(", end: "\\)", contains: recursiveParen("\\(", "\\)"), }, { begin: "%q\\[", end: "\\]", contains: recursiveParen("\\[", "\\]"), }, { begin: "%q{", end: "}", contains: recursiveParen("{", "}"), }, { begin: "%q<", end: ">", contains: recursiveParen("<", ">"), }, { begin: "%q\\|", end: "\\|" }, { begin: /<<-'\w+'$/, end: /^\s*\w+$/ }, ], relevance: 0, }; var REGEXP = { begin: "(?!%})(" + hljs.RE_STARTERS_RE + "|\\n|\\b(case|if|select|unless|until|when|while)\\b)\\s*", keywords: "case if select unless until when while", contains: [ { className: "regexp", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: "//[a-z]*", relevance: 0 }, { begin: "/(?!\\/)", end: "/[a-z]*" }, ], }, ], relevance: 0, }; var REGEXP2 = { className: "regexp", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: "%r\\(", end: "\\)", contains: recursiveParen("\\(", "\\)"), }, { begin: "%r\\[", end: "\\]", contains: recursiveParen("\\[", "\\]"), }, { begin: "%r{", end: "}", contains: recursiveParen("{", "}"), }, { begin: "%r<", end: ">", contains: recursiveParen("<", ">"), }, { begin: "%r\\|", end: "\\|" }, ], relevance: 0, }; var ATTRIBUTE = { className: "meta", begin: "@\\[", end: "\\]", contains: [ hljs.inherit(hljs.QUOTE_STRING_MODE, { className: "meta-string", }), ], }; var CRYSTAL_DEFAULT_CONTAINS = [ EXPANSION, STRING, Q_STRING, REGEXP2, REGEXP, ATTRIBUTE, hljs.HASH_COMMENT_MODE, { className: "class", beginKeywords: "class module struct", end: "$|;", illegal: /=/, contains: [ hljs.HASH_COMMENT_MODE, hljs.inherit(hljs.TITLE_MODE, { begin: CRYSTAL_PATH_RE }), { begin: "<" }, // relevance booster for inheritance ], }, { className: "class", beginKeywords: "lib enum union", end: "$|;", illegal: /=/, contains: [ hljs.HASH_COMMENT_MODE, hljs.inherit(hljs.TITLE_MODE, { begin: CRYSTAL_PATH_RE }), ], relevance: 10, }, { beginKeywords: "annotation", end: "$|;", illegal: /=/, contains: [ hljs.HASH_COMMENT_MODE, hljs.inherit(hljs.TITLE_MODE, { begin: CRYSTAL_PATH_RE }), ], relevance: 10, }, { className: "function", beginKeywords: "def", end: /\B\b/, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: CRYSTAL_METHOD_RE, endsParent: true, }), ], }, { className: "function", beginKeywords: "fun macro", end: /\B\b/, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: CRYSTAL_METHOD_RE, endsParent: true, }), ], relevance: 5, }, { className: "symbol", begin: hljs.UNDERSCORE_IDENT_RE + "(\\!|\\?)?:", relevance: 0, }, { className: "symbol", begin: ":", contains: [STRING, { begin: CRYSTAL_METHOD_RE }], relevance: 0, }, { className: "number", variants: [ { begin: "\\b0b([01_]+)" + INT_SUFFIX }, { begin: "\\b0o([0-7_]+)" + INT_SUFFIX }, { begin: "\\b0x([A-Fa-f0-9_]+)" + INT_SUFFIX }, { begin: "\\b([1-9][0-9_]*[0-9]|[0-9])(\\.[0-9][0-9_]*)?([eE]_*[-+]?[0-9_]*)?" + FLOAT_SUFFIX + "(?!_)", }, { begin: "\\b([1-9][0-9_]*|0)" + INT_SUFFIX }, ], relevance: 0, }, ]; SUBST.contains = CRYSTAL_DEFAULT_CONTAINS; EXPANSION.contains = CRYSTAL_DEFAULT_CONTAINS.slice(1); // without EXPANSION return { aliases: ["cr"], lexemes: CRYSTAL_IDENT_RE, keywords: CRYSTAL_KEYWORDS, contains: CRYSTAL_DEFAULT_CONTAINS, }; }, }, { name: "cs", /* Language: C# Author: Jason Diamond Contributor: Nicolas LLOBERA , Pieter Vantorre Category: common */ create: function (hljs) { var KEYWORDS = { keyword: // Normal keywords. "abstract as base bool break byte case catch char checked const continue decimal " + "default delegate do double enum event explicit extern finally fixed float " + "for foreach goto if implicit in int interface internal is lock long nameof " + "object operator out override params private protected public readonly ref sbyte " + "sealed short sizeof stackalloc static string struct switch this try typeof " + "uint ulong unchecked unsafe ushort using virtual void volatile while " + // Contextual keywords. "add alias ascending async await by descending dynamic equals from get global group into join " + "let on orderby partial remove select set value var where yield", literal: "null false true", }; var NUMBERS = { className: "number", variants: [ { begin: "\\b(0b[01']+)" }, { begin: "(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)", }, { begin: "(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)", }, ], relevance: 0, }; var VERBATIM_STRING = { className: "string", begin: '@"', end: '"', contains: [{ begin: '""' }], }; var VERBATIM_STRING_NO_LF = hljs.inherit(VERBATIM_STRING, { illegal: /\n/, }); var SUBST = { className: "subst", begin: "{", end: "}", keywords: KEYWORDS, }; var SUBST_NO_LF = hljs.inherit(SUBST, { illegal: /\n/ }); var INTERPOLATED_STRING = { className: "string", begin: /\$"/, end: '"', illegal: /\n/, contains: [ { begin: "{{" }, { begin: "}}" }, hljs.BACKSLASH_ESCAPE, SUBST_NO_LF, ], }; var INTERPOLATED_VERBATIM_STRING = { className: "string", begin: /\$@"/, end: '"', contains: [ { begin: "{{" }, { begin: "}}" }, { begin: '""' }, SUBST, ], }; var INTERPOLATED_VERBATIM_STRING_NO_LF = hljs.inherit( INTERPOLATED_VERBATIM_STRING, { illegal: /\n/, contains: [ { begin: "{{" }, { begin: "}}" }, { begin: '""' }, SUBST_NO_LF, ], }, ); SUBST.contains = [ INTERPOLATED_VERBATIM_STRING, INTERPOLATED_STRING, VERBATIM_STRING, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, NUMBERS, hljs.C_BLOCK_COMMENT_MODE, ]; SUBST_NO_LF.contains = [ INTERPOLATED_VERBATIM_STRING_NO_LF, INTERPOLATED_STRING, VERBATIM_STRING_NO_LF, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, NUMBERS, hljs.inherit(hljs.C_BLOCK_COMMENT_MODE, { illegal: /\n/ }), ]; var STRING = { variants: [ INTERPOLATED_VERBATIM_STRING, INTERPOLATED_STRING, VERBATIM_STRING, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, ], }; var TYPE_IDENT_RE = hljs.IDENT_RE + "(<" + hljs.IDENT_RE + "(\\s*,\\s*" + hljs.IDENT_RE + ")*>)?(\\[\\])?"; return { aliases: ["csharp", "c#"], keywords: KEYWORDS, illegal: /::/, contains: [ hljs.COMMENT("///", "$", { returnBegin: true, contains: [ { className: "doctag", variants: [ { begin: "///", relevance: 0, }, { begin: "", }, { begin: "", }, ], }, ], }), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "meta", begin: "#", end: "$", keywords: { "meta-keyword": "if else elif endif define undef warning error line region endregion pragma checksum", }, }, STRING, NUMBERS, { beginKeywords: "class interface", end: /[{;=]/, illegal: /[^\s:,]/, contains: [ hljs.TITLE_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, { beginKeywords: "namespace", end: /[{;=]/, illegal: /[^\s:]/, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: "[a-zA-Z](\\.?\\w)*", }), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, { // [Attributes("")] className: "meta", begin: "^\\s*\\[", excludeBegin: true, end: "\\]", excludeEnd: true, contains: [ { className: "meta-string", begin: /"/, end: /"/ }, ], }, { // Expression keywords prevent 'keyword Name(...)' from being // recognized as a function definition beginKeywords: "new return throw await else", relevance: 0, }, { className: "function", begin: "(" + TYPE_IDENT_RE + "\\s+)+" + hljs.IDENT_RE + "\\s*\\(", returnBegin: true, end: /\s*[{;=]/, excludeEnd: true, keywords: KEYWORDS, contains: [ { begin: hljs.IDENT_RE + "\\s*\\(", returnBegin: true, contains: [hljs.TITLE_MODE], relevance: 0, }, { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, keywords: KEYWORDS, relevance: 0, contains: [ STRING, NUMBERS, hljs.C_BLOCK_COMMENT_MODE, ], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, ], }; }, }, { name: "csp", /* Language: CSP Description: Content Security Policy definition highlighting Author: Taras vim: ts=2 sw=2 st=2 */ create: function (hljs) { return { case_insensitive: false, lexemes: "[a-zA-Z][a-zA-Z0-9_-]*", keywords: { keyword: "base-uri child-src connect-src default-src font-src form-action" + " frame-ancestors frame-src img-src media-src object-src plugin-types" + " report-uri sandbox script-src style-src", }, contains: [ { className: "string", begin: "'", end: "'", }, { className: "attribute", begin: "^Content", end: ":", excludeEnd: true, }, ], }; }, }, { name: "css", /* Language: CSS Category: common, css */ create: function (hljs) { var IDENT_RE = "[a-zA-Z-][a-zA-Z0-9_-]*"; var RULE = { begin: /(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/, returnBegin: true, end: ";", endsWithParent: true, contains: [ { className: "attribute", begin: /\S/, end: ":", excludeEnd: true, starts: { endsWithParent: true, excludeEnd: true, contains: [ { begin: /[\w-]+\(/, returnBegin: true, contains: [ { className: "built_in", begin: /[\w-]+/, }, { begin: /\(/, end: /\)/, contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, ], }, ], }, hljs.CSS_NUMBER_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "number", begin: "#[0-9A-Fa-f]+", }, { className: "meta", begin: "!important", }, ], }, }, ], }; return { case_insensitive: true, illegal: /[=\/|'\$]/, contains: [ hljs.C_BLOCK_COMMENT_MODE, { className: "selector-id", begin: /#[A-Za-z0-9_-]+/, }, { className: "selector-class", begin: /\.[A-Za-z0-9_-]+/, }, { className: "selector-attr", begin: /\[/, end: /\]/, illegal: "$", }, { className: "selector-pseudo", begin: /:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/, }, { begin: "@(font-face|page)", lexemes: "[a-z-]+", keywords: "font-face page", }, { begin: "@", end: "[{;]", // at_rule eating first "{" is a good thing // because it doesn’t let it to be parsed as // a rule set but instead drops parser into // the default mode which is how it should be. illegal: /:/, // break on Less variables @var: ... contains: [ { className: "keyword", begin: /\w+/, }, { begin: /\s/, endsWithParent: true, excludeEnd: true, relevance: 0, contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.CSS_NUMBER_MODE, ], }, ], }, { className: "selector-tag", begin: IDENT_RE, relevance: 0, }, { begin: "{", end: "}", illegal: /\S/, contains: [hljs.C_BLOCK_COMMENT_MODE, RULE], }, ], }; }, }, { name: "d", /* Language: D Author: Aleksandar Ruzicic Description: D is a language with C-like syntax and static typing. It pragmatically combines efficiency, control, and modeling power, with safety and programmer productivity. Version: 1.0a Date: 2012-04-08 */ create: /** * Known issues: * * - invalid hex string literals will be recognized as a double quoted strings * but 'x' at the beginning of string will not be matched * * - delimited string literals are not checked for matching end delimiter * (not possible to do with js regexp) * * - content of token string is colored as a string (i.e. no keyword coloring inside a token string) * also, content of token string is not validated to contain only valid D tokens * * - special token sequence rule is not strictly following D grammar (anything following #line * up to the end of line is matched as special token sequence) */ function (hljs) { /** * Language keywords * * @type {Object} */ var D_KEYWORDS = { keyword: "abstract alias align asm assert auto body break byte case cast catch class " + "const continue debug default delete deprecated do else enum export extern final " + "finally for foreach foreach_reverse|10 goto if immutable import in inout int " + "interface invariant is lazy macro mixin module new nothrow out override package " + "pragma private protected public pure ref return scope shared static struct " + "super switch synchronized template this throw try typedef typeid typeof union " + "unittest version void volatile while with __FILE__ __LINE__ __gshared|10 " + "__thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__", built_in: "bool cdouble cent cfloat char creal dchar delegate double dstring float function " + "idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar " + "wstring", literal: "false null true", }; /** * Number literal regexps * * @type {String} */ var decimal_integer_re = "(0|[1-9][\\d_]*)", decimal_integer_nosus_re = "(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)", binary_integer_re = "0[bB][01_]+", hexadecimal_digits_re = "([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)", hexadecimal_integer_re = "0[xX]" + hexadecimal_digits_re, decimal_exponent_re = "([eE][+-]?" + decimal_integer_nosus_re + ")", decimal_float_re = "(" + decimal_integer_nosus_re + "(\\.\\d*|" + decimal_exponent_re + ")|" + "\\d+\\." + decimal_integer_nosus_re + decimal_integer_nosus_re + "|" + "\\." + decimal_integer_re + decimal_exponent_re + "?" + ")", hexadecimal_float_re = "(0[xX](" + hexadecimal_digits_re + "\\." + hexadecimal_digits_re + "|" + "\\.?" + hexadecimal_digits_re + ")[pP][+-]?" + decimal_integer_nosus_re + ")", integer_re = "(" + decimal_integer_re + "|" + binary_integer_re + "|" + hexadecimal_integer_re + ")", float_re = "(" + hexadecimal_float_re + "|" + decimal_float_re + ")"; /** * Escape sequence supported in D string and character literals * * @type {String} */ var escape_sequence_re = "\\\\(" + "['\"\\?\\\\abfnrtv]|" + // common escapes "u[\\dA-Fa-f]{4}|" + // four hex digit unicode codepoint "[0-7]{1,3}|" + // one to three octal digit ascii char code "x[\\dA-Fa-f]{2}|" + // two hex digit ascii char code "U[\\dA-Fa-f]{8}" + // eight hex digit unicode codepoint ")|" + "&[a-zA-Z\\d]{2,};"; // named character entity /** * D integer number literals * * @type {Object} */ var D_INTEGER_MODE = { className: "number", begin: "\\b" + integer_re + "(L|u|U|Lu|LU|uL|UL)?", relevance: 0, }; /** * [D_FLOAT_MODE description] * @type {Object} */ var D_FLOAT_MODE = { className: "number", begin: "\\b(" + float_re + "([fF]|L|i|[fF]i|Li)?|" + integer_re + "(i|[fF]i|Li)" + ")", relevance: 0, }; /** * D character literal * * @type {Object} */ var D_CHARACTER_MODE = { className: "string", begin: "'(" + escape_sequence_re + "|.)", end: "'", illegal: ".", }; /** * D string escape sequence * * @type {Object} */ var D_ESCAPE_SEQUENCE = { begin: escape_sequence_re, relevance: 0, }; /** * D double quoted string literal * * @type {Object} */ var D_STRING_MODE = { className: "string", begin: '"', contains: [D_ESCAPE_SEQUENCE], end: '"[cwd]?', }; /** * D wysiwyg and delimited string literals * * @type {Object} */ var D_WYSIWYG_DELIMITED_STRING_MODE = { className: "string", begin: '[rq]"', end: '"[cwd]?', relevance: 5, }; /** * D alternate wysiwyg string literal * * @type {Object} */ var D_ALTERNATE_WYSIWYG_STRING_MODE = { className: "string", begin: "`", end: "`[cwd]?", }; /** * D hexadecimal string literal * * @type {Object} */ var D_HEX_STRING_MODE = { className: "string", begin: 'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?', relevance: 10, }; /** * D delimited string literal * * @type {Object} */ var D_TOKEN_STRING_MODE = { className: "string", begin: 'q"\\{', end: '\\}"', }; /** * Hashbang support * * @type {Object} */ var D_HASHBANG_MODE = { className: "meta", begin: "^#!", end: "$", relevance: 5, }; /** * D special token sequence * * @type {Object} */ var D_SPECIAL_TOKEN_SEQUENCE_MODE = { className: "meta", begin: "#(line)", end: "$", relevance: 5, }; /** * D attributes * * @type {Object} */ var D_ATTRIBUTE_MODE = { className: "keyword", begin: "@[a-zA-Z_][a-zA-Z_\\d]*", }; /** * D nesting comment * * @type {Object} */ var D_NESTING_COMMENT_MODE = hljs.COMMENT( "\\/\\+", "\\+\\/", { contains: ["self"], relevance: 10, }, ); return { lexemes: hljs.UNDERSCORE_IDENT_RE, keywords: D_KEYWORDS, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, D_NESTING_COMMENT_MODE, D_HEX_STRING_MODE, D_STRING_MODE, D_WYSIWYG_DELIMITED_STRING_MODE, D_ALTERNATE_WYSIWYG_STRING_MODE, D_TOKEN_STRING_MODE, D_FLOAT_MODE, D_INTEGER_MODE, D_CHARACTER_MODE, D_HASHBANG_MODE, D_SPECIAL_TOKEN_SEQUENCE_MODE, D_ATTRIBUTE_MODE, ], }; }, }, { name: "dart", /* Language: Dart Requires: markdown.js Author: Maxim Dikun Description: Dart a modern, object-oriented language developed by Google. For more information see https://www.dartlang.org/ Category: scripting */ create: function (hljs) { var SUBST = { className: "subst", variants: [{ begin: "\\$[A-Za-z0-9_]+" }], }; var BRACED_SUBST = { className: "subst", variants: [{ begin: "\\${", end: "}" }], keywords: "true false null this is new super", }; var STRING = { className: "string", variants: [ { begin: "r'''", end: "'''", }, { begin: 'r"""', end: '"""', }, { begin: "r'", end: "'", illegal: "\\n", }, { begin: 'r"', end: '"', illegal: "\\n", }, { begin: "'''", end: "'''", contains: [hljs.BACKSLASH_ESCAPE, SUBST, BRACED_SUBST], }, { begin: '"""', end: '"""', contains: [hljs.BACKSLASH_ESCAPE, SUBST, BRACED_SUBST], }, { begin: "'", end: "'", illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE, SUBST, BRACED_SUBST], }, { begin: '"', end: '"', illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE, SUBST, BRACED_SUBST], }, ], }; BRACED_SUBST.contains = [hljs.C_NUMBER_MODE, STRING]; var KEYWORDS = { keyword: "assert async await break case catch class const continue default do else enum extends false final " + "finally for if in is new null rethrow return super switch sync this throw true try var void while with yield " + "abstract as dynamic export external factory get implements import library operator part set static typedef", built_in: // dart:core "print Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set " + "Stopwatch String StringBuffer StringSink Symbol Type Uri bool double int num " + // dart:html "document window querySelector querySelectorAll Element ElementList", }; return { keywords: KEYWORDS, contains: [ STRING, hljs.COMMENT("/\\*\\*", "\\*/", { subLanguage: "markdown", }), hljs.COMMENT("///", "$", { subLanguage: "markdown", }), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "class", beginKeywords: "class interface", end: "{", excludeEnd: true, contains: [ { beginKeywords: "extends implements", }, hljs.UNDERSCORE_TITLE_MODE, ], }, hljs.C_NUMBER_MODE, { className: "meta", begin: "@[A-Za-z]+", }, { begin: "=>", // No markup, just a relevance booster }, ], }; }, }, { name: "delphi", /* Language: Delphi */ create: function (hljs) { var KEYWORDS = "exports register file shl array record property for mod while set ally label uses raise not " + "stored class safecall var interface or private static exit index inherited to else stdcall " + "override shr asm far resourcestring finalization packed virtual out and protected library do " + "xorwrite goto near function end div overload object unit begin string on inline repeat until " + "destructor write message program with read initialization except default nil if case cdecl in " + "downto threadvar of try pascal const external constructor type public then implementation " + "finally published procedure absolute reintroduce operator as is abstract alias assembler " + "bitpacked break continue cppdecl cvar enumerator experimental platform deprecated " + "unimplemented dynamic export far16 forward generic helper implements interrupt iochecks " + "local name nodefault noreturn nostackframe oldfpccall otherwise saveregisters softfloat " + "specialize strict unaligned varargs "; var COMMENT_MODES = [ hljs.C_LINE_COMMENT_MODE, hljs.COMMENT(/\{/, /\}/, { relevance: 0 }), hljs.COMMENT(/\(\*/, /\*\)/, { relevance: 10 }), ]; var DIRECTIVE = { className: "meta", variants: [ { begin: /\{\$/, end: /\}/ }, { begin: /\(\*\$/, end: /\*\)/ }, ], }; var STRING = { className: "string", begin: /'/, end: /'/, contains: [{ begin: /''/ }], }; var CHAR_STRING = { className: "string", begin: /(#\d+)+/, }; var CLASS = { begin: hljs.IDENT_RE + "\\s*=\\s*class\\s*\\(", returnBegin: true, contains: [hljs.TITLE_MODE], }; var FUNCTION = { className: "function", beginKeywords: "function constructor destructor procedure", end: /[:;]/, keywords: "function constructor|10 destructor|10 procedure|10", contains: [ hljs.TITLE_MODE, { className: "params", begin: /\(/, end: /\)/, keywords: KEYWORDS, contains: [STRING, CHAR_STRING, DIRECTIVE].concat( COMMENT_MODES, ), }, DIRECTIVE, ].concat(COMMENT_MODES), }; return { aliases: [ "dpr", "dfm", "pas", "pascal", "freepascal", "lazarus", "lpr", "lfm", ], case_insensitive: true, keywords: KEYWORDS, illegal: /"|\$[G-Zg-z]|\/\*|<\/|\|/, contains: [ STRING, CHAR_STRING, hljs.NUMBER_MODE, CLASS, FUNCTION, DIRECTIVE, ].concat(COMMENT_MODES), }; }, }, { name: "diff", /* Language: Diff Description: Unified and context diff Author: Vasily Polovnyov Category: common */ create: function (hljs) { return { aliases: ["patch"], contains: [ { className: "meta", relevance: 10, variants: [ { begin: /^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/ }, { begin: /^\*\*\* +\d+,\d+ +\*\*\*\*$/ }, { begin: /^\-\-\- +\d+,\d+ +\-\-\-\-$/ }, ], }, { className: "comment", variants: [ { begin: /Index: /, end: /$/ }, { begin: /={3,}/, end: /$/ }, { begin: /^\-{3}/, end: /$/ }, { begin: /^\*{3} /, end: /$/ }, { begin: /^\+{3}/, end: /$/ }, { begin: /\*{5}/, end: /\*{5}$/ }, ], }, { className: "addition", begin: "^\\+", end: "$", }, { className: "deletion", begin: "^\\-", end: "$", }, { className: "addition", begin: "^\\!", end: "$", }, ], }; }, }, { name: "django", /* Language: Django Requires: xml.js Author: Ivan Sagalaev Contributors: Ilya Baryshev Category: template */ create: function (hljs) { var FILTER = { begin: /\|[A-Za-z]+:?/, keywords: { name: "truncatewords removetags linebreaksbr yesno get_digit timesince random striptags " + "filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands " + "title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode " + "timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort " + "dictsortreversed default_if_none pluralize lower join center default " + "truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first " + "escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize " + "localtime utc timezone", }, contains: [hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE], }; return { aliases: ["jinja"], case_insensitive: true, subLanguage: "xml", contains: [ hljs.COMMENT(/\{%\s*comment\s*%}/, /\{%\s*endcomment\s*%}/), hljs.COMMENT(/\{#/, /#}/), { className: "template-tag", begin: /\{%/, end: /%}/, contains: [ { className: "name", begin: /\w+/, keywords: { name: "comment endcomment load templatetag ifchanged endifchanged if endif firstof for " + "endfor ifnotequal endifnotequal widthratio extends include spaceless " + "endspaceless regroup ifequal endifequal ssi now with cycle url filter " + "endfilter debug block endblock else autoescape endautoescape csrf_token empty elif " + "endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix " + "plural get_current_language language get_available_languages " + "get_current_language_bidi get_language_info get_language_info_list localize " + "endlocalize localtime endlocaltime timezone endtimezone get_current_timezone " + "verbatim", }, starts: { endsWithParent: true, keywords: "in by as", contains: [FILTER], relevance: 0, }, }, ], }, { className: "template-variable", begin: /\{\{/, end: /}}/, contains: [FILTER], }, ], }; }, }, { name: "dns", /* Language: DNS Zone file Author: Tim Schumacher Category: config */ create: function (hljs) { return { aliases: ["bind", "zone"], keywords: { keyword: "IN A AAAA AFSDB APL CAA CDNSKEY CDS CERT CNAME DHCID DLV DNAME DNSKEY DS HIP IPSECKEY KEY KX " + "LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RRSIG RP SIG SOA SRV SSHFP TA TKEY TLSA TSIG TXT", }, contains: [ hljs.COMMENT(";", "$", { relevance: 0 }), { className: "meta", begin: /^\$(TTL|GENERATE|INCLUDE|ORIGIN)\b/, }, // IPv6 { className: "number", begin: "((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))\\b", }, // IPv4 { className: "number", begin: "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\b", }, hljs.inherit(hljs.NUMBER_MODE, { begin: /\b\d+[dhwm]?/ }), ], }; }, }, { name: "dockerfile", /* Language: Dockerfile Requires: bash.js Author: Alexis Hénaut Description: language definition for Dockerfile files Category: config */ create: function (hljs) { return { aliases: ["docker"], case_insensitive: true, keywords: "from maintainer expose env arg user onbuild stopsignal", contains: [ hljs.HASH_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, { beginKeywords: "run cmd entrypoint volume add copy workdir label healthcheck shell", starts: { end: /[^\\]$/, subLanguage: "bash", }, }, ], illegal: " Contributors: Anton Kochkov */ create: function (hljs) { var COMMENT = hljs.COMMENT(/^\s*@?rem\b/, /$/, { relevance: 10, }); var LABEL = { className: "symbol", begin: "^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)", relevance: 0, }; return { aliases: ["bat", "cmd"], case_insensitive: true, illegal: /\/\*/, keywords: { keyword: "if else goto for in do call exit not exist errorlevel defined " + "equ neq lss leq gtr geq", built_in: "prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux " + "shift cd dir echo setlocal endlocal set pause copy " + "append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color " + "comp compact convert date dir diskcomp diskcopy doskey erase fs " + "find findstr format ftype graftabl help keyb label md mkdir mode more move path " + "pause print popd pushd promt rd recover rem rename replace restore rmdir shift" + "sort start subst time title tree type ver verify vol " + // winutils "ping net ipconfig taskkill xcopy ren del", }, contains: [ { className: "variable", begin: /%%[^ ]|%[^ ]+?%|![^ ]+?!/, }, { className: "function", begin: LABEL.begin, end: "goto:eof", contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: "([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*", }), COMMENT, ], }, { className: "number", begin: "\\b\\d+", relevance: 0, }, COMMENT, ], }; }, }, { name: "dsconfig", /* Language: dsconfig Description: dsconfig batch configuration language for LDAP directory servers Contributors: Jacob Childress Category: enterprise, config */ create: function (hljs) { var QUOTED_PROPERTY = { className: "string", begin: /"/, end: /"/, }; var APOS_PROPERTY = { className: "string", begin: /'/, end: /'/, }; var UNQUOTED_PROPERTY = { className: "string", begin: "[\\w-?]+:\\w+", end: "\\W", relevance: 0, }; var VALUELESS_PROPERTY = { className: "string", begin: "\\w+-?\\w+", end: "\\W", relevance: 0, }; return { keywords: "dsconfig", contains: [ { className: "keyword", begin: "^dsconfig", end: "\\s", excludeEnd: true, relevance: 10, }, { className: "built_in", begin: "(list|create|get|set|delete)-(\\w+)", end: "\\s", excludeEnd: true, illegal: "!@#$%^&*()", relevance: 10, }, { className: "built_in", begin: "--(\\w+)", end: "\\s", excludeEnd: true, }, QUOTED_PROPERTY, APOS_PROPERTY, UNQUOTED_PROPERTY, VALUELESS_PROPERTY, hljs.HASH_COMMENT_MODE, ], }; }, }, { name: "dts", /* Language: Device Tree Description: *.dts files used in the Linux kernel Author: Martin Braun , Moritz Fischer Category: config */ create: function (hljs) { var STRINGS = { className: "string", variants: [ hljs.inherit(hljs.QUOTE_STRING_MODE, { begin: '((u8?|U)|L)?"', }), { begin: '(u8?|U)?R"', end: '"', contains: [hljs.BACKSLASH_ESCAPE], }, { begin: "'\\\\?.", end: "'", illegal: ".", }, ], }; var NUMBERS = { className: "number", variants: [ { begin: "\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)" }, { begin: hljs.C_NUMBER_RE }, ], relevance: 0, }; var PREPROCESSOR = { className: "meta", begin: "#", end: "$", keywords: { "meta-keyword": "if else elif endif define undef ifdef ifndef", }, contains: [ { begin: /\\\n/, relevance: 0, }, { beginKeywords: "include", end: "$", keywords: { "meta-keyword": "include" }, contains: [ hljs.inherit(STRINGS, { className: "meta-string" }), { className: "meta-string", begin: "<", end: ">", illegal: "\\n", }, ], }, STRINGS, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }; var DTS_REFERENCE = { className: "variable", begin: "\\&[a-z\\d_]*\\b", }; var DTS_KEYWORD = { className: "meta-keyword", begin: "/[a-z][a-z\\d-]*/", }; var DTS_LABEL = { className: "symbol", begin: "^\\s*[a-zA-Z_][a-zA-Z\\d_]*:", }; var DTS_CELL_PROPERTY = { className: "params", begin: "<", end: ">", contains: [NUMBERS, DTS_REFERENCE], }; var DTS_NODE = { className: "class", begin: /[a-zA-Z_][a-zA-Z\d_@]*\s{/, end: /[{;=]/, returnBegin: true, excludeEnd: true, }; var DTS_ROOT_NODE = { className: "class", begin: "/\\s*{", end: "};", relevance: 10, contains: [ DTS_REFERENCE, DTS_KEYWORD, DTS_LABEL, DTS_NODE, DTS_CELL_PROPERTY, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, NUMBERS, STRINGS, ], }; return { keywords: "", contains: [ DTS_ROOT_NODE, DTS_REFERENCE, DTS_KEYWORD, DTS_LABEL, DTS_NODE, DTS_CELL_PROPERTY, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, NUMBERS, STRINGS, PREPROCESSOR, { begin: hljs.IDENT_RE + "::", keywords: "", }, ], }; }, }, { name: "dust", /* Language: Dust Requires: xml.js Author: Michael Allen Description: Matcher for dust.js templates. Category: template */ create: function (hljs) { var EXPRESSION_KEYWORDS = "if eq ne lt lte gt gte select default math sep"; return { aliases: ["dst"], case_insensitive: true, subLanguage: "xml", contains: [ { className: "template-tag", begin: /\{[#\/]/, end: /\}/, illegal: /;/, contains: [ { className: "name", begin: /[a-zA-Z\.-]+/, starts: { endsWithParent: true, relevance: 0, contains: [hljs.QUOTE_STRING_MODE], }, }, ], }, { className: "template-variable", begin: /\{/, end: /\}/, illegal: /;/, keywords: EXPRESSION_KEYWORDS, }, ], }; }, }, { name: "ebnf", /* Language: Extended Backus-Naur Form Author: Alex McKibben */ create: function (hljs) { var commentMode = hljs.COMMENT(/\(\*/, /\*\)/); var nonTerminalMode = { className: "attribute", begin: /^[ ]*[a-zA-Z][a-zA-Z-]*([\s-]+[a-zA-Z][a-zA-Z]*)*/, }; var specialSequenceMode = { className: "meta", begin: /\?.*\?/, }; var ruleBodyMode = { begin: /=/, end: /;/, contains: [ commentMode, specialSequenceMode, // terminals hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, ], }; return { illegal: /\S/, contains: [commentMode, nonTerminalMode, ruleBodyMode], }; }, }, { name: "elixir", /* Language: Elixir Author: Josh Adams Description: language definition for Elixir source code files (.ex and .exs). Based on ruby language support. Category: functional */ create: function (hljs) { var ELIXIR_IDENT_RE = "[a-zA-Z_][a-zA-Z0-9_.]*(\\!|\\?)?"; var ELIXIR_METHOD_RE = "[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"; var ELIXIR_KEYWORDS = "and false then defined module in return redo retry end for true self when " + "next until do begin unless nil break not case cond alias while ensure or " + "include use alias fn quote require import with|0"; var SUBST = { className: "subst", begin: "#\\{", end: "}", lexemes: ELIXIR_IDENT_RE, keywords: ELIXIR_KEYWORDS, }; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: /'/, end: /'/, }, { begin: /"/, end: /"/, }, ], }; var FUNCTION = { className: "function", beginKeywords: "def defp defmacro", end: /\B\b/, // the mode is ended by the title contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: ELIXIR_IDENT_RE, endsParent: true, }), ], }; var CLASS = hljs.inherit(FUNCTION, { className: "class", beginKeywords: "defimpl defmodule defprotocol defrecord", end: /\bdo\b|$|;/, }); var ELIXIR_DEFAULT_CONTAINS = [ STRING, hljs.HASH_COMMENT_MODE, CLASS, FUNCTION, { begin: "::", }, { className: "symbol", begin: ":(?![\\s:])", contains: [STRING, { begin: ELIXIR_METHOD_RE }], relevance: 0, }, { className: "symbol", begin: ELIXIR_IDENT_RE + ":(?!:)", relevance: 0, }, { className: "number", begin: "(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", relevance: 0, }, { className: "variable", begin: "(\\$\\W)|((\\$|\\@\\@?)(\\w+))", }, { begin: "->", }, { // regexp container begin: "(" + hljs.RE_STARTERS_RE + ")\\s*", contains: [ hljs.HASH_COMMENT_MODE, { className: "regexp", illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: "/", end: "/[a-z]*", }, { begin: "%r\\[", end: "\\][a-z]*", }, ], }, ], relevance: 0, }, ]; SUBST.contains = ELIXIR_DEFAULT_CONTAINS; return { lexemes: ELIXIR_IDENT_RE, keywords: ELIXIR_KEYWORDS, contains: ELIXIR_DEFAULT_CONTAINS, }; }, }, { name: "elm", /* Language: Elm Author: Janis Voigtlaender Category: functional */ create: function (hljs) { var COMMENT = { variants: [ hljs.COMMENT("--", "$"), hljs.COMMENT("{-", "-}", { contains: ["self"], }), ], }; var CONSTRUCTOR = { className: "type", begin: "\\b[A-Z][\\w']*", // TODO: other constructors (built-in, infix). relevance: 0, }; var LIST = { begin: "\\(", end: "\\)", illegal: '"', contains: [ { className: "type", begin: "\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?", }, COMMENT, ], }; var RECORD = { begin: "{", end: "}", contains: LIST.contains, }; var CHARACTER = { className: "string", begin: "'\\\\?.", end: "'", illegal: ".", }; return { keywords: "let in if then else case of where module import exposing " + "type alias as infix infixl infixr port effect command subscription", contains: [ // Top-level constructions. { beginKeywords: "port effect module", end: "exposing", keywords: "port effect module where command subscription exposing", contains: [LIST, COMMENT], illegal: "\\W\\.|;", }, { begin: "import", end: "$", keywords: "import as exposing", contains: [LIST, COMMENT], illegal: "\\W\\.|;", }, { begin: "type", end: "$", keywords: "type alias", contains: [CONSTRUCTOR, LIST, RECORD, COMMENT], }, { beginKeywords: "infix infixl infixr", end: "$", contains: [hljs.C_NUMBER_MODE, COMMENT], }, { begin: "port", end: "$", keywords: "port", contains: [COMMENT], }, // Literals and names. CHARACTER, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, CONSTRUCTOR, hljs.inherit(hljs.TITLE_MODE, { begin: "^[_a-z][\\w']*" }), COMMENT, { begin: "->|<-" }, // No markup, relevance booster ], illegal: /;/, }; }, }, { name: "erb", /* Language: ERB (Embedded Ruby) Requires: xml.js, ruby.js Author: Lucas Mazza Contributors: Kassio Borges Description: "Bridge" language defining fragments of Ruby in HTML within <% .. %> Category: template */ create: function (hljs) { return { subLanguage: "xml", contains: [ hljs.COMMENT("<%#", "%>"), { begin: "<%[%=-]?", end: "[%-]?%>", subLanguage: "ruby", excludeBegin: true, excludeEnd: true, }, ], }; }, }, { name: "erlang-repl", /* Language: Erlang REPL Author: Sergey Ignatov Category: functional */ create: function (hljs) { return { keywords: { built_in: "spawn spawn_link self", keyword: "after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if " + "let not of or orelse|10 query receive rem try when xor", }, contains: [ { className: "meta", begin: "^[0-9]+> ", relevance: 10, }, hljs.COMMENT("%", "$"), { className: "number", begin: "\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)", relevance: 0, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { begin: "\\?(::)?([A-Z]\\w*(::)?)+", }, { begin: "->", }, { begin: "ok", }, { begin: "!", }, { begin: "(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)", relevance: 0, }, { begin: "[A-Z][a-zA-Z0-9_']*", relevance: 0, }, ], }; }, }, { name: "erlang", /* Language: Erlang Description: Erlang is a general-purpose functional language, with strict evaluation, single assignment, and dynamic typing. Author: Nikolay Zakharov , Dmitry Kovega Category: functional */ create: function (hljs) { var BASIC_ATOM_RE = "[a-z'][a-zA-Z0-9_']*"; var FUNCTION_NAME_RE = "(" + BASIC_ATOM_RE + ":" + BASIC_ATOM_RE + "|" + BASIC_ATOM_RE + ")"; var ERLANG_RESERVED = { keyword: "after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun if " + "let not of orelse|10 query receive rem try when xor", literal: "false true", }; var COMMENT = hljs.COMMENT("%", "$"); var NUMBER = { className: "number", begin: "\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)", relevance: 0, }; var NAMED_FUN = { begin: "fun\\s+" + BASIC_ATOM_RE + "/\\d+", }; var FUNCTION_CALL = { begin: FUNCTION_NAME_RE + "\\(", end: "\\)", returnBegin: true, relevance: 0, contains: [ { begin: FUNCTION_NAME_RE, relevance: 0, }, { begin: "\\(", end: "\\)", endsWithParent: true, returnEnd: true, relevance: 0, // "contains" defined later }, ], }; var TUPLE = { begin: "{", end: "}", relevance: 0, // "contains" defined later }; var VAR1 = { begin: "\\b_([A-Z][A-Za-z0-9_]*)?", relevance: 0, }; var VAR2 = { begin: "[A-Z][a-zA-Z0-9_]*", relevance: 0, }; var RECORD_ACCESS = { begin: "#" + hljs.UNDERSCORE_IDENT_RE, relevance: 0, returnBegin: true, contains: [ { begin: "#" + hljs.UNDERSCORE_IDENT_RE, relevance: 0, }, { begin: "{", end: "}", relevance: 0, // "contains" defined later }, ], }; var BLOCK_STATEMENTS = { beginKeywords: "fun receive if try case", end: "end", keywords: ERLANG_RESERVED, }; BLOCK_STATEMENTS.contains = [ COMMENT, NAMED_FUN, hljs.inherit(hljs.APOS_STRING_MODE, { className: "" }), BLOCK_STATEMENTS, FUNCTION_CALL, hljs.QUOTE_STRING_MODE, NUMBER, TUPLE, VAR1, VAR2, RECORD_ACCESS, ]; var BASIC_MODES = [ COMMENT, NAMED_FUN, BLOCK_STATEMENTS, FUNCTION_CALL, hljs.QUOTE_STRING_MODE, NUMBER, TUPLE, VAR1, VAR2, RECORD_ACCESS, ]; FUNCTION_CALL.contains[1].contains = BASIC_MODES; TUPLE.contains = BASIC_MODES; RECORD_ACCESS.contains[1].contains = BASIC_MODES; var PARAMS = { className: "params", begin: "\\(", end: "\\)", contains: BASIC_MODES, }; return { aliases: ["erl"], keywords: ERLANG_RESERVED, illegal: "(", returnBegin: true, illegal: "\\(|#|//|/\\*|\\\\|:|;", contains: [ PARAMS, hljs.inherit(hljs.TITLE_MODE, { begin: BASIC_ATOM_RE }), ], starts: { end: ";|\\.", keywords: ERLANG_RESERVED, contains: BASIC_MODES, }, }, COMMENT, { begin: "^-", end: "\\.", relevance: 0, excludeEnd: true, returnBegin: true, lexemes: "-" + hljs.IDENT_RE, keywords: "-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn " + "-import -include -include_lib -compile -define -else -endif -file -behaviour " + "-behavior -spec", contains: [PARAMS], }, NUMBER, hljs.QUOTE_STRING_MODE, RECORD_ACCESS, VAR1, VAR2, TUPLE, { begin: /\.$/ }, // relevance booster ], }; }, }, { name: "excel", /* Language: Excel Author: Victor Zhou Description: Excel formulae */ create: function (hljs) { return { aliases: ["xlsx", "xls"], case_insensitive: true, lexemes: /[a-zA-Z][\w\.]*/, // built-in functions imported from https://web.archive.org/web/20160513042710/https://support.office.com/en-us/article/Excel-functions-alphabetical-b3944572-255d-4efb-bb96-c6d90033e188 keywords: { built_in: "ABS ACCRINT ACCRINTM ACOS ACOSH ACOT ACOTH AGGREGATE ADDRESS AMORDEGRC AMORLINC AND ARABIC AREAS ASC ASIN ASINH ATAN ATAN2 ATANH AVEDEV AVERAGE AVERAGEA AVERAGEIF AVERAGEIFS BAHTTEXT BASE BESSELI BESSELJ BESSELK BESSELY BETADIST BETA.DIST BETAINV BETA.INV BIN2DEC BIN2HEX BIN2OCT BINOMDIST BINOM.DIST BINOM.DIST.RANGE BINOM.INV BITAND BITLSHIFT BITOR BITRSHIFT BITXOR CALL CEILING CEILING.MATH CEILING.PRECISE CELL CHAR CHIDIST CHIINV CHITEST CHISQ.DIST CHISQ.DIST.RT CHISQ.INV CHISQ.INV.RT CHISQ.TEST CHOOSE CLEAN CODE COLUMN COLUMNS COMBIN COMBINA COMPLEX CONCAT CONCATENATE CONFIDENCE CONFIDENCE.NORM CONFIDENCE.T CONVERT CORREL COS COSH COT COTH COUNT COUNTA COUNTBLANK COUNTIF COUNTIFS COUPDAYBS COUPDAYS COUPDAYSNC COUPNCD COUPNUM COUPPCD COVAR COVARIANCE.P COVARIANCE.S CRITBINOM CSC CSCH CUBEKPIMEMBER CUBEMEMBER CUBEMEMBERPROPERTY CUBERANKEDMEMBER CUBESET CUBESETCOUNT CUBEVALUE CUMIPMT CUMPRINC DATE DATEDIF DATEVALUE DAVERAGE DAY DAYS DAYS360 DB DBCS DCOUNT DCOUNTA DDB DEC2BIN DEC2HEX DEC2OCT DECIMAL DEGREES DELTA DEVSQ DGET DISC DMAX DMIN DOLLAR DOLLARDE DOLLARFR DPRODUCT DSTDEV DSTDEVP DSUM DURATION DVAR DVARP EDATE EFFECT ENCODEURL EOMONTH ERF ERF.PRECISE ERFC ERFC.PRECISE ERROR.TYPE EUROCONVERT EVEN EXACT EXP EXPON.DIST EXPONDIST FACT FACTDOUBLE FALSE|0 F.DIST FDIST F.DIST.RT FILTERXML FIND FINDB F.INV F.INV.RT FINV FISHER FISHERINV FIXED FLOOR FLOOR.MATH FLOOR.PRECISE FORECAST FORECAST.ETS FORECAST.ETS.CONFINT FORECAST.ETS.SEASONALITY FORECAST.ETS.STAT FORECAST.LINEAR FORMULATEXT FREQUENCY F.TEST FTEST FV FVSCHEDULE GAMMA GAMMA.DIST GAMMADIST GAMMA.INV GAMMAINV GAMMALN GAMMALN.PRECISE GAUSS GCD GEOMEAN GESTEP GETPIVOTDATA GROWTH HARMEAN HEX2BIN HEX2DEC HEX2OCT HLOOKUP HOUR HYPERLINK HYPGEOM.DIST HYPGEOMDIST IF|0 IFERROR IFNA IFS IMABS IMAGINARY IMARGUMENT IMCONJUGATE IMCOS IMCOSH IMCOT IMCSC IMCSCH IMDIV IMEXP IMLN IMLOG10 IMLOG2 IMPOWER IMPRODUCT IMREAL IMSEC IMSECH IMSIN IMSINH IMSQRT IMSUB IMSUM IMTAN INDEX INDIRECT INFO INT INTERCEPT INTRATE IPMT IRR ISBLANK ISERR ISERROR ISEVEN ISFORMULA ISLOGICAL ISNA ISNONTEXT ISNUMBER ISODD ISREF ISTEXT ISO.CEILING ISOWEEKNUM ISPMT JIS KURT LARGE LCM LEFT LEFTB LEN LENB LINEST LN LOG LOG10 LOGEST LOGINV LOGNORM.DIST LOGNORMDIST LOGNORM.INV LOOKUP LOWER MATCH MAX MAXA MAXIFS MDETERM MDURATION MEDIAN MID MIDBs MIN MINIFS MINA MINUTE MINVERSE MIRR MMULT MOD MODE MODE.MULT MODE.SNGL MONTH MROUND MULTINOMIAL MUNIT N NA NEGBINOM.DIST NEGBINOMDIST NETWORKDAYS NETWORKDAYS.INTL NOMINAL NORM.DIST NORMDIST NORMINV NORM.INV NORM.S.DIST NORMSDIST NORM.S.INV NORMSINV NOT NOW NPER NPV NUMBERVALUE OCT2BIN OCT2DEC OCT2HEX ODD ODDFPRICE ODDFYIELD ODDLPRICE ODDLYIELD OFFSET OR PDURATION PEARSON PERCENTILE.EXC PERCENTILE.INC PERCENTILE PERCENTRANK.EXC PERCENTRANK.INC PERCENTRANK PERMUT PERMUTATIONA PHI PHONETIC PI PMT POISSON.DIST POISSON POWER PPMT PRICE PRICEDISC PRICEMAT PROB PRODUCT PROPER PV QUARTILE QUARTILE.EXC QUARTILE.INC QUOTIENT RADIANS RAND RANDBETWEEN RANK.AVG RANK.EQ RANK RATE RECEIVED REGISTER.ID REPLACE REPLACEB REPT RIGHT RIGHTB ROMAN ROUND ROUNDDOWN ROUNDUP ROW ROWS RRI RSQ RTD SEARCH SEARCHB SEC SECH SECOND SERIESSUM SHEET SHEETS SIGN SIN SINH SKEW SKEW.P SLN SLOPE SMALL SQL.REQUEST SQRT SQRTPI STANDARDIZE STDEV STDEV.P STDEV.S STDEVA STDEVP STDEVPA STEYX SUBSTITUTE SUBTOTAL SUM SUMIF SUMIFS SUMPRODUCT SUMSQ SUMX2MY2 SUMX2PY2 SUMXMY2 SWITCH SYD T TAN TANH TBILLEQ TBILLPRICE TBILLYIELD T.DIST T.DIST.2T T.DIST.RT TDIST TEXT TEXTJOIN TIME TIMEVALUE T.INV T.INV.2T TINV TODAY TRANSPOSE TREND TRIM TRIMMEAN TRUE|0 TRUNC T.TEST TTEST TYPE UNICHAR UNICODE UPPER VALUE VAR VAR.P VAR.S VARA VARP VARPA VDB VLOOKUP WEBSERVICE WEEKDAY WEEKNUM WEIBULL WEIBULL.DIST WORKDAY WORKDAY.INTL XIRR XNPV XOR YEAR YEARFRAC YIELD YIELDDISC YIELDMAT Z.TEST ZTEST", }, contains: [ { /* matches a beginning equal sign found in Excel formula examples */ begin: /^=/, end: /[^=]/, returnEnd: true, illegal: /=/ /* only allow single equal sign at front of line */, relevance: 10, }, /* technically, there can be more than 2 letters in column names, but this prevents conflict with some keywords */ { /* matches a reference to a single cell */ className: "symbol", begin: /\b[A-Z]{1,2}\d+\b/, end: /[^\d]/, excludeEnd: true, relevance: 0, }, { /* matches a reference to a range of cells */ className: "symbol", begin: /[A-Z]{0,2}\d*:[A-Z]{0,2}\d*/, relevance: 0, }, hljs.BACKSLASH_ESCAPE, hljs.QUOTE_STRING_MODE, { className: "number", begin: hljs.NUMBER_RE + "(%)?", relevance: 0, }, /* Excel formula comments are done by putting the comment in a function call to N() */ hljs.COMMENT(/\bN\(/, /\)/, { excludeBegin: true, excludeEnd: true, illegal: /\n/, }), ], }; }, }, { name: "fix", /* Language: FIX Author: Brent Bradbury */ create: function (hljs) { return { contains: [ { begin: /[^\u2401\u0001]+/, end: /[\u2401\u0001]/, excludeEnd: true, returnBegin: true, returnEnd: false, contains: [ { begin: /([^\u2401\u0001=]+)/, end: /=([^\u2401\u0001=]+)/, returnEnd: true, returnBegin: false, className: "attr", }, { begin: /=/, end: /([\u2401\u0001])/, excludeEnd: true, excludeBegin: true, className: "string", }, ], }, ], case_insensitive: true, }; }, }, { name: "flix", /* Language: Flix Category: functional Author: Magnus Madsen */ create: function (hljs) { var CHAR = { className: "string", begin: /'(.|\\[xXuU][a-zA-Z0-9]+)'/, }; var STRING = { className: "string", variants: [ { begin: '"', end: '"', }, ], }; var NAME = { className: "title", begin: /[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/, }; var METHOD = { className: "function", beginKeywords: "def", end: /[:={\[(\n;]/, excludeEnd: true, contains: [NAME], }; return { keywords: { literal: "true false", keyword: "case class def else enum if impl import in lat rel index let match namespace switch type yield with", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, CHAR, STRING, METHOD, hljs.C_NUMBER_MODE, ], }; }, }, { name: "fortran", /* Language: Fortran Author: Anthony Scemama Category: scientific */ create: function (hljs) { var PARAMS = { className: "params", begin: "\\(", end: "\\)", }; var F_KEYWORDS = { literal: ".False. .True.", keyword: "kind do while private call intrinsic where elsewhere " + "type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then " + "public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. " + "goto save else use module select case " + "access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit " + "continue format pause cycle exit " + "c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg " + "synchronous nopass non_overridable pass protected volatile abstract extends import " + "non_intrinsic value deferred generic final enumerator class associate bind enum " + "c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t " + "c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double " + "c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr " + "c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer " + "c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor " + "numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control " + "ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive " + "pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure " + "integer real character complex logical dimension allocatable|10 parameter " + "external implicit|10 none double precision assign intent optional pointer " + "target in out common equivalence data", built_in: "alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint " + "dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl " + "algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama " + "iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod " + "qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log " + "log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate " + "adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product " + "eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul " + "maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product " + "radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind " + "set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer " + "dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end " + "ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode " + "is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_of" + "acosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 " + "atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits " + "bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr " + "num_images parity popcnt poppar shifta shiftl shiftr this_image", }; return { case_insensitive: true, aliases: ["f90", "f95"], keywords: F_KEYWORDS, illegal: /\/\*/, contains: [ hljs.inherit(hljs.APOS_STRING_MODE, { className: "string", relevance: 0, }), hljs.inherit(hljs.QUOTE_STRING_MODE, { className: "string", relevance: 0, }), { className: "function", beginKeywords: "subroutine function program", illegal: "[${=\\n]", contains: [hljs.UNDERSCORE_TITLE_MODE, PARAMS], }, hljs.COMMENT("!", "$", { relevance: 0 }), { className: "number", begin: "(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?", relevance: 0, }, ], }; }, }, { name: "fsharp", /* Language: F# Author: Jonas Follesø Contributors: Troy Kershaw , Henrik Feldt Category: functional */ create: function (hljs) { var TYPEPARAM = { begin: "<", end: ">", contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /'[a-zA-Z0-9_]+/ }), ], }; return { aliases: ["fs"], keywords: "abstract and as assert base begin class default delegate do done " + "downcast downto elif else end exception extern false finally for " + "fun function global if in inherit inline interface internal lazy let " + "match member module mutable namespace new null of open or " + "override private public rec return sig static struct then to " + "true try type upcast use val void when while with yield", illegal: /\/\*/, contains: [ { // monad builder keywords (matches before non-bang kws) className: "keyword", begin: /\b(yield|return|let|do)!/, }, { className: "string", begin: '@"', end: '"', contains: [{ begin: '""' }], }, { className: "string", begin: '"""', end: '"""', }, hljs.COMMENT("\\(\\*", "\\*\\)"), { className: "class", beginKeywords: "type", end: "\\(|=|$", excludeEnd: true, contains: [hljs.UNDERSCORE_TITLE_MODE, TYPEPARAM], }, { className: "meta", begin: "\\[<", end: ">\\]", relevance: 10, }, { className: "symbol", begin: "\\B('[A-Za-z])\\b", contains: [hljs.BACKSLASH_ESCAPE], }, hljs.C_LINE_COMMENT_MODE, hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), hljs.C_NUMBER_MODE, ], }; }, }, { name: "gams", create: /* Language: GAMS Author: Stefan Bechert Contributors: Oleg Efimov , Mikko Kouhia Description: The General Algebraic Modeling System language Category: scientific */ function (hljs) { var KEYWORDS = { keyword: "abort acronym acronyms alias all and assign binary card diag display " + "else eq file files for free ge gt if integer le loop lt maximizing " + "minimizing model models ne negative no not option options or ord " + "positive prod put putpage puttl repeat sameas semicont semiint smax " + "smin solve sos1 sos2 sum system table then until using while xor yes", literal: "eps inf na", "built-in": "abs arccos arcsin arctan arctan2 Beta betaReg binomial ceil centropy " + "cos cosh cvPower div div0 eDist entropy errorf execSeed exp fact " + "floor frac gamma gammaReg log logBeta logGamma log10 log2 mapVal max " + "min mod ncpCM ncpF ncpVUpow ncpVUsin normal pi poly power " + "randBinomial randLinear randTriangle round rPower sigmoid sign " + "signPower sin sinh slexp sllog10 slrec sqexp sqlog10 sqr sqrec sqrt " + "tan tanh trunc uniform uniformInt vcPower bool_and bool_eqv bool_imp " + "bool_not bool_or bool_xor ifThen rel_eq rel_ge rel_gt rel_le rel_lt " + "rel_ne gday gdow ghour gleap gmillisec gminute gmonth gsecond gyear " + "jdate jnow jstart jtime errorLevel execError gamsRelease gamsVersion " + "handleCollect handleDelete handleStatus handleSubmit heapFree " + "heapLimit heapSize jobHandle jobKill jobStatus jobTerminate " + "licenseLevel licenseStatus maxExecError sleep timeClose timeComp " + "timeElapsed timeExec timeStart", }; var PARAMS = { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, }; var SYMBOLS = { className: "symbol", variants: [{ begin: /\=[lgenxc]=/ }, { begin: /\$/ }], }; var QSTR = { // One-line quoted comment string className: "comment", variants: [ { begin: "'", end: "'" }, { begin: '"', end: '"' }, ], illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], }; var ASSIGNMENT = { begin: "/", end: "/", keywords: KEYWORDS, contains: [ QSTR, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, hljs.C_NUMBER_MODE, ], }; var DESCTEXT = { // Parameter/set/variable description text begin: /[a-z][a-z0-9_]*(\([a-z0-9_, ]*\))?[ \t]+/, excludeBegin: true, end: "$", endsWithParent: true, contains: [ QSTR, ASSIGNMENT, { className: "comment", begin: /([ ]*[a-z0-9&#*=?@>\\<:\-,()$\[\]_.{}!+%^]+)+/, relevance: 0, }, ], }; return { aliases: ["gms"], case_insensitive: true, keywords: KEYWORDS, contains: [ hljs.COMMENT(/^\$ontext/, /^\$offtext/), { className: "meta", begin: "^\\$[a-z0-9]+", end: "$", returnBegin: true, contains: [ { className: "meta-keyword", begin: "^\\$[a-z0-9]+", }, ], }, hljs.COMMENT("^\\*", "$"), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, // Declarations { beginKeywords: "set sets parameter parameters variable variables " + "scalar scalars equation equations", end: ";", contains: [ hljs.COMMENT("^\\*", "$"), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, ASSIGNMENT, DESCTEXT, ], }, { // table environment beginKeywords: "table", end: ";", returnBegin: true, contains: [ { // table header row beginKeywords: "table", end: "$", contains: [DESCTEXT], }, hljs.COMMENT("^\\*", "$"), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, hljs.C_NUMBER_MODE, // Table does not contain DESCTEXT or ASSIGNMENT ], }, // Function definitions { className: "function", begin: /^[a-z][a-z0-9_,\-+' ()$]+\.{2}/, returnBegin: true, contains: [ { // Function title className: "title", begin: /^[a-z0-9_]+/, }, PARAMS, SYMBOLS, ], }, hljs.C_NUMBER_MODE, SYMBOLS, ], }; }, }, { name: "gauss", /* Language: GAUSS Author: Matt Evans Category: scientific Description: GAUSS Mathematical and Statistical language */ create: function (hljs) { var KEYWORDS = { keyword: "bool break call callexe checkinterrupt clear clearg closeall cls comlog compile " + "continue create debug declare delete disable dlibrary dllcall do dos ed edit else " + "elseif enable end endfor endif endp endo errorlog errorlogat expr external fn " + "for format goto gosub graph if keyword let lib library line load loadarray loadexe " + "loadf loadk loadm loadp loads loadx local locate loopnextindex lprint lpwidth lshow " + "matrix msym ndpclex new open output outwidth plot plotsym pop prcsn print " + "printdos proc push retp return rndcon rndmod rndmult rndseed run save saveall screen " + "scroll setarray show sparse stop string struct system trace trap threadfor " + "threadendfor threadbegin threadjoin threadstat threadend until use while winprint " + "ne ge le gt lt and xor or not eq eqv", built_in: "abs acf aconcat aeye amax amean AmericanBinomCall AmericanBinomCall_Greeks AmericanBinomCall_ImpVol " + "AmericanBinomPut AmericanBinomPut_Greeks AmericanBinomPut_ImpVol AmericanBSCall AmericanBSCall_Greeks " + "AmericanBSCall_ImpVol AmericanBSPut AmericanBSPut_Greeks AmericanBSPut_ImpVol amin amult annotationGetDefaults " + "annotationSetBkd annotationSetFont annotationSetLineColor annotationSetLineStyle annotationSetLineThickness " + "annualTradingDays arccos arcsin areshape arrayalloc arrayindex arrayinit arraytomat asciiload asclabel astd " + "astds asum atan atan2 atranspose axmargin balance band bandchol bandcholsol bandltsol bandrv bandsolpd bar " + "base10 begwind besselj bessely beta box boxcox cdfBeta cdfBetaInv cdfBinomial cdfBinomialInv cdfBvn cdfBvn2 " + "cdfBvn2e cdfCauchy cdfCauchyInv cdfChic cdfChii cdfChinc cdfChincInv cdfExp cdfExpInv cdfFc cdfFnc cdfFncInv " + "cdfGam cdfGenPareto cdfHyperGeo cdfLaplace cdfLaplaceInv cdfLogistic cdfLogisticInv cdfmControlCreate cdfMvn " + "cdfMvn2e cdfMvnce cdfMvne cdfMvt2e cdfMvtce cdfMvte cdfN cdfN2 cdfNc cdfNegBinomial cdfNegBinomialInv cdfNi " + "cdfPoisson cdfPoissonInv cdfRayleigh cdfRayleighInv cdfTc cdfTci cdfTnc cdfTvn cdfWeibull cdfWeibullInv cdir " + "ceil ChangeDir chdir chiBarSquare chol choldn cholsol cholup chrs close code cols colsf combinate combinated " + "complex con cond conj cons ConScore contour conv convertsatostr convertstrtosa corrm corrms corrvc corrx corrxs " + "cos cosh counts countwts crossprd crout croutp csrcol csrlin csvReadM csvReadSA cumprodc cumsumc curve cvtos " + "datacreate datacreatecomplex datalist dataload dataloop dataopen datasave date datestr datestring datestrymd " + "dayinyr dayofweek dbAddDatabase dbClose dbCommit dbCreateQuery dbExecQuery dbGetConnectOptions dbGetDatabaseName " + "dbGetDriverName dbGetDrivers dbGetHostName dbGetLastErrorNum dbGetLastErrorText dbGetNumericalPrecPolicy " + "dbGetPassword dbGetPort dbGetTableHeaders dbGetTables dbGetUserName dbHasFeature dbIsDriverAvailable dbIsOpen " + "dbIsOpenError dbOpen dbQueryBindValue dbQueryClear dbQueryCols dbQueryExecPrepared dbQueryFetchAllM dbQueryFetchAllSA " + "dbQueryFetchOneM dbQueryFetchOneSA dbQueryFinish dbQueryGetBoundValue dbQueryGetBoundValues dbQueryGetField " + "dbQueryGetLastErrorNum dbQueryGetLastErrorText dbQueryGetLastInsertID dbQueryGetLastQuery dbQueryGetPosition " + "dbQueryIsActive dbQueryIsForwardOnly dbQueryIsNull dbQueryIsSelect dbQueryIsValid dbQueryPrepare dbQueryRows " + "dbQuerySeek dbQuerySeekFirst dbQuerySeekLast dbQuerySeekNext dbQuerySeekPrevious dbQuerySetForwardOnly " + "dbRemoveDatabase dbRollback dbSetConnectOptions dbSetDatabaseName dbSetHostName dbSetNumericalPrecPolicy " + "dbSetPort dbSetUserName dbTransaction DeleteFile delif delrows denseToSp denseToSpRE denToZero design det detl " + "dfft dffti diag diagrv digamma doswin DOSWinCloseall DOSWinOpen dotfeq dotfeqmt dotfge dotfgemt dotfgt dotfgtmt " + "dotfle dotflemt dotflt dotfltmt dotfne dotfnemt draw drop dsCreate dstat dstatmt dstatmtControlCreate dtdate dtday " + "dttime dttodtv dttostr dttoutc dtvnormal dtvtodt dtvtoutc dummy dummybr dummydn eig eigh eighv eigv elapsedTradingDays " + "endwind envget eof eqSolve eqSolvemt eqSolvemtControlCreate eqSolvemtOutCreate eqSolveset erf erfc erfccplx erfcplx error " + "etdays ethsec etstr EuropeanBinomCall EuropeanBinomCall_Greeks EuropeanBinomCall_ImpVol EuropeanBinomPut " + "EuropeanBinomPut_Greeks EuropeanBinomPut_ImpVol EuropeanBSCall EuropeanBSCall_Greeks EuropeanBSCall_ImpVol " + "EuropeanBSPut EuropeanBSPut_Greeks EuropeanBSPut_ImpVol exctsmpl exec execbg exp extern eye fcheckerr fclearerr feq " + "feqmt fflush fft ffti fftm fftmi fftn fge fgemt fgets fgetsa fgetsat fgetst fgt fgtmt fileinfo filesa fle flemt " + "floor flt fltmt fmod fne fnemt fonts fopen formatcv formatnv fputs fputst fseek fstrerror ftell ftocv ftos ftostrC " + "gamma gammacplx gammaii gausset gdaAppend gdaCreate gdaDStat gdaDStatMat gdaGetIndex gdaGetName gdaGetNames gdaGetOrders " + "gdaGetType gdaGetTypes gdaGetVarInfo gdaIsCplx gdaLoad gdaPack gdaRead gdaReadByIndex gdaReadSome gdaReadSparse " + "gdaReadStruct gdaReportVarInfo gdaSave gdaUpdate gdaUpdateAndPack gdaVars gdaWrite gdaWrite32 gdaWriteSome getarray " + "getdims getf getGAUSShome getmatrix getmatrix4D getname getnamef getNextTradingDay getNextWeekDay getnr getorders " + "getpath getPreviousTradingDay getPreviousWeekDay getRow getscalar3D getscalar4D getTrRow getwind glm gradcplx gradMT " + "gradMTm gradMTT gradMTTm gradp graphprt graphset hasimag header headermt hess hessMT hessMTg hessMTgw hessMTm " + "hessMTmw hessMTT hessMTTg hessMTTgw hessMTTm hessMTw hessp hist histf histp hsec imag indcv indexcat indices indices2 " + "indicesf indicesfn indnv indsav integrate1d integrateControlCreate intgrat2 intgrat3 inthp1 inthp2 inthp3 inthp4 " + "inthpControlCreate intquad1 intquad2 intquad3 intrleav intrleavsa intrsect intsimp inv invpd invswp iscplx iscplxf " + "isden isinfnanmiss ismiss key keyav keyw lag lag1 lagn lapEighb lapEighi lapEighvb lapEighvi lapgEig lapgEigh lapgEighv " + "lapgEigv lapgSchur lapgSvdcst lapgSvds lapgSvdst lapSvdcusv lapSvds lapSvdusv ldlp ldlsol linSolve listwise ln lncdfbvn " + "lncdfbvn2 lncdfmvn lncdfn lncdfn2 lncdfnc lnfact lngammacplx lnpdfmvn lnpdfmvt lnpdfn lnpdft loadd loadstruct loadwind " + "loess loessmt loessmtControlCreate log loglog logx logy lower lowmat lowmat1 ltrisol lu lusol machEpsilon make makevars " + "makewind margin matalloc matinit mattoarray maxbytes maxc maxindc maxv maxvec mbesselei mbesselei0 mbesselei1 mbesseli " + "mbesseli0 mbesseli1 meanc median mergeby mergevar minc minindc minv miss missex missrv moment momentd movingave " + "movingaveExpwgt movingaveWgt nextindex nextn nextnevn nextwind ntos null null1 numCombinations ols olsmt olsmtControlCreate " + "olsqr olsqr2 olsqrmt ones optn optnevn orth outtyp pacf packedToSp packr parse pause pdfCauchy pdfChi pdfExp pdfGenPareto " + "pdfHyperGeo pdfLaplace pdfLogistic pdfn pdfPoisson pdfRayleigh pdfWeibull pi pinv pinvmt plotAddArrow plotAddBar plotAddBox " + "plotAddHist plotAddHistF plotAddHistP plotAddPolar plotAddScatter plotAddShape plotAddTextbox plotAddTS plotAddXY plotArea " + "plotBar plotBox plotClearLayout plotContour plotCustomLayout plotGetDefaults plotHist plotHistF plotHistP plotLayout " + "plotLogLog plotLogX plotLogY plotOpenWindow plotPolar plotSave plotScatter plotSetAxesPen plotSetBar plotSetBarFill " + "plotSetBarStacked plotSetBkdColor plotSetFill plotSetGrid plotSetLegend plotSetLineColor plotSetLineStyle plotSetLineSymbol " + "plotSetLineThickness plotSetNewWindow plotSetTitle plotSetWhichYAxis plotSetXAxisShow plotSetXLabel plotSetXRange " + "plotSetXTicInterval plotSetXTicLabel plotSetYAxisShow plotSetYLabel plotSetYRange plotSetZAxisShow plotSetZLabel " + "plotSurface plotTS plotXY polar polychar polyeval polygamma polyint polymake polymat polymroot polymult polyroot " + "pqgwin previousindex princomp printfm printfmt prodc psi putarray putf putvals pvCreate pvGetIndex pvGetParNames " + "pvGetParVector pvLength pvList pvPack pvPacki pvPackm pvPackmi pvPacks pvPacksi pvPacksm pvPacksmi pvPutParVector " + "pvTest pvUnpack QNewton QNewtonmt QNewtonmtControlCreate QNewtonmtOutCreate QNewtonSet QProg QProgmt QProgmtInCreate " + "qqr qqre qqrep qr qre qrep qrsol qrtsol qtyr qtyre qtyrep quantile quantiled qyr qyre qyrep qz rank rankindx readr " + "real reclassify reclassifyCuts recode recserar recsercp recserrc rerun rescale reshape rets rev rfft rffti rfftip rfftn " + "rfftnp rfftp rndBernoulli rndBeta rndBinomial rndCauchy rndChiSquare rndCon rndCreateState rndExp rndGamma rndGeo rndGumbel " + "rndHyperGeo rndi rndKMbeta rndKMgam rndKMi rndKMn rndKMnb rndKMp rndKMu rndKMvm rndLaplace rndLCbeta rndLCgam rndLCi rndLCn " + "rndLCnb rndLCp rndLCu rndLCvm rndLogNorm rndMTu rndMVn rndMVt rndn rndnb rndNegBinomial rndp rndPoisson rndRayleigh " + "rndStateSkip rndu rndvm rndWeibull rndWishart rotater round rows rowsf rref sampleData satostrC saved saveStruct savewind " + "scale scale3d scalerr scalinfnanmiss scalmiss schtoc schur searchsourcepath seekr select selif seqa seqm setdif setdifsa " + "setvars setvwrmode setwind shell shiftr sin singleindex sinh sleep solpd sortc sortcc sortd sorthc sorthcc sortind " + "sortindc sortmc sortr sortrc spBiconjGradSol spChol spConjGradSol spCreate spDenseSubmat spDiagRvMat spEigv spEye spLDL " + "spline spLU spNumNZE spOnes spreadSheetReadM spreadSheetReadSA spreadSheetWrite spScale spSubmat spToDense spTrTDense " + "spTScalar spZeros sqpSolve sqpSolveMT sqpSolveMTControlCreate sqpSolveMTlagrangeCreate sqpSolveMToutCreate sqpSolveSet " + "sqrt statements stdc stdsc stocv stof strcombine strindx strlen strput strrindx strsect strsplit strsplitPad strtodt " + "strtof strtofcplx strtriml strtrimr strtrunc strtruncl strtruncpad strtruncr submat subscat substute subvec sumc sumr " + "surface svd svd1 svd2 svdcusv svds svdusv sysstate tab tan tanh tempname " + "time timedt timestr timeutc title tkf2eps tkf2ps tocart todaydt toeplitz token topolar trapchk " + "trigamma trimr trunc type typecv typef union unionsa uniqindx uniqindxsa unique uniquesa upmat upmat1 upper utctodt " + "utctodtv utrisol vals varCovMS varCovXS varget vargetl varmall varmares varput varputl vartypef vcm vcms vcx vcxs " + "vec vech vecr vector vget view viewxyz vlist vnamecv volume vput vread vtypecv wait waitc walkindex where window " + "writer xlabel xlsGetSheetCount xlsGetSheetSize xlsGetSheetTypes xlsMakeRange xlsReadM xlsReadSA xlsWrite xlsWriteM " + "xlsWriteSA xpnd xtics xy xyz ylabel ytics zeros zeta zlabel ztics cdfEmpirical dot h5create h5open h5read h5readAttribute " + "h5write h5writeAttribute ldl plotAddErrorBar plotAddSurface plotCDFEmpirical plotSetColormap plotSetContourLabels " + "plotSetLegendFont plotSetTextInterpreter plotSetXTicCount plotSetYTicCount plotSetZLevels powerm strjoin sylvester " + "strtrim", literal: "DB_AFTER_LAST_ROW DB_ALL_TABLES DB_BATCH_OPERATIONS DB_BEFORE_FIRST_ROW DB_BLOB DB_EVENT_NOTIFICATIONS " + "DB_FINISH_QUERY DB_HIGH_PRECISION DB_LAST_INSERT_ID DB_LOW_PRECISION_DOUBLE DB_LOW_PRECISION_INT32 " + "DB_LOW_PRECISION_INT64 DB_LOW_PRECISION_NUMBERS DB_MULTIPLE_RESULT_SETS DB_NAMED_PLACEHOLDERS " + "DB_POSITIONAL_PLACEHOLDERS DB_PREPARED_QUERIES DB_QUERY_SIZE DB_SIMPLE_LOCKING DB_SYSTEM_TABLES DB_TABLES " + "DB_TRANSACTIONS DB_UNICODE DB_VIEWS __STDIN __STDOUT __STDERR __FILE_DIR", }; var AT_COMMENT_MODE = hljs.COMMENT("@", "@"); var PREPROCESSOR = { className: "meta", begin: "#", end: "$", keywords: { "meta-keyword": "define definecs|10 undef ifdef ifndef iflight ifdllcall ifmac ifos2win ifunix else endif lineson linesoff srcfile srcline", }, contains: [ { begin: /\\\n/, relevance: 0, }, { beginKeywords: "include", end: "$", keywords: { "meta-keyword": "include" }, contains: [ { className: "meta-string", begin: '"', end: '"', illegal: "\\n", }, ], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, AT_COMMENT_MODE, ], }; var STRUCT_TYPE = { begin: /\bstruct\s+/, end: /\s/, keywords: "struct", contains: [ { className: "type", begin: hljs.UNDERSCORE_IDENT_RE, relevance: 0, }, ], }; // only for definitions var PARSE_PARAMS = [ { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, endsWithParent: true, relevance: 0, contains: [ { // dots className: "literal", begin: /\.\.\./, }, hljs.C_NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, AT_COMMENT_MODE, STRUCT_TYPE, ], }, ]; var FUNCTION_DEF = { className: "title", begin: hljs.UNDERSCORE_IDENT_RE, relevance: 0, }; var DEFINITION = function (beginKeywords, end, inherits) { var mode = hljs.inherit( { className: "function", beginKeywords: beginKeywords, end: end, excludeEnd: true, contains: [].concat(PARSE_PARAMS), }, inherits || {}, ); mode.contains.push(FUNCTION_DEF); mode.contains.push(hljs.C_NUMBER_MODE); mode.contains.push(hljs.C_BLOCK_COMMENT_MODE); mode.contains.push(AT_COMMENT_MODE); return mode; }; var BUILT_IN_REF = { // these are explicitly named internal function calls className: "built_in", begin: "\\b(" + KEYWORDS.built_in.split(" ").join("|") + ")\\b", }; var STRING_REF = { className: "string", begin: '"', end: '"', contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }; var FUNCTION_REF = { //className: "fn_ref", begin: hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", returnBegin: true, keywords: KEYWORDS, relevance: 0, contains: [ { beginKeywords: KEYWORDS.keyword, }, BUILT_IN_REF, { // ambiguously named function calls get a relevance of 0 className: "built_in", begin: hljs.UNDERSCORE_IDENT_RE, relevance: 0, }, ], }; var FUNCTION_REF_PARAMS = { //className: "fn_ref_params", begin: /\(/, end: /\)/, relevance: 0, keywords: { built_in: KEYWORDS.built_in, literal: KEYWORDS.literal, }, contains: [ hljs.C_NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, AT_COMMENT_MODE, BUILT_IN_REF, FUNCTION_REF, STRING_REF, "self", ], }; FUNCTION_REF.contains.push(FUNCTION_REF_PARAMS); return { aliases: ["gss"], case_insensitive: true, // language is case-insensitive keywords: KEYWORDS, illegal: /(\{[%#]|[%#]\}| <- )/, contains: [ hljs.C_NUMBER_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, AT_COMMENT_MODE, STRING_REF, PREPROCESSOR, { className: "keyword", begin: /\bexternal (matrix|string|array|sparse matrix|struct|proc|keyword|fn)/, }, DEFINITION("proc keyword", ";"), DEFINITION("fn", "="), { beginKeywords: "for threadfor", end: /;/, //end: /\(/, relevance: 0, contains: [ hljs.C_BLOCK_COMMENT_MODE, AT_COMMENT_MODE, FUNCTION_REF_PARAMS, ], }, { // custom method guard // excludes method names from keyword processing variants: [ { begin: hljs.UNDERSCORE_IDENT_RE + "\\." + hljs.UNDERSCORE_IDENT_RE, }, { begin: hljs.UNDERSCORE_IDENT_RE + "\\s*=" }, ], relevance: 0, }, FUNCTION_REF, STRUCT_TYPE, ], }; }, }, { name: "gcode", /* Language: G-code (ISO 6983) Contributors: Adam Joseph Cook Description: G-code syntax highlighter for Fanuc and other common CNC machine tool controls. */ create: function (hljs) { var GCODE_IDENT_RE = "[A-Z_][A-Z0-9_.]*"; var GCODE_CLOSE_RE = "\\%"; var GCODE_KEYWORDS = "IF DO WHILE ENDWHILE CALL ENDIF SUB ENDSUB GOTO REPEAT ENDREPEAT " + "EQ LT GT NE GE LE OR XOR"; var GCODE_START = { className: "meta", begin: "([O])([0-9]+)", }; var GCODE_CODE = [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.COMMENT(/\(/, /\)/), hljs.inherit(hljs.C_NUMBER_MODE, { begin: "([-+]?([0-9]*\\.?[0-9]+\\.?))|" + hljs.C_NUMBER_RE, }), hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), { className: "name", begin: "([G])([0-9]+\\.?[0-9]?)", }, { className: "name", begin: "([M])([0-9]+\\.?[0-9]?)", }, { className: "attr", begin: "(VC|VS|#)", end: "(\\d+)", }, { className: "attr", begin: "(VZOFX|VZOFY|VZOFZ)", }, { className: "built_in", begin: "(ATAN|ABS|ACOS|ASIN|SIN|COS|EXP|FIX|FUP|ROUND|LN|TAN)(\\[)", end: "([-+]?([0-9]*\\.?[0-9]+\\.?))(\\])", }, { className: "symbol", variants: [ { begin: "N", end: "\\d+", illegal: "\\W", }, ], }, ]; return { aliases: ["nc"], // Some implementations (CNC controls) of G-code are interoperable with uppercase and lowercase letters seamlessly. // However, most prefer all uppercase and uppercase is customary. case_insensitive: true, lexemes: GCODE_IDENT_RE, keywords: GCODE_KEYWORDS, contains: [ { className: "meta", begin: GCODE_CLOSE_RE, }, GCODE_START, ].concat(GCODE_CODE), }; }, }, { name: "gherkin", /* Language: Gherkin Author: Sam Pikesley (@pikesley) Description: Gherkin (Cucumber etc) */ create: function (hljs) { return { aliases: ["feature"], keywords: "Feature Background Ability Business\ Need Scenario Scenarios Scenario\ Outline Scenario\ Template Examples Given And Then But When", contains: [ { className: "symbol", begin: "\\*", relevance: 0, }, { className: "meta", begin: "@[^@\\s]+", }, { begin: "\\|", end: "\\|\\w*$", contains: [ { className: "string", begin: "[^|]+", }, ], }, { className: "variable", begin: "<", end: ">", }, hljs.HASH_COMMENT_MODE, { className: "string", begin: '"""', end: '"""', }, hljs.QUOTE_STRING_MODE, ], }; }, }, { name: "glsl", /* Language: GLSL Description: OpenGL Shading Language Author: Sergey Tikhomirov Category: graphics */ create: function (hljs) { return { keywords: { keyword: // Statements "break continue discard do else for if return while switch case default " + // Qualifiers "attribute binding buffer ccw centroid centroid varying coherent column_major const cw " + "depth_any depth_greater depth_less depth_unchanged early_fragment_tests equal_spacing " + "flat fractional_even_spacing fractional_odd_spacing highp in index inout invariant " + "invocations isolines layout line_strip lines lines_adjacency local_size_x local_size_y " + "local_size_z location lowp max_vertices mediump noperspective offset origin_upper_left " + "out packed patch pixel_center_integer point_mode points precise precision quads r11f_g11f_b10f " + "r16 r16_snorm r16f r16i r16ui r32f r32i r32ui r8 r8_snorm r8i r8ui readonly restrict " + "rg16 rg16_snorm rg16f rg16i rg16ui rg32f rg32i rg32ui rg8 rg8_snorm rg8i rg8ui rgb10_a2 " + "rgb10_a2ui rgba16 rgba16_snorm rgba16f rgba16i rgba16ui rgba32f rgba32i rgba32ui rgba8 " + "rgba8_snorm rgba8i rgba8ui row_major sample shared smooth std140 std430 stream triangle_strip " + "triangles triangles_adjacency uniform varying vertices volatile writeonly", type: "atomic_uint bool bvec2 bvec3 bvec4 dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 " + "dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 double dvec2 dvec3 dvec4 float iimage1D iimage1DArray " + "iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBuffer" + "iimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray " + "image2DRect image3D imageBuffer imageCube imageCubeArray int isampler1D isampler1DArray " + "isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D " + "isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 mat2 mat2x2 mat2x3 " + "mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 sampler1D sampler1DArray " + "sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow " + "sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D " + "samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow " + "image1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect " + "uimage3D uimageBuffer uimageCube uimageCubeArray uint usampler1D usampler1DArray " + "usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D " + "samplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 vec2 vec3 vec4 void", built_in: // Constants "gl_MaxAtomicCounterBindings gl_MaxAtomicCounterBufferSize gl_MaxClipDistances gl_MaxClipPlanes " + "gl_MaxCombinedAtomicCounterBuffers gl_MaxCombinedAtomicCounters gl_MaxCombinedImageUniforms " + "gl_MaxCombinedImageUnitsAndFragmentOutputs gl_MaxCombinedTextureImageUnits gl_MaxComputeAtomicCounterBuffers " + "gl_MaxComputeAtomicCounters gl_MaxComputeImageUniforms gl_MaxComputeTextureImageUnits " + "gl_MaxComputeUniformComponents gl_MaxComputeWorkGroupCount gl_MaxComputeWorkGroupSize " + "gl_MaxDrawBuffers gl_MaxFragmentAtomicCounterBuffers gl_MaxFragmentAtomicCounters " + "gl_MaxFragmentImageUniforms gl_MaxFragmentInputComponents gl_MaxFragmentInputVectors " + "gl_MaxFragmentUniformComponents gl_MaxFragmentUniformVectors gl_MaxGeometryAtomicCounterBuffers " + "gl_MaxGeometryAtomicCounters gl_MaxGeometryImageUniforms gl_MaxGeometryInputComponents " + "gl_MaxGeometryOutputComponents gl_MaxGeometryOutputVertices gl_MaxGeometryTextureImageUnits " + "gl_MaxGeometryTotalOutputComponents gl_MaxGeometryUniformComponents gl_MaxGeometryVaryingComponents " + "gl_MaxImageSamples gl_MaxImageUnits gl_MaxLights gl_MaxPatchVertices gl_MaxProgramTexelOffset " + "gl_MaxTessControlAtomicCounterBuffers gl_MaxTessControlAtomicCounters gl_MaxTessControlImageUniforms " + "gl_MaxTessControlInputComponents gl_MaxTessControlOutputComponents gl_MaxTessControlTextureImageUnits " + "gl_MaxTessControlTotalOutputComponents gl_MaxTessControlUniformComponents " + "gl_MaxTessEvaluationAtomicCounterBuffers gl_MaxTessEvaluationAtomicCounters " + "gl_MaxTessEvaluationImageUniforms gl_MaxTessEvaluationInputComponents gl_MaxTessEvaluationOutputComponents " + "gl_MaxTessEvaluationTextureImageUnits gl_MaxTessEvaluationUniformComponents " + "gl_MaxTessGenLevel gl_MaxTessPatchComponents gl_MaxTextureCoords gl_MaxTextureImageUnits " + "gl_MaxTextureUnits gl_MaxVaryingComponents gl_MaxVaryingFloats gl_MaxVaryingVectors " + "gl_MaxVertexAtomicCounterBuffers gl_MaxVertexAtomicCounters gl_MaxVertexAttribs gl_MaxVertexImageUniforms " + "gl_MaxVertexOutputComponents gl_MaxVertexOutputVectors gl_MaxVertexTextureImageUnits " + "gl_MaxVertexUniformComponents gl_MaxVertexUniformVectors gl_MaxViewports gl_MinProgramTexelOffset " + // Variables "gl_BackColor gl_BackLightModelProduct gl_BackLightProduct gl_BackMaterial " + "gl_BackSecondaryColor gl_ClipDistance gl_ClipPlane gl_ClipVertex gl_Color " + "gl_DepthRange gl_EyePlaneQ gl_EyePlaneR gl_EyePlaneS gl_EyePlaneT gl_Fog gl_FogCoord " + "gl_FogFragCoord gl_FragColor gl_FragCoord gl_FragData gl_FragDepth gl_FrontColor " + "gl_FrontFacing gl_FrontLightModelProduct gl_FrontLightProduct gl_FrontMaterial " + "gl_FrontSecondaryColor gl_GlobalInvocationID gl_InstanceID gl_InvocationID gl_Layer gl_LightModel " + "gl_LightSource gl_LocalInvocationID gl_LocalInvocationIndex gl_ModelViewMatrix " + "gl_ModelViewMatrixInverse gl_ModelViewMatrixInverseTranspose gl_ModelViewMatrixTranspose " + "gl_ModelViewProjectionMatrix gl_ModelViewProjectionMatrixInverse gl_ModelViewProjectionMatrixInverseTranspose " + "gl_ModelViewProjectionMatrixTranspose gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 " + "gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 " + "gl_Normal gl_NormalMatrix gl_NormalScale gl_NumSamples gl_NumWorkGroups gl_ObjectPlaneQ " + "gl_ObjectPlaneR gl_ObjectPlaneS gl_ObjectPlaneT gl_PatchVerticesIn gl_Point gl_PointCoord " + "gl_PointSize gl_Position gl_PrimitiveID gl_PrimitiveIDIn gl_ProjectionMatrix gl_ProjectionMatrixInverse " + "gl_ProjectionMatrixInverseTranspose gl_ProjectionMatrixTranspose gl_SampleID gl_SampleMask " + "gl_SampleMaskIn gl_SamplePosition gl_SecondaryColor gl_TessCoord gl_TessLevelInner gl_TessLevelOuter " + "gl_TexCoord gl_TextureEnvColor gl_TextureMatrix gl_TextureMatrixInverse gl_TextureMatrixInverseTranspose " + "gl_TextureMatrixTranspose gl_Vertex gl_VertexID gl_ViewportIndex gl_WorkGroupID gl_WorkGroupSize gl_in gl_out " + // Functions "EmitStreamVertex EmitVertex EndPrimitive EndStreamPrimitive abs acos acosh all any asin " + "asinh atan atanh atomicAdd atomicAnd atomicCompSwap atomicCounter atomicCounterDecrement " + "atomicCounterIncrement atomicExchange atomicMax atomicMin atomicOr atomicXor barrier " + "bitCount bitfieldExtract bitfieldInsert bitfieldReverse ceil clamp cos cosh cross " + "dFdx dFdy degrees determinant distance dot equal exp exp2 faceforward findLSB findMSB " + "floatBitsToInt floatBitsToUint floor fma fract frexp ftransform fwidth greaterThan " + "greaterThanEqual groupMemoryBarrier imageAtomicAdd imageAtomicAnd imageAtomicCompSwap " + "imageAtomicExchange imageAtomicMax imageAtomicMin imageAtomicOr imageAtomicXor imageLoad " + "imageSize imageStore imulExtended intBitsToFloat interpolateAtCentroid interpolateAtOffset " + "interpolateAtSample inverse inversesqrt isinf isnan ldexp length lessThan lessThanEqual log " + "log2 matrixCompMult max memoryBarrier memoryBarrierAtomicCounter memoryBarrierBuffer " + "memoryBarrierImage memoryBarrierShared min mix mod modf noise1 noise2 noise3 noise4 " + "normalize not notEqual outerProduct packDouble2x32 packHalf2x16 packSnorm2x16 packSnorm4x8 " + "packUnorm2x16 packUnorm4x8 pow radians reflect refract round roundEven shadow1D shadow1DLod " + "shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin sinh " + "smoothstep sqrt step tan tanh texelFetch texelFetchOffset texture texture1D texture1DLod " + "texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod " + "texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod " + "textureGather textureGatherOffset textureGatherOffsets textureGrad textureGradOffset " + "textureLod textureLodOffset textureOffset textureProj textureProjGrad textureProjGradOffset " + "textureProjLod textureProjLodOffset textureProjOffset textureQueryLevels textureQueryLod " + "textureSize transpose trunc uaddCarry uintBitsToFloat umulExtended unpackDouble2x32 " + "unpackHalf2x16 unpackSnorm2x16 unpackSnorm4x8 unpackUnorm2x16 unpackUnorm4x8 usubBorrow", literal: "true false", }, illegal: '"', contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.C_NUMBER_MODE, { className: "meta", begin: "#", end: "$", }, ], }; }, }, { name: "gml", /* Language: GML Author: Meseta Description: Game Maker Language for GameMaker Studio 2 Category: scripting */ create: function (hljs) { var GML_KEYWORDS = { keywords: "begin end if then else while do for break continue with until " + "repeat exit and or xor not return mod div switch case default var " + "globalvar enum #macro #region #endregion", built_in: "is_real is_string is_array is_undefined is_int32 is_int64 " + "is_ptr is_vec3 is_vec4 is_matrix is_bool typeof " + "variable_global_exists variable_global_get variable_global_set " + "variable_instance_exists variable_instance_get variable_instance_set " + "variable_instance_get_names array_length_1d array_length_2d " + "array_height_2d array_equals array_create array_copy random " + "random_range irandom irandom_range random_set_seed random_get_seed " + "randomize randomise choose abs round floor ceil sign frac sqrt sqr " + "exp ln log2 log10 sin cos tan arcsin arccos arctan arctan2 dsin dcos " + "dtan darcsin darccos darctan darctan2 degtorad radtodeg power logn " + "min max mean median clamp lerp dot_product dot_product_3d " + "dot_product_normalised dot_product_3d_normalised " + "dot_product_normalized dot_product_3d_normalized math_set_epsilon " + "math_get_epsilon angle_difference point_distance_3d point_distance " + "point_direction lengthdir_x lengthdir_y real string int64 ptr " + "string_format chr ansi_char ord string_length string_byte_length " + "string_pos string_copy string_char_at string_ord_at string_byte_at " + "string_set_byte_at string_delete string_insert string_lower " + "string_upper string_repeat string_letters string_digits " + "string_lettersdigits string_replace string_replace_all string_count " + "string_hash_to_newline clipboard_has_text clipboard_set_text " + "clipboard_get_text date_current_datetime date_create_datetime " + "date_valid_datetime date_inc_year date_inc_month date_inc_week " + "date_inc_day date_inc_hour date_inc_minute date_inc_second " + "date_get_year date_get_month date_get_week date_get_day " + "date_get_hour date_get_minute date_get_second date_get_weekday " + "date_get_day_of_year date_get_hour_of_year date_get_minute_of_year " + "date_get_second_of_year date_year_span date_month_span " + "date_week_span date_day_span date_hour_span date_minute_span " + "date_second_span date_compare_datetime date_compare_date " + "date_compare_time date_date_of date_time_of date_datetime_string " + "date_date_string date_time_string date_days_in_month " + "date_days_in_year date_leap_year date_is_today date_set_timezone " + "date_get_timezone game_set_speed game_get_speed motion_set " + "motion_add place_free place_empty place_meeting place_snapped " + "move_random move_snap move_towards_point move_contact_solid " + "move_contact_all move_outside_solid move_outside_all " + "move_bounce_solid move_bounce_all move_wrap distance_to_point " + "distance_to_object position_empty position_meeting path_start " + "path_end mp_linear_step mp_potential_step mp_linear_step_object " + "mp_potential_step_object mp_potential_settings mp_linear_path " + "mp_potential_path mp_linear_path_object mp_potential_path_object " + "mp_grid_create mp_grid_destroy mp_grid_clear_all mp_grid_clear_cell " + "mp_grid_clear_rectangle mp_grid_add_cell mp_grid_get_cell " + "mp_grid_add_rectangle mp_grid_add_instances mp_grid_path " + "mp_grid_draw mp_grid_to_ds_grid collision_point collision_rectangle " + "collision_circle collision_ellipse collision_line " + "collision_point_list collision_rectangle_list collision_circle_list " + "collision_ellipse_list collision_line_list instance_position_list " + "instance_place_list point_in_rectangle " + "point_in_triangle point_in_circle rectangle_in_rectangle " + "rectangle_in_triangle rectangle_in_circle instance_find " + "instance_exists instance_number instance_position instance_nearest " + "instance_furthest instance_place instance_create_depth " + "instance_create_layer instance_copy instance_change instance_destroy " + "position_destroy position_change instance_id_get " + "instance_deactivate_all instance_deactivate_object " + "instance_deactivate_region instance_activate_all " + "instance_activate_object instance_activate_region room_goto " + "room_goto_previous room_goto_next room_previous room_next " + "room_restart game_end game_restart game_load game_save " + "game_save_buffer game_load_buffer event_perform event_user " + "event_perform_object event_inherited show_debug_message " + "show_debug_overlay debug_event debug_get_callstack alarm_get " + "alarm_set font_texture_page_size keyboard_set_map keyboard_get_map " + "keyboard_unset_map keyboard_check keyboard_check_pressed " + "keyboard_check_released keyboard_check_direct keyboard_get_numlock " + "keyboard_set_numlock keyboard_key_press keyboard_key_release " + "keyboard_clear io_clear mouse_check_button " + "mouse_check_button_pressed mouse_check_button_released " + "mouse_wheel_up mouse_wheel_down mouse_clear draw_self draw_sprite " + "draw_sprite_pos draw_sprite_ext draw_sprite_stretched " + "draw_sprite_stretched_ext draw_sprite_tiled draw_sprite_tiled_ext " + "draw_sprite_part draw_sprite_part_ext draw_sprite_general draw_clear " + "draw_clear_alpha draw_point draw_line draw_line_width draw_rectangle " + "draw_roundrect draw_roundrect_ext draw_triangle draw_circle " + "draw_ellipse draw_set_circle_precision draw_arrow draw_button " + "draw_path draw_healthbar draw_getpixel draw_getpixel_ext " + "draw_set_colour draw_set_color draw_set_alpha draw_get_colour " + "draw_get_color draw_get_alpha merge_colour make_colour_rgb " + "make_colour_hsv colour_get_red colour_get_green colour_get_blue " + "colour_get_hue colour_get_saturation colour_get_value merge_color " + "make_color_rgb make_color_hsv color_get_red color_get_green " + "color_get_blue color_get_hue color_get_saturation color_get_value " + "merge_color screen_save screen_save_part draw_set_font " + "draw_set_halign draw_set_valign draw_text draw_text_ext string_width " + "string_height string_width_ext string_height_ext " + "draw_text_transformed draw_text_ext_transformed draw_text_colour " + "draw_text_ext_colour draw_text_transformed_colour " + "draw_text_ext_transformed_colour draw_text_color draw_text_ext_color " + "draw_text_transformed_color draw_text_ext_transformed_color " + "draw_point_colour draw_line_colour draw_line_width_colour " + "draw_rectangle_colour draw_roundrect_colour " + "draw_roundrect_colour_ext draw_triangle_colour draw_circle_colour " + "draw_ellipse_colour draw_point_color draw_line_color " + "draw_line_width_color draw_rectangle_color draw_roundrect_color " + "draw_roundrect_color_ext draw_triangle_color draw_circle_color " + "draw_ellipse_color draw_primitive_begin draw_vertex " + "draw_vertex_colour draw_vertex_color draw_primitive_end " + "sprite_get_uvs font_get_uvs sprite_get_texture font_get_texture " + "texture_get_width texture_get_height texture_get_uvs " + "draw_primitive_begin_texture draw_vertex_texture " + "draw_vertex_texture_colour draw_vertex_texture_color " + "texture_global_scale surface_create surface_create_ext " + "surface_resize surface_free surface_exists surface_get_width " + "surface_get_height surface_get_texture surface_set_target " + "surface_set_target_ext surface_reset_target surface_depth_disable " + "surface_get_depth_disable draw_surface draw_surface_stretched " + "draw_surface_tiled draw_surface_part draw_surface_ext " + "draw_surface_stretched_ext draw_surface_tiled_ext " + "draw_surface_part_ext draw_surface_general surface_getpixel " + "surface_getpixel_ext surface_save surface_save_part surface_copy " + "surface_copy_part application_surface_draw_enable " + "application_get_position application_surface_enable " + "application_surface_is_enabled display_get_width display_get_height " + "display_get_orientation display_get_gui_width display_get_gui_height " + "display_reset display_mouse_get_x display_mouse_get_y " + "display_mouse_set display_set_ui_visibility " + "window_set_fullscreen window_get_fullscreen " + "window_set_caption window_set_min_width window_set_max_width " + "window_set_min_height window_set_max_height window_get_visible_rects " + "window_get_caption window_set_cursor window_get_cursor " + "window_set_colour window_get_colour window_set_color " + "window_get_color window_set_position window_set_size " + "window_set_rectangle window_center window_get_x window_get_y " + "window_get_width window_get_height window_mouse_get_x " + "window_mouse_get_y window_mouse_set window_view_mouse_get_x " + "window_view_mouse_get_y window_views_mouse_get_x " + "window_views_mouse_get_y audio_listener_position " + "audio_listener_velocity audio_listener_orientation " + "audio_emitter_position audio_emitter_create audio_emitter_free " + "audio_emitter_exists audio_emitter_pitch audio_emitter_velocity " + "audio_emitter_falloff audio_emitter_gain audio_play_sound " + "audio_play_sound_on audio_play_sound_at audio_stop_sound " + "audio_resume_music audio_music_is_playing audio_resume_sound " + "audio_pause_sound audio_pause_music audio_channel_num " + "audio_sound_length audio_get_type audio_falloff_set_model " + "audio_play_music audio_stop_music audio_master_gain audio_music_gain " + "audio_sound_gain audio_sound_pitch audio_stop_all audio_resume_all " + "audio_pause_all audio_is_playing audio_is_paused audio_exists " + "audio_sound_set_track_position audio_sound_get_track_position " + "audio_emitter_get_gain audio_emitter_get_pitch audio_emitter_get_x " + "audio_emitter_get_y audio_emitter_get_z audio_emitter_get_vx " + "audio_emitter_get_vy audio_emitter_get_vz " + "audio_listener_set_position audio_listener_set_velocity " + "audio_listener_set_orientation audio_listener_get_data " + "audio_set_master_gain audio_get_master_gain audio_sound_get_gain " + "audio_sound_get_pitch audio_get_name audio_sound_set_track_position " + "audio_sound_get_track_position audio_create_stream " + "audio_destroy_stream audio_create_sync_group " + "audio_destroy_sync_group audio_play_in_sync_group " + "audio_start_sync_group audio_stop_sync_group audio_pause_sync_group " + "audio_resume_sync_group audio_sync_group_get_track_pos " + "audio_sync_group_debug audio_sync_group_is_playing audio_debug " + "audio_group_load audio_group_unload audio_group_is_loaded " + "audio_group_load_progress audio_group_name audio_group_stop_all " + "audio_group_set_gain audio_create_buffer_sound " + "audio_free_buffer_sound audio_create_play_queue " + "audio_free_play_queue audio_queue_sound audio_get_recorder_count " + "audio_get_recorder_info audio_start_recording audio_stop_recording " + "audio_sound_get_listener_mask audio_emitter_get_listener_mask " + "audio_get_listener_mask audio_sound_set_listener_mask " + "audio_emitter_set_listener_mask audio_set_listener_mask " + "audio_get_listener_count audio_get_listener_info audio_system " + "show_message show_message_async clickable_add clickable_add_ext " + "clickable_change clickable_change_ext clickable_delete " + "clickable_exists clickable_set_style show_question " + "show_question_async get_integer get_string get_integer_async " + "get_string_async get_login_async get_open_filename get_save_filename " + "get_open_filename_ext get_save_filename_ext show_error " + "highscore_clear highscore_add highscore_value highscore_name " + "draw_highscore sprite_exists sprite_get_name sprite_get_number " + "sprite_get_width sprite_get_height sprite_get_xoffset " + "sprite_get_yoffset sprite_get_bbox_left sprite_get_bbox_right " + "sprite_get_bbox_top sprite_get_bbox_bottom sprite_save " + "sprite_save_strip sprite_set_cache_size sprite_set_cache_size_ext " + "sprite_get_tpe sprite_prefetch sprite_prefetch_multi sprite_flush " + "sprite_flush_multi sprite_set_speed sprite_get_speed_type " + "sprite_get_speed font_exists font_get_name font_get_fontname " + "font_get_bold font_get_italic font_get_first font_get_last " + "font_get_size font_set_cache_size path_exists path_get_name " + "path_get_length path_get_time path_get_kind path_get_closed " + "path_get_precision path_get_number path_get_point_x path_get_point_y " + "path_get_point_speed path_get_x path_get_y path_get_speed " + "script_exists script_get_name timeline_add timeline_delete " + "timeline_clear timeline_exists timeline_get_name " + "timeline_moment_clear timeline_moment_add_script timeline_size " + "timeline_max_moment object_exists object_get_name object_get_sprite " + "object_get_solid object_get_visible object_get_persistent " + "object_get_mask object_get_parent object_get_physics " + "object_is_ancestor room_exists room_get_name sprite_set_offset " + "sprite_duplicate sprite_assign sprite_merge sprite_add " + "sprite_replace sprite_create_from_surface sprite_add_from_surface " + "sprite_delete sprite_set_alpha_from_sprite sprite_collision_mask " + "font_add_enable_aa font_add_get_enable_aa font_add font_add_sprite " + "font_add_sprite_ext font_replace font_replace_sprite " + "font_replace_sprite_ext font_delete path_set_kind path_set_closed " + "path_set_precision path_add path_assign path_duplicate path_append " + "path_delete path_add_point path_insert_point path_change_point " + "path_delete_point path_clear_points path_reverse path_mirror " + "path_flip path_rotate path_rescale path_shift script_execute " + "object_set_sprite object_set_solid object_set_visible " + "object_set_persistent object_set_mask room_set_width room_set_height " + "room_set_persistent room_set_background_colour " + "room_set_background_color room_set_view room_set_viewport " + "room_get_viewport room_set_view_enabled room_add room_duplicate " + "room_assign room_instance_add room_instance_clear room_get_camera " + "room_set_camera asset_get_index asset_get_type " + "file_text_open_from_string file_text_open_read file_text_open_write " + "file_text_open_append file_text_close file_text_write_string " + "file_text_write_real file_text_writeln file_text_read_string " + "file_text_read_real file_text_readln file_text_eof file_text_eoln " + "file_exists file_delete file_rename file_copy directory_exists " + "directory_create directory_destroy file_find_first file_find_next " + "file_find_close file_attributes filename_name filename_path " + "filename_dir filename_drive filename_ext filename_change_ext " + "file_bin_open file_bin_rewrite file_bin_close file_bin_position " + "file_bin_size file_bin_seek file_bin_write_byte file_bin_read_byte " + "parameter_count parameter_string environment_get_variable " + "ini_open_from_string ini_open ini_close ini_read_string " + "ini_read_real ini_write_string ini_write_real ini_key_exists " + "ini_section_exists ini_key_delete ini_section_delete " + "ds_set_precision ds_exists ds_stack_create ds_stack_destroy " + "ds_stack_clear ds_stack_copy ds_stack_size ds_stack_empty " + "ds_stack_push ds_stack_pop ds_stack_top ds_stack_write ds_stack_read " + "ds_queue_create ds_queue_destroy ds_queue_clear ds_queue_copy " + "ds_queue_size ds_queue_empty ds_queue_enqueue ds_queue_dequeue " + "ds_queue_head ds_queue_tail ds_queue_write ds_queue_read " + "ds_list_create ds_list_destroy ds_list_clear ds_list_copy " + "ds_list_size ds_list_empty ds_list_add ds_list_insert " + "ds_list_replace ds_list_delete ds_list_find_index ds_list_find_value " + "ds_list_mark_as_list ds_list_mark_as_map ds_list_sort " + "ds_list_shuffle ds_list_write ds_list_read ds_list_set ds_map_create " + "ds_map_destroy ds_map_clear ds_map_copy ds_map_size ds_map_empty " + "ds_map_add ds_map_add_list ds_map_add_map ds_map_replace " + "ds_map_replace_map ds_map_replace_list ds_map_delete ds_map_exists " + "ds_map_find_value ds_map_find_previous ds_map_find_next " + "ds_map_find_first ds_map_find_last ds_map_write ds_map_read " + "ds_map_secure_save ds_map_secure_load ds_map_secure_load_buffer " + "ds_map_secure_save_buffer ds_map_set ds_priority_create " + "ds_priority_destroy ds_priority_clear ds_priority_copy " + "ds_priority_size ds_priority_empty ds_priority_add " + "ds_priority_change_priority ds_priority_find_priority " + "ds_priority_delete_value ds_priority_delete_min ds_priority_find_min " + "ds_priority_delete_max ds_priority_find_max ds_priority_write " + "ds_priority_read ds_grid_create ds_grid_destroy ds_grid_copy " + "ds_grid_resize ds_grid_width ds_grid_height ds_grid_clear " + "ds_grid_set ds_grid_add ds_grid_multiply ds_grid_set_region " + "ds_grid_add_region ds_grid_multiply_region ds_grid_set_disk " + "ds_grid_add_disk ds_grid_multiply_disk ds_grid_set_grid_region " + "ds_grid_add_grid_region ds_grid_multiply_grid_region ds_grid_get " + "ds_grid_get_sum ds_grid_get_max ds_grid_get_min ds_grid_get_mean " + "ds_grid_get_disk_sum ds_grid_get_disk_min ds_grid_get_disk_max " + "ds_grid_get_disk_mean ds_grid_value_exists ds_grid_value_x " + "ds_grid_value_y ds_grid_value_disk_exists ds_grid_value_disk_x " + "ds_grid_value_disk_y ds_grid_shuffle ds_grid_write ds_grid_read " + "ds_grid_sort ds_grid_set ds_grid_get effect_create_below " + "effect_create_above effect_clear part_type_create part_type_destroy " + "part_type_exists part_type_clear part_type_shape part_type_sprite " + "part_type_size part_type_scale part_type_orientation part_type_life " + "part_type_step part_type_death part_type_speed part_type_direction " + "part_type_gravity part_type_colour1 part_type_colour2 " + "part_type_colour3 part_type_colour_mix part_type_colour_rgb " + "part_type_colour_hsv part_type_color1 part_type_color2 " + "part_type_color3 part_type_color_mix part_type_color_rgb " + "part_type_color_hsv part_type_alpha1 part_type_alpha2 " + "part_type_alpha3 part_type_blend part_system_create " + "part_system_create_layer part_system_destroy part_system_exists " + "part_system_clear part_system_draw_order part_system_depth " + "part_system_position part_system_automatic_update " + "part_system_automatic_draw part_system_update part_system_drawit " + "part_system_get_layer part_system_layer part_particles_create " + "part_particles_create_colour part_particles_create_color " + "part_particles_clear part_particles_count part_emitter_create " + "part_emitter_destroy part_emitter_destroy_all part_emitter_exists " + "part_emitter_clear part_emitter_region part_emitter_burst " + "part_emitter_stream external_call external_define external_free " + "window_handle window_device matrix_get matrix_set " + "matrix_build_identity matrix_build matrix_build_lookat " + "matrix_build_projection_ortho matrix_build_projection_perspective " + "matrix_build_projection_perspective_fov matrix_multiply " + "matrix_transform_vertex matrix_stack_push matrix_stack_pop " + "matrix_stack_multiply matrix_stack_set matrix_stack_clear " + "matrix_stack_top matrix_stack_is_empty browser_input_capture " + "os_get_config os_get_info os_get_language os_get_region " + "os_lock_orientation display_get_dpi_x display_get_dpi_y " + "display_set_gui_size display_set_gui_maximise " + "display_set_gui_maximize device_mouse_dbclick_enable " + "display_set_timing_method display_get_timing_method " + "display_set_sleep_margin display_get_sleep_margin virtual_key_add " + "virtual_key_hide virtual_key_delete virtual_key_show " + "draw_enable_drawevent draw_enable_swf_aa draw_set_swf_aa_level " + "draw_get_swf_aa_level draw_texture_flush draw_flush " + "gpu_set_blendenable gpu_set_ztestenable gpu_set_zfunc " + "gpu_set_zwriteenable gpu_set_lightingenable gpu_set_fog " + "gpu_set_cullmode gpu_set_blendmode gpu_set_blendmode_ext " + "gpu_set_blendmode_ext_sepalpha gpu_set_colorwriteenable " + "gpu_set_colourwriteenable gpu_set_alphatestenable " + "gpu_set_alphatestref gpu_set_alphatestfunc gpu_set_texfilter " + "gpu_set_texfilter_ext gpu_set_texrepeat gpu_set_texrepeat_ext " + "gpu_set_tex_filter gpu_set_tex_filter_ext gpu_set_tex_repeat " + "gpu_set_tex_repeat_ext gpu_set_tex_mip_filter " + "gpu_set_tex_mip_filter_ext gpu_set_tex_mip_bias " + "gpu_set_tex_mip_bias_ext gpu_set_tex_min_mip gpu_set_tex_min_mip_ext " + "gpu_set_tex_max_mip gpu_set_tex_max_mip_ext gpu_set_tex_max_aniso " + "gpu_set_tex_max_aniso_ext gpu_set_tex_mip_enable " + "gpu_set_tex_mip_enable_ext gpu_get_blendenable gpu_get_ztestenable " + "gpu_get_zfunc gpu_get_zwriteenable gpu_get_lightingenable " + "gpu_get_fog gpu_get_cullmode gpu_get_blendmode gpu_get_blendmode_ext " + "gpu_get_blendmode_ext_sepalpha gpu_get_blendmode_src " + "gpu_get_blendmode_dest gpu_get_blendmode_srcalpha " + "gpu_get_blendmode_destalpha gpu_get_colorwriteenable " + "gpu_get_colourwriteenable gpu_get_alphatestenable " + "gpu_get_alphatestref gpu_get_alphatestfunc gpu_get_texfilter " + "gpu_get_texfilter_ext gpu_get_texrepeat gpu_get_texrepeat_ext " + "gpu_get_tex_filter gpu_get_tex_filter_ext gpu_get_tex_repeat " + "gpu_get_tex_repeat_ext gpu_get_tex_mip_filter " + "gpu_get_tex_mip_filter_ext gpu_get_tex_mip_bias " + "gpu_get_tex_mip_bias_ext gpu_get_tex_min_mip gpu_get_tex_min_mip_ext " + "gpu_get_tex_max_mip gpu_get_tex_max_mip_ext gpu_get_tex_max_aniso " + "gpu_get_tex_max_aniso_ext gpu_get_tex_mip_enable " + "gpu_get_tex_mip_enable_ext gpu_push_state gpu_pop_state " + "gpu_get_state gpu_set_state draw_light_define_ambient " + "draw_light_define_direction draw_light_define_point " + "draw_light_enable draw_set_lighting draw_light_get_ambient " + "draw_light_get draw_get_lighting shop_leave_rating url_get_domain " + "url_open url_open_ext url_open_full get_timer achievement_login " + "achievement_logout achievement_post achievement_increment " + "achievement_post_score achievement_available " + "achievement_show_achievements achievement_show_leaderboards " + "achievement_load_friends achievement_load_leaderboard " + "achievement_send_challenge achievement_load_progress " + "achievement_reset achievement_login_status achievement_get_pic " + "achievement_show_challenge_notifications achievement_get_challenges " + "achievement_event achievement_show achievement_get_info " + "cloud_file_save cloud_string_save cloud_synchronise ads_enable " + "ads_disable ads_setup ads_engagement_launch ads_engagement_available " + "ads_engagement_active ads_event ads_event_preload " + "ads_set_reward_callback ads_get_display_height ads_get_display_width " + "ads_move ads_interstitial_available ads_interstitial_display " + "device_get_tilt_x device_get_tilt_y device_get_tilt_z " + "device_is_keypad_open device_mouse_check_button " + "device_mouse_check_button_pressed device_mouse_check_button_released " + "device_mouse_x device_mouse_y device_mouse_raw_x device_mouse_raw_y " + "device_mouse_x_to_gui device_mouse_y_to_gui iap_activate iap_status " + "iap_enumerate_products iap_restore_all iap_acquire iap_consume " + "iap_product_details iap_purchase_details facebook_init " + "facebook_login facebook_status facebook_graph_request " + "facebook_dialog facebook_logout facebook_launch_offerwall " + "facebook_post_message facebook_send_invite facebook_user_id " + "facebook_accesstoken facebook_check_permission " + "facebook_request_read_permissions " + "facebook_request_publish_permissions gamepad_is_supported " + "gamepad_get_device_count gamepad_is_connected " + "gamepad_get_description gamepad_get_button_threshold " + "gamepad_set_button_threshold gamepad_get_axis_deadzone " + "gamepad_set_axis_deadzone gamepad_button_count gamepad_button_check " + "gamepad_button_check_pressed gamepad_button_check_released " + "gamepad_button_value gamepad_axis_count gamepad_axis_value " + "gamepad_set_vibration gamepad_set_colour gamepad_set_color " + "os_is_paused window_has_focus code_is_compiled http_get " + "http_get_file http_post_string http_request json_encode json_decode " + "zip_unzip load_csv base64_encode base64_decode md5_string_unicode " + "md5_string_utf8 md5_file os_is_network_connected sha1_string_unicode " + "sha1_string_utf8 sha1_file os_powersave_enable analytics_event " + "analytics_event_ext win8_livetile_tile_notification " + "win8_livetile_tile_clear win8_livetile_badge_notification " + "win8_livetile_badge_clear win8_livetile_queue_enable " + "win8_secondarytile_pin win8_secondarytile_badge_notification " + "win8_secondarytile_delete win8_livetile_notification_begin " + "win8_livetile_notification_secondary_begin " + "win8_livetile_notification_expiry win8_livetile_notification_tag " + "win8_livetile_notification_text_add " + "win8_livetile_notification_image_add win8_livetile_notification_end " + "win8_appbar_enable win8_appbar_add_element " + "win8_appbar_remove_element win8_settingscharm_add_entry " + "win8_settingscharm_add_html_entry win8_settingscharm_add_xaml_entry " + "win8_settingscharm_set_xaml_property " + "win8_settingscharm_get_xaml_property win8_settingscharm_remove_entry " + "win8_share_image win8_share_screenshot win8_share_file " + "win8_share_url win8_share_text win8_search_enable " + "win8_search_disable win8_search_add_suggestions " + "win8_device_touchscreen_available win8_license_initialize_sandbox " + "win8_license_trial_version winphone_license_trial_version " + "winphone_tile_title winphone_tile_count winphone_tile_back_title " + "winphone_tile_back_content winphone_tile_back_content_wide " + "winphone_tile_front_image winphone_tile_front_image_small " + "winphone_tile_front_image_wide winphone_tile_back_image " + "winphone_tile_back_image_wide winphone_tile_background_colour " + "winphone_tile_background_color winphone_tile_icon_image " + "winphone_tile_small_icon_image winphone_tile_wide_content " + "winphone_tile_cycle_images winphone_tile_small_background_image " + "physics_world_create physics_world_gravity " + "physics_world_update_speed physics_world_update_iterations " + "physics_world_draw_debug physics_pause_enable physics_fixture_create " + "physics_fixture_set_kinematic physics_fixture_set_density " + "physics_fixture_set_awake physics_fixture_set_restitution " + "physics_fixture_set_friction physics_fixture_set_collision_group " + "physics_fixture_set_sensor physics_fixture_set_linear_damping " + "physics_fixture_set_angular_damping physics_fixture_set_circle_shape " + "physics_fixture_set_box_shape physics_fixture_set_edge_shape " + "physics_fixture_set_polygon_shape physics_fixture_set_chain_shape " + "physics_fixture_add_point physics_fixture_bind " + "physics_fixture_bind_ext physics_fixture_delete physics_apply_force " + "physics_apply_impulse physics_apply_angular_impulse " + "physics_apply_local_force physics_apply_local_impulse " + "physics_apply_torque physics_mass_properties physics_draw_debug " + "physics_test_overlap physics_remove_fixture physics_set_friction " + "physics_set_density physics_set_restitution physics_get_friction " + "physics_get_density physics_get_restitution " + "physics_joint_distance_create physics_joint_rope_create " + "physics_joint_revolute_create physics_joint_prismatic_create " + "physics_joint_pulley_create physics_joint_wheel_create " + "physics_joint_weld_create physics_joint_friction_create " + "physics_joint_gear_create physics_joint_enable_motor " + "physics_joint_get_value physics_joint_set_value physics_joint_delete " + "physics_particle_create physics_particle_delete " + "physics_particle_delete_region_circle " + "physics_particle_delete_region_box " + "physics_particle_delete_region_poly physics_particle_set_flags " + "physics_particle_set_category_flags physics_particle_draw " + "physics_particle_draw_ext physics_particle_count " + "physics_particle_get_data physics_particle_get_data_particle " + "physics_particle_group_begin physics_particle_group_circle " + "physics_particle_group_box physics_particle_group_polygon " + "physics_particle_group_add_point physics_particle_group_end " + "physics_particle_group_join physics_particle_group_delete " + "physics_particle_group_count physics_particle_group_get_data " + "physics_particle_group_get_mass physics_particle_group_get_inertia " + "physics_particle_group_get_centre_x " + "physics_particle_group_get_centre_y physics_particle_group_get_vel_x " + "physics_particle_group_get_vel_y physics_particle_group_get_ang_vel " + "physics_particle_group_get_x physics_particle_group_get_y " + "physics_particle_group_get_angle physics_particle_set_group_flags " + "physics_particle_get_group_flags physics_particle_get_max_count " + "physics_particle_get_radius physics_particle_get_density " + "physics_particle_get_damping physics_particle_get_gravity_scale " + "physics_particle_set_max_count physics_particle_set_radius " + "physics_particle_set_density physics_particle_set_damping " + "physics_particle_set_gravity_scale network_create_socket " + "network_create_socket_ext network_create_server " + "network_create_server_raw network_connect network_connect_raw " + "network_send_packet network_send_raw network_send_broadcast " + "network_send_udp network_send_udp_raw network_set_timeout " + "network_set_config network_resolve network_destroy buffer_create " + "buffer_write buffer_read buffer_seek buffer_get_surface " + "buffer_set_surface buffer_delete buffer_exists buffer_get_type " + "buffer_get_alignment buffer_poke buffer_peek buffer_save " + "buffer_save_ext buffer_load buffer_load_ext buffer_load_partial " + "buffer_copy buffer_fill buffer_get_size buffer_tell buffer_resize " + "buffer_md5 buffer_sha1 buffer_base64_encode buffer_base64_decode " + "buffer_base64_decode_ext buffer_sizeof buffer_get_address " + "buffer_create_from_vertex_buffer " + "buffer_create_from_vertex_buffer_ext buffer_copy_from_vertex_buffer " + "buffer_async_group_begin buffer_async_group_option " + "buffer_async_group_end buffer_load_async buffer_save_async " + "gml_release_mode gml_pragma steam_activate_overlay " + "steam_is_overlay_enabled steam_is_overlay_activated " + "steam_get_persona_name steam_initialised " + "steam_is_cloud_enabled_for_app steam_is_cloud_enabled_for_account " + "steam_file_persisted steam_get_quota_total steam_get_quota_free " + "steam_file_write steam_file_write_file steam_file_read " + "steam_file_delete steam_file_exists steam_file_size steam_file_share " + "steam_is_screenshot_requested steam_send_screenshot " + "steam_is_user_logged_on steam_get_user_steam_id steam_user_owns_dlc " + "steam_user_installed_dlc steam_set_achievement steam_get_achievement " + "steam_clear_achievement steam_set_stat_int steam_set_stat_float " + "steam_set_stat_avg_rate steam_get_stat_int steam_get_stat_float " + "steam_get_stat_avg_rate steam_reset_all_stats " + "steam_reset_all_stats_achievements steam_stats_ready " + "steam_create_leaderboard steam_upload_score steam_upload_score_ext " + "steam_download_scores_around_user steam_download_scores " + "steam_download_friends_scores steam_upload_score_buffer " + "steam_upload_score_buffer_ext steam_current_game_language " + "steam_available_languages steam_activate_overlay_browser " + "steam_activate_overlay_user steam_activate_overlay_store " + "steam_get_user_persona_name steam_get_app_id " + "steam_get_user_account_id steam_ugc_download steam_ugc_create_item " + "steam_ugc_start_item_update steam_ugc_set_item_title " + "steam_ugc_set_item_description steam_ugc_set_item_visibility " + "steam_ugc_set_item_tags steam_ugc_set_item_content " + "steam_ugc_set_item_preview steam_ugc_submit_item_update " + "steam_ugc_get_item_update_progress steam_ugc_subscribe_item " + "steam_ugc_unsubscribe_item steam_ugc_num_subscribed_items " + "steam_ugc_get_subscribed_items steam_ugc_get_item_install_info " + "steam_ugc_get_item_update_info steam_ugc_request_item_details " + "steam_ugc_create_query_user steam_ugc_create_query_user_ex " + "steam_ugc_create_query_all steam_ugc_create_query_all_ex " + "steam_ugc_query_set_cloud_filename_filter " + "steam_ugc_query_set_match_any_tag steam_ugc_query_set_search_text " + "steam_ugc_query_set_ranked_by_trend_days " + "steam_ugc_query_add_required_tag steam_ugc_query_add_excluded_tag " + "steam_ugc_query_set_return_long_description " + "steam_ugc_query_set_return_total_only " + "steam_ugc_query_set_allow_cached_response steam_ugc_send_query " + "shader_set shader_get_name shader_reset shader_current " + "shader_is_compiled shader_get_sampler_index shader_get_uniform " + "shader_set_uniform_i shader_set_uniform_i_array shader_set_uniform_f " + "shader_set_uniform_f_array shader_set_uniform_matrix " + "shader_set_uniform_matrix_array shader_enable_corner_id " + "texture_set_stage texture_get_texel_width texture_get_texel_height " + "shaders_are_supported vertex_format_begin vertex_format_end " + "vertex_format_delete vertex_format_add_position " + "vertex_format_add_position_3d vertex_format_add_colour " + "vertex_format_add_color vertex_format_add_normal " + "vertex_format_add_texcoord vertex_format_add_textcoord " + "vertex_format_add_custom vertex_create_buffer " + "vertex_create_buffer_ext vertex_delete_buffer vertex_begin " + "vertex_end vertex_position vertex_position_3d vertex_colour " + "vertex_color vertex_argb vertex_texcoord vertex_normal vertex_float1 " + "vertex_float2 vertex_float3 vertex_float4 vertex_ubyte4 " + "vertex_submit vertex_freeze vertex_get_number vertex_get_buffer_size " + "vertex_create_buffer_from_buffer " + "vertex_create_buffer_from_buffer_ext push_local_notification " + "push_get_first_local_notification push_get_next_local_notification " + "push_cancel_local_notification skeleton_animation_set " + "skeleton_animation_get skeleton_animation_mix " + "skeleton_animation_set_ext skeleton_animation_get_ext " + "skeleton_animation_get_duration skeleton_animation_get_frames " + "skeleton_animation_clear skeleton_skin_set skeleton_skin_get " + "skeleton_attachment_set skeleton_attachment_get " + "skeleton_attachment_create skeleton_collision_draw_set " + "skeleton_bone_data_get skeleton_bone_data_set " + "skeleton_bone_state_get skeleton_bone_state_set skeleton_get_minmax " + "skeleton_get_num_bounds skeleton_get_bounds " + "skeleton_animation_get_frame skeleton_animation_set_frame " + "draw_skeleton draw_skeleton_time draw_skeleton_instance " + "draw_skeleton_collision skeleton_animation_list skeleton_skin_list " + "skeleton_slot_data layer_get_id layer_get_id_at_depth " + "layer_get_depth layer_create layer_destroy layer_destroy_instances " + "layer_add_instance layer_has_instance layer_set_visible " + "layer_get_visible layer_exists layer_x layer_y layer_get_x " + "layer_get_y layer_hspeed layer_vspeed layer_get_hspeed " + "layer_get_vspeed layer_script_begin layer_script_end layer_shader " + "layer_get_script_begin layer_get_script_end layer_get_shader " + "layer_set_target_room layer_get_target_room layer_reset_target_room " + "layer_get_all layer_get_all_elements layer_get_name layer_depth " + "layer_get_element_layer layer_get_element_type layer_element_move " + "layer_force_draw_depth layer_is_draw_depth_forced " + "layer_get_forced_depth layer_background_get_id " + "layer_background_exists layer_background_create " + "layer_background_destroy layer_background_visible " + "layer_background_change layer_background_sprite " + "layer_background_htiled layer_background_vtiled " + "layer_background_stretch layer_background_yscale " + "layer_background_xscale layer_background_blend " + "layer_background_alpha layer_background_index layer_background_speed " + "layer_background_get_visible layer_background_get_sprite " + "layer_background_get_htiled layer_background_get_vtiled " + "layer_background_get_stretch layer_background_get_yscale " + "layer_background_get_xscale layer_background_get_blend " + "layer_background_get_alpha layer_background_get_index " + "layer_background_get_speed layer_sprite_get_id layer_sprite_exists " + "layer_sprite_create layer_sprite_destroy layer_sprite_change " + "layer_sprite_index layer_sprite_speed layer_sprite_xscale " + "layer_sprite_yscale layer_sprite_angle layer_sprite_blend " + "layer_sprite_alpha layer_sprite_x layer_sprite_y " + "layer_sprite_get_sprite layer_sprite_get_index " + "layer_sprite_get_speed layer_sprite_get_xscale " + "layer_sprite_get_yscale layer_sprite_get_angle " + "layer_sprite_get_blend layer_sprite_get_alpha layer_sprite_get_x " + "layer_sprite_get_y layer_tilemap_get_id layer_tilemap_exists " + "layer_tilemap_create layer_tilemap_destroy tilemap_tileset tilemap_x " + "tilemap_y tilemap_set tilemap_set_at_pixel tilemap_get_tileset " + "tilemap_get_tile_width tilemap_get_tile_height tilemap_get_width " + "tilemap_get_height tilemap_get_x tilemap_get_y tilemap_get " + "tilemap_get_at_pixel tilemap_get_cell_x_at_pixel " + "tilemap_get_cell_y_at_pixel tilemap_clear draw_tilemap draw_tile " + "tilemap_set_global_mask tilemap_get_global_mask tilemap_set_mask " + "tilemap_get_mask tilemap_get_frame tile_set_empty tile_set_index " + "tile_set_flip tile_set_mirror tile_set_rotate tile_get_empty " + "tile_get_index tile_get_flip tile_get_mirror tile_get_rotate " + "layer_tile_exists layer_tile_create layer_tile_destroy " + "layer_tile_change layer_tile_xscale layer_tile_yscale " + "layer_tile_blend layer_tile_alpha layer_tile_x layer_tile_y " + "layer_tile_region layer_tile_visible layer_tile_get_sprite " + "layer_tile_get_xscale layer_tile_get_yscale layer_tile_get_blend " + "layer_tile_get_alpha layer_tile_get_x layer_tile_get_y " + "layer_tile_get_region layer_tile_get_visible " + "layer_instance_get_instance instance_activate_layer " + "instance_deactivate_layer camera_create camera_create_view " + "camera_destroy camera_apply camera_get_active camera_get_default " + "camera_set_default camera_set_view_mat camera_set_proj_mat " + "camera_set_update_script camera_set_begin_script " + "camera_set_end_script camera_set_view_pos camera_set_view_size " + "camera_set_view_speed camera_set_view_border camera_set_view_angle " + "camera_set_view_target camera_get_view_mat camera_get_proj_mat " + "camera_get_update_script camera_get_begin_script " + "camera_get_end_script camera_get_view_x camera_get_view_y " + "camera_get_view_width camera_get_view_height camera_get_view_speed_x " + "camera_get_view_speed_y camera_get_view_border_x " + "camera_get_view_border_y camera_get_view_angle " + "camera_get_view_target view_get_camera view_get_visible " + "view_get_xport view_get_yport view_get_wport view_get_hport " + "view_get_surface_id view_set_camera view_set_visible view_set_xport " + "view_set_yport view_set_wport view_set_hport view_set_surface_id " + "gesture_drag_time gesture_drag_distance gesture_flick_speed " + "gesture_double_tap_time gesture_double_tap_distance " + "gesture_pinch_distance gesture_pinch_angle_towards " + "gesture_pinch_angle_away gesture_rotate_time gesture_rotate_angle " + "gesture_tap_count gesture_get_drag_time gesture_get_drag_distance " + "gesture_get_flick_speed gesture_get_double_tap_time " + "gesture_get_double_tap_distance gesture_get_pinch_distance " + "gesture_get_pinch_angle_towards gesture_get_pinch_angle_away " + "gesture_get_rotate_time gesture_get_rotate_angle " + "gesture_get_tap_count keyboard_virtual_show keyboard_virtual_hide " + "keyboard_virtual_status keyboard_virtual_height", literal: "self other all noone global local undefined pointer_invalid " + "pointer_null path_action_stop path_action_restart " + "path_action_continue path_action_reverse true false pi GM_build_date " + "GM_version GM_runtime_version timezone_local timezone_utc " + "gamespeed_fps gamespeed_microseconds ev_create ev_destroy ev_step " + "ev_alarm ev_keyboard ev_mouse ev_collision ev_other ev_draw " + "ev_draw_begin ev_draw_end ev_draw_pre ev_draw_post ev_keypress " + "ev_keyrelease ev_trigger ev_left_button ev_right_button " + "ev_middle_button ev_no_button ev_left_press ev_right_press " + "ev_middle_press ev_left_release ev_right_release ev_middle_release " + "ev_mouse_enter ev_mouse_leave ev_mouse_wheel_up ev_mouse_wheel_down " + "ev_global_left_button ev_global_right_button ev_global_middle_button " + "ev_global_left_press ev_global_right_press ev_global_middle_press " + "ev_global_left_release ev_global_right_release " + "ev_global_middle_release ev_joystick1_left ev_joystick1_right " + "ev_joystick1_up ev_joystick1_down ev_joystick1_button1 " + "ev_joystick1_button2 ev_joystick1_button3 ev_joystick1_button4 " + "ev_joystick1_button5 ev_joystick1_button6 ev_joystick1_button7 " + "ev_joystick1_button8 ev_joystick2_left ev_joystick2_right " + "ev_joystick2_up ev_joystick2_down ev_joystick2_button1 " + "ev_joystick2_button2 ev_joystick2_button3 ev_joystick2_button4 " + "ev_joystick2_button5 ev_joystick2_button6 ev_joystick2_button7 " + "ev_joystick2_button8 ev_outside ev_boundary ev_game_start " + "ev_game_end ev_room_start ev_room_end ev_no_more_lives " + "ev_animation_end ev_end_of_path ev_no_more_health ev_close_button " + "ev_user0 ev_user1 ev_user2 ev_user3 ev_user4 ev_user5 ev_user6 " + "ev_user7 ev_user8 ev_user9 ev_user10 ev_user11 ev_user12 ev_user13 " + "ev_user14 ev_user15 ev_step_normal ev_step_begin ev_step_end ev_gui " + "ev_gui_begin ev_gui_end ev_cleanup ev_gesture ev_gesture_tap " + "ev_gesture_double_tap ev_gesture_drag_start ev_gesture_dragging " + "ev_gesture_drag_end ev_gesture_flick ev_gesture_pinch_start " + "ev_gesture_pinch_in ev_gesture_pinch_out ev_gesture_pinch_end " + "ev_gesture_rotate_start ev_gesture_rotating ev_gesture_rotate_end " + "ev_global_gesture_tap ev_global_gesture_double_tap " + "ev_global_gesture_drag_start ev_global_gesture_dragging " + "ev_global_gesture_drag_end ev_global_gesture_flick " + "ev_global_gesture_pinch_start ev_global_gesture_pinch_in " + "ev_global_gesture_pinch_out ev_global_gesture_pinch_end " + "ev_global_gesture_rotate_start ev_global_gesture_rotating " + "ev_global_gesture_rotate_end vk_nokey vk_anykey vk_enter vk_return " + "vk_shift vk_control vk_alt vk_escape vk_space vk_backspace vk_tab " + "vk_pause vk_printscreen vk_left vk_right vk_up vk_down vk_home " + "vk_end vk_delete vk_insert vk_pageup vk_pagedown vk_f1 vk_f2 vk_f3 " + "vk_f4 vk_f5 vk_f6 vk_f7 vk_f8 vk_f9 vk_f10 vk_f11 vk_f12 vk_numpad0 " + "vk_numpad1 vk_numpad2 vk_numpad3 vk_numpad4 vk_numpad5 vk_numpad6 " + "vk_numpad7 vk_numpad8 vk_numpad9 vk_divide vk_multiply vk_subtract " + "vk_add vk_decimal vk_lshift vk_lcontrol vk_lalt vk_rshift " + "vk_rcontrol vk_ralt mb_any mb_none mb_left mb_right mb_middle " + "c_aqua c_black c_blue c_dkgray c_fuchsia c_gray c_green c_lime " + "c_ltgray c_maroon c_navy c_olive c_purple c_red c_silver c_teal " + "c_white c_yellow c_orange fa_left fa_center fa_right fa_top " + "fa_middle fa_bottom pr_pointlist pr_linelist pr_linestrip " + "pr_trianglelist pr_trianglestrip pr_trianglefan bm_complex bm_normal " + "bm_add bm_max bm_subtract bm_zero bm_one bm_src_colour " + "bm_inv_src_colour bm_src_color bm_inv_src_color bm_src_alpha " + "bm_inv_src_alpha bm_dest_alpha bm_inv_dest_alpha bm_dest_colour " + "bm_inv_dest_colour bm_dest_color bm_inv_dest_color bm_src_alpha_sat " + "tf_point tf_linear tf_anisotropic mip_off mip_on mip_markedonly " + "audio_falloff_none audio_falloff_inverse_distance " + "audio_falloff_inverse_distance_clamped audio_falloff_linear_distance " + "audio_falloff_linear_distance_clamped " + "audio_falloff_exponent_distance " + "audio_falloff_exponent_distance_clamped audio_old_system " + "audio_new_system audio_mono audio_stereo audio_3d cr_default cr_none " + "cr_arrow cr_cross cr_beam cr_size_nesw cr_size_ns cr_size_nwse " + "cr_size_we cr_uparrow cr_hourglass cr_drag cr_appstart cr_handpoint " + "cr_size_all spritespeed_framespersecond " + "spritespeed_framespergameframe asset_object asset_unknown " + "asset_sprite asset_sound asset_room asset_path asset_script " + "asset_font asset_timeline asset_tiles asset_shader fa_readonly " + "fa_hidden fa_sysfile fa_volumeid fa_directory fa_archive " + "ds_type_map ds_type_list ds_type_stack ds_type_queue ds_type_grid " + "ds_type_priority ef_explosion ef_ring ef_ellipse ef_firework " + "ef_smoke ef_smokeup ef_star ef_spark ef_flare ef_cloud ef_rain " + "ef_snow pt_shape_pixel pt_shape_disk pt_shape_square pt_shape_line " + "pt_shape_star pt_shape_circle pt_shape_ring pt_shape_sphere " + "pt_shape_flare pt_shape_spark pt_shape_explosion pt_shape_cloud " + "pt_shape_smoke pt_shape_snow ps_distr_linear ps_distr_gaussian " + "ps_distr_invgaussian ps_shape_rectangle ps_shape_ellipse " + "ps_shape_diamond ps_shape_line ty_real ty_string dll_cdecl " + "dll_stdcall matrix_view matrix_projection matrix_world os_win32 " + "os_windows os_macosx os_ios os_android os_symbian os_linux " + "os_unknown os_winphone os_tizen os_win8native " + "os_wiiu os_3ds os_psvita os_bb10 os_ps4 os_xboxone " + "os_ps3 os_xbox360 os_uwp os_tvos os_switch " + "browser_not_a_browser browser_unknown browser_ie browser_firefox " + "browser_chrome browser_safari browser_safari_mobile browser_opera " + "browser_tizen browser_edge browser_windows_store browser_ie_mobile " + "device_ios_unknown device_ios_iphone device_ios_iphone_retina " + "device_ios_ipad device_ios_ipad_retina device_ios_iphone5 " + "device_ios_iphone6 device_ios_iphone6plus device_emulator " + "device_tablet display_landscape display_landscape_flipped " + "display_portrait display_portrait_flipped tm_sleep tm_countvsyncs " + "of_challenge_win of_challen ge_lose of_challenge_tie " + "leaderboard_type_number leaderboard_type_time_mins_secs " + "cmpfunc_never cmpfunc_less cmpfunc_equal cmpfunc_lessequal " + "cmpfunc_greater cmpfunc_notequal cmpfunc_greaterequal cmpfunc_always " + "cull_noculling cull_clockwise cull_counterclockwise lighttype_dir " + "lighttype_point iap_ev_storeload iap_ev_product iap_ev_purchase " + "iap_ev_consume iap_ev_restore iap_storeload_ok iap_storeload_failed " + "iap_status_uninitialised iap_status_unavailable iap_status_loading " + "iap_status_available iap_status_processing iap_status_restoring " + "iap_failed iap_unavailable iap_available iap_purchased iap_canceled " + "iap_refunded fb_login_default fb_login_fallback_to_webview " + "fb_login_no_fallback_to_webview fb_login_forcing_webview " + "fb_login_use_system_account fb_login_forcing_safari " + "phy_joint_anchor_1_x phy_joint_anchor_1_y phy_joint_anchor_2_x " + "phy_joint_anchor_2_y phy_joint_reaction_force_x " + "phy_joint_reaction_force_y phy_joint_reaction_torque " + "phy_joint_motor_speed phy_joint_angle phy_joint_motor_torque " + "phy_joint_max_motor_torque phy_joint_translation phy_joint_speed " + "phy_joint_motor_force phy_joint_max_motor_force phy_joint_length_1 " + "phy_joint_length_2 phy_joint_damping_ratio phy_joint_frequency " + "phy_joint_lower_angle_limit phy_joint_upper_angle_limit " + "phy_joint_angle_limits phy_joint_max_length phy_joint_max_torque " + "phy_joint_max_force phy_debug_render_aabb " + "phy_debug_render_collision_pairs phy_debug_render_coms " + "phy_debug_render_core_shapes phy_debug_render_joints " + "phy_debug_render_obb phy_debug_render_shapes " + "phy_particle_flag_water phy_particle_flag_zombie " + "phy_particle_flag_wall phy_particle_flag_spring " + "phy_particle_flag_elastic phy_particle_flag_viscous " + "phy_particle_flag_powder phy_particle_flag_tensile " + "phy_particle_flag_colourmixing phy_particle_flag_colormixing " + "phy_particle_group_flag_solid phy_particle_group_flag_rigid " + "phy_particle_data_flag_typeflags phy_particle_data_flag_position " + "phy_particle_data_flag_velocity phy_particle_data_flag_colour " + "phy_particle_data_flag_color phy_particle_data_flag_category " + "achievement_our_info achievement_friends_info " + "achievement_leaderboard_info achievement_achievement_info " + "achievement_filter_all_players achievement_filter_friends_only " + "achievement_filter_favorites_only " + "achievement_type_achievement_challenge " + "achievement_type_score_challenge achievement_pic_loaded " + "achievement_show_ui achievement_show_profile " + "achievement_show_leaderboard achievement_show_achievement " + "achievement_show_bank achievement_show_friend_picker " + "achievement_show_purchase_prompt network_socket_tcp " + "network_socket_udp network_socket_bluetooth network_type_connect " + "network_type_disconnect network_type_data " + "network_type_non_blocking_connect network_config_connect_timeout " + "network_config_use_non_blocking_socket " + "network_config_enable_reliable_udp " + "network_config_disable_reliable_udp buffer_fixed buffer_grow " + "buffer_wrap buffer_fast buffer_vbuffer buffer_network buffer_u8 " + "buffer_s8 buffer_u16 buffer_s16 buffer_u32 buffer_s32 buffer_u64 " + "buffer_f16 buffer_f32 buffer_f64 buffer_bool buffer_text " + "buffer_string buffer_surface_copy buffer_seek_start " + "buffer_seek_relative buffer_seek_end " + "buffer_generalerror buffer_outofspace buffer_outofbounds " + "buffer_invalidtype text_type button_type input_type ANSI_CHARSET " + "DEFAULT_CHARSET EASTEUROPE_CHARSET RUSSIAN_CHARSET SYMBOL_CHARSET " + "SHIFTJIS_CHARSET HANGEUL_CHARSET GB2312_CHARSET CHINESEBIG5_CHARSET " + "JOHAB_CHARSET HEBREW_CHARSET ARABIC_CHARSET GREEK_CHARSET " + "TURKISH_CHARSET VIETNAMESE_CHARSET THAI_CHARSET MAC_CHARSET " + "BALTIC_CHARSET OEM_CHARSET gp_face1 gp_face2 gp_face3 gp_face4 " + "gp_shoulderl gp_shoulderr gp_shoulderlb gp_shoulderrb gp_select " + "gp_start gp_stickl gp_stickr gp_padu gp_padd gp_padl gp_padr " + "gp_axislh gp_axislv gp_axisrh gp_axisrv ov_friends ov_community " + "ov_players ov_settings ov_gamegroup ov_achievements lb_sort_none " + "lb_sort_ascending lb_sort_descending lb_disp_none lb_disp_numeric " + "lb_disp_time_sec lb_disp_time_ms ugc_result_success " + "ugc_filetype_community ugc_filetype_microtrans ugc_visibility_public " + "ugc_visibility_friends_only ugc_visibility_private " + "ugc_query_RankedByVote ugc_query_RankedByPublicationDate " + "ugc_query_AcceptedForGameRankedByAcceptanceDate " + "ugc_query_RankedByTrend " + "ugc_query_FavoritedByFriendsRankedByPublicationDate " + "ugc_query_CreatedByFriendsRankedByPublicationDate " + "ugc_query_RankedByNumTimesReported " + "ugc_query_CreatedByFollowedUsersRankedByPublicationDate " + "ugc_query_NotYetRated ugc_query_RankedByTotalVotesAsc " + "ugc_query_RankedByVotesUp ugc_query_RankedByTextSearch " + "ugc_sortorder_CreationOrderDesc ugc_sortorder_CreationOrderAsc " + "ugc_sortorder_TitleAsc ugc_sortorder_LastUpdatedDesc " + "ugc_sortorder_SubscriptionDateDesc ugc_sortorder_VoteScoreDesc " + "ugc_sortorder_ForModeration ugc_list_Published ugc_list_VotedOn " + "ugc_list_VotedUp ugc_list_VotedDown ugc_list_WillVoteLater " + "ugc_list_Favorited ugc_list_Subscribed ugc_list_UsedOrPlayed " + "ugc_list_Followed ugc_match_Items ugc_match_Items_Mtx " + "ugc_match_Items_ReadyToUse ugc_match_Collections ugc_match_Artwork " + "ugc_match_Videos ugc_match_Screenshots ugc_match_AllGuides " + "ugc_match_WebGuides ugc_match_IntegratedGuides " + "ugc_match_UsableInGame ugc_match_ControllerBindings " + "vertex_usage_position vertex_usage_colour vertex_usage_color " + "vertex_usage_normal vertex_usage_texcoord vertex_usage_textcoord " + "vertex_usage_blendweight vertex_usage_blendindices " + "vertex_usage_psize vertex_usage_tangent vertex_usage_binormal " + "vertex_usage_fog vertex_usage_depth vertex_usage_sample " + "vertex_type_float1 vertex_type_float2 vertex_type_float3 " + "vertex_type_float4 vertex_type_colour vertex_type_color " + "vertex_type_ubyte4 layerelementtype_undefined " + "layerelementtype_background layerelementtype_instance " + "layerelementtype_oldtilemap layerelementtype_sprite " + "layerelementtype_tilemap layerelementtype_particlesystem " + "layerelementtype_tile tile_rotate tile_flip tile_mirror " + "tile_index_mask kbv_type_default kbv_type_ascii kbv_type_url " + "kbv_type_email kbv_type_numbers kbv_type_phone kbv_type_phone_name " + "kbv_returnkey_default kbv_returnkey_go kbv_returnkey_google " + "kbv_returnkey_join kbv_returnkey_next kbv_returnkey_route " + "kbv_returnkey_search kbv_returnkey_send kbv_returnkey_yahoo " + "kbv_returnkey_done kbv_returnkey_continue kbv_returnkey_emergency " + "kbv_autocapitalize_none kbv_autocapitalize_words " + "kbv_autocapitalize_sentences kbv_autocapitalize_characters", symbol: "argument_relative argument argument0 argument1 argument2 " + "argument3 argument4 argument5 argument6 argument7 argument8 " + "argument9 argument10 argument11 argument12 argument13 argument14 " + "argument15 argument_count x y xprevious yprevious xstart ystart " + "hspeed vspeed direction speed friction gravity gravity_direction " + "path_index path_position path_positionprevious path_speed " + "path_scale path_orientation path_endaction object_index id solid " + "persistent mask_index instance_count instance_id room_speed fps " + "fps_real current_time current_year current_month current_day " + "current_weekday current_hour current_minute current_second alarm " + "timeline_index timeline_position timeline_speed timeline_running " + "timeline_loop room room_first room_last room_width room_height " + "room_caption room_persistent score lives health show_score " + "show_lives show_health caption_score caption_lives caption_health " + "event_type event_number event_object event_action " + "application_surface gamemaker_pro gamemaker_registered " + "gamemaker_version error_occurred error_last debug_mode " + "keyboard_key keyboard_lastkey keyboard_lastchar keyboard_string " + "mouse_x mouse_y mouse_button mouse_lastbutton cursor_sprite " + "visible sprite_index sprite_width sprite_height sprite_xoffset " + "sprite_yoffset image_number image_index image_speed depth " + "image_xscale image_yscale image_angle image_alpha image_blend " + "bbox_left bbox_right bbox_top bbox_bottom layer background_colour " + "background_showcolour background_color background_showcolor " + "view_enabled view_current view_visible view_xview view_yview " + "view_wview view_hview view_xport view_yport view_wport view_hport " + "view_angle view_hborder view_vborder view_hspeed view_vspeed " + "view_object view_surface_id view_camera game_id game_display_name " + "game_project_name game_save_id working_directory temp_directory " + "program_directory browser_width browser_height os_type os_device " + "os_browser os_version display_aa async_load delta_time " + "webgl_enabled event_data iap_data phy_rotation phy_position_x " + "phy_position_y phy_angular_velocity phy_linear_velocity_x " + "phy_linear_velocity_y phy_speed_x phy_speed_y phy_speed " + "phy_angular_damping phy_linear_damping phy_bullet " + "phy_fixed_rotation phy_active phy_mass phy_inertia phy_com_x " + "phy_com_y phy_dynamic phy_kinematic phy_sleeping " + "phy_collision_points phy_collision_x phy_collision_y " + "phy_col_normal_x phy_col_normal_y phy_position_xprevious " + "phy_position_yprevious", }; return { aliases: ["gml", "GML"], case_insensitive: false, // language is case-insensitive keywords: GML_KEYWORDS, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, ], }; }, }, { name: "go", /* Language: Go Author: Stephan Kountso aka StepLg Contributors: Evgeny Stepanischev Description: Google go language (golang). For info about language see http://golang.org/ Category: system */ create: function (hljs) { var GO_KEYWORDS = { keyword: "break default func interface select case map struct chan else goto package switch " + "const fallthrough if range type continue for import return var go defer " + "bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 " + "uint16 uint32 uint64 int uint uintptr rune", literal: "true false iota nil", built_in: "append cap close complex copy imag len make new panic print println real recover delete", }; return { aliases: ["golang"], keywords: GO_KEYWORDS, illegal: " Description: a lightweight dynamic language for the JVM, see http://golo-lang.org/ */ create: function (hljs) { return { keywords: { keyword: "println readln print import module function local return let var " + "while for foreach times in case when match with break continue " + "augment augmentation each find filter reduce " + "if then else otherwise try catch finally raise throw orIfNull " + "DynamicObject|10 DynamicVariable struct Observable map set vector list array", literal: "true false null", }, contains: [ hljs.HASH_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, { className: "meta", begin: "@[A-Za-z]+", }, ], }; }, }, { name: "gradle", /* Language: Gradle Author: Damian Mee Website: http://meeDamian.com */ create: function (hljs) { return { case_insensitive: true, keywords: { keyword: "task project allprojects subprojects artifacts buildscript configurations " + "dependencies repositories sourceSets description delete from into include " + "exclude source classpath destinationDir includes options sourceCompatibility " + "targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant " + "def abstract break case catch continue default do else extends final finally " + "for if implements instanceof native new private protected public return static " + "switch synchronized throw throws transient try volatile while strictfp package " + "import false null super this true antlrtask checkstyle codenarc copy boolean " + "byte char class double float int interface long short void compile runTime " + "file fileTree abs any append asList asWritable call collect compareTo count " + "div dump each eachByte eachFile eachLine every find findAll flatten getAt " + "getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods " + "isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter " + "newReader newWriter next plus pop power previous print println push putAt read " + "readBytes readLines reverse reverseEach round size sort splitEachLine step subMap " + "times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader " + "withStream withWriter withWriterAppend write writeLine", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, hljs.REGEXP_MODE, ], }; }, }, { name: "groovy", /* Language: Groovy Author: Guillaume Laforge Website: http://glaforge.appspot.com Description: Groovy programming language implementation inspired from Vsevolod's Java mode */ create: function (hljs) { return { keywords: { literal: "true false null", keyword: "byte short char int long boolean float double void " + // groovy specific keywords "def as in assert trait " + // common keywords with Java "super this abstract static volatile transient public private protected synchronized final " + "class interface enum if else for while switch case break default continue " + "throw throws try catch finally implements extends new import package return instanceof", }, contains: [ hljs.COMMENT("/\\*\\*", "\\*/", { relevance: 0, contains: [ { // eat up @'s in emails to prevent them to be recognized as doctags begin: /\w+@/, relevance: 0, }, { className: "doctag", begin: "@[A-Za-z]+", }, ], }), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "string", begin: '"""', end: '"""', }, { className: "string", begin: "'''", end: "'''", }, { className: "string", begin: "\\$/", end: "/\\$", relevance: 10, }, hljs.APOS_STRING_MODE, { className: "regexp", begin: /~?\/[^\/\n]+\//, contains: [hljs.BACKSLASH_ESCAPE], }, hljs.QUOTE_STRING_MODE, { className: "meta", begin: "^#!/usr/bin/env", end: "$", illegal: "\n", }, hljs.BINARY_NUMBER_MODE, { className: "class", beginKeywords: "class interface trait enum", end: "{", illegal: ":", contains: [ { beginKeywords: "extends implements" }, hljs.UNDERSCORE_TITLE_MODE, ], }, hljs.C_NUMBER_MODE, { className: "meta", begin: "@[A-Za-z]+", }, { // highlight map keys and named parameters as strings className: "string", begin: /[^\?]{0}[A-Za-z0-9_$]+ *:/, }, { // catch middle element of the ternary operator // to avoid highlight it as a label, named parameter, or map key begin: /\?/, end: /\:/, }, { // highlight labeled statements className: "symbol", begin: "^\\s*[A-Za-z0-9_$]+:", relevance: 0, }, ], illegal: /#|<\//, }; }, }, { name: "haml", /* Language: Haml Requires: ruby.js Author: Dan Allen Website: http://google.com/profiles/dan.j.allen Category: template */ create: // TODO support filter tags like :javascript, support inline HTML function (hljs) { return { case_insensitive: true, contains: [ { className: "meta", begin: "^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$", relevance: 10, }, // FIXME these comments should be allowed to span indented lines hljs.COMMENT("^\\s*(!=#|=#|-#|/).*$", false, { relevance: 0, }), { begin: "^\\s*(-|=|!=)(?!#)", starts: { end: "\\n", subLanguage: "ruby", }, }, { className: "tag", begin: "^\\s*%", contains: [ { className: "selector-tag", begin: "\\w+", }, { className: "selector-id", begin: "#[\\w-]+", }, { className: "selector-class", begin: "\\.[\\w-]+", }, { begin: "{\\s*", end: "\\s*}", contains: [ { begin: ":\\w+\\s*=>", end: ",\\s+", returnBegin: true, endsWithParent: true, contains: [ { className: "attr", begin: ":\\w+", }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { begin: "\\w+", relevance: 0, }, ], }, ], }, { begin: "\\(\\s*", end: "\\s*\\)", excludeEnd: true, contains: [ { begin: "\\w+\\s*=", end: "\\s+", returnBegin: true, endsWithParent: true, contains: [ { className: "attr", begin: "\\w+", relevance: 0, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { begin: "\\w+", relevance: 0, }, ], }, ], }, ], }, { begin: "^\\s*[=~]\\s*", }, { begin: "#{", starts: { end: "}", subLanguage: "ruby", }, }, ], }; }, }, { name: "handlebars", /* Language: Handlebars Requires: xml.js Author: Robin Ward Description: Matcher for Handlebars as well as EmberJS additions. Category: template */ create: function (hljs) { var BUILT_INS = { "builtin-name": "each in with if else unless bindattr action collection debugger log outlet template unbound view yield", }; return { aliases: ["hbs", "html.hbs", "html.handlebars"], case_insensitive: true, subLanguage: "xml", contains: [ hljs.COMMENT("{{!(--)?", "(--)?}}"), { className: "template-tag", begin: /\{\{[#\/]/, end: /\}\}/, contains: [ { className: "name", begin: /[a-zA-Z\.-]+/, keywords: BUILT_INS, starts: { endsWithParent: true, relevance: 0, contains: [hljs.QUOTE_STRING_MODE], }, }, ], }, { className: "template-variable", begin: /\{\{/, end: /\}\}/, keywords: BUILT_INS, }, ], }; }, }, { name: "haskell", /* Language: Haskell Author: Jeremy Hull Contributors: Zena Treep Category: functional */ create: function (hljs) { var COMMENT = { variants: [ hljs.COMMENT("--", "$"), hljs.COMMENT("{-", "-}", { contains: ["self"], }), ], }; var PRAGMA = { className: "meta", begin: "{-#", end: "#-}", }; var PREPROCESSOR = { className: "meta", begin: "^#", end: "$", }; var CONSTRUCTOR = { className: "type", begin: "\\b[A-Z][\\w']*", // TODO: other constructors (build-in, infix). relevance: 0, }; var LIST = { begin: "\\(", end: "\\)", illegal: '"', contains: [ PRAGMA, PREPROCESSOR, { className: "type", begin: "\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?", }, hljs.inherit(hljs.TITLE_MODE, { begin: "[_a-z][\\w']*" }), COMMENT, ], }; var RECORD = { begin: "{", end: "}", contains: LIST.contains, }; return { aliases: ["hs"], keywords: "let in if then else case of where do module import hiding " + "qualified type data newtype deriving class instance as default " + "infix infixl infixr foreign export ccall stdcall cplusplus " + "jvm dotnet safe unsafe family forall mdo proc rec", contains: [ // Top-level constructions. { beginKeywords: "module", end: "where", keywords: "module where", contains: [LIST, COMMENT], illegal: "\\W\\.|;", }, { begin: "\\bimport\\b", end: "$", keywords: "import qualified as hiding", contains: [LIST, COMMENT], illegal: "\\W\\.|;", }, { className: "class", begin: "^(\\s*)?(class|instance)\\b", end: "where", keywords: "class family instance where", contains: [CONSTRUCTOR, LIST, COMMENT], }, { className: "class", begin: "\\b(data|(new)?type)\\b", end: "$", keywords: "data family type newtype deriving", contains: [PRAGMA, CONSTRUCTOR, LIST, RECORD, COMMENT], }, { beginKeywords: "default", end: "$", contains: [CONSTRUCTOR, LIST, COMMENT], }, { beginKeywords: "infix infixl infixr", end: "$", contains: [hljs.C_NUMBER_MODE, COMMENT], }, { begin: "\\bforeign\\b", end: "$", keywords: "foreign import export ccall stdcall cplusplus jvm " + "dotnet safe unsafe", contains: [CONSTRUCTOR, hljs.QUOTE_STRING_MODE, COMMENT], }, { className: "meta", begin: "#!\\/usr\\/bin\\/env\ runhaskell", end: "$", }, // "Whitespaces". PRAGMA, PREPROCESSOR, // Literals and names. // TODO: characters. hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, CONSTRUCTOR, hljs.inherit(hljs.TITLE_MODE, { begin: "^[_a-z][\\w']*" }), COMMENT, { begin: "->|<-" }, // No markup, relevance booster ], }; }, }, { name: "haxe", /* Language: Haxe Author: Christopher Kaster (Based on the actionscript.js language file by Alexander Myadzel) Contributors: Kenton Hamaluik */ create: function (hljs) { var IDENT_RE = "[a-zA-Z_$][a-zA-Z0-9_$]*"; var IDENT_FUNC_RETURN_TYPE_RE = "([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)"; var HAXE_BASIC_TYPES = "Int Float String Bool Dynamic Void Array "; return { aliases: ["hx"], keywords: { keyword: "break case cast catch continue default do dynamic else enum extern " + "for function here if import in inline never new override package private get set " + "public return static super switch this throw trace try typedef untyped using var while " + HAXE_BASIC_TYPES, built_in: "trace this", literal: "true false null _", }, contains: [ { className: "string", // interpolate-able strings begin: "'", end: "'", contains: [ hljs.BACKSLASH_ESCAPE, { className: "subst", // interpolation begin: "\\$\\{", end: "\\}", }, { className: "subst", // interpolation begin: "\\$", end: "\\W}", }, ], }, hljs.QUOTE_STRING_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.C_NUMBER_MODE, { className: "meta", // compiler meta begin: "@:", end: "$", }, { className: "meta", // compiler conditionals begin: "#", end: "$", keywords: { "meta-keyword": "if else elseif end error" }, }, { className: "type", // function types begin: ":[ \t]*", end: "[^A-Za-z0-9_ \t\\->]", excludeBegin: true, excludeEnd: true, relevance: 0, }, { className: "type", // types begin: ":[ \t]*", end: "\\W", excludeBegin: true, excludeEnd: true, }, { className: "type", // instantiation begin: "new *", end: "\\W", excludeBegin: true, excludeEnd: true, }, { className: "class", // enums beginKeywords: "enum", end: "\\{", contains: [hljs.TITLE_MODE], }, { className: "class", // abstracts beginKeywords: "abstract", end: "[\\{$]", contains: [ { className: "type", begin: "\\(", end: "\\)", excludeBegin: true, excludeEnd: true, }, { className: "type", begin: "from +", end: "\\W", excludeBegin: true, excludeEnd: true, }, { className: "type", begin: "to +", end: "\\W", excludeBegin: true, excludeEnd: true, }, hljs.TITLE_MODE, ], keywords: { keyword: "abstract from to", }, }, { className: "class", // classes begin: "\\b(class|interface) +", end: "[\\{$]", excludeEnd: true, keywords: "class interface", contains: [ { className: "keyword", begin: "\\b(extends|implements) +", keywords: "extends implements", contains: [ { className: "type", begin: hljs.IDENT_RE, relevance: 0, }, ], }, hljs.TITLE_MODE, ], }, { className: "function", beginKeywords: "function", end: "\\(", excludeEnd: true, illegal: "\\S", contains: [hljs.TITLE_MODE], }, ], illegal: /<\//, }; }, }, { name: "hsp", /* Language: HSP Author: prince Website: http://prince.webcrow.jp/ Category: scripting */ create: function (hljs) { return { case_insensitive: true, lexemes: /[\w\._]+/, keywords: "goto gosub return break repeat loop continue wait await dim sdim foreach dimtype dup dupptr end stop newmod delmod mref run exgoto on mcall assert logmes newlab resume yield onexit onerror onkey onclick oncmd exist delete mkdir chdir dirlist bload bsave bcopy memfile if else poke wpoke lpoke getstr chdpm memexpand memcpy memset notesel noteadd notedel noteload notesave randomize noteunsel noteget split strrep setease button chgdisp exec dialog mmload mmplay mmstop mci pset pget syscolor mes print title pos circle cls font sysfont objsize picload color palcolor palette redraw width gsel gcopy gzoom gmode bmpsave hsvcolor getkey listbox chkbox combox input mesbox buffer screen bgscr mouse objsel groll line clrobj boxf objprm objmode stick grect grotate gsquare gradf objimage objskip objenable celload celdiv celput newcom querycom delcom cnvstow comres axobj winobj sendmsg comevent comevarg sarrayconv callfunc cnvwtos comevdisp libptr system hspstat hspver stat cnt err strsize looplev sublev iparam wparam lparam refstr refdval int rnd strlen length length2 length3 length4 vartype gettime peek wpeek lpeek varptr varuse noteinfo instr abs limit getease str strmid strf getpath strtrim sin cos tan atan sqrt double absf expf logf limitf powf geteasef mousex mousey mousew hwnd hinstance hdc ginfo objinfo dirinfo sysinfo thismod __hspver__ __hsp30__ __date__ __time__ __line__ __file__ _debug __hspdef__ and or xor not screen_normal screen_palette screen_hide screen_fixedsize screen_tool screen_frame gmode_gdi gmode_mem gmode_rgb0 gmode_alpha gmode_rgb0alpha gmode_add gmode_sub gmode_pixela ginfo_mx ginfo_my ginfo_act ginfo_sel ginfo_wx1 ginfo_wy1 ginfo_wx2 ginfo_wy2 ginfo_vx ginfo_vy ginfo_sizex ginfo_sizey ginfo_winx ginfo_winy ginfo_mesx ginfo_mesy ginfo_r ginfo_g ginfo_b ginfo_paluse ginfo_dispx ginfo_dispy ginfo_cx ginfo_cy ginfo_intid ginfo_newid ginfo_sx ginfo_sy objinfo_mode objinfo_bmscr objinfo_hwnd notemax notesize dir_cur dir_exe dir_win dir_sys dir_cmdline dir_desktop dir_mydoc dir_tv font_normal font_bold font_italic font_underline font_strikeout font_antialias objmode_normal objmode_guifont objmode_usefont gsquare_grad msgothic msmincho do until while wend for next _break _continue switch case default swbreak swend ddim ldim alloc m_pi rad2deg deg2rad ease_linear ease_quad_in ease_quad_out ease_quad_inout ease_cubic_in ease_cubic_out ease_cubic_inout ease_quartic_in ease_quartic_out ease_quartic_inout ease_bounce_in ease_bounce_out ease_bounce_inout ease_shake_in ease_shake_out ease_shake_inout ease_loop", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, { // multi-line string className: "string", begin: '{"', end: '"}', contains: [hljs.BACKSLASH_ESCAPE], }, hljs.COMMENT(";", "$", { relevance: 0 }), { // pre-processor className: "meta", begin: "#", end: "$", keywords: { "meta-keyword": "addion cfunc cmd cmpopt comfunc const defcfunc deffunc define else endif enum epack func global if ifdef ifndef include modcfunc modfunc modinit modterm module pack packopt regcmd runtime undef usecom uselib", }, contains: [ hljs.inherit(hljs.QUOTE_STRING_MODE, { className: "meta-string", }), hljs.NUMBER_MODE, hljs.C_NUMBER_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, { // label className: "symbol", begin: "^\\*(\\w+|@)", }, hljs.NUMBER_MODE, hljs.C_NUMBER_MODE, ], }; }, }, { name: "htmlbars", /* Language: HTMLBars Requires: xml.js Author: Michael Johnston Description: Matcher for HTMLBars Category: template */ create: function (hljs) { var BUILT_INS = "action collection component concat debugger each each-in else get hash if input link-to loc log mut outlet partial query-params render textarea unbound unless with yield view"; var ATTR_ASSIGNMENT = { illegal: /\}\}/, begin: /[a-zA-Z0-9_]+=/, returnBegin: true, relevance: 0, contains: [ { className: "attr", begin: /[a-zA-Z0-9_]+/, }, ], }; var SUB_EXPR = { illegal: /\}\}/, begin: /\)/, end: /\)/, contains: [ { begin: /[a-zA-Z\.\-]+/, keywords: { built_in: BUILT_INS }, starts: { endsWithParent: true, relevance: 0, contains: [hljs.QUOTE_STRING_MODE], }, }, ], }; var TAG_INNARDS = { endsWithParent: true, relevance: 0, keywords: { keyword: "as", built_in: BUILT_INS }, contains: [ hljs.QUOTE_STRING_MODE, ATTR_ASSIGNMENT, hljs.NUMBER_MODE, ], }; return { case_insensitive: true, subLanguage: "xml", contains: [ hljs.COMMENT("{{!(--)?", "(--)?}}"), { className: "template-tag", begin: /\{\{[#\/]/, end: /\}\}/, contains: [ { className: "name", begin: /[a-zA-Z\.\-]+/, keywords: { "builtin-name": BUILT_INS }, starts: TAG_INNARDS, }, ], }, { className: "template-variable", begin: /\{\{[a-zA-Z][a-zA-Z\-]+/, end: /\}\}/, keywords: { keyword: "as", built_in: BUILT_INS }, contains: [hljs.QUOTE_STRING_MODE], }, ], }; }, }, { name: "http", /* Language: HTTP Description: HTTP request and response headers with automatic body highlighting Author: Ivan Sagalaev Category: common, protocols */ create: function (hljs) { var VERSION = "HTTP/[0-9\\.]+"; return { aliases: ["https"], illegal: "\\S", contains: [ { begin: "^" + VERSION, end: "$", contains: [ { className: "number", begin: "\\b\\d{3}\\b" }, ], }, { begin: "^[A-Z]+ (.*?) " + VERSION + "$", returnBegin: true, end: "$", contains: [ { className: "string", begin: " ", end: " ", excludeBegin: true, excludeEnd: true, }, { begin: VERSION, }, { className: "keyword", begin: "[A-Z]+", }, ], }, { className: "attribute", begin: "^\\w", end: ": ", excludeEnd: true, illegal: "\\n|\\s|=", starts: { end: "$", relevance: 0 }, }, { begin: "\\n\\n", starts: { subLanguage: [], endsWithParent: true }, }, ], }; }, }, { name: "hy", /* Language: Hy Description: Hy syntax (based on clojure.js) Author: Sergey Sobko Category: lisp */ create: function (hljs) { var keywords = { "builtin-name": // keywords "!= % %= & &= * ** **= *= *map " + "+ += , --build-class-- --import-- -= . / // //= " + "/= < << <<= <= = > >= >> >>= " + "@ @= ^ ^= abs accumulate all and any ap-compose " + "ap-dotimes ap-each ap-each-while ap-filter ap-first ap-if ap-last ap-map ap-map-when ap-pipe " + "ap-reduce ap-reject apply as-> ascii assert assoc bin break butlast " + "callable calling-module-name car case cdr chain chr coll? combinations compile " + "compress cond cons cons? continue count curry cut cycle dec " + "def default-method defclass defmacro defmacro-alias defmacro/g! defmain defmethod defmulti defn " + "defn-alias defnc defnr defreader defseq del delattr delete-route dict-comp dir " + "disassemble dispatch-reader-macro distinct divmod do doto drop drop-last drop-while empty? " + "end-sequence eval eval-and-compile eval-when-compile even? every? except exec filter first " + "flatten float? fn fnc fnr for for* format fraction genexpr " + "gensym get getattr global globals group-by hasattr hash hex id " + "identity if if* if-not if-python2 import in inc input instance? " + "integer integer-char? integer? interleave interpose is is-coll is-cons is-empty is-even " + "is-every is-float is-instance is-integer is-integer-char is-iterable is-iterator is-keyword is-neg is-none " + "is-not is-numeric is-odd is-pos is-string is-symbol is-zero isinstance islice issubclass " + "iter iterable? iterate iterator? keyword keyword? lambda last len let " + "lif lif-not list* list-comp locals loop macro-error macroexpand macroexpand-1 macroexpand-all " + "map max merge-with method-decorator min multi-decorator multicombinations name neg? next " + "none? nonlocal not not-in not? nth numeric? oct odd? open " + "or ord partition permutations pos? post-route postwalk pow prewalk print " + "product profile/calls profile/cpu put-route quasiquote quote raise range read read-str " + "recursive-replace reduce remove repeat repeatedly repr require rest round route " + "route-with-methods rwm second seq set-comp setattr setv some sorted string " + "string? sum switch symbol? take take-nth take-while tee try unless " + "unquote unquote-splicing vars walk when while with with* with-decorator with-gensyms " + "xi xor yield yield-from zero? zip zip-longest | |= ~", }; var SYMBOLSTART = "a-zA-Z_\\-!.?+*=<>&#'"; var SYMBOL_RE = "[" + SYMBOLSTART + "][" + SYMBOLSTART + "0-9/;:]*"; var SIMPLE_NUMBER_RE = "[-+]?\\d+(\\.\\d+)?"; var SHEBANG = { className: "meta", begin: "^#!", end: "$", }; var SYMBOL = { begin: SYMBOL_RE, relevance: 0, }; var NUMBER = { className: "number", begin: SIMPLE_NUMBER_RE, relevance: 0, }; var STRING = hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null, }); var COMMENT = hljs.COMMENT(";", "$", { relevance: 0, }); var LITERAL = { className: "literal", begin: /\b([Tt]rue|[Ff]alse|nil|None)\b/, }; var COLLECTION = { begin: "[\\[\\{]", end: "[\\]\\}]", }; var HINT = { className: "comment", begin: "\\^" + SYMBOL_RE, }; var HINT_COL = hljs.COMMENT("\\^\\{", "\\}"); var KEY = { className: "symbol", begin: "[:]{1,2}" + SYMBOL_RE, }; var LIST = { begin: "\\(", end: "\\)", }; var BODY = { endsWithParent: true, relevance: 0, }; var NAME = { keywords: keywords, lexemes: SYMBOL_RE, className: "name", begin: SYMBOL_RE, starts: BODY, }; var DEFAULT_CONTAINS = [ LIST, STRING, HINT, HINT_COL, COMMENT, KEY, COLLECTION, NUMBER, LITERAL, SYMBOL, ]; LIST.contains = [hljs.COMMENT("comment", ""), NAME, BODY]; BODY.contains = DEFAULT_CONTAINS; COLLECTION.contains = DEFAULT_CONTAINS; return { aliases: ["hylang"], illegal: /\S/, contains: [ SHEBANG, LIST, STRING, HINT, HINT_COL, COMMENT, KEY, COLLECTION, NUMBER, LITERAL, ], }; }, }, { name: "inform7", /* Language: Inform 7 Author: Bruno Dias Description: Language definition for Inform 7, a DSL for writing parser interactive fiction. */ create: function (hljs) { var START_BRACKET = "\\["; var END_BRACKET = "\\]"; return { aliases: ["i7"], case_insensitive: true, keywords: { // Some keywords more or less unique to I7, for relevance. keyword: // kind: "thing room person man woman animal container " + "supporter backdrop door " + // characteristic: "scenery open closed locked inside gender " + // verb: "is are say understand " + // misc keyword: "kind of rule", }, contains: [ { className: "string", begin: '"', end: '"', relevance: 0, contains: [ { className: "subst", begin: START_BRACKET, end: END_BRACKET, }, ], }, { className: "section", begin: /^(Volume|Book|Part|Chapter|Section|Table)\b/, end: "$", }, { // Rule definition // This is here for relevance. begin: /^(Check|Carry out|Report|Instead of|To|Rule|When|Before|After)\b/, end: ":", contains: [ { //Rule name begin: "\\(This", end: "\\)", }, ], }, { className: "comment", begin: START_BRACKET, end: END_BRACKET, contains: ["self"], }, ], }; }, }, { name: "ini", /* Language: Ini, TOML Contributors: Guillaume Gomez Category: common, config */ create: function (hljs) { var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE], variants: [ { begin: "'''", end: "'''", relevance: 10, }, { begin: '"""', end: '"""', relevance: 10, }, { begin: '"', end: '"', }, { begin: "'", end: "'", }, ], }; return { aliases: ["toml"], case_insensitive: true, illegal: /\S/, contains: [ hljs.COMMENT(";", "$"), hljs.HASH_COMMENT_MODE, { className: "section", begin: /^\s*\[+/, end: /\]+/, }, { begin: /^[a-z0-9\[\]_\.-]+\s*=\s*/, end: "$", returnBegin: true, contains: [ { className: "attr", begin: /[a-z0-9\[\]_\.-]+/, }, { begin: /=/, endsWithParent: true, relevance: 0, contains: [ hljs.COMMENT(";", "$"), hljs.HASH_COMMENT_MODE, { className: "literal", begin: /\bon|off|true|false|yes|no\b/, }, { className: "variable", variants: [ { begin: /\$[\w\d"][\w\d_]*/ }, { begin: /\$\{(.*?)}/ }, ], }, STRING, { className: "number", begin: /([\+\-]+)?[\d]+_[\d_]+/, }, hljs.NUMBER_MODE, ], }, ], }, ], }; }, }, { name: "irpf90", /* Language: IRPF90 Author: Anthony Scemama Description: IRPF90 is an open-source Fortran code generator : http://irpf90.ups-tlse.fr Category: scientific */ create: function (hljs) { var PARAMS = { className: "params", begin: "\\(", end: "\\)", }; var F_KEYWORDS = { literal: ".False. .True.", keyword: "kind do while private call intrinsic where elsewhere " + "type endtype endmodule endselect endinterface end enddo endif if forall endforall only contains default return stop then " + "public subroutine|10 function program .and. .or. .not. .le. .eq. .ge. .gt. .lt. " + "goto save else use module select case " + "access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit " + "continue format pause cycle exit " + "c_null_char c_alert c_backspace c_form_feed flush wait decimal round iomsg " + "synchronous nopass non_overridable pass protected volatile abstract extends import " + "non_intrinsic value deferred generic final enumerator class associate bind enum " + "c_int c_short c_long c_long_long c_signed_char c_size_t c_int8_t c_int16_t c_int32_t c_int64_t c_int_least8_t c_int_least16_t " + "c_int_least32_t c_int_least64_t c_int_fast8_t c_int_fast16_t c_int_fast32_t c_int_fast64_t c_intmax_t C_intptr_t c_float c_double " + "c_long_double c_float_complex c_double_complex c_long_double_complex c_bool c_char c_null_ptr c_null_funptr " + "c_new_line c_carriage_return c_horizontal_tab c_vertical_tab iso_c_binding c_loc c_funloc c_associated c_f_pointer " + "c_ptr c_funptr iso_fortran_env character_storage_size error_unit file_storage_size input_unit iostat_end iostat_eor " + "numeric_storage_size output_unit c_f_procpointer ieee_arithmetic ieee_support_underflow_control " + "ieee_get_underflow_mode ieee_set_underflow_mode newunit contiguous recursive " + "pad position action delim readwrite eor advance nml interface procedure namelist include sequence elemental pure " + "integer real character complex logical dimension allocatable|10 parameter " + "external implicit|10 none double precision assign intent optional pointer " + "target in out common equivalence data " + // IRPF90 special keywords "begin_provider &begin_provider end_provider begin_shell end_shell begin_template end_template subst assert touch " + "soft_touch provide no_dep free irp_if irp_else irp_endif irp_write irp_read", built_in: "alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint " + "dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl " + "algama cdabs cdcos cdexp cdlog cdsin cdsqrt cqabs cqcos cqexp cqlog cqsin cqsqrt dcmplx dconjg derf derfc dfloat dgamma dimag dlgama " + "iqint qabs qacos qasin qatan qatan2 qcmplx qconjg qcos qcosh qdim qerf qerfc qexp qgamma qimag qlgama qlog qlog10 qmax1 qmin1 qmod " + "qnint qsign qsin qsinh qsqrt qtan qtanh abs acos aimag aint anint asin atan atan2 char cmplx conjg cos cosh exp ichar index int log " + "log10 max min nint sign sin sinh sqrt tan tanh print write dim lge lgt lle llt mod nullify allocate deallocate " + "adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product " + "eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul " + "maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack present product " + "radix random_number random_seed range repeat reshape rrspacing scale scan selected_int_kind selected_real_kind " + "set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify achar iachar transfer " + "dble entry dprod cpu_time command_argument_count get_command get_command_argument get_environment_variable is_iostat_end " + "ieee_arithmetic ieee_support_underflow_control ieee_get_underflow_mode ieee_set_underflow_mode " + "is_iostat_eor move_alloc new_line selected_char_kind same_type_as extends_type_of" + "acosh asinh atanh bessel_j0 bessel_j1 bessel_jn bessel_y0 bessel_y1 bessel_yn erf erfc erfc_scaled gamma log_gamma hypot norm2 " + "atomic_define atomic_ref execute_command_line leadz trailz storage_size merge_bits " + "bge bgt ble blt dshiftl dshiftr findloc iall iany iparity image_index lcobound ucobound maskl maskr " + "num_images parity popcnt poppar shifta shiftl shiftr this_image " + // IRPF90 special built_ins "IRP_ALIGN irp_here", }; return { case_insensitive: true, keywords: F_KEYWORDS, illegal: /\/\*/, contains: [ hljs.inherit(hljs.APOS_STRING_MODE, { className: "string", relevance: 0, }), hljs.inherit(hljs.QUOTE_STRING_MODE, { className: "string", relevance: 0, }), { className: "function", beginKeywords: "subroutine function program", illegal: "[${=\\n]", contains: [hljs.UNDERSCORE_TITLE_MODE, PARAMS], }, hljs.COMMENT("!", "$", { relevance: 0 }), hljs.COMMENT("begin_doc", "end_doc", { relevance: 10 }), { className: "number", begin: "(?=\\b|\\+|\\-|\\.)(?=\\.\\d|\\d)(?:\\d+)?(?:\\.?\\d*)(?:[de][+-]?\\d+)?\\b\\.?", relevance: 0, }, ], }; }, }, { name: "isbl", /* Language: ISBL Author: Dmitriy Tarasov Description: built-in language DIRECTUM Category: enterprise */ create: function (hljs) { // Определение идентификаторов var UNDERSCORE_IDENT_RE = "[A-Za-zА-Яа-яёЁ_!][A-Za-zА-Яа-яёЁ_0-9]*"; // Определение имен функций var FUNCTION_NAME_IDENT_RE = "[A-Za-zА-Яа-яёЁ_][A-Za-zА-Яа-яёЁ_0-9]*"; // keyword : ключевые слова var KEYWORD = "and и else иначе endexcept endfinally endforeach конецвсе endif конецесли endwhile конецпока " + "except exitfor finally foreach все if если in в not не or или try while пока "; // SYSRES Constants var sysres_constants = "SYSRES_CONST_ACCES_RIGHT_TYPE_EDIT " + "SYSRES_CONST_ACCES_RIGHT_TYPE_FULL " + "SYSRES_CONST_ACCES_RIGHT_TYPE_VIEW " + "SYSRES_CONST_ACCESS_MODE_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_NO_ACCESS_VIEW " + "SYSRES_CONST_ACCESS_NO_ACCESS_VIEW_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_ADD_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_ADD_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_CHANGE_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_CHANGE_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_DELETE_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_DELETE_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_EXECUTE_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_EXECUTE_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_NO_ACCESS_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_NO_ACCESS_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_RATIFY_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_RATIFY_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_VIEW " + "SYSRES_CONST_ACCESS_RIGHTS_VIEW_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_VIEW_REQUISITE_CODE " + "SYSRES_CONST_ACCESS_RIGHTS_VIEW_REQUISITE_YES_CODE " + "SYSRES_CONST_ACCESS_TYPE_CHANGE " + "SYSRES_CONST_ACCESS_TYPE_CHANGE_CODE " + "SYSRES_CONST_ACCESS_TYPE_EXISTS " + "SYSRES_CONST_ACCESS_TYPE_EXISTS_CODE " + "SYSRES_CONST_ACCESS_TYPE_FULL " + "SYSRES_CONST_ACCESS_TYPE_FULL_CODE " + "SYSRES_CONST_ACCESS_TYPE_VIEW " + "SYSRES_CONST_ACCESS_TYPE_VIEW_CODE " + "SYSRES_CONST_ACTION_TYPE_ABORT " + "SYSRES_CONST_ACTION_TYPE_ACCEPT " + "SYSRES_CONST_ACTION_TYPE_ACCESS_RIGHTS " + "SYSRES_CONST_ACTION_TYPE_ADD_ATTACHMENT " + "SYSRES_CONST_ACTION_TYPE_CHANGE_CARD " + "SYSRES_CONST_ACTION_TYPE_CHANGE_KIND " + "SYSRES_CONST_ACTION_TYPE_CHANGE_STORAGE " + "SYSRES_CONST_ACTION_TYPE_CONTINUE " + "SYSRES_CONST_ACTION_TYPE_COPY " + "SYSRES_CONST_ACTION_TYPE_CREATE " + "SYSRES_CONST_ACTION_TYPE_CREATE_VERSION " + "SYSRES_CONST_ACTION_TYPE_DELETE " + "SYSRES_CONST_ACTION_TYPE_DELETE_ATTACHMENT " + "SYSRES_CONST_ACTION_TYPE_DELETE_VERSION " + "SYSRES_CONST_ACTION_TYPE_DISABLE_DELEGATE_ACCESS_RIGHTS " + "SYSRES_CONST_ACTION_TYPE_ENABLE_DELEGATE_ACCESS_RIGHTS " + "SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_CERTIFICATE " + "SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_CERTIFICATE_AND_PASSWORD " + "SYSRES_CONST_ACTION_TYPE_ENCRYPTION_BY_PASSWORD " + "SYSRES_CONST_ACTION_TYPE_EXPORT_WITH_LOCK " + "SYSRES_CONST_ACTION_TYPE_EXPORT_WITHOUT_LOCK " + "SYSRES_CONST_ACTION_TYPE_IMPORT_WITH_UNLOCK " + "SYSRES_CONST_ACTION_TYPE_IMPORT_WITHOUT_UNLOCK " + "SYSRES_CONST_ACTION_TYPE_LIFE_CYCLE_STAGE " + "SYSRES_CONST_ACTION_TYPE_LOCK " + "SYSRES_CONST_ACTION_TYPE_LOCK_FOR_SERVER " + "SYSRES_CONST_ACTION_TYPE_LOCK_MODIFY " + "SYSRES_CONST_ACTION_TYPE_MARK_AS_READED " + "SYSRES_CONST_ACTION_TYPE_MARK_AS_UNREADED " + "SYSRES_CONST_ACTION_TYPE_MODIFY " + "SYSRES_CONST_ACTION_TYPE_MODIFY_CARD " + "SYSRES_CONST_ACTION_TYPE_MOVE_TO_ARCHIVE " + "SYSRES_CONST_ACTION_TYPE_OFF_ENCRYPTION " + "SYSRES_CONST_ACTION_TYPE_PASSWORD_CHANGE " + "SYSRES_CONST_ACTION_TYPE_PERFORM " + "SYSRES_CONST_ACTION_TYPE_RECOVER_FROM_LOCAL_COPY " + "SYSRES_CONST_ACTION_TYPE_RESTART " + "SYSRES_CONST_ACTION_TYPE_RESTORE_FROM_ARCHIVE " + "SYSRES_CONST_ACTION_TYPE_REVISION " + "SYSRES_CONST_ACTION_TYPE_SEND_BY_MAIL " + "SYSRES_CONST_ACTION_TYPE_SIGN " + "SYSRES_CONST_ACTION_TYPE_START " + "SYSRES_CONST_ACTION_TYPE_UNLOCK " + "SYSRES_CONST_ACTION_TYPE_UNLOCK_FROM_SERVER " + "SYSRES_CONST_ACTION_TYPE_VERSION_STATE " + "SYSRES_CONST_ACTION_TYPE_VERSION_VISIBILITY " + "SYSRES_CONST_ACTION_TYPE_VIEW " + "SYSRES_CONST_ACTION_TYPE_VIEW_SHADOW_COPY " + "SYSRES_CONST_ACTION_TYPE_WORKFLOW_DESCRIPTION_MODIFY " + "SYSRES_CONST_ACTION_TYPE_WRITE_HISTORY " + "SYSRES_CONST_ACTIVE_VERSION_STATE_PICK_VALUE " + "SYSRES_CONST_ADD_REFERENCE_MODE_NAME " + "SYSRES_CONST_ADDITION_REQUISITE_CODE " + "SYSRES_CONST_ADDITIONAL_PARAMS_REQUISITE_CODE " + "SYSRES_CONST_ADITIONAL_JOB_END_DATE_REQUISITE_NAME " + "SYSRES_CONST_ADITIONAL_JOB_READ_REQUISITE_NAME " + "SYSRES_CONST_ADITIONAL_JOB_START_DATE_REQUISITE_NAME " + "SYSRES_CONST_ADITIONAL_JOB_STATE_REQUISITE_NAME " + "SYSRES_CONST_ADMINISTRATION_HISTORY_ADDING_USER_TO_GROUP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_ADDING_USER_TO_GROUP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_COMP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_COMP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_GROUP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_GROUP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_USER_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_CREATION_USER_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_CREATION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_CREATION_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_DELETION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DATABASE_USER_DELETION_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_COMP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_COMP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_GROUP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_GROUP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_FROM_GROUP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_DELETION_USER_FROM_GROUP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_RESTRICTION_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_FILTERER_RESTRICTION_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_PRIVILEGE_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_PRIVILEGE_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_RIGHTS_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_GRANTING_RIGHTS_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_IS_MAIN_SERVER_CHANGED_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_IS_MAIN_SERVER_CHANGED_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_IS_PUBLIC_CHANGED_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_IS_PUBLIC_CHANGED_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_RESTRICTION_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_FILTERER_RESTRICTION_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_PRIVILEGE_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_PRIVILEGE_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_RIGHTS_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_REMOVING_RIGHTS_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_CREATION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_CREATION_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_DELETION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_SERVER_LOGIN_DELETION_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_CATEGORY_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_CATEGORY_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_COMP_TITLE_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_COMP_TITLE_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_FULL_NAME_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_FULL_NAME_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_GROUP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_GROUP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_PARENT_GROUP_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_PARENT_GROUP_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_AUTH_TYPE_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_AUTH_TYPE_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_LOGIN_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_LOGIN_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_STATUS_ACTION " + "SYSRES_CONST_ADMINISTRATION_HISTORY_UPDATING_USER_STATUS_ACTION_CODE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_USER_PASSWORD_CHANGE " + "SYSRES_CONST_ADMINISTRATION_HISTORY_USER_PASSWORD_CHANGE_ACTION " + "SYSRES_CONST_ALL_ACCEPT_CONDITION_RUS " + "SYSRES_CONST_ALL_USERS_GROUP " + "SYSRES_CONST_ALL_USERS_GROUP_NAME " + "SYSRES_CONST_ALL_USERS_SERVER_GROUP_NAME " + "SYSRES_CONST_ALLOWED_ACCESS_TYPE_CODE " + "SYSRES_CONST_ALLOWED_ACCESS_TYPE_NAME " + "SYSRES_CONST_APP_VIEWER_TYPE_REQUISITE_CODE " + "SYSRES_CONST_APPROVING_SIGNATURE_NAME " + "SYSRES_CONST_APPROVING_SIGNATURE_REQUISITE_CODE " + "SYSRES_CONST_ASSISTANT_SUBSTITUE_TYPE " + "SYSRES_CONST_ASSISTANT_SUBSTITUE_TYPE_CODE " + "SYSRES_CONST_ATTACH_TYPE_COMPONENT_TOKEN " + "SYSRES_CONST_ATTACH_TYPE_DOC " + "SYSRES_CONST_ATTACH_TYPE_EDOC " + "SYSRES_CONST_ATTACH_TYPE_FOLDER " + "SYSRES_CONST_ATTACH_TYPE_JOB " + "SYSRES_CONST_ATTACH_TYPE_REFERENCE " + "SYSRES_CONST_ATTACH_TYPE_TASK " + "SYSRES_CONST_AUTH_ENCODED_PASSWORD " + "SYSRES_CONST_AUTH_ENCODED_PASSWORD_CODE " + "SYSRES_CONST_AUTH_NOVELL " + "SYSRES_CONST_AUTH_PASSWORD " + "SYSRES_CONST_AUTH_PASSWORD_CODE " + "SYSRES_CONST_AUTH_WINDOWS " + "SYSRES_CONST_AUTHENTICATING_SIGNATURE_NAME " + "SYSRES_CONST_AUTHENTICATING_SIGNATURE_REQUISITE_CODE " + "SYSRES_CONST_AUTO_ENUM_METHOD_FLAG " + "SYSRES_CONST_AUTO_NUMERATION_CODE " + "SYSRES_CONST_AUTO_STRONG_ENUM_METHOD_FLAG " + "SYSRES_CONST_AUTOTEXT_NAME_REQUISITE_CODE " + "SYSRES_CONST_AUTOTEXT_TEXT_REQUISITE_CODE " + "SYSRES_CONST_AUTOTEXT_USAGE_ALL " + "SYSRES_CONST_AUTOTEXT_USAGE_ALL_CODE " + "SYSRES_CONST_AUTOTEXT_USAGE_SIGN " + "SYSRES_CONST_AUTOTEXT_USAGE_SIGN_CODE " + "SYSRES_CONST_AUTOTEXT_USAGE_WORK " + "SYSRES_CONST_AUTOTEXT_USAGE_WORK_CODE " + "SYSRES_CONST_AUTOTEXT_USE_ANYWHERE_CODE " + "SYSRES_CONST_AUTOTEXT_USE_ON_SIGNING_CODE " + "SYSRES_CONST_AUTOTEXT_USE_ON_WORK_CODE " + "SYSRES_CONST_BEGIN_DATE_REQUISITE_CODE " + "SYSRES_CONST_BLACK_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_BLUE_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_BTN_PART " + "SYSRES_CONST_CALCULATED_ROLE_TYPE_CODE " + "SYSRES_CONST_CALL_TYPE_VARIABLE_BUTTON_VALUE " + "SYSRES_CONST_CALL_TYPE_VARIABLE_PROGRAM_VALUE " + "SYSRES_CONST_CANCEL_MESSAGE_FUNCTION_RESULT " + "SYSRES_CONST_CARD_PART " + "SYSRES_CONST_CARD_REFERENCE_MODE_NAME " + "SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_ENCRYPT_VALUE " + "SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_SIGN_AND_ENCRYPT_VALUE " + "SYSRES_CONST_CERTIFICATE_TYPE_REQUISITE_SIGN_VALUE " + "SYSRES_CONST_CHECK_PARAM_VALUE_DATE_PARAM_TYPE " + "SYSRES_CONST_CHECK_PARAM_VALUE_FLOAT_PARAM_TYPE " + "SYSRES_CONST_CHECK_PARAM_VALUE_INTEGER_PARAM_TYPE " + "SYSRES_CONST_CHECK_PARAM_VALUE_PICK_PARAM_TYPE " + "SYSRES_CONST_CHECK_PARAM_VALUE_REEFRENCE_PARAM_TYPE " + "SYSRES_CONST_CLOSED_RECORD_FLAG_VALUE_FEMININE " + "SYSRES_CONST_CLOSED_RECORD_FLAG_VALUE_MASCULINE " + "SYSRES_CONST_CODE_COMPONENT_TYPE_ADMIN " + "SYSRES_CONST_CODE_COMPONENT_TYPE_DEVELOPER " + "SYSRES_CONST_CODE_COMPONENT_TYPE_DOCS " + "SYSRES_CONST_CODE_COMPONENT_TYPE_EDOC_CARDS " + "SYSRES_CONST_CODE_COMPONENT_TYPE_EXTERNAL_EXECUTABLE " + "SYSRES_CONST_CODE_COMPONENT_TYPE_OTHER " + "SYSRES_CONST_CODE_COMPONENT_TYPE_REFERENCE " + "SYSRES_CONST_CODE_COMPONENT_TYPE_REPORT " + "SYSRES_CONST_CODE_COMPONENT_TYPE_SCRIPT " + "SYSRES_CONST_CODE_COMPONENT_TYPE_URL " + "SYSRES_CONST_CODE_REQUISITE_ACCESS " + "SYSRES_CONST_CODE_REQUISITE_CODE " + "SYSRES_CONST_CODE_REQUISITE_COMPONENT " + "SYSRES_CONST_CODE_REQUISITE_DESCRIPTION " + "SYSRES_CONST_CODE_REQUISITE_EXCLUDE_COMPONENT " + "SYSRES_CONST_CODE_REQUISITE_RECORD " + "SYSRES_CONST_COMMENT_REQ_CODE " + "SYSRES_CONST_COMMON_SETTINGS_REQUISITE_CODE " + "SYSRES_CONST_COMP_CODE_GRD " + "SYSRES_CONST_COMPONENT_GROUP_TYPE_REQUISITE_CODE " + "SYSRES_CONST_COMPONENT_TYPE_ADMIN_COMPONENTS " + "SYSRES_CONST_COMPONENT_TYPE_DEVELOPER_COMPONENTS " + "SYSRES_CONST_COMPONENT_TYPE_DOCS " + "SYSRES_CONST_COMPONENT_TYPE_EDOC_CARDS " + "SYSRES_CONST_COMPONENT_TYPE_EDOCS " + "SYSRES_CONST_COMPONENT_TYPE_EXTERNAL_EXECUTABLE " + "SYSRES_CONST_COMPONENT_TYPE_OTHER " + "SYSRES_CONST_COMPONENT_TYPE_REFERENCE_TYPES " + "SYSRES_CONST_COMPONENT_TYPE_REFERENCES " + "SYSRES_CONST_COMPONENT_TYPE_REPORTS " + "SYSRES_CONST_COMPONENT_TYPE_SCRIPTS " + "SYSRES_CONST_COMPONENT_TYPE_URL " + "SYSRES_CONST_COMPONENTS_REMOTE_SERVERS_VIEW_CODE " + "SYSRES_CONST_CONDITION_BLOCK_DESCRIPTION " + "SYSRES_CONST_CONST_FIRM_STATUS_COMMON " + "SYSRES_CONST_CONST_FIRM_STATUS_INDIVIDUAL " + "SYSRES_CONST_CONST_NEGATIVE_VALUE " + "SYSRES_CONST_CONST_POSITIVE_VALUE " + "SYSRES_CONST_CONST_SERVER_STATUS_DONT_REPLICATE " + "SYSRES_CONST_CONST_SERVER_STATUS_REPLICATE " + "SYSRES_CONST_CONTENTS_REQUISITE_CODE " + "SYSRES_CONST_DATA_TYPE_BOOLEAN " + "SYSRES_CONST_DATA_TYPE_DATE " + "SYSRES_CONST_DATA_TYPE_FLOAT " + "SYSRES_CONST_DATA_TYPE_INTEGER " + "SYSRES_CONST_DATA_TYPE_PICK " + "SYSRES_CONST_DATA_TYPE_REFERENCE " + "SYSRES_CONST_DATA_TYPE_STRING " + "SYSRES_CONST_DATA_TYPE_TEXT " + "SYSRES_CONST_DATA_TYPE_VARIANT " + "SYSRES_CONST_DATE_CLOSE_REQ_CODE " + "SYSRES_CONST_DATE_FORMAT_DATE_ONLY_CHAR " + "SYSRES_CONST_DATE_OPEN_REQ_CODE " + "SYSRES_CONST_DATE_REQUISITE " + "SYSRES_CONST_DATE_REQUISITE_CODE " + "SYSRES_CONST_DATE_REQUISITE_NAME " + "SYSRES_CONST_DATE_REQUISITE_TYPE " + "SYSRES_CONST_DATE_TYPE_CHAR " + "SYSRES_CONST_DATETIME_FORMAT_VALUE " + "SYSRES_CONST_DEA_ACCESS_RIGHTS_ACTION_CODE " + "SYSRES_CONST_DESCRIPTION_LOCALIZE_ID_REQUISITE_CODE " + "SYSRES_CONST_DESCRIPTION_REQUISITE_CODE " + "SYSRES_CONST_DET1_PART " + "SYSRES_CONST_DET2_PART " + "SYSRES_CONST_DET3_PART " + "SYSRES_CONST_DET4_PART " + "SYSRES_CONST_DET5_PART " + "SYSRES_CONST_DET6_PART " + "SYSRES_CONST_DETAIL_DATASET_KEY_REQUISITE_CODE " + "SYSRES_CONST_DETAIL_PICK_REQUISITE_CODE " + "SYSRES_CONST_DETAIL_REQ_CODE " + "SYSRES_CONST_DO_NOT_USE_ACCESS_TYPE_CODE " + "SYSRES_CONST_DO_NOT_USE_ACCESS_TYPE_NAME " + "SYSRES_CONST_DO_NOT_USE_ON_VIEW_ACCESS_TYPE_CODE " + "SYSRES_CONST_DO_NOT_USE_ON_VIEW_ACCESS_TYPE_NAME " + "SYSRES_CONST_DOCUMENT_STORAGES_CODE " + "SYSRES_CONST_DOCUMENT_TEMPLATES_TYPE_NAME " + "SYSRES_CONST_DOUBLE_REQUISITE_CODE " + "SYSRES_CONST_EDITOR_CLOSE_FILE_OBSERV_TYPE_CODE " + "SYSRES_CONST_EDITOR_CLOSE_PROCESS_OBSERV_TYPE_CODE " + "SYSRES_CONST_EDITOR_TYPE_REQUISITE_CODE " + "SYSRES_CONST_EDITORS_APPLICATION_NAME_REQUISITE_CODE " + "SYSRES_CONST_EDITORS_CREATE_SEVERAL_PROCESSES_REQUISITE_CODE " + "SYSRES_CONST_EDITORS_EXTENSION_REQUISITE_CODE " + "SYSRES_CONST_EDITORS_OBSERVER_BY_PROCESS_TYPE " + "SYSRES_CONST_EDITORS_REFERENCE_CODE " + "SYSRES_CONST_EDITORS_REPLACE_SPEC_CHARS_REQUISITE_CODE " + "SYSRES_CONST_EDITORS_USE_PLUGINS_REQUISITE_CODE " + "SYSRES_CONST_EDITORS_VIEW_DOCUMENT_OPENED_TO_EDIT_CODE " + "SYSRES_CONST_EDOC_CARD_TYPE_REQUISITE_CODE " + "SYSRES_CONST_EDOC_CARD_TYPES_LINK_REQUISITE_CODE " + "SYSRES_CONST_EDOC_CERTIFICATE_AND_PASSWORD_ENCODE_CODE " + "SYSRES_CONST_EDOC_CERTIFICATE_ENCODE_CODE " + "SYSRES_CONST_EDOC_DATE_REQUISITE_CODE " + "SYSRES_CONST_EDOC_KIND_REFERENCE_CODE " + "SYSRES_CONST_EDOC_KINDS_BY_TEMPLATE_ACTION_CODE " + "SYSRES_CONST_EDOC_MANAGE_ACCESS_CODE " + "SYSRES_CONST_EDOC_NONE_ENCODE_CODE " + "SYSRES_CONST_EDOC_NUMBER_REQUISITE_CODE " + "SYSRES_CONST_EDOC_PASSWORD_ENCODE_CODE " + "SYSRES_CONST_EDOC_READONLY_ACCESS_CODE " + "SYSRES_CONST_EDOC_SHELL_LIFE_TYPE_VIEW_VALUE " + "SYSRES_CONST_EDOC_SIZE_RESTRICTION_PRIORITY_REQUISITE_CODE " + "SYSRES_CONST_EDOC_STORAGE_CHECK_ACCESS_RIGHTS_REQUISITE_CODE " + "SYSRES_CONST_EDOC_STORAGE_COMPUTER_NAME_REQUISITE_CODE " + "SYSRES_CONST_EDOC_STORAGE_DATABASE_NAME_REQUISITE_CODE " + "SYSRES_CONST_EDOC_STORAGE_EDIT_IN_STORAGE_REQUISITE_CODE " + "SYSRES_CONST_EDOC_STORAGE_LOCAL_PATH_REQUISITE_CODE " + "SYSRES_CONST_EDOC_STORAGE_SHARED_SOURCE_NAME_REQUISITE_CODE " + "SYSRES_CONST_EDOC_TEMPLATE_REQUISITE_CODE " + "SYSRES_CONST_EDOC_TYPES_REFERENCE_CODE " + "SYSRES_CONST_EDOC_VERSION_ACTIVE_STAGE_CODE " + "SYSRES_CONST_EDOC_VERSION_DESIGN_STAGE_CODE " + "SYSRES_CONST_EDOC_VERSION_OBSOLETE_STAGE_CODE " + "SYSRES_CONST_EDOC_WRITE_ACCES_CODE " + "SYSRES_CONST_EDOCUMENT_CARD_REQUISITES_REFERENCE_CODE_SELECTED_REQUISITE " + "SYSRES_CONST_ENCODE_CERTIFICATE_TYPE_CODE " + "SYSRES_CONST_END_DATE_REQUISITE_CODE " + "SYSRES_CONST_ENUMERATION_TYPE_REQUISITE_CODE " + "SYSRES_CONST_EXECUTE_ACCESS_RIGHTS_TYPE_CODE " + "SYSRES_CONST_EXECUTIVE_FILE_STORAGE_TYPE " + "SYSRES_CONST_EXIST_CONST " + "SYSRES_CONST_EXIST_VALUE " + "SYSRES_CONST_EXPORT_LOCK_TYPE_ASK " + "SYSRES_CONST_EXPORT_LOCK_TYPE_WITH_LOCK " + "SYSRES_CONST_EXPORT_LOCK_TYPE_WITHOUT_LOCK " + "SYSRES_CONST_EXPORT_VERSION_TYPE_ASK " + "SYSRES_CONST_EXPORT_VERSION_TYPE_LAST " + "SYSRES_CONST_EXPORT_VERSION_TYPE_LAST_ACTIVE " + "SYSRES_CONST_EXTENSION_REQUISITE_CODE " + "SYSRES_CONST_FILTER_NAME_REQUISITE_CODE " + "SYSRES_CONST_FILTER_REQUISITE_CODE " + "SYSRES_CONST_FILTER_TYPE_COMMON_CODE " + "SYSRES_CONST_FILTER_TYPE_COMMON_NAME " + "SYSRES_CONST_FILTER_TYPE_USER_CODE " + "SYSRES_CONST_FILTER_TYPE_USER_NAME " + "SYSRES_CONST_FILTER_VALUE_REQUISITE_NAME " + "SYSRES_CONST_FLOAT_NUMBER_FORMAT_CHAR " + "SYSRES_CONST_FLOAT_REQUISITE_TYPE " + "SYSRES_CONST_FOLDER_AUTHOR_VALUE " + "SYSRES_CONST_FOLDER_KIND_ANY_OBJECTS " + "SYSRES_CONST_FOLDER_KIND_COMPONENTS " + "SYSRES_CONST_FOLDER_KIND_EDOCS " + "SYSRES_CONST_FOLDER_KIND_JOBS " + "SYSRES_CONST_FOLDER_KIND_TASKS " + "SYSRES_CONST_FOLDER_TYPE_COMMON " + "SYSRES_CONST_FOLDER_TYPE_COMPONENT " + "SYSRES_CONST_FOLDER_TYPE_FAVORITES " + "SYSRES_CONST_FOLDER_TYPE_INBOX " + "SYSRES_CONST_FOLDER_TYPE_OUTBOX " + "SYSRES_CONST_FOLDER_TYPE_QUICK_LAUNCH " + "SYSRES_CONST_FOLDER_TYPE_SEARCH " + "SYSRES_CONST_FOLDER_TYPE_SHORTCUTS " + "SYSRES_CONST_FOLDER_TYPE_USER " + "SYSRES_CONST_FROM_DICTIONARY_ENUM_METHOD_FLAG " + "SYSRES_CONST_FULL_SUBSTITUTE_TYPE " + "SYSRES_CONST_FULL_SUBSTITUTE_TYPE_CODE " + "SYSRES_CONST_FUNCTION_CANCEL_RESULT " + "SYSRES_CONST_FUNCTION_CATEGORY_SYSTEM " + "SYSRES_CONST_FUNCTION_CATEGORY_USER " + "SYSRES_CONST_FUNCTION_FAILURE_RESULT " + "SYSRES_CONST_FUNCTION_SAVE_RESULT " + "SYSRES_CONST_GENERATED_REQUISITE " + "SYSRES_CONST_GREEN_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_GROUP_ACCOUNT_TYPE_VALUE_CODE " + "SYSRES_CONST_GROUP_CATEGORY_NORMAL_CODE " + "SYSRES_CONST_GROUP_CATEGORY_NORMAL_NAME " + "SYSRES_CONST_GROUP_CATEGORY_SERVICE_CODE " + "SYSRES_CONST_GROUP_CATEGORY_SERVICE_NAME " + "SYSRES_CONST_GROUP_COMMON_CATEGORY_FIELD_VALUE " + "SYSRES_CONST_GROUP_FULL_NAME_REQUISITE_CODE " + "SYSRES_CONST_GROUP_NAME_REQUISITE_CODE " + "SYSRES_CONST_GROUP_RIGHTS_T_REQUISITE_CODE " + "SYSRES_CONST_GROUP_SERVER_CODES_REQUISITE_CODE " + "SYSRES_CONST_GROUP_SERVER_NAME_REQUISITE_CODE " + "SYSRES_CONST_GROUP_SERVICE_CATEGORY_FIELD_VALUE " + "SYSRES_CONST_GROUP_USER_REQUISITE_CODE " + "SYSRES_CONST_GROUPS_REFERENCE_CODE " + "SYSRES_CONST_GROUPS_REQUISITE_CODE " + "SYSRES_CONST_HIDDEN_MODE_NAME " + "SYSRES_CONST_HIGH_LVL_REQUISITE_CODE " + "SYSRES_CONST_HISTORY_ACTION_CREATE_CODE " + "SYSRES_CONST_HISTORY_ACTION_DELETE_CODE " + "SYSRES_CONST_HISTORY_ACTION_EDIT_CODE " + "SYSRES_CONST_HOUR_CHAR " + "SYSRES_CONST_ID_REQUISITE_CODE " + "SYSRES_CONST_IDSPS_REQUISITE_CODE " + "SYSRES_CONST_IMAGE_MODE_COLOR " + "SYSRES_CONST_IMAGE_MODE_GREYSCALE " + "SYSRES_CONST_IMAGE_MODE_MONOCHROME " + "SYSRES_CONST_IMPORTANCE_HIGH " + "SYSRES_CONST_IMPORTANCE_LOW " + "SYSRES_CONST_IMPORTANCE_NORMAL " + "SYSRES_CONST_IN_DESIGN_VERSION_STATE_PICK_VALUE " + "SYSRES_CONST_INCOMING_WORK_RULE_TYPE_CODE " + "SYSRES_CONST_INT_REQUISITE " + "SYSRES_CONST_INT_REQUISITE_TYPE " + "SYSRES_CONST_INTEGER_NUMBER_FORMAT_CHAR " + "SYSRES_CONST_INTEGER_TYPE_CHAR " + "SYSRES_CONST_IS_GENERATED_REQUISITE_NEGATIVE_VALUE " + "SYSRES_CONST_IS_PUBLIC_ROLE_REQUISITE_CODE " + "SYSRES_CONST_IS_REMOTE_USER_NEGATIVE_VALUE " + "SYSRES_CONST_IS_REMOTE_USER_POSITIVE_VALUE " + "SYSRES_CONST_IS_STORED_REQUISITE_NEGATIVE_VALUE " + "SYSRES_CONST_IS_STORED_REQUISITE_STORED_VALUE " + "SYSRES_CONST_ITALIC_LIFE_CYCLE_STAGE_DRAW_STYLE " + "SYSRES_CONST_JOB_BLOCK_DESCRIPTION " + "SYSRES_CONST_JOB_KIND_CONTROL_JOB " + "SYSRES_CONST_JOB_KIND_JOB " + "SYSRES_CONST_JOB_KIND_NOTICE " + "SYSRES_CONST_JOB_STATE_ABORTED " + "SYSRES_CONST_JOB_STATE_COMPLETE " + "SYSRES_CONST_JOB_STATE_WORKING " + "SYSRES_CONST_KIND_REQUISITE_CODE " + "SYSRES_CONST_KIND_REQUISITE_NAME " + "SYSRES_CONST_KINDS_CREATE_SHADOW_COPIES_REQUISITE_CODE " + "SYSRES_CONST_KINDS_DEFAULT_EDOC_LIFE_STAGE_REQUISITE_CODE " + "SYSRES_CONST_KINDS_EDOC_ALL_TEPLATES_ALLOWED_REQUISITE_CODE " + "SYSRES_CONST_KINDS_EDOC_ALLOW_LIFE_CYCLE_STAGE_CHANGING_REQUISITE_CODE " + "SYSRES_CONST_KINDS_EDOC_ALLOW_MULTIPLE_ACTIVE_VERSIONS_REQUISITE_CODE " + "SYSRES_CONST_KINDS_EDOC_SHARE_ACCES_RIGHTS_BY_DEFAULT_CODE " + "SYSRES_CONST_KINDS_EDOC_TEMPLATE_REQUISITE_CODE " + "SYSRES_CONST_KINDS_EDOC_TYPE_REQUISITE_CODE " + "SYSRES_CONST_KINDS_SIGNERS_REQUISITES_CODE " + "SYSRES_CONST_KOD_INPUT_TYPE " + "SYSRES_CONST_LAST_UPDATE_DATE_REQUISITE_CODE " + "SYSRES_CONST_LIFE_CYCLE_START_STAGE_REQUISITE_CODE " + "SYSRES_CONST_LILAC_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_LINK_OBJECT_KIND_COMPONENT " + "SYSRES_CONST_LINK_OBJECT_KIND_DOCUMENT " + "SYSRES_CONST_LINK_OBJECT_KIND_EDOC " + "SYSRES_CONST_LINK_OBJECT_KIND_FOLDER " + "SYSRES_CONST_LINK_OBJECT_KIND_JOB " + "SYSRES_CONST_LINK_OBJECT_KIND_REFERENCE " + "SYSRES_CONST_LINK_OBJECT_KIND_TASK " + "SYSRES_CONST_LINK_REF_TYPE_REQUISITE_CODE " + "SYSRES_CONST_LIST_REFERENCE_MODE_NAME " + "SYSRES_CONST_LOCALIZATION_DICTIONARY_MAIN_VIEW_CODE " + "SYSRES_CONST_MAIN_VIEW_CODE " + "SYSRES_CONST_MANUAL_ENUM_METHOD_FLAG " + "SYSRES_CONST_MASTER_COMP_TYPE_REQUISITE_CODE " + "SYSRES_CONST_MASTER_TABLE_REC_ID_REQUISITE_CODE " + "SYSRES_CONST_MAXIMIZED_MODE_NAME " + "SYSRES_CONST_ME_VALUE " + "SYSRES_CONST_MESSAGE_ATTENTION_CAPTION " + "SYSRES_CONST_MESSAGE_CONFIRMATION_CAPTION " + "SYSRES_CONST_MESSAGE_ERROR_CAPTION " + "SYSRES_CONST_MESSAGE_INFORMATION_CAPTION " + "SYSRES_CONST_MINIMIZED_MODE_NAME " + "SYSRES_CONST_MINUTE_CHAR " + "SYSRES_CONST_MODULE_REQUISITE_CODE " + "SYSRES_CONST_MONITORING_BLOCK_DESCRIPTION " + "SYSRES_CONST_MONTH_FORMAT_VALUE " + "SYSRES_CONST_NAME_LOCALIZE_ID_REQUISITE_CODE " + "SYSRES_CONST_NAME_REQUISITE_CODE " + "SYSRES_CONST_NAME_SINGULAR_REQUISITE_CODE " + "SYSRES_CONST_NAMEAN_INPUT_TYPE " + "SYSRES_CONST_NEGATIVE_PICK_VALUE " + "SYSRES_CONST_NEGATIVE_VALUE " + "SYSRES_CONST_NO " + "SYSRES_CONST_NO_PICK_VALUE " + "SYSRES_CONST_NO_SIGNATURE_REQUISITE_CODE " + "SYSRES_CONST_NO_VALUE " + "SYSRES_CONST_NONE_ACCESS_RIGHTS_TYPE_CODE " + "SYSRES_CONST_NONOPERATING_RECORD_FLAG_VALUE " + "SYSRES_CONST_NONOPERATING_RECORD_FLAG_VALUE_MASCULINE " + "SYSRES_CONST_NORMAL_ACCESS_RIGHTS_TYPE_CODE " + "SYSRES_CONST_NORMAL_LIFE_CYCLE_STAGE_DRAW_STYLE " + "SYSRES_CONST_NORMAL_MODE_NAME " + "SYSRES_CONST_NOT_ALLOWED_ACCESS_TYPE_CODE " + "SYSRES_CONST_NOT_ALLOWED_ACCESS_TYPE_NAME " + "SYSRES_CONST_NOTE_REQUISITE_CODE " + "SYSRES_CONST_NOTICE_BLOCK_DESCRIPTION " + "SYSRES_CONST_NUM_REQUISITE " + "SYSRES_CONST_NUM_STR_REQUISITE_CODE " + "SYSRES_CONST_NUMERATION_AUTO_NOT_STRONG " + "SYSRES_CONST_NUMERATION_AUTO_STRONG " + "SYSRES_CONST_NUMERATION_FROM_DICTONARY " + "SYSRES_CONST_NUMERATION_MANUAL " + "SYSRES_CONST_NUMERIC_TYPE_CHAR " + "SYSRES_CONST_NUMREQ_REQUISITE_CODE " + "SYSRES_CONST_OBSOLETE_VERSION_STATE_PICK_VALUE " + "SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE " + "SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_CODE " + "SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_FEMININE " + "SYSRES_CONST_OPERATING_RECORD_FLAG_VALUE_MASCULINE " + "SYSRES_CONST_OPTIONAL_FORM_COMP_REQCODE_PREFIX " + "SYSRES_CONST_ORANGE_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_ORIGINALREF_REQUISITE_CODE " + "SYSRES_CONST_OURFIRM_REF_CODE " + "SYSRES_CONST_OURFIRM_REQUISITE_CODE " + "SYSRES_CONST_OURFIRM_VAR " + "SYSRES_CONST_OUTGOING_WORK_RULE_TYPE_CODE " + "SYSRES_CONST_PICK_NEGATIVE_RESULT " + "SYSRES_CONST_PICK_POSITIVE_RESULT " + "SYSRES_CONST_PICK_REQUISITE " + "SYSRES_CONST_PICK_REQUISITE_TYPE " + "SYSRES_CONST_PICK_TYPE_CHAR " + "SYSRES_CONST_PLAN_STATUS_REQUISITE_CODE " + "SYSRES_CONST_PLATFORM_VERSION_COMMENT " + "SYSRES_CONST_PLUGINS_SETTINGS_DESCRIPTION_REQUISITE_CODE " + "SYSRES_CONST_POSITIVE_PICK_VALUE " + "SYSRES_CONST_POWER_TO_CREATE_ACTION_CODE " + "SYSRES_CONST_POWER_TO_SIGN_ACTION_CODE " + "SYSRES_CONST_PRIORITY_REQUISITE_CODE " + "SYSRES_CONST_QUALIFIED_TASK_TYPE " + "SYSRES_CONST_QUALIFIED_TASK_TYPE_CODE " + "SYSRES_CONST_RECSTAT_REQUISITE_CODE " + "SYSRES_CONST_RED_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_REF_ID_T_REF_TYPE_REQUISITE_CODE " + "SYSRES_CONST_REF_REQUISITE " + "SYSRES_CONST_REF_REQUISITE_TYPE " + "SYSRES_CONST_REF_REQUISITES_REFERENCE_CODE_SELECTED_REQUISITE " + "SYSRES_CONST_REFERENCE_RECORD_HISTORY_CREATE_ACTION_CODE " + "SYSRES_CONST_REFERENCE_RECORD_HISTORY_DELETE_ACTION_CODE " + "SYSRES_CONST_REFERENCE_RECORD_HISTORY_MODIFY_ACTION_CODE " + "SYSRES_CONST_REFERENCE_TYPE_CHAR " + "SYSRES_CONST_REFERENCE_TYPE_REQUISITE_NAME " + "SYSRES_CONST_REFERENCES_ADD_PARAMS_REQUISITE_CODE " + "SYSRES_CONST_REFERENCES_DISPLAY_REQUISITE_REQUISITE_CODE " + "SYSRES_CONST_REMOTE_SERVER_STATUS_WORKING " + "SYSRES_CONST_REMOTE_SERVER_TYPE_MAIN " + "SYSRES_CONST_REMOTE_SERVER_TYPE_SECONDARY " + "SYSRES_CONST_REMOTE_USER_FLAG_VALUE_CODE " + "SYSRES_CONST_REPORT_APP_EDITOR_INTERNAL " + "SYSRES_CONST_REPORT_BASE_REPORT_ID_REQUISITE_CODE " + "SYSRES_CONST_REPORT_BASE_REPORT_REQUISITE_CODE " + "SYSRES_CONST_REPORT_SCRIPT_REQUISITE_CODE " + "SYSRES_CONST_REPORT_TEMPLATE_REQUISITE_CODE " + "SYSRES_CONST_REPORT_VIEWER_CODE_REQUISITE_CODE " + "SYSRES_CONST_REQ_ALLOW_COMPONENT_DEFAULT_VALUE " + "SYSRES_CONST_REQ_ALLOW_RECORD_DEFAULT_VALUE " + "SYSRES_CONST_REQ_ALLOW_SERVER_COMPONENT_DEFAULT_VALUE " + "SYSRES_CONST_REQ_MODE_AVAILABLE_CODE " + "SYSRES_CONST_REQ_MODE_EDIT_CODE " + "SYSRES_CONST_REQ_MODE_HIDDEN_CODE " + "SYSRES_CONST_REQ_MODE_NOT_AVAILABLE_CODE " + "SYSRES_CONST_REQ_MODE_VIEW_CODE " + "SYSRES_CONST_REQ_NUMBER_REQUISITE_CODE " + "SYSRES_CONST_REQ_SECTION_VALUE " + "SYSRES_CONST_REQ_TYPE_VALUE " + "SYSRES_CONST_REQUISITE_FORMAT_BY_UNIT " + "SYSRES_CONST_REQUISITE_FORMAT_DATE_FULL " + "SYSRES_CONST_REQUISITE_FORMAT_DATE_TIME " + "SYSRES_CONST_REQUISITE_FORMAT_LEFT " + "SYSRES_CONST_REQUISITE_FORMAT_RIGHT " + "SYSRES_CONST_REQUISITE_FORMAT_WITHOUT_UNIT " + "SYSRES_CONST_REQUISITE_NUMBER_REQUISITE_CODE " + "SYSRES_CONST_REQUISITE_SECTION_ACTIONS " + "SYSRES_CONST_REQUISITE_SECTION_BUTTON " + "SYSRES_CONST_REQUISITE_SECTION_BUTTONS " + "SYSRES_CONST_REQUISITE_SECTION_CARD " + "SYSRES_CONST_REQUISITE_SECTION_TABLE " + "SYSRES_CONST_REQUISITE_SECTION_TABLE10 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE11 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE12 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE13 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE14 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE15 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE16 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE17 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE18 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE19 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE2 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE20 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE21 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE22 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE23 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE24 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE3 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE4 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE5 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE6 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE7 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE8 " + "SYSRES_CONST_REQUISITE_SECTION_TABLE9 " + "SYSRES_CONST_REQUISITES_PSEUDOREFERENCE_REQUISITE_NUMBER_REQUISITE_CODE " + "SYSRES_CONST_RIGHT_ALIGNMENT_CODE " + "SYSRES_CONST_ROLES_REFERENCE_CODE " + "SYSRES_CONST_ROUTE_STEP_AFTER_RUS " + "SYSRES_CONST_ROUTE_STEP_AND_CONDITION_RUS " + "SYSRES_CONST_ROUTE_STEP_OR_CONDITION_RUS " + "SYSRES_CONST_ROUTE_TYPE_COMPLEX " + "SYSRES_CONST_ROUTE_TYPE_PARALLEL " + "SYSRES_CONST_ROUTE_TYPE_SERIAL " + "SYSRES_CONST_SBDATASETDESC_NEGATIVE_VALUE " + "SYSRES_CONST_SBDATASETDESC_POSITIVE_VALUE " + "SYSRES_CONST_SBVIEWSDESC_POSITIVE_VALUE " + "SYSRES_CONST_SCRIPT_BLOCK_DESCRIPTION " + "SYSRES_CONST_SEARCH_BY_TEXT_REQUISITE_CODE " + "SYSRES_CONST_SEARCHES_COMPONENT_CONTENT " + "SYSRES_CONST_SEARCHES_CRITERIA_ACTION_NAME " + "SYSRES_CONST_SEARCHES_EDOC_CONTENT " + "SYSRES_CONST_SEARCHES_FOLDER_CONTENT " + "SYSRES_CONST_SEARCHES_JOB_CONTENT " + "SYSRES_CONST_SEARCHES_REFERENCE_CODE " + "SYSRES_CONST_SEARCHES_TASK_CONTENT " + "SYSRES_CONST_SECOND_CHAR " + "SYSRES_CONST_SECTION_REQUISITE_ACTIONS_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_CARD_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_CODE " + "SYSRES_CONST_SECTION_REQUISITE_DETAIL_1_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_DETAIL_2_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_DETAIL_3_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_DETAIL_4_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_DETAIL_5_VALUE " + "SYSRES_CONST_SECTION_REQUISITE_DETAIL_6_VALUE " + "SYSRES_CONST_SELECT_REFERENCE_MODE_NAME " + "SYSRES_CONST_SELECT_TYPE_SELECTABLE " + "SYSRES_CONST_SELECT_TYPE_SELECTABLE_ONLY_CHILD " + "SYSRES_CONST_SELECT_TYPE_SELECTABLE_WITH_CHILD " + "SYSRES_CONST_SELECT_TYPE_UNSLECTABLE " + "SYSRES_CONST_SERVER_TYPE_MAIN " + "SYSRES_CONST_SERVICE_USER_CATEGORY_FIELD_VALUE " + "SYSRES_CONST_SETTINGS_USER_REQUISITE_CODE " + "SYSRES_CONST_SIGNATURE_AND_ENCODE_CERTIFICATE_TYPE_CODE " + "SYSRES_CONST_SIGNATURE_CERTIFICATE_TYPE_CODE " + "SYSRES_CONST_SINGULAR_TITLE_REQUISITE_CODE " + "SYSRES_CONST_SQL_SERVER_AUTHENTIFICATION_FLAG_VALUE_CODE " + "SYSRES_CONST_SQL_SERVER_ENCODE_AUTHENTIFICATION_FLAG_VALUE_CODE " + "SYSRES_CONST_STANDART_ROUTE_REFERENCE_CODE " + "SYSRES_CONST_STANDART_ROUTE_REFERENCE_COMMENT_REQUISITE_CODE " + "SYSRES_CONST_STANDART_ROUTES_GROUPS_REFERENCE_CODE " + "SYSRES_CONST_STATE_REQ_NAME " + "SYSRES_CONST_STATE_REQUISITE_ACTIVE_VALUE " + "SYSRES_CONST_STATE_REQUISITE_CLOSED_VALUE " + "SYSRES_CONST_STATE_REQUISITE_CODE " + "SYSRES_CONST_STATIC_ROLE_TYPE_CODE " + "SYSRES_CONST_STATUS_PLAN_DEFAULT_VALUE " + "SYSRES_CONST_STATUS_VALUE_AUTOCLEANING " + "SYSRES_CONST_STATUS_VALUE_BLUE_SQUARE " + "SYSRES_CONST_STATUS_VALUE_COMPLETE " + "SYSRES_CONST_STATUS_VALUE_GREEN_SQUARE " + "SYSRES_CONST_STATUS_VALUE_ORANGE_SQUARE " + "SYSRES_CONST_STATUS_VALUE_PURPLE_SQUARE " + "SYSRES_CONST_STATUS_VALUE_RED_SQUARE " + "SYSRES_CONST_STATUS_VALUE_SUSPEND " + "SYSRES_CONST_STATUS_VALUE_YELLOW_SQUARE " + "SYSRES_CONST_STDROUTE_SHOW_TO_USERS_REQUISITE_CODE " + "SYSRES_CONST_STORAGE_TYPE_FILE " + "SYSRES_CONST_STORAGE_TYPE_SQL_SERVER " + "SYSRES_CONST_STR_REQUISITE " + "SYSRES_CONST_STRIKEOUT_LIFE_CYCLE_STAGE_DRAW_STYLE " + "SYSRES_CONST_STRING_FORMAT_LEFT_ALIGN_CHAR " + "SYSRES_CONST_STRING_FORMAT_RIGHT_ALIGN_CHAR " + "SYSRES_CONST_STRING_REQUISITE_CODE " + "SYSRES_CONST_STRING_REQUISITE_TYPE " + "SYSRES_CONST_STRING_TYPE_CHAR " + "SYSRES_CONST_SUBSTITUTES_PSEUDOREFERENCE_CODE " + "SYSRES_CONST_SUBTASK_BLOCK_DESCRIPTION " + "SYSRES_CONST_SYSTEM_SETTING_CURRENT_USER_PARAM_VALUE " + "SYSRES_CONST_SYSTEM_SETTING_EMPTY_VALUE_PARAM_VALUE " + "SYSRES_CONST_SYSTEM_VERSION_COMMENT " + "SYSRES_CONST_TASK_ACCESS_TYPE_ALL " + "SYSRES_CONST_TASK_ACCESS_TYPE_ALL_MEMBERS " + "SYSRES_CONST_TASK_ACCESS_TYPE_MANUAL " + "SYSRES_CONST_TASK_ENCODE_TYPE_CERTIFICATION " + "SYSRES_CONST_TASK_ENCODE_TYPE_CERTIFICATION_AND_PASSWORD " + "SYSRES_CONST_TASK_ENCODE_TYPE_NONE " + "SYSRES_CONST_TASK_ENCODE_TYPE_PASSWORD " + "SYSRES_CONST_TASK_ROUTE_ALL_CONDITION " + "SYSRES_CONST_TASK_ROUTE_AND_CONDITION " + "SYSRES_CONST_TASK_ROUTE_OR_CONDITION " + "SYSRES_CONST_TASK_STATE_ABORTED " + "SYSRES_CONST_TASK_STATE_COMPLETE " + "SYSRES_CONST_TASK_STATE_CONTINUED " + "SYSRES_CONST_TASK_STATE_CONTROL " + "SYSRES_CONST_TASK_STATE_INIT " + "SYSRES_CONST_TASK_STATE_WORKING " + "SYSRES_CONST_TASK_TITLE " + "SYSRES_CONST_TASK_TYPES_GROUPS_REFERENCE_CODE " + "SYSRES_CONST_TASK_TYPES_REFERENCE_CODE " + "SYSRES_CONST_TEMPLATES_REFERENCE_CODE " + "SYSRES_CONST_TEST_DATE_REQUISITE_NAME " + "SYSRES_CONST_TEST_DEV_DATABASE_NAME " + "SYSRES_CONST_TEST_DEV_SYSTEM_CODE " + "SYSRES_CONST_TEST_EDMS_DATABASE_NAME " + "SYSRES_CONST_TEST_EDMS_MAIN_CODE " + "SYSRES_CONST_TEST_EDMS_MAIN_DB_NAME " + "SYSRES_CONST_TEST_EDMS_SECOND_CODE " + "SYSRES_CONST_TEST_EDMS_SECOND_DB_NAME " + "SYSRES_CONST_TEST_EDMS_SYSTEM_CODE " + "SYSRES_CONST_TEST_NUMERIC_REQUISITE_NAME " + "SYSRES_CONST_TEXT_REQUISITE " + "SYSRES_CONST_TEXT_REQUISITE_CODE " + "SYSRES_CONST_TEXT_REQUISITE_TYPE " + "SYSRES_CONST_TEXT_TYPE_CHAR " + "SYSRES_CONST_TYPE_CODE_REQUISITE_CODE " + "SYSRES_CONST_TYPE_REQUISITE_CODE " + "SYSRES_CONST_UNDEFINED_LIFE_CYCLE_STAGE_FONT_COLOR " + "SYSRES_CONST_UNITS_SECTION_ID_REQUISITE_CODE " + "SYSRES_CONST_UNITS_SECTION_REQUISITE_CODE " + "SYSRES_CONST_UNOPERATING_RECORD_FLAG_VALUE_CODE " + "SYSRES_CONST_UNSTORED_DATA_REQUISITE_CODE " + "SYSRES_CONST_UNSTORED_DATA_REQUISITE_NAME " + "SYSRES_CONST_USE_ACCESS_TYPE_CODE " + "SYSRES_CONST_USE_ACCESS_TYPE_NAME " + "SYSRES_CONST_USER_ACCOUNT_TYPE_VALUE_CODE " + "SYSRES_CONST_USER_ADDITIONAL_INFORMATION_REQUISITE_CODE " + "SYSRES_CONST_USER_AND_GROUP_ID_FROM_PSEUDOREFERENCE_REQUISITE_CODE " + "SYSRES_CONST_USER_CATEGORY_NORMAL " + "SYSRES_CONST_USER_CERTIFICATE_REQUISITE_CODE " + "SYSRES_CONST_USER_CERTIFICATE_STATE_REQUISITE_CODE " + "SYSRES_CONST_USER_CERTIFICATE_SUBJECT_NAME_REQUISITE_CODE " + "SYSRES_CONST_USER_CERTIFICATE_THUMBPRINT_REQUISITE_CODE " + "SYSRES_CONST_USER_COMMON_CATEGORY " + "SYSRES_CONST_USER_COMMON_CATEGORY_CODE " + "SYSRES_CONST_USER_FULL_NAME_REQUISITE_CODE " + "SYSRES_CONST_USER_GROUP_TYPE_REQUISITE_CODE " + "SYSRES_CONST_USER_LOGIN_REQUISITE_CODE " + "SYSRES_CONST_USER_REMOTE_CONTROLLER_REQUISITE_CODE " + "SYSRES_CONST_USER_REMOTE_SYSTEM_REQUISITE_CODE " + "SYSRES_CONST_USER_RIGHTS_T_REQUISITE_CODE " + "SYSRES_CONST_USER_SERVER_NAME_REQUISITE_CODE " + "SYSRES_CONST_USER_SERVICE_CATEGORY " + "SYSRES_CONST_USER_SERVICE_CATEGORY_CODE " + "SYSRES_CONST_USER_STATUS_ADMINISTRATOR_CODE " + "SYSRES_CONST_USER_STATUS_ADMINISTRATOR_NAME " + "SYSRES_CONST_USER_STATUS_DEVELOPER_CODE " + "SYSRES_CONST_USER_STATUS_DEVELOPER_NAME " + "SYSRES_CONST_USER_STATUS_DISABLED_CODE " + "SYSRES_CONST_USER_STATUS_DISABLED_NAME " + "SYSRES_CONST_USER_STATUS_SYSTEM_DEVELOPER_CODE " + "SYSRES_CONST_USER_STATUS_USER_CODE " + "SYSRES_CONST_USER_STATUS_USER_NAME " + "SYSRES_CONST_USER_STATUS_USER_NAME_DEPRECATED " + "SYSRES_CONST_USER_TYPE_FIELD_VALUE_USER " + "SYSRES_CONST_USER_TYPE_REQUISITE_CODE " + "SYSRES_CONST_USERS_CONTROLLER_REQUISITE_CODE " + "SYSRES_CONST_USERS_IS_MAIN_SERVER_REQUISITE_CODE " + "SYSRES_CONST_USERS_REFERENCE_CODE " + "SYSRES_CONST_USERS_REGISTRATION_CERTIFICATES_ACTION_NAME " + "SYSRES_CONST_USERS_REQUISITE_CODE " + "SYSRES_CONST_USERS_SYSTEM_REQUISITE_CODE " + "SYSRES_CONST_USERS_USER_ACCESS_RIGHTS_TYPR_REQUISITE_CODE " + "SYSRES_CONST_USERS_USER_AUTHENTICATION_REQUISITE_CODE " + "SYSRES_CONST_USERS_USER_COMPONENT_REQUISITE_CODE " + "SYSRES_CONST_USERS_USER_GROUP_REQUISITE_CODE " + "SYSRES_CONST_USERS_VIEW_CERTIFICATES_ACTION_NAME " + "SYSRES_CONST_VIEW_DEFAULT_CODE " + "SYSRES_CONST_VIEW_DEFAULT_NAME " + "SYSRES_CONST_VIEWER_REQUISITE_CODE " + "SYSRES_CONST_WAITING_BLOCK_DESCRIPTION " + "SYSRES_CONST_WIZARD_FORM_LABEL_TEST_STRING " + "SYSRES_CONST_WIZARD_QUERY_PARAM_HEIGHT_ETALON_STRING " + "SYSRES_CONST_WIZARD_REFERENCE_COMMENT_REQUISITE_CODE " + "SYSRES_CONST_WORK_RULES_DESCRIPTION_REQUISITE_CODE " + "SYSRES_CONST_WORK_TIME_CALENDAR_REFERENCE_CODE " + "SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE " + "SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE_CODE " + "SYSRES_CONST_WORK_WORKFLOW_HARD_ROUTE_TYPE_VALUE_CODE_RUS " + "SYSRES_CONST_WORK_WORKFLOW_SOFT_ROUTE_TYPE_VALUE_CODE_RUS " + "SYSRES_CONST_WORKFLOW_ROUTE_TYPR_HARD " + "SYSRES_CONST_WORKFLOW_ROUTE_TYPR_SOFT " + "SYSRES_CONST_XML_ENCODING " + "SYSRES_CONST_XREC_STAT_REQUISITE_CODE " + "SYSRES_CONST_XRECID_FIELD_NAME " + "SYSRES_CONST_YES " + "SYSRES_CONST_YES_NO_2_REQUISITE_CODE " + "SYSRES_CONST_YES_NO_REQUISITE_CODE " + "SYSRES_CONST_YES_NO_T_REF_TYPE_REQUISITE_CODE " + "SYSRES_CONST_YES_PICK_VALUE " + "SYSRES_CONST_YES_VALUE "; // Base constant var base_constants = "CR FALSE nil NO_VALUE NULL TAB TRUE YES_VALUE "; // Base group name var base_group_name_constants = "ADMINISTRATORS_GROUP_NAME CUSTOMIZERS_GROUP_NAME DEVELOPERS_GROUP_NAME SERVICE_USERS_GROUP_NAME "; // Decision block properties var decision_block_properties_constants = "DECISION_BLOCK_FIRST_OPERAND_PROPERTY DECISION_BLOCK_NAME_PROPERTY DECISION_BLOCK_OPERATION_PROPERTY " + "DECISION_BLOCK_RESULT_TYPE_PROPERTY DECISION_BLOCK_SECOND_OPERAND_PROPERTY "; // File extension var file_extension_constants = "ANY_FILE_EXTENTION COMPRESSED_DOCUMENT_EXTENSION EXTENDED_DOCUMENT_EXTENSION " + "SHORT_COMPRESSED_DOCUMENT_EXTENSION SHORT_EXTENDED_DOCUMENT_EXTENSION "; // Job block properties var job_block_properties_constants = "JOB_BLOCK_ABORT_DEADLINE_PROPERTY " + "JOB_BLOCK_AFTER_FINISH_EVENT " + "JOB_BLOCK_AFTER_QUERY_PARAMETERS_EVENT " + "JOB_BLOCK_ATTACHMENT_PROPERTY " + "JOB_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY " + "JOB_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY " + "JOB_BLOCK_BEFORE_QUERY_PARAMETERS_EVENT " + "JOB_BLOCK_BEFORE_START_EVENT " + "JOB_BLOCK_CREATED_JOBS_PROPERTY " + "JOB_BLOCK_DEADLINE_PROPERTY " + "JOB_BLOCK_EXECUTION_RESULTS_PROPERTY " + "JOB_BLOCK_IS_PARALLEL_PROPERTY " + "JOB_BLOCK_IS_RELATIVE_ABORT_DEADLINE_PROPERTY " + "JOB_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY " + "JOB_BLOCK_JOB_TEXT_PROPERTY " + "JOB_BLOCK_NAME_PROPERTY " + "JOB_BLOCK_NEED_SIGN_ON_PERFORM_PROPERTY " + "JOB_BLOCK_PERFORMER_PROPERTY " + "JOB_BLOCK_RELATIVE_ABORT_DEADLINE_TYPE_PROPERTY " + "JOB_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY " + "JOB_BLOCK_SUBJECT_PROPERTY "; // Language code var language_code_constants = "ENGLISH_LANGUAGE_CODE RUSSIAN_LANGUAGE_CODE "; // Launching external applications var launching_external_applications_constants = "smHidden smMaximized smMinimized smNormal wmNo wmYes "; // Link kind var link_kind_constants = "COMPONENT_TOKEN_LINK_KIND " + "DOCUMENT_LINK_KIND " + "EDOCUMENT_LINK_KIND " + "FOLDER_LINK_KIND " + "JOB_LINK_KIND " + "REFERENCE_LINK_KIND " + "TASK_LINK_KIND "; // Lock type var lock_type_constants = "COMPONENT_TOKEN_LOCK_TYPE EDOCUMENT_VERSION_LOCK_TYPE "; // Monitor block properties var monitor_block_properties_constants = "MONITOR_BLOCK_AFTER_FINISH_EVENT " + "MONITOR_BLOCK_BEFORE_START_EVENT " + "MONITOR_BLOCK_DEADLINE_PROPERTY " + "MONITOR_BLOCK_INTERVAL_PROPERTY " + "MONITOR_BLOCK_INTERVAL_TYPE_PROPERTY " + "MONITOR_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY " + "MONITOR_BLOCK_NAME_PROPERTY " + "MONITOR_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY " + "MONITOR_BLOCK_SEARCH_SCRIPT_PROPERTY "; // Notice block properties var notice_block_properties_constants = "NOTICE_BLOCK_AFTER_FINISH_EVENT " + "NOTICE_BLOCK_ATTACHMENT_PROPERTY " + "NOTICE_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY " + "NOTICE_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY " + "NOTICE_BLOCK_BEFORE_START_EVENT " + "NOTICE_BLOCK_CREATED_NOTICES_PROPERTY " + "NOTICE_BLOCK_DEADLINE_PROPERTY " + "NOTICE_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY " + "NOTICE_BLOCK_NAME_PROPERTY " + "NOTICE_BLOCK_NOTICE_TEXT_PROPERTY " + "NOTICE_BLOCK_PERFORMER_PROPERTY " + "NOTICE_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY " + "NOTICE_BLOCK_SUBJECT_PROPERTY "; // Object events var object_events_constants = "dseAfterCancel " + "dseAfterClose " + "dseAfterDelete " + "dseAfterDeleteOutOfTransaction " + "dseAfterInsert " + "dseAfterOpen " + "dseAfterScroll " + "dseAfterUpdate " + "dseAfterUpdateOutOfTransaction " + "dseBeforeCancel " + "dseBeforeClose " + "dseBeforeDelete " + "dseBeforeDetailUpdate " + "dseBeforeInsert " + "dseBeforeOpen " + "dseBeforeUpdate " + "dseOnAnyRequisiteChange " + "dseOnCloseRecord " + "dseOnDeleteError " + "dseOnOpenRecord " + "dseOnPrepareUpdate " + "dseOnUpdateError " + "dseOnUpdateRatifiedRecord " + "dseOnValidDelete " + "dseOnValidUpdate " + "reOnChange " + "reOnChangeValues " + "SELECTION_BEGIN_ROUTE_EVENT " + "SELECTION_END_ROUTE_EVENT "; // Object params var object_params_constants = "CURRENT_PERIOD_IS_REQUIRED " + "PREVIOUS_CARD_TYPE_NAME " + "SHOW_RECORD_PROPERTIES_FORM "; // Other var other_constants = "ACCESS_RIGHTS_SETTING_DIALOG_CODE " + "ADMINISTRATOR_USER_CODE " + "ANALYTIC_REPORT_TYPE " + "asrtHideLocal " + "asrtHideRemote " + "CALCULATED_ROLE_TYPE_CODE " + "COMPONENTS_REFERENCE_DEVELOPER_VIEW_CODE " + "DCTS_TEST_PROTOCOLS_FOLDER_PATH " + "E_EDOC_VERSION_ALREADY_APPROVINGLY_SIGNED " + "E_EDOC_VERSION_ALREADY_APPROVINGLY_SIGNED_BY_USER " + "E_EDOC_VERSION_ALREDY_SIGNED " + "E_EDOC_VERSION_ALREDY_SIGNED_BY_USER " + "EDOC_TYPES_CODE_REQUISITE_FIELD_NAME " + "EDOCUMENTS_ALIAS_NAME " + "FILES_FOLDER_PATH " + "FILTER_OPERANDS_DELIMITER " + "FILTER_OPERATIONS_DELIMITER " + "FORMCARD_NAME " + "FORMLIST_NAME " + "GET_EXTENDED_DOCUMENT_EXTENSION_CREATION_MODE " + "GET_EXTENDED_DOCUMENT_EXTENSION_IMPORT_MODE " + "INTEGRATED_REPORT_TYPE " + "IS_BUILDER_APPLICATION_ROLE " + "IS_BUILDER_APPLICATION_ROLE2 " + "IS_BUILDER_USERS " + "ISBSYSDEV " + "LOG_FOLDER_PATH " + "mbCancel " + "mbNo " + "mbNoToAll " + "mbOK " + "mbYes " + "mbYesToAll " + "MEMORY_DATASET_DESRIPTIONS_FILENAME " + "mrNo " + "mrNoToAll " + "mrYes " + "mrYesToAll " + "MULTIPLE_SELECT_DIALOG_CODE " + "NONOPERATING_RECORD_FLAG_FEMININE " + "NONOPERATING_RECORD_FLAG_MASCULINE " + "OPERATING_RECORD_FLAG_FEMININE " + "OPERATING_RECORD_FLAG_MASCULINE " + "PROFILING_SETTINGS_COMMON_SETTINGS_CODE_VALUE " + "PROGRAM_INITIATED_LOOKUP_ACTION " + "ratDelete " + "ratEdit " + "ratInsert " + "REPORT_TYPE " + "REQUIRED_PICK_VALUES_VARIABLE " + "rmCard " + "rmList " + "SBRTE_PROGID_DEV " + "SBRTE_PROGID_RELEASE " + "STATIC_ROLE_TYPE_CODE " + "SUPPRESS_EMPTY_TEMPLATE_CREATION " + "SYSTEM_USER_CODE " + "UPDATE_DIALOG_DATASET " + "USED_IN_OBJECT_HINT_PARAM " + "USER_INITIATED_LOOKUP_ACTION " + "USER_NAME_FORMAT " + "USER_SELECTION_RESTRICTIONS " + "WORKFLOW_TEST_PROTOCOLS_FOLDER_PATH " + "ELS_SUBTYPE_CONTROL_NAME " + "ELS_FOLDER_KIND_CONTROL_NAME " + "REPEAT_PROCESS_CURRENT_OBJECT_EXCEPTION_NAME "; // Privileges var privileges_constants = "PRIVILEGE_COMPONENT_FULL_ACCESS " + "PRIVILEGE_DEVELOPMENT_EXPORT " + "PRIVILEGE_DEVELOPMENT_IMPORT " + "PRIVILEGE_DOCUMENT_DELETE " + "PRIVILEGE_ESD " + "PRIVILEGE_FOLDER_DELETE " + "PRIVILEGE_MANAGE_ACCESS_RIGHTS " + "PRIVILEGE_MANAGE_REPLICATION " + "PRIVILEGE_MANAGE_SESSION_SERVER " + "PRIVILEGE_OBJECT_FULL_ACCESS " + "PRIVILEGE_OBJECT_VIEW " + "PRIVILEGE_RESERVE_LICENSE " + "PRIVILEGE_SYSTEM_CUSTOMIZE " + "PRIVILEGE_SYSTEM_DEVELOP " + "PRIVILEGE_SYSTEM_INSTALL " + "PRIVILEGE_TASK_DELETE " + "PRIVILEGE_USER_PLUGIN_SETTINGS_CUSTOMIZE " + "PRIVILEGES_PSEUDOREFERENCE_CODE "; // Pseudoreference code var pseudoreference_code_constants = "ACCESS_TYPES_PSEUDOREFERENCE_CODE " + "ALL_AVAILABLE_COMPONENTS_PSEUDOREFERENCE_CODE " + "ALL_AVAILABLE_PRIVILEGES_PSEUDOREFERENCE_CODE " + "ALL_REPLICATE_COMPONENTS_PSEUDOREFERENCE_CODE " + "AVAILABLE_DEVELOPERS_COMPONENTS_PSEUDOREFERENCE_CODE " + "COMPONENTS_PSEUDOREFERENCE_CODE " + "FILTRATER_SETTINGS_CONFLICTS_PSEUDOREFERENCE_CODE " + "GROUPS_PSEUDOREFERENCE_CODE " + "RECEIVE_PROTOCOL_PSEUDOREFERENCE_CODE " + "REFERENCE_REQUISITE_PSEUDOREFERENCE_CODE " + "REFERENCE_REQUISITES_PSEUDOREFERENCE_CODE " + "REFTYPES_PSEUDOREFERENCE_CODE " + "REPLICATION_SEANCES_DIARY_PSEUDOREFERENCE_CODE " + "SEND_PROTOCOL_PSEUDOREFERENCE_CODE " + "SUBSTITUTES_PSEUDOREFERENCE_CODE " + "SYSTEM_SETTINGS_PSEUDOREFERENCE_CODE " + "UNITS_PSEUDOREFERENCE_CODE " + "USERS_PSEUDOREFERENCE_CODE " + "VIEWERS_PSEUDOREFERENCE_CODE "; // Requisite ISBCertificateType values var requisite_ISBCertificateType_values_constants = "CERTIFICATE_TYPE_ENCRYPT " + "CERTIFICATE_TYPE_SIGN " + "CERTIFICATE_TYPE_SIGN_AND_ENCRYPT "; // Requisite ISBEDocStorageType values var requisite_ISBEDocStorageType_values_constants = "STORAGE_TYPE_FILE " + "STORAGE_TYPE_NAS_CIFS " + "STORAGE_TYPE_SAPERION " + "STORAGE_TYPE_SQL_SERVER "; // Requisite CompType2 values var requisite_compType2_values_constants = "COMPTYPE2_REQUISITE_DOCUMENTS_VALUE " + "COMPTYPE2_REQUISITE_TASKS_VALUE " + "COMPTYPE2_REQUISITE_FOLDERS_VALUE " + "COMPTYPE2_REQUISITE_REFERENCES_VALUE "; // Requisite name var requisite_name_constants = "SYSREQ_CODE " + "SYSREQ_COMPTYPE2 " + "SYSREQ_CONST_AVAILABLE_FOR_WEB " + "SYSREQ_CONST_COMMON_CODE " + "SYSREQ_CONST_COMMON_VALUE " + "SYSREQ_CONST_FIRM_CODE " + "SYSREQ_CONST_FIRM_STATUS " + "SYSREQ_CONST_FIRM_VALUE " + "SYSREQ_CONST_SERVER_STATUS " + "SYSREQ_CONTENTS " + "SYSREQ_DATE_OPEN " + "SYSREQ_DATE_CLOSE " + "SYSREQ_DESCRIPTION " + "SYSREQ_DESCRIPTION_LOCALIZE_ID " + "SYSREQ_DOUBLE " + "SYSREQ_EDOC_ACCESS_TYPE " + "SYSREQ_EDOC_AUTHOR " + "SYSREQ_EDOC_CREATED " + "SYSREQ_EDOC_DELEGATE_RIGHTS_REQUISITE_CODE " + "SYSREQ_EDOC_EDITOR " + "SYSREQ_EDOC_ENCODE_TYPE " + "SYSREQ_EDOC_ENCRYPTION_PLUGIN_NAME " + "SYSREQ_EDOC_ENCRYPTION_PLUGIN_VERSION " + "SYSREQ_EDOC_EXPORT_DATE " + "SYSREQ_EDOC_EXPORTER " + "SYSREQ_EDOC_KIND " + "SYSREQ_EDOC_LIFE_STAGE_NAME " + "SYSREQ_EDOC_LOCKED_FOR_SERVER_CODE " + "SYSREQ_EDOC_MODIFIED " + "SYSREQ_EDOC_NAME " + "SYSREQ_EDOC_NOTE " + "SYSREQ_EDOC_QUALIFIED_ID " + "SYSREQ_EDOC_SESSION_KEY " + "SYSREQ_EDOC_SESSION_KEY_ENCRYPTION_PLUGIN_NAME " + "SYSREQ_EDOC_SESSION_KEY_ENCRYPTION_PLUGIN_VERSION " + "SYSREQ_EDOC_SIGNATURE_TYPE " + "SYSREQ_EDOC_SIGNED " + "SYSREQ_EDOC_STORAGE " + "SYSREQ_EDOC_STORAGES_ARCHIVE_STORAGE " + "SYSREQ_EDOC_STORAGES_CHECK_RIGHTS " + "SYSREQ_EDOC_STORAGES_COMPUTER_NAME " + "SYSREQ_EDOC_STORAGES_EDIT_IN_STORAGE " + "SYSREQ_EDOC_STORAGES_EXECUTIVE_STORAGE " + "SYSREQ_EDOC_STORAGES_FUNCTION " + "SYSREQ_EDOC_STORAGES_INITIALIZED " + "SYSREQ_EDOC_STORAGES_LOCAL_PATH " + "SYSREQ_EDOC_STORAGES_SAPERION_DATABASE_NAME " + "SYSREQ_EDOC_STORAGES_SEARCH_BY_TEXT " + "SYSREQ_EDOC_STORAGES_SERVER_NAME " + "SYSREQ_EDOC_STORAGES_SHARED_SOURCE_NAME " + "SYSREQ_EDOC_STORAGES_TYPE " + "SYSREQ_EDOC_TEXT_MODIFIED " + "SYSREQ_EDOC_TYPE_ACT_CODE " + "SYSREQ_EDOC_TYPE_ACT_DESCRIPTION " + "SYSREQ_EDOC_TYPE_ACT_DESCRIPTION_LOCALIZE_ID " + "SYSREQ_EDOC_TYPE_ACT_ON_EXECUTE " + "SYSREQ_EDOC_TYPE_ACT_ON_EXECUTE_EXISTS " + "SYSREQ_EDOC_TYPE_ACT_SECTION " + "SYSREQ_EDOC_TYPE_ADD_PARAMS " + "SYSREQ_EDOC_TYPE_COMMENT " + "SYSREQ_EDOC_TYPE_EVENT_TEXT " + "SYSREQ_EDOC_TYPE_NAME_IN_SINGULAR " + "SYSREQ_EDOC_TYPE_NAME_IN_SINGULAR_LOCALIZE_ID " + "SYSREQ_EDOC_TYPE_NAME_LOCALIZE_ID " + "SYSREQ_EDOC_TYPE_NUMERATION_METHOD " + "SYSREQ_EDOC_TYPE_PSEUDO_REQUISITE_CODE " + "SYSREQ_EDOC_TYPE_REQ_CODE " + "SYSREQ_EDOC_TYPE_REQ_DESCRIPTION " + "SYSREQ_EDOC_TYPE_REQ_DESCRIPTION_LOCALIZE_ID " + "SYSREQ_EDOC_TYPE_REQ_IS_LEADING " + "SYSREQ_EDOC_TYPE_REQ_IS_REQUIRED " + "SYSREQ_EDOC_TYPE_REQ_NUMBER " + "SYSREQ_EDOC_TYPE_REQ_ON_CHANGE " + "SYSREQ_EDOC_TYPE_REQ_ON_CHANGE_EXISTS " + "SYSREQ_EDOC_TYPE_REQ_ON_SELECT " + "SYSREQ_EDOC_TYPE_REQ_ON_SELECT_KIND " + "SYSREQ_EDOC_TYPE_REQ_SECTION " + "SYSREQ_EDOC_TYPE_VIEW_CARD " + "SYSREQ_EDOC_TYPE_VIEW_CODE " + "SYSREQ_EDOC_TYPE_VIEW_COMMENT " + "SYSREQ_EDOC_TYPE_VIEW_IS_MAIN " + "SYSREQ_EDOC_TYPE_VIEW_NAME " + "SYSREQ_EDOC_TYPE_VIEW_NAME_LOCALIZE_ID " + "SYSREQ_EDOC_VERSION_AUTHOR " + "SYSREQ_EDOC_VERSION_CRC " + "SYSREQ_EDOC_VERSION_DATA " + "SYSREQ_EDOC_VERSION_EDITOR " + "SYSREQ_EDOC_VERSION_EXPORT_DATE " + "SYSREQ_EDOC_VERSION_EXPORTER " + "SYSREQ_EDOC_VERSION_HIDDEN " + "SYSREQ_EDOC_VERSION_LIFE_STAGE " + "SYSREQ_EDOC_VERSION_MODIFIED " + "SYSREQ_EDOC_VERSION_NOTE " + "SYSREQ_EDOC_VERSION_SIGNATURE_TYPE " + "SYSREQ_EDOC_VERSION_SIGNED " + "SYSREQ_EDOC_VERSION_SIZE " + "SYSREQ_EDOC_VERSION_SOURCE " + "SYSREQ_EDOC_VERSION_TEXT_MODIFIED " + "SYSREQ_EDOCKIND_DEFAULT_VERSION_STATE_CODE " + "SYSREQ_FOLDER_KIND " + "SYSREQ_FUNC_CATEGORY " + "SYSREQ_FUNC_COMMENT " + "SYSREQ_FUNC_GROUP " + "SYSREQ_FUNC_GROUP_COMMENT " + "SYSREQ_FUNC_GROUP_NUMBER " + "SYSREQ_FUNC_HELP " + "SYSREQ_FUNC_PARAM_DEF_VALUE " + "SYSREQ_FUNC_PARAM_IDENT " + "SYSREQ_FUNC_PARAM_NUMBER " + "SYSREQ_FUNC_PARAM_TYPE " + "SYSREQ_FUNC_TEXT " + "SYSREQ_GROUP_CATEGORY " + "SYSREQ_ID " + "SYSREQ_LAST_UPDATE " + "SYSREQ_LEADER_REFERENCE " + "SYSREQ_LINE_NUMBER " + "SYSREQ_MAIN_RECORD_ID " + "SYSREQ_NAME " + "SYSREQ_NAME_LOCALIZE_ID " + "SYSREQ_NOTE " + "SYSREQ_ORIGINAL_RECORD " + "SYSREQ_OUR_FIRM " + "SYSREQ_PROFILING_SETTINGS_BATCH_LOGING " + "SYSREQ_PROFILING_SETTINGS_BATCH_SIZE " + "SYSREQ_PROFILING_SETTINGS_PROFILING_ENABLED " + "SYSREQ_PROFILING_SETTINGS_SQL_PROFILING_ENABLED " + "SYSREQ_PROFILING_SETTINGS_START_LOGGED " + "SYSREQ_RECORD_STATUS " + "SYSREQ_REF_REQ_FIELD_NAME " + "SYSREQ_REF_REQ_FORMAT " + "SYSREQ_REF_REQ_GENERATED " + "SYSREQ_REF_REQ_LENGTH " + "SYSREQ_REF_REQ_PRECISION " + "SYSREQ_REF_REQ_REFERENCE " + "SYSREQ_REF_REQ_SECTION " + "SYSREQ_REF_REQ_STORED " + "SYSREQ_REF_REQ_TOKENS " + "SYSREQ_REF_REQ_TYPE " + "SYSREQ_REF_REQ_VIEW " + "SYSREQ_REF_TYPE_ACT_CODE " + "SYSREQ_REF_TYPE_ACT_DESCRIPTION " + "SYSREQ_REF_TYPE_ACT_DESCRIPTION_LOCALIZE_ID " + "SYSREQ_REF_TYPE_ACT_ON_EXECUTE " + "SYSREQ_REF_TYPE_ACT_ON_EXECUTE_EXISTS " + "SYSREQ_REF_TYPE_ACT_SECTION " + "SYSREQ_REF_TYPE_ADD_PARAMS " + "SYSREQ_REF_TYPE_COMMENT " + "SYSREQ_REF_TYPE_COMMON_SETTINGS " + "SYSREQ_REF_TYPE_DISPLAY_REQUISITE_NAME " + "SYSREQ_REF_TYPE_EVENT_TEXT " + "SYSREQ_REF_TYPE_MAIN_LEADING_REF " + "SYSREQ_REF_TYPE_NAME_IN_SINGULAR " + "SYSREQ_REF_TYPE_NAME_IN_SINGULAR_LOCALIZE_ID " + "SYSREQ_REF_TYPE_NAME_LOCALIZE_ID " + "SYSREQ_REF_TYPE_NUMERATION_METHOD " + "SYSREQ_REF_TYPE_REQ_CODE " + "SYSREQ_REF_TYPE_REQ_DESCRIPTION " + "SYSREQ_REF_TYPE_REQ_DESCRIPTION_LOCALIZE_ID " + "SYSREQ_REF_TYPE_REQ_IS_CONTROL " + "SYSREQ_REF_TYPE_REQ_IS_FILTER " + "SYSREQ_REF_TYPE_REQ_IS_LEADING " + "SYSREQ_REF_TYPE_REQ_IS_REQUIRED " + "SYSREQ_REF_TYPE_REQ_NUMBER " + "SYSREQ_REF_TYPE_REQ_ON_CHANGE " + "SYSREQ_REF_TYPE_REQ_ON_CHANGE_EXISTS " + "SYSREQ_REF_TYPE_REQ_ON_SELECT " + "SYSREQ_REF_TYPE_REQ_ON_SELECT_KIND " + "SYSREQ_REF_TYPE_REQ_SECTION " + "SYSREQ_REF_TYPE_VIEW_CARD " + "SYSREQ_REF_TYPE_VIEW_CODE " + "SYSREQ_REF_TYPE_VIEW_COMMENT " + "SYSREQ_REF_TYPE_VIEW_IS_MAIN " + "SYSREQ_REF_TYPE_VIEW_NAME " + "SYSREQ_REF_TYPE_VIEW_NAME_LOCALIZE_ID " + "SYSREQ_REFERENCE_TYPE_ID " + "SYSREQ_STATE " + "SYSREQ_STATЕ " + "SYSREQ_SYSTEM_SETTINGS_VALUE " + "SYSREQ_TYPE " + "SYSREQ_UNIT " + "SYSREQ_UNIT_ID " + "SYSREQ_USER_GROUPS_GROUP_FULL_NAME " + "SYSREQ_USER_GROUPS_GROUP_NAME " + "SYSREQ_USER_GROUPS_GROUP_SERVER_NAME " + "SYSREQ_USERS_ACCESS_RIGHTS " + "SYSREQ_USERS_AUTHENTICATION " + "SYSREQ_USERS_CATEGORY " + "SYSREQ_USERS_COMPONENT " + "SYSREQ_USERS_COMPONENT_USER_IS_PUBLIC " + "SYSREQ_USERS_DOMAIN " + "SYSREQ_USERS_FULL_USER_NAME " + "SYSREQ_USERS_GROUP " + "SYSREQ_USERS_IS_MAIN_SERVER " + "SYSREQ_USERS_LOGIN " + "SYSREQ_USERS_REFERENCE_USER_IS_PUBLIC " + "SYSREQ_USERS_STATUS " + "SYSREQ_USERS_USER_CERTIFICATE " + "SYSREQ_USERS_USER_CERTIFICATE_INFO " + "SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_NAME " + "SYSREQ_USERS_USER_CERTIFICATE_PLUGIN_VERSION " + "SYSREQ_USERS_USER_CERTIFICATE_STATE " + "SYSREQ_USERS_USER_CERTIFICATE_SUBJECT_NAME " + "SYSREQ_USERS_USER_CERTIFICATE_THUMBPRINT " + "SYSREQ_USERS_USER_DEFAULT_CERTIFICATE " + "SYSREQ_USERS_USER_DESCRIPTION " + "SYSREQ_USERS_USER_GLOBAL_NAME " + "SYSREQ_USERS_USER_LOGIN " + "SYSREQ_USERS_USER_MAIN_SERVER " + "SYSREQ_USERS_USER_TYPE " + "SYSREQ_WORK_RULES_FOLDER_ID "; // Result var result_constants = "RESULT_VAR_NAME RESULT_VAR_NAME_ENG "; // Rule identification var rule_identification_constants = "AUTO_NUMERATION_RULE_ID " + "CANT_CHANGE_ID_REQUISITE_RULE_ID " + "CANT_CHANGE_OURFIRM_REQUISITE_RULE_ID " + "CHECK_CHANGING_REFERENCE_RECORD_USE_RULE_ID " + "CHECK_CODE_REQUISITE_RULE_ID " + "CHECK_DELETING_REFERENCE_RECORD_USE_RULE_ID " + "CHECK_FILTRATER_CHANGES_RULE_ID " + "CHECK_RECORD_INTERVAL_RULE_ID " + "CHECK_REFERENCE_INTERVAL_RULE_ID " + "CHECK_REQUIRED_DATA_FULLNESS_RULE_ID " + "CHECK_REQUIRED_REQUISITES_FULLNESS_RULE_ID " + "MAKE_RECORD_UNRATIFIED_RULE_ID " + "RESTORE_AUTO_NUMERATION_RULE_ID " + "SET_FIRM_CONTEXT_FROM_RECORD_RULE_ID " + "SET_FIRST_RECORD_IN_LIST_FORM_RULE_ID " + "SET_IDSPS_VALUE_RULE_ID " + "SET_NEXT_CODE_VALUE_RULE_ID " + "SET_OURFIRM_BOUNDS_RULE_ID " + "SET_OURFIRM_REQUISITE_RULE_ID "; // Script block properties var script_block_properties_constants = "SCRIPT_BLOCK_AFTER_FINISH_EVENT " + "SCRIPT_BLOCK_BEFORE_START_EVENT " + "SCRIPT_BLOCK_EXECUTION_RESULTS_PROPERTY " + "SCRIPT_BLOCK_NAME_PROPERTY " + "SCRIPT_BLOCK_SCRIPT_PROPERTY "; // Subtask block properties var subtask_block_properties_constants = "SUBTASK_BLOCK_ABORT_DEADLINE_PROPERTY " + "SUBTASK_BLOCK_AFTER_FINISH_EVENT " + "SUBTASK_BLOCK_ASSIGN_PARAMS_EVENT " + "SUBTASK_BLOCK_ATTACHMENTS_PROPERTY " + "SUBTASK_BLOCK_ATTACHMENTS_RIGHTS_GROUP_PROPERTY " + "SUBTASK_BLOCK_ATTACHMENTS_RIGHTS_TYPE_PROPERTY " + "SUBTASK_BLOCK_BEFORE_START_EVENT " + "SUBTASK_BLOCK_CREATED_TASK_PROPERTY " + "SUBTASK_BLOCK_CREATION_EVENT " + "SUBTASK_BLOCK_DEADLINE_PROPERTY " + "SUBTASK_BLOCK_IMPORTANCE_PROPERTY " + "SUBTASK_BLOCK_INITIATOR_PROPERTY " + "SUBTASK_BLOCK_IS_RELATIVE_ABORT_DEADLINE_PROPERTY " + "SUBTASK_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY " + "SUBTASK_BLOCK_JOBS_TYPE_PROPERTY " + "SUBTASK_BLOCK_NAME_PROPERTY " + "SUBTASK_BLOCK_PARALLEL_ROUTE_PROPERTY " + "SUBTASK_BLOCK_PERFORMERS_PROPERTY " + "SUBTASK_BLOCK_RELATIVE_ABORT_DEADLINE_TYPE_PROPERTY " + "SUBTASK_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY " + "SUBTASK_BLOCK_REQUIRE_SIGN_PROPERTY " + "SUBTASK_BLOCK_STANDARD_ROUTE_PROPERTY " + "SUBTASK_BLOCK_START_EVENT " + "SUBTASK_BLOCK_STEP_CONTROL_PROPERTY " + "SUBTASK_BLOCK_SUBJECT_PROPERTY " + "SUBTASK_BLOCK_TASK_CONTROL_PROPERTY " + "SUBTASK_BLOCK_TEXT_PROPERTY " + "SUBTASK_BLOCK_UNLOCK_ATTACHMENTS_ON_STOP_PROPERTY " + "SUBTASK_BLOCK_USE_STANDARD_ROUTE_PROPERTY " + "SUBTASK_BLOCK_WAIT_FOR_TASK_COMPLETE_PROPERTY "; // System component var system_component_constants = "SYSCOMP_CONTROL_JOBS " + "SYSCOMP_FOLDERS " + "SYSCOMP_JOBS " + "SYSCOMP_NOTICES " + "SYSCOMP_TASKS "; // System dialogs var system_dialogs_constants = "SYSDLG_CREATE_EDOCUMENT " + "SYSDLG_CREATE_EDOCUMENT_VERSION " + "SYSDLG_CURRENT_PERIOD " + "SYSDLG_EDIT_FUNCTION_HELP " + "SYSDLG_EDOCUMENT_KINDS_FOR_TEMPLATE " + "SYSDLG_EXPORT_MULTIPLE_EDOCUMENTS " + "SYSDLG_EXPORT_SINGLE_EDOCUMENT " + "SYSDLG_IMPORT_EDOCUMENT " + "SYSDLG_MULTIPLE_SELECT " + "SYSDLG_SETUP_ACCESS_RIGHTS " + "SYSDLG_SETUP_DEFAULT_RIGHTS " + "SYSDLG_SETUP_FILTER_CONDITION " + "SYSDLG_SETUP_SIGN_RIGHTS " + "SYSDLG_SETUP_TASK_OBSERVERS " + "SYSDLG_SETUP_TASK_ROUTE " + "SYSDLG_SETUP_USERS_LIST " + "SYSDLG_SIGN_EDOCUMENT " + "SYSDLG_SIGN_MULTIPLE_EDOCUMENTS "; // System reference names var system_reference_names_constants = "SYSREF_ACCESS_RIGHTS_TYPES " + "SYSREF_ADMINISTRATION_HISTORY " + "SYSREF_ALL_AVAILABLE_COMPONENTS " + "SYSREF_ALL_AVAILABLE_PRIVILEGES " + "SYSREF_ALL_REPLICATING_COMPONENTS " + "SYSREF_AVAILABLE_DEVELOPERS_COMPONENTS " + "SYSREF_CALENDAR_EVENTS " + "SYSREF_COMPONENT_TOKEN_HISTORY " + "SYSREF_COMPONENT_TOKENS " + "SYSREF_COMPONENTS " + "SYSREF_CONSTANTS " + "SYSREF_DATA_RECEIVE_PROTOCOL " + "SYSREF_DATA_SEND_PROTOCOL " + "SYSREF_DIALOGS " + "SYSREF_DIALOGS_REQUISITES " + "SYSREF_EDITORS " + "SYSREF_EDOC_CARDS " + "SYSREF_EDOC_TYPES " + "SYSREF_EDOCUMENT_CARD_REQUISITES " + "SYSREF_EDOCUMENT_CARD_TYPES " + "SYSREF_EDOCUMENT_CARD_TYPES_REFERENCE " + "SYSREF_EDOCUMENT_CARDS " + "SYSREF_EDOCUMENT_HISTORY " + "SYSREF_EDOCUMENT_KINDS " + "SYSREF_EDOCUMENT_REQUISITES " + "SYSREF_EDOCUMENT_SIGNATURES " + "SYSREF_EDOCUMENT_TEMPLATES " + "SYSREF_EDOCUMENT_TEXT_STORAGES " + "SYSREF_EDOCUMENT_VIEWS " + "SYSREF_FILTERER_SETUP_CONFLICTS " + "SYSREF_FILTRATER_SETTING_CONFLICTS " + "SYSREF_FOLDER_HISTORY " + "SYSREF_FOLDERS " + "SYSREF_FUNCTION_GROUPS " + "SYSREF_FUNCTION_PARAMS " + "SYSREF_FUNCTIONS " + "SYSREF_JOB_HISTORY " + "SYSREF_LINKS " + "SYSREF_LOCALIZATION_DICTIONARY " + "SYSREF_LOCALIZATION_LANGUAGES " + "SYSREF_MODULES " + "SYSREF_PRIVILEGES " + "SYSREF_RECORD_HISTORY " + "SYSREF_REFERENCE_REQUISITES " + "SYSREF_REFERENCE_TYPE_VIEWS " + "SYSREF_REFERENCE_TYPES " + "SYSREF_REFERENCES " + "SYSREF_REFERENCES_REQUISITES " + "SYSREF_REMOTE_SERVERS " + "SYSREF_REPLICATION_SESSIONS_LOG " + "SYSREF_REPLICATION_SESSIONS_PROTOCOL " + "SYSREF_REPORTS " + "SYSREF_ROLES " + "SYSREF_ROUTE_BLOCK_GROUPS " + "SYSREF_ROUTE_BLOCKS " + "SYSREF_SCRIPTS " + "SYSREF_SEARCHES " + "SYSREF_SERVER_EVENTS " + "SYSREF_SERVER_EVENTS_HISTORY " + "SYSREF_STANDARD_ROUTE_GROUPS " + "SYSREF_STANDARD_ROUTES " + "SYSREF_STATUSES " + "SYSREF_SYSTEM_SETTINGS " + "SYSREF_TASK_HISTORY " + "SYSREF_TASK_KIND_GROUPS " + "SYSREF_TASK_KINDS " + "SYSREF_TASK_RIGHTS " + "SYSREF_TASK_SIGNATURES " + "SYSREF_TASKS " + "SYSREF_UNITS " + "SYSREF_USER_GROUPS " + "SYSREF_USER_GROUPS_REFERENCE " + "SYSREF_USER_SUBSTITUTION " + "SYSREF_USERS " + "SYSREF_USERS_REFERENCE " + "SYSREF_VIEWERS " + "SYSREF_WORKING_TIME_CALENDARS "; // Table name var table_name_constants = "ACCESS_RIGHTS_TABLE_NAME " + "EDMS_ACCESS_TABLE_NAME " + "EDOC_TYPES_TABLE_NAME "; // Test var test_constants = "TEST_DEV_DB_NAME " + "TEST_DEV_SYSTEM_CODE " + "TEST_EDMS_DB_NAME " + "TEST_EDMS_MAIN_CODE " + "TEST_EDMS_MAIN_DB_NAME " + "TEST_EDMS_SECOND_CODE " + "TEST_EDMS_SECOND_DB_NAME " + "TEST_EDMS_SYSTEM_CODE " + "TEST_ISB5_MAIN_CODE " + "TEST_ISB5_SECOND_CODE " + "TEST_SQL_SERVER_2005_NAME " + "TEST_SQL_SERVER_NAME "; // Using the dialog windows var using_the_dialog_windows_constants = "ATTENTION_CAPTION " + "cbsCommandLinks " + "cbsDefault " + "CONFIRMATION_CAPTION " + "ERROR_CAPTION " + "INFORMATION_CAPTION " + "mrCancel " + "mrOk "; // Using the document var using_the_document_constants = "EDOC_VERSION_ACTIVE_STAGE_CODE " + "EDOC_VERSION_DESIGN_STAGE_CODE " + "EDOC_VERSION_OBSOLETE_STAGE_CODE "; // Using the EA and encryption var using_the_EA_and_encryption_constants = "cpDataEnciphermentEnabled " + "cpDigitalSignatureEnabled " + "cpID " + "cpIssuer " + "cpPluginVersion " + "cpSerial " + "cpSubjectName " + "cpSubjSimpleName " + "cpValidFromDate " + "cpValidToDate "; // Using the ISBL-editor var using_the_ISBL_editor_constants = "ISBL_SYNTAX " + "NO_SYNTAX " + "XML_SYNTAX "; // Wait block properties var wait_block_properties_constants = "WAIT_BLOCK_AFTER_FINISH_EVENT " + "WAIT_BLOCK_BEFORE_START_EVENT " + "WAIT_BLOCK_DEADLINE_PROPERTY " + "WAIT_BLOCK_IS_RELATIVE_DEADLINE_PROPERTY " + "WAIT_BLOCK_NAME_PROPERTY " + "WAIT_BLOCK_RELATIVE_DEADLINE_TYPE_PROPERTY "; // SYSRES Common var sysres_common_constants = "SYSRES_COMMON " + "SYSRES_CONST " + "SYSRES_MBFUNC " + "SYSRES_SBDATA " + "SYSRES_SBGUI " + "SYSRES_SBINTF " + "SYSRES_SBREFDSC " + "SYSRES_SQLERRORS " + "SYSRES_SYSCOMP "; // Константы ==> built_in var CONSTANTS = sysres_constants + base_constants + base_group_name_constants + decision_block_properties_constants + file_extension_constants + job_block_properties_constants + language_code_constants + launching_external_applications_constants + link_kind_constants + lock_type_constants + monitor_block_properties_constants + notice_block_properties_constants + object_events_constants + object_params_constants + other_constants + privileges_constants + pseudoreference_code_constants + requisite_ISBCertificateType_values_constants + requisite_ISBEDocStorageType_values_constants + requisite_compType2_values_constants + requisite_name_constants + result_constants + rule_identification_constants + script_block_properties_constants + subtask_block_properties_constants + system_component_constants + system_dialogs_constants + system_reference_names_constants + table_name_constants + test_constants + using_the_dialog_windows_constants + using_the_document_constants + using_the_EA_and_encryption_constants + using_the_ISBL_editor_constants + wait_block_properties_constants + sysres_common_constants; // enum TAccountType var TAccountType = "atUser atGroup atRole "; // enum TActionEnabledMode var TActionEnabledMode = "aemEnabledAlways " + "aemDisabledAlways " + "aemEnabledOnBrowse " + "aemEnabledOnEdit " + "aemDisabledOnBrowseEmpty "; // enum TAddPosition var TAddPosition = "apBegin apEnd "; // enum TAlignment var TAlignment = "alLeft alRight "; // enum TAreaShowMode var TAreaShowMode = "asmNever " + "asmNoButCustomize " + "asmAsLastTime " + "asmYesButCustomize " + "asmAlways "; // enum TCertificateInvalidationReason var TCertificateInvalidationReason = "cirCommon cirRevoked "; // enum TCertificateType var TCertificateType = "ctSignature ctEncode ctSignatureEncode "; // enum TCheckListBoxItemState var TCheckListBoxItemState = "clbUnchecked clbChecked clbGrayed "; // enum TCloseOnEsc var TCloseOnEsc = "ceISB ceAlways ceNever "; // enum TCompType var TCompType = "ctDocument " + "ctReference " + "ctScript " + "ctUnknown " + "ctReport " + "ctDialog " + "ctFunction " + "ctFolder " + "ctEDocument " + "ctTask " + "ctJob " + "ctNotice " + "ctControlJob "; // enum TConditionFormat var TConditionFormat = "cfInternal cfDisplay "; // enum TConnectionIntent var TConnectionIntent = "ciUnspecified ciWrite ciRead "; // enum TContentKind var TContentKind = "ckFolder " + "ckEDocument " + "ckTask " + "ckJob " + "ckComponentToken " + "ckAny " + "ckReference " + "ckScript " + "ckReport " + "ckDialog "; // enum TControlType var TControlType = "ctISBLEditor " + "ctBevel " + "ctButton " + "ctCheckListBox " + "ctComboBox " + "ctComboEdit " + "ctGrid " + "ctDBCheckBox " + "ctDBComboBox " + "ctDBEdit " + "ctDBEllipsis " + "ctDBMemo " + "ctDBNavigator " + "ctDBRadioGroup " + "ctDBStatusLabel " + "ctEdit " + "ctGroupBox " + "ctInplaceHint " + "ctMemo " + "ctPanel " + "ctListBox " + "ctRadioButton " + "ctRichEdit " + "ctTabSheet " + "ctWebBrowser " + "ctImage " + "ctHyperLink " + "ctLabel " + "ctDBMultiEllipsis " + "ctRibbon " + "ctRichView " + "ctInnerPanel " + "ctPanelGroup " + "ctBitButton "; // enum TCriterionContentType var TCriterionContentType = "cctDate " + "cctInteger " + "cctNumeric " + "cctPick " + "cctReference " + "cctString " + "cctText "; // enum TCultureType var TCultureType = "cltInternal cltPrimary cltGUI "; // enum TDataSetEventType var TDataSetEventType = "dseBeforeOpen " + "dseAfterOpen " + "dseBeforeClose " + "dseAfterClose " + "dseOnValidDelete " + "dseBeforeDelete " + "dseAfterDelete " + "dseAfterDeleteOutOfTransaction " + "dseOnDeleteError " + "dseBeforeInsert " + "dseAfterInsert " + "dseOnValidUpdate " + "dseBeforeUpdate " + "dseOnUpdateRatifiedRecord " + "dseAfterUpdate " + "dseAfterUpdateOutOfTransaction " + "dseOnUpdateError " + "dseAfterScroll " + "dseOnOpenRecord " + "dseOnCloseRecord " + "dseBeforeCancel " + "dseAfterCancel " + "dseOnUpdateDeadlockError " + "dseBeforeDetailUpdate " + "dseOnPrepareUpdate " + "dseOnAnyRequisiteChange "; // enum TDataSetState var TDataSetState = "dssEdit dssInsert dssBrowse dssInActive "; // enum TDateFormatType var TDateFormatType = "dftDate dftShortDate dftDateTime dftTimeStamp "; // enum TDateOffsetType var TDateOffsetType = "dotDays dotHours dotMinutes dotSeconds "; // enum TDateTimeKind var TDateTimeKind = "dtkndLocal dtkndUTC "; // enum TDeaAccessRights var TDeaAccessRights = "arNone arView arEdit arFull "; // enum TDocumentDefaultAction var TDocumentDefaultAction = "ddaView ddaEdit "; // enum TEditMode var TEditMode = "emLock " + "emEdit " + "emSign " + "emExportWithLock " + "emImportWithUnlock " + "emChangeVersionNote " + "emOpenForModify " + "emChangeLifeStage " + "emDelete " + "emCreateVersion " + "emImport " + "emUnlockExportedWithLock " + "emStart " + "emAbort " + "emReInit " + "emMarkAsReaded " + "emMarkAsUnreaded " + "emPerform " + "emAccept " + "emResume " + "emChangeRights " + "emEditRoute " + "emEditObserver " + "emRecoveryFromLocalCopy " + "emChangeWorkAccessType " + "emChangeEncodeTypeToCertificate " + "emChangeEncodeTypeToPassword " + "emChangeEncodeTypeToNone " + "emChangeEncodeTypeToCertificatePassword " + "emChangeStandardRoute " + "emGetText " + "emOpenForView " + "emMoveToStorage " + "emCreateObject " + "emChangeVersionHidden " + "emDeleteVersion " + "emChangeLifeCycleStage " + "emApprovingSign " + "emExport " + "emContinue " + "emLockFromEdit " + "emUnLockForEdit " + "emLockForServer " + "emUnlockFromServer " + "emDelegateAccessRights " + "emReEncode "; // enum TEditorCloseObservType var TEditorCloseObservType = "ecotFile ecotProcess "; // enum TEdmsApplicationAction var TEdmsApplicationAction = "eaGet eaCopy eaCreate eaCreateStandardRoute "; // enum TEDocumentLockType var TEDocumentLockType = "edltAll edltNothing edltQuery "; // enum TEDocumentStepShowMode var TEDocumentStepShowMode = "essmText essmCard "; // enum TEDocumentStepVersionType var TEDocumentStepVersionType = "esvtLast esvtLastActive esvtSpecified "; // enum TEDocumentStorageFunction var TEDocumentStorageFunction = "edsfExecutive edsfArchive "; // enum TEDocumentStorageType var TEDocumentStorageType = "edstSQLServer edstFile "; // enum TEDocumentVersionSourceType var TEDocumentVersionSourceType = "edvstNone edvstEDocumentVersionCopy edvstFile edvstTemplate edvstScannedFile "; // enum TEDocumentVersionState var TEDocumentVersionState = "vsDefault vsDesign vsActive vsObsolete "; // enum TEncodeType var TEncodeType = "etNone etCertificate etPassword etCertificatePassword "; // enum TExceptionCategory var TExceptionCategory = "ecException ecWarning ecInformation "; // enum TExportedSignaturesType var TExportedSignaturesType = "estAll estApprovingOnly "; // enum TExportedVersionType var TExportedVersionType = "evtLast evtLastActive evtQuery "; // enum TFieldDataType var TFieldDataType = "fdtString " + "fdtNumeric " + "fdtInteger " + "fdtDate " + "fdtText " + "fdtUnknown " + "fdtWideString " + "fdtLargeInteger "; // enum TFolderType var TFolderType = "ftInbox " + "ftOutbox " + "ftFavorites " + "ftCommonFolder " + "ftUserFolder " + "ftComponents " + "ftQuickLaunch " + "ftShortcuts " + "ftSearch "; // enum TGridRowHeight var TGridRowHeight = "grhAuto " + "grhX1 " + "grhX2 " + "grhX3 "; // enum THyperlinkType var THyperlinkType = "hltText " + "hltRTF " + "hltHTML "; // enum TImageFileFormat var TImageFileFormat = "iffBMP " + "iffJPEG " + "iffMultiPageTIFF " + "iffSinglePageTIFF " + "iffTIFF " + "iffPNG "; // enum TImageMode var TImageMode = "im8bGrayscale " + "im24bRGB " + "im1bMonochrome "; // enum TImageType var TImageType = "itBMP " + "itJPEG " + "itWMF " + "itPNG "; // enum TInplaceHintKind var TInplaceHintKind = "ikhInformation " + "ikhWarning " + "ikhError " + "ikhNoIcon "; // enum TISBLContext var TISBLContext = "icUnknown " + "icScript " + "icFunction " + "icIntegratedReport " + "icAnalyticReport " + "icDataSetEventHandler " + "icActionHandler " + "icFormEventHandler " + "icLookUpEventHandler " + "icRequisiteChangeEventHandler " + "icBeforeSearchEventHandler " + "icRoleCalculation " + "icSelectRouteEventHandler " + "icBlockPropertyCalculation " + "icBlockQueryParamsEventHandler " + "icChangeSearchResultEventHandler " + "icBlockEventHandler " + "icSubTaskInitEventHandler " + "icEDocDataSetEventHandler " + "icEDocLookUpEventHandler " + "icEDocActionHandler " + "icEDocFormEventHandler " + "icEDocRequisiteChangeEventHandler " + "icStructuredConversionRule " + "icStructuredConversionEventBefore " + "icStructuredConversionEventAfter " + "icWizardEventHandler " + "icWizardFinishEventHandler " + "icWizardStepEventHandler " + "icWizardStepFinishEventHandler " + "icWizardActionEnableEventHandler " + "icWizardActionExecuteEventHandler " + "icCreateJobsHandler " + "icCreateNoticesHandler " + "icBeforeLookUpEventHandler " + "icAfterLookUpEventHandler " + "icTaskAbortEventHandler " + "icWorkflowBlockActionHandler " + "icDialogDataSetEventHandler " + "icDialogActionHandler " + "icDialogLookUpEventHandler " + "icDialogRequisiteChangeEventHandler " + "icDialogFormEventHandler " + "icDialogValidCloseEventHandler " + "icBlockFormEventHandler " + "icTaskFormEventHandler " + "icReferenceMethod " + "icEDocMethod " + "icDialogMethod " + "icProcessMessageHandler "; // enum TItemShow var TItemShow = "isShow " + "isHide " + "isByUserSettings "; // enum TJobKind var TJobKind = "jkJob " + "jkNotice " + "jkControlJob "; // enum TJoinType var TJoinType = "jtInner " + "jtLeft " + "jtRight " + "jtFull " + "jtCross "; // enum TLabelPos var TLabelPos = "lbpAbove " + "lbpBelow " + "lbpLeft " + "lbpRight "; // enum TLicensingType var TLicensingType = "eltPerConnection " + "eltPerUser "; // enum TLifeCycleStageFontColor var TLifeCycleStageFontColor = "sfcUndefined " + "sfcBlack " + "sfcGreen " + "sfcRed " + "sfcBlue " + "sfcOrange " + "sfcLilac "; // enum TLifeCycleStageFontStyle var TLifeCycleStageFontStyle = "sfsItalic " + "sfsStrikeout " + "sfsNormal "; // enum TLockableDevelopmentComponentType var TLockableDevelopmentComponentType = "ldctStandardRoute " + "ldctWizard " + "ldctScript " + "ldctFunction " + "ldctRouteBlock " + "ldctIntegratedReport " + "ldctAnalyticReport " + "ldctReferenceType " + "ldctEDocumentType " + "ldctDialog " + "ldctServerEvents "; // enum TMaxRecordCountRestrictionType var TMaxRecordCountRestrictionType = "mrcrtNone " + "mrcrtUser " + "mrcrtMaximal " + "mrcrtCustom "; // enum TRangeValueType var TRangeValueType = "vtEqual " + "vtGreaterOrEqual " + "vtLessOrEqual " + "vtRange "; // enum TRelativeDate var TRelativeDate = "rdYesterday " + "rdToday " + "rdTomorrow " + "rdThisWeek " + "rdThisMonth " + "rdThisYear " + "rdNextMonth " + "rdNextWeek " + "rdLastWeek " + "rdLastMonth "; // enum TReportDestination var TReportDestination = "rdWindow " + "rdFile " + "rdPrinter "; // enum TReqDataType var TReqDataType = "rdtString " + "rdtNumeric " + "rdtInteger " + "rdtDate " + "rdtReference " + "rdtAccount " + "rdtText " + "rdtPick " + "rdtUnknown " + "rdtLargeInteger " + "rdtDocument "; // enum TRequisiteEventType var TRequisiteEventType = "reOnChange " + "reOnChangeValues "; // enum TSBTimeType var TSBTimeType = "ttGlobal " + "ttLocal " + "ttUser " + "ttSystem "; // enum TSearchShowMode var TSearchShowMode = "ssmBrowse " + "ssmSelect " + "ssmMultiSelect " + "ssmBrowseModal "; // enum TSelectMode var TSelectMode = "smSelect " + "smLike " + "smCard "; // enum TSignatureType var TSignatureType = "stNone " + "stAuthenticating " + "stApproving "; // enum TSignerContentType var TSignerContentType = "sctString " + "sctStream "; // enum TStringsSortType var TStringsSortType = "sstAnsiSort " + "sstNaturalSort "; // enum TStringValueType var TStringValueType = "svtEqual " + "svtContain "; // enum TStructuredObjectAttributeType var TStructuredObjectAttributeType = "soatString " + "soatNumeric " + "soatInteger " + "soatDatetime " + "soatReferenceRecord " + "soatText " + "soatPick " + "soatBoolean " + "soatEDocument " + "soatAccount " + "soatIntegerCollection " + "soatNumericCollection " + "soatStringCollection " + "soatPickCollection " + "soatDatetimeCollection " + "soatBooleanCollection " + "soatReferenceRecordCollection " + "soatEDocumentCollection " + "soatAccountCollection " + "soatContents " + "soatUnknown "; // enum TTaskAbortReason var TTaskAbortReason = "tarAbortByUser " + "tarAbortByWorkflowException "; // enum TTextValueType var TTextValueType = "tvtAllWords " + "tvtExactPhrase " + "tvtAnyWord "; // enum TUserObjectStatus var TUserObjectStatus = "usNone " + "usCompleted " + "usRedSquare " + "usBlueSquare " + "usYellowSquare " + "usGreenSquare " + "usOrangeSquare " + "usPurpleSquare " + "usFollowUp "; // enum TUserType var TUserType = "utUnknown " + "utUser " + "utDeveloper " + "utAdministrator " + "utSystemDeveloper " + "utDisconnected "; // enum TValuesBuildType var TValuesBuildType = "btAnd " + "btDetailAnd " + "btOr " + "btNotOr " + "btOnly "; // enum TViewMode var TViewMode = "vmView " + "vmSelect " + "vmNavigation "; // enum TViewSelectionMode var TViewSelectionMode = "vsmSingle " + "vsmMultiple " + "vsmMultipleCheck " + "vsmNoSelection "; // enum TWizardActionType var TWizardActionType = "wfatPrevious " + "wfatNext " + "wfatCancel " + "wfatFinish "; // enum TWizardFormElementProperty var TWizardFormElementProperty = "wfepUndefined " + "wfepText3 " + "wfepText6 " + "wfepText9 " + "wfepSpinEdit " + "wfepDropDown " + "wfepRadioGroup " + "wfepFlag " + "wfepText12 " + "wfepText15 " + "wfepText18 " + "wfepText21 " + "wfepText24 " + "wfepText27 " + "wfepText30 " + "wfepRadioGroupColumn1 " + "wfepRadioGroupColumn2 " + "wfepRadioGroupColumn3 "; // enum TWizardFormElementType var TWizardFormElementType = "wfetQueryParameter " + "wfetText " + "wfetDelimiter " + "wfetLabel "; // enum TWizardParamType var TWizardParamType = "wptString " + "wptInteger " + "wptNumeric " + "wptBoolean " + "wptDateTime " + "wptPick " + "wptText " + "wptUser " + "wptUserList " + "wptEDocumentInfo " + "wptEDocumentInfoList " + "wptReferenceRecordInfo " + "wptReferenceRecordInfoList " + "wptFolderInfo " + "wptTaskInfo " + "wptContents " + "wptFileName " + "wptDate "; // enum TWizardStepResult var TWizardStepResult = "wsrComplete " + "wsrGoNext " + "wsrGoPrevious " + "wsrCustom " + "wsrCancel " + "wsrGoFinal "; // enum TWizardStepType var TWizardStepType = "wstForm " + "wstEDocument " + "wstTaskCard " + "wstReferenceRecordCard " + "wstFinal "; // enum TWorkAccessType var TWorkAccessType = "waAll " + "waPerformers " + "waManual "; // enum TWorkflowBlockType var TWorkflowBlockType = "wsbStart " + "wsbFinish " + "wsbNotice " + "wsbStep " + "wsbDecision " + "wsbWait " + "wsbMonitor " + "wsbScript " + "wsbConnector " + "wsbSubTask " + "wsbLifeCycleStage " + "wsbPause "; // enum TWorkflowDataType var TWorkflowDataType = "wdtInteger " + "wdtFloat " + "wdtString " + "wdtPick " + "wdtDateTime " + "wdtBoolean " + "wdtTask " + "wdtJob " + "wdtFolder " + "wdtEDocument " + "wdtReferenceRecord " + "wdtUser " + "wdtGroup " + "wdtRole " + "wdtIntegerCollection " + "wdtFloatCollection " + "wdtStringCollection " + "wdtPickCollection " + "wdtDateTimeCollection " + "wdtBooleanCollection " + "wdtTaskCollection " + "wdtJobCollection " + "wdtFolderCollection " + "wdtEDocumentCollection " + "wdtReferenceRecordCollection " + "wdtUserCollection " + "wdtGroupCollection " + "wdtRoleCollection " + "wdtContents " + "wdtUserList " + "wdtSearchDescription " + "wdtDeadLine " + "wdtPickSet " + "wdtAccountCollection "; // enum TWorkImportance var TWorkImportance = "wiLow " + "wiNormal " + "wiHigh "; // enum TWorkRouteType var TWorkRouteType = "wrtSoft " + "wrtHard "; // enum TWorkState var TWorkState = "wsInit " + "wsRunning " + "wsDone " + "wsControlled " + "wsAborted " + "wsContinued "; // enum TWorkTextBuildingMode var TWorkTextBuildingMode = "wtmFull " + "wtmFromCurrent " + "wtmOnlyCurrent "; // Перечисления var ENUMS = TAccountType + TActionEnabledMode + TAddPosition + TAlignment + TAreaShowMode + TCertificateInvalidationReason + TCertificateType + TCheckListBoxItemState + TCloseOnEsc + TCompType + TConditionFormat + TConnectionIntent + TContentKind + TControlType + TCriterionContentType + TCultureType + TDataSetEventType + TDataSetState + TDateFormatType + TDateOffsetType + TDateTimeKind + TDeaAccessRights + TDocumentDefaultAction + TEditMode + TEditorCloseObservType + TEdmsApplicationAction + TEDocumentLockType + TEDocumentStepShowMode + TEDocumentStepVersionType + TEDocumentStorageFunction + TEDocumentStorageType + TEDocumentVersionSourceType + TEDocumentVersionState + TEncodeType + TExceptionCategory + TExportedSignaturesType + TExportedVersionType + TFieldDataType + TFolderType + TGridRowHeight + THyperlinkType + TImageFileFormat + TImageMode + TImageType + TInplaceHintKind + TISBLContext + TItemShow + TJobKind + TJoinType + TLabelPos + TLicensingType + TLifeCycleStageFontColor + TLifeCycleStageFontStyle + TLockableDevelopmentComponentType + TMaxRecordCountRestrictionType + TRangeValueType + TRelativeDate + TReportDestination + TReqDataType + TRequisiteEventType + TSBTimeType + TSearchShowMode + TSelectMode + TSignatureType + TSignerContentType + TStringsSortType + TStringValueType + TStructuredObjectAttributeType + TTaskAbortReason + TTextValueType + TUserObjectStatus + TUserType + TValuesBuildType + TViewMode + TViewSelectionMode + TWizardActionType + TWizardFormElementProperty + TWizardFormElementType + TWizardParamType + TWizardStepResult + TWizardStepType + TWorkAccessType + TWorkflowBlockType + TWorkflowDataType + TWorkImportance + TWorkRouteType + TWorkState + TWorkTextBuildingMode; // Системные функции ==> SYSFUNCTIONS var system_functions = "AddSubString " + "AdjustLineBreaks " + "AmountInWords " + "Analysis " + "ArrayDimCount " + "ArrayHighBound " + "ArrayLowBound " + "ArrayOf " + "ArrayReDim " + "Assert " + "Assigned " + "BeginOfMonth " + "BeginOfPeriod " + "BuildProfilingOperationAnalysis " + "CallProcedure " + "CanReadFile " + "CArrayElement " + "CDataSetRequisite " + "ChangeDate " + "ChangeReferenceDataset " + "Char " + "CharPos " + "CheckParam " + "CheckParamValue " + "CompareStrings " + "ConstantExists " + "ControlState " + "ConvertDateStr " + "Copy " + "CopyFile " + "CreateArray " + "CreateCachedReference " + "CreateConnection " + "CreateDialog " + "CreateDualListDialog " + "CreateEditor " + "CreateException " + "CreateFile " + "CreateFolderDialog " + "CreateInputDialog " + "CreateLinkFile " + "CreateList " + "CreateLock " + "CreateMemoryDataSet " + "CreateObject " + "CreateOpenDialog " + "CreateProgress " + "CreateQuery " + "CreateReference " + "CreateReport " + "CreateSaveDialog " + "CreateScript " + "CreateSQLPivotFunction " + "CreateStringList " + "CreateTreeListSelectDialog " + "CSelectSQL " + "CSQL " + "CSubString " + "CurrentUserID " + "CurrentUserName " + "CurrentVersion " + "DataSetLocateEx " + "DateDiff " + "DateTimeDiff " + "DateToStr " + "DayOfWeek " + "DeleteFile " + "DirectoryExists " + "DisableCheckAccessRights " + "DisableCheckFullShowingRestriction " + "DisableMassTaskSendingRestrictions " + "DropTable " + "DupeString " + "EditText " + "EnableCheckAccessRights " + "EnableCheckFullShowingRestriction " + "EnableMassTaskSendingRestrictions " + "EndOfMonth " + "EndOfPeriod " + "ExceptionExists " + "ExceptionsOff " + "ExceptionsOn " + "Execute " + "ExecuteProcess " + "Exit " + "ExpandEnvironmentVariables " + "ExtractFileDrive " + "ExtractFileExt " + "ExtractFileName " + "ExtractFilePath " + "ExtractParams " + "FileExists " + "FileSize " + "FindFile " + "FindSubString " + "FirmContext " + "ForceDirectories " + "Format " + "FormatDate " + "FormatNumeric " + "FormatSQLDate " + "FormatString " + "FreeException " + "GetComponent " + "GetComponentLaunchParam " + "GetConstant " + "GetLastException " + "GetReferenceRecord " + "GetRefTypeByRefID " + "GetTableID " + "GetTempFolder " + "IfThen " + "In " + "IndexOf " + "InputDialog " + "InputDialogEx " + "InteractiveMode " + "IsFileLocked " + "IsGraphicFile " + "IsNumeric " + "Length " + "LoadString " + "LoadStringFmt " + "LocalTimeToUTC " + "LowerCase " + "Max " + "MessageBox " + "MessageBoxEx " + "MimeDecodeBinary " + "MimeDecodeString " + "MimeEncodeBinary " + "MimeEncodeString " + "Min " + "MoneyInWords " + "MoveFile " + "NewID " + "Now " + "OpenFile " + "Ord " + "Precision " + "Raise " + "ReadCertificateFromFile " + "ReadFile " + "ReferenceCodeByID " + "ReferenceNumber " + "ReferenceRequisiteMode " + "ReferenceRequisiteValue " + "RegionDateSettings " + "RegionNumberSettings " + "RegionTimeSettings " + "RegRead " + "RegWrite " + "RenameFile " + "Replace " + "Round " + "SelectServerCode " + "SelectSQL " + "ServerDateTime " + "SetConstant " + "SetManagedFolderFieldsState " + "ShowConstantsInputDialog " + "ShowMessage " + "Sleep " + "Split " + "SQL " + "SQL2XLSTAB " + "SQLProfilingSendReport " + "StrToDate " + "SubString " + "SubStringCount " + "SystemSetting " + "Time " + "TimeDiff " + "Today " + "Transliterate " + "Trim " + "UpperCase " + "UserStatus " + "UTCToLocalTime " + "ValidateXML " + "VarIsClear " + "VarIsEmpty " + "VarIsNull " + "WorkTimeDiff " + "WriteFile " + "WriteFileEx " + "WriteObjectHistory " + "Анализ " + "БазаДанных " + "БлокЕсть " + "БлокЕстьРасш " + "БлокИнфо " + "БлокСнять " + "БлокСнятьРасш " + "БлокУстановить " + "Ввод " + "ВводМеню " + "ВедС " + "ВедСпр " + "ВерхняяГраницаМассива " + "ВнешПрогр " + "Восст " + "ВременнаяПапка " + "Время " + "ВыборSQL " + "ВыбратьЗапись " + "ВыделитьСтр " + "Вызвать " + "Выполнить " + "ВыпПрогр " + "ГрафическийФайл " + "ГруппаДополнительно " + "ДатаВремяСерв " + "ДеньНедели " + "ДиалогДаНет " + "ДлинаСтр " + "ДобПодстр " + "ЕПусто " + "ЕслиТо " + "ЕЧисло " + "ЗамПодстр " + "ЗаписьСправочника " + "ЗначПоляСпр " + "ИДТипСпр " + "ИзвлечьДиск " + "ИзвлечьИмяФайла " + "ИзвлечьПуть " + "ИзвлечьРасширение " + "ИзмДат " + "ИзменитьРазмерМассива " + "ИзмеренийМассива " + "ИмяОрг " + "ИмяПоляСпр " + "Индекс " + "ИндикаторЗакрыть " + "ИндикаторОткрыть " + "ИндикаторШаг " + "ИнтерактивныйРежим " + "ИтогТблСпр " + "КодВидВедСпр " + "КодВидСпрПоИД " + "КодПоAnalit " + "КодСимвола " + "КодСпр " + "КолПодстр " + "КолПроп " + "КонМес " + "Конст " + "КонстЕсть " + "КонстЗнач " + "КонТран " + "КопироватьФайл " + "КопияСтр " + "КПериод " + "КСтрТблСпр " + "Макс " + "МаксСтрТблСпр " + "Массив " + "Меню " + "МенюРасш " + "Мин " + "НаборДанныхНайтиРасш " + "НаимВидСпр " + "НаимПоAnalit " + "НаимСпр " + "НастроитьПереводыСтрок " + "НачМес " + "НачТран " + "НижняяГраницаМассива " + "НомерСпр " + "НПериод " + "Окно " + "Окр " + "Окружение " + "ОтлИнфДобавить " + "ОтлИнфУдалить " + "Отчет " + "ОтчетАнал " + "ОтчетИнт " + "ПапкаСуществует " + "Пауза " + "ПВыборSQL " + "ПереименоватьФайл " + "Переменные " + "ПереместитьФайл " + "Подстр " + "ПоискПодстр " + "ПоискСтр " + "ПолучитьИДТаблицы " + "ПользовательДополнительно " + "ПользовательИД " + "ПользовательИмя " + "ПользовательСтатус " + "Прервать " + "ПроверитьПараметр " + "ПроверитьПараметрЗнач " + "ПроверитьУсловие " + "РазбСтр " + "РазнВремя " + "РазнДат " + "РазнДатаВремя " + "РазнРабВремя " + "РегУстВрем " + "РегУстДат " + "РегУстЧсл " + "РедТекст " + "РеестрЗапись " + "РеестрСписокИменПарам " + "РеестрЧтение " + "РеквСпр " + "РеквСпрПр " + "Сегодня " + "Сейчас " + "Сервер " + "СерверПроцессИД " + "СертификатФайлСчитать " + "СжПроб " + "Символ " + "СистемаДиректумКод " + "СистемаИнформация " + "СистемаКод " + "Содержит " + "СоединениеЗакрыть " + "СоединениеОткрыть " + "СоздатьДиалог " + "СоздатьДиалогВыбораИзДвухСписков " + "СоздатьДиалогВыбораПапки " + "СоздатьДиалогОткрытияФайла " + "СоздатьДиалогСохраненияФайла " + "СоздатьЗапрос " + "СоздатьИндикатор " + "СоздатьИсключение " + "СоздатьКэшированныйСправочник " + "СоздатьМассив " + "СоздатьНаборДанных " + "СоздатьОбъект " + "СоздатьОтчет " + "СоздатьПапку " + "СоздатьРедактор " + "СоздатьСоединение " + "СоздатьСписок " + "СоздатьСписокСтрок " + "СоздатьСправочник " + "СоздатьСценарий " + "СоздСпр " + "СостСпр " + "Сохр " + "СохрСпр " + "СписокСистем " + "Спр " + "Справочник " + "СпрБлокЕсть " + "СпрБлокСнять " + "СпрБлокСнятьРасш " + "СпрБлокУстановить " + "СпрИзмНабДан " + "СпрКод " + "СпрНомер " + "СпрОбновить " + "СпрОткрыть " + "СпрОтменить " + "СпрПарам " + "СпрПолеЗнач " + "СпрПолеИмя " + "СпрРекв " + "СпрРеквВведЗн " + "СпрРеквНовые " + "СпрРеквПр " + "СпрРеквПредЗн " + "СпрРеквРежим " + "СпрРеквТипТекст " + "СпрСоздать " + "СпрСост " + "СпрСохранить " + "СпрТблИтог " + "СпрТблСтр " + "СпрТблСтрКол " + "СпрТблСтрМакс " + "СпрТблСтрМин " + "СпрТблСтрПред " + "СпрТблСтрСлед " + "СпрТблСтрСозд " + "СпрТблСтрУд " + "СпрТекПредст " + "СпрУдалить " + "СравнитьСтр " + "СтрВерхРегистр " + "СтрНижнРегистр " + "СтрТблСпр " + "СумПроп " + "Сценарий " + "СценарийПарам " + "ТекВерсия " + "ТекОрг " + "Точн " + "Тран " + "Транслитерация " + "УдалитьТаблицу " + "УдалитьФайл " + "УдСпр " + "УдСтрТблСпр " + "Уст " + "УстановкиКонстант " + "ФайлАтрибутСчитать " + "ФайлАтрибутУстановить " + "ФайлВремя " + "ФайлВремяУстановить " + "ФайлВыбрать " + "ФайлЗанят " + "ФайлЗаписать " + "ФайлИскать " + "ФайлКопировать " + "ФайлМожноЧитать " + "ФайлОткрыть " + "ФайлПереименовать " + "ФайлПерекодировать " + "ФайлПереместить " + "ФайлПросмотреть " + "ФайлРазмер " + "ФайлСоздать " + "ФайлСсылкаСоздать " + "ФайлСуществует " + "ФайлСчитать " + "ФайлУдалить " + "ФмтSQLДат " + "ФмтДат " + "ФмтСтр " + "ФмтЧсл " + "Формат " + "ЦМассивЭлемент " + "ЦНаборДанныхРеквизит " + "ЦПодстр "; // Предопределенные переменные ==> built_in var predefined_variables = "AltState " + "Application " + "CallType " + "ComponentTokens " + "CreatedJobs " + "CreatedNotices " + "ControlState " + "DialogResult " + "Dialogs " + "EDocuments " + "EDocumentVersionSource " + "Folders " + "GlobalIDs " + "Job " + "Jobs " + "InputValue " + "LookUpReference " + "LookUpRequisiteNames " + "LookUpSearch " + "Object " + "ParentComponent " + "Processes " + "References " + "Requisite " + "ReportName " + "Reports " + "Result " + "Scripts " + "Searches " + "SelectedAttachments " + "SelectedItems " + "SelectMode " + "Sender " + "ServerEvents " + "ServiceFactory " + "ShiftState " + "SubTask " + "SystemDialogs " + "Tasks " + "Wizard " + "Wizards " + "Work " + "ВызовСпособ " + "ИмяОтчета " + "РеквЗнач "; // Интерфейсы ==> type var interfaces = "IApplication " + "IAccessRights " + "IAccountRepository " + "IAccountSelectionRestrictions " + "IAction " + "IActionList " + "IAdministrationHistoryDescription " + "IAnchors " + "IApplication " + "IArchiveInfo " + "IAttachment " + "IAttachmentList " + "ICheckListBox " + "ICheckPointedList " + "IColumn " + "IComponent " + "IComponentDescription " + "IComponentToken " + "IComponentTokenFactory " + "IComponentTokenInfo " + "ICompRecordInfo " + "IConnection " + "IContents " + "IControl " + "IControlJob " + "IControlJobInfo " + "IControlList " + "ICrypto " + "ICrypto2 " + "ICustomJob " + "ICustomJobInfo " + "ICustomListBox " + "ICustomObjectWizardStep " + "ICustomWork " + "ICustomWorkInfo " + "IDataSet " + "IDataSetAccessInfo " + "IDataSigner " + "IDateCriterion " + "IDateRequisite " + "IDateRequisiteDescription " + "IDateValue " + "IDeaAccessRights " + "IDeaObjectInfo " + "IDevelopmentComponentLock " + "IDialog " + "IDialogFactory " + "IDialogPickRequisiteItems " + "IDialogsFactory " + "IDICSFactory " + "IDocRequisite " + "IDocumentInfo " + "IDualListDialog " + "IECertificate " + "IECertificateInfo " + "IECertificates " + "IEditControl " + "IEditorForm " + "IEdmsExplorer " + "IEdmsObject " + "IEdmsObjectDescription " + "IEdmsObjectFactory " + "IEdmsObjectInfo " + "IEDocument " + "IEDocumentAccessRights " + "IEDocumentDescription " + "IEDocumentEditor " + "IEDocumentFactory " + "IEDocumentInfo " + "IEDocumentStorage " + "IEDocumentVersion " + "IEDocumentVersionListDialog " + "IEDocumentVersionSource " + "IEDocumentWizardStep " + "IEDocVerSignature " + "IEDocVersionState " + "IEnabledMode " + "IEncodeProvider " + "IEncrypter " + "IEvent " + "IEventList " + "IException " + "IExternalEvents " + "IExternalHandler " + "IFactory " + "IField " + "IFileDialog " + "IFolder " + "IFolderDescription " + "IFolderDialog " + "IFolderFactory " + "IFolderInfo " + "IForEach " + "IForm " + "IFormTitle " + "IFormWizardStep " + "IGlobalIDFactory " + "IGlobalIDInfo " + "IGrid " + "IHasher " + "IHistoryDescription " + "IHyperLinkControl " + "IImageButton " + "IImageControl " + "IInnerPanel " + "IInplaceHint " + "IIntegerCriterion " + "IIntegerList " + "IIntegerRequisite " + "IIntegerValue " + "IISBLEditorForm " + "IJob " + "IJobDescription " + "IJobFactory " + "IJobForm " + "IJobInfo " + "ILabelControl " + "ILargeIntegerCriterion " + "ILargeIntegerRequisite " + "ILargeIntegerValue " + "ILicenseInfo " + "ILifeCycleStage " + "IList " + "IListBox " + "ILocalIDInfo " + "ILocalization " + "ILock " + "IMemoryDataSet " + "IMessagingFactory " + "IMetadataRepository " + "INotice " + "INoticeInfo " + "INumericCriterion " + "INumericRequisite " + "INumericValue " + "IObject " + "IObjectDescription " + "IObjectImporter " + "IObjectInfo " + "IObserver " + "IPanelGroup " + "IPickCriterion " + "IPickProperty " + "IPickRequisite " + "IPickRequisiteDescription " + "IPickRequisiteItem " + "IPickRequisiteItems " + "IPickValue " + "IPrivilege " + "IPrivilegeList " + "IProcess " + "IProcessFactory " + "IProcessMessage " + "IProgress " + "IProperty " + "IPropertyChangeEvent " + "IQuery " + "IReference " + "IReferenceCriterion " + "IReferenceEnabledMode " + "IReferenceFactory " + "IReferenceHistoryDescription " + "IReferenceInfo " + "IReferenceRecordCardWizardStep " + "IReferenceRequisiteDescription " + "IReferencesFactory " + "IReferenceValue " + "IRefRequisite " + "IReport " + "IReportFactory " + "IRequisite " + "IRequisiteDescription " + "IRequisiteDescriptionList " + "IRequisiteFactory " + "IRichEdit " + "IRouteStep " + "IRule " + "IRuleList " + "ISchemeBlock " + "IScript " + "IScriptFactory " + "ISearchCriteria " + "ISearchCriterion " + "ISearchDescription " + "ISearchFactory " + "ISearchFolderInfo " + "ISearchForObjectDescription " + "ISearchResultRestrictions " + "ISecuredContext " + "ISelectDialog " + "IServerEvent " + "IServerEventFactory " + "IServiceDialog " + "IServiceFactory " + "ISignature " + "ISignProvider " + "ISignProvider2 " + "ISignProvider3 " + "ISimpleCriterion " + "IStringCriterion " + "IStringList " + "IStringRequisite " + "IStringRequisiteDescription " + "IStringValue " + "ISystemDialogsFactory " + "ISystemInfo " + "ITabSheet " + "ITask " + "ITaskAbortReasonInfo " + "ITaskCardWizardStep " + "ITaskDescription " + "ITaskFactory " + "ITaskInfo " + "ITaskRoute " + "ITextCriterion " + "ITextRequisite " + "ITextValue " + "ITreeListSelectDialog " + "IUser " + "IUserList " + "IValue " + "IView " + "IWebBrowserControl " + "IWizard " + "IWizardAction " + "IWizardFactory " + "IWizardFormElement " + "IWizardParam " + "IWizardPickParam " + "IWizardReferenceParam " + "IWizardStep " + "IWorkAccessRights " + "IWorkDescription " + "IWorkflowAskableParam " + "IWorkflowAskableParams " + "IWorkflowBlock " + "IWorkflowBlockResult " + "IWorkflowEnabledMode " + "IWorkflowParam " + "IWorkflowPickParam " + "IWorkflowReferenceParam " + "IWorkState " + "IWorkTreeCustomNode " + "IWorkTreeJobNode " + "IWorkTreeTaskNode " + "IXMLEditorForm " + "SBCrypto "; // built_in : встроенные или библиотечные объекты (константы, перечисления) var BUILTIN = CONSTANTS + ENUMS; // class: встроенные наборы значений, системные объекты, фабрики var CLASS = predefined_variables; // literal : примитивные типы var LITERAL = "null true false nil "; // number : числа var NUMBERS = { className: "number", begin: hljs.NUMBER_RE, relevance: 0, }; // string : строки var STRINGS = { className: "string", variants: [ { begin: '"', end: '"' }, { begin: "'", end: "'" }, ], }; // Токены var DOCTAGS = { className: "doctag", begin: "\\b(?:TODO|DONE|BEGIN|END|STUB|CHG|FIXME|NOTE|BUG|XXX)\\b", relevance: 0, }; // Однострочный комментарий var ISBL_LINE_COMMENT_MODE = { className: "comment", begin: "//", end: "$", relevance: 0, contains: [hljs.PHRASAL_WORDS_MODE, DOCTAGS], }; // Многострочный комментарий var ISBL_BLOCK_COMMENT_MODE = { className: "comment", begin: "/\\*", end: "\\*/", relevance: 0, contains: [hljs.PHRASAL_WORDS_MODE, DOCTAGS], }; // comment : комментарии var COMMENTS = { variants: [ISBL_LINE_COMMENT_MODE, ISBL_BLOCK_COMMENT_MODE], }; // keywords : ключевые слова var KEYWORDS = { keyword: KEYWORD, built_in: BUILTIN, class: CLASS, literal: LITERAL, }; // methods : методы var METHODS = { begin: "\\.\\s*" + hljs.UNDERSCORE_IDENT_RE, keywords: KEYWORDS, relevance: 0, }; // type : встроенные типы var TYPES = { className: "type", begin: ":[ \\t]*(" + interfaces.trim().replace(/\s/g, "|") + ")", end: "[ \\t]*=", excludeEnd: true, }; // variables : переменные var VARIABLES = { className: "variable", lexemes: UNDERSCORE_IDENT_RE, keywords: KEYWORDS, begin: UNDERSCORE_IDENT_RE, relevance: 0, contains: [TYPES, METHODS], }; // Имена функций var FUNCTION_TITLE = FUNCTION_NAME_IDENT_RE + "\\("; var TITLE_MODE = { className: "title", lexemes: UNDERSCORE_IDENT_RE, keywords: { built_in: system_functions, }, begin: FUNCTION_TITLE, end: "\\(", returnBegin: true, excludeEnd: true, }; // function : функции var FUNCTIONS = { className: "function", begin: FUNCTION_TITLE, end: "\\)$", returnBegin: true, lexemes: UNDERSCORE_IDENT_RE, keywords: KEYWORDS, illegal: "[\\[\\]\\|\\$\\?%,~#@]", contains: [ TITLE_MODE, METHODS, VARIABLES, STRINGS, NUMBERS, COMMENTS, ], }; return { aliases: ["isbl"], case_insensitive: true, lexemes: UNDERSCORE_IDENT_RE, keywords: KEYWORDS, illegal: "\\$|\\?|%|,|;$|~|#|@| Category: common, enterprise */ create: function (hljs) { var JAVA_IDENT_RE = "[\u00C0-\u02B8a-zA-Z_$][\u00C0-\u02B8a-zA-Z_$0-9]*"; var GENERIC_IDENT_RE = JAVA_IDENT_RE + "(<" + JAVA_IDENT_RE + "(\\s*,\\s*" + JAVA_IDENT_RE + ")*>)?"; var KEYWORDS = "false synchronized int abstract float private char boolean var static null if const " + "for true while long strictfp finally protected import native final void " + "enum else break transient catch instanceof byte super volatile case assert short " + "package default double public try this switch continue throws protected public private " + "module requires exports do"; // https://docs.oracle.com/javase/7/docs/technotes/guides/language/underscores-literals.html var JAVA_NUMBER_RE = "\\b" + "(" + "0[bB]([01]+[01_]+[01]+|[01]+)" + // 0b... "|" + "0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)" + // 0x... "|" + "(" + "([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?" + "|" + "\\.([\\d]+[\\d_]+[\\d]+|[\\d]+)" + ")" + "([eE][-+]?\\d+)?" + // octal, decimal, float ")" + "[lLfF]?"; var JAVA_NUMBER_MODE = { className: "number", begin: JAVA_NUMBER_RE, relevance: 0, }; return { aliases: ["jsp"], keywords: KEYWORDS, illegal: /<\/|#/, contains: [ hljs.COMMENT("/\\*\\*", "\\*/", { relevance: 0, contains: [ { // eat up @'s in emails to prevent them to be recognized as doctags begin: /\w+@/, relevance: 0, }, { className: "doctag", begin: "@[A-Za-z]+", }, ], }), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { className: "class", beginKeywords: "class interface", end: /[{;=]/, excludeEnd: true, keywords: "class interface", illegal: /[:"\[\]]/, contains: [ { beginKeywords: "extends implements" }, hljs.UNDERSCORE_TITLE_MODE, ], }, { // Expression keywords prevent 'keyword Name(...)' from being // recognized as a function definition beginKeywords: "new throw return else", relevance: 0, }, { className: "function", begin: "(" + GENERIC_IDENT_RE + "\\s+)+" + hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", returnBegin: true, end: /[{;=]/, excludeEnd: true, keywords: KEYWORDS, contains: [ { begin: hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", returnBegin: true, relevance: 0, contains: [hljs.UNDERSCORE_TITLE_MODE], }, { className: "params", begin: /\(/, end: /\)/, keywords: KEYWORDS, relevance: 0, contains: [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, JAVA_NUMBER_MODE, { className: "meta", begin: "@[A-Za-z]+", }, ], }; }, }, { name: "javascript", /* Language: JavaScript Category: common, scripting */ create: function (hljs) { var IDENT_RE = "[A-Za-z$_][0-9A-Za-z$_]*"; var KEYWORDS = { keyword: "in of if for while finally var new function do return void else break catch " + "instanceof with throw case default try this switch continue typeof delete " + "let yield const export super debugger as async await static " + // ECMAScript 6 modules import "import from as", literal: "true false null undefined NaN Infinity", built_in: "eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent " + "encodeURI encodeURIComponent escape unescape Object Function Boolean Error " + "EvalError InternalError RangeError ReferenceError StopIteration SyntaxError " + "TypeError URIError Number Math Date String RegExp Array Float32Array " + "Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array " + "Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require " + "module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect " + "Promise", }; var NUMBER = { className: "number", variants: [ { begin: "\\b(0[bB][01]+)" }, { begin: "\\b(0[oO][0-7]+)" }, { begin: hljs.C_NUMBER_RE }, ], relevance: 0, }; var SUBST = { className: "subst", begin: "\\$\\{", end: "\\}", keywords: KEYWORDS, contains: [], // defined later }; var HTML_TEMPLATE = { begin: "html`", end: "", starts: { end: "`", returnEnd: false, contains: [hljs.BACKSLASH_ESCAPE, SUBST], subLanguage: "xml", }, }; var CSS_TEMPLATE = { begin: "css`", end: "", starts: { end: "`", returnEnd: false, contains: [hljs.BACKSLASH_ESCAPE, SUBST], subLanguage: "css", }, }; var TEMPLATE_STRING = { className: "string", begin: "`", end: "`", contains: [hljs.BACKSLASH_ESCAPE, SUBST], }; SUBST.contains = [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, HTML_TEMPLATE, CSS_TEMPLATE, TEMPLATE_STRING, NUMBER, hljs.REGEXP_MODE, ]; var PARAMS_CONTAINS = SUBST.contains.concat([ hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE, ]); return { aliases: ["js", "jsx"], keywords: KEYWORDS, contains: [ { className: "meta", relevance: 10, begin: /^\s*['"]use (strict|asm)['"]/, }, { className: "meta", begin: /^#!/, end: /$/, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, HTML_TEMPLATE, CSS_TEMPLATE, TEMPLATE_STRING, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, NUMBER, { // object attr container begin: /[{,]\s*/, relevance: 0, contains: [ { begin: IDENT_RE + "\\s*:", returnBegin: true, relevance: 0, contains: [ { className: "attr", begin: IDENT_RE, relevance: 0, }, ], }, ], }, { // "value" container begin: "(" + hljs.RE_STARTERS_RE + "|\\b(case|return|throw)\\b)\\s*", keywords: "return throw case", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.REGEXP_MODE, { className: "function", begin: "(\\(.*?\\)|" + IDENT_RE + ")\\s*=>", returnBegin: true, end: "\\s*=>", contains: [ { className: "params", variants: [ { begin: IDENT_RE, }, { begin: /\(\s*\)/, }, { begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, keywords: KEYWORDS, contains: PARAMS_CONTAINS, }, ], }, ], }, { className: "", begin: /\s/, end: /\s*/, skip: true, }, { // E4X / JSX begin: //, subLanguage: "xml", contains: [ { begin: /<[A-Za-z0-9\\._:-]+\s*\/>/, skip: true }, { begin: /<[A-Za-z0-9\\._:-]+/, end: /(\/[A-Za-z0-9\\._:-]+|[A-Za-z0-9\\._:-]+\/)>/, skip: true, contains: [ { begin: /<[A-Za-z0-9\\._:-]+\s*\/>/, skip: true, }, "self", ], }, ], }, ], relevance: 0, }, { className: "function", beginKeywords: "function", end: /\{/, excludeEnd: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE }), { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, contains: PARAMS_CONTAINS, }, ], illegal: /\[|%/, }, { begin: /\$[(.]/, // relevance booster for a pattern common to JS libs: `$(something)` and `$.something` }, hljs.METHOD_GUARD, { // ES6 class className: "class", beginKeywords: "class", end: /[{;=]/, excludeEnd: true, illegal: /[:"\[\]]/, contains: [ { beginKeywords: "extends" }, hljs.UNDERSCORE_TITLE_MODE, ], }, { beginKeywords: "constructor get set", end: /\{/, excludeEnd: true, }, ], illegal: /#(?!!)/, }; }, }, { name: "jboss-cli", /* Language: jboss-cli Author: Raphaël Parrëe Description: language definition jboss cli Category: config */ create: function (hljs) { var PARAM = { begin: /[\w-]+ *=/, returnBegin: true, relevance: 0, contains: [{ className: "attr", begin: /[\w-]+/ }], }; var PARAMSBLOCK = { className: "params", begin: /\(/, end: /\)/, contains: [PARAM], relevance: 0, }; var OPERATION = { className: "function", begin: /:[\w\-.]+/, relevance: 0, }; var PATH = { className: "string", begin: /\B(([\/.])[\w\-.\/=]+)+/, }; var COMMAND_PARAMS = { className: "params", begin: /--[\w\-=\/]+/, }; return { aliases: ["wildfly-cli"], lexemes: "[a-z\-]+", keywords: { keyword: "alias batch cd clear command connect connection-factory connection-info data-source deploy " + "deployment-info deployment-overlay echo echo-dmr help history if jdbc-driver-info jms-queue|20 jms-topic|20 ls " + "patch pwd quit read-attribute read-operation reload rollout-plan run-batch set shutdown try unalias " + "undeploy unset version xa-data-source", // module literal: "true false", }, contains: [ hljs.HASH_COMMENT_MODE, hljs.QUOTE_STRING_MODE, COMMAND_PARAMS, OPERATION, PATH, PARAMSBLOCK, ], }; }, }, { name: "json", /* Language: JSON Author: Ivan Sagalaev Category: common, protocols */ create: function (hljs) { var LITERALS = { literal: "true false null" }; var TYPES = [hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE]; var VALUE_CONTAINER = { end: ",", endsWithParent: true, excludeEnd: true, contains: TYPES, keywords: LITERALS, }; var OBJECT = { begin: "{", end: "}", contains: [ { className: "attr", begin: /"/, end: /"/, contains: [hljs.BACKSLASH_ESCAPE], illegal: "\\n", }, hljs.inherit(VALUE_CONTAINER, { begin: /:/ }), ], illegal: "\\S", }; var ARRAY = { begin: "\\[", end: "\\]", contains: [hljs.inherit(VALUE_CONTAINER)], // inherit is a workaround for a bug that makes shared modes with endsWithParent compile only the ending of one of the parents illegal: "\\S", }; TYPES.splice(TYPES.length, 0, OBJECT, ARRAY); return { contains: TYPES, keywords: LITERALS, illegal: "\\S", }; }, }, { name: "julia-repl", /* Language: Julia REPL Description: Julia REPL sessions Author: Morten Piibeleht Requires: julia.js The Julia REPL code blocks look something like the following: julia> function foo(x) x + 1 end foo (generic function with 1 method) They start on a new line with "julia>". Usually there should also be a space after this, but we also allow the code to start right after the > character. The code may run over multiple lines, but the additional lines must start with six spaces (i.e. be indented to match "julia>"). The rest of the code is assumed to be output from the executed code and will be left un-highlighted. Using simply spaces to identify line continuations may get a false-positive if the output also prints out six spaces, but such cases should be rare. */ create: function (hljs) { return { contains: [ { className: "meta", begin: /^julia>/, relevance: 10, starts: { // end the highlighting if we are on a new line and the line does not have at // least six spaces in the beginning end: /^(?![ ]{6})/, subLanguage: "julia", }, // jldoctest Markdown blocks are used in the Julia manual and package docs indicate // code snippets that should be verified when the documentation is built. They can be // either REPL-like or script-like, but are usually REPL-like and therefore we apply // julia-repl highlighting to them. More information can be found in Documenter's // manual: https://juliadocs.github.io/Documenter.jl/latest/man/doctests.html aliases: ["jldoctest"], }, ], }; }, }, { name: "julia", /* Language: Julia Author: Kenta Sato Contributors: Alex Arslan */ create: function (hljs) { // Since there are numerous special names in Julia, it is too much trouble // to maintain them by hand. Hence these names (i.e. keywords, literals and // built-ins) are automatically generated from Julia v0.6 itself through // the following scripts for each. var KEYWORDS = { // # keyword generator, multi-word keywords handled manually below // foreach(println, ["in", "isa", "where"]) // for kw in Base.REPLCompletions.complete_keyword("") // if !(contains(kw, " ") || kw == "struct") // println(kw) // end // end keyword: "in isa where " + "baremodule begin break catch ccall const continue do else elseif end export false finally for function " + "global if import importall let local macro module quote return true try using while " + // legacy, to be deprecated in the next release "type immutable abstract bitstype typealias ", // # literal generator // println("true") // println("false") // for name in Base.REPLCompletions.completions("", 0)[1] // try // v = eval(Symbol(name)) // if !(v isa Function || v isa Type || v isa TypeVar || v isa Module || v isa Colon) // println(name) // end // end // end literal: "true false " + "ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort " + "NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway " + "RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im " + "nothing pi γ π φ ", // # built_in generator: // for name in Base.REPLCompletions.completions("", 0)[1] // try // v = eval(Symbol(name)) // if v isa Type || v isa TypeVar // println(name) // end // end // end built_in: "ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet " + "AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat " + "AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal " + "BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException " + "CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager " + "Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix " + "ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t " + "Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict " + "DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate " + "ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function " + "Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear " + "IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException " + "InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix " + "MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict " + "OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe " + "PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD " + "ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode " + "RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed " + "SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange " + "StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal " + "Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry " + "TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError " + "UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector " + "VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool ", }; // ref: http://julia.readthedocs.org/en/latest/manual/variables/#allowed-variable-names var VARIABLE_NAME_RE = "[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*"; // placeholder for recursive self-reference var DEFAULT = { lexemes: VARIABLE_NAME_RE, keywords: KEYWORDS, illegal: /<\//, }; // ref: http://julia.readthedocs.org/en/latest/manual/integers-and-floating-point-numbers/ var NUMBER = { className: "number", // supported numeric literals: // * binary literal (e.g. 0x10) // * octal literal (e.g. 0o76543210) // * hexadecimal literal (e.g. 0xfedcba876543210) // * hexadecimal floating point literal (e.g. 0x1p0, 0x1.2p2) // * decimal literal (e.g. 9876543210, 100_000_000) // * floating pointe literal (e.g. 1.2, 1.2f, .2, 1., 1.2e10, 1.2e-10) begin: /(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/, relevance: 0, }; var CHAR = { className: "string", begin: /'(.|\\[xXuU][a-zA-Z0-9]+)'/, }; var INTERPOLATION = { className: "subst", begin: /\$\(/, end: /\)/, keywords: KEYWORDS, }; var INTERPOLATED_VARIABLE = { className: "variable", begin: "\\$" + VARIABLE_NAME_RE, }; // TODO: neatly escape normal code in string literal var STRING = { className: "string", contains: [ hljs.BACKSLASH_ESCAPE, INTERPOLATION, INTERPOLATED_VARIABLE, ], variants: [ { begin: /\w*"""/, end: /"""\w*/, relevance: 10 }, { begin: /\w*"/, end: /"\w*/ }, ], }; var COMMAND = { className: "string", contains: [ hljs.BACKSLASH_ESCAPE, INTERPOLATION, INTERPOLATED_VARIABLE, ], begin: "`", end: "`", }; var MACROCALL = { className: "meta", begin: "@" + VARIABLE_NAME_RE, }; var COMMENT = { className: "comment", variants: [ { begin: "#=", end: "=#", relevance: 10 }, { begin: "#", end: "$" }, ], }; DEFAULT.contains = [ NUMBER, CHAR, STRING, COMMAND, MACROCALL, COMMENT, hljs.HASH_COMMENT_MODE, { className: "keyword", begin: "\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b", }, { begin: /<:/ }, // relevance booster ]; INTERPOLATION.contains = DEFAULT.contains; return DEFAULT; }, }, { name: "kotlin", /* Language: Kotlin Author: Sergey Mashkov */ create: function (hljs) { var KEYWORDS = { keyword: "abstract as val var vararg get set class object open private protected public noinline " + "crossinline dynamic final enum if else do while for when throw try catch finally " + "import package is in fun override companion reified inline lateinit init " + "interface annotation data sealed internal infix operator out by constructor super " + "tailrec where const inner suspend typealias external expect actual " + // to be deleted soon "trait volatile transient native default", built_in: "Byte Short Char Int Long Boolean Float Double Void Unit Nothing", literal: "true false null", }; var KEYWORDS_WITH_LABEL = { className: "keyword", begin: /\b(break|continue|return|this)\b/, starts: { contains: [ { className: "symbol", begin: /@\w+/, }, ], }, }; var LABEL = { className: "symbol", begin: hljs.UNDERSCORE_IDENT_RE + "@", }; // for string templates var SUBST = { className: "subst", begin: "\\${", end: "}", contains: [hljs.APOS_STRING_MODE, hljs.C_NUMBER_MODE], }; var VARIABLE = { className: "variable", begin: "\\$" + hljs.UNDERSCORE_IDENT_RE, }; var STRING = { className: "string", variants: [ { begin: '"""', end: '"""', contains: [VARIABLE, SUBST], }, // Can't use built-in modes easily, as we want to use STRING in the meta // context as 'meta-string' and there's no syntax to remove explicitly set // classNames in built-in modes. { begin: "'", end: "'", illegal: /\n/, contains: [hljs.BACKSLASH_ESCAPE], }, { begin: '"', end: '"', illegal: /\n/, contains: [hljs.BACKSLASH_ESCAPE, VARIABLE, SUBST], }, ], }; var ANNOTATION_USE_SITE = { className: "meta", begin: "@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*" + hljs.UNDERSCORE_IDENT_RE + ")?", }; var ANNOTATION = { className: "meta", begin: "@" + hljs.UNDERSCORE_IDENT_RE, contains: [ { begin: /\(/, end: /\)/, contains: [ hljs.inherit(STRING, { className: "meta-string" }), ], }, ], }; // https://kotlinlang.org/docs/reference/whatsnew11.html#underscores-in-numeric-literals // According to the doc above, the number mode of kotlin is the same as java 8, // so the code below is copied from java.js var KOTLIN_NUMBER_RE = "\\b" + "(" + "0[bB]([01]+[01_]+[01]+|[01]+)" + // 0b... "|" + "0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)" + // 0x... "|" + "(" + "([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?" + "|" + "\\.([\\d]+[\\d_]+[\\d]+|[\\d]+)" + ")" + "([eE][-+]?\\d+)?" + // octal, decimal, float ")" + "[lLfF]?"; var KOTLIN_NUMBER_MODE = { className: "number", begin: KOTLIN_NUMBER_RE, relevance: 0, }; var KOTLIN_NESTED_COMMENT = hljs.COMMENT("/\\*", "\\*/", { contains: [hljs.C_BLOCK_COMMENT_MODE], }); var KOTLIN_PAREN_TYPE = { variants: [ { className: "type", begin: hljs.UNDERSCORE_IDENT_RE }, { begin: /\(/, end: /\)/, contains: [], //defined later }, ], }; var KOTLIN_PAREN_TYPE2 = KOTLIN_PAREN_TYPE; KOTLIN_PAREN_TYPE2.variants[1].contains = [KOTLIN_PAREN_TYPE]; KOTLIN_PAREN_TYPE.variants[1].contains = [KOTLIN_PAREN_TYPE2]; return { aliases: ["kt"], keywords: KEYWORDS, contains: [ hljs.COMMENT("/\\*\\*", "\\*/", { relevance: 0, contains: [ { className: "doctag", begin: "@[A-Za-z]+", }, ], }), hljs.C_LINE_COMMENT_MODE, KOTLIN_NESTED_COMMENT, KEYWORDS_WITH_LABEL, LABEL, ANNOTATION_USE_SITE, ANNOTATION, { className: "function", beginKeywords: "fun", end: "[(]|$", returnBegin: true, excludeEnd: true, keywords: KEYWORDS, illegal: /fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/, relevance: 5, contains: [ { begin: hljs.UNDERSCORE_IDENT_RE + "\\s*\\(", returnBegin: true, relevance: 0, contains: [hljs.UNDERSCORE_TITLE_MODE], }, { className: "type", begin: //, keywords: "reified", relevance: 0, }, { className: "params", begin: /\(/, end: /\)/, endsParent: true, keywords: KEYWORDS, relevance: 0, contains: [ { begin: /:/, end: /[=,\/]/, endsWithParent: true, contains: [ KOTLIN_PAREN_TYPE, hljs.C_LINE_COMMENT_MODE, KOTLIN_NESTED_COMMENT, ], relevance: 0, }, hljs.C_LINE_COMMENT_MODE, KOTLIN_NESTED_COMMENT, ANNOTATION_USE_SITE, ANNOTATION, STRING, hljs.C_NUMBER_MODE, ], }, KOTLIN_NESTED_COMMENT, ], }, { className: "class", beginKeywords: "class interface trait", end: /[:\{(]|$/, // remove 'trait' when removed from KEYWORDS excludeEnd: true, illegal: "extends implements", contains: [ { beginKeywords: "public protected internal private constructor", }, hljs.UNDERSCORE_TITLE_MODE, { className: "type", begin: //, excludeBegin: true, excludeEnd: true, relevance: 0, }, { className: "type", begin: /[,:]\s*/, end: /[<\(,]|$/, excludeBegin: true, returnEnd: true, }, ANNOTATION_USE_SITE, ANNOTATION, ], }, STRING, { className: "meta", begin: "^#!/usr/bin/env", end: "$", illegal: "\n", }, KOTLIN_NUMBER_MODE, ], }; }, }, { name: "lasso", /* Language: Lasso Author: Eric Knibbe Description: Lasso is a language and server platform for database-driven web applications. This definition handles Lasso 9 syntax and LassoScript for Lasso 8.6 and earlier. */ create: function (hljs) { var LASSO_IDENT_RE = "[a-zA-Z_][\\w.]*"; var LASSO_ANGLE_RE = "<\\?(lasso(script)?|=)"; var LASSO_CLOSE_RE = "\\]|\\?>"; var LASSO_KEYWORDS = { literal: "true false none minimal full all void and or not " + "bw nbw ew new cn ncn lt lte gt gte eq neq rx nrx ft", built_in: "array date decimal duration integer map pair string tag xml null " + "boolean bytes keyword list locale queue set stack staticarray " + "local var variable global data self inherited currentcapture givenblock", keyword: "cache database_names database_schemanames database_tablenames " + "define_tag define_type email_batch encode_set html_comment handle " + "handle_error header if inline iterate ljax_target link " + "link_currentaction link_currentgroup link_currentrecord link_detail " + "link_firstgroup link_firstrecord link_lastgroup link_lastrecord " + "link_nextgroup link_nextrecord link_prevgroup link_prevrecord log " + "loop namespace_using output_none portal private protect records " + "referer referrer repeating resultset rows search_args " + "search_arguments select sort_args sort_arguments thread_atomic " + "value_list while abort case else fail_if fail_ifnot fail if_empty " + "if_false if_null if_true loop_abort loop_continue loop_count params " + "params_up return return_value run_children soap_definetag " + "soap_lastrequest soap_lastresponse tag_name ascending average by " + "define descending do equals frozen group handle_failure import in " + "into join let match max min on order parent protected provide public " + "require returnhome skip split_thread sum take thread to trait type " + "where with yield yieldhome", }; var HTML_COMMENT = hljs.COMMENT("", { relevance: 0, }); var LASSO_NOPROCESS = { className: "meta", begin: "\\[noprocess\\]", starts: { end: "\\[/noprocess\\]", returnEnd: true, contains: [HTML_COMMENT], }, }; var LASSO_START = { className: "meta", begin: "\\[/noprocess|" + LASSO_ANGLE_RE, }; var LASSO_DATAMEMBER = { className: "symbol", begin: "'" + LASSO_IDENT_RE + "'", }; var LASSO_CODE = [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.inherit(hljs.C_NUMBER_MODE, { begin: hljs.C_NUMBER_RE + "|(-?infinity|NaN)\\b", }), hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), { className: "string", begin: "`", end: "`", }, { // variables variants: [ { begin: "[#$]" + LASSO_IDENT_RE, }, { begin: "#", end: "\\d+", illegal: "\\W", }, ], }, { className: "type", begin: "::\\s*", end: LASSO_IDENT_RE, illegal: "\\W", }, { className: "params", variants: [ { begin: "-(?!infinity)" + LASSO_IDENT_RE, relevance: 0, }, { begin: "(\\.\\.\\.)", }, ], }, { begin: /(->|\.)\s*/, relevance: 0, contains: [LASSO_DATAMEMBER], }, { className: "class", beginKeywords: "define", returnEnd: true, end: "\\(|=>", contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: LASSO_IDENT_RE + "(=(?!>))?|[-+*/%](?!>)", }), ], }, ]; return { aliases: ["ls", "lassoscript"], case_insensitive: true, lexemes: LASSO_IDENT_RE + "|&[lg]t;", keywords: LASSO_KEYWORDS, contains: [ { className: "meta", begin: LASSO_CLOSE_RE, relevance: 0, starts: { // markup end: "\\[|" + LASSO_ANGLE_RE, returnEnd: true, relevance: 0, contains: [HTML_COMMENT], }, }, LASSO_NOPROCESS, LASSO_START, { className: "meta", begin: "\\[no_square_brackets", starts: { end: "\\[/no_square_brackets\\]", // not implemented in the language lexemes: LASSO_IDENT_RE + "|&[lg]t;", keywords: LASSO_KEYWORDS, contains: [ { className: "meta", begin: LASSO_CLOSE_RE, relevance: 0, starts: { end: "\\[noprocess\\]|" + LASSO_ANGLE_RE, returnEnd: true, contains: [HTML_COMMENT], }, }, LASSO_NOPROCESS, LASSO_START, ].concat(LASSO_CODE), }, }, { className: "meta", begin: "\\[", relevance: 0, }, { className: "meta", begin: "^#!", end: "lasso9$", relevance: 10, }, ].concat(LASSO_CODE), }; }, }, { name: "ldif", /* Language: LDIF Contributors: Jacob Childress Category: enterprise, config */ create: function (hljs) { return { contains: [ { className: "attribute", begin: "^dn", end: ": ", excludeEnd: true, starts: { end: "$", relevance: 0 }, relevance: 10, }, { className: "attribute", begin: "^\\w", end: ": ", excludeEnd: true, starts: { end: "$", relevance: 0 }, }, { className: "literal", begin: "^-", end: "$", }, hljs.HASH_COMMENT_MODE, ], }; }, }, { name: "leaf", /* Language: Leaf Author: Hale Chan Description: Based on the Leaf reference from https://vapor.github.io/documentation/guide/leaf.html. */ create: function (hljs) { return { contains: [ { className: "function", begin: "#+" + "[A-Za-z_0-9]*" + "\\(", end: " {", returnBegin: true, excludeEnd: true, contains: [ { className: "keyword", begin: "#+", }, { className: "title", begin: "[A-Za-z_][A-Za-z_0-9]*", }, { className: "params", begin: "\\(", end: "\\)", endsParent: true, contains: [ { className: "string", begin: '"', end: '"', }, { className: "variable", begin: "[A-Za-z_][A-Za-z_0-9]*", }, ], }, ], }, ], }; }, }, { name: "less", /* Language: Less Author: Max Mikhailov Category: css */ create: function (hljs) { var IDENT_RE = "[\\w-]+"; // yes, Less identifiers may begin with a digit var INTERP_IDENT_RE = "(" + IDENT_RE + "|@{" + IDENT_RE + "})"; /* Generic Modes */ var RULES = [], VALUE = []; // forward def. for recursive modes var STRING_MODE = function (c) { return { // Less strings are not multiline (also include '~' for more consistent coloring of "escaped" strings) className: "string", begin: "~?" + c + ".*?" + c, }; }; var IDENT_MODE = function (name, begin, relevance) { return { className: name, begin: begin, relevance: relevance, }; }; var PARENS_MODE = { // used only to properly balance nested parens inside mixin call, def. arg list begin: "\\(", end: "\\)", contains: VALUE, relevance: 0, }; // generic Less highlighter (used almost everywhere except selectors): VALUE.push( hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, STRING_MODE("'"), STRING_MODE('"'), hljs.CSS_NUMBER_MODE, // fixme: it does not include dot for numbers like .5em :( { begin: "(url|data-uri)\\(", starts: { className: "string", end: "[\\)\\n]", excludeEnd: true, }, }, IDENT_MODE("number", "#[0-9A-Fa-f]+\\b"), PARENS_MODE, IDENT_MODE("variable", "@@?" + IDENT_RE, 10), IDENT_MODE("variable", "@{" + IDENT_RE + "}"), IDENT_MODE("built_in", "~?`[^`]*?`"), // inline javascript (or whatever host language) *multiline* string { // @media features (it’s here to not duplicate things in AT_RULE_MODE with extra PARENS_MODE overriding): className: "attribute", begin: IDENT_RE + "\\s*:", end: ":", returnBegin: true, excludeEnd: true, }, { className: "meta", begin: "!important", }, ); var VALUE_WITH_RULESETS = VALUE.concat({ begin: "{", end: "}", contains: RULES, }); var MIXIN_GUARD_MODE = { beginKeywords: "when", endsWithParent: true, contains: [{ beginKeywords: "and not" }].concat(VALUE), // using this form to override VALUE’s 'function' match }; /* Rule-Level Modes */ var RULE_MODE = { begin: INTERP_IDENT_RE + "\\s*:", returnBegin: true, end: "[;}]", relevance: 0, contains: [ { className: "attribute", begin: INTERP_IDENT_RE, end: ":", excludeEnd: true, starts: { endsWithParent: true, illegal: "[<=$]", relevance: 0, contains: VALUE, }, }, ], }; var AT_RULE_MODE = { className: "keyword", begin: "@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", starts: { end: "[;{}]", returnEnd: true, contains: VALUE, relevance: 0, }, }; // variable definitions and calls var VAR_RULE_MODE = { className: "variable", variants: [ // using more strict pattern for higher relevance to increase chances of Less detection. // this is *the only* Less specific statement used in most of the sources, so... // (we’ll still often loose to the css-parser unless there's '//' comment, // simply because 1 variable just can't beat 99 properties :) { begin: "@" + IDENT_RE + "\\s*:", relevance: 15 }, { begin: "@" + IDENT_RE }, ], starts: { end: "[;}]", returnEnd: true, contains: VALUE_WITH_RULESETS, }, }; var SELECTOR_MODE = { // first parse unambiguous selectors (i.e. those not starting with tag) // then fall into the scary lookahead-discriminator variant. // this mode also handles mixin definitions and calls variants: [ { begin: "[\\.#:&\\[>]", end: "[;{}]", // mixin calls end with ';' }, { begin: INTERP_IDENT_RE, end: "{", }, ], returnBegin: true, returnEnd: true, illegal: "[<='$\"]", relevance: 0, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, MIXIN_GUARD_MODE, IDENT_MODE("keyword", "all\\b"), IDENT_MODE("variable", "@{" + IDENT_RE + "}"), // otherwise it’s identified as tag IDENT_MODE("selector-tag", INTERP_IDENT_RE + "%?", 0), // '%' for more consistent coloring of @keyframes "tags" IDENT_MODE("selector-id", "#" + INTERP_IDENT_RE), IDENT_MODE("selector-class", "\\." + INTERP_IDENT_RE, 0), IDENT_MODE("selector-tag", "&", 0), { className: "selector-attr", begin: "\\[", end: "\\]" }, { className: "selector-pseudo", begin: /:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/, }, { begin: "\\(", end: "\\)", contains: VALUE_WITH_RULESETS }, // argument list of parametric mixins { begin: "!important" }, // eat !important after mixin call or it will be colored as tag ], }; RULES.push( hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, AT_RULE_MODE, VAR_RULE_MODE, RULE_MODE, SELECTOR_MODE, ); return { case_insensitive: true, illegal: "[=>'/<($\"]", contains: RULES, }; }, }, { name: "lisp", /* Language: Lisp Description: Generic lisp syntax Author: Vasily Polovnyov Category: lisp */ create: function (hljs) { var LISP_IDENT_RE = "[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"; var MEC_RE = "\\|[^]*?\\|"; var LISP_SIMPLE_NUMBER_RE = "(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?"; var SHEBANG = { className: "meta", begin: "^#!", end: "$", }; var LITERAL = { className: "literal", begin: "\\b(t{1}|nil)\\b", }; var NUMBER = { className: "number", variants: [ { begin: LISP_SIMPLE_NUMBER_RE, relevance: 0 }, { begin: "#(b|B)[0-1]+(/[0-1]+)?" }, { begin: "#(o|O)[0-7]+(/[0-7]+)?" }, { begin: "#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?" }, { begin: "#(c|C)\\(" + LISP_SIMPLE_NUMBER_RE + " +" + LISP_SIMPLE_NUMBER_RE, end: "\\)", }, ], }; var STRING = hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null, }); var COMMENT = hljs.COMMENT(";", "$", { relevance: 0, }); var VARIABLE = { begin: "\\*", end: "\\*", }; var KEYWORD = { className: "symbol", begin: "[:&]" + LISP_IDENT_RE, }; var IDENT = { begin: LISP_IDENT_RE, relevance: 0, }; var MEC = { begin: MEC_RE, }; var QUOTED_LIST = { begin: "\\(", end: "\\)", contains: ["self", LITERAL, STRING, NUMBER, IDENT], }; var QUOTED = { contains: [ NUMBER, STRING, VARIABLE, KEYWORD, QUOTED_LIST, IDENT, ], variants: [ { begin: "['`]\\(", end: "\\)", }, { begin: "\\(quote ", end: "\\)", keywords: { name: "quote" }, }, { begin: "'" + MEC_RE, }, ], }; var QUOTED_ATOM = { variants: [ { begin: "'" + LISP_IDENT_RE }, { begin: "#'" + LISP_IDENT_RE + "(::" + LISP_IDENT_RE + ")*", }, ], }; var LIST = { begin: "\\(\\s*", end: "\\)", }; var BODY = { endsWithParent: true, relevance: 0, }; LIST.contains = [ { className: "name", variants: [{ begin: LISP_IDENT_RE }, { begin: MEC_RE }], }, BODY, ]; BODY.contains = [ QUOTED, QUOTED_ATOM, LIST, LITERAL, NUMBER, STRING, COMMENT, VARIABLE, KEYWORD, MEC, IDENT, ]; return { illegal: /\S/, contains: [ NUMBER, SHEBANG, LITERAL, STRING, COMMENT, QUOTED, QUOTED_ATOM, LIST, IDENT, ], }; }, }, { name: "livecodeserver", /* Language: LiveCode Author: Ralf Bitter Description: Language definition for LiveCode server accounting for revIgniter (a web application framework) characteristics. Version: 1.1 Date: 2019-04-17 Category: enterprise */ create: function (hljs) { var VARIABLE = { className: "variable", variants: [ { begin: "\\b([gtps][A-Z]{1}[a-zA-Z0-9]*)(\\[.+\\])?(?:\\s*?)", }, { begin: "\\$_[A-Z]+" }, ], relevance: 0, }; var COMMENT_MODES = [ hljs.C_BLOCK_COMMENT_MODE, hljs.HASH_COMMENT_MODE, hljs.COMMENT("--", "$"), hljs.COMMENT("[^:]//", "$"), ]; var TITLE1 = hljs.inherit(hljs.TITLE_MODE, { variants: [ { begin: "\\b_*rig[A-Z]+[A-Za-z0-9_\\-]*" }, { begin: "\\b_[a-z0-9\\-]+" }, ], }); var TITLE2 = hljs.inherit(hljs.TITLE_MODE, { begin: "\\b([A-Za-z0-9_\\-]+)\\b", }); return { case_insensitive: false, keywords: { keyword: "$_COOKIE $_FILES $_GET $_GET_BINARY $_GET_RAW $_POST $_POST_BINARY $_POST_RAW $_SESSION $_SERVER " + "codepoint codepoints segment segments codeunit codeunits sentence sentences trueWord trueWords paragraph " + "after byte bytes english the until http forever descending using line real8 with seventh " + "for stdout finally element word words fourth before black ninth sixth characters chars stderr " + "uInt1 uInt1s uInt2 uInt2s stdin string lines relative rel any fifth items from middle mid " + "at else of catch then third it file milliseconds seconds second secs sec int1 int1s int4 " + "int4s internet int2 int2s normal text item last long detailed effective uInt4 uInt4s repeat " + "end repeat URL in try into switch to words https token binfile each tenth as ticks tick " + "system real4 by dateItems without char character ascending eighth whole dateTime numeric short " + "first ftp integer abbreviated abbr abbrev private case while if " + "div mod wrap and or bitAnd bitNot bitOr bitXor among not in a an within " + "contains ends with begins the keys of keys", literal: "SIX TEN FORMFEED NINE ZERO NONE SPACE FOUR FALSE COLON CRLF PI COMMA ENDOFFILE EOF EIGHT FIVE " + "QUOTE EMPTY ONE TRUE RETURN CR LINEFEED RIGHT BACKSLASH NULL SEVEN TAB THREE TWO " + "six ten formfeed nine zero none space four false colon crlf pi comma endoffile eof eight five " + "quote empty one true return cr linefeed right backslash null seven tab three two " + "RIVERSION RISTATE FILE_READ_MODE FILE_WRITE_MODE FILE_WRITE_MODE DIR_WRITE_MODE FILE_READ_UMASK " + "FILE_WRITE_UMASK DIR_READ_UMASK DIR_WRITE_UMASK", built_in: "put abs acos aliasReference annuity arrayDecode arrayEncode asin atan atan2 average avg avgDev base64Decode " + "base64Encode baseConvert binaryDecode binaryEncode byteOffset byteToNum cachedURL cachedURLs charToNum " + "cipherNames codepointOffset codepointProperty codepointToNum codeunitOffset commandNames compound compress " + "constantNames cos date dateFormat decompress difference directories " + "diskSpace DNSServers exp exp1 exp2 exp10 extents files flushEvents folders format functionNames geometricMean global " + "globals hasMemory harmonicMean hostAddress hostAddressToName hostName hostNameToAddress isNumber ISOToMac itemOffset " + "keys len length libURLErrorData libUrlFormData libURLftpCommand libURLLastHTTPHeaders libURLLastRHHeaders " + "libUrlMultipartFormAddPart libUrlMultipartFormData libURLVersion lineOffset ln ln1 localNames log log2 log10 " + "longFilePath lower macToISO matchChunk matchText matrixMultiply max md5Digest median merge messageAuthenticationCode messageDigest millisec " + "millisecs millisecond milliseconds min monthNames nativeCharToNum normalizeText num number numToByte numToChar " + "numToCodepoint numToNativeChar offset open openfiles openProcesses openProcessIDs openSockets " + "paragraphOffset paramCount param params peerAddress pendingMessages platform popStdDev populationStandardDeviation " + "populationVariance popVariance processID random randomBytes replaceText result revCreateXMLTree revCreateXMLTreeFromFile " + "revCurrentRecord revCurrentRecordIsFirst revCurrentRecordIsLast revDatabaseColumnCount revDatabaseColumnIsNull " + "revDatabaseColumnLengths revDatabaseColumnNames revDatabaseColumnNamed revDatabaseColumnNumbered " + "revDatabaseColumnTypes revDatabaseConnectResult revDatabaseCursors revDatabaseID revDatabaseTableNames " + "revDatabaseType revDataFromQuery revdb_closeCursor revdb_columnbynumber revdb_columncount revdb_columnisnull " + "revdb_columnlengths revdb_columnnames revdb_columntypes revdb_commit revdb_connect revdb_connections " + "revdb_connectionerr revdb_currentrecord revdb_cursorconnection revdb_cursorerr revdb_cursors revdb_dbtype " + "revdb_disconnect revdb_execute revdb_iseof revdb_isbof revdb_movefirst revdb_movelast revdb_movenext " + "revdb_moveprev revdb_query revdb_querylist revdb_recordcount revdb_rollback revdb_tablenames " + "revGetDatabaseDriverPath revNumberOfRecords revOpenDatabase revOpenDatabases revQueryDatabase " + "revQueryDatabaseBlob revQueryResult revQueryIsAtStart revQueryIsAtEnd revUnixFromMacPath revXMLAttribute " + "revXMLAttributes revXMLAttributeValues revXMLChildContents revXMLChildNames revXMLCreateTreeFromFileWithNamespaces " + "revXMLCreateTreeWithNamespaces revXMLDataFromXPathQuery revXMLEvaluateXPath revXMLFirstChild revXMLMatchingNode " + "revXMLNextSibling revXMLNodeContents revXMLNumberOfChildren revXMLParent revXMLPreviousSibling " + "revXMLRootNode revXMLRPC_CreateRequest revXMLRPC_Documents revXMLRPC_Error " + "revXMLRPC_GetHost revXMLRPC_GetMethod revXMLRPC_GetParam revXMLText revXMLRPC_Execute " + "revXMLRPC_GetParamCount revXMLRPC_GetParamNode revXMLRPC_GetParamType revXMLRPC_GetPath revXMLRPC_GetPort " + "revXMLRPC_GetProtocol revXMLRPC_GetRequest revXMLRPC_GetResponse revXMLRPC_GetSocket revXMLTree " + "revXMLTrees revXMLValidateDTD revZipDescribeItem revZipEnumerateItems revZipOpenArchives round sampVariance " + "sec secs seconds sentenceOffset sha1Digest shell shortFilePath sin specialFolderPath sqrt standardDeviation statRound " + "stdDev sum sysError systemVersion tan tempName textDecode textEncode tick ticks time to tokenOffset toLower toUpper " + "transpose truewordOffset trunc uniDecode uniEncode upper URLDecode URLEncode URLStatus uuid value variableNames " + "variance version waitDepth weekdayNames wordOffset xsltApplyStylesheet xsltApplyStylesheetFromFile xsltLoadStylesheet " + "xsltLoadStylesheetFromFile add breakpoint cancel clear local variable file word line folder directory URL close socket process " + "combine constant convert create new alias folder directory decrypt delete variable word line folder " + "directory URL dispatch divide do encrypt filter get include intersect kill libURLDownloadToFile " + "libURLFollowHttpRedirects libURLftpUpload libURLftpUploadFile libURLresetAll libUrlSetAuthCallback libURLSetDriver " + "libURLSetCustomHTTPHeaders libUrlSetExpect100 libURLSetFTPListCommand libURLSetFTPMode libURLSetFTPStopTime " + "libURLSetStatusCallback load extension loadedExtensions multiply socket prepare process post seek rel relative read from process rename " + "replace require resetAll resolve revAddXMLNode revAppendXML revCloseCursor revCloseDatabase revCommitDatabase " + "revCopyFile revCopyFolder revCopyXMLNode revDeleteFolder revDeleteXMLNode revDeleteAllXMLTrees " + "revDeleteXMLTree revExecuteSQL revGoURL revInsertXMLNode revMoveFolder revMoveToFirstRecord revMoveToLastRecord " + "revMoveToNextRecord revMoveToPreviousRecord revMoveToRecord revMoveXMLNode revPutIntoXMLNode revRollBackDatabase " + "revSetDatabaseDriverPath revSetXMLAttribute revXMLRPC_AddParam revXMLRPC_DeleteAllDocuments revXMLAddDTD " + "revXMLRPC_Free revXMLRPC_FreeAll revXMLRPC_DeleteDocument revXMLRPC_DeleteParam revXMLRPC_SetHost " + "revXMLRPC_SetMethod revXMLRPC_SetPort revXMLRPC_SetProtocol revXMLRPC_SetSocket revZipAddItemWithData " + "revZipAddItemWithFile revZipAddUncompressedItemWithData revZipAddUncompressedItemWithFile revZipCancel " + "revZipCloseArchive revZipDeleteItem revZipExtractItemToFile revZipExtractItemToVariable revZipSetProgressCallback " + "revZipRenameItem revZipReplaceItemWithData revZipReplaceItemWithFile revZipOpenArchive send set sort split start stop " + "subtract symmetric union unload vectorDotProduct wait write", }, contains: [ VARIABLE, { className: "keyword", begin: "\\bend\\sif\\b", }, { className: "function", beginKeywords: "function", end: "$", contains: [ VARIABLE, TITLE2, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.BINARY_NUMBER_MODE, hljs.C_NUMBER_MODE, TITLE1, ], }, { className: "function", begin: "\\bend\\s+", end: "$", keywords: "end", contains: [TITLE2, TITLE1], relevance: 0, }, { beginKeywords: "command on", end: "$", contains: [ VARIABLE, TITLE2, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.BINARY_NUMBER_MODE, hljs.C_NUMBER_MODE, TITLE1, ], }, { className: "meta", variants: [ { begin: "<\\?(rev|lc|livecode)", relevance: 10, }, { begin: "<\\?" }, { begin: "\\?>" }, ], }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.BINARY_NUMBER_MODE, hljs.C_NUMBER_MODE, TITLE1, ].concat(COMMENT_MODES), illegal: ";$|^\\[|^=|&|{", }; }, }, { name: "livescript", /* Language: LiveScript Author: Taneli Vatanen Contributors: Jen Evers-Corvina Origin: coffeescript.js Description: LiveScript is a programming language that transcompiles to JavaScript. For info about language see http://livescript.net/ Category: scripting */ create: function (hljs) { var KEYWORDS = { keyword: // JS keywords "in if for while finally new do return else break catch instanceof throw try this " + "switch continue typeof delete debugger case default function var with " + // LiveScript keywords "then unless until loop of by when and or is isnt not it that otherwise from to til fallthrough super " + "case default function var void const let enum export import native " + "__hasProp __extends __slice __bind __indexOf", literal: // JS literals "true false null undefined " + // LiveScript literals "yes no on off it that void", built_in: "npm require console print module global window document", }; var JS_IDENT_RE = "[A-Za-z$_](?:\-[0-9A-Za-z$_]|[0-9A-Za-z$_])*"; var TITLE = hljs.inherit(hljs.TITLE_MODE, { begin: JS_IDENT_RE, }); var SUBST = { className: "subst", begin: /#\{/, end: /}/, keywords: KEYWORDS, }; var SUBST_SIMPLE = { className: "subst", begin: /#[A-Za-z$_]/, end: /(?:\-[0-9A-Za-z$_]|[0-9A-Za-z$_])*/, keywords: KEYWORDS, }; var EXPRESSIONS = [ hljs.BINARY_NUMBER_MODE, { className: "number", begin: "(\\b0[xX][a-fA-F0-9_]+)|(\\b\\d(\\d|_\\d)*(\\.(\\d(\\d|_\\d)*)?)?(_*[eE]([-+]\\d(_\\d|\\d)*)?)?[_a-z]*)", relevance: 0, starts: { end: "(\\s*/)?", relevance: 0 }, // a number tries to eat the following slash to prevent treating it as a regexp }, { className: "string", variants: [ { begin: /'''/, end: /'''/, contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /'/, end: /'/, contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /"""/, end: /"""/, contains: [hljs.BACKSLASH_ESCAPE, SUBST, SUBST_SIMPLE], }, { begin: /"/, end: /"/, contains: [hljs.BACKSLASH_ESCAPE, SUBST, SUBST_SIMPLE], }, { begin: /\\/, end: /(\s|$)/, excludeEnd: true, }, ], }, { className: "regexp", variants: [ { begin: "//", end: "//[gim]*", contains: [SUBST, hljs.HASH_COMMENT_MODE], }, { // regex can't start with space to parse x / 2 / 3 as two divisions // regex can't start with *, and it supports an "illegal" in the main mode begin: /\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/, }, ], }, { begin: "@" + JS_IDENT_RE, }, { begin: "``", end: "``", excludeBegin: true, excludeEnd: true, subLanguage: "javascript", }, ]; SUBST.contains = EXPRESSIONS; var PARAMS = { className: "params", begin: "\\(", returnBegin: true, /* We need another contained nameless mode to not have every nested pair of parens to be called "params" */ contains: [ { begin: /\(/, end: /\)/, keywords: KEYWORDS, contains: ["self"].concat(EXPRESSIONS), }, ], }; return { aliases: ["ls"], keywords: KEYWORDS, illegal: /\/\*/, contains: EXPRESSIONS.concat([ hljs.COMMENT("\\/\\*", "\\*\\/"), hljs.HASH_COMMENT_MODE, { className: "function", contains: [TITLE, PARAMS], returnBegin: true, variants: [ { begin: "(" + JS_IDENT_RE + "\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B\\->\\*?", end: "\\->\\*?", }, { begin: "(" + JS_IDENT_RE + "\\s*(?:=|:=)\\s*)?!?(\\(.*\\))?\\s*\\B[-~]{1,2}>\\*?", end: "[-~]{1,2}>\\*?", }, { begin: "(" + JS_IDENT_RE + "\\s*(?:=|:=)\\s*)?(\\(.*\\))?\\s*\\B!?[-~]{1,2}>\\*?", end: "!?[-~]{1,2}>\\*?", }, ], }, { className: "class", beginKeywords: "class", end: "$", illegal: /[:="\[\]]/, contains: [ { beginKeywords: "extends", endsWithParent: true, illegal: /[:="\[\]]/, contains: [TITLE], }, TITLE, ], }, { begin: JS_IDENT_RE + ":", end: ":", returnBegin: true, returnEnd: true, relevance: 0, }, ]), }; }, }, { name: "llvm", /* Language: LLVM IR Author: Michael Rodler Description: language used as intermediate representation in the LLVM compiler framework Category: assembler */ create: function (hljs) { var identifier = "([-a-zA-Z$._][\\w\\-$.]*)"; return { //lexemes: '[.%]?' + hljs.IDENT_RE, keywords: "begin end true false declare define global " + "constant private linker_private internal " + "available_externally linkonce linkonce_odr weak " + "weak_odr appending dllimport dllexport common " + "default hidden protected extern_weak external " + "thread_local zeroinitializer undef null to tail " + "target triple datalayout volatile nuw nsw nnan " + "ninf nsz arcp fast exact inbounds align " + "addrspace section alias module asm sideeffect " + "gc dbg linker_private_weak attributes blockaddress " + "initialexec localdynamic localexec prefix unnamed_addr " + "ccc fastcc coldcc x86_stdcallcc x86_fastcallcc " + "arm_apcscc arm_aapcscc arm_aapcs_vfpcc ptx_device " + "ptx_kernel intel_ocl_bicc msp430_intrcc spir_func " + "spir_kernel x86_64_sysvcc x86_64_win64cc x86_thiscallcc " + "cc c signext zeroext inreg sret nounwind " + "noreturn noalias nocapture byval nest readnone " + "readonly inlinehint noinline alwaysinline optsize ssp " + "sspreq noredzone noimplicitfloat naked builtin cold " + "nobuiltin noduplicate nonlazybind optnone returns_twice " + "sanitize_address sanitize_memory sanitize_thread sspstrong " + "uwtable returned type opaque eq ne slt sgt " + "sle sge ult ugt ule uge oeq one olt ogt " + "ole oge ord uno ueq une x acq_rel acquire " + "alignstack atomic catch cleanup filter inteldialect " + "max min monotonic nand personality release seq_cst " + "singlethread umax umin unordered xchg add fadd " + "sub fsub mul fmul udiv sdiv fdiv urem srem " + "frem shl lshr ashr and or xor icmp fcmp " + "phi call trunc zext sext fptrunc fpext uitofp " + "sitofp fptoui fptosi inttoptr ptrtoint bitcast " + "addrspacecast select va_arg ret br switch invoke " + "unwind unreachable indirectbr landingpad resume " + "malloc alloca free load store getelementptr " + "extractelement insertelement shufflevector getresult " + "extractvalue insertvalue atomicrmw cmpxchg fence " + "argmemonly double", contains: [ { className: "keyword", begin: "i\\d+", }, hljs.COMMENT(";", "\\n", { relevance: 0 }), // Double quote string hljs.QUOTE_STRING_MODE, { className: "string", variants: [ // Double-quoted string { begin: '"', end: '[^\\\\]"' }, ], relevance: 0, }, { className: "title", variants: [ { begin: "@" + identifier }, { begin: "@\\d+" }, { begin: "!" + identifier }, { begin: "!\\d+" + identifier }, ], }, { className: "symbol", variants: [ { begin: "%" + identifier }, { begin: "%\\d+" }, { begin: "#\\d+" }, ], }, { className: "number", variants: [ { begin: "0[xX][a-fA-F0-9]+" }, { begin: "-?\\d+(?:[.]\\d+)?(?:[eE][-+]?\\d+(?:[.]\\d+)?)?", }, ], relevance: 0, }, ], }; }, }, { name: "lsl", /* Language: Linden Scripting Language Description: The Linden Scripting Language is used in Second Life by Linden Labs. Author: Builder's Brewery Category: scripting */ create: function (hljs) { var LSL_STRING_ESCAPE_CHARS = { className: "subst", begin: /\\[tn"\\]/, }; var LSL_STRINGS = { className: "string", begin: '"', end: '"', contains: [LSL_STRING_ESCAPE_CHARS], }; var LSL_NUMBERS = { className: "number", begin: hljs.C_NUMBER_RE, }; var LSL_CONSTANTS = { className: "literal", variants: [ { begin: "\\b(?:PI|TWO_PI|PI_BY_TWO|DEG_TO_RAD|RAD_TO_DEG|SQRT2)\\b", }, { begin: "\\b(?:XP_ERROR_(?:EXPERIENCES_DISABLED|EXPERIENCE_(?:DISABLED|SUSPENDED)|INVALID_(?:EXPERIENCE|PARAMETERS)|KEY_NOT_FOUND|MATURITY_EXCEEDED|NONE|NOT_(?:FOUND|PERMITTED(?:_LAND)?)|NO_EXPERIENCE|QUOTA_EXCEEDED|RETRY_UPDATE|STORAGE_EXCEPTION|STORE_DISABLED|THROTTLED|UNKNOWN_ERROR)|JSON_APPEND|STATUS_(?:PHYSICS|ROTATE_[XYZ]|PHANTOM|SANDBOX|BLOCK_GRAB(?:_OBJECT)?|(?:DIE|RETURN)_AT_EDGE|CAST_SHADOWS|OK|MALFORMED_PARAMS|TYPE_MISMATCH|BOUNDS_ERROR|NOT_(?:FOUND|SUPPORTED)|INTERNAL_ERROR|WHITELIST_FAILED)|AGENT(?:_(?:BY_(?:LEGACY_|USER)NAME|FLYING|ATTACHMENTS|SCRIPTED|MOUSELOOK|SITTING|ON_OBJECT|AWAY|WALKING|IN_AIR|TYPING|CROUCHING|BUSY|ALWAYS_RUN|AUTOPILOT|LIST_(?:PARCEL(?:_OWNER)?|REGION)))?|CAMERA_(?:PITCH|DISTANCE|BEHINDNESS_(?:ANGLE|LAG)|(?:FOCUS|POSITION)(?:_(?:THRESHOLD|LOCKED|LAG))?|FOCUS_OFFSET|ACTIVE)|ANIM_ON|LOOP|REVERSE|PING_PONG|SMOOTH|ROTATE|SCALE|ALL_SIDES|LINK_(?:ROOT|SET|ALL_(?:OTHERS|CHILDREN)|THIS)|ACTIVE|PASS(?:IVE|_(?:ALWAYS|IF_NOT_HANDLED|NEVER))|SCRIPTED|CONTROL_(?:FWD|BACK|(?:ROT_)?(?:LEFT|RIGHT)|UP|DOWN|(?:ML_)?LBUTTON)|PERMISSION_(?:RETURN_OBJECTS|DEBIT|OVERRIDE_ANIMATIONS|SILENT_ESTATE_MANAGEMENT|TAKE_CONTROLS|TRIGGER_ANIMATION|ATTACH|CHANGE_LINKS|(?:CONTROL|TRACK)_CAMERA|TELEPORT)|INVENTORY_(?:TEXTURE|SOUND|OBJECT|SCRIPT|LANDMARK|CLOTHING|NOTECARD|BODYPART|ANIMATION|GESTURE|ALL|NONE)|CHANGED_(?:INVENTORY|COLOR|SHAPE|SCALE|TEXTURE|LINK|ALLOWED_DROP|OWNER|REGION(?:_START)?|TELEPORT|MEDIA)|OBJECT_(?:CLICK_ACTION|HOVER_HEIGHT|LAST_OWNER_ID|(?:PHYSICS|SERVER|STREAMING)_COST|UNKNOWN_DETAIL|CHARACTER_TIME|PHANTOM|PHYSICS|TEMP_ON_REZ|NAME|DESC|POS|PRIM_(?:COUNT|EQUIVALENCE)|RETURN_(?:PARCEL(?:_OWNER)?|REGION)|REZZER_KEY|ROO?T|VELOCITY|OMEGA|OWNER|GROUP|CREATOR|ATTACHED_POINT|RENDER_WEIGHT|(?:BODY_SHAPE|PATHFINDING)_TYPE|(?:RUNNING|TOTAL)_SCRIPT_COUNT|TOTAL_INVENTORY_COUNT|SCRIPT_(?:MEMORY|TIME))|TYPE_(?:INTEGER|FLOAT|STRING|KEY|VECTOR|ROTATION|INVALID)|(?:DEBUG|PUBLIC)_CHANNEL|ATTACH_(?:AVATAR_CENTER|CHEST|HEAD|BACK|PELVIS|MOUTH|CHIN|NECK|NOSE|BELLY|[LR](?:SHOULDER|HAND|FOOT|EAR|EYE|[UL](?:ARM|LEG)|HIP)|(?:LEFT|RIGHT)_PEC|HUD_(?:CENTER_[12]|TOP_(?:RIGHT|CENTER|LEFT)|BOTTOM(?:_(?:RIGHT|LEFT))?)|[LR]HAND_RING1|TAIL_(?:BASE|TIP)|[LR]WING|FACE_(?:JAW|[LR]EAR|[LR]EYE|TOUNGE)|GROIN|HIND_[LR]FOOT)|LAND_(?:LEVEL|RAISE|LOWER|SMOOTH|NOISE|REVERT)|DATA_(?:ONLINE|NAME|BORN|SIM_(?:POS|STATUS|RATING)|PAYINFO)|PAYMENT_INFO_(?:ON_FILE|USED)|REMOTE_DATA_(?:CHANNEL|REQUEST|REPLY)|PSYS_(?:PART_(?:BF_(?:ZERO|ONE(?:_MINUS_(?:DEST_COLOR|SOURCE_(ALPHA|COLOR)))?|DEST_COLOR|SOURCE_(ALPHA|COLOR))|BLEND_FUNC_(DEST|SOURCE)|FLAGS|(?:START|END)_(?:COLOR|ALPHA|SCALE|GLOW)|MAX_AGE|(?:RIBBON|WIND|INTERP_(?:COLOR|SCALE)|BOUNCE|FOLLOW_(?:SRC|VELOCITY)|TARGET_(?:POS|LINEAR)|EMISSIVE)_MASK)|SRC_(?:MAX_AGE|PATTERN|ANGLE_(?:BEGIN|END)|BURST_(?:RATE|PART_COUNT|RADIUS|SPEED_(?:MIN|MAX))|ACCEL|TEXTURE|TARGET_KEY|OMEGA|PATTERN_(?:DROP|EXPLODE|ANGLE(?:_CONE(?:_EMPTY)?)?)))|VEHICLE_(?:REFERENCE_FRAME|TYPE_(?:NONE|SLED|CAR|BOAT|AIRPLANE|BALLOON)|(?:LINEAR|ANGULAR)_(?:FRICTION_TIMESCALE|MOTOR_DIRECTION)|LINEAR_MOTOR_OFFSET|HOVER_(?:HEIGHT|EFFICIENCY|TIMESCALE)|BUOYANCY|(?:LINEAR|ANGULAR)_(?:DEFLECTION_(?:EFFICIENCY|TIMESCALE)|MOTOR_(?:DECAY_)?TIMESCALE)|VERTICAL_ATTRACTION_(?:EFFICIENCY|TIMESCALE)|BANKING_(?:EFFICIENCY|MIX|TIMESCALE)|FLAG_(?:NO_DEFLECTION_UP|LIMIT_(?:ROLL_ONLY|MOTOR_UP)|HOVER_(?:(?:WATER|TERRAIN|UP)_ONLY|GLOBAL_HEIGHT)|MOUSELOOK_(?:STEER|BANK)|CAMERA_DECOUPLED))|PRIM_(?:ALPHA_MODE(?:_(?:BLEND|EMISSIVE|MASK|NONE))?|NORMAL|SPECULAR|TYPE(?:_(?:BOX|CYLINDER|PRISM|SPHERE|TORUS|TUBE|RING|SCULPT))?|HOLE_(?:DEFAULT|CIRCLE|SQUARE|TRIANGLE)|MATERIAL(?:_(?:STONE|METAL|GLASS|WOOD|FLESH|PLASTIC|RUBBER))?|SHINY_(?:NONE|LOW|MEDIUM|HIGH)|BUMP_(?:NONE|BRIGHT|DARK|WOOD|BARK|BRICKS|CHECKER|CONCRETE|TILE|STONE|DISKS|GRAVEL|BLOBS|SIDING|LARGETILE|STUCCO|SUCTION|WEAVE)|TEXGEN_(?:DEFAULT|PLANAR)|SCULPT_(?:TYPE_(?:SPHERE|TORUS|PLANE|CYLINDER|MASK)|FLAG_(?:MIRROR|INVERT))|PHYSICS(?:_(?:SHAPE_(?:CONVEX|NONE|PRIM|TYPE)))?|(?:POS|ROT)_LOCAL|SLICE|TEXT|FLEXIBLE|POINT_LIGHT|TEMP_ON_REZ|PHANTOM|POSITION|SIZE|ROTATION|TEXTURE|NAME|OMEGA|DESC|LINK_TARGET|COLOR|BUMP_SHINY|FULLBRIGHT|TEXGEN|GLOW|MEDIA_(?:ALT_IMAGE_ENABLE|CONTROLS|(?:CURRENT|HOME)_URL|AUTO_(?:LOOP|PLAY|SCALE|ZOOM)|FIRST_CLICK_INTERACT|(?:WIDTH|HEIGHT)_PIXELS|WHITELIST(?:_ENABLE)?|PERMS_(?:INTERACT|CONTROL)|PARAM_MAX|CONTROLS_(?:STANDARD|MINI)|PERM_(?:NONE|OWNER|GROUP|ANYONE)|MAX_(?:URL_LENGTH|WHITELIST_(?:SIZE|COUNT)|(?:WIDTH|HEIGHT)_PIXELS)))|MASK_(?:BASE|OWNER|GROUP|EVERYONE|NEXT)|PERM_(?:TRANSFER|MODIFY|COPY|MOVE|ALL)|PARCEL_(?:MEDIA_COMMAND_(?:STOP|PAUSE|PLAY|LOOP|TEXTURE|URL|TIME|AGENT|UNLOAD|AUTO_ALIGN|TYPE|SIZE|DESC|LOOP_SET)|FLAG_(?:ALLOW_(?:FLY|(?:GROUP_)?SCRIPTS|LANDMARK|TERRAFORM|DAMAGE|CREATE_(?:GROUP_)?OBJECTS)|USE_(?:ACCESS_(?:GROUP|LIST)|BAN_LIST|LAND_PASS_LIST)|LOCAL_SOUND_ONLY|RESTRICT_PUSHOBJECT|ALLOW_(?:GROUP|ALL)_OBJECT_ENTRY)|COUNT_(?:TOTAL|OWNER|GROUP|OTHER|SELECTED|TEMP)|DETAILS_(?:NAME|DESC|OWNER|GROUP|AREA|ID|SEE_AVATARS))|LIST_STAT_(?:MAX|MIN|MEAN|MEDIAN|STD_DEV|SUM(?:_SQUARES)?|NUM_COUNT|GEOMETRIC_MEAN|RANGE)|PAY_(?:HIDE|DEFAULT)|REGION_FLAG_(?:ALLOW_DAMAGE|FIXED_SUN|BLOCK_TERRAFORM|SANDBOX|DISABLE_(?:COLLISIONS|PHYSICS)|BLOCK_FLY|ALLOW_DIRECT_TELEPORT|RESTRICT_PUSHOBJECT)|HTTP_(?:METHOD|MIMETYPE|BODY_(?:MAXLENGTH|TRUNCATED)|CUSTOM_HEADER|PRAGMA_NO_CACHE|VERBOSE_THROTTLE|VERIFY_CERT)|STRING_(?:TRIM(?:_(?:HEAD|TAIL))?)|CLICK_ACTION_(?:NONE|TOUCH|SIT|BUY|PAY|OPEN(?:_MEDIA)?|PLAY|ZOOM)|TOUCH_INVALID_FACE|PROFILE_(?:NONE|SCRIPT_MEMORY)|RC_(?:DATA_FLAGS|DETECT_PHANTOM|GET_(?:LINK_NUM|NORMAL|ROOT_KEY)|MAX_HITS|REJECT_(?:TYPES|AGENTS|(?:NON)?PHYSICAL|LAND))|RCERR_(?:CAST_TIME_EXCEEDED|SIM_PERF_LOW|UNKNOWN)|ESTATE_ACCESS_(?:ALLOWED_(?:AGENT|GROUP)_(?:ADD|REMOVE)|BANNED_AGENT_(?:ADD|REMOVE))|DENSITY|FRICTION|RESTITUTION|GRAVITY_MULTIPLIER|KFM_(?:COMMAND|CMD_(?:PLAY|STOP|PAUSE)|MODE|FORWARD|LOOP|PING_PONG|REVERSE|DATA|ROTATION|TRANSLATION)|ERR_(?:GENERIC|PARCEL_PERMISSIONS|MALFORMED_PARAMS|RUNTIME_PERMISSIONS|THROTTLED)|CHARACTER_(?:CMD_(?:(?:SMOOTH_)?STOP|JUMP)|DESIRED_(?:TURN_)?SPEED|RADIUS|STAY_WITHIN_PARCEL|LENGTH|ORIENTATION|ACCOUNT_FOR_SKIPPED_FRAMES|AVOIDANCE_MODE|TYPE(?:_(?:[ABCD]|NONE))?|MAX_(?:DECEL|TURN_RADIUS|(?:ACCEL|SPEED)))|PURSUIT_(?:OFFSET|FUZZ_FACTOR|GOAL_TOLERANCE|INTERCEPT)|REQUIRE_LINE_OF_SIGHT|FORCE_DIRECT_PATH|VERTICAL|HORIZONTAL|AVOID_(?:CHARACTERS|DYNAMIC_OBSTACLES|NONE)|PU_(?:EVADE_(?:HIDDEN|SPOTTED)|FAILURE_(?:DYNAMIC_PATHFINDING_DISABLED|INVALID_(?:GOAL|START)|NO_(?:NAVMESH|VALID_DESTINATION)|OTHER|TARGET_GONE|(?:PARCEL_)?UNREACHABLE)|(?:GOAL|SLOWDOWN_DISTANCE)_REACHED)|TRAVERSAL_TYPE(?:_(?:FAST|NONE|SLOW))?|CONTENT_TYPE_(?:ATOM|FORM|HTML|JSON|LLSD|RSS|TEXT|XHTML|XML)|GCNP_(?:RADIUS|STATIC)|(?:PATROL|WANDER)_PAUSE_AT_WAYPOINTS|OPT_(?:AVATAR|CHARACTER|EXCLUSION_VOLUME|LEGACY_LINKSET|MATERIAL_VOLUME|OTHER|STATIC_OBSTACLE|WALKABLE)|SIM_STAT_PCT_CHARS_STEPPED)\\b", }, { begin: "\\b(?:FALSE|TRUE)\\b", }, { begin: "\\b(?:ZERO_ROTATION)\\b", }, { begin: "\\b(?:EOF|JSON_(?:ARRAY|DELETE|FALSE|INVALID|NULL|NUMBER|OBJECT|STRING|TRUE)|NULL_KEY|TEXTURE_(?:BLANK|DEFAULT|MEDIA|PLYWOOD|TRANSPARENT)|URL_REQUEST_(?:GRANTED|DENIED))\\b", }, { begin: "\\b(?:ZERO_VECTOR|TOUCH_INVALID_(?:TEXCOORD|VECTOR))\\b", }, ], }; var LSL_FUNCTIONS = { className: "built_in", begin: "\\b(?:ll(?:AgentInExperience|(?:Create|DataSize|Delete|KeyCount|Keys|Read|Update)KeyValue|GetExperience(?:Details|ErrorMessage)|ReturnObjectsBy(?:ID|Owner)|Json(?:2List|[GS]etValue|ValueType)|Sin|Cos|Tan|Atan2|Sqrt|Pow|Abs|Fabs|Frand|Floor|Ceil|Round|Vec(?:Mag|Norm|Dist)|Rot(?:Between|2(?:Euler|Fwd|Left|Up))|(?:Euler|Axes)2Rot|Whisper|(?:Region|Owner)?Say|Shout|Listen(?:Control|Remove)?|Sensor(?:Repeat|Remove)?|Detected(?:Name|Key|Owner|Type|Pos|Vel|Grab|Rot|Group|LinkNumber)|Die|Ground|Wind|(?:[GS]et)(?:AnimationOverride|MemoryLimit|PrimMediaParams|ParcelMusicURL|Object(?:Desc|Name)|PhysicsMaterial|Status|Scale|Color|Alpha|Texture|Pos|Rot|Force|Torque)|ResetAnimationOverride|(?:Scale|Offset|Rotate)Texture|(?:Rot)?Target(?:Remove)?|(?:Stop)?MoveToTarget|Apply(?:Rotational)?Impulse|Set(?:KeyframedMotion|ContentType|RegionPos|(?:Angular)?Velocity|Buoyancy|HoverHeight|ForceAndTorque|TimerEvent|ScriptState|Damage|TextureAnim|Sound(?:Queueing|Radius)|Vehicle(?:Type|(?:Float|Vector|Rotation)Param)|(?:Touch|Sit)?Text|Camera(?:Eye|At)Offset|PrimitiveParams|ClickAction|Link(?:Alpha|Color|PrimitiveParams(?:Fast)?|Texture(?:Anim)?|Camera|Media)|RemoteScriptAccessPin|PayPrice|LocalRot)|ScaleByFactor|Get(?:(?:Max|Min)ScaleFactor|ClosestNavPoint|StaticPath|SimStats|Env|PrimitiveParams|Link(?:PrimitiveParams|Number(?:OfSides)?|Key|Name|Media)|HTTPHeader|FreeURLs|Object(?:Details|PermMask|PrimCount)|Parcel(?:MaxPrims|Details|Prim(?:Count|Owners))|Attached(?:List)?|(?:SPMax|Free|Used)Memory|Region(?:Name|TimeDilation|FPS|Corner|AgentCount)|Root(?:Position|Rotation)|UnixTime|(?:Parcel|Region)Flags|(?:Wall|GMT)clock|SimulatorHostname|BoundingBox|GeometricCenter|Creator|NumberOf(?:Prims|NotecardLines|Sides)|Animation(?:List)?|(?:Camera|Local)(?:Pos|Rot)|Vel|Accel|Omega|Time(?:stamp|OfDay)|(?:Object|CenterOf)?Mass|MassMKS|Energy|Owner|(?:Owner)?Key|SunDirection|Texture(?:Offset|Scale|Rot)|Inventory(?:Number|Name|Key|Type|Creator|PermMask)|Permissions(?:Key)?|StartParameter|List(?:Length|EntryType)|Date|Agent(?:Size|Info|Language|List)|LandOwnerAt|NotecardLine|Script(?:Name|State))|(?:Get|Reset|GetAndReset)Time|PlaySound(?:Slave)?|LoopSound(?:Master|Slave)?|(?:Trigger|Stop|Preload)Sound|(?:(?:Get|Delete)Sub|Insert)String|To(?:Upper|Lower)|Give(?:InventoryList|Money)|RezObject|(?:Stop)?LookAt|Sleep|CollisionFilter|(?:Take|Release)Controls|DetachFromAvatar|AttachToAvatar(?:Temp)?|InstantMessage|(?:GetNext)?Email|StopHover|MinEventDelay|RotLookAt|String(?:Length|Trim)|(?:Start|Stop)Animation|TargetOmega|Request(?:Experience)?Permissions|(?:Create|Break)Link|BreakAllLinks|(?:Give|Remove)Inventory|Water|PassTouches|Request(?:Agent|Inventory)Data|TeleportAgent(?:Home|GlobalCoords)?|ModifyLand|CollisionSound|ResetScript|MessageLinked|PushObject|PassCollisions|AxisAngle2Rot|Rot2(?:Axis|Angle)|A(?:cos|sin)|AngleBetween|AllowInventoryDrop|SubStringIndex|List2(?:CSV|Integer|Json|Float|String|Key|Vector|Rot|List(?:Strided)?)|DeleteSubList|List(?:Statistics|Sort|Randomize|(?:Insert|Find|Replace)List)|EdgeOfWorld|AdjustSoundVolume|Key2Name|TriggerSoundLimited|EjectFromLand|(?:CSV|ParseString)2List|OverMyLand|SameGroup|UnSit|Ground(?:Slope|Normal|Contour)|GroundRepel|(?:Set|Remove)VehicleFlags|(?:AvatarOn)?(?:Link)?SitTarget|Script(?:Danger|Profiler)|Dialog|VolumeDetect|ResetOtherScript|RemoteLoadScriptPin|(?:Open|Close)RemoteDataChannel|SendRemoteData|RemoteDataReply|(?:Integer|String)ToBase64|XorBase64|Log(?:10)?|Base64To(?:String|Integer)|ParseStringKeepNulls|RezAtRoot|RequestSimulatorData|ForceMouselook|(?:Load|Release|(?:E|Une)scape)URL|ParcelMedia(?:CommandList|Query)|ModPow|MapDestination|(?:RemoveFrom|AddTo|Reset)Land(?:Pass|Ban)List|(?:Set|Clear)CameraParams|HTTP(?:Request|Response)|TextBox|DetectedTouch(?:UV|Face|Pos|(?:N|Bin)ormal|ST)|(?:MD5|SHA1|DumpList2)String|Request(?:Secure)?URL|Clear(?:Prim|Link)Media|(?:Link)?ParticleSystem|(?:Get|Request)(?:Username|DisplayName)|RegionSayTo|CastRay|GenerateKey|TransferLindenDollars|ManageEstateAccess|(?:Create|Delete)Character|ExecCharacterCmd|Evade|FleeFrom|NavigateTo|PatrolPoints|Pursue|UpdateCharacter|WanderWithin))\\b", }; return { illegal: ":", contains: [ LSL_STRINGS, { className: "comment", variants: [ hljs.COMMENT("//", "$"), hljs.COMMENT("/\\*", "\\*/"), ], }, LSL_NUMBERS, { className: "section", variants: [ { begin: "\\b(?:state|default)\\b", }, { begin: "\\b(?:state_(?:entry|exit)|touch(?:_(?:start|end))?|(?:land_)?collision(?:_(?:start|end))?|timer|listen|(?:no_)?sensor|control|(?:not_)?at_(?:rot_)?target|money|email|experience_permissions(?:_denied)?|run_time_permissions|changed|attach|dataserver|moving_(?:start|end)|link_message|(?:on|object)_rez|remote_data|http_re(?:sponse|quest)|path_update|transaction_result)\\b", }, ], }, LSL_FUNCTIONS, LSL_CONSTANTS, { className: "type", begin: "\\b(?:integer|float|string|key|vector|quaternion|rotation|list)\\b", }, ], }; }, }, { name: "lua", /* Language: Lua Author: Andrew Fedorov Category: scripting */ create: function (hljs) { var OPENING_LONG_BRACKET = "\\[=*\\["; var CLOSING_LONG_BRACKET = "\\]=*\\]"; var LONG_BRACKETS = { begin: OPENING_LONG_BRACKET, end: CLOSING_LONG_BRACKET, contains: ["self"], }; var COMMENTS = [ hljs.COMMENT("--(?!" + OPENING_LONG_BRACKET + ")", "$"), hljs.COMMENT( "--" + OPENING_LONG_BRACKET, CLOSING_LONG_BRACKET, { contains: [LONG_BRACKETS], relevance: 10, }, ), ]; return { lexemes: hljs.UNDERSCORE_IDENT_RE, keywords: { literal: "true false nil", keyword: "and break do else elseif end for goto if in local not or repeat return then until while", built_in: //Metatags and globals: "_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len " + "__gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert " + //Standard methods and properties: "collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring" + "module next pairs pcall print rawequal rawget rawset require select setfenv" + "setmetatable tonumber tostring type unpack xpcall arg self" + //Library methods and properties (one line per library): "coroutine resume yield status wrap create running debug getupvalue " + "debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv " + "io lines write close flush open output type read stderr stdin input stdout popen tmpfile " + "math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan " + "os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall " + "string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower " + "table setn insert getn foreachi maxn foreach concat sort remove", }, contains: COMMENTS.concat([ { className: "function", beginKeywords: "function", end: "\\)", contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: "([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*", }), { className: "params", begin: "\\(", endsWithParent: true, contains: COMMENTS, }, ].concat(COMMENTS), }, hljs.C_NUMBER_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { className: "string", begin: OPENING_LONG_BRACKET, end: CLOSING_LONG_BRACKET, contains: [LONG_BRACKETS], relevance: 5, }, ]), }; }, }, { name: "makefile", /* Language: Makefile Author: Ivan Sagalaev Contributors: Joël Porquet Category: common */ create: function (hljs) { /* Variables: simple (eg $(var)) and special (eg $@) */ var VARIABLE = { className: "variable", variants: [ { begin: "\\$\\(" + hljs.UNDERSCORE_IDENT_RE + "\\)", contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /\$[@% Website: http://seejohncode.com/ Category: common, markup */ create: function (hljs) { return { aliases: ["md", "mkdown", "mkd"], contains: [ // highlight headers { className: "section", variants: [ { begin: "^#{1,6}", end: "$" }, { begin: "^.+?\\n[=-]{2,}$" }, ], }, // inline html { begin: "<", end: ">", subLanguage: "xml", relevance: 0, }, // lists (indicators only) { className: "bullet", begin: "^\\s*([*+-]|(\\d+\\.))\\s+", }, // strong segments { className: "strong", begin: "[*_]{2}.+?[*_]{2}", }, // emphasis segments { className: "emphasis", variants: [ { begin: "\\*.+?\\*" }, { begin: "_.+?_", relevance: 0 }, ], }, // blockquotes { className: "quote", begin: "^>\\s+", end: "$", }, // code snippets { className: "code", variants: [ { begin: "^```\w*\s*$", end: "^```\s*$", }, { begin: "`.+?`", }, { begin: "^( {4}|\t)", end: "$", relevance: 0, }, ], }, // horizontal rules { begin: "^[-\\*]{3,}", end: "$", }, // using links - title and link { begin: "\\[.+?\\][\\(\\[].*?[\\)\\]]", returnBegin: true, contains: [ { className: "string", begin: "\\[", end: "\\]", excludeBegin: true, returnEnd: true, relevance: 0, }, { className: "link", begin: "\\]\\(", end: "\\)", excludeBegin: true, excludeEnd: true, }, { className: "symbol", begin: "\\]\\[", end: "\\]", excludeBegin: true, excludeEnd: true, }, ], relevance: 10, }, { begin: /^\[[^\n]+\]:/, returnBegin: true, contains: [ { className: "symbol", begin: /\[/, end: /\]/, excludeBegin: true, excludeEnd: true, }, { className: "link", begin: /:\s*/, end: /$/, excludeBegin: true, }, ], }, ], }; }, }, { name: "mathematica", /* Language: Mathematica Authors: Daniel Kvasnicka , Jan Poeschko Category: scientific */ create: function (hljs) { return { aliases: ["mma", "wl"], lexemes: "(\\$|\\b)" + hljs.IDENT_RE + "\\b", // // The list of "keywords" (System` symbols) was determined by evaluating the following Wolfram Language code in Mathematica 12.0: // // StringRiffle[ // "'" <> StringRiffle[#, " "] <> "'" & /@ // Values[GroupBy[ // Select[Names["System`*"], // StringStartsQ[#, CharacterRange["A", "Z"] | "$"] &], // First[Characters[#]] &]], " +\n"] // keywords: "AASTriangle AbelianGroup Abort AbortKernels AbortProtect AbortScheduledTask Above Abs AbsArg AbsArgPlot Absolute AbsoluteCorrelation AbsoluteCorrelationFunction AbsoluteCurrentValue AbsoluteDashing AbsoluteFileName AbsoluteOptions AbsolutePointSize AbsoluteThickness AbsoluteTime AbsoluteTiming AcceptanceThreshold AccountingForm Accumulate Accuracy AccuracyGoal ActionDelay ActionMenu ActionMenuBox ActionMenuBoxOptions Activate Active ActiveClassification ActiveClassificationObject ActiveItem ActivePrediction ActivePredictionObject ActiveStyle AcyclicGraphQ AddOnHelpPath AddSides AddTo AddToSearchIndex AddUsers AdjacencyGraph AdjacencyList AdjacencyMatrix AdjustmentBox AdjustmentBoxOptions AdjustTimeSeriesForecast AdministrativeDivisionData AffineHalfSpace AffineSpace AffineStateSpaceModel AffineTransform After AggregatedEntityClass AggregationLayer AircraftData AirportData AirPressureData AirTemperatureData AiryAi AiryAiPrime AiryAiZero AiryBi AiryBiPrime AiryBiZero AlgebraicIntegerQ AlgebraicNumber AlgebraicNumberDenominator AlgebraicNumberNorm AlgebraicNumberPolynomial AlgebraicNumberTrace AlgebraicRules AlgebraicRulesData Algebraics AlgebraicUnitQ Alignment AlignmentMarker AlignmentPoint All AllowAdultContent AllowedCloudExtraParameters AllowedCloudParameterExtensions AllowedDimensions AllowedFrequencyRange AllowedHeads AllowGroupClose AllowIncomplete AllowInlineCells AllowKernelInitialization AllowLooseGrammar AllowReverseGroupClose AllowScriptLevelChange AllTrue Alphabet AlphabeticOrder AlphabeticSort AlphaChannel AlternateImage AlternatingFactorial AlternatingGroup AlternativeHypothesis Alternatives AltitudeMethod AmbientLight AmbiguityFunction AmbiguityList Analytic AnatomyData AnatomyForm AnatomyPlot3D AnatomySkinStyle AnatomyStyling AnchoredSearch And AndersonDarlingTest AngerJ AngleBisector AngleBracket AnglePath AnglePath3D AngleVector AngularGauge Animate AnimationCycleOffset AnimationCycleRepetitions AnimationDirection AnimationDisplayTime AnimationRate AnimationRepetitions AnimationRunning AnimationRunTime AnimationTimeIndex Animator AnimatorBox AnimatorBoxOptions AnimatorElements Annotate Annotation AnnotationDelete AnnotationNames AnnotationRules AnnotationValue Annuity AnnuityDue Annulus AnomalyDetection AnomalyDetectorFunction Anonymous Antialiasing AntihermitianMatrixQ Antisymmetric AntisymmetricMatrixQ Antonyms AnyOrder AnySubset AnyTrue Apart ApartSquareFree APIFunction Appearance AppearanceElements AppearanceRules AppellF1 Append AppendCheck AppendLayer AppendTo ApplicationIdentificationKey Apply ApplySides ArcCos ArcCosh ArcCot ArcCoth ArcCsc ArcCsch ArcCurvature ARCHProcess ArcLength ArcSec ArcSech ArcSin ArcSinDistribution ArcSinh ArcTan ArcTanh Area Arg ArgMax ArgMin ArgumentCountQ ARIMAProcess ArithmeticGeometricMean ARMAProcess Around AroundReplace ARProcess Array ArrayComponents ArrayDepth ArrayFilter ArrayFlatten ArrayMesh ArrayPad ArrayPlot ArrayQ ArrayResample ArrayReshape ArrayRules Arrays Arrow Arrow3DBox ArrowBox Arrowheads ASATriangle Ask AskAppend AskConfirm AskDisplay AskedQ AskedValue AskFunction AskState AskTemplateDisplay AspectRatio AspectRatioFixed Assert AssociateTo Association AssociationFormat AssociationMap AssociationQ AssociationThread AssumeDeterministic Assuming Assumptions AstronomicalData AsymptoticDSolveValue AsymptoticEqual AsymptoticEquivalent AsymptoticGreater AsymptoticGreaterEqual AsymptoticIntegrate AsymptoticLess AsymptoticLessEqual AsymptoticOutputTracker AsymptoticRSolveValue AsymptoticSolve AsymptoticSum Asynchronous AsynchronousTaskObject AsynchronousTasks Atom AtomCoordinates AtomCount AtomDiagramCoordinates AtomList AtomQ AttentionLayer Attributes Audio AudioAmplify AudioAnnotate AudioAnnotationLookup AudioBlockMap AudioCapture AudioChannelAssignment AudioChannelCombine AudioChannelMix AudioChannels AudioChannelSeparate AudioData AudioDelay AudioDelete AudioDevice AudioDistance AudioFade AudioFrequencyShift AudioGenerator AudioIdentify AudioInputDevice AudioInsert AudioIntervals AudioJoin AudioLabel AudioLength AudioLocalMeasurements AudioLooping AudioLoudness AudioMeasurements AudioNormalize AudioOutputDevice AudioOverlay AudioPad AudioPan AudioPartition AudioPause AudioPitchShift AudioPlay AudioPlot AudioQ AudioRecord AudioReplace AudioResample AudioReverb AudioSampleRate AudioSpectralMap AudioSpectralTransformation AudioSplit AudioStop AudioStream AudioStreams AudioTimeStretch AudioTrim AudioType AugmentedPolyhedron AugmentedSymmetricPolynomial Authenticate Authentication AuthenticationDialog AutoAction Autocomplete AutocompletionFunction AutoCopy AutocorrelationTest AutoDelete AutoEvaluateEvents AutoGeneratedPackage AutoIndent AutoIndentSpacings AutoItalicWords AutoloadPath AutoMatch Automatic AutomaticImageSize AutoMultiplicationSymbol AutoNumberFormatting AutoOpenNotebooks AutoOpenPalettes AutoQuoteCharacters AutoRefreshed AutoRemove AutorunSequencing AutoScaling AutoScroll AutoSpacing AutoStyleOptions AutoStyleWords AutoSubmitting Axes AxesEdge AxesLabel AxesOrigin AxesStyle AxiomaticTheory Axis" + "BabyMonsterGroupB Back Background BackgroundAppearance BackgroundTasksSettings Backslash Backsubstitution Backward Ball Band BandpassFilter BandstopFilter BarabasiAlbertGraphDistribution BarChart BarChart3D BarcodeImage BarcodeRecognize BaringhausHenzeTest BarLegend BarlowProschanImportance BarnesG BarOrigin BarSpacing BartlettHannWindow BartlettWindow BaseDecode BaseEncode BaseForm Baseline BaselinePosition BaseStyle BasicRecurrentLayer BatchNormalizationLayer BatchSize BatesDistribution BattleLemarieWavelet BayesianMaximization BayesianMaximizationObject BayesianMinimization BayesianMinimizationObject Because BeckmannDistribution Beep Before Begin BeginDialogPacket BeginFrontEndInteractionPacket BeginPackage BellB BellY Below BenfordDistribution BeniniDistribution BenktanderGibratDistribution BenktanderWeibullDistribution BernoulliB BernoulliDistribution BernoulliGraphDistribution BernoulliProcess BernsteinBasis BesselFilterModel BesselI BesselJ BesselJZero BesselK BesselY BesselYZero Beta BetaBinomialDistribution BetaDistribution BetaNegativeBinomialDistribution BetaPrimeDistribution BetaRegularized Between BetweennessCentrality BeveledPolyhedron BezierCurve BezierCurve3DBox BezierCurve3DBoxOptions BezierCurveBox BezierCurveBoxOptions BezierFunction BilateralFilter Binarize BinaryDeserialize BinaryDistance BinaryFormat BinaryImageQ BinaryRead BinaryReadList BinarySerialize BinaryWrite BinCounts BinLists Binomial BinomialDistribution BinomialProcess BinormalDistribution BiorthogonalSplineWavelet BipartiteGraphQ BiquadraticFilterModel BirnbaumImportance BirnbaumSaundersDistribution BitAnd BitClear BitGet BitLength BitNot BitOr BitSet BitShiftLeft BitShiftRight BitXor BiweightLocation BiweightMidvariance Black BlackmanHarrisWindow BlackmanNuttallWindow BlackmanWindow Blank BlankForm BlankNullSequence BlankSequence Blend Block BlockchainAddressData BlockchainBase BlockchainBlockData BlockchainContractValue BlockchainData BlockchainGet BlockchainKeyEncode BlockchainPut BlockchainTokenData BlockchainTransaction BlockchainTransactionData BlockchainTransactionSign BlockchainTransactionSubmit BlockMap BlockRandom BlomqvistBeta BlomqvistBetaTest Blue Blur BodePlot BohmanWindow Bold Bond BondCount BondList BondQ Bookmarks Boole BooleanConsecutiveFunction BooleanConvert BooleanCountingFunction BooleanFunction BooleanGraph BooleanMaxterms BooleanMinimize BooleanMinterms BooleanQ BooleanRegion Booleans BooleanStrings BooleanTable BooleanVariables BorderDimensions BorelTannerDistribution Bottom BottomHatTransform BoundaryDiscretizeGraphics BoundaryDiscretizeRegion BoundaryMesh BoundaryMeshRegion BoundaryMeshRegionQ BoundaryStyle BoundedRegionQ BoundingRegion Bounds Box BoxBaselineShift BoxData BoxDimensions Boxed Boxes BoxForm BoxFormFormatTypes BoxFrame BoxID BoxMargins BoxMatrix BoxObject BoxRatios BoxRotation BoxRotationPoint BoxStyle BoxWhiskerChart Bra BracketingBar BraKet BrayCurtisDistance BreadthFirstScan Break BridgeData BrightnessEqualize BroadcastStationData Brown BrownForsytheTest BrownianBridgeProcess BrowserCategory BSplineBasis BSplineCurve BSplineCurve3DBox BSplineCurve3DBoxOptions BSplineCurveBox BSplineCurveBoxOptions BSplineFunction BSplineSurface BSplineSurface3DBox BSplineSurface3DBoxOptions BubbleChart BubbleChart3D BubbleScale BubbleSizes BuildingData BulletGauge BusinessDayQ ButterflyGraph ButterworthFilterModel Button ButtonBar ButtonBox ButtonBoxOptions ButtonCell ButtonContents ButtonData ButtonEvaluator ButtonExpandable ButtonFrame ButtonFunction ButtonMargins ButtonMinHeight ButtonNote ButtonNotebook ButtonSource ButtonStyle ButtonStyleMenuListing Byte ByteArray ByteArrayFormat ByteArrayQ ByteArrayToString ByteCount ByteOrdering" + "C CachedValue CacheGraphics CachePersistence CalendarConvert CalendarData CalendarType Callout CalloutMarker CalloutStyle CallPacket CanberraDistance Cancel CancelButton CandlestickChart CanonicalGraph CanonicalizePolygon CanonicalizePolyhedron CanonicalName CanonicalWarpingCorrespondence CanonicalWarpingDistance CantorMesh CantorStaircase Cap CapForm CapitalDifferentialD Capitalize CapsuleShape CaptureRunning CardinalBSplineBasis CarlemanLinearize CarmichaelLambda CaseOrdering Cases CaseSensitive Cashflow Casoratian Catalan CatalanNumber Catch Catenate CatenateLayer CauchyDistribution CauchyWindow CayleyGraph CDF CDFDeploy CDFInformation CDFWavelet Ceiling CelestialSystem Cell CellAutoOverwrite CellBaseline CellBoundingBox CellBracketOptions CellChangeTimes CellContents CellContext CellDingbat CellDynamicExpression CellEditDuplicate CellElementsBoundingBox CellElementSpacings CellEpilog CellEvaluationDuplicate CellEvaluationFunction CellEvaluationLanguage CellEventActions CellFrame CellFrameColor CellFrameLabelMargins CellFrameLabels CellFrameMargins CellGroup CellGroupData CellGrouping CellGroupingRules CellHorizontalScrolling CellID CellLabel CellLabelAutoDelete CellLabelMargins CellLabelPositioning CellLabelStyle CellLabelTemplate CellMargins CellObject CellOpen CellPrint CellProlog Cells CellSize CellStyle CellTags CellularAutomaton CensoredDistribution Censoring Center CenterArray CenterDot CentralFeature CentralMoment CentralMomentGeneratingFunction Cepstrogram CepstrogramArray CepstrumArray CForm ChampernowneNumber ChangeOptions ChannelBase ChannelBrokerAction ChannelDatabin ChannelHistoryLength ChannelListen ChannelListener ChannelListeners ChannelListenerWait ChannelObject ChannelPreSendFunction ChannelReceiverFunction ChannelSend ChannelSubscribers ChanVeseBinarize Character CharacterCounts CharacterEncoding CharacterEncodingsPath CharacteristicFunction CharacteristicPolynomial CharacterName CharacterRange Characters ChartBaseStyle ChartElementData ChartElementDataFunction ChartElementFunction ChartElements ChartLabels ChartLayout ChartLegends ChartStyle Chebyshev1FilterModel Chebyshev2FilterModel ChebyshevDistance ChebyshevT ChebyshevU Check CheckAbort CheckAll Checkbox CheckboxBar CheckboxBox CheckboxBoxOptions ChemicalData ChessboardDistance ChiDistribution ChineseRemainder ChiSquareDistribution ChoiceButtons ChoiceDialog CholeskyDecomposition Chop ChromaticityPlot ChromaticityPlot3D ChromaticPolynomial Circle CircleBox CircleDot CircleMinus CirclePlus CirclePoints CircleThrough CircleTimes CirculantGraph CircularOrthogonalMatrixDistribution CircularQuaternionMatrixDistribution CircularRealMatrixDistribution CircularSymplecticMatrixDistribution CircularUnitaryMatrixDistribution Circumsphere CityData ClassifierFunction ClassifierInformation ClassifierMeasurements ClassifierMeasurementsObject Classify ClassPriors Clear ClearAll ClearAttributes ClearCookies ClearPermissions ClearSystemCache ClebschGordan ClickPane Clip ClipboardNotebook ClipFill ClippingStyle ClipPlanes ClipPlanesStyle ClipRange Clock ClockGauge ClockwiseContourIntegral Close Closed CloseKernels ClosenessCentrality Closing ClosingAutoSave ClosingEvent CloudAccountData CloudBase CloudConnect CloudDeploy CloudDirectory CloudDisconnect CloudEvaluate CloudExport CloudExpression CloudExpressions CloudFunction CloudGet CloudImport CloudLoggingData CloudObject CloudObjectInformation CloudObjectInformationData CloudObjectNameFormat CloudObjects CloudObjectURLType CloudPublish CloudPut CloudRenderingMethod CloudSave CloudShare CloudSubmit CloudSymbol CloudUnshare ClusterClassify ClusterDissimilarityFunction ClusteringComponents ClusteringTree CMYKColor Coarse CodeAssistOptions Coefficient CoefficientArrays CoefficientDomain CoefficientList CoefficientRules CoifletWavelet Collect Colon ColonForm ColorBalance ColorCombine ColorConvert ColorCoverage ColorData ColorDataFunction ColorDetect ColorDistance ColorFunction ColorFunctionScaling Colorize ColorNegate ColorOutput ColorProfileData ColorQ ColorQuantize ColorReplace ColorRules ColorSelectorSettings ColorSeparate ColorSetter ColorSetterBox ColorSetterBoxOptions ColorSlider ColorsNear ColorSpace ColorToneMapping Column ColumnAlignments ColumnBackgrounds ColumnForm ColumnLines ColumnsEqual ColumnSpacings ColumnWidths CombinedEntityClass CombinerFunction CometData CommonDefaultFormatTypes Commonest CommonestFilter CommonName CommonUnits CommunityBoundaryStyle CommunityGraphPlot CommunityLabels CommunityRegionStyle CompanyData CompatibleUnitQ CompilationOptions CompilationTarget Compile Compiled CompiledCodeFunction CompiledFunction CompilerOptions Complement CompleteGraph CompleteGraphQ CompleteKaryTree CompletionsListPacket Complex Complexes ComplexExpand ComplexInfinity ComplexityFunction ComplexListPlot ComplexPlot ComplexPlot3D ComponentMeasurements ComponentwiseContextMenu Compose ComposeList ComposeSeries CompositeQ Composition CompoundElement CompoundExpression CompoundPoissonDistribution CompoundPoissonProcess CompoundRenewalProcess Compress CompressedData ComputeUncertainty Condition ConditionalExpression Conditioned Cone ConeBox ConfidenceLevel ConfidenceRange ConfidenceTransform ConfigurationPath ConformAudio ConformImages Congruent ConicHullRegion ConicHullRegion3DBox ConicHullRegionBox ConicOptimization Conjugate ConjugateTranspose Conjunction Connect ConnectedComponents ConnectedGraphComponents ConnectedGraphQ ConnectedMeshComponents ConnectedMoleculeComponents ConnectedMoleculeQ ConnectionSettings ConnectLibraryCallbackFunction ConnectSystemModelComponents ConnesWindow ConoverTest ConsoleMessage ConsoleMessagePacket ConsolePrint Constant ConstantArray ConstantArrayLayer ConstantImage ConstantPlusLayer ConstantRegionQ Constants ConstantTimesLayer ConstellationData ConstrainedMax ConstrainedMin Construct Containing ContainsAll ContainsAny ContainsExactly ContainsNone ContainsOnly ContentFieldOptions ContentLocationFunction ContentObject ContentPadding ContentsBoundingBox ContentSelectable ContentSize Context ContextMenu Contexts ContextToFileName Continuation Continue ContinuedFraction ContinuedFractionK ContinuousAction ContinuousMarkovProcess ContinuousTask ContinuousTimeModelQ ContinuousWaveletData ContinuousWaveletTransform ContourDetect ContourGraphics ContourIntegral ContourLabels ContourLines ContourPlot ContourPlot3D Contours ContourShading ContourSmoothing ContourStyle ContraharmonicMean ContrastiveLossLayer Control ControlActive ControlAlignment ControlGroupContentsBox ControllabilityGramian ControllabilityMatrix ControllableDecomposition ControllableModelQ ControllerDuration ControllerInformation ControllerInformationData ControllerLinking ControllerManipulate ControllerMethod ControllerPath ControllerState ControlPlacement ControlsRendering ControlType Convergents ConversionOptions ConversionRules ConvertToBitmapPacket ConvertToPostScript ConvertToPostScriptPacket ConvexHullMesh ConvexPolygonQ ConvexPolyhedronQ ConvolutionLayer Convolve ConwayGroupCo1 ConwayGroupCo2 ConwayGroupCo3 CookieFunction Cookies CoordinateBoundingBox CoordinateBoundingBoxArray CoordinateBounds CoordinateBoundsArray CoordinateChartData CoordinatesToolOptions CoordinateTransform CoordinateTransformData CoprimeQ Coproduct CopulaDistribution Copyable CopyDatabin CopyDirectory CopyFile CopyTag CopyToClipboard CornerFilter CornerNeighbors Correlation CorrelationDistance CorrelationFunction CorrelationTest Cos Cosh CoshIntegral CosineDistance CosineWindow CosIntegral Cot Coth Count CountDistinct CountDistinctBy CounterAssignments CounterBox CounterBoxOptions CounterClockwiseContourIntegral CounterEvaluator CounterFunction CounterIncrements CounterStyle CounterStyleMenuListing CountRoots CountryData Counts CountsBy Covariance CovarianceEstimatorFunction CovarianceFunction CoxianDistribution CoxIngersollRossProcess CoxModel CoxModelFit CramerVonMisesTest CreateArchive CreateCellID CreateChannel CreateCloudExpression CreateDatabin CreateDataSystemModel CreateDialog CreateDirectory CreateDocument CreateFile CreateIntermediateDirectories CreateManagedLibraryExpression CreateNotebook CreatePalette CreatePalettePacket CreatePermissionsGroup CreateScheduledTask CreateSearchIndex CreateSystemModel CreateTemporary CreateUUID CreateWindow CriterionFunction CriticalityFailureImportance CriticalitySuccessImportance CriticalSection Cross CrossEntropyLossLayer CrossingCount CrossingDetect CrossingPolygon CrossMatrix Csc Csch CTCLossLayer Cube CubeRoot Cubics Cuboid CuboidBox Cumulant CumulantGeneratingFunction Cup CupCap Curl CurlyDoubleQuote CurlyQuote CurrencyConvert CurrentDate CurrentImage CurrentlySpeakingPacket CurrentNotebookImage CurrentScreenImage CurrentValue Curry CurvatureFlowFilter CurveClosed Cyan CycleGraph CycleIndexPolynomial Cycles CyclicGroup Cyclotomic Cylinder CylinderBox CylindricalDecomposition" + "D DagumDistribution DamData DamerauLevenshteinDistance DampingFactor Darker Dashed Dashing DatabaseConnect DatabaseDisconnect DatabaseReference Databin DatabinAdd DatabinRemove Databins DatabinUpload DataCompression DataDistribution DataRange DataReversed Dataset Date DateBounds Dated DateDelimiters DateDifference DatedUnit DateFormat DateFunction DateHistogram DateList DateListLogPlot DateListPlot DateListStepPlot DateObject DateObjectQ DateOverlapsQ DatePattern DatePlus DateRange DateReduction DateString DateTicksFormat DateValue DateWithinQ DaubechiesWavelet DavisDistribution DawsonF DayCount DayCountConvention DayHemisphere DaylightQ DayMatchQ DayName DayNightTerminator DayPlus DayRange DayRound DeBruijnGraph DeBruijnSequence Debug DebugTag Decapitalize Decimal DecimalForm DeclareKnownSymbols DeclarePackage Decompose DeconvolutionLayer Decrement Decrypt DecryptFile DedekindEta DeepSpaceProbeData Default DefaultAxesStyle DefaultBaseStyle DefaultBoxStyle DefaultButton DefaultColor DefaultControlPlacement DefaultDuplicateCellStyle DefaultDuration DefaultElement DefaultFaceGridsStyle DefaultFieldHintStyle DefaultFont DefaultFontProperties DefaultFormatType DefaultFormatTypeForStyle DefaultFrameStyle DefaultFrameTicksStyle DefaultGridLinesStyle DefaultInlineFormatType DefaultInputFormatType DefaultLabelStyle DefaultMenuStyle DefaultNaturalLanguage DefaultNewCellStyle DefaultNewInlineCellStyle DefaultNotebook DefaultOptions DefaultOutputFormatType DefaultPrintPrecision DefaultStyle DefaultStyleDefinitions DefaultTextFormatType DefaultTextInlineFormatType DefaultTicksStyle DefaultTooltipStyle DefaultValue DefaultValues Defer DefineExternal DefineInputStreamMethod DefineOutputStreamMethod DefineResourceFunction Definition Degree DegreeCentrality DegreeGraphDistribution DegreeLexicographic DegreeReverseLexicographic DEigensystem DEigenvalues Deinitialization Del DelaunayMesh Delayed Deletable Delete DeleteAnomalies DeleteBorderComponents DeleteCases DeleteChannel DeleteCloudExpression DeleteContents DeleteDirectory DeleteDuplicates DeleteDuplicatesBy DeleteFile DeleteMissing DeleteObject DeletePermissionsKey DeleteSearchIndex DeleteSmallComponents DeleteStopwords DeleteWithContents DeletionWarning DelimitedArray DelimitedSequence Delimiter DelimiterFlashTime DelimiterMatching Delimiters DeliveryFunction Dendrogram Denominator DensityGraphics DensityHistogram DensityPlot DensityPlot3D DependentVariables Deploy Deployed Depth DepthFirstScan Derivative DerivativeFilter DerivedKey DescriptorStateSpace DesignMatrix DestroyAfterEvaluation Det DeviceClose DeviceConfigure DeviceExecute DeviceExecuteAsynchronous DeviceObject DeviceOpen DeviceOpenQ DeviceRead DeviceReadBuffer DeviceReadLatest DeviceReadList DeviceReadTimeSeries Devices DeviceStreams DeviceWrite DeviceWriteBuffer DGaussianWavelet DiacriticalPositioning Diagonal DiagonalizableMatrixQ DiagonalMatrix DiagonalMatrixQ Dialog DialogIndent DialogInput DialogLevel DialogNotebook DialogProlog DialogReturn DialogSymbols Diamond DiamondMatrix DiceDissimilarity DictionaryLookup DictionaryWordQ DifferenceDelta DifferenceOrder DifferenceQuotient DifferenceRoot DifferenceRootReduce Differences DifferentialD DifferentialRoot DifferentialRootReduce DifferentiatorFilter DigitalSignature DigitBlock DigitBlockMinimum DigitCharacter DigitCount DigitQ DihedralAngle DihedralGroup Dilation DimensionalCombinations DimensionalMeshComponents DimensionReduce DimensionReducerFunction DimensionReduction Dimensions DiracComb DiracDelta DirectedEdge DirectedEdges DirectedGraph DirectedGraphQ DirectedInfinity Direction Directive Directory DirectoryName DirectoryQ DirectoryStack DirichletBeta DirichletCharacter DirichletCondition DirichletConvolve DirichletDistribution DirichletEta DirichletL DirichletLambda DirichletTransform DirichletWindow DisableConsolePrintPacket DisableFormatting DiscreteChirpZTransform DiscreteConvolve DiscreteDelta DiscreteHadamardTransform DiscreteIndicator DiscreteLimit DiscreteLQEstimatorGains DiscreteLQRegulatorGains DiscreteLyapunovSolve DiscreteMarkovProcess DiscreteMaxLimit DiscreteMinLimit DiscretePlot DiscretePlot3D DiscreteRatio DiscreteRiccatiSolve DiscreteShift DiscreteTimeModelQ DiscreteUniformDistribution DiscreteVariables DiscreteWaveletData DiscreteWaveletPacketTransform DiscreteWaveletTransform DiscretizeGraphics DiscretizeRegion Discriminant DisjointQ Disjunction Disk DiskBox DiskMatrix DiskSegment Dispatch DispatchQ DispersionEstimatorFunction Display DisplayAllSteps DisplayEndPacket DisplayFlushImagePacket DisplayForm DisplayFunction DisplayPacket DisplayRules DisplaySetSizePacket DisplayString DisplayTemporary DisplayWith DisplayWithRef DisplayWithVariable DistanceFunction DistanceMatrix DistanceTransform Distribute Distributed DistributedContexts DistributeDefinitions DistributionChart DistributionDomain DistributionFitTest DistributionParameterAssumptions DistributionParameterQ Dithering Div Divergence Divide DivideBy Dividers DivideSides Divisible Divisors DivisorSigma DivisorSum DMSList DMSString Do DockedCells DocumentGenerator DocumentGeneratorInformation DocumentGeneratorInformationData DocumentGenerators DocumentNotebook DocumentWeightingRules Dodecahedron DomainRegistrationInformation DominantColors DOSTextFormat Dot DotDashed DotEqual DotLayer DotPlusLayer Dotted DoubleBracketingBar DoubleContourIntegral DoubleDownArrow DoubleLeftArrow DoubleLeftRightArrow DoubleLeftTee DoubleLongLeftArrow DoubleLongLeftRightArrow DoubleLongRightArrow DoubleRightArrow DoubleRightTee DoubleUpArrow DoubleUpDownArrow DoubleVerticalBar DoublyInfinite Down DownArrow DownArrowBar DownArrowUpArrow DownLeftRightVector DownLeftTeeVector DownLeftVector DownLeftVectorBar DownRightTeeVector DownRightVector DownRightVectorBar Downsample DownTee DownTeeArrow DownValues DragAndDrop DrawEdges DrawFrontFaces DrawHighlighted Drop DropoutLayer DSolve DSolveValue Dt DualLinearProgramming DualPolyhedron DualSystemsModel DumpGet DumpSave DuplicateFreeQ Duration Dynamic DynamicBox DynamicBoxOptions DynamicEvaluationTimeout DynamicGeoGraphics DynamicImage DynamicLocation DynamicModule DynamicModuleBox DynamicModuleBoxOptions DynamicModuleParent DynamicModuleValues DynamicName DynamicNamespace DynamicReference DynamicSetting DynamicUpdating DynamicWrapper DynamicWrapperBox DynamicWrapperBoxOptions" + "E EarthImpactData EarthquakeData EccentricityCentrality Echo EchoFunction EclipseType EdgeAdd EdgeBetweennessCentrality EdgeCapacity EdgeCapForm EdgeColor EdgeConnectivity EdgeContract EdgeCost EdgeCount EdgeCoverQ EdgeCycleMatrix EdgeDashing EdgeDelete EdgeDetect EdgeForm EdgeIndex EdgeJoinForm EdgeLabeling EdgeLabels EdgeLabelStyle EdgeList EdgeOpacity EdgeQ EdgeRenderingFunction EdgeRules EdgeShapeFunction EdgeStyle EdgeThickness EdgeWeight EdgeWeightedGraphQ Editable EditButtonSettings EditCellTagsSettings EditDistance EffectiveInterest Eigensystem Eigenvalues EigenvectorCentrality Eigenvectors Element ElementData ElementwiseLayer ElidedForms Eliminate EliminationOrder Ellipsoid EllipticE EllipticExp EllipticExpPrime EllipticF EllipticFilterModel EllipticK EllipticLog EllipticNomeQ EllipticPi EllipticReducedHalfPeriods EllipticTheta EllipticThetaPrime EmbedCode EmbeddedHTML EmbeddedService EmbeddingLayer EmbeddingObject EmitSound EmphasizeSyntaxErrors EmpiricalDistribution Empty EmptyGraphQ EmptyRegion EnableConsolePrintPacket Enabled Encode Encrypt EncryptedObject EncryptFile End EndAdd EndDialogPacket EndFrontEndInteractionPacket EndOfBuffer EndOfFile EndOfLine EndOfString EndPackage EngineEnvironment EngineeringForm Enter EnterExpressionPacket EnterTextPacket Entity EntityClass EntityClassList EntityCopies EntityFunction EntityGroup EntityInstance EntityList EntityPrefetch EntityProperties EntityProperty EntityPropertyClass EntityRegister EntityStore EntityStores EntityTypeName EntityUnregister EntityValue Entropy EntropyFilter Environment Epilog EpilogFunction Equal EqualColumns EqualRows EqualTilde EqualTo EquatedTo Equilibrium EquirippleFilterKernel Equivalent Erf Erfc Erfi ErlangB ErlangC ErlangDistribution Erosion ErrorBox ErrorBoxOptions ErrorNorm ErrorPacket ErrorsDialogSettings EscapeRadius EstimatedBackground EstimatedDistribution EstimatedProcess EstimatorGains EstimatorRegulator EuclideanDistance EulerAngles EulerCharacteristic EulerE EulerGamma EulerianGraphQ EulerMatrix EulerPhi Evaluatable Evaluate Evaluated EvaluatePacket EvaluateScheduledTask EvaluationBox EvaluationCell EvaluationCompletionAction EvaluationData EvaluationElements EvaluationEnvironment EvaluationMode EvaluationMonitor EvaluationNotebook EvaluationObject EvaluationOrder Evaluator EvaluatorNames EvenQ EventData EventEvaluator EventHandler EventHandlerTag EventLabels EventSeries ExactBlackmanWindow ExactNumberQ ExactRootIsolation ExampleData Except ExcludedForms ExcludedLines ExcludedPhysicalQuantities ExcludePods Exclusions ExclusionsStyle Exists Exit ExitDialog ExoplanetData Exp Expand ExpandAll ExpandDenominator ExpandFileName ExpandNumerator Expectation ExpectationE ExpectedValue ExpGammaDistribution ExpIntegralE ExpIntegralEi ExpirationDate Exponent ExponentFunction ExponentialDistribution ExponentialFamily ExponentialGeneratingFunction ExponentialMovingAverage ExponentialPowerDistribution ExponentPosition ExponentStep Export ExportAutoReplacements ExportByteArray ExportForm ExportPacket ExportString Expression ExpressionCell ExpressionPacket ExpressionUUID ExpToTrig ExtendedEntityClass ExtendedGCD Extension ExtentElementFunction ExtentMarkers ExtentSize ExternalBundle ExternalCall ExternalDataCharacterEncoding ExternalEvaluate ExternalFunction ExternalFunctionName ExternalObject ExternalOptions ExternalSessionObject ExternalSessions ExternalTypeSignature ExternalValue Extract ExtractArchive ExtractLayer ExtremeValueDistribution" + "FaceForm FaceGrids FaceGridsStyle FacialFeatures Factor FactorComplete Factorial Factorial2 FactorialMoment FactorialMomentGeneratingFunction FactorialPower FactorInteger FactorList FactorSquareFree FactorSquareFreeList FactorTerms FactorTermsList Fail Failure FailureAction FailureDistribution FailureQ False FareySequence FARIMAProcess FeatureDistance FeatureExtract FeatureExtraction FeatureExtractor FeatureExtractorFunction FeatureNames FeatureNearest FeatureSpacePlot FeatureSpacePlot3D FeatureTypes FEDisableConsolePrintPacket FeedbackLinearize FeedbackSector FeedbackSectorStyle FeedbackType FEEnableConsolePrintPacket FetalGrowthData Fibonacci Fibonorial FieldCompletionFunction FieldHint FieldHintStyle FieldMasked FieldSize File FileBaseName FileByteCount FileConvert FileDate FileExistsQ FileExtension FileFormat FileHandler FileHash FileInformation FileName FileNameDepth FileNameDialogSettings FileNameDrop FileNameForms FileNameJoin FileNames FileNameSetter FileNameSplit FileNameTake FilePrint FileSize FileSystemMap FileSystemScan FileTemplate FileTemplateApply FileType FilledCurve FilledCurveBox FilledCurveBoxOptions Filling FillingStyle FillingTransform FilteredEntityClass FilterRules FinancialBond FinancialData FinancialDerivative FinancialIndicator Find FindAnomalies FindArgMax FindArgMin FindChannels FindClique FindClusters FindCookies FindCurvePath FindCycle FindDevices FindDistribution FindDistributionParameters FindDivisions FindEdgeCover FindEdgeCut FindEdgeIndependentPaths FindEquationalProof FindEulerianCycle FindExternalEvaluators FindFaces FindFile FindFit FindFormula FindFundamentalCycles FindGeneratingFunction FindGeoLocation FindGeometricConjectures FindGeometricTransform FindGraphCommunities FindGraphIsomorphism FindGraphPartition FindHamiltonianCycle FindHamiltonianPath FindHiddenMarkovStates FindIndependentEdgeSet FindIndependentVertexSet FindInstance FindIntegerNullVector FindKClan FindKClique FindKClub FindKPlex FindLibrary FindLinearRecurrence FindList FindMatchingColor FindMaximum FindMaximumFlow FindMaxValue FindMeshDefects FindMinimum FindMinimumCostFlow FindMinimumCut FindMinValue FindMoleculeSubstructure FindPath FindPeaks FindPermutation FindPostmanTour FindProcessParameters FindRepeat FindRoot FindSequenceFunction FindSettings FindShortestPath FindShortestTour FindSpanningTree FindSystemModelEquilibrium FindTextualAnswer FindThreshold FindTransientRepeat FindVertexCover FindVertexCut FindVertexIndependentPaths Fine FinishDynamic FiniteAbelianGroupCount FiniteGroupCount FiniteGroupData First FirstCase FirstPassageTimeDistribution FirstPosition FischerGroupFi22 FischerGroupFi23 FischerGroupFi24Prime FisherHypergeometricDistribution FisherRatioTest FisherZDistribution Fit FitAll FitRegularization FittedModel FixedOrder FixedPoint FixedPointList FlashSelection Flat Flatten FlattenAt FlattenLayer FlatTopWindow FlipView Floor FlowPolynomial FlushPrintOutputPacket Fold FoldList FoldPair FoldPairList FollowRedirects Font FontColor FontFamily FontForm FontName FontOpacity FontPostScriptName FontProperties FontReencoding FontSize FontSlant FontSubstitutions FontTracking FontVariations FontWeight For ForAll Format FormatRules FormatType FormatTypeAutoConvert FormatValues FormBox FormBoxOptions FormControl FormFunction FormLayoutFunction FormObject FormPage FormTheme FormulaData FormulaLookup FortranForm Forward ForwardBackward Fourier FourierCoefficient FourierCosCoefficient FourierCosSeries FourierCosTransform FourierDCT FourierDCTFilter FourierDCTMatrix FourierDST FourierDSTMatrix FourierMatrix FourierParameters FourierSequenceTransform FourierSeries FourierSinCoefficient FourierSinSeries FourierSinTransform FourierTransform FourierTrigSeries FractionalBrownianMotionProcess FractionalGaussianNoiseProcess FractionalPart FractionBox FractionBoxOptions FractionLine Frame FrameBox FrameBoxOptions Framed FrameInset FrameLabel Frameless FrameMargins FrameRate FrameStyle FrameTicks FrameTicksStyle FRatioDistribution FrechetDistribution FreeQ FrenetSerretSystem FrequencySamplingFilterKernel FresnelC FresnelF FresnelG FresnelS Friday FrobeniusNumber FrobeniusSolve FromAbsoluteTime FromCharacterCode FromCoefficientRules FromContinuedFraction FromDate FromDigits FromDMS FromEntity FromJulianDate FromLetterNumber FromPolarCoordinates FromRomanNumeral FromSphericalCoordinates FromUnixTime Front FrontEndDynamicExpression FrontEndEventActions FrontEndExecute FrontEndObject FrontEndResource FrontEndResourceString FrontEndStackSize FrontEndToken FrontEndTokenExecute FrontEndValueCache FrontEndVersion FrontFaceColor FrontFaceOpacity Full FullAxes FullDefinition FullForm FullGraphics FullInformationOutputRegulator FullOptions FullRegion FullSimplify Function FunctionCompile FunctionCompileExport FunctionCompileExportByteArray FunctionCompileExportLibrary FunctionCompileExportString FunctionDomain FunctionExpand FunctionInterpolation FunctionPeriod FunctionRange FunctionSpace FussellVeselyImportance" + "GaborFilter GaborMatrix GaborWavelet GainMargins GainPhaseMargins GalaxyData GalleryView Gamma GammaDistribution GammaRegularized GapPenalty GARCHProcess GatedRecurrentLayer Gather GatherBy GaugeFaceElementFunction GaugeFaceStyle GaugeFrameElementFunction GaugeFrameSize GaugeFrameStyle GaugeLabels GaugeMarkers GaugeStyle GaussianFilter GaussianIntegers GaussianMatrix GaussianOrthogonalMatrixDistribution GaussianSymplecticMatrixDistribution GaussianUnitaryMatrixDistribution GaussianWindow GCD GegenbauerC General GeneralizedLinearModelFit GenerateAsymmetricKeyPair GenerateConditions GeneratedCell GeneratedDocumentBinding GenerateDerivedKey GenerateDigitalSignature GenerateDocument GeneratedParameters GeneratedQuantityMagnitudes GenerateHTTPResponse GenerateSecuredAuthenticationKey GenerateSymmetricKey GeneratingFunction GeneratorDescription GeneratorHistoryLength GeneratorOutputType Generic GenericCylindricalDecomposition GenomeData GenomeLookup GeoAntipode GeoArea GeoArraySize GeoBackground GeoBoundingBox GeoBounds GeoBoundsRegion GeoBubbleChart GeoCenter GeoCircle GeodesicClosing GeodesicDilation GeodesicErosion GeodesicOpening GeoDestination GeodesyData GeoDirection GeoDisk GeoDisplacement GeoDistance GeoDistanceList GeoElevationData GeoEntities GeoGraphics GeogravityModelData GeoGridDirectionDifference GeoGridLines GeoGridLinesStyle GeoGridPosition GeoGridRange GeoGridRangePadding GeoGridUnitArea GeoGridUnitDistance GeoGridVector GeoGroup GeoHemisphere GeoHemisphereBoundary GeoHistogram GeoIdentify GeoImage GeoLabels GeoLength GeoListPlot GeoLocation GeologicalPeriodData GeomagneticModelData GeoMarker GeometricAssertion GeometricBrownianMotionProcess GeometricDistribution GeometricMean GeometricMeanFilter GeometricScene GeometricTransformation GeometricTransformation3DBox GeometricTransformation3DBoxOptions GeometricTransformationBox GeometricTransformationBoxOptions GeoModel GeoNearest GeoPath GeoPosition GeoPositionENU GeoPositionXYZ GeoProjection GeoProjectionData GeoRange GeoRangePadding GeoRegionValuePlot GeoResolution GeoScaleBar GeoServer GeoSmoothHistogram GeoStreamPlot GeoStyling GeoStylingImageFunction GeoVariant GeoVector GeoVectorENU GeoVectorPlot GeoVectorXYZ GeoVisibleRegion GeoVisibleRegionBoundary GeoWithinQ GeoZoomLevel GestureHandler GestureHandlerTag Get GetBoundingBoxSizePacket GetContext GetEnvironment GetFileName GetFrontEndOptionsDataPacket GetLinebreakInformationPacket GetMenusPacket GetPageBreakInformationPacket Glaisher GlobalClusteringCoefficient GlobalPreferences GlobalSession Glow GoldenAngle GoldenRatio GompertzMakehamDistribution GoodmanKruskalGamma GoodmanKruskalGammaTest Goto Grad Gradient GradientFilter GradientOrientationFilter GrammarApply GrammarRules GrammarToken Graph Graph3D GraphAssortativity GraphAutomorphismGroup GraphCenter GraphComplement GraphData GraphDensity GraphDiameter GraphDifference GraphDisjointUnion GraphDistance GraphDistanceMatrix GraphElementData GraphEmbedding GraphHighlight GraphHighlightStyle GraphHub Graphics Graphics3D Graphics3DBox Graphics3DBoxOptions GraphicsArray GraphicsBaseline GraphicsBox GraphicsBoxOptions GraphicsColor GraphicsColumn GraphicsComplex GraphicsComplex3DBox GraphicsComplex3DBoxOptions GraphicsComplexBox GraphicsComplexBoxOptions GraphicsContents GraphicsData GraphicsGrid GraphicsGridBox GraphicsGroup GraphicsGroup3DBox GraphicsGroup3DBoxOptions GraphicsGroupBox GraphicsGroupBoxOptions GraphicsGrouping GraphicsHighlightColor GraphicsRow GraphicsSpacing GraphicsStyle GraphIntersection GraphLayout GraphLinkEfficiency GraphPeriphery GraphPlot GraphPlot3D GraphPower GraphPropertyDistribution GraphQ GraphRadius GraphReciprocity GraphRoot GraphStyle GraphUnion Gray GrayLevel Greater GreaterEqual GreaterEqualLess GreaterEqualThan GreaterFullEqual GreaterGreater GreaterLess GreaterSlantEqual GreaterThan GreaterTilde Green GreenFunction Grid GridBaseline GridBox GridBoxAlignment GridBoxBackground GridBoxDividers GridBoxFrame GridBoxItemSize GridBoxItemStyle GridBoxOptions GridBoxSpacings GridCreationSettings GridDefaultElement GridElementStyleOptions GridFrame GridFrameMargins GridGraph GridLines GridLinesStyle GroebnerBasis GroupActionBase GroupBy GroupCentralizer GroupElementFromWord GroupElementPosition GroupElementQ GroupElements GroupElementToWord GroupGenerators Groupings GroupMultiplicationTable GroupOrbits GroupOrder GroupPageBreakWithin GroupSetwiseStabilizer GroupStabilizer GroupStabilizerChain GroupTogetherGrouping GroupTogetherNestedGrouping GrowCutComponents Gudermannian GuidedFilter GumbelDistribution" + "HaarWavelet HadamardMatrix HalfLine HalfNormalDistribution HalfPlane HalfSpace HamiltonianGraphQ HammingDistance HammingWindow HandlerFunctions HandlerFunctionsKeys HankelH1 HankelH2 HankelMatrix HankelTransform HannPoissonWindow HannWindow HaradaNortonGroupHN HararyGraph HarmonicMean HarmonicMeanFilter HarmonicNumber Hash Haversine HazardFunction Head HeadCompose HeaderLines Heads HeavisideLambda HeavisidePi HeavisideTheta HeldGroupHe HeldPart HelpBrowserLookup HelpBrowserNotebook HelpBrowserSettings Here HermiteDecomposition HermiteH HermitianMatrixQ HessenbergDecomposition Hessian HexadecimalCharacter Hexahedron HexahedronBox HexahedronBoxOptions HiddenMarkovProcess HiddenSurface Highlighted HighlightGraph HighlightImage HighlightMesh HighpassFilter HigmanSimsGroupHS HilbertCurve HilbertFilter HilbertMatrix Histogram Histogram3D HistogramDistribution HistogramList HistogramTransform HistogramTransformInterpolation HistoricalPeriodData HitMissTransform HITSCentrality HjorthDistribution HodgeDual HoeffdingD HoeffdingDTest Hold HoldAll HoldAllComplete HoldComplete HoldFirst HoldForm HoldPattern HoldRest HolidayCalendar HomeDirectory HomePage Horizontal HorizontalForm HorizontalGauge HorizontalScrollPosition HornerForm HostLookup HotellingTSquareDistribution HoytDistribution HTMLSave HTTPErrorResponse HTTPRedirect HTTPRequest HTTPRequestData HTTPResponse Hue HumanGrowthData HumpDownHump HumpEqual HurwitzLerchPhi HurwitzZeta HyperbolicDistribution HypercubeGraph HyperexponentialDistribution Hyperfactorial Hypergeometric0F1 Hypergeometric0F1Regularized Hypergeometric1F1 Hypergeometric1F1Regularized Hypergeometric2F1 Hypergeometric2F1Regularized HypergeometricDistribution HypergeometricPFQ HypergeometricPFQRegularized HypergeometricU Hyperlink HyperlinkCreationSettings Hyperplane Hyphenation HyphenationOptions HypoexponentialDistribution HypothesisTestData" + "I IconData Iconize IconizedObject IconRules Icosahedron Identity IdentityMatrix If IgnoreCase IgnoreDiacritics IgnorePunctuation IgnoreSpellCheck IgnoringInactive Im Image Image3D Image3DProjection Image3DSlices ImageAccumulate ImageAdd ImageAdjust ImageAlign ImageApply ImageApplyIndexed ImageAspectRatio ImageAssemble ImageAugmentationLayer ImageBoundingBoxes ImageCache ImageCacheValid ImageCapture ImageCaptureFunction ImageCases ImageChannels ImageClip ImageCollage ImageColorSpace ImageCompose ImageContainsQ ImageContents ImageConvolve ImageCooccurrence ImageCorners ImageCorrelate ImageCorrespondingPoints ImageCrop ImageData ImageDeconvolve ImageDemosaic ImageDifference ImageDimensions ImageDisplacements ImageDistance ImageEffect ImageExposureCombine ImageFeatureTrack ImageFileApply ImageFileFilter ImageFileScan ImageFilter ImageFocusCombine ImageForestingComponents ImageFormattingWidth ImageForwardTransformation ImageGraphics ImageHistogram ImageIdentify ImageInstanceQ ImageKeypoints ImageLevels ImageLines ImageMargins ImageMarker ImageMarkers ImageMeasurements ImageMesh ImageMultiply ImageOffset ImagePad ImagePadding ImagePartition ImagePeriodogram ImagePerspectiveTransformation ImagePosition ImagePreviewFunction ImagePyramid ImagePyramidApply ImageQ ImageRangeCache ImageRecolor ImageReflect ImageRegion ImageResize ImageResolution ImageRestyle ImageRotate ImageRotated ImageSaliencyFilter ImageScaled ImageScan ImageSize ImageSizeAction ImageSizeCache ImageSizeMultipliers ImageSizeRaw ImageSubtract ImageTake ImageTransformation ImageTrim ImageType ImageValue ImageValuePositions ImagingDevice ImplicitRegion Implies Import ImportAutoReplacements ImportByteArray ImportOptions ImportString ImprovementImportance In Inactivate Inactive IncidenceGraph IncidenceList IncidenceMatrix IncludeAromaticBonds IncludeConstantBasis IncludeDefinitions IncludeDirectories IncludeFileExtension IncludeGeneratorTasks IncludeHydrogens IncludeInflections IncludeMetaInformation IncludePods IncludeQuantities IncludeRelatedTables IncludeSingularTerm IncludeWindowTimes Increment IndefiniteMatrixQ Indent IndentingNewlineSpacings IndentMaxFraction IndependenceTest IndependentEdgeSetQ IndependentPhysicalQuantity IndependentUnit IndependentUnitDimension IndependentVertexSetQ Indeterminate IndeterminateThreshold IndexCreationOptions Indexed IndexGraph IndexTag Inequality InexactNumberQ InexactNumbers InfiniteLine InfinitePlane Infinity Infix InflationAdjust InflationMethod Information InformationData InformationDataGrid Inherited InheritScope InhomogeneousPoissonProcess InitialEvaluationHistory Initialization InitializationCell InitializationCellEvaluation InitializationCellWarning InitializationObjects InitializationValue Initialize InitialSeeding InlineCounterAssignments InlineCounterIncrements InlineRules Inner InnerPolygon InnerPolyhedron Inpaint Input InputAliases InputAssumptions InputAutoReplacements InputField InputFieldBox InputFieldBoxOptions InputForm InputGrouping InputNamePacket InputNotebook InputPacket InputSettings InputStream InputString InputStringPacket InputToBoxFormPacket Insert InsertionFunction InsertionPointObject InsertLinebreaks InsertResults Inset Inset3DBox Inset3DBoxOptions InsetBox InsetBoxOptions Insphere Install InstallService InstanceNormalizationLayer InString Integer IntegerDigits IntegerExponent IntegerLength IntegerName IntegerPart IntegerPartitions IntegerQ IntegerReverse Integers IntegerString Integral Integrate Interactive InteractiveTradingChart Interlaced Interleaving InternallyBalancedDecomposition InterpolatingFunction InterpolatingPolynomial Interpolation InterpolationOrder InterpolationPoints InterpolationPrecision Interpretation InterpretationBox InterpretationBoxOptions InterpretationFunction Interpreter InterpretTemplate InterquartileRange Interrupt InterruptSettings IntersectingQ Intersection Interval IntervalIntersection IntervalMarkers IntervalMarkersStyle IntervalMemberQ IntervalSlider IntervalUnion Into Inverse InverseBetaRegularized InverseCDF InverseChiSquareDistribution InverseContinuousWaveletTransform InverseDistanceTransform InverseEllipticNomeQ InverseErf InverseErfc InverseFourier InverseFourierCosTransform InverseFourierSequenceTransform InverseFourierSinTransform InverseFourierTransform InverseFunction InverseFunctions InverseGammaDistribution InverseGammaRegularized InverseGaussianDistribution InverseGudermannian InverseHankelTransform InverseHaversine InverseImagePyramid InverseJacobiCD InverseJacobiCN InverseJacobiCS InverseJacobiDC InverseJacobiDN InverseJacobiDS InverseJacobiNC InverseJacobiND InverseJacobiNS InverseJacobiSC InverseJacobiSD InverseJacobiSN InverseLaplaceTransform InverseMellinTransform InversePermutation InverseRadon InverseRadonTransform InverseSeries InverseShortTimeFourier InverseSpectrogram InverseSurvivalFunction InverseTransformedRegion InverseWaveletTransform InverseWeierstrassP InverseWishartMatrixDistribution InverseZTransform Invisible InvisibleApplication InvisibleTimes IPAddress IrreduciblePolynomialQ IslandData IsolatingInterval IsomorphicGraphQ IsotopeData Italic Item ItemAspectRatio ItemBox ItemBoxOptions ItemSize ItemStyle ItoProcess" + "JaccardDissimilarity JacobiAmplitude Jacobian JacobiCD JacobiCN JacobiCS JacobiDC JacobiDN JacobiDS JacobiNC JacobiND JacobiNS JacobiP JacobiSC JacobiSD JacobiSN JacobiSymbol JacobiZeta JankoGroupJ1 JankoGroupJ2 JankoGroupJ3 JankoGroupJ4 JarqueBeraALMTest JohnsonDistribution Join JoinAcross Joined JoinedCurve JoinedCurveBox JoinedCurveBoxOptions JoinForm JordanDecomposition JordanModelDecomposition JulianDate JuliaSetBoettcher JuliaSetIterationCount JuliaSetPlot JuliaSetPoints" + "K KagiChart KaiserBesselWindow KaiserWindow KalmanEstimator KalmanFilter KarhunenLoeveDecomposition KaryTree KatzCentrality KCoreComponents KDistribution KEdgeConnectedComponents KEdgeConnectedGraphQ KelvinBei KelvinBer KelvinKei KelvinKer KendallTau KendallTauTest KernelExecute KernelFunction KernelMixtureDistribution Kernels Ket Key KeyCollisionFunction KeyComplement KeyDrop KeyDropFrom KeyExistsQ KeyFreeQ KeyIntersection KeyMap KeyMemberQ KeypointStrength Keys KeySelect KeySort KeySortBy KeyTake KeyUnion KeyValueMap KeyValuePattern Khinchin KillProcess KirchhoffGraph KirchhoffMatrix KleinInvariantJ KnapsackSolve KnightTourGraph KnotData KnownUnitQ KochCurve KolmogorovSmirnovTest KroneckerDelta KroneckerModelDecomposition KroneckerProduct KroneckerSymbol KuiperTest KumaraswamyDistribution Kurtosis KuwaharaFilter KVertexConnectedComponents KVertexConnectedGraphQ" + "LABColor Label Labeled LabeledSlider LabelingFunction LabelingSize LabelStyle LabelVisibility LaguerreL LakeData LambdaComponents LambertW LaminaData LanczosWindow LandauDistribution Language LanguageCategory LanguageData LanguageIdentify LanguageOptions LaplaceDistribution LaplaceTransform Laplacian LaplacianFilter LaplacianGaussianFilter Large Larger Last Latitude LatitudeLongitude LatticeData LatticeReduce Launch LaunchKernels LayeredGraphPlot LayerSizeFunction LayoutInformation LCHColor LCM LeaderSize LeafCount LeapYearQ LearnDistribution LearnedDistribution LearningRate LearningRateMultipliers LeastSquares LeastSquaresFilterKernel Left LeftArrow LeftArrowBar LeftArrowRightArrow LeftDownTeeVector LeftDownVector LeftDownVectorBar LeftRightArrow LeftRightVector LeftTee LeftTeeArrow LeftTeeVector LeftTriangle LeftTriangleBar LeftTriangleEqual LeftUpDownVector LeftUpTeeVector LeftUpVector LeftUpVectorBar LeftVector LeftVectorBar LegendAppearance Legended LegendFunction LegendLabel LegendLayout LegendMargins LegendMarkers LegendMarkerSize LegendreP LegendreQ LegendreType Length LengthWhile LerchPhi Less LessEqual LessEqualGreater LessEqualThan LessFullEqual LessGreater LessLess LessSlantEqual LessThan LessTilde LetterCharacter LetterCounts LetterNumber LetterQ Level LeveneTest LeviCivitaTensor LevyDistribution Lexicographic LibraryDataType LibraryFunction LibraryFunctionError LibraryFunctionInformation LibraryFunctionLoad LibraryFunctionUnload LibraryLoad LibraryUnload LicenseID LiftingFilterData LiftingWaveletTransform LightBlue LightBrown LightCyan Lighter LightGray LightGreen Lighting LightingAngle LightMagenta LightOrange LightPink LightPurple LightRed LightSources LightYellow Likelihood Limit LimitsPositioning LimitsPositioningTokens LindleyDistribution Line Line3DBox Line3DBoxOptions LinearFilter LinearFractionalOptimization LinearFractionalTransform LinearGradientImage LinearizingTransformationData LinearLayer LinearModelFit LinearOffsetFunction LinearOptimization LinearProgramming LinearRecurrence LinearSolve LinearSolveFunction LineBox LineBoxOptions LineBreak LinebreakAdjustments LineBreakChart LinebreakSemicolonWeighting LineBreakWithin LineColor LineGraph LineIndent LineIndentMaxFraction LineIntegralConvolutionPlot LineIntegralConvolutionScale LineLegend LineOpacity LineSpacing LineWrapParts LinkActivate LinkClose LinkConnect LinkConnectedQ LinkCreate LinkError LinkFlush LinkFunction LinkHost LinkInterrupt LinkLaunch LinkMode LinkObject LinkOpen LinkOptions LinkPatterns LinkProtocol LinkRankCentrality LinkRead LinkReadHeld LinkReadyQ Links LinkService LinkWrite LinkWriteHeld LiouvilleLambda List Listable ListAnimate ListContourPlot ListContourPlot3D ListConvolve ListCorrelate ListCurvePathPlot ListDeconvolve ListDensityPlot ListDensityPlot3D Listen ListFormat ListFourierSequenceTransform ListInterpolation ListLineIntegralConvolutionPlot ListLinePlot ListLogLinearPlot ListLogLogPlot ListLogPlot ListPicker ListPickerBox ListPickerBoxBackground ListPickerBoxOptions ListPlay ListPlot ListPlot3D ListPointPlot3D ListPolarPlot ListQ ListSliceContourPlot3D ListSliceDensityPlot3D ListSliceVectorPlot3D ListStepPlot ListStreamDensityPlot ListStreamPlot ListSurfacePlot3D ListVectorDensityPlot ListVectorPlot ListVectorPlot3D ListZTransform Literal LiteralSearch LocalAdaptiveBinarize LocalCache LocalClusteringCoefficient LocalizeDefinitions LocalizeVariables LocalObject LocalObjects LocalResponseNormalizationLayer LocalSubmit LocalSymbol LocalTime LocalTimeZone LocationEquivalenceTest LocationTest Locator LocatorAutoCreate LocatorBox LocatorBoxOptions LocatorCentering LocatorPane LocatorPaneBox LocatorPaneBoxOptions LocatorRegion Locked Log Log10 Log2 LogBarnesG LogGamma LogGammaDistribution LogicalExpand LogIntegral LogisticDistribution LogisticSigmoid LogitModelFit LogLikelihood LogLinearPlot LogLogisticDistribution LogLogPlot LogMultinormalDistribution LogNormalDistribution LogPlot LogRankTest LogSeriesDistribution LongEqual Longest LongestCommonSequence LongestCommonSequencePositions LongestCommonSubsequence LongestCommonSubsequencePositions LongestMatch LongestOrderedSequence LongForm Longitude LongLeftArrow LongLeftRightArrow LongRightArrow LongShortTermMemoryLayer Lookup Loopback LoopFreeGraphQ LossFunction LowerCaseQ LowerLeftArrow LowerRightArrow LowerTriangularize LowerTriangularMatrixQ LowpassFilter LQEstimatorGains LQGRegulator LQOutputRegulatorGains LQRegulatorGains LUBackSubstitution LucasL LuccioSamiComponents LUDecomposition LunarEclipse LUVColor LyapunovSolve LyonsGroupLy" + "MachineID MachineName MachineNumberQ MachinePrecision MacintoshSystemPageSetup Magenta Magnification Magnify MailAddressValidation MailExecute MailFolder MailItem MailReceiverFunction MailResponseFunction MailSearch MailServerConnect MailServerConnection MailSettings MainSolve MaintainDynamicCaches Majority MakeBoxes MakeExpression MakeRules ManagedLibraryExpressionID ManagedLibraryExpressionQ MandelbrotSetBoettcher MandelbrotSetDistance MandelbrotSetIterationCount MandelbrotSetMemberQ MandelbrotSetPlot MangoldtLambda ManhattanDistance Manipulate Manipulator MannedSpaceMissionData MannWhitneyTest MantissaExponent Manual Map MapAll MapAt MapIndexed MAProcess MapThread MarchenkoPasturDistribution MarcumQ MardiaCombinedTest MardiaKurtosisTest MardiaSkewnessTest MarginalDistribution MarkovProcessProperties Masking MatchingDissimilarity MatchLocalNameQ MatchLocalNames MatchQ Material MathematicalFunctionData MathematicaNotation MathieuC MathieuCharacteristicA MathieuCharacteristicB MathieuCharacteristicExponent MathieuCPrime MathieuGroupM11 MathieuGroupM12 MathieuGroupM22 MathieuGroupM23 MathieuGroupM24 MathieuS MathieuSPrime MathMLForm MathMLText Matrices MatrixExp MatrixForm MatrixFunction MatrixLog MatrixNormalDistribution MatrixPlot MatrixPower MatrixPropertyDistribution MatrixQ MatrixRank MatrixTDistribution Max MaxBend MaxCellMeasure MaxColorDistance MaxDetect MaxDuration MaxExtraBandwidths MaxExtraConditions MaxFeatureDisplacement MaxFeatures MaxFilter MaximalBy Maximize MaxItems MaxIterations MaxLimit MaxMemoryUsed MaxMixtureKernels MaxOverlapFraction MaxPlotPoints MaxPoints MaxRecursion MaxStableDistribution MaxStepFraction MaxSteps MaxStepSize MaxTrainingRounds MaxValue MaxwellDistribution MaxWordGap McLaughlinGroupMcL Mean MeanAbsoluteLossLayer MeanAround MeanClusteringCoefficient MeanDegreeConnectivity MeanDeviation MeanFilter MeanGraphDistance MeanNeighborDegree MeanShift MeanShiftFilter MeanSquaredLossLayer Median MedianDeviation MedianFilter MedicalTestData Medium MeijerG MeijerGReduce MeixnerDistribution MellinConvolve MellinTransform MemberQ MemoryAvailable MemoryConstrained MemoryConstraint MemoryInUse MengerMesh Menu MenuAppearance MenuCommandKey MenuEvaluator MenuItem MenuList MenuPacket MenuSortingValue MenuStyle MenuView Merge MergeDifferences MergingFunction MersennePrimeExponent MersennePrimeExponentQ Mesh MeshCellCentroid MeshCellCount MeshCellHighlight MeshCellIndex MeshCellLabel MeshCellMarker MeshCellMeasure MeshCellQuality MeshCells MeshCellShapeFunction MeshCellStyle MeshCoordinates MeshFunctions MeshPrimitives MeshQualityGoal MeshRange MeshRefinementFunction MeshRegion MeshRegionQ MeshShading MeshStyle Message MessageDialog MessageList MessageName MessageObject MessageOptions MessagePacket Messages MessagesNotebook MetaCharacters MetaInformation MeteorShowerData Method MethodOptions MexicanHatWavelet MeyerWavelet Midpoint Min MinColorDistance MinDetect MineralData MinFilter MinimalBy MinimalPolynomial MinimalStateSpaceModel Minimize MinimumTimeIncrement MinIntervalSize MinkowskiQuestionMark MinLimit MinMax MinorPlanetData Minors MinRecursion MinSize MinStableDistribution Minus MinusPlus MinValue Missing MissingBehavior MissingDataMethod MissingDataRules MissingQ MissingString MissingStyle MissingValuePattern MittagLefflerE MixedFractionParts MixedGraphQ MixedMagnitude MixedRadix MixedRadixQuantity MixedUnit MixtureDistribution Mod Modal Mode Modular ModularInverse ModularLambda Module Modulus MoebiusMu Molecule MoleculeContainsQ MoleculeEquivalentQ MoleculeGraph MoleculeModify MoleculePattern MoleculePlot MoleculePlot3D MoleculeProperty MoleculeQ MoleculeValue Moment Momentary MomentConvert MomentEvaluate MomentGeneratingFunction MomentOfInertia Monday Monitor MonomialList MonomialOrder MonsterGroupM MoonPhase MoonPosition MorletWavelet MorphologicalBinarize MorphologicalBranchPoints MorphologicalComponents MorphologicalEulerNumber MorphologicalGraph MorphologicalPerimeter MorphologicalTransform MortalityData Most MountainData MouseAnnotation MouseAppearance MouseAppearanceTag MouseButtons Mouseover MousePointerNote MousePosition MovieData MovingAverage MovingMap MovingMedian MoyalDistribution Multicolumn MultiedgeStyle MultigraphQ MultilaunchWarning MultiLetterItalics MultiLetterStyle MultilineFunction Multinomial MultinomialDistribution MultinormalDistribution MultiplicativeOrder Multiplicity MultiplySides Multiselection MultivariateHypergeometricDistribution MultivariatePoissonDistribution MultivariateTDistribution" + "N NakagamiDistribution NameQ Names NamespaceBox NamespaceBoxOptions Nand NArgMax NArgMin NBernoulliB NBodySimulation NBodySimulationData NCache NDEigensystem NDEigenvalues NDSolve NDSolveValue Nearest NearestFunction NearestNeighborGraph NearestTo NebulaData NeedCurrentFrontEndPackagePacket NeedCurrentFrontEndSymbolsPacket NeedlemanWunschSimilarity Needs Negative NegativeBinomialDistribution NegativeDefiniteMatrixQ NegativeIntegers NegativeMultinomialDistribution NegativeRationals NegativeReals NegativeSemidefiniteMatrixQ NeighborhoodData NeighborhoodGraph Nest NestedGreaterGreater NestedLessLess NestedScriptRules NestGraph NestList NestWhile NestWhileList NetAppend NetBidirectionalOperator NetChain NetDecoder NetDelete NetDrop NetEncoder NetEvaluationMode NetExtract NetFlatten NetFoldOperator NetGraph NetInformation NetInitialize NetInsert NetInsertSharedArrays NetJoin NetMapOperator NetMapThreadOperator NetMeasurements NetModel NetNestOperator NetPairEmbeddingOperator NetPort NetPortGradient NetPrepend NetRename NetReplace NetReplacePart NetSharedArray NetStateObject NetTake NetTrain NetTrainResultsObject NetworkPacketCapture NetworkPacketRecording NetworkPacketRecordingDuring NetworkPacketTrace NeumannValue NevilleThetaC NevilleThetaD NevilleThetaN NevilleThetaS NewPrimitiveStyle NExpectation Next NextCell NextDate NextPrime NextScheduledTaskTime NHoldAll NHoldFirst NHoldRest NicholsGridLines NicholsPlot NightHemisphere NIntegrate NMaximize NMaxValue NMinimize NMinValue NominalVariables NonAssociative NoncentralBetaDistribution NoncentralChiSquareDistribution NoncentralFRatioDistribution NoncentralStudentTDistribution NonCommutativeMultiply NonConstants NondimensionalizationTransform None NoneTrue NonlinearModelFit NonlinearStateSpaceModel NonlocalMeansFilter NonNegative NonNegativeIntegers NonNegativeRationals NonNegativeReals NonPositive NonPositiveIntegers NonPositiveRationals NonPositiveReals Nor NorlundB Norm Normal NormalDistribution NormalGrouping NormalizationLayer Normalize Normalized NormalizedSquaredEuclideanDistance NormalMatrixQ NormalsFunction NormFunction Not NotCongruent NotCupCap NotDoubleVerticalBar Notebook NotebookApply NotebookAutoSave NotebookClose NotebookConvertSettings NotebookCreate NotebookCreateReturnObject NotebookDefault NotebookDelete NotebookDirectory NotebookDynamicExpression NotebookEvaluate NotebookEventActions NotebookFileName NotebookFind NotebookFindReturnObject NotebookGet NotebookGetLayoutInformationPacket NotebookGetMisspellingsPacket NotebookImport NotebookInformation NotebookInterfaceObject NotebookLocate NotebookObject NotebookOpen NotebookOpenReturnObject NotebookPath NotebookPrint NotebookPut NotebookPutReturnObject NotebookRead NotebookResetGeneratedCells Notebooks NotebookSave NotebookSaveAs NotebookSelection NotebookSetupLayoutInformationPacket NotebooksMenu NotebookTemplate NotebookWrite NotElement NotEqualTilde NotExists NotGreater NotGreaterEqual NotGreaterFullEqual NotGreaterGreater NotGreaterLess NotGreaterSlantEqual NotGreaterTilde Nothing NotHumpDownHump NotHumpEqual NotificationFunction NotLeftTriangle NotLeftTriangleBar NotLeftTriangleEqual NotLess NotLessEqual NotLessFullEqual NotLessGreater NotLessLess NotLessSlantEqual NotLessTilde NotNestedGreaterGreater NotNestedLessLess NotPrecedes NotPrecedesEqual NotPrecedesSlantEqual NotPrecedesTilde NotReverseElement NotRightTriangle NotRightTriangleBar NotRightTriangleEqual NotSquareSubset NotSquareSubsetEqual NotSquareSuperset NotSquareSupersetEqual NotSubset NotSubsetEqual NotSucceeds NotSucceedsEqual NotSucceedsSlantEqual NotSucceedsTilde NotSuperset NotSupersetEqual NotTilde NotTildeEqual NotTildeFullEqual NotTildeTilde NotVerticalBar Now NoWhitespace NProbability NProduct NProductFactors NRoots NSolve NSum NSumTerms NuclearExplosionData NuclearReactorData Null NullRecords NullSpace NullWords Number NumberCompose NumberDecompose NumberExpand NumberFieldClassNumber NumberFieldDiscriminant NumberFieldFundamentalUnits NumberFieldIntegralBasis NumberFieldNormRepresentatives NumberFieldRegulator NumberFieldRootsOfUnity NumberFieldSignature NumberForm NumberFormat NumberLinePlot NumberMarks NumberMultiplier NumberPadding NumberPoint NumberQ NumberSeparator NumberSigns NumberString Numerator NumeratorDenominator NumericalOrder NumericalSort NumericArray NumericArrayQ NumericArrayType NumericFunction NumericQ NuttallWindow NValues NyquistGridLines NyquistPlot" + "O ObservabilityGramian ObservabilityMatrix ObservableDecomposition ObservableModelQ OceanData Octahedron OddQ Off Offset OLEData On ONanGroupON Once OneIdentity Opacity OpacityFunction OpacityFunctionScaling Open OpenAppend Opener OpenerBox OpenerBoxOptions OpenerView OpenFunctionInspectorPacket Opening OpenRead OpenSpecialOptions OpenTemporary OpenWrite Operate OperatingSystem OptimumFlowData Optional OptionalElement OptionInspectorSettings OptionQ Options OptionsPacket OptionsPattern OptionValue OptionValueBox OptionValueBoxOptions Or Orange Order OrderDistribution OrderedQ Ordering OrderingBy OrderingLayer Orderless OrderlessPatternSequence OrnsteinUhlenbeckProcess Orthogonalize OrthogonalMatrixQ Out Outer OuterPolygon OuterPolyhedron OutputAutoOverwrite OutputControllabilityMatrix OutputControllableModelQ OutputForm OutputFormData OutputGrouping OutputMathEditExpression OutputNamePacket OutputResponse OutputSizeLimit OutputStream Over OverBar OverDot Overflow OverHat Overlaps Overlay OverlayBox OverlayBoxOptions Overscript OverscriptBox OverscriptBoxOptions OverTilde OverVector OverwriteTarget OwenT OwnValues" + "Package PackingMethod PaddedForm Padding PaddingLayer PaddingSize PadeApproximant PadLeft PadRight PageBreakAbove PageBreakBelow PageBreakWithin PageFooterLines PageFooters PageHeaderLines PageHeaders PageHeight PageRankCentrality PageTheme PageWidth Pagination PairedBarChart PairedHistogram PairedSmoothHistogram PairedTTest PairedZTest PaletteNotebook PalettePath PalindromeQ Pane PaneBox PaneBoxOptions Panel PanelBox PanelBoxOptions Paneled PaneSelector PaneSelectorBox PaneSelectorBoxOptions PaperWidth ParabolicCylinderD ParagraphIndent ParagraphSpacing ParallelArray ParallelCombine ParallelDo Parallelepiped ParallelEvaluate Parallelization Parallelize ParallelMap ParallelNeeds Parallelogram ParallelProduct ParallelSubmit ParallelSum ParallelTable ParallelTry Parameter ParameterEstimator ParameterMixtureDistribution ParameterVariables ParametricFunction ParametricNDSolve ParametricNDSolveValue ParametricPlot ParametricPlot3D ParametricRegion ParentBox ParentCell ParentConnect ParentDirectory ParentForm Parenthesize ParentList ParentNotebook ParetoDistribution ParetoPickandsDistribution ParkData Part PartBehavior PartialCorrelationFunction PartialD ParticleAcceleratorData ParticleData Partition PartitionGranularity PartitionsP PartitionsQ PartLayer PartOfSpeech PartProtection ParzenWindow PascalDistribution PassEventsDown PassEventsUp Paste PasteAutoQuoteCharacters PasteBoxFormInlineCells PasteButton Path PathGraph PathGraphQ Pattern PatternSequence PatternTest PauliMatrix PaulWavelet Pause PausedTime PDF PeakDetect PeanoCurve PearsonChiSquareTest PearsonCorrelationTest PearsonDistribution PercentForm PerfectNumber PerfectNumberQ PerformanceGoal Perimeter PeriodicBoundaryCondition PeriodicInterpolation Periodogram PeriodogramArray Permanent Permissions PermissionsGroup PermissionsGroupMemberQ PermissionsGroups PermissionsKey PermissionsKeys PermutationCycles PermutationCyclesQ PermutationGroup PermutationLength PermutationList PermutationListQ PermutationMax PermutationMin PermutationOrder PermutationPower PermutationProduct PermutationReplace Permutations PermutationSupport Permute PeronaMalikFilter Perpendicular PerpendicularBisector PersistenceLocation PersistenceTime PersistentObject PersistentObjects PersistentValue PersonData PERTDistribution PetersenGraph PhaseMargins PhaseRange PhysicalSystemData Pi Pick PIDData PIDDerivativeFilter PIDFeedforward PIDTune Piecewise PiecewiseExpand PieChart PieChart3D PillaiTrace PillaiTraceTest PingTime Pink PitchRecognize Pivoting PixelConstrained PixelValue PixelValuePositions Placed Placeholder PlaceholderReplace Plain PlanarAngle PlanarGraph PlanarGraphQ PlanckRadiationLaw PlaneCurveData PlanetaryMoonData PlanetData PlantData Play PlayRange Plot Plot3D Plot3Matrix PlotDivision PlotJoined PlotLabel PlotLabels PlotLayout PlotLegends PlotMarkers PlotPoints PlotRange PlotRangeClipping PlotRangeClipPlanesStyle PlotRangePadding PlotRegion PlotStyle PlotTheme Pluralize Plus PlusMinus Pochhammer PodStates PodWidth Point Point3DBox Point3DBoxOptions PointBox PointBoxOptions PointFigureChart PointLegend PointSize PoissonConsulDistribution PoissonDistribution PoissonProcess PoissonWindow PolarAxes PolarAxesOrigin PolarGridLines PolarPlot PolarTicks PoleZeroMarkers PolyaAeppliDistribution PolyGamma Polygon Polygon3DBox Polygon3DBoxOptions PolygonalNumber PolygonAngle PolygonBox PolygonBoxOptions PolygonCoordinates PolygonDecomposition PolygonHoleScale PolygonIntersections PolygonScale Polyhedron PolyhedronAngle PolyhedronCoordinates PolyhedronData PolyhedronDecomposition PolyhedronGenus PolyLog PolynomialExtendedGCD PolynomialForm PolynomialGCD PolynomialLCM PolynomialMod PolynomialQ PolynomialQuotient PolynomialQuotientRemainder PolynomialReduce PolynomialRemainder Polynomials PoolingLayer PopupMenu PopupMenuBox PopupMenuBoxOptions PopupView PopupWindow Position PositionIndex Positive PositiveDefiniteMatrixQ PositiveIntegers PositiveRationals PositiveReals PositiveSemidefiniteMatrixQ PossibleZeroQ Postfix PostScript Power PowerDistribution PowerExpand PowerMod PowerModList PowerRange PowerSpectralDensity PowersRepresentations PowerSymmetricPolynomial Precedence PrecedenceForm Precedes PrecedesEqual PrecedesSlantEqual PrecedesTilde Precision PrecisionGoal PreDecrement Predict PredictionRoot PredictorFunction PredictorInformation PredictorMeasurements PredictorMeasurementsObject PreemptProtect PreferencesPath Prefix PreIncrement Prepend PrependLayer PrependTo PreprocessingRules PreserveColor PreserveImageOptions Previous PreviousCell PreviousDate PriceGraphDistribution PrimaryPlaceholder Prime PrimeNu PrimeOmega PrimePi PrimePowerQ PrimeQ Primes PrimeZetaP PrimitivePolynomialQ PrimitiveRoot PrimitiveRootList PrincipalComponents PrincipalValue Print PrintableASCIIQ PrintAction PrintForm PrintingCopies PrintingOptions PrintingPageRange PrintingStartingPageNumber PrintingStyleEnvironment Printout3D Printout3DPreviewer PrintPrecision PrintTemporary Prism PrismBox PrismBoxOptions PrivateCellOptions PrivateEvaluationOptions PrivateFontOptions PrivateFrontEndOptions PrivateKey PrivateNotebookOptions PrivatePaths Probability ProbabilityDistribution ProbabilityPlot ProbabilityPr ProbabilityScalePlot ProbitModelFit ProcessConnection ProcessDirectory ProcessEnvironment Processes ProcessEstimator ProcessInformation ProcessObject ProcessParameterAssumptions ProcessParameterQ ProcessStateDomain ProcessStatus ProcessTimeDomain Product ProductDistribution ProductLog ProgressIndicator ProgressIndicatorBox ProgressIndicatorBoxOptions Projection Prolog PromptForm ProofObject Properties Property PropertyList PropertyValue Proportion Proportional Protect Protected ProteinData Pruning PseudoInverse PsychrometricPropertyData PublicKey PublisherID PulsarData PunctuationCharacter Purple Put PutAppend Pyramid PyramidBox PyramidBoxOptions" + "QBinomial QFactorial QGamma QHypergeometricPFQ QnDispersion QPochhammer QPolyGamma QRDecomposition QuadraticIrrationalQ QuadraticOptimization Quantile QuantilePlot Quantity QuantityArray QuantityDistribution QuantityForm QuantityMagnitude QuantityQ QuantityUnit QuantityVariable QuantityVariableCanonicalUnit QuantityVariableDimensions QuantityVariableIdentifier QuantityVariablePhysicalQuantity Quartics QuartileDeviation Quartiles QuartileSkewness Query QueueingNetworkProcess QueueingProcess QueueProperties Quiet Quit Quotient QuotientRemainder" + "RadialGradientImage RadialityCentrality RadicalBox RadicalBoxOptions RadioButton RadioButtonBar RadioButtonBox RadioButtonBoxOptions Radon RadonTransform RamanujanTau RamanujanTauL RamanujanTauTheta RamanujanTauZ Ramp Random RandomChoice RandomColor RandomComplex RandomEntity RandomFunction RandomGeoPosition RandomGraph RandomImage RandomInstance RandomInteger RandomPermutation RandomPoint RandomPolygon RandomPolyhedron RandomPrime RandomReal RandomSample RandomSeed RandomSeeding RandomVariate RandomWalkProcess RandomWord Range RangeFilter RangeSpecification RankedMax RankedMin RarerProbability Raster Raster3D Raster3DBox Raster3DBoxOptions RasterArray RasterBox RasterBoxOptions Rasterize RasterSize Rational RationalFunctions Rationalize Rationals Ratios RawArray RawBoxes RawData RawMedium RayleighDistribution Re Read ReadByteArray ReadLine ReadList ReadProtected ReadString Real RealAbs RealBlockDiagonalForm RealDigits RealExponent Reals RealSign Reap RecognitionPrior RecognitionThreshold Record RecordLists RecordSeparators Rectangle RectangleBox RectangleBoxOptions RectangleChart RectangleChart3D RectangularRepeatingElement RecurrenceFilter RecurrenceTable RecurringDigitsForm Red Reduce RefBox ReferenceLineStyle ReferenceMarkers ReferenceMarkerStyle Refine ReflectionMatrix ReflectionTransform Refresh RefreshRate Region RegionBinarize RegionBoundary RegionBounds RegionCentroid RegionDifference RegionDimension RegionDisjoint RegionDistance RegionDistanceFunction RegionEmbeddingDimension RegionEqual RegionFunction RegionImage RegionIntersection RegionMeasure RegionMember RegionMemberFunction RegionMoment RegionNearest RegionNearestFunction RegionPlot RegionPlot3D RegionProduct RegionQ RegionResize RegionSize RegionSymmetricDifference RegionUnion RegionWithin RegisterExternalEvaluator RegularExpression Regularization RegularlySampledQ RegularPolygon ReIm ReImLabels ReImPlot ReImStyle Reinstall RelationalDatabase RelationGraph Release ReleaseHold ReliabilityDistribution ReliefImage ReliefPlot RemoteAuthorizationCaching RemoteConnect RemoteConnectionObject RemoteFile RemoteRun RemoteRunProcess Remove RemoveAlphaChannel RemoveAsynchronousTask RemoveAudioStream RemoveBackground RemoveChannelListener RemoveChannelSubscribers Removed RemoveDiacritics RemoveInputStreamMethod RemoveOutputStreamMethod RemoveProperty RemoveScheduledTask RemoveUsers RenameDirectory RenameFile RenderAll RenderingOptions RenewalProcess RenkoChart RepairMesh Repeated RepeatedNull RepeatedString RepeatedTiming RepeatingElement Replace ReplaceAll ReplaceHeldPart ReplaceImageValue ReplaceList ReplacePart ReplacePixelValue ReplaceRepeated ReplicateLayer RequiredPhysicalQuantities Resampling ResamplingAlgorithmData ResamplingMethod Rescale RescalingTransform ResetDirectory ResetMenusPacket ResetScheduledTask ReshapeLayer Residue ResizeLayer Resolve ResourceAcquire ResourceData ResourceFunction ResourceObject ResourceRegister ResourceRemove ResourceSearch ResourceSubmissionObject ResourceSubmit ResourceSystemBase ResourceUpdate ResponseForm Rest RestartInterval Restricted Resultant ResumePacket Return ReturnEntersInput ReturnExpressionPacket ReturnInputFormPacket ReturnPacket ReturnReceiptFunction ReturnTextPacket Reverse ReverseBiorthogonalSplineWavelet ReverseElement ReverseEquilibrium ReverseGraph ReverseSort ReverseSortBy ReverseUpEquilibrium RevolutionAxis RevolutionPlot3D RGBColor RiccatiSolve RiceDistribution RidgeFilter RiemannR RiemannSiegelTheta RiemannSiegelZ RiemannXi Riffle Right RightArrow RightArrowBar RightArrowLeftArrow RightComposition RightCosetRepresentative RightDownTeeVector RightDownVector RightDownVectorBar RightTee RightTeeArrow RightTeeVector RightTriangle RightTriangleBar RightTriangleEqual RightUpDownVector RightUpTeeVector RightUpVector RightUpVectorBar RightVector RightVectorBar RiskAchievementImportance RiskReductionImportance RogersTanimotoDissimilarity RollPitchYawAngles RollPitchYawMatrix RomanNumeral Root RootApproximant RootIntervals RootLocusPlot RootMeanSquare RootOfUnityQ RootReduce Roots RootSum Rotate RotateLabel RotateLeft RotateRight RotationAction RotationBox RotationBoxOptions RotationMatrix RotationTransform Round RoundImplies RoundingRadius Row RowAlignments RowBackgrounds RowBox RowHeights RowLines RowMinHeight RowReduce RowsEqual RowSpacings RSolve RSolveValue RudinShapiro RudvalisGroupRu Rule RuleCondition RuleDelayed RuleForm RulePlot RulerUnits Run RunProcess RunScheduledTask RunThrough RuntimeAttributes RuntimeOptions RussellRaoDissimilarity" + "SameQ SameTest SampledEntityClass SampleDepth SampledSoundFunction SampledSoundList SampleRate SamplingPeriod SARIMAProcess SARMAProcess SASTriangle SatelliteData SatisfiabilityCount SatisfiabilityInstances SatisfiableQ Saturday Save Saveable SaveAutoDelete SaveConnection SaveDefinitions SavitzkyGolayMatrix SawtoothWave Scale Scaled ScaleDivisions ScaledMousePosition ScaleOrigin ScalePadding ScaleRanges ScaleRangeStyle ScalingFunctions ScalingMatrix ScalingTransform Scan ScheduledTask ScheduledTaskActiveQ ScheduledTaskInformation ScheduledTaskInformationData ScheduledTaskObject ScheduledTasks SchurDecomposition ScientificForm ScientificNotationThreshold ScorerGi ScorerGiPrime ScorerHi ScorerHiPrime ScreenRectangle ScreenStyleEnvironment ScriptBaselineShifts ScriptForm ScriptLevel ScriptMinSize ScriptRules ScriptSizeMultipliers Scrollbars ScrollingOptions ScrollPosition SearchAdjustment SearchIndexObject SearchIndices SearchQueryString SearchResultObject Sec Sech SechDistribution SecondOrderConeOptimization SectionGrouping SectorChart SectorChart3D SectorOrigin SectorSpacing SecuredAuthenticationKey SecuredAuthenticationKeys SeedRandom Select Selectable SelectComponents SelectedCells SelectedNotebook SelectFirst Selection SelectionAnimate SelectionCell SelectionCellCreateCell SelectionCellDefaultStyle SelectionCellParentStyle SelectionCreateCell SelectionDebuggerTag SelectionDuplicateCell SelectionEvaluate SelectionEvaluateCreateCell SelectionMove SelectionPlaceholder SelectionSetStyle SelectWithContents SelfLoops SelfLoopStyle SemanticImport SemanticImportString SemanticInterpretation SemialgebraicComponentInstances SemidefiniteOptimization SendMail SendMessage Sequence SequenceAlignment SequenceAttentionLayer SequenceCases SequenceCount SequenceFold SequenceFoldList SequenceForm SequenceHold SequenceLastLayer SequenceMostLayer SequencePosition SequencePredict SequencePredictorFunction SequenceReplace SequenceRestLayer SequenceReverseLayer SequenceSplit Series SeriesCoefficient SeriesData ServiceConnect ServiceDisconnect ServiceExecute ServiceObject ServiceRequest ServiceResponse ServiceSubmit SessionSubmit SessionTime Set SetAccuracy SetAlphaChannel SetAttributes Setbacks SetBoxFormNamesPacket SetCloudDirectory SetCookies SetDelayed SetDirectory SetEnvironment SetEvaluationNotebook SetFileDate SetFileLoadingContext SetNotebookStatusLine SetOptions SetOptionsPacket SetPermissions SetPrecision SetProperty SetSecuredAuthenticationKey SetSelectedNotebook SetSharedFunction SetSharedVariable SetSpeechParametersPacket SetStreamPosition SetSystemModel SetSystemOptions Setter SetterBar SetterBox SetterBoxOptions Setting SetUsers SetValue Shading Shallow ShannonWavelet ShapiroWilkTest Share SharingList Sharpen ShearingMatrix ShearingTransform ShellRegion ShenCastanMatrix ShiftedGompertzDistribution ShiftRegisterSequence Short ShortDownArrow Shortest ShortestMatch ShortestPathFunction ShortLeftArrow ShortRightArrow ShortTimeFourier ShortTimeFourierData ShortUpArrow Show ShowAutoConvert ShowAutoSpellCheck ShowAutoStyles ShowCellBracket ShowCellLabel ShowCellTags ShowClosedCellArea ShowCodeAssist ShowContents ShowControls ShowCursorTracker ShowGroupOpenCloseIcon ShowGroupOpener ShowInvisibleCharacters ShowPageBreaks ShowPredictiveInterface ShowSelection ShowShortBoxForm ShowSpecialCharacters ShowStringCharacters ShowSyntaxStyles ShrinkingDelay ShrinkWrapBoundingBox SiderealTime SiegelTheta SiegelTukeyTest SierpinskiCurve SierpinskiMesh Sign Signature SignedRankTest SignedRegionDistance SignificanceLevel SignPadding SignTest SimilarityRules SimpleGraph SimpleGraphQ SimplePolygonQ SimplePolyhedronQ Simplex Simplify Sin Sinc SinghMaddalaDistribution SingleEvaluation SingleLetterItalics SingleLetterStyle SingularValueDecomposition SingularValueList SingularValuePlot SingularValues Sinh SinhIntegral SinIntegral SixJSymbol Skeleton SkeletonTransform SkellamDistribution Skewness SkewNormalDistribution SkinStyle Skip SliceContourPlot3D SliceDensityPlot3D SliceDistribution SliceVectorPlot3D Slider Slider2D Slider2DBox Slider2DBoxOptions SliderBox SliderBoxOptions SlideView Slot SlotSequence Small SmallCircle Smaller SmithDecomposition SmithDelayCompensator SmithWatermanSimilarity SmoothDensityHistogram SmoothHistogram SmoothHistogram3D SmoothKernelDistribution SnDispersion Snippet SnubPolyhedron SocialMediaData Socket SocketConnect SocketListen SocketListener SocketObject SocketOpen SocketReadMessage SocketReadyQ Sockets SocketWaitAll SocketWaitNext SoftmaxLayer SokalSneathDissimilarity SolarEclipse SolarSystemFeatureData SolidAngle SolidData SolidRegionQ Solve SolveAlways SolveDelayed Sort SortBy SortedBy SortedEntityClass Sound SoundAndGraphics SoundNote SoundVolume SourceLink Sow Space SpaceCurveData SpaceForm Spacer Spacings Span SpanAdjustments SpanCharacterRounding SpanFromAbove SpanFromBoth SpanFromLeft SpanLineThickness SpanMaxSize SpanMinSize SpanningCharacters SpanSymmetric SparseArray SpatialGraphDistribution SpatialMedian SpatialTransformationLayer Speak SpeakTextPacket SpearmanRankTest SpearmanRho SpeciesData SpecificityGoal SpectralLineData Spectrogram SpectrogramArray Specularity SpeechRecognize SpeechSynthesize SpellingCorrection SpellingCorrectionList SpellingDictionaries SpellingDictionariesPath SpellingOptions SpellingSuggestionsPacket Sphere SphereBox SpherePoints SphericalBesselJ SphericalBesselY SphericalHankelH1 SphericalHankelH2 SphericalHarmonicY SphericalPlot3D SphericalRegion SphericalShell SpheroidalEigenvalue SpheroidalJoiningFactor SpheroidalPS SpheroidalPSPrime SpheroidalQS SpheroidalQSPrime SpheroidalRadialFactor SpheroidalS1 SpheroidalS1Prime SpheroidalS2 SpheroidalS2Prime Splice SplicedDistribution SplineClosed SplineDegree SplineKnots SplineWeights Split SplitBy SpokenString Sqrt SqrtBox SqrtBoxOptions Square SquaredEuclideanDistance SquareFreeQ SquareIntersection SquareMatrixQ SquareRepeatingElement SquaresR SquareSubset SquareSubsetEqual SquareSuperset SquareSupersetEqual SquareUnion SquareWave SSSTriangle StabilityMargins StabilityMarginsStyle StableDistribution Stack StackBegin StackComplete StackedDateListPlot StackedListPlot StackInhibit StadiumShape StandardAtmosphereData StandardDeviation StandardDeviationFilter StandardForm Standardize Standardized StandardOceanData StandbyDistribution Star StarClusterData StarData StarGraph StartAsynchronousTask StartExternalSession StartingStepSize StartOfLine StartOfString StartProcess StartScheduledTask StartupSound StartWebSession StateDimensions StateFeedbackGains StateOutputEstimator StateResponse StateSpaceModel StateSpaceRealization StateSpaceTransform StateTransformationLinearize StationaryDistribution StationaryWaveletPacketTransform StationaryWaveletTransform StatusArea StatusCentrality StepMonitor StereochemistryElements StieltjesGamma StirlingS1 StirlingS2 StopAsynchronousTask StoppingPowerData StopScheduledTask StrataVariables StratonovichProcess StreamColorFunction StreamColorFunctionScaling StreamDensityPlot StreamMarkers StreamPlot StreamPoints StreamPosition Streams StreamScale StreamStyle String StringBreak StringByteCount StringCases StringContainsQ StringCount StringDelete StringDrop StringEndsQ StringExpression StringExtract StringForm StringFormat StringFreeQ StringInsert StringJoin StringLength StringMatchQ StringPadLeft StringPadRight StringPart StringPartition StringPosition StringQ StringRepeat StringReplace StringReplaceList StringReplacePart StringReverse StringRiffle StringRotateLeft StringRotateRight StringSkeleton StringSplit StringStartsQ StringTake StringTemplate StringToByteArray StringToStream StringTrim StripBoxes StripOnInput StripWrapperBoxes StrokeForm StructuralImportance StructuredArray StructuredSelection StruveH StruveL Stub StudentTDistribution Style StyleBox StyleBoxAutoDelete StyleData StyleDefinitions StyleForm StyleHints StyleKeyMapping StyleMenuListing StyleNameDialogSettings StyleNames StylePrint StyleSheetPath Subdivide Subfactorial Subgraph SubMinus SubPlus SubresultantPolynomialRemainders SubresultantPolynomials Subresultants Subscript SubscriptBox SubscriptBoxOptions Subscripted Subsequences Subset SubsetEqual SubsetMap SubsetQ Subsets SubStar SubstitutionSystem Subsuperscript SubsuperscriptBox SubsuperscriptBoxOptions Subtract SubtractFrom SubtractSides SubValues Succeeds SucceedsEqual SucceedsSlantEqual SucceedsTilde Success SuchThat Sum SumConvergence SummationLayer Sunday SunPosition Sunrise Sunset SuperDagger SuperMinus SupernovaData SuperPlus Superscript SuperscriptBox SuperscriptBoxOptions Superset SupersetEqual SuperStar Surd SurdForm SurfaceArea SurfaceColor SurfaceData SurfaceGraphics SurvivalDistribution SurvivalFunction SurvivalModel SurvivalModelFit SuspendPacket SuzukiDistribution SuzukiGroupSuz SwatchLegend Switch Symbol SymbolName SymletWavelet Symmetric SymmetricGroup SymmetricKey SymmetricMatrixQ SymmetricPolynomial SymmetricReduction Symmetrize SymmetrizedArray SymmetrizedArrayRules SymmetrizedDependentComponents SymmetrizedIndependentComponents SymmetrizedReplacePart SynchronousInitialization SynchronousUpdating Synonyms Syntax SyntaxForm SyntaxInformation SyntaxLength SyntaxPacket SyntaxQ SynthesizeMissingValues SystemDialogInput SystemException SystemGet SystemHelpPath SystemInformation SystemInformationData SystemInstall SystemModel SystemModeler SystemModelExamples SystemModelLinearize SystemModelParametricSimulate SystemModelPlot SystemModelProgressReporting SystemModelReliability SystemModels SystemModelSimulate SystemModelSimulateSensitivity SystemModelSimulationData SystemOpen SystemOptions SystemProcessData SystemProcesses SystemsConnectionsModel SystemsModelDelay SystemsModelDelayApproximate SystemsModelDelete SystemsModelDimensions SystemsModelExtract SystemsModelFeedbackConnect SystemsModelLabels SystemsModelLinearity SystemsModelMerge SystemsModelOrder SystemsModelParallelConnect SystemsModelSeriesConnect SystemsModelStateFeedbackConnect SystemsModelVectorRelativeOrders SystemStub SystemTest" + "Tab TabFilling Table TableAlignments TableDepth TableDirections TableForm TableHeadings TableSpacing TableView TableViewBox TableViewBoxBackground TableViewBoxOptions TabSpacings TabView TabViewBox TabViewBoxOptions TagBox TagBoxNote TagBoxOptions TaggingRules TagSet TagSetDelayed TagStyle TagUnset Take TakeDrop TakeLargest TakeLargestBy TakeList TakeSmallest TakeSmallestBy TakeWhile Tally Tan Tanh TargetDevice TargetFunctions TargetSystem TargetUnits TaskAbort TaskExecute TaskObject TaskRemove TaskResume Tasks TaskSuspend TaskWait TautologyQ TelegraphProcess TemplateApply TemplateArgBox TemplateBox TemplateBoxOptions TemplateEvaluate TemplateExpression TemplateIf TemplateObject TemplateSequence TemplateSlot TemplateSlotSequence TemplateUnevaluated TemplateVerbatim TemplateWith TemporalData TemporalRegularity Temporary TemporaryVariable TensorContract TensorDimensions TensorExpand TensorProduct TensorQ TensorRank TensorReduce TensorSymmetry TensorTranspose TensorWedge TestID TestReport TestReportObject TestResultObject Tetrahedron TetrahedronBox TetrahedronBoxOptions TeXForm TeXSave Text Text3DBox Text3DBoxOptions TextAlignment TextBand TextBoundingBox TextBox TextCases TextCell TextClipboardType TextContents TextData TextElement TextForm TextGrid TextJustification TextLine TextPacket TextParagraph TextPosition TextRecognize TextSearch TextSearchReport TextSentences TextString TextStructure TextStyle TextTranslation Texture TextureCoordinateFunction TextureCoordinateScaling TextWords Therefore ThermodynamicData ThermometerGauge Thick Thickness Thin Thinning ThisLink ThompsonGroupTh Thread ThreadingLayer ThreeJSymbol Threshold Through Throw ThueMorse Thumbnail Thursday Ticks TicksStyle TideData Tilde TildeEqual TildeFullEqual TildeTilde TimeConstrained TimeConstraint TimeDirection TimeFormat TimeGoal TimelinePlot TimeObject TimeObjectQ Times TimesBy TimeSeries TimeSeriesAggregate TimeSeriesForecast TimeSeriesInsert TimeSeriesInvertibility TimeSeriesMap TimeSeriesMapThread TimeSeriesModel TimeSeriesModelFit TimeSeriesResample TimeSeriesRescale TimeSeriesShift TimeSeriesThread TimeSeriesWindow TimeUsed TimeValue TimeWarpingCorrespondence TimeWarpingDistance TimeZone TimeZoneConvert TimeZoneOffset Timing Tiny TitleGrouping TitsGroupT ToBoxes ToCharacterCode ToColor ToContinuousTimeModel ToDate Today ToDiscreteTimeModel ToEntity ToeplitzMatrix ToExpression ToFileName Together Toggle ToggleFalse Toggler TogglerBar TogglerBox TogglerBoxOptions ToHeldExpression ToInvertibleTimeSeries TokenWords Tolerance ToLowerCase Tomorrow ToNumberField TooBig Tooltip TooltipBox TooltipBoxOptions TooltipDelay TooltipStyle Top TopHatTransform ToPolarCoordinates TopologicalSort ToRadicals ToRules ToSphericalCoordinates ToString Total TotalHeight TotalLayer TotalVariationFilter TotalWidth TouchPosition TouchscreenAutoZoom TouchscreenControlPlacement ToUpperCase Tr Trace TraceAbove TraceAction TraceBackward TraceDepth TraceDialog TraceForward TraceInternal TraceLevel TraceOff TraceOn TraceOriginal TracePrint TraceScan TrackedSymbols TrackingFunction TracyWidomDistribution TradingChart TraditionalForm TraditionalFunctionNotation TraditionalNotation TraditionalOrder TrainingProgressCheckpointing TrainingProgressFunction TrainingProgressMeasurements TrainingProgressReporting TrainingStoppingCriterion TransferFunctionCancel TransferFunctionExpand TransferFunctionFactor TransferFunctionModel TransferFunctionPoles TransferFunctionTransform TransferFunctionZeros TransformationClass TransformationFunction TransformationFunctions TransformationMatrix TransformedDistribution TransformedField TransformedProcess TransformedRegion TransitionDirection TransitionDuration TransitionEffect TransitiveClosureGraph TransitiveReductionGraph Translate TranslationOptions TranslationTransform Transliterate Transparent TransparentColor Transpose TransposeLayer TrapSelection TravelDirections TravelDirectionsData TravelDistance TravelDistanceList TravelMethod TravelTime TreeForm TreeGraph TreeGraphQ TreePlot TrendStyle Triangle TriangleCenter TriangleConstruct TriangleMeasurement TriangleWave TriangularDistribution TriangulateMesh Trig TrigExpand TrigFactor TrigFactorList Trigger TrigReduce TrigToExp TrimmedMean TrimmedVariance TropicalStormData True TrueQ TruncatedDistribution TruncatedPolyhedron TsallisQExponentialDistribution TsallisQGaussianDistribution TTest Tube TubeBezierCurveBox TubeBezierCurveBoxOptions TubeBox TubeBoxOptions TubeBSplineCurveBox TubeBSplineCurveBoxOptions Tuesday TukeyLambdaDistribution TukeyWindow TunnelData Tuples TuranGraph TuringMachine TuttePolynomial TwoWayRule Typed TypeSpecifier" + "UnateQ Uncompress UnconstrainedParameters Undefined UnderBar Underflow Underlined Underoverscript UnderoverscriptBox UnderoverscriptBoxOptions Underscript UnderscriptBox UnderscriptBoxOptions UnderseaFeatureData UndirectedEdge UndirectedGraph UndirectedGraphQ UndoOptions UndoTrackedVariables Unequal UnequalTo Unevaluated UniformDistribution UniformGraphDistribution UniformPolyhedron UniformSumDistribution Uninstall Union UnionPlus Unique UnitaryMatrixQ UnitBox UnitConvert UnitDimensions Unitize UnitRootTest UnitSimplify UnitStep UnitSystem UnitTriangle UnitVector UnitVectorLayer UnityDimensions UniverseModelData UniversityData UnixTime Unprotect UnregisterExternalEvaluator UnsameQ UnsavedVariables Unset UnsetShared UntrackedVariables Up UpArrow UpArrowBar UpArrowDownArrow Update UpdateDynamicObjects UpdateDynamicObjectsSynchronous UpdateInterval UpdateSearchIndex UpDownArrow UpEquilibrium UpperCaseQ UpperLeftArrow UpperRightArrow UpperTriangularize UpperTriangularMatrixQ Upsample UpSet UpSetDelayed UpTee UpTeeArrow UpTo UpValues URL URLBuild URLDecode URLDispatcher URLDownload URLDownloadSubmit URLEncode URLExecute URLExpand URLFetch URLFetchAsynchronous URLParse URLQueryDecode URLQueryEncode URLRead URLResponseTime URLSave URLSaveAsynchronous URLShorten URLSubmit UseGraphicsRange UserDefinedWavelet Using UsingFrontEnd UtilityFunction" + "V2Get ValenceErrorHandling ValidationLength ValidationSet Value ValueBox ValueBoxOptions ValueDimensions ValueForm ValuePreprocessingFunction ValueQ Values ValuesData Variables Variance VarianceEquivalenceTest VarianceEstimatorFunction VarianceGammaDistribution VarianceTest VectorAngle VectorAround VectorColorFunction VectorColorFunctionScaling VectorDensityPlot VectorGlyphData VectorGreater VectorGreaterEqual VectorLess VectorLessEqual VectorMarkers VectorPlot VectorPlot3D VectorPoints VectorQ Vectors VectorScale VectorStyle Vee Verbatim Verbose VerboseConvertToPostScriptPacket VerificationTest VerifyConvergence VerifyDerivedKey VerifyDigitalSignature VerifyInterpretation VerifySecurityCertificates VerifySolutions VerifyTestAssumptions Version VersionNumber VertexAdd VertexCapacity VertexColors VertexComponent VertexConnectivity VertexContract VertexCoordinateRules VertexCoordinates VertexCorrelationSimilarity VertexCosineSimilarity VertexCount VertexCoverQ VertexDataCoordinates VertexDegree VertexDelete VertexDiceSimilarity VertexEccentricity VertexInComponent VertexInDegree VertexIndex VertexJaccardSimilarity VertexLabeling VertexLabels VertexLabelStyle VertexList VertexNormals VertexOutComponent VertexOutDegree VertexQ VertexRenderingFunction VertexReplace VertexShape VertexShapeFunction VertexSize VertexStyle VertexTextureCoordinates VertexWeight VertexWeightedGraphQ Vertical VerticalBar VerticalForm VerticalGauge VerticalSeparator VerticalSlider VerticalTilde ViewAngle ViewCenter ViewMatrix ViewPoint ViewPointSelectorSettings ViewPort ViewProjection ViewRange ViewVector ViewVertical VirtualGroupData Visible VisibleCell VoiceStyleData VoigtDistribution VolcanoData Volume VonMisesDistribution VoronoiMesh" + "WaitAll WaitAsynchronousTask WaitNext WaitUntil WakebyDistribution WalleniusHypergeometricDistribution WaringYuleDistribution WarpingCorrespondence WarpingDistance WatershedComponents WatsonUSquareTest WattsStrogatzGraphDistribution WaveletBestBasis WaveletFilterCoefficients WaveletImagePlot WaveletListPlot WaveletMapIndexed WaveletMatrixPlot WaveletPhi WaveletPsi WaveletScale WaveletScalogram WaveletThreshold WeaklyConnectedComponents WeaklyConnectedGraphComponents WeaklyConnectedGraphQ WeakStationarity WeatherData WeatherForecastData WebAudioSearch WebElementObject WeberE WebExecute WebImage WebImageSearch WebSearch WebSessionObject WebSessions WebWindowObject Wedge Wednesday WeibullDistribution WeierstrassE1 WeierstrassE2 WeierstrassE3 WeierstrassEta1 WeierstrassEta2 WeierstrassEta3 WeierstrassHalfPeriods WeierstrassHalfPeriodW1 WeierstrassHalfPeriodW2 WeierstrassHalfPeriodW3 WeierstrassInvariantG2 WeierstrassInvariantG3 WeierstrassInvariants WeierstrassP WeierstrassPPrime WeierstrassSigma WeierstrassZeta WeightedAdjacencyGraph WeightedAdjacencyMatrix WeightedData WeightedGraphQ Weights WelchWindow WheelGraph WhenEvent Which While White WhiteNoiseProcess WhitePoint Whitespace WhitespaceCharacter WhittakerM WhittakerW WienerFilter WienerProcess WignerD WignerSemicircleDistribution WikipediaData WikipediaSearch WilksW WilksWTest WindDirectionData WindingCount WindingPolygon WindowClickSelect WindowElements WindowFloating WindowFrame WindowFrameElements WindowMargins WindowMovable WindowOpacity WindowPersistentStyles WindowSelected WindowSize WindowStatusArea WindowTitle WindowToolbars WindowWidth WindSpeedData WindVectorData WinsorizedMean WinsorizedVariance WishartMatrixDistribution With WolframAlpha WolframAlphaDate WolframAlphaQuantity WolframAlphaResult WolframLanguageData Word WordBoundary WordCharacter WordCloud WordCount WordCounts WordData WordDefinition WordFrequency WordFrequencyData WordList WordOrientation WordSearch WordSelectionFunction WordSeparators WordSpacings WordStem WordTranslation WorkingPrecision WrapAround Write WriteLine WriteString Wronskian" + "XMLElement XMLObject XMLTemplate Xnor Xor XYZColor" + "Yellow Yesterday YuleDissimilarity" + "ZernikeR ZeroSymmetric ZeroTest ZeroWidthTimes Zeta ZetaZero ZIPCodeData ZipfDistribution ZoomCenter ZoomFactor ZTest ZTransform" + "$Aborted $ActivationGroupID $ActivationKey $ActivationUserRegistered $AddOnsDirectory $AllowExternalChannelFunctions $AssertFunction $Assumptions $AsynchronousTask $AudioInputDevices $AudioOutputDevices $BaseDirectory $BatchInput $BatchOutput $BlockchainBase $BoxForms $ByteOrdering $CacheBaseDirectory $Canceled $ChannelBase $CharacterEncoding $CharacterEncodings $CloudBase $CloudConnected $CloudCreditsAvailable $CloudEvaluation $CloudExpressionBase $CloudObjectNameFormat $CloudObjectURLType $CloudRootDirectory $CloudSymbolBase $CloudUserID $CloudUserUUID $CloudVersion $CloudVersionNumber $CloudWolframEngineVersionNumber $CommandLine $CompilationTarget $ConditionHold $ConfiguredKernels $Context $ContextPath $ControlActiveSetting $Cookies $CookieStore $CreationDate $CurrentLink $CurrentTask $CurrentWebSession $DateStringFormat $DefaultAudioInputDevice $DefaultAudioOutputDevice $DefaultFont $DefaultFrontEnd $DefaultImagingDevice $DefaultLocalBase $DefaultMailbox $DefaultNetworkInterface $DefaultPath $Display $DisplayFunction $DistributedContexts $DynamicEvaluation $Echo $EmbedCodeEnvironments $EmbeddableServices $EntityStores $Epilog $EvaluationCloudBase $EvaluationCloudObject $EvaluationEnvironment $ExportFormats $Failed $FinancialDataSource $FontFamilies $FormatType $FrontEnd $FrontEndSession $GeoEntityTypes $GeoLocation $GeoLocationCity $GeoLocationCountry $GeoLocationPrecision $GeoLocationSource $HistoryLength $HomeDirectory $HTMLExportRules $HTTPCookies $HTTPRequest $IgnoreEOF $ImageFormattingWidth $ImagingDevice $ImagingDevices $ImportFormats $IncomingMailSettings $InitialDirectory $Initialization $InitializationContexts $Input $InputFileName $InputStreamMethods $Inspector $InstallationDate $InstallationDirectory $InterfaceEnvironment $InterpreterTypes $IterationLimit $KernelCount $KernelID $Language $LaunchDirectory $LibraryPath $LicenseExpirationDate $LicenseID $LicenseProcesses $LicenseServer $LicenseSubprocesses $LicenseType $Line $Linked $LinkSupported $LoadedFiles $LocalBase $LocalSymbolBase $MachineAddresses $MachineDomain $MachineDomains $MachineEpsilon $MachineID $MachineName $MachinePrecision $MachineType $MaxExtraPrecision $MaxLicenseProcesses $MaxLicenseSubprocesses $MaxMachineNumber $MaxNumber $MaxPiecewiseCases $MaxPrecision $MaxRootDegree $MessageGroups $MessageList $MessagePrePrint $Messages $MinMachineNumber $MinNumber $MinorReleaseNumber $MinPrecision $MobilePhone $ModuleNumber $NetworkConnected $NetworkInterfaces $NetworkLicense $NewMessage $NewSymbol $Notebooks $NoValue $NumberMarks $Off $OperatingSystem $Output $OutputForms $OutputSizeLimit $OutputStreamMethods $Packages $ParentLink $ParentProcessID $PasswordFile $PatchLevelID $Path $PathnameSeparator $PerformanceGoal $Permissions $PermissionsGroupBase $PersistenceBase $PersistencePath $PipeSupported $PlotTheme $Post $Pre $PreferencesDirectory $PreInitialization $PrePrint $PreRead $PrintForms $PrintLiteral $Printout3DPreviewer $ProcessID $ProcessorCount $ProcessorType $ProductInformation $ProgramName $PublisherID $RandomState $RecursionLimit $RegisteredDeviceClasses $RegisteredUserName $ReleaseNumber $RequesterAddress $RequesterWolframID $RequesterWolframUUID $ResourceSystemBase $RootDirectory $ScheduledTask $ScriptCommandLine $ScriptInputString $SecuredAuthenticationKeyTokens $ServiceCreditsAvailable $Services $SessionID $SetParentLink $SharedFunctions $SharedVariables $SoundDisplay $SoundDisplayFunction $SourceLink $SSHAuthentication $SummaryBoxDataSizeLimit $SuppressInputFormHeads $SynchronousEvaluation $SyntaxHandler $System $SystemCharacterEncoding $SystemID $SystemMemory $SystemShell $SystemTimeZone $SystemWordLength $TemplatePath $TemporaryDirectory $TemporaryPrefix $TestFileName $TextStyle $TimedOut $TimeUnit $TimeZone $TimeZoneEntity $TopDirectory $TraceOff $TraceOn $TracePattern $TracePostAction $TracePreAction $UnitSystem $Urgent $UserAddOnsDirectory $UserAgentLanguages $UserAgentMachine $UserAgentName $UserAgentOperatingSystem $UserAgentString $UserAgentVersion $UserBaseDirectory $UserDocumentsDirectory $Username $UserName $UserURLBase $Version $VersionNumber $VoiceStyles $WolframID $WolframUUID", contains: [ hljs.COMMENT("\\(\\*", "\\*\\)", { contains: ["self"] }), hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, ], }; }, }, { name: "matlab", /* Language: Matlab Author: Denis Bardadym Contributors: Eugene Nizhibitsky , Egor Rogov Category: scientific */ create: /* Formal syntax is not published, helpful link: https://github.com/kornilova-l/matlab-IntelliJ-plugin/blob/master/src/main/grammar/Matlab.bnf */ function (hljs) { var TRANSPOSE_RE = "('|\\.')+"; var TRANSPOSE = { relevance: 0, contains: [{ begin: TRANSPOSE_RE }], }; return { keywords: { keyword: "break case catch classdef continue else elseif end enumerated events for function " + "global if methods otherwise parfor persistent properties return spmd switch try while", built_in: "sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan " + "atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot " + "cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog " + "realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal " + "cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli " + "besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma " + "gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms " + "nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones " + "eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length " + "ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril " + "triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute " + "shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan " + "isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal " + "rosser toeplitz vander wilkinson max min nanmax nanmin mean nanmean type table " + "readtable writetable sortrows sort figure plot plot3 scatter scatter3 cellfun " + "legend intersect ismember procrustes hold num2cell ", }, illegal: '(//|"|#|/\\*|\\s+/\\w+)', contains: [ { className: "function", beginKeywords: "function", end: "$", contains: [ hljs.UNDERSCORE_TITLE_MODE, { className: "params", variants: [ { begin: "\\(", end: "\\)" }, { begin: "\\[", end: "\\]" }, ], }, ], }, { className: "built_in", begin: /true|false/, relevance: 0, starts: TRANSPOSE, }, { begin: "[a-zA-Z][a-zA-Z_0-9]*" + TRANSPOSE_RE, relevance: 0, }, { className: "number", begin: hljs.C_NUMBER_RE, relevance: 0, starts: TRANSPOSE, }, { className: "string", begin: "'", end: "'", contains: [hljs.BACKSLASH_ESCAPE, { begin: "''" }], }, { begin: /\]|}|\)/, relevance: 0, starts: TRANSPOSE, }, { className: "string", begin: '"', end: '"', contains: [hljs.BACKSLASH_ESCAPE, { begin: '""' }], starts: TRANSPOSE, }, hljs.COMMENT("^\\s*\\%\\{\\s*$", "^\\s*\\%\\}\\s*$"), hljs.COMMENT("\\%", "$"), ], }; }, }, { name: "maxima", /* Language: Maxima Author: Robert Dodier Category: scientific */ create: function (hljs) { var KEYWORDS = "if then else elseif for thru do while unless step in and or not"; var LITERALS = "true false unknown inf minf ind und %e %i %pi %phi %gamma"; var BUILTIN_FUNCTIONS = " abasep abs absint absolute_real_time acos acosh acot acoth acsc acsch activate" + " addcol add_edge add_edges addmatrices addrow add_vertex add_vertices adjacency_matrix" + " adjoin adjoint af agd airy airy_ai airy_bi airy_dai airy_dbi algsys alg_type" + " alias allroots alphacharp alphanumericp amortization %and annuity_fv" + " annuity_pv antid antidiff AntiDifference append appendfile apply apply1 apply2" + " applyb1 apropos args arit_amortization arithmetic arithsum array arrayapply" + " arrayinfo arraymake arraysetapply ascii asec asech asin asinh askinteger" + " asksign assoc assoc_legendre_p assoc_legendre_q assume assume_external_byte_order" + " asympa at atan atan2 atanh atensimp atom atvalue augcoefmatrix augmented_lagrangian_method" + " av average_degree backtrace bars barsplot barsplot_description base64 base64_decode" + " bashindices batch batchload bc2 bdvac belln benefit_cost bern bernpoly bernstein_approx" + " bernstein_expand bernstein_poly bessel bessel_i bessel_j bessel_k bessel_simplify" + " bessel_y beta beta_incomplete beta_incomplete_generalized beta_incomplete_regularized" + " bezout bfallroots bffac bf_find_root bf_fmin_cobyla bfhzeta bfloat bfloatp" + " bfpsi bfpsi0 bfzeta biconnected_components bimetric binomial bipartition" + " block blockmatrixp bode_gain bode_phase bothcoef box boxplot boxplot_description" + " break bug_report build_info|10 buildq build_sample burn cabs canform canten" + " cardinality carg cartan cartesian_product catch cauchy_matrix cbffac cdf_bernoulli" + " cdf_beta cdf_binomial cdf_cauchy cdf_chi2 cdf_continuous_uniform cdf_discrete_uniform" + " cdf_exp cdf_f cdf_gamma cdf_general_finite_discrete cdf_geometric cdf_gumbel" + " cdf_hypergeometric cdf_laplace cdf_logistic cdf_lognormal cdf_negative_binomial" + " cdf_noncentral_chi2 cdf_noncentral_student_t cdf_normal cdf_pareto cdf_poisson" + " cdf_rank_sum cdf_rayleigh cdf_signed_rank cdf_student_t cdf_weibull cdisplay" + " ceiling central_moment cequal cequalignore cf cfdisrep cfexpand cgeodesic" + " cgreaterp cgreaterpignore changename changevar chaosgame charat charfun charfun2" + " charlist charp charpoly chdir chebyshev_t chebyshev_u checkdiv check_overlaps" + " chinese cholesky christof chromatic_index chromatic_number cint circulant_graph" + " clear_edge_weight clear_rules clear_vertex_label clebsch_gordan clebsch_graph" + " clessp clesspignore close closefile cmetric coeff coefmatrix cograd col collapse" + " collectterms columnop columnspace columnswap columnvector combination combine" + " comp2pui compare compfile compile compile_file complement_graph complete_bipartite_graph" + " complete_graph complex_number_p components compose_functions concan concat" + " conjugate conmetderiv connected_components connect_vertices cons constant" + " constantp constituent constvalue cont2part content continuous_freq contortion" + " contour_plot contract contract_edge contragrad contrib_ode convert coord" + " copy copy_file copy_graph copylist copymatrix cor cos cosh cot coth cov cov1" + " covdiff covect covers crc24sum create_graph create_list csc csch csetup cspline" + " ctaylor ct_coordsys ctransform ctranspose cube_graph cuboctahedron_graph" + " cunlisp cv cycle_digraph cycle_graph cylindrical days360 dblint deactivate" + " declare declare_constvalue declare_dimensions declare_fundamental_dimensions" + " declare_fundamental_units declare_qty declare_translated declare_unit_conversion" + " declare_units declare_weights decsym defcon define define_alt_display define_variable" + " defint defmatch defrule defstruct deftaylor degree_sequence del delete deleten" + " delta demo demoivre denom depends derivdegree derivlist describe desolve" + " determinant dfloat dgauss_a dgauss_b dgeev dgemm dgeqrf dgesv dgesvd diag" + " diagmatrix diag_matrix diagmatrixp diameter diff digitcharp dimacs_export" + " dimacs_import dimension dimensionless dimensions dimensions_as_list direct" + " directory discrete_freq disjoin disjointp disolate disp dispcon dispform" + " dispfun dispJordan display disprule dispterms distrib divide divisors divsum" + " dkummer_m dkummer_u dlange dodecahedron_graph dotproduct dotsimp dpart" + " draw draw2d draw3d drawdf draw_file draw_graph dscalar echelon edge_coloring" + " edge_connectivity edges eigens_by_jacobi eigenvalues eigenvectors eighth" + " einstein eivals eivects elapsed_real_time elapsed_run_time ele2comp ele2polynome" + " ele2pui elem elementp elevation_grid elim elim_allbut eliminate eliminate_using" + " ellipse elliptic_e elliptic_ec elliptic_eu elliptic_f elliptic_kc elliptic_pi" + " ematrix empty_graph emptyp endcons entermatrix entertensor entier equal equalp" + " equiv_classes erf erfc erf_generalized erfi errcatch error errormsg errors" + " euler ev eval_string evenp every evolution evolution2d evundiff example exp" + " expand expandwrt expandwrt_factored expint expintegral_chi expintegral_ci" + " expintegral_e expintegral_e1 expintegral_ei expintegral_e_simplify expintegral_li" + " expintegral_shi expintegral_si explicit explose exponentialize express expt" + " exsec extdiff extract_linear_equations extremal_subset ezgcd %f f90 facsum" + " factcomb factor factorfacsum factorial factorout factorsum facts fast_central_elements" + " fast_linsolve fasttimes featurep fernfale fft fib fibtophi fifth filename_merge" + " file_search file_type fillarray findde find_root find_root_abs find_root_error" + " find_root_rel first fix flatten flength float floatnump floor flower_snark" + " flush flush1deriv flushd flushnd flush_output fmin_cobyla forget fortran" + " fourcos fourexpand fourier fourier_elim fourint fourintcos fourintsin foursimp" + " foursin fourth fposition frame_bracket freeof freshline fresnel_c fresnel_s" + " from_adjacency_matrix frucht_graph full_listify fullmap fullmapl fullratsimp" + " fullratsubst fullsetify funcsolve fundamental_dimensions fundamental_units" + " fundef funmake funp fv g0 g1 gamma gamma_greek gamma_incomplete gamma_incomplete_generalized" + " gamma_incomplete_regularized gauss gauss_a gauss_b gaussprob gcd gcdex gcdivide" + " gcfac gcfactor gd generalized_lambert_w genfact gen_laguerre genmatrix gensym" + " geo_amortization geo_annuity_fv geo_annuity_pv geomap geometric geometric_mean" + " geosum get getcurrentdirectory get_edge_weight getenv get_lu_factors get_output_stream_string" + " get_pixel get_plot_option get_tex_environment get_tex_environment_default" + " get_vertex_label gfactor gfactorsum ggf girth global_variances gn gnuplot_close" + " gnuplot_replot gnuplot_reset gnuplot_restart gnuplot_start go Gosper GosperSum" + " gr2d gr3d gradef gramschmidt graph6_decode graph6_encode graph6_export graph6_import" + " graph_center graph_charpoly graph_eigenvalues graph_flow graph_order graph_periphery" + " graph_product graph_size graph_union great_rhombicosidodecahedron_graph great_rhombicuboctahedron_graph" + " grid_graph grind grobner_basis grotzch_graph hamilton_cycle hamilton_path" + " hankel hankel_1 hankel_2 harmonic harmonic_mean hav heawood_graph hermite" + " hessian hgfred hilbertmap hilbert_matrix hipow histogram histogram_description" + " hodge horner hypergeometric i0 i1 %ibes ic1 ic2 ic_convert ichr1 ichr2 icosahedron_graph" + " icosidodecahedron_graph icurvature ident identfor identity idiff idim idummy" + " ieqn %if ifactors iframes ifs igcdex igeodesic_coords ilt image imagpart" + " imetric implicit implicit_derivative implicit_plot indexed_tensor indices" + " induced_subgraph inferencep inference_result infix info_display init_atensor" + " init_ctensor in_neighbors innerproduct inpart inprod inrt integerp integer_partitions" + " integrate intersect intersection intervalp intopois intosum invariant1 invariant2" + " inverse_fft inverse_jacobi_cd inverse_jacobi_cn inverse_jacobi_cs inverse_jacobi_dc" + " inverse_jacobi_dn inverse_jacobi_ds inverse_jacobi_nc inverse_jacobi_nd inverse_jacobi_ns" + " inverse_jacobi_sc inverse_jacobi_sd inverse_jacobi_sn invert invert_by_adjoint" + " invert_by_lu inv_mod irr is is_biconnected is_bipartite is_connected is_digraph" + " is_edge_in_graph is_graph is_graph_or_digraph ishow is_isomorphic isolate" + " isomorphism is_planar isqrt isreal_p is_sconnected is_tree is_vertex_in_graph" + " items_inference %j j0 j1 jacobi jacobian jacobi_cd jacobi_cn jacobi_cs jacobi_dc" + " jacobi_dn jacobi_ds jacobi_nc jacobi_nd jacobi_ns jacobi_p jacobi_sc jacobi_sd" + " jacobi_sn JF jn join jordan julia julia_set julia_sin %k kdels kdelta kill" + " killcontext kostka kron_delta kronecker_product kummer_m kummer_u kurtosis" + " kurtosis_bernoulli kurtosis_beta kurtosis_binomial kurtosis_chi2 kurtosis_continuous_uniform" + " kurtosis_discrete_uniform kurtosis_exp kurtosis_f kurtosis_gamma kurtosis_general_finite_discrete" + " kurtosis_geometric kurtosis_gumbel kurtosis_hypergeometric kurtosis_laplace" + " kurtosis_logistic kurtosis_lognormal kurtosis_negative_binomial kurtosis_noncentral_chi2" + " kurtosis_noncentral_student_t kurtosis_normal kurtosis_pareto kurtosis_poisson" + " kurtosis_rayleigh kurtosis_student_t kurtosis_weibull label labels lagrange" + " laguerre lambda lambert_w laplace laplacian_matrix last lbfgs lc2kdt lcharp" + " lc_l lcm lc_u ldefint ldisp ldisplay legendre_p legendre_q leinstein length" + " let letrules letsimp levi_civita lfreeof lgtreillis lhs li liediff limit" + " Lindstedt linear linearinterpol linear_program linear_regression line_graph" + " linsolve listarray list_correlations listify list_matrix_entries list_nc_monomials" + " listoftens listofvars listp lmax lmin load loadfile local locate_matrix_entry" + " log logcontract log_gamma lopow lorentz_gauge lowercasep lpart lratsubst" + " lreduce lriemann lsquares_estimates lsquares_estimates_approximate lsquares_estimates_exact" + " lsquares_mse lsquares_residual_mse lsquares_residuals lsum ltreillis lu_backsub" + " lucas lu_factor %m macroexpand macroexpand1 make_array makebox makefact makegamma" + " make_graph make_level_picture makelist makeOrders make_poly_continent make_poly_country" + " make_polygon make_random_state make_rgb_picture makeset make_string_input_stream" + " make_string_output_stream make_transform mandelbrot mandelbrot_set map mapatom" + " maplist matchdeclare matchfix mat_cond mat_fullunblocker mat_function mathml_display" + " mat_norm matrix matrixmap matrixp matrix_size mattrace mat_trace mat_unblocker" + " max max_clique max_degree max_flow maximize_lp max_independent_set max_matching" + " maybe md5sum mean mean_bernoulli mean_beta mean_binomial mean_chi2 mean_continuous_uniform" + " mean_deviation mean_discrete_uniform mean_exp mean_f mean_gamma mean_general_finite_discrete" + " mean_geometric mean_gumbel mean_hypergeometric mean_laplace mean_logistic" + " mean_lognormal mean_negative_binomial mean_noncentral_chi2 mean_noncentral_student_t" + " mean_normal mean_pareto mean_poisson mean_rayleigh mean_student_t mean_weibull" + " median median_deviation member mesh metricexpandall mgf1_sha1 min min_degree" + " min_edge_cut minfactorial minimalPoly minimize_lp minimum_spanning_tree minor" + " minpack_lsquares minpack_solve min_vertex_cover min_vertex_cut mkdir mnewton" + " mod mode_declare mode_identity ModeMatrix moebius mon2schur mono monomial_dimensions" + " multibernstein_poly multi_display_for_texinfo multi_elem multinomial multinomial_coeff" + " multi_orbit multiplot_mode multi_pui multsym multthru mycielski_graph nary" + " natural_unit nc_degree ncexpt ncharpoly negative_picture neighbors new newcontext" + " newdet new_graph newline newton new_variable next_prime nicedummies niceindices" + " ninth nofix nonarray noncentral_moment nonmetricity nonnegintegerp nonscalarp" + " nonzeroandfreeof notequal nounify nptetrad npv nroots nterms ntermst" + " nthroot nullity nullspace num numbered_boundaries numberp number_to_octets" + " num_distinct_partitions numerval numfactor num_partitions nusum nzeta nzetai" + " nzetar octets_to_number octets_to_oid odd_girth oddp ode2 ode_check odelin" + " oid_to_octets op opena opena_binary openr openr_binary openw openw_binary" + " operatorp opsubst optimize %or orbit orbits ordergreat ordergreatp orderless" + " orderlessp orthogonal_complement orthopoly_recur orthopoly_weight outermap" + " out_neighbors outofpois pade parabolic_cylinder_d parametric parametric_surface" + " parg parGosper parse_string parse_timedate part part2cont partfrac partition" + " partition_set partpol path_digraph path_graph pathname_directory pathname_name" + " pathname_type pdf_bernoulli pdf_beta pdf_binomial pdf_cauchy pdf_chi2 pdf_continuous_uniform" + " pdf_discrete_uniform pdf_exp pdf_f pdf_gamma pdf_general_finite_discrete" + " pdf_geometric pdf_gumbel pdf_hypergeometric pdf_laplace pdf_logistic pdf_lognormal" + " pdf_negative_binomial pdf_noncentral_chi2 pdf_noncentral_student_t pdf_normal" + " pdf_pareto pdf_poisson pdf_rank_sum pdf_rayleigh pdf_signed_rank pdf_student_t" + " pdf_weibull pearson_skewness permanent permut permutation permutations petersen_graph" + " petrov pickapart picture_equalp picturep piechart piechart_description planar_embedding" + " playback plog plot2d plot3d plotdf ploteq plsquares pochhammer points poisdiff" + " poisexpt poisint poismap poisplus poissimp poissubst poistimes poistrim polar" + " polarform polartorect polar_to_xy poly_add poly_buchberger poly_buchberger_criterion" + " poly_colon_ideal poly_content polydecomp poly_depends_p poly_elimination_ideal" + " poly_exact_divide poly_expand poly_expt poly_gcd polygon poly_grobner poly_grobner_equal" + " poly_grobner_member poly_grobner_subsetp poly_ideal_intersection poly_ideal_polysaturation" + " poly_ideal_polysaturation1 poly_ideal_saturation poly_ideal_saturation1 poly_lcm" + " poly_minimization polymod poly_multiply polynome2ele polynomialp poly_normal_form" + " poly_normalize poly_normalize_list poly_polysaturation_extension poly_primitive_part" + " poly_pseudo_divide poly_reduced_grobner poly_reduction poly_saturation_extension" + " poly_s_polynomial poly_subtract polytocompanion pop postfix potential power_mod" + " powerseries powerset prefix prev_prime primep primes principal_components" + " print printf printfile print_graph printpois printprops prodrac product properties" + " propvars psi psubst ptriangularize pui pui2comp pui2ele pui2polynome pui_direct" + " puireduc push put pv qput qrange qty quad_control quad_qag quad_qagi quad_qagp" + " quad_qags quad_qawc quad_qawf quad_qawo quad_qaws quadrilateral quantile" + " quantile_bernoulli quantile_beta quantile_binomial quantile_cauchy quantile_chi2" + " quantile_continuous_uniform quantile_discrete_uniform quantile_exp quantile_f" + " quantile_gamma quantile_general_finite_discrete quantile_geometric quantile_gumbel" + " quantile_hypergeometric quantile_laplace quantile_logistic quantile_lognormal" + " quantile_negative_binomial quantile_noncentral_chi2 quantile_noncentral_student_t" + " quantile_normal quantile_pareto quantile_poisson quantile_rayleigh quantile_student_t" + " quantile_weibull quartile_skewness quit qunit quotient racah_v racah_w radcan" + " radius random random_bernoulli random_beta random_binomial random_bipartite_graph" + " random_cauchy random_chi2 random_continuous_uniform random_digraph random_discrete_uniform" + " random_exp random_f random_gamma random_general_finite_discrete random_geometric" + " random_graph random_graph1 random_gumbel random_hypergeometric random_laplace" + " random_logistic random_lognormal random_negative_binomial random_network" + " random_noncentral_chi2 random_noncentral_student_t random_normal random_pareto" + " random_permutation random_poisson random_rayleigh random_regular_graph random_student_t" + " random_tournament random_tree random_weibull range rank rat ratcoef ratdenom" + " ratdiff ratdisrep ratexpand ratinterpol rational rationalize ratnumer ratnump" + " ratp ratsimp ratsubst ratvars ratweight read read_array read_binary_array" + " read_binary_list read_binary_matrix readbyte readchar read_hashed_array readline" + " read_list read_matrix read_nested_list readonly read_xpm real_imagpart_to_conjugate" + " realpart realroots rearray rectangle rectform rectform_log_if_constant recttopolar" + " rediff reduce_consts reduce_order region region_boundaries region_boundaries_plus" + " rem remainder remarray rembox remcomps remcon remcoord remfun remfunction" + " remlet remove remove_constvalue remove_dimensions remove_edge remove_fundamental_dimensions" + " remove_fundamental_units remove_plot_option remove_vertex rempart remrule" + " remsym remvalue rename rename_file reset reset_displays residue resolvante" + " resolvante_alternee1 resolvante_bipartite resolvante_diedrale resolvante_klein" + " resolvante_klein3 resolvante_produit_sym resolvante_unitaire resolvante_vierer" + " rest resultant return reveal reverse revert revert2 rgb2level rhs ricci riemann" + " rinvariant risch rk rmdir rncombine romberg room rootscontract round row" + " rowop rowswap rreduce run_testsuite %s save saving scalarp scaled_bessel_i" + " scaled_bessel_i0 scaled_bessel_i1 scalefactors scanmap scatterplot scatterplot_description" + " scene schur2comp sconcat scopy scsimp scurvature sdowncase sec sech second" + " sequal sequalignore set_alt_display setdifference set_draw_defaults set_edge_weight" + " setelmx setequalp setify setp set_partitions set_plot_option set_prompt set_random_state" + " set_tex_environment set_tex_environment_default setunits setup_autoload set_up_dot_simplifications" + " set_vertex_label seventh sexplode sf sha1sum sha256sum shortest_path shortest_weighted_path" + " show showcomps showratvars sierpinskiale sierpinskimap sign signum similaritytransform" + " simp_inequality simplify_sum simplode simpmetderiv simtran sin sinh sinsert" + " sinvertcase sixth skewness skewness_bernoulli skewness_beta skewness_binomial" + " skewness_chi2 skewness_continuous_uniform skewness_discrete_uniform skewness_exp" + " skewness_f skewness_gamma skewness_general_finite_discrete skewness_geometric" + " skewness_gumbel skewness_hypergeometric skewness_laplace skewness_logistic" + " skewness_lognormal skewness_negative_binomial skewness_noncentral_chi2 skewness_noncentral_student_t" + " skewness_normal skewness_pareto skewness_poisson skewness_rayleigh skewness_student_t" + " skewness_weibull slength smake small_rhombicosidodecahedron_graph small_rhombicuboctahedron_graph" + " smax smin smismatch snowmap snub_cube_graph snub_dodecahedron_graph solve" + " solve_rec solve_rec_rat some somrac sort sparse6_decode sparse6_encode sparse6_export" + " sparse6_import specint spherical spherical_bessel_j spherical_bessel_y spherical_hankel1" + " spherical_hankel2 spherical_harmonic spherical_to_xyz splice split sposition" + " sprint sqfr sqrt sqrtdenest sremove sremovefirst sreverse ssearch ssort sstatus" + " ssubst ssubstfirst staircase standardize standardize_inverse_trig starplot" + " starplot_description status std std1 std_bernoulli std_beta std_binomial" + " std_chi2 std_continuous_uniform std_discrete_uniform std_exp std_f std_gamma" + " std_general_finite_discrete std_geometric std_gumbel std_hypergeometric std_laplace" + " std_logistic std_lognormal std_negative_binomial std_noncentral_chi2 std_noncentral_student_t" + " std_normal std_pareto std_poisson std_rayleigh std_student_t std_weibull" + " stemplot stirling stirling1 stirling2 strim striml strimr string stringout" + " stringp strong_components struve_h struve_l sublis sublist sublist_indices" + " submatrix subsample subset subsetp subst substinpart subst_parallel substpart" + " substring subvar subvarp sum sumcontract summand_to_rec supcase supcontext" + " symbolp symmdifference symmetricp system take_channel take_inference tan" + " tanh taylor taylorinfo taylorp taylor_simplifier taytorat tcl_output tcontract" + " tellrat tellsimp tellsimpafter tentex tenth test_mean test_means_difference" + " test_normality test_proportion test_proportions_difference test_rank_sum" + " test_sign test_signed_rank test_variance test_variance_ratio tex tex1 tex_display" + " texput %th third throw time timedate timer timer_info tldefint tlimit todd_coxeter" + " toeplitz tokens to_lisp topological_sort to_poly to_poly_solve totaldisrep" + " totalfourier totient tpartpol trace tracematrix trace_options transform_sample" + " translate translate_file transpose treefale tree_reduce treillis treinat" + " triangle triangularize trigexpand trigrat trigreduce trigsimp trunc truncate" + " truncated_cube_graph truncated_dodecahedron_graph truncated_icosahedron_graph" + " truncated_tetrahedron_graph tr_warnings_get tube tutte_graph ueivects uforget" + " ultraspherical underlying_graph undiff union unique uniteigenvectors unitp" + " units unit_step unitvector unorder unsum untellrat untimer" + " untrace uppercasep uricci uriemann uvect vandermonde_matrix var var1 var_bernoulli" + " var_beta var_binomial var_chi2 var_continuous_uniform var_discrete_uniform" + " var_exp var_f var_gamma var_general_finite_discrete var_geometric var_gumbel" + " var_hypergeometric var_laplace var_logistic var_lognormal var_negative_binomial" + " var_noncentral_chi2 var_noncentral_student_t var_normal var_pareto var_poisson" + " var_rayleigh var_student_t var_weibull vector vectorpotential vectorsimp" + " verbify vers vertex_coloring vertex_connectivity vertex_degree vertex_distance" + " vertex_eccentricity vertex_in_degree vertex_out_degree vertices vertices_to_cycle" + " vertices_to_path %w weyl wheel_graph wiener_index wigner_3j wigner_6j" + " wigner_9j with_stdout write_binary_data writebyte write_data writefile wronskian" + " xreduce xthru %y Zeilberger zeroequiv zerofor zeromatrix zeromatrixp zeta" + " zgeev zheev zlange zn_add_table zn_carmichael_lambda zn_characteristic_factors" + " zn_determinant zn_factor_generators zn_invert_by_lu zn_log zn_mult_table" + " absboxchar activecontexts adapt_depth additive adim aform algebraic" + " algepsilon algexact aliases allbut all_dotsimp_denoms allocation allsym alphabetic" + " animation antisymmetric arrays askexp assume_pos assume_pos_pred assumescalar" + " asymbol atomgrad atrig1 axes axis_3d axis_bottom axis_left axis_right axis_top" + " azimuth background background_color backsubst berlefact bernstein_explicit" + " besselexpand beta_args_sum_to_integer beta_expand bftorat bftrunc bindtest" + " border boundaries_array box boxchar breakup %c capping cauchysum cbrange" + " cbtics center cflength cframe_flag cnonmet_flag color color_bar color_bar_tics" + " colorbox columns commutative complex cone context contexts contour contour_levels" + " cosnpiflag ctaypov ctaypt ctayswitch ctayvar ct_coords ctorsion_flag ctrgsimp" + " cube current_let_rule_package cylinder data_file_name debugmode decreasing" + " default_let_rule_package delay dependencies derivabbrev derivsubst detout" + " diagmetric diff dim dimensions dispflag display2d|10 display_format_internal" + " distribute_over doallmxops domain domxexpt domxmxops domxnctimes dontfactor" + " doscmxops doscmxplus dot0nscsimp dot0simp dot1simp dotassoc dotconstrules" + " dotdistrib dotexptsimp dotident dotscrules draw_graph_program draw_realpart" + " edge_color edge_coloring edge_partition edge_type edge_width %edispflag" + " elevation %emode endphi endtheta engineering_format_floats enhanced3d %enumer" + " epsilon_lp erfflag erf_representation errormsg error_size error_syms error_type" + " %e_to_numlog eval even evenfun evflag evfun ev_point expandwrt_denom expintexpand" + " expintrep expon expop exptdispflag exptisolate exptsubst facexpand facsum_combine" + " factlim factorflag factorial_expand factors_only fb feature features" + " file_name file_output_append file_search_demo file_search_lisp file_search_maxima|10" + " file_search_tests file_search_usage file_type_lisp file_type_maxima|10 fill_color" + " fill_density filled_func fixed_vertices flipflag float2bf font font_size" + " fortindent fortspaces fpprec fpprintprec functions gamma_expand gammalim" + " gdet genindex gensumnum GGFCFMAX GGFINFINITY globalsolve gnuplot_command" + " gnuplot_curve_styles gnuplot_curve_titles gnuplot_default_term_command gnuplot_dumb_term_command" + " gnuplot_file_args gnuplot_file_name gnuplot_out_file gnuplot_pdf_term_command" + " gnuplot_pm3d gnuplot_png_term_command gnuplot_postamble gnuplot_preamble" + " gnuplot_ps_term_command gnuplot_svg_term_command gnuplot_term gnuplot_view_args" + " Gosper_in_Zeilberger gradefs grid grid2d grind halfangles head_angle head_both" + " head_length head_type height hypergeometric_representation %iargs ibase" + " icc1 icc2 icounter idummyx ieqnprint ifb ifc1 ifc2 ifg ifgi ifr iframe_bracket_form" + " ifri igeowedge_flag ikt1 ikt2 imaginary inchar increasing infeval" + " infinity inflag infolists inm inmc1 inmc2 intanalysis integer integervalued" + " integrate_use_rootsof integration_constant integration_constant_counter interpolate_color" + " intfaclim ip_grid ip_grid_in irrational isolate_wrt_times iterations itr" + " julia_parameter %k1 %k2 keepfloat key key_pos kinvariant kt label label_alignment" + " label_orientation labels lassociative lbfgs_ncorrections lbfgs_nfeval_max" + " leftjust legend letrat let_rule_packages lfg lg lhospitallim limsubst linear" + " linear_solver linechar linel|10 linenum line_type linewidth line_width linsolve_params" + " linsolvewarn lispdisp listarith listconstvars listdummyvars lmxchar load_pathname" + " loadprint logabs logarc logcb logconcoeffp logexpand lognegint logsimp logx" + " logx_secondary logy logy_secondary logz lriem m1pbranch macroexpansion macros" + " mainvar manual_demo maperror mapprint matrix_element_add matrix_element_mult" + " matrix_element_transpose maxapplydepth maxapplyheight maxima_tempdir|10 maxima_userdir|10" + " maxnegex MAX_ORD maxposex maxpsifracdenom maxpsifracnum maxpsinegint maxpsiposint" + " maxtayorder mesh_lines_color method mod_big_prime mode_check_errorp" + " mode_checkp mode_check_warnp mod_test mod_threshold modular_linear_solver" + " modulus multiplicative multiplicities myoptions nary negdistrib negsumdispflag" + " newline newtonepsilon newtonmaxiter nextlayerfactor niceindicespref nm nmc" + " noeval nolabels nonegative_lp noninteger nonscalar noun noundisp nouns np" + " npi nticks ntrig numer numer_pbranch obase odd oddfun opacity opproperties" + " opsubst optimprefix optionset orientation origin orthopoly_returns_intervals" + " outative outchar packagefile palette partswitch pdf_file pfeformat phiresolution" + " %piargs piece pivot_count_sx pivot_max_sx plot_format plot_options plot_realpart" + " png_file pochhammer_max_index points pointsize point_size points_joined point_type" + " poislim poisson poly_coefficient_ring poly_elimination_order polyfactor poly_grobner_algorithm" + " poly_grobner_debug poly_monomial_order poly_primary_elimination_order poly_return_term_list" + " poly_secondary_elimination_order poly_top_reduction_only posfun position" + " powerdisp pred prederror primep_number_of_tests product_use_gamma program" + " programmode promote_float_to_bigfloat prompt proportional_axes props psexpand" + " ps_file radexpand radius radsubstflag rassociative ratalgdenom ratchristof" + " ratdenomdivide rateinstein ratepsilon ratfac rational ratmx ratprint ratriemann" + " ratsimpexpons ratvarswitch ratweights ratweyl ratwtlvl real realonly redraw" + " refcheck resolution restart resultant ric riem rmxchar %rnum_list rombergabs" + " rombergit rombergmin rombergtol rootsconmode rootsepsilon run_viewer same_xy" + " same_xyz savedef savefactors scalar scalarmatrixp scale scale_lp setcheck" + " setcheckbreak setval show_edge_color show_edges show_edge_type show_edge_width" + " show_id show_label showtime show_vertex_color show_vertex_size show_vertex_type" + " show_vertices show_weight simp simplified_output simplify_products simpproduct" + " simpsum sinnpiflag solvedecomposes solveexplicit solvefactors solvenullwarn" + " solveradcan solvetrigwarn space sparse sphere spring_embedding_depth sqrtdispflag" + " stardisp startphi starttheta stats_numer stringdisp structures style sublis_apply_lambda" + " subnumsimp sumexpand sumsplitfact surface surface_hide svg_file symmetric" + " tab taylordepth taylor_logexpand taylor_order_coefficients taylor_truncate_polynomials" + " tensorkill terminal testsuite_files thetaresolution timer_devalue title tlimswitch" + " tr track transcompile transform transform_xy translate_fast_arrays transparent" + " transrun tr_array_as_ref tr_bound_function_applyp tr_file_tty_messagesp tr_float_can_branch_complex" + " tr_function_call_default trigexpandplus trigexpandtimes triginverses trigsign" + " trivial_solutions tr_numer tr_optimize_max_loop tr_semicompile tr_state_vars" + " tr_warn_bad_function_calls tr_warn_fexpr tr_warn_meval tr_warn_mode" + " tr_warn_undeclared tr_warn_undefined_variable tstep ttyoff tube_extremes" + " ufg ug %unitexpand unit_vectors uric uriem use_fast_arrays user_preamble" + " usersetunits values vect_cross verbose vertex_color vertex_coloring vertex_partition" + " vertex_size vertex_type view warnings weyl width windowname windowtitle wired_surface" + " wireframe xaxis xaxis_color xaxis_secondary xaxis_type xaxis_width xlabel" + " xlabel_secondary xlength xrange xrange_secondary xtics xtics_axis xtics_rotate" + " xtics_rotate_secondary xtics_secondary xtics_secondary_axis xu_grid x_voxel" + " xy_file xyplane xy_scale yaxis yaxis_color yaxis_secondary yaxis_type yaxis_width" + " ylabel ylabel_secondary ylength yrange yrange_secondary ytics ytics_axis" + " ytics_rotate ytics_rotate_secondary ytics_secondary ytics_secondary_axis" + " yv_grid y_voxel yx_ratio zaxis zaxis_color zaxis_type zaxis_width zeroa zerob" + " zerobern zeta%pi zlabel zlabel_rotate zlength zmin zn_primroot_limit zn_primroot_pretest"; var SYMBOLS = "_ __ %|0 %%|0"; return { lexemes: "[A-Za-z_%][0-9A-Za-z_%]*", keywords: { keyword: KEYWORDS, literal: LITERALS, built_in: BUILTIN_FUNCTIONS, symbol: SYMBOLS, }, contains: [ { className: "comment", begin: "/\\*", end: "\\*/", contains: ["self"], }, hljs.QUOTE_STRING_MODE, { className: "number", relevance: 0, variants: [ { // float number w/ exponent // hmm, I wonder if we ought to include other exponent markers? begin: "\\b(\\d+|\\d+\\.|\\.\\d+|\\d+\\.\\d+)[Ee][-+]?\\d+\\b", }, { // bigfloat number begin: "\\b(\\d+|\\d+\\.|\\.\\d+|\\d+\\.\\d+)[Bb][-+]?\\d+\\b", relevance: 10, }, { // float number w/out exponent // Doesn't seem to recognize floats which start with '.' begin: "\\b(\\.\\d+|\\d+\\.\\d+)\\b", }, { // integer in base up to 36 // Doesn't seem to recognize integers which end with '.' begin: "\\b(\\d+|0[0-9A-Za-z]+)\\.?\\b", }, ], }, ], illegal: /@/, }; }, }, { name: "mel", /* Language: MEL Description: Maya Embedded Language Author: Shuen-Huei Guan Category: graphics */ create: function (hljs) { return { keywords: "int float string vector matrix if else switch case default while do for in break " + "continue global proc return about abs addAttr addAttributeEditorNodeHelp addDynamic " + "addNewShelfTab addPP addPanelCategory addPrefixToName advanceToNextDrivenKey " + "affectedNet affects aimConstraint air alias aliasAttr align alignCtx alignCurve " + "alignSurface allViewFit ambientLight angle angleBetween animCone animCurveEditor " + "animDisplay animView annotate appendStringArray applicationName applyAttrPreset " + "applyTake arcLenDimContext arcLengthDimension arclen arrayMapper art3dPaintCtx " + "artAttrCtx artAttrPaintVertexCtx artAttrSkinPaintCtx artAttrTool artBuildPaintMenu " + "artFluidAttrCtx artPuttyCtx artSelectCtx artSetPaintCtx artUserPaintCtx assignCommand " + "assignInputDevice assignViewportFactories attachCurve attachDeviceAttr attachSurface " + "attrColorSliderGrp attrCompatibility attrControlGrp attrEnumOptionMenu " + "attrEnumOptionMenuGrp attrFieldGrp attrFieldSliderGrp attrNavigationControlGrp " + "attrPresetEditWin attributeExists attributeInfo attributeMenu attributeQuery " + "autoKeyframe autoPlace bakeClip bakeFluidShading bakePartialHistory bakeResults " + "bakeSimulation basename basenameEx batchRender bessel bevel bevelPlus binMembership " + "bindSkin blend2 blendShape blendShapeEditor blendShapePanel blendTwoAttr blindDataType " + "boneLattice boundary boxDollyCtx boxZoomCtx bufferCurve buildBookmarkMenu " + "buildKeyframeMenu button buttonManip CBG cacheFile cacheFileCombine cacheFileMerge " + "cacheFileTrack camera cameraView canCreateManip canvas capitalizeString catch " + "catchQuiet ceil changeSubdivComponentDisplayLevel changeSubdivRegion channelBox " + "character characterMap characterOutlineEditor characterize chdir checkBox checkBoxGrp " + "checkDefaultRenderGlobals choice circle circularFillet clamp clear clearCache clip " + "clipEditor clipEditorCurrentTimeCtx clipSchedule clipSchedulerOutliner clipTrimBefore " + "closeCurve closeSurface cluster cmdFileOutput cmdScrollFieldExecuter " + "cmdScrollFieldReporter cmdShell coarsenSubdivSelectionList collision color " + "colorAtPoint colorEditor colorIndex colorIndexSliderGrp colorSliderButtonGrp " + "colorSliderGrp columnLayout commandEcho commandLine commandPort compactHairSystem " + "componentEditor compositingInterop computePolysetVolume condition cone confirmDialog " + "connectAttr connectControl connectDynamic connectJoint connectionInfo constrain " + "constrainValue constructionHistory container containsMultibyte contextInfo control " + "convertFromOldLayers convertIffToPsd convertLightmap convertSolidTx convertTessellation " + "convertUnit copyArray copyFlexor copyKey copySkinWeights cos cpButton cpCache " + "cpClothSet cpCollision cpConstraint cpConvClothToMesh cpForces cpGetSolverAttr cpPanel " + "cpProperty cpRigidCollisionFilter cpSeam cpSetEdit cpSetSolverAttr cpSolver " + "cpSolverTypes cpTool cpUpdateClothUVs createDisplayLayer createDrawCtx createEditor " + "createLayeredPsdFile createMotionField createNewShelf createNode createRenderLayer " + "createSubdivRegion cross crossProduct ctxAbort ctxCompletion ctxEditMode ctxTraverse " + "currentCtx currentTime currentTimeCtx currentUnit curve curveAddPtCtx " + "curveCVCtx curveEPCtx curveEditorCtx curveIntersect curveMoveEPCtx curveOnSurface " + "curveSketchCtx cutKey cycleCheck cylinder dagPose date defaultLightListCheckBox " + "defaultNavigation defineDataServer defineVirtualDevice deformer deg_to_rad delete " + "deleteAttr deleteShadingGroupsAndMaterials deleteShelfTab deleteUI deleteUnusedBrushes " + "delrandstr detachCurve detachDeviceAttr detachSurface deviceEditor devicePanel dgInfo " + "dgdirty dgeval dgtimer dimWhen directKeyCtx directionalLight dirmap dirname disable " + "disconnectAttr disconnectJoint diskCache displacementToPoly displayAffected " + "displayColor displayCull displayLevelOfDetail displayPref displayRGBColor " + "displaySmoothness displayStats displayString displaySurface distanceDimContext " + "distanceDimension doBlur dolly dollyCtx dopeSheetEditor dot dotProduct " + "doubleProfileBirailSurface drag dragAttrContext draggerContext dropoffLocator " + "duplicate duplicateCurve duplicateSurface dynCache dynControl dynExport dynExpression " + "dynGlobals dynPaintEditor dynParticleCtx dynPref dynRelEdPanel dynRelEditor " + "dynamicLoad editAttrLimits editDisplayLayerGlobals editDisplayLayerMembers " + "editRenderLayerAdjustment editRenderLayerGlobals editRenderLayerMembers editor " + "editorTemplate effector emit emitter enableDevice encodeString endString endsWith env " + "equivalent equivalentTol erf error eval evalDeferred evalEcho event " + "exactWorldBoundingBox exclusiveLightCheckBox exec executeForEachObject exists exp " + "expression expressionEditorListen extendCurve extendSurface extrude fcheck fclose feof " + "fflush fgetline fgetword file fileBrowserDialog fileDialog fileExtension fileInfo " + "filetest filletCurve filter filterCurve filterExpand filterStudioImport " + "findAllIntersections findAnimCurves findKeyframe findMenuItem findRelatedSkinCluster " + "finder firstParentOf fitBspline flexor floatEq floatField floatFieldGrp floatScrollBar " + "floatSlider floatSlider2 floatSliderButtonGrp floatSliderGrp floor flow fluidCacheInfo " + "fluidEmitter fluidVoxelInfo flushUndo fmod fontDialog fopen formLayout format fprint " + "frameLayout fread freeFormFillet frewind fromNativePath fwrite gamma gauss " + "geometryConstraint getApplicationVersionAsFloat getAttr getClassification " + "getDefaultBrush getFileList getFluidAttr getInputDeviceRange getMayaPanelTypes " + "getModifiers getPanel getParticleAttr getPluginResource getenv getpid glRender " + "glRenderEditor globalStitch gmatch goal gotoBindPose grabColor gradientControl " + "gradientControlNoAttr graphDollyCtx graphSelectContext graphTrackCtx gravity grid " + "gridLayout group groupObjectsByName HfAddAttractorToAS HfAssignAS HfBuildEqualMap " + "HfBuildFurFiles HfBuildFurImages HfCancelAFR HfConnectASToHF HfCreateAttractor " + "HfDeleteAS HfEditAS HfPerformCreateAS HfRemoveAttractorFromAS HfSelectAttached " + "HfSelectAttractors HfUnAssignAS hardenPointCurve hardware hardwareRenderPanel " + "headsUpDisplay headsUpMessage help helpLine hermite hide hilite hitTest hotBox hotkey " + "hotkeyCheck hsv_to_rgb hudButton hudSlider hudSliderButton hwReflectionMap hwRender " + "hwRenderLoad hyperGraph hyperPanel hyperShade hypot iconTextButton iconTextCheckBox " + "iconTextRadioButton iconTextRadioCollection iconTextScrollList iconTextStaticLabel " + "ikHandle ikHandleCtx ikHandleDisplayScale ikSolver ikSplineHandleCtx ikSystem " + "ikSystemInfo ikfkDisplayMethod illustratorCurves image imfPlugins inheritTransform " + "insertJoint insertJointCtx insertKeyCtx insertKnotCurve insertKnotSurface instance " + "instanceable instancer intField intFieldGrp intScrollBar intSlider intSliderGrp " + "interToUI internalVar intersect iprEngine isAnimCurve isConnected isDirty isParentOf " + "isSameObject isTrue isValidObjectName isValidString isValidUiName isolateSelect " + "itemFilter itemFilterAttr itemFilterRender itemFilterType joint jointCluster jointCtx " + "jointDisplayScale jointLattice keyTangent keyframe keyframeOutliner " + "keyframeRegionCurrentTimeCtx keyframeRegionDirectKeyCtx keyframeRegionDollyCtx " + "keyframeRegionInsertKeyCtx keyframeRegionMoveKeyCtx keyframeRegionScaleKeyCtx " + "keyframeRegionSelectKeyCtx keyframeRegionSetKeyCtx keyframeRegionTrackCtx " + "keyframeStats lassoContext lattice latticeDeformKeyCtx launch launchImageEditor " + "layerButton layeredShaderPort layeredTexturePort layout layoutDialog lightList " + "lightListEditor lightListPanel lightlink lineIntersection linearPrecision linstep " + "listAnimatable listAttr listCameras listConnections listDeviceAttachments listHistory " + "listInputDeviceAxes listInputDeviceButtons listInputDevices listMenuAnnotation " + "listNodeTypes listPanelCategories listRelatives listSets listTransforms " + "listUnselected listerEditor loadFluid loadNewShelf loadPlugin " + "loadPluginLanguageResources loadPrefObjects localizedPanelLabel lockNode loft log " + "longNameOf lookThru ls lsThroughFilter lsType lsUI Mayatomr mag makeIdentity makeLive " + "makePaintable makeRoll makeSingleSurface makeTubeOn makebot manipMoveContext " + "manipMoveLimitsCtx manipOptions manipRotateContext manipRotateLimitsCtx " + "manipScaleContext manipScaleLimitsCtx marker match max memory menu menuBarLayout " + "menuEditor menuItem menuItemToShelf menuSet menuSetPref messageLine min minimizeApp " + "mirrorJoint modelCurrentTimeCtx modelEditor modelPanel mouse movIn movOut move " + "moveIKtoFK moveKeyCtx moveVertexAlongDirection multiProfileBirailSurface mute " + "nParticle nameCommand nameField namespace namespaceInfo newPanelItems newton nodeCast " + "nodeIconButton nodeOutliner nodePreset nodeType noise nonLinear normalConstraint " + "normalize nurbsBoolean nurbsCopyUVSet nurbsCube nurbsEditUV nurbsPlane nurbsSelect " + "nurbsSquare nurbsToPoly nurbsToPolygonsPref nurbsToSubdiv nurbsToSubdivPref " + "nurbsUVSet nurbsViewDirectionVector objExists objectCenter objectLayer objectType " + "objectTypeUI obsoleteProc oceanNurbsPreviewPlane offsetCurve offsetCurveOnSurface " + "offsetSurface openGLExtension openMayaPref optionMenu optionMenuGrp optionVar orbit " + "orbitCtx orientConstraint outlinerEditor outlinerPanel overrideModifier " + "paintEffectsDisplay pairBlend palettePort paneLayout panel panelConfiguration " + "panelHistory paramDimContext paramDimension paramLocator parent parentConstraint " + "particle particleExists particleInstancer particleRenderInfo partition pasteKey " + "pathAnimation pause pclose percent performanceOptions pfxstrokes pickWalk picture " + "pixelMove planarSrf plane play playbackOptions playblast plugAttr plugNode pluginInfo " + "pluginResourceUtil pointConstraint pointCurveConstraint pointLight pointMatrixMult " + "pointOnCurve pointOnSurface pointPosition poleVectorConstraint polyAppend " + "polyAppendFacetCtx polyAppendVertex polyAutoProjection polyAverageNormal " + "polyAverageVertex polyBevel polyBlendColor polyBlindData polyBoolOp polyBridgeEdge " + "polyCacheMonitor polyCheck polyChipOff polyClipboard polyCloseBorder polyCollapseEdge " + "polyCollapseFacet polyColorBlindData polyColorDel polyColorPerVertex polyColorSet " + "polyCompare polyCone polyCopyUV polyCrease polyCreaseCtx polyCreateFacet " + "polyCreateFacetCtx polyCube polyCut polyCutCtx polyCylinder polyCylindricalProjection " + "polyDelEdge polyDelFacet polyDelVertex polyDuplicateAndConnect polyDuplicateEdge " + "polyEditUV polyEditUVShell polyEvaluate polyExtrudeEdge polyExtrudeFacet " + "polyExtrudeVertex polyFlipEdge polyFlipUV polyForceUV polyGeoSampler polyHelix " + "polyInfo polyInstallAction polyLayoutUV polyListComponentConversion polyMapCut " + "polyMapDel polyMapSew polyMapSewMove polyMergeEdge polyMergeEdgeCtx polyMergeFacet " + "polyMergeFacetCtx polyMergeUV polyMergeVertex polyMirrorFace polyMoveEdge " + "polyMoveFacet polyMoveFacetUV polyMoveUV polyMoveVertex polyNormal polyNormalPerVertex " + "polyNormalizeUV polyOptUvs polyOptions polyOutput polyPipe polyPlanarProjection " + "polyPlane polyPlatonicSolid polyPoke polyPrimitive polyPrism polyProjection " + "polyPyramid polyQuad polyQueryBlindData polyReduce polySelect polySelectConstraint " + "polySelectConstraintMonitor polySelectCtx polySelectEditCtx polySeparate " + "polySetToFaceNormal polySewEdge polyShortestPathCtx polySmooth polySoftEdge " + "polySphere polySphericalProjection polySplit polySplitCtx polySplitEdge polySplitRing " + "polySplitVertex polyStraightenUVBorder polySubdivideEdge polySubdivideFacet " + "polyToSubdiv polyTorus polyTransfer polyTriangulate polyUVSet polyUnite polyWedgeFace " + "popen popupMenu pose pow preloadRefEd print progressBar progressWindow projFileViewer " + "projectCurve projectTangent projectionContext projectionManip promptDialog propModCtx " + "propMove psdChannelOutliner psdEditTextureFile psdExport psdTextureFile putenv pwd " + "python querySubdiv quit rad_to_deg radial radioButton radioButtonGrp radioCollection " + "radioMenuItemCollection rampColorPort rand randomizeFollicles randstate rangeControl " + "readTake rebuildCurve rebuildSurface recordAttr recordDevice redo reference " + "referenceEdit referenceQuery refineSubdivSelectionList refresh refreshAE " + "registerPluginResource rehash reloadImage removeJoint removeMultiInstance " + "removePanelCategory rename renameAttr renameSelectionList renameUI render " + "renderGlobalsNode renderInfo renderLayerButton renderLayerParent " + "renderLayerPostProcess renderLayerUnparent renderManip renderPartition " + "renderQualityNode renderSettings renderThumbnailUpdate renderWindowEditor " + "renderWindowSelectContext renderer reorder reorderDeformers requires reroot " + "resampleFluid resetAE resetPfxToPolyCamera resetTool resolutionNode retarget " + "reverseCurve reverseSurface revolve rgb_to_hsv rigidBody rigidSolver roll rollCtx " + "rootOf rot rotate rotationInterpolation roundConstantRadius rowColumnLayout rowLayout " + "runTimeCommand runup sampleImage saveAllShelves saveAttrPreset saveFluid saveImage " + "saveInitialState saveMenu savePrefObjects savePrefs saveShelf saveToolSettings scale " + "scaleBrushBrightness scaleComponents scaleConstraint scaleKey scaleKeyCtx sceneEditor " + "sceneUIReplacement scmh scriptCtx scriptEditorInfo scriptJob scriptNode scriptTable " + "scriptToShelf scriptedPanel scriptedPanelType scrollField scrollLayout sculpt " + "searchPathArray seed selLoadSettings select selectContext selectCurveCV selectKey " + "selectKeyCtx selectKeyframeRegionCtx selectMode selectPref selectPriority selectType " + "selectedNodes selectionConnection separator setAttr setAttrEnumResource " + "setAttrMapping setAttrNiceNameResource setConstraintRestPosition " + "setDefaultShadingGroup setDrivenKeyframe setDynamic setEditCtx setEditor setFluidAttr " + "setFocus setInfinity setInputDeviceMapping setKeyCtx setKeyPath setKeyframe " + "setKeyframeBlendshapeTargetWts setMenuMode setNodeNiceNameResource setNodeTypeFlag " + "setParent setParticleAttr setPfxToPolyCamera setPluginResource setProject " + "setStampDensity setStartupMessage setState setToolTo setUITemplate setXformManip sets " + "shadingConnection shadingGeometryRelCtx shadingLightRelCtx shadingNetworkCompare " + "shadingNode shapeCompare shelfButton shelfLayout shelfTabLayout shellField " + "shortNameOf showHelp showHidden showManipCtx showSelectionInTitle " + "showShadingGroupAttrEditor showWindow sign simplify sin singleProfileBirailSurface " + "size sizeBytes skinCluster skinPercent smoothCurve smoothTangentSurface smoothstep " + "snap2to2 snapKey snapMode snapTogetherCtx snapshot soft softMod softModCtx sort sound " + "soundControl source spaceLocator sphere sphrand spotLight spotLightPreviewPort " + "spreadSheetEditor spring sqrt squareSurface srtContext stackTrace startString " + "startsWith stitchAndExplodeShell stitchSurface stitchSurfacePoints strcmp " + "stringArrayCatenate stringArrayContains stringArrayCount stringArrayInsertAtIndex " + "stringArrayIntersector stringArrayRemove stringArrayRemoveAtIndex " + "stringArrayRemoveDuplicates stringArrayRemoveExact stringArrayToString " + "stringToStringArray strip stripPrefixFromName stroke subdAutoProjection " + "subdCleanTopology subdCollapse subdDuplicateAndConnect subdEditUV " + "subdListComponentConversion subdMapCut subdMapSewMove subdMatchTopology subdMirror " + "subdToBlind subdToPoly subdTransferUVsToCache subdiv subdivCrease " + "subdivDisplaySmoothness substitute substituteAllString substituteGeometry substring " + "surface surfaceSampler surfaceShaderList swatchDisplayPort switchTable symbolButton " + "symbolCheckBox sysFile system tabLayout tan tangentConstraint texLatticeDeformContext " + "texManipContext texMoveContext texMoveUVShellContext texRotateContext texScaleContext " + "texSelectContext texSelectShortestPathCtx texSmudgeUVContext texWinToolCtx text " + "textCurves textField textFieldButtonGrp textFieldGrp textManip textScrollList " + "textToShelf textureDisplacePlane textureHairColor texturePlacementContext " + "textureWindow threadCount threePointArcCtx timeControl timePort timerX toNativePath " + "toggle toggleAxis toggleWindowVisibility tokenize tokenizeList tolerance tolower " + "toolButton toolCollection toolDropped toolHasOptions toolPropertyWindow torus toupper " + "trace track trackCtx transferAttributes transformCompare transformLimits translator " + "trim trunc truncateFluidCache truncateHairCache tumble tumbleCtx turbulence " + "twoPointArcCtx uiRes uiTemplate unassignInputDevice undo undoInfo ungroup uniform unit " + "unloadPlugin untangleUV untitledFileName untrim upAxis updateAE userCtx uvLink " + "uvSnapshot validateShelfName vectorize view2dToolCtx viewCamera viewClipPlane " + "viewFit viewHeadOn viewLookAt viewManip viewPlace viewSet visor volumeAxis vortex " + "waitCursor warning webBrowser webBrowserPrefs whatIs window windowPref wire " + "wireContext workspace wrinkle wrinkleContext writeTake xbmLangPathList xform", illegal: " Description: Mercury is a logic/functional programming language which combines the clarity and expressiveness of declarative programming with advanced static analysis and error detection features. */ create: function (hljs) { var KEYWORDS = { keyword: "module use_module import_module include_module end_module initialise " + "mutable initialize finalize finalise interface implementation pred " + "mode func type inst solver any_pred any_func is semidet det nondet " + "multi erroneous failure cc_nondet cc_multi typeclass instance where " + "pragma promise external trace atomic or_else require_complete_switch " + "require_det require_semidet require_multi require_nondet " + "require_cc_multi require_cc_nondet require_erroneous require_failure", meta: // pragma "inline no_inline type_spec source_file fact_table obsolete memo " + "loop_check minimal_model terminates does_not_terminate " + "check_termination promise_equivalent_clauses " + // preprocessor "foreign_proc foreign_decl foreign_code foreign_type " + "foreign_import_module foreign_export_enum foreign_export " + "foreign_enum may_call_mercury will_not_call_mercury thread_safe " + "not_thread_safe maybe_thread_safe promise_pure promise_semipure " + "tabled_for_io local untrailed trailed attach_to_io_state " + "can_pass_as_mercury_type stable will_not_throw_exception " + "may_modify_trail will_not_modify_trail may_duplicate " + "may_not_duplicate affects_liveness does_not_affect_liveness " + "doesnt_affect_liveness no_sharing unknown_sharing sharing", built_in: "some all not if then else true fail false try catch catch_any " + "semidet_true semidet_false semidet_fail impure_true impure semipure", }; var COMMENT = hljs.COMMENT("%", "$"); var NUMCODE = { className: "number", begin: "0'.\\|0[box][0-9a-fA-F]*", }; var ATOM = hljs.inherit(hljs.APOS_STRING_MODE, { relevance: 0, }); var STRING = hljs.inherit(hljs.QUOTE_STRING_MODE, { relevance: 0, }); var STRING_FMT = { className: "subst", begin: "\\\\[abfnrtv]\\|\\\\x[0-9a-fA-F]*\\\\\\|%[-+# *.0-9]*[dioxXucsfeEgGp]", relevance: 0, }; STRING.contains.push(STRING_FMT); var IMPLICATION = { className: "built_in", variants: [ { begin: "<=>" }, { begin: "<=", relevance: 0 }, { begin: "=>", relevance: 0 }, { begin: "/\\\\" }, { begin: "\\\\/" }, ], }; var HEAD_BODY_CONJUNCTION = { className: "built_in", variants: [ { begin: ":-\\|-->" }, { begin: "=", relevance: 0 }, ], }; return { aliases: ["m", "moo"], keywords: KEYWORDS, contains: [ IMPLICATION, HEAD_BODY_CONJUNCTION, COMMENT, hljs.C_BLOCK_COMMENT_MODE, NUMCODE, hljs.NUMBER_MODE, ATOM, STRING, { begin: /:-/ }, // relevance booster ], }; }, }, { name: "mipsasm", /* Language: MIPS Assembly Author: Nebuleon Fumika Description: MIPS Assembly (up to MIPS32R2) Category: assembler */ create: function (hljs) { //local labels: %?[FB]?[AT]?\d{1,2}\w+ return { case_insensitive: true, aliases: ["mips"], lexemes: "\\.?" + hljs.IDENT_RE, keywords: { meta: //GNU preprocs ".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .ltorg ", built_in: "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 " + // integer registers "$16 $17 $18 $19 $20 $21 $22 $23 $24 $25 $26 $27 $28 $29 $30 $31 " + // integer registers "zero at v0 v1 a0 a1 a2 a3 a4 a5 a6 a7 " + // integer register aliases "t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 s0 s1 s2 s3 s4 s5 s6 s7 s8 " + // integer register aliases "k0 k1 gp sp fp ra " + // integer register aliases "$f0 $f1 $f2 $f2 $f4 $f5 $f6 $f7 $f8 $f9 $f10 $f11 $f12 $f13 $f14 $f15 " + // floating-point registers "$f16 $f17 $f18 $f19 $f20 $f21 $f22 $f23 $f24 $f25 $f26 $f27 $f28 $f29 $f30 $f31 " + // floating-point registers "Context Random EntryLo0 EntryLo1 Context PageMask Wired EntryHi " + // Coprocessor 0 registers "HWREna BadVAddr Count Compare SR IntCtl SRSCtl SRSMap Cause EPC PRId " + // Coprocessor 0 registers "EBase Config Config1 Config2 Config3 LLAddr Debug DEPC DESAVE CacheErr " + // Coprocessor 0 registers "ECC ErrorEPC TagLo DataLo TagHi DataHi WatchLo WatchHi PerfCtl PerfCnt ", // Coprocessor 0 registers }, contains: [ { className: "keyword", begin: "\\b(" + //mnemonics // 32-bit integer instructions "addi?u?|andi?|b(al)?|beql?|bgez(al)?l?|bgtzl?|blezl?|bltz(al)?l?|" + "bnel?|cl[oz]|divu?|ext|ins|j(al)?|jalr(\.hb)?|jr(\.hb)?|lbu?|lhu?|" + "ll|lui|lw[lr]?|maddu?|mfhi|mflo|movn|movz|move|msubu?|mthi|mtlo|mul|" + "multu?|nop|nor|ori?|rotrv?|sb|sc|se[bh]|sh|sllv?|slti?u?|srav?|" + "srlv?|subu?|sw[lr]?|xori?|wsbh|" + // floating-point instructions "abs\.[sd]|add\.[sd]|alnv.ps|bc1[ft]l?|" + "c\.(s?f|un|u?eq|[ou]lt|[ou]le|ngle?|seq|l[et]|ng[et])\.[sd]|" + "(ceil|floor|round|trunc)\.[lw]\.[sd]|cfc1|cvt\.d\.[lsw]|" + "cvt\.l\.[dsw]|cvt\.ps\.s|cvt\.s\.[dlw]|cvt\.s\.p[lu]|cvt\.w\.[dls]|" + "div\.[ds]|ldx?c1|luxc1|lwx?c1|madd\.[sd]|mfc1|mov[fntz]?\.[ds]|" + "msub\.[sd]|mth?c1|mul\.[ds]|neg\.[ds]|nmadd\.[ds]|nmsub\.[ds]|" + "p[lu][lu]\.ps|recip\.fmt|r?sqrt\.[ds]|sdx?c1|sub\.[ds]|suxc1|" + "swx?c1|" + // system control instructions "break|cache|d?eret|[de]i|ehb|mfc0|mtc0|pause|prefx?|rdhwr|" + "rdpgpr|sdbbp|ssnop|synci?|syscall|teqi?|tgei?u?|tlb(p|r|w[ir])|" + "tlti?u?|tnei?|wait|wrpgpr" + ")", end: "\\s", }, hljs.COMMENT("[;#]", "$"), hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, { className: "string", begin: "'", end: "[^\\\\]'", relevance: 0, }, { className: "title", begin: "\\|", end: "\\|", illegal: "\\n", relevance: 0, }, { className: "number", variants: [ { begin: "0x[0-9a-f]+" }, //hex { begin: "\\b-?\\d+" }, //bare number ], relevance: 0, }, { className: "symbol", variants: [ { begin: "^\\s*[a-z_\\.\\$][a-z0-9_\\.\\$]+:" }, //GNU MIPS syntax { begin: "^\\s*[0-9]+:" }, // numbered local labels { begin: "[0-9]+[bf]" }, // number local label reference (backwards, forwards) ], relevance: 0, }, ], illegal: "\/", }; }, }, { name: "mizar", /* Language: Mizar Author: Kelley van Evert Category: scientific */ create: function (hljs) { return { keywords: "environ vocabularies notations constructors definitions " + "registrations theorems schemes requirements begin end definition " + "registration cluster existence pred func defpred deffunc theorem " + "proof let take assume then thus hence ex for st holds consider " + "reconsider such that and in provided of as from be being by means " + "equals implies iff redefine define now not or attr is mode " + "suppose per cases set thesis contradiction scheme reserve struct " + "correctness compatibility coherence symmetry assymetry " + "reflexivity irreflexivity connectedness uniqueness commutativity " + "idempotence involutiveness projectivity", contains: [hljs.COMMENT("::", "$")], }; }, }, { name: "mojolicious", /* Language: Mojolicious Requires: xml.js, perl.js Author: Dotan Dimet Description: Mojolicious .ep (Embedded Perl) templates Category: template */ create: function (hljs) { return { subLanguage: "xml", contains: [ { className: "meta", begin: "^__(END|DATA)__$", }, // mojolicious line { begin: "^\\s*%{1,2}={0,2}", end: "$", subLanguage: "perl", }, // mojolicious block { begin: "<%{1,2}={0,2}", end: "={0,1}%>", subLanguage: "perl", excludeBegin: true, excludeEnd: true, }, ], }; }, }, { name: "monkey", /* Language: Monkey Author: Arthur Bikmullin */ create: function (hljs) { var NUMBER = { className: "number", relevance: 0, variants: [ { begin: "[$][a-fA-F0-9]+", }, hljs.NUMBER_MODE, ], }; return { case_insensitive: true, keywords: { keyword: "public private property continue exit extern new try catch " + "eachin not abstract final select case default const local global field " + "end if then else elseif endif while wend repeat until forever for " + "to step next return module inline throw import", built_in: "DebugLog DebugStop Error Print ACos ACosr ASin ASinr ATan ATan2 ATan2r ATanr Abs Abs Ceil " + "Clamp Clamp Cos Cosr Exp Floor Log Max Max Min Min Pow Sgn Sgn Sin Sinr Sqrt Tan Tanr Seed PI HALFPI TWOPI", literal: "true false null and or shl shr mod", }, illegal: /\/\*/, contains: [ hljs.COMMENT("#rem", "#end"), hljs.COMMENT("'", "$", { relevance: 0, }), { className: "function", beginKeywords: "function method", end: "[(=:]|$", illegal: /\n/, contains: [hljs.UNDERSCORE_TITLE_MODE], }, { className: "class", beginKeywords: "class interface", end: "$", contains: [ { beginKeywords: "extends implements", }, hljs.UNDERSCORE_TITLE_MODE, ], }, { className: "built_in", begin: "\\b(self|super)\\b", }, { className: "meta", begin: "\\s*#", end: "$", keywords: { "meta-keyword": "if else elseif endif end then", }, }, { className: "meta", begin: "^\\s*strict\\b", }, { beginKeywords: "alias", end: "=", contains: [hljs.UNDERSCORE_TITLE_MODE], }, hljs.QUOTE_STRING_MODE, NUMBER, ], }; }, }, { name: "moonscript", /* Language: MoonScript Author: Billy Quith Description: MoonScript is a programming language that transcompiles to Lua. For info about language see http://moonscript.org/ Origin: coffeescript.js Category: scripting */ create: function (hljs) { var KEYWORDS = { keyword: // Moonscript keywords "if then not for in while do return else elseif break continue switch and or " + "unless when class extends super local import export from using", literal: "true false nil", built_in: "_G _VERSION assert collectgarbage dofile error getfenv getmetatable ipairs load " + "loadfile loadstring module next pairs pcall print rawequal rawget rawset require " + "select setfenv setmetatable tonumber tostring type unpack xpcall coroutine debug " + "io math os package string table", }; var JS_IDENT_RE = "[A-Za-z$_][0-9A-Za-z$_]*"; var SUBST = { className: "subst", begin: /#\{/, end: /}/, keywords: KEYWORDS, }; var EXPRESSIONS = [ hljs.inherit(hljs.C_NUMBER_MODE, { starts: { end: "(\\s*/)?", relevance: 0 }, }), // a number tries to eat the following slash to prevent treating it as a regexp { className: "string", variants: [ { begin: /'/, end: /'/, contains: [hljs.BACKSLASH_ESCAPE], }, { begin: /"/, end: /"/, contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, ], }, { className: "built_in", begin: "@__" + hljs.IDENT_RE, }, { begin: "@" + hljs.IDENT_RE, // relevance booster on par with CoffeeScript }, { begin: hljs.IDENT_RE + "\\\\" + hljs.IDENT_RE, // inst\method }, ]; SUBST.contains = EXPRESSIONS; var TITLE = hljs.inherit(hljs.TITLE_MODE, { begin: JS_IDENT_RE, }); var PARAMS_RE = "(\\(.*\\))?\\s*\\B[-=]>"; var PARAMS = { className: "params", begin: "\\([^\\(]", returnBegin: true, /* We need another contained nameless mode to not have every nested pair of parens to be called "params" */ contains: [ { begin: /\(/, end: /\)/, keywords: KEYWORDS, contains: ["self"].concat(EXPRESSIONS), }, ], }; return { aliases: ["moon"], keywords: KEYWORDS, illegal: /\/\*/, contains: EXPRESSIONS.concat([ hljs.COMMENT("--", "$"), { className: "function", // function: -> => begin: "^\\s*" + JS_IDENT_RE + "\\s*=\\s*" + PARAMS_RE, end: "[-=]>", returnBegin: true, contains: [TITLE, PARAMS], }, { begin: /[\(,:=]\s*/, // anonymous function start relevance: 0, contains: [ { className: "function", begin: PARAMS_RE, end: "[-=]>", returnBegin: true, contains: [PARAMS], }, ], }, { className: "class", beginKeywords: "class", end: "$", illegal: /[:="\[\]]/, contains: [ { beginKeywords: "extends", endsWithParent: true, illegal: /[:="\[\]]/, contains: [TITLE], }, TITLE, ], }, { className: "name", // table begin: JS_IDENT_RE + ":", end: ":", returnBegin: true, returnEnd: true, relevance: 0, }, ]), }; }, }, { name: "n1ql", /* Language: N1QL Author: Andres Täht Contributors: Rene Saarsoo Description: Couchbase query language */ create: function (hljs) { return { case_insensitive: true, contains: [ { beginKeywords: "build create index delete drop explain infer|10 insert merge prepare select update upsert|10", end: /;/, endsWithParent: true, keywords: { // Taken from http://developer.couchbase.com/documentation/server/current/n1ql/n1ql-language-reference/reservedwords.html keyword: "all alter analyze and any array as asc begin between binary boolean break bucket build by call " + "case cast cluster collate collection commit connect continue correlate cover create database " + "dataset datastore declare decrement delete derived desc describe distinct do drop each element " + "else end every except exclude execute exists explain fetch first flatten for force from " + "function grant group gsi having if ignore ilike in include increment index infer inline inner " + "insert intersect into is join key keys keyspace known last left let letting like limit lsm map " + "mapping matched materialized merge minus namespace nest not number object offset on " + "option or order outer over parse partition password path pool prepare primary private privilege " + "procedure public raw realm reduce rename return returning revoke right role rollback satisfies " + "schema select self semi set show some start statistics string system then to transaction trigger " + "truncate under union unique unknown unnest unset update upsert use user using validate value " + "valued values via view when where while with within work xor", // Taken from http://developer.couchbase.com/documentation/server/4.5/n1ql/n1ql-language-reference/literals.html literal: "true false null missing|5", // Taken from http://developer.couchbase.com/documentation/server/4.5/n1ql/n1ql-language-reference/functions.html built_in: "array_agg array_append array_concat array_contains array_count array_distinct array_ifnull array_length " + "array_max array_min array_position array_prepend array_put array_range array_remove array_repeat array_replace " + "array_reverse array_sort array_sum avg count max min sum greatest least ifmissing ifmissingornull ifnull " + "missingif nullif ifinf ifnan ifnanorinf naninf neginfif posinfif clock_millis clock_str date_add_millis " + "date_add_str date_diff_millis date_diff_str date_part_millis date_part_str date_trunc_millis date_trunc_str " + "duration_to_str millis str_to_millis millis_to_str millis_to_utc millis_to_zone_name now_millis now_str " + "str_to_duration str_to_utc str_to_zone_name decode_json encode_json encoded_size poly_length base64 base64_encode " + "base64_decode meta uuid abs acos asin atan atan2 ceil cos degrees e exp ln log floor pi power radians random " + "round sign sin sqrt tan trunc object_length object_names object_pairs object_inner_pairs object_values " + "object_inner_values object_add object_put object_remove object_unwrap regexp_contains regexp_like regexp_position " + "regexp_replace contains initcap length lower ltrim position repeat replace rtrim split substr title trim upper " + "isarray isatom isboolean isnumber isobject isstring type toarray toatom toboolean tonumber toobject tostring", }, contains: [ { className: "string", begin: "'", end: "'", contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }, { className: "string", begin: '"', end: '"', contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }, { className: "symbol", begin: "`", end: "`", contains: [hljs.BACKSLASH_ESCAPE], relevance: 2, }, hljs.C_NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, hljs.C_BLOCK_COMMENT_MODE, ], }; }, }, { name: "nginx", /* Language: Nginx Author: Peter Leonov Contributors: Ivan Sagalaev Category: common, config */ create: function (hljs) { var VAR = { className: "variable", variants: [ { begin: /\$\d+/ }, { begin: /\$\{/, end: /}/ }, { begin: "[\\$\\@]" + hljs.UNDERSCORE_IDENT_RE }, ], }; var DEFAULT = { endsWithParent: true, lexemes: "[a-z/_]+", keywords: { literal: "on off yes no true false none blocked debug info notice warn error crit " + "select break last permanent redirect kqueue rtsig epoll poll /dev/poll", }, relevance: 0, illegal: "=>", contains: [ hljs.HASH_COMMENT_MODE, { className: "string", contains: [hljs.BACKSLASH_ESCAPE, VAR], variants: [ { begin: /"/, end: /"/ }, { begin: /'/, end: /'/ }, ], }, // this swallows entire URLs to avoid detecting numbers within { begin: "([a-z]+):/", end: "\\s", endsWithParent: true, excludeEnd: true, contains: [VAR], }, { className: "regexp", contains: [hljs.BACKSLASH_ESCAPE, VAR], variants: [ { begin: "\\s\\^", end: "\\s|{|;", returnEnd: true }, // regexp locations (~, ~*) { begin: "~\\*?\\s+", end: "\\s|{|;", returnEnd: true }, // *.example.com { begin: "\\*(\\.[a-z\\-]+)+" }, // sub.example.* { begin: "([a-z\\-]+\\.)+\\*" }, ], }, // IP { className: "number", begin: "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b", }, // units { className: "number", begin: "\\b\\d+[kKmMgGdshdwy]*\\b", relevance: 0, }, VAR, ], }; return { aliases: ["nginxconf"], contains: [ hljs.HASH_COMMENT_MODE, { begin: hljs.UNDERSCORE_IDENT_RE + "\\s+{", returnBegin: true, end: "{", contains: [ { className: "section", begin: hljs.UNDERSCORE_IDENT_RE, }, ], relevance: 0, }, { begin: hljs.UNDERSCORE_IDENT_RE + "\\s", end: ";|{", returnBegin: true, contains: [ { className: "attribute", begin: hljs.UNDERSCORE_IDENT_RE, starts: DEFAULT, }, ], relevance: 0, }, ], illegal: "[^\\s\\}]", }; }, }, { name: "nimrod", /* Language: Nimrod */ create: function (hljs) { return { aliases: ["nim"], keywords: { keyword: "addr and as asm bind block break case cast const continue converter " + "discard distinct div do elif else end enum except export finally " + "for from generic if import in include interface is isnot iterator " + "let macro method mixin mod nil not notin object of or out proc ptr " + "raise ref return shl shr static template try tuple type using var " + "when while with without xor yield", literal: "shared guarded stdin stdout stderr result true false", built_in: "int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 float " + "float32 float64 bool char string cstring pointer expr stmt void " + "auto any range array openarray varargs seq set clong culong cchar " + "cschar cshort cint csize clonglong cfloat cdouble clongdouble " + "cuchar cushort cuint culonglong cstringarray semistatic", }, contains: [ { className: "meta", // Actually pragma begin: /{\./, end: /\.}/, relevance: 10, }, { className: "string", begin: /[a-zA-Z]\w*"/, end: /"/, contains: [{ begin: /""/ }], }, { className: "string", begin: /([a-zA-Z]\w*)?"""/, end: /"""/, }, hljs.QUOTE_STRING_MODE, { className: "type", begin: /\b[A-Z]\w+\b/, relevance: 0, }, { className: "number", relevance: 0, variants: [ { begin: /\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/, }, { begin: /\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/, }, { begin: /\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/, }, { begin: /\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/ }, ], }, hljs.HASH_COMMENT_MODE, ], }; }, }, { name: "nix", /* Language: Nix Author: Domen Kožar Description: Nix functional language. See http://nixos.org/nix */ create: function (hljs) { var NIX_KEYWORDS = { keyword: "rec with let in inherit assert if else then", literal: "true false or and null", built_in: "import abort baseNameOf dirOf isNull builtins map removeAttrs throw " + "toString derivation", }; var ANTIQUOTE = { className: "subst", begin: /\$\{/, end: /}/, keywords: NIX_KEYWORDS, }; var ATTRS = { begin: /[a-zA-Z0-9-_]+(\s*=)/, returnBegin: true, relevance: 0, contains: [ { className: "attr", begin: /\S+/, }, ], }; var STRING = { className: "string", contains: [ANTIQUOTE], variants: [ { begin: "''", end: "''" }, { begin: '"', end: '"' }, ], }; var EXPRESSIONS = [ hljs.NUMBER_MODE, hljs.HASH_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, STRING, ATTRS, ]; ANTIQUOTE.contains = EXPRESSIONS; return { aliases: ["nixos"], keywords: NIX_KEYWORDS, contains: EXPRESSIONS, }; }, }, { name: "nsis", /* Language: NSIS Description: Nullsoft Scriptable Install System Author: Jan T. Sott Website: http://github.com/idleberg */ create: function (hljs) { var CONSTANTS = { className: "variable", begin: /\$(ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES32|COMMONFILES64|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES32|PROGRAMFILES64|PROGRAMFILES|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)/, }; var DEFINES = { // ${defines} className: "variable", begin: /\$+{[\w\.:-]+}/, }; var VARIABLES = { // $variables className: "variable", begin: /\$+\w+/, illegal: /\(\){}/, }; var LANGUAGES = { // $(language_strings) className: "variable", begin: /\$+\([\w\^\.:-]+\)/, }; var PARAMETERS = { // command parameters className: "params", begin: "(ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)", }; var COMPILER = { // !compiler_flags className: "keyword", begin: /\!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversion|gettlbversion|if|ifdef|ifmacrodef|ifmacrondef|ifndef|include|insertmacro|macro|macroend|makensis|packhdr|searchparse|searchreplace|system|tempfile|undef|verbose|warning)/, }; var METACHARS = { // $\n, $\r, $\t, $$ className: "meta", begin: /\$(\\[nrt]|\$)/, }; var PLUGINS = { // plug::ins className: "class", begin: /\w+\:\:\w+/, }; var STRING = { className: "string", variants: [ { begin: '"', end: '"', }, { begin: "'", end: "'", }, { begin: "`", end: "`", }, ], illegal: /\n/, contains: [ METACHARS, CONSTANTS, DEFINES, VARIABLES, LANGUAGES, ], }; return { case_insensitive: false, keywords: { keyword: "Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ChangeUI CheckBitmap ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exch Exec ExecShell ExecShellWait ExecWait ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileReadUTF16LE FileReadWord FileSeek FileWrite FileWriteByte FileWriteUTF16LE FileWriteWord FindClose FindFirst FindNext FindWindow FlushINI FunctionEnd GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetLabelAddress GetTempFileName Goto HideWindow Icon IfAbort IfErrors IfFileExists IfRebootFlag IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText Int64Cmp Int64CmpU Int64Fmt IntCmp IntCmpU IntFmt IntOp IntPtrCmp IntPtrCmpU IntPtrOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadLanguageFile LockWindow LogSet LogText ManifestDPIAware ManifestSupportedOS MessageBox MiscButtonText Name Nop OutFile Page PageCallbacks PageExEnd Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename RequestExecutionLevel ReserveFile Return RMDir SearchPath SectionEnd SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionGroupEnd SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetRebootFlag SetRegView SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCmpS StrCpy StrLen SubCaption Unicode UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIFileVersion VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegMultiStr WriteRegNone WriteRegStr WriteUninstaller XPStyle", literal: "admin all auto both bottom bzip2 colored components current custom directory false force hide highest ifdiff ifnewer instfiles lastused leave left license listonly lzma nevershow none normal notset off on open print right show silent silentlog smooth textonly top true try un.components un.custom un.directory un.instfiles un.license uninstConfirm user Win10 Win7 Win8 WinVista zlib", }, contains: [ hljs.HASH_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.COMMENT(";", "$", { relevance: 0, }), { className: "function", beginKeywords: "Function PageEx Section SectionGroup", end: "$", }, STRING, COMPILER, DEFINES, VARIABLES, LANGUAGES, PARAMETERS, PLUGINS, hljs.NUMBER_MODE, ], }; }, }, { name: "objectivec", /* Language: Objective-C Author: Valerii Hiora Contributors: Angel G. Olloqui , Matt Diephouse , Andrew Farmer , Minh Nguyễn Category: common */ create: function (hljs) { var API_CLASS = { className: "built_in", begin: "\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+", }; var OBJC_KEYWORDS = { keyword: "int float while char export sizeof typedef const struct for union " + "unsigned long volatile static bool mutable if do return goto void " + "enum else break extern asm case short default double register explicit " + "signed typename this switch continue wchar_t inline readonly assign " + "readwrite self @synchronized id typeof " + "nonatomic super unichar IBOutlet IBAction strong weak copy " + "in out inout bycopy byref oneway __strong __weak __block __autoreleasing " + "@private @protected @public @try @property @end @throw @catch @finally " + "@autoreleasepool @synthesize @dynamic @selector @optional @required " + "@encode @package @import @defs @compatibility_alias " + "__bridge __bridge_transfer __bridge_retained __bridge_retain " + "__covariant __contravariant __kindof " + "_Nonnull _Nullable _Null_unspecified " + "__FUNCTION__ __PRETTY_FUNCTION__ __attribute__ " + "getter setter retain unsafe_unretained " + "nonnull nullable null_unspecified null_resettable class instancetype " + "NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER " + "NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED " + "NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE " + "NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END " + "NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW " + "NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN", literal: "false true FALSE TRUE nil YES NO NULL", built_in: "BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once", }; var LEXEMES = /[a-zA-Z@][a-zA-Z0-9_]*/; var CLASS_KEYWORDS = "@interface @class @protocol @implementation"; return { aliases: ["mm", "objc", "obj-c"], keywords: OBJC_KEYWORDS, lexemes: LEXEMES, illegal: "" }, ], }, ], }, { className: "class", begin: "(" + CLASS_KEYWORDS.split(" ").join("|") + ")\\b", end: "({|$)", excludeEnd: true, keywords: CLASS_KEYWORDS, lexemes: LEXEMES, contains: [hljs.UNDERSCORE_TITLE_MODE], }, { begin: "\\." + hljs.UNDERSCORE_IDENT_RE, relevance: 0, }, ], }; }, }, { name: "ocaml", /* Language: OCaml Author: Mehdi Dogguy Contributors: Nicolas Braud-Santoni , Mickael Delahaye Description: OCaml language definition. Category: functional */ create: function (hljs) { /* missing support for heredoc-like string (OCaml 4.0.2+) */ return { aliases: ["ml"], keywords: { keyword: "and as assert asr begin class constraint do done downto else end " + "exception external for fun function functor if in include " + "inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method " + "mod module mutable new object of open! open or private rec sig struct " + "then to try type val! val virtual when while with " + /* camlp4 */ "parser value", built_in: /* built-in types */ "array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit " + /* (some) types in Pervasives */ "in_channel out_channel ref", literal: "true false", }, illegal: /\/\/|>>/, lexemes: "[a-z_]\\w*!?", contains: [ { className: "literal", begin: "\\[(\\|\\|)?\\]|\\(\\)", relevance: 0, }, hljs.COMMENT("\\(\\*", "\\*\\)", { contains: ["self"], }), { /* type variable */ className: "symbol", begin: "'[A-Za-z_](?!')[\\w']*", /* the grammar is ambiguous on how 'a'b should be interpreted but not the compiler */ }, { /* polymorphic variant */ className: "type", begin: "`[A-Z][\\w']*", }, { /* module or constructor */ className: "type", begin: "\\b[A-Z][\\w']*", relevance: 0, }, { /* don't color identifiers, but safely catch all identifiers with '*/ begin: "[a-z_]\\w*'[\\w']*", relevance: 0, }, hljs.inherit(hljs.APOS_STRING_MODE, { className: "string", relevance: 0, }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), { className: "number", begin: "\\b(0[xX][a-fA-F0-9_]+[Lln]?|" + "0[oO][0-7_]+[Lln]?|" + "0[bB][01_]+[Lln]?|" + "[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)", relevance: 0, }, { begin: /[-=]>/, // relevance booster }, ], }; }, }, { name: "openscad", /* Language: OpenSCAD Author: Dan Panzarella Description: OpenSCAD is a language for the 3D CAD modeling software of the same name. Category: scientific */ create: function (hljs) { var SPECIAL_VARS = { className: "keyword", begin: "\\$(f[asn]|t|vp[rtd]|children)", }, LITERALS = { className: "literal", begin: "false|true|PI|undef", }, NUMBERS = { className: "number", begin: "\\b\\d+(\\.\\d+)?(e-?\\d+)?", //adds 1e5, 1e-10 relevance: 0, }, STRING = hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null, }), PREPRO = { className: "meta", keywords: { "meta-keyword": "include use" }, begin: "include|use <", end: ">", }, PARAMS = { className: "params", begin: "\\(", end: "\\)", contains: ["self", NUMBERS, STRING, SPECIAL_VARS, LITERALS], }, MODIFIERS = { begin: "[*!#%]", relevance: 0, }, FUNCTIONS = { className: "function", beginKeywords: "module function", end: "\\=|\\{", contains: [PARAMS, hljs.UNDERSCORE_TITLE_MODE], }; return { aliases: ["scad"], keywords: { keyword: "function module include use for intersection_for if else \\%", literal: "false true PI undef", built_in: "circle square polygon text sphere cube cylinder polyhedron translate rotate scale resize mirror multmatrix color offset hull minkowski union difference intersection abs sign sin cos tan acos asin atan atan2 floor round ceil ln log pow sqrt exp rands min max concat lookup str chr search version version_num norm cross parent_module echo import import_dxf dxf_linear_extrude linear_extrude rotate_extrude surface projection render children dxf_cross dxf_dim let assign", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, NUMBERS, PREPRO, STRING, SPECIAL_VARS, MODIFIERS, FUNCTIONS, ], }; }, }, { name: "oxygene", /* Language: Oxygene Author: Carlo Kok Description: Language definition for RemObjects Oxygene (http://www.remobjects.com) */ create: function (hljs) { var OXYGENE_KEYWORDS = "abstract add and array as asc aspect assembly async begin break block by case class concat const copy constructor continue " + "create default delegate desc distinct div do downto dynamic each else empty end ensure enum equals event except exit extension external false " + "final finalize finalizer finally flags for forward from function future global group has if implementation implements implies in index inherited " + "inline interface into invariants is iterator join locked locking loop matching method mod module namespace nested new nil not notify nullable of " + "old on operator or order out override parallel params partial pinned private procedure property protected public queryable raise read readonly " + "record reintroduce remove repeat require result reverse sealed select self sequence set shl shr skip static step soft take then to true try tuple " + "type union unit unsafe until uses using var virtual raises volatile where while with write xor yield await mapped deprecated stdcall cdecl pascal " + "register safecall overload library platform reference packed strict published autoreleasepool selector strong weak unretained"; var CURLY_COMMENT = hljs.COMMENT("{", "}", { relevance: 0, }); var PAREN_COMMENT = hljs.COMMENT("\\(\\*", "\\*\\)", { relevance: 10, }); var STRING = { className: "string", begin: "'", end: "'", contains: [{ begin: "''" }], }; var CHAR_STRING = { className: "string", begin: "(#\\d+)+", }; var FUNCTION = { className: "function", beginKeywords: "function constructor destructor procedure method", end: "[:;]", keywords: "function constructor|10 destructor|10 procedure|10 method|10", contains: [ hljs.TITLE_MODE, { className: "params", begin: "\\(", end: "\\)", keywords: OXYGENE_KEYWORDS, contains: [STRING, CHAR_STRING], }, CURLY_COMMENT, PAREN_COMMENT, ], }; return { case_insensitive: true, lexemes: /\.?\w+/, keywords: OXYGENE_KEYWORDS, illegal: '("|\\$[G-Zg-z]|\\/\\*||->)', contains: [ CURLY_COMMENT, PAREN_COMMENT, hljs.C_LINE_COMMENT_MODE, STRING, CHAR_STRING, hljs.NUMBER_MODE, FUNCTION, { className: "class", begin: "=\\bclass\\b", end: "end;", keywords: OXYGENE_KEYWORDS, contains: [ STRING, CHAR_STRING, CURLY_COMMENT, PAREN_COMMENT, hljs.C_LINE_COMMENT_MODE, FUNCTION, ], }, ], }; }, }, { name: "parser3", /* Language: Parser3 Requires: xml.js Author: Oleg Volchkov Category: template */ create: function (hljs) { var CURLY_SUBCOMMENT = hljs.COMMENT("{", "}", { contains: ["self"], }); return { subLanguage: "xml", relevance: 0, contains: [ hljs.COMMENT("^#", "$"), hljs.COMMENT("\\^rem{", "}", { relevance: 10, contains: [CURLY_SUBCOMMENT], }), { className: "meta", begin: "^@(?:BASE|USE|CLASS|OPTIONS)$", relevance: 10, }, { className: "title", begin: "@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$", }, { className: "variable", begin: "\\$\\{?[\\w\\-\\.\\:]+\\}?", }, { className: "keyword", begin: "\\^[\\w\\-\\.\\:]+", }, { className: "number", begin: "\\^#[0-9a-fA-F]+", }, hljs.C_NUMBER_MODE, ], }; }, }, { name: "perl", /* Language: Perl Author: Peter Leonov Category: common */ create: function (hljs) { var PERL_KEYWORDS = "getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc " + "ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime " + "readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq" + "fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent " + "shutdown dump chomp connect getsockname die socketpair close flock exists index shmget" + "sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr " + "unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 " + "getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline " + "endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand " + "mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink " + "getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr " + "untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link " + "getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller " + "lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and " + "sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 " + "chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach " + "tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir" + "ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe " + "atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"; var SUBST = { className: "subst", begin: "[$@]\\{", end: "\\}", keywords: PERL_KEYWORDS, }; var METHOD = { begin: "->{", end: "}", // contains defined later }; var VAR = { variants: [ { begin: /\$\d/ }, { begin: /[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/ }, { begin: /[\$%@][^\s\w{]/, relevance: 0 }, ], }; var STRING_CONTAINS = [hljs.BACKSLASH_ESCAPE, SUBST, VAR]; var PERL_DEFAULT_CONTAINS = [ VAR, hljs.HASH_COMMENT_MODE, hljs.COMMENT("^\\=\\w", "\\=cut", { endsWithParent: true, }), METHOD, { className: "string", contains: STRING_CONTAINS, variants: [ { begin: "q[qwxr]?\\s*\\(", end: "\\)", relevance: 5, }, { begin: "q[qwxr]?\\s*\\[", end: "\\]", relevance: 5, }, { begin: "q[qwxr]?\\s*\\{", end: "\\}", relevance: 5, }, { begin: "q[qwxr]?\\s*\\|", end: "\\|", relevance: 5, }, { begin: "q[qwxr]?\\s*\\<", end: "\\>", relevance: 5, }, { begin: "qw\\s+q", end: "q", relevance: 5, }, { begin: "'", end: "'", contains: [hljs.BACKSLASH_ESCAPE], }, { begin: '"', end: '"', }, { begin: "`", end: "`", contains: [hljs.BACKSLASH_ESCAPE], }, { begin: "{\\w+}", contains: [], relevance: 0, }, { begin: "\-?\\w+\\s*\\=\\>", contains: [], relevance: 0, }, ], }, { className: "number", begin: "(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", relevance: 0, }, { // regexp container begin: "(\\/\\/|" + hljs.RE_STARTERS_RE + "|\\b(split|return|print|reverse|grep)\\b)\\s*", keywords: "split return print reverse grep", relevance: 0, contains: [ hljs.HASH_COMMENT_MODE, { className: "regexp", begin: "(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*", relevance: 10, }, { className: "regexp", begin: "(m|qr)?/", end: "/[a-z]*", contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, // allows empty "//" which is a common comment delimiter in other languages }, ], }, { className: "function", beginKeywords: "sub", end: "(\\s*\\(.*?\\))?[;{]", excludeEnd: true, relevance: 5, contains: [hljs.TITLE_MODE], }, { begin: "-\\w\\b", relevance: 0, }, { begin: "^__DATA__$", end: "^__END__$", subLanguage: "mojolicious", contains: [ { begin: "^@@.*", end: "$", className: "comment", }, ], }, ]; SUBST.contains = PERL_DEFAULT_CONTAINS; METHOD.contains = PERL_DEFAULT_CONTAINS; return { aliases: ["pl", "pm"], lexemes: /[\w\.]+/, keywords: PERL_KEYWORDS, contains: PERL_DEFAULT_CONTAINS, }; }, }, { name: "pf", /* Language: pf Category: config Author: Peter Piwowarski Description: The pf.conf(5) format as of OpenBSD 5.6 */ create: function (hljs) { var MACRO = { className: "variable", begin: /\$[\w\d#@][\w\d_]*/, }; var TABLE = { className: "variable", begin: /<(?!\/)/, end: />/, }; var QUOTE_STRING = { className: "string", begin: /"/, end: /"/, }; return { aliases: ["pf.conf"], lexemes: /[a-z0-9_<>-]+/, keywords: { /* block match pass are "actions" in pf.conf(5), the rest are * lexically similar top-level commands. */ built_in: "block match pass load anchor|5 antispoof|10 set table", keyword: "in out log quick on rdomain inet inet6 proto from port os to route" + "allow-opts divert-packet divert-reply divert-to flags group icmp-type" + "icmp6-type label once probability recieved-on rtable prio queue" + "tos tag tagged user keep fragment for os drop" + "af-to|10 binat-to|10 nat-to|10 rdr-to|10 bitmask least-stats random round-robin" + "source-hash static-port" + "dup-to reply-to route-to" + "parent bandwidth default min max qlimit" + "block-policy debug fingerprints hostid limit loginterface optimization" + "reassemble ruleset-optimization basic none profile skip state-defaults" + "state-policy timeout" + "const counters persist" + "no modulate synproxy state|5 floating if-bound no-sync pflow|10 sloppy" + "source-track global rule max-src-nodes max-src-states max-src-conn" + "max-src-conn-rate overload flush" + "scrub|5 max-mss min-ttl no-df|10 random-id", literal: "all any no-route self urpf-failed egress|5 unknown", }, contains: [ hljs.HASH_COMMENT_MODE, hljs.NUMBER_MODE, hljs.QUOTE_STRING_MODE, MACRO, TABLE, ], }; }, }, { name: "pgsql", /* Language: PostgreSQL SQL dialect and PL/pgSQL Author: Egor Rogov (e.rogov@postgrespro.ru) Description: This language incorporates both PostgreSQL SQL dialect and PL/pgSQL language. It is based on PostgreSQL version 11. Some notes: - Text in double-dollar-strings is _always_ interpreted as some programming code. Text in ordinary quotes is _never_ interpreted that way and highlighted just as a string. - There are quite a bit "special cases". That's because many keywords are not strictly they are keywords in some contexts and ordinary identifiers in others. Only some of such cases are handled; you still can get some of your identifiers highlighted wrong way. - Function names deliberately are not highlighted. There is no way to tell function call from other constructs, hence we can't highlight _all_ function names. And some names highlighted while others not looks ugly. */ create: function (hljs) { var COMMENT_MODE = hljs.COMMENT("--", "$"); var UNQUOTED_IDENT = "[a-zA-Z_][a-zA-Z_0-9$]*"; var DOLLAR_STRING = "\\$([a-zA-Z_]?|[a-zA-Z_][a-zA-Z_0-9]*)\\$"; var LABEL = "<<\\s*" + UNQUOTED_IDENT + "\\s*>>"; var SQL_KW = // https://www.postgresql.org/docs/11/static/sql-keywords-appendix.html // https://www.postgresql.org/docs/11/static/sql-commands.html // SQL commands (starting words) "ABORT ALTER ANALYZE BEGIN CALL CHECKPOINT|10 CLOSE CLUSTER COMMENT COMMIT COPY CREATE DEALLOCATE DECLARE " + "DELETE DISCARD DO DROP END EXECUTE EXPLAIN FETCH GRANT IMPORT INSERT LISTEN LOAD LOCK MOVE NOTIFY " + "PREPARE REASSIGN|10 REFRESH REINDEX RELEASE RESET REVOKE ROLLBACK SAVEPOINT SECURITY SELECT SET SHOW " + "START TRUNCATE UNLISTEN|10 UPDATE VACUUM|10 VALUES " + // SQL commands (others) "AGGREGATE COLLATION CONVERSION|10 DATABASE DEFAULT PRIVILEGES DOMAIN TRIGGER EXTENSION FOREIGN " + "WRAPPER|10 TABLE FUNCTION GROUP LANGUAGE LARGE OBJECT MATERIALIZED VIEW OPERATOR CLASS " + "FAMILY POLICY PUBLICATION|10 ROLE RULE SCHEMA SEQUENCE SERVER STATISTICS SUBSCRIPTION SYSTEM " + "TABLESPACE CONFIGURATION DICTIONARY PARSER TEMPLATE TYPE USER MAPPING PREPARED ACCESS " + "METHOD CAST AS TRANSFORM TRANSACTION OWNED TO INTO SESSION AUTHORIZATION " + "INDEX PROCEDURE ASSERTION " + // additional reserved key words "ALL ANALYSE AND ANY ARRAY ASC ASYMMETRIC|10 BOTH CASE CHECK " + "COLLATE COLUMN CONCURRENTLY|10 CONSTRAINT CROSS " + "DEFERRABLE RANGE " + "DESC DISTINCT ELSE EXCEPT FOR FREEZE|10 FROM FULL HAVING " + "ILIKE IN INITIALLY INNER INTERSECT IS ISNULL JOIN LATERAL LEADING LIKE LIMIT " + "NATURAL NOT NOTNULL NULL OFFSET ON ONLY OR ORDER OUTER OVERLAPS PLACING PRIMARY " + "REFERENCES RETURNING SIMILAR SOME SYMMETRIC TABLESAMPLE THEN " + "TRAILING UNION UNIQUE USING VARIADIC|10 VERBOSE WHEN WHERE WINDOW WITH " + // some of non-reserved (which are used in clauses or as PL/pgSQL keyword) "BY RETURNS INOUT OUT SETOF|10 IF STRICT CURRENT CONTINUE OWNER LOCATION OVER PARTITION WITHIN " + "BETWEEN ESCAPE EXTERNAL INVOKER DEFINER WORK RENAME VERSION CONNECTION CONNECT " + "TABLES TEMP TEMPORARY FUNCTIONS SEQUENCES TYPES SCHEMAS OPTION CASCADE RESTRICT ADD ADMIN " + "EXISTS VALID VALIDATE ENABLE DISABLE REPLICA|10 ALWAYS PASSING COLUMNS PATH " + "REF VALUE OVERRIDING IMMUTABLE STABLE VOLATILE BEFORE AFTER EACH ROW PROCEDURAL " + "ROUTINE NO HANDLER VALIDATOR OPTIONS STORAGE OIDS|10 WITHOUT INHERIT DEPENDS CALLED " + "INPUT LEAKPROOF|10 COST ROWS NOWAIT SEARCH UNTIL ENCRYPTED|10 PASSWORD CONFLICT|10 " + "INSTEAD INHERITS CHARACTERISTICS WRITE CURSOR ALSO STATEMENT SHARE EXCLUSIVE INLINE " + "ISOLATION REPEATABLE READ COMMITTED SERIALIZABLE UNCOMMITTED LOCAL GLOBAL SQL PROCEDURES " + "RECURSIVE SNAPSHOT ROLLUP CUBE TRUSTED|10 INCLUDE FOLLOWING PRECEDING UNBOUNDED RANGE GROUPS " + "UNENCRYPTED|10 SYSID FORMAT DELIMITER HEADER QUOTE ENCODING FILTER OFF " + // some parameters of VACUUM/ANALYZE/EXPLAIN "FORCE_QUOTE FORCE_NOT_NULL FORCE_NULL COSTS BUFFERS TIMING SUMMARY DISABLE_PAGE_SKIPPING " + // "RESTART CYCLE GENERATED IDENTITY DEFERRED IMMEDIATE LEVEL LOGGED UNLOGGED " + "OF NOTHING NONE EXCLUDE ATTRIBUTE " + // from GRANT (not keywords actually) "USAGE ROUTINES " + // actually literals, but look better this way (due to IS TRUE, IS FALSE, ISNULL etc) "TRUE FALSE NAN INFINITY "; var ROLE_ATTRS = // only those not in keywrods already "SUPERUSER NOSUPERUSER CREATEDB NOCREATEDB CREATEROLE NOCREATEROLE INHERIT NOINHERIT " + "LOGIN NOLOGIN REPLICATION NOREPLICATION BYPASSRLS NOBYPASSRLS "; var PLPGSQL_KW = "ALIAS BEGIN CONSTANT DECLARE END EXCEPTION RETURN PERFORM|10 RAISE GET DIAGNOSTICS " + "STACKED|10 FOREACH LOOP ELSIF EXIT WHILE REVERSE SLICE DEBUG LOG INFO NOTICE WARNING ASSERT " + "OPEN "; var TYPES = // https://www.postgresql.org/docs/11/static/datatype.html "BIGINT INT8 BIGSERIAL SERIAL8 BIT VARYING VARBIT BOOLEAN BOOL BOX BYTEA CHARACTER CHAR VARCHAR " + "CIDR CIRCLE DATE DOUBLE PRECISION FLOAT8 FLOAT INET INTEGER INT INT4 INTERVAL JSON JSONB LINE LSEG|10 " + "MACADDR MACADDR8 MONEY NUMERIC DEC DECIMAL PATH POINT POLYGON REAL FLOAT4 SMALLINT INT2 " + "SMALLSERIAL|10 SERIAL2|10 SERIAL|10 SERIAL4|10 TEXT TIME ZONE TIMETZ|10 TIMESTAMP TIMESTAMPTZ|10 TSQUERY|10 TSVECTOR|10 " + "TXID_SNAPSHOT|10 UUID XML NATIONAL NCHAR " + "INT4RANGE|10 INT8RANGE|10 NUMRANGE|10 TSRANGE|10 TSTZRANGE|10 DATERANGE|10 " + // pseudotypes "ANYELEMENT ANYARRAY ANYNONARRAY ANYENUM ANYRANGE CSTRING INTERNAL " + "RECORD PG_DDL_COMMAND VOID UNKNOWN OPAQUE REFCURSOR " + // spec. type "NAME " + // OID-types "OID REGPROC|10 REGPROCEDURE|10 REGOPER|10 REGOPERATOR|10 REGCLASS|10 REGTYPE|10 REGROLE|10 " + "REGNAMESPACE|10 REGCONFIG|10 REGDICTIONARY|10 "; // + // some types from standard extensions ("HSTORE|10 LO LTREE|10 "); var TYPES_RE = TYPES.trim() .split(" ") .map(function (val) { return val.split("|")[0]; }) .join("|"); var SQL_BI = "CURRENT_TIME CURRENT_TIMESTAMP CURRENT_USER CURRENT_CATALOG|10 CURRENT_DATE LOCALTIME LOCALTIMESTAMP " + "CURRENT_ROLE|10 CURRENT_SCHEMA|10 SESSION_USER PUBLIC "; var PLPGSQL_BI = "FOUND NEW OLD TG_NAME|10 TG_WHEN|10 TG_LEVEL|10 TG_OP|10 TG_RELID|10 TG_RELNAME|10 " + "TG_TABLE_NAME|10 TG_TABLE_SCHEMA|10 TG_NARGS|10 TG_ARGV|10 TG_EVENT|10 TG_TAG|10 " + // get diagnostics "ROW_COUNT RESULT_OID|10 PG_CONTEXT|10 RETURNED_SQLSTATE COLUMN_NAME CONSTRAINT_NAME " + "PG_DATATYPE_NAME|10 MESSAGE_TEXT TABLE_NAME SCHEMA_NAME PG_EXCEPTION_DETAIL|10 " + "PG_EXCEPTION_HINT|10 PG_EXCEPTION_CONTEXT|10 "; var PLPGSQL_EXCEPTIONS = // exceptions https://www.postgresql.org/docs/current/static/errcodes-appendix.html "SQLSTATE SQLERRM|10 " + "SUCCESSFUL_COMPLETION WARNING DYNAMIC_RESULT_SETS_RETURNED IMPLICIT_ZERO_BIT_PADDING " + "NULL_VALUE_ELIMINATED_IN_SET_FUNCTION PRIVILEGE_NOT_GRANTED PRIVILEGE_NOT_REVOKED " + "STRING_DATA_RIGHT_TRUNCATION DEPRECATED_FEATURE NO_DATA NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED " + "SQL_STATEMENT_NOT_YET_COMPLETE CONNECTION_EXCEPTION CONNECTION_DOES_NOT_EXIST CONNECTION_FAILURE " + "SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION " + "TRANSACTION_RESOLUTION_UNKNOWN PROTOCOL_VIOLATION TRIGGERED_ACTION_EXCEPTION FEATURE_NOT_SUPPORTED " + "INVALID_TRANSACTION_INITIATION LOCATOR_EXCEPTION INVALID_LOCATOR_SPECIFICATION INVALID_GRANTOR " + "INVALID_GRANT_OPERATION INVALID_ROLE_SPECIFICATION DIAGNOSTICS_EXCEPTION " + "STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER CASE_NOT_FOUND CARDINALITY_VIOLATION " + "DATA_EXCEPTION ARRAY_SUBSCRIPT_ERROR CHARACTER_NOT_IN_REPERTOIRE DATETIME_FIELD_OVERFLOW " + "DIVISION_BY_ZERO ERROR_IN_ASSIGNMENT ESCAPE_CHARACTER_CONFLICT INDICATOR_OVERFLOW " + "INTERVAL_FIELD_OVERFLOW INVALID_ARGUMENT_FOR_LOGARITHM INVALID_ARGUMENT_FOR_NTILE_FUNCTION " + "INVALID_ARGUMENT_FOR_NTH_VALUE_FUNCTION INVALID_ARGUMENT_FOR_POWER_FUNCTION " + "INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION INVALID_CHARACTER_VALUE_FOR_CAST " + "INVALID_DATETIME_FORMAT INVALID_ESCAPE_CHARACTER INVALID_ESCAPE_OCTET INVALID_ESCAPE_SEQUENCE " + "NONSTANDARD_USE_OF_ESCAPE_CHARACTER INVALID_INDICATOR_PARAMETER_VALUE INVALID_PARAMETER_VALUE " + "INVALID_REGULAR_EXPRESSION INVALID_ROW_COUNT_IN_LIMIT_CLAUSE " + "INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE INVALID_TABLESAMPLE_ARGUMENT INVALID_TABLESAMPLE_REPEAT " + "INVALID_TIME_ZONE_DISPLACEMENT_VALUE INVALID_USE_OF_ESCAPE_CHARACTER MOST_SPECIFIC_TYPE_MISMATCH " + "NULL_VALUE_NOT_ALLOWED NULL_VALUE_NO_INDICATOR_PARAMETER NUMERIC_VALUE_OUT_OF_RANGE " + "SEQUENCE_GENERATOR_LIMIT_EXCEEDED STRING_DATA_LENGTH_MISMATCH STRING_DATA_RIGHT_TRUNCATION " + "SUBSTRING_ERROR TRIM_ERROR UNTERMINATED_C_STRING ZERO_LENGTH_CHARACTER_STRING " + "FLOATING_POINT_EXCEPTION INVALID_TEXT_REPRESENTATION INVALID_BINARY_REPRESENTATION " + "BAD_COPY_FILE_FORMAT UNTRANSLATABLE_CHARACTER NOT_AN_XML_DOCUMENT INVALID_XML_DOCUMENT " + "INVALID_XML_CONTENT INVALID_XML_COMMENT INVALID_XML_PROCESSING_INSTRUCTION " + "INTEGRITY_CONSTRAINT_VIOLATION RESTRICT_VIOLATION NOT_NULL_VIOLATION FOREIGN_KEY_VIOLATION " + "UNIQUE_VIOLATION CHECK_VIOLATION EXCLUSION_VIOLATION INVALID_CURSOR_STATE " + "INVALID_TRANSACTION_STATE ACTIVE_SQL_TRANSACTION BRANCH_TRANSACTION_ALREADY_ACTIVE " + "HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION " + "INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION " + "NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION READ_ONLY_SQL_TRANSACTION " + "SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED NO_ACTIVE_SQL_TRANSACTION " + "IN_FAILED_SQL_TRANSACTION IDLE_IN_TRANSACTION_SESSION_TIMEOUT INVALID_SQL_STATEMENT_NAME " + "TRIGGERED_DATA_CHANGE_VIOLATION INVALID_AUTHORIZATION_SPECIFICATION INVALID_PASSWORD " + "DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST DEPENDENT_OBJECTS_STILL_EXIST " + "INVALID_TRANSACTION_TERMINATION SQL_ROUTINE_EXCEPTION FUNCTION_EXECUTED_NO_RETURN_STATEMENT " + "MODIFYING_SQL_DATA_NOT_PERMITTED PROHIBITED_SQL_STATEMENT_ATTEMPTED " + "READING_SQL_DATA_NOT_PERMITTED INVALID_CURSOR_NAME EXTERNAL_ROUTINE_EXCEPTION " + "CONTAINING_SQL_NOT_PERMITTED MODIFYING_SQL_DATA_NOT_PERMITTED " + "PROHIBITED_SQL_STATEMENT_ATTEMPTED READING_SQL_DATA_NOT_PERMITTED " + "EXTERNAL_ROUTINE_INVOCATION_EXCEPTION INVALID_SQLSTATE_RETURNED NULL_VALUE_NOT_ALLOWED " + "TRIGGER_PROTOCOL_VIOLATED SRF_PROTOCOL_VIOLATED EVENT_TRIGGER_PROTOCOL_VIOLATED " + "SAVEPOINT_EXCEPTION INVALID_SAVEPOINT_SPECIFICATION INVALID_CATALOG_NAME " + "INVALID_SCHEMA_NAME TRANSACTION_ROLLBACK TRANSACTION_INTEGRITY_CONSTRAINT_VIOLATION " + "SERIALIZATION_FAILURE STATEMENT_COMPLETION_UNKNOWN DEADLOCK_DETECTED " + "SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION SYNTAX_ERROR INSUFFICIENT_PRIVILEGE CANNOT_COERCE " + "GROUPING_ERROR WINDOWING_ERROR INVALID_RECURSION INVALID_FOREIGN_KEY INVALID_NAME " + "NAME_TOO_LONG RESERVED_NAME DATATYPE_MISMATCH INDETERMINATE_DATATYPE COLLATION_MISMATCH " + "INDETERMINATE_COLLATION WRONG_OBJECT_TYPE GENERATED_ALWAYS UNDEFINED_COLUMN " + "UNDEFINED_FUNCTION UNDEFINED_TABLE UNDEFINED_PARAMETER UNDEFINED_OBJECT " + "DUPLICATE_COLUMN DUPLICATE_CURSOR DUPLICATE_DATABASE DUPLICATE_FUNCTION " + "DUPLICATE_PREPARED_STATEMENT DUPLICATE_SCHEMA DUPLICATE_TABLE DUPLICATE_ALIAS " + "DUPLICATE_OBJECT AMBIGUOUS_COLUMN AMBIGUOUS_FUNCTION AMBIGUOUS_PARAMETER AMBIGUOUS_ALIAS " + "INVALID_COLUMN_REFERENCE INVALID_COLUMN_DEFINITION INVALID_CURSOR_DEFINITION " + "INVALID_DATABASE_DEFINITION INVALID_FUNCTION_DEFINITION " + "INVALID_PREPARED_STATEMENT_DEFINITION INVALID_SCHEMA_DEFINITION INVALID_TABLE_DEFINITION " + "INVALID_OBJECT_DEFINITION WITH_CHECK_OPTION_VIOLATION INSUFFICIENT_RESOURCES DISK_FULL " + "OUT_OF_MEMORY TOO_MANY_CONNECTIONS CONFIGURATION_LIMIT_EXCEEDED PROGRAM_LIMIT_EXCEEDED " + "STATEMENT_TOO_COMPLEX TOO_MANY_COLUMNS TOO_MANY_ARGUMENTS OBJECT_NOT_IN_PREREQUISITE_STATE " + "OBJECT_IN_USE CANT_CHANGE_RUNTIME_PARAM LOCK_NOT_AVAILABLE OPERATOR_INTERVENTION " + "QUERY_CANCELED ADMIN_SHUTDOWN CRASH_SHUTDOWN CANNOT_CONNECT_NOW DATABASE_DROPPED " + "SYSTEM_ERROR IO_ERROR UNDEFINED_FILE DUPLICATE_FILE SNAPSHOT_TOO_OLD CONFIG_FILE_ERROR " + "LOCK_FILE_EXISTS FDW_ERROR FDW_COLUMN_NAME_NOT_FOUND FDW_DYNAMIC_PARAMETER_VALUE_NEEDED " + "FDW_FUNCTION_SEQUENCE_ERROR FDW_INCONSISTENT_DESCRIPTOR_INFORMATION " + "FDW_INVALID_ATTRIBUTE_VALUE FDW_INVALID_COLUMN_NAME FDW_INVALID_COLUMN_NUMBER " + "FDW_INVALID_DATA_TYPE FDW_INVALID_DATA_TYPE_DESCRIPTORS " + "FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER FDW_INVALID_HANDLE FDW_INVALID_OPTION_INDEX " + "FDW_INVALID_OPTION_NAME FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH " + "FDW_INVALID_STRING_FORMAT FDW_INVALID_USE_OF_NULL_POINTER FDW_TOO_MANY_HANDLES " + "FDW_OUT_OF_MEMORY FDW_NO_SCHEMAS FDW_OPTION_NAME_NOT_FOUND FDW_REPLY_HANDLE " + "FDW_SCHEMA_NOT_FOUND FDW_TABLE_NOT_FOUND FDW_UNABLE_TO_CREATE_EXECUTION " + "FDW_UNABLE_TO_CREATE_REPLY FDW_UNABLE_TO_ESTABLISH_CONNECTION PLPGSQL_ERROR " + "RAISE_EXCEPTION NO_DATA_FOUND TOO_MANY_ROWS ASSERT_FAILURE INTERNAL_ERROR DATA_CORRUPTED " + "INDEX_CORRUPTED "; var FUNCTIONS = // https://www.postgresql.org/docs/11/static/functions-aggregate.html "ARRAY_AGG AVG BIT_AND BIT_OR BOOL_AND BOOL_OR COUNT EVERY JSON_AGG JSONB_AGG JSON_OBJECT_AGG " + "JSONB_OBJECT_AGG MAX MIN MODE STRING_AGG SUM XMLAGG " + "CORR COVAR_POP COVAR_SAMP REGR_AVGX REGR_AVGY REGR_COUNT REGR_INTERCEPT REGR_R2 REGR_SLOPE " + "REGR_SXX REGR_SXY REGR_SYY STDDEV STDDEV_POP STDDEV_SAMP VARIANCE VAR_POP VAR_SAMP " + "PERCENTILE_CONT PERCENTILE_DISC " + // https://www.postgresql.org/docs/11/static/functions-window.html "ROW_NUMBER RANK DENSE_RANK PERCENT_RANK CUME_DIST NTILE LAG LEAD FIRST_VALUE LAST_VALUE NTH_VALUE " + // https://www.postgresql.org/docs/11/static/functions-comparison.html "NUM_NONNULLS NUM_NULLS " + // https://www.postgresql.org/docs/11/static/functions-math.html "ABS CBRT CEIL CEILING DEGREES DIV EXP FLOOR LN LOG MOD PI POWER RADIANS ROUND SCALE SIGN SQRT " + "TRUNC WIDTH_BUCKET " + "RANDOM SETSEED " + "ACOS ACOSD ASIN ASIND ATAN ATAND ATAN2 ATAN2D COS COSD COT COTD SIN SIND TAN TAND " + // https://www.postgresql.org/docs/11/static/functions-string.html "BIT_LENGTH CHAR_LENGTH CHARACTER_LENGTH LOWER OCTET_LENGTH OVERLAY POSITION SUBSTRING TREAT TRIM UPPER " + "ASCII BTRIM CHR CONCAT CONCAT_WS CONVERT CONVERT_FROM CONVERT_TO DECODE ENCODE INITCAP" + "LEFT LENGTH LPAD LTRIM MD5 PARSE_IDENT PG_CLIENT_ENCODING QUOTE_IDENT|10 QUOTE_LITERAL|10 " + "QUOTE_NULLABLE|10 REGEXP_MATCH REGEXP_MATCHES REGEXP_REPLACE REGEXP_SPLIT_TO_ARRAY " + "REGEXP_SPLIT_TO_TABLE REPEAT REPLACE REVERSE RIGHT RPAD RTRIM SPLIT_PART STRPOS SUBSTR " + "TO_ASCII TO_HEX TRANSLATE " + // https://www.postgresql.org/docs/11/static/functions-binarystring.html "OCTET_LENGTH GET_BIT GET_BYTE SET_BIT SET_BYTE " + // https://www.postgresql.org/docs/11/static/functions-formatting.html "TO_CHAR TO_DATE TO_NUMBER TO_TIMESTAMP " + // https://www.postgresql.org/docs/11/static/functions-datetime.html "AGE CLOCK_TIMESTAMP|10 DATE_PART DATE_TRUNC ISFINITE JUSTIFY_DAYS JUSTIFY_HOURS JUSTIFY_INTERVAL " + "MAKE_DATE MAKE_INTERVAL|10 MAKE_TIME MAKE_TIMESTAMP|10 MAKE_TIMESTAMPTZ|10 NOW STATEMENT_TIMESTAMP|10 " + "TIMEOFDAY TRANSACTION_TIMESTAMP|10 " + // https://www.postgresql.org/docs/11/static/functions-enum.html "ENUM_FIRST ENUM_LAST ENUM_RANGE " + // https://www.postgresql.org/docs/11/static/functions-geometry.html "AREA CENTER DIAMETER HEIGHT ISCLOSED ISOPEN NPOINTS PCLOSE POPEN RADIUS WIDTH " + "BOX BOUND_BOX CIRCLE LINE LSEG PATH POLYGON " + // https://www.postgresql.org/docs/11/static/functions-net.html "ABBREV BROADCAST HOST HOSTMASK MASKLEN NETMASK NETWORK SET_MASKLEN TEXT INET_SAME_FAMILY" + "INET_MERGE MACADDR8_SET7BIT " + // https://www.postgresql.org/docs/11/static/functions-textsearch.html "ARRAY_TO_TSVECTOR GET_CURRENT_TS_CONFIG NUMNODE PLAINTO_TSQUERY PHRASETO_TSQUERY WEBSEARCH_TO_TSQUERY " + "QUERYTREE SETWEIGHT STRIP TO_TSQUERY TO_TSVECTOR JSON_TO_TSVECTOR JSONB_TO_TSVECTOR TS_DELETE " + "TS_FILTER TS_HEADLINE TS_RANK TS_RANK_CD TS_REWRITE TSQUERY_PHRASE TSVECTOR_TO_ARRAY " + "TSVECTOR_UPDATE_TRIGGER TSVECTOR_UPDATE_TRIGGER_COLUMN " + // https://www.postgresql.org/docs/11/static/functions-xml.html "XMLCOMMENT XMLCONCAT XMLELEMENT XMLFOREST XMLPI XMLROOT " + "XMLEXISTS XML_IS_WELL_FORMED XML_IS_WELL_FORMED_DOCUMENT XML_IS_WELL_FORMED_CONTENT " + "XPATH XPATH_EXISTS XMLTABLE XMLNAMESPACES " + "TABLE_TO_XML TABLE_TO_XMLSCHEMA TABLE_TO_XML_AND_XMLSCHEMA " + "QUERY_TO_XML QUERY_TO_XMLSCHEMA QUERY_TO_XML_AND_XMLSCHEMA " + "CURSOR_TO_XML CURSOR_TO_XMLSCHEMA " + "SCHEMA_TO_XML SCHEMA_TO_XMLSCHEMA SCHEMA_TO_XML_AND_XMLSCHEMA " + "DATABASE_TO_XML DATABASE_TO_XMLSCHEMA DATABASE_TO_XML_AND_XMLSCHEMA " + "XMLATTRIBUTES " + // https://www.postgresql.org/docs/11/static/functions-json.html "TO_JSON TO_JSONB ARRAY_TO_JSON ROW_TO_JSON JSON_BUILD_ARRAY JSONB_BUILD_ARRAY JSON_BUILD_OBJECT " + "JSONB_BUILD_OBJECT JSON_OBJECT JSONB_OBJECT JSON_ARRAY_LENGTH JSONB_ARRAY_LENGTH JSON_EACH " + "JSONB_EACH JSON_EACH_TEXT JSONB_EACH_TEXT JSON_EXTRACT_PATH JSONB_EXTRACT_PATH " + "JSON_OBJECT_KEYS JSONB_OBJECT_KEYS JSON_POPULATE_RECORD JSONB_POPULATE_RECORD JSON_POPULATE_RECORDSET " + "JSONB_POPULATE_RECORDSET JSON_ARRAY_ELEMENTS JSONB_ARRAY_ELEMENTS JSON_ARRAY_ELEMENTS_TEXT " + "JSONB_ARRAY_ELEMENTS_TEXT JSON_TYPEOF JSONB_TYPEOF JSON_TO_RECORD JSONB_TO_RECORD JSON_TO_RECORDSET " + "JSONB_TO_RECORDSET JSON_STRIP_NULLS JSONB_STRIP_NULLS JSONB_SET JSONB_INSERT JSONB_PRETTY " + // https://www.postgresql.org/docs/11/static/functions-sequence.html "CURRVAL LASTVAL NEXTVAL SETVAL " + // https://www.postgresql.org/docs/11/static/functions-conditional.html "COALESCE NULLIF GREATEST LEAST " + // https://www.postgresql.org/docs/11/static/functions-array.html "ARRAY_APPEND ARRAY_CAT ARRAY_NDIMS ARRAY_DIMS ARRAY_FILL ARRAY_LENGTH ARRAY_LOWER ARRAY_POSITION " + "ARRAY_POSITIONS ARRAY_PREPEND ARRAY_REMOVE ARRAY_REPLACE ARRAY_TO_STRING ARRAY_UPPER CARDINALITY " + "STRING_TO_ARRAY UNNEST " + // https://www.postgresql.org/docs/11/static/functions-range.html "ISEMPTY LOWER_INC UPPER_INC LOWER_INF UPPER_INF RANGE_MERGE " + // https://www.postgresql.org/docs/11/static/functions-srf.html "GENERATE_SERIES GENERATE_SUBSCRIPTS " + // https://www.postgresql.org/docs/11/static/functions-info.html "CURRENT_DATABASE CURRENT_QUERY CURRENT_SCHEMA|10 CURRENT_SCHEMAS|10 INET_CLIENT_ADDR INET_CLIENT_PORT " + "INET_SERVER_ADDR INET_SERVER_PORT ROW_SECURITY_ACTIVE FORMAT_TYPE " + "TO_REGCLASS TO_REGPROC TO_REGPROCEDURE TO_REGOPER TO_REGOPERATOR TO_REGTYPE TO_REGNAMESPACE TO_REGROLE " + "COL_DESCRIPTION OBJ_DESCRIPTION SHOBJ_DESCRIPTION " + "TXID_CURRENT TXID_CURRENT_IF_ASSIGNED TXID_CURRENT_SNAPSHOT TXID_SNAPSHOT_XIP TXID_SNAPSHOT_XMAX " + "TXID_SNAPSHOT_XMIN TXID_VISIBLE_IN_SNAPSHOT TXID_STATUS " + // https://www.postgresql.org/docs/11/static/functions-admin.html "CURRENT_SETTING SET_CONFIG BRIN_SUMMARIZE_NEW_VALUES BRIN_SUMMARIZE_RANGE BRIN_DESUMMARIZE_RANGE " + "GIN_CLEAN_PENDING_LIST " + // https://www.postgresql.org/docs/11/static/functions-trigger.html "SUPPRESS_REDUNDANT_UPDATES_TRIGGER " + // ihttps://www.postgresql.org/docs/devel/static/lo-funcs.html "LO_FROM_BYTEA LO_PUT LO_GET LO_CREAT LO_CREATE LO_UNLINK LO_IMPORT LO_EXPORT LOREAD LOWRITE " + // "GROUPING CAST "; var FUNCTIONS_RE = FUNCTIONS.trim() .split(" ") .map(function (val) { return val.split("|")[0]; }) .join("|"); return { aliases: ["postgres", "postgresql"], case_insensitive: true, keywords: { keyword: SQL_KW + PLPGSQL_KW + ROLE_ATTRS, built_in: SQL_BI + PLPGSQL_BI + PLPGSQL_EXCEPTIONS, }, // Forbid some cunstructs from other languages to improve autodetect. In fact // "[a-z]:" is legal (as part of array slice), but improbabal. illegal: /:==|\W\s*\(\*|(^|\s)\$[a-z]|{{|[a-z]:\s*$|\.\.\.|TO:|DO:/, contains: [ // special handling of some words, which are reserved only in some contexts { className: "keyword", variants: [ { begin: /\bTEXT\s*SEARCH\b/ }, { begin: /\b(PRIMARY|FOREIGN|FOR(\s+NO)?)\s+KEY\b/ }, { begin: /\bPARALLEL\s+(UNSAFE|RESTRICTED|SAFE)\b/ }, { begin: /\bSTORAGE\s+(PLAIN|EXTERNAL|EXTENDED|MAIN)\b/, }, { begin: /\bMATCH\s+(FULL|PARTIAL|SIMPLE)\b/ }, { begin: /\bNULLS\s+(FIRST|LAST)\b/ }, { begin: /\bEVENT\s+TRIGGER\b/ }, { begin: /\b(MAPPING|OR)\s+REPLACE\b/ }, { begin: /\b(FROM|TO)\s+(PROGRAM|STDIN|STDOUT)\b/ }, { begin: /\b(SHARE|EXCLUSIVE)\s+MODE\b/ }, { begin: /\b(LEFT|RIGHT)\s+(OUTER\s+)?JOIN\b/ }, { begin: /\b(FETCH|MOVE)\s+(NEXT|PRIOR|FIRST|LAST|ABSOLUTE|RELATIVE|FORWARD|BACKWARD)\b/, }, { begin: /\bPRESERVE\s+ROWS\b/ }, { begin: /\bDISCARD\s+PLANS\b/ }, { begin: /\bREFERENCING\s+(OLD|NEW)\b/ }, { begin: /\bSKIP\s+LOCKED\b/ }, { begin: /\bGROUPING\s+SETS\b/ }, { begin: /\b(BINARY|INSENSITIVE|SCROLL|NO\s+SCROLL)\s+(CURSOR|FOR)\b/, }, { begin: /\b(WITH|WITHOUT)\s+HOLD\b/ }, { begin: /\bWITH\s+(CASCADED|LOCAL)\s+CHECK\s+OPTION\b/, }, { begin: /\bEXCLUDE\s+(TIES|NO\s+OTHERS)\b/ }, { begin: /\bFORMAT\s+(TEXT|XML|JSON|YAML)\b/ }, { begin: /\bSET\s+((SESSION|LOCAL)\s+)?NAMES\b/ }, { begin: /\bIS\s+(NOT\s+)?UNKNOWN\b/ }, { begin: /\bSECURITY\s+LABEL\b/ }, { begin: /\bSTANDALONE\s+(YES|NO|NO\s+VALUE)\b/ }, { begin: /\bWITH\s+(NO\s+)?DATA\b/ }, { begin: /\b(FOREIGN|SET)\s+DATA\b/ }, { begin: /\bSET\s+(CATALOG|CONSTRAINTS)\b/ }, { begin: /\b(WITH|FOR)\s+ORDINALITY\b/ }, { begin: /\bIS\s+(NOT\s+)?DOCUMENT\b/ }, { begin: /\bXML\s+OPTION\s+(DOCUMENT|CONTENT)\b/ }, { begin: /\b(STRIP|PRESERVE)\s+WHITESPACE\b/ }, { begin: /\bNO\s+(ACTION|MAXVALUE|MINVALUE)\b/ }, { begin: /\bPARTITION\s+BY\s+(RANGE|LIST|HASH)\b/ }, { begin: /\bAT\s+TIME\s+ZONE\b/ }, { begin: /\bGRANTED\s+BY\b/ }, { begin: /\bRETURN\s+(QUERY|NEXT)\b/ }, { begin: /\b(ATTACH|DETACH)\s+PARTITION\b/ }, { begin: /\bFORCE\s+ROW\s+LEVEL\s+SECURITY\b/ }, { begin: /\b(INCLUDING|EXCLUDING)\s+(COMMENTS|CONSTRAINTS|DEFAULTS|IDENTITY|INDEXES|STATISTICS|STORAGE|ALL)\b/, }, { begin: /\bAS\s+(ASSIGNMENT|IMPLICIT|PERMISSIVE|RESTRICTIVE|ENUM|RANGE)\b/, }, ], }, // functions named as keywords, followed by '(' { begin: /\b(FORMAT|FAMILY|VERSION)\s*\(/, //keywords: { built_in: 'FORMAT FAMILY VERSION' } }, // INCLUDE ( ... ) in index_parameters in CREATE TABLE { begin: /\bINCLUDE\s*\(/, keywords: "INCLUDE", }, // not highlight RANGE if not in frame_clause (not 100% correct, but seems satisfactory) { begin: /\bRANGE(?!\s*(BETWEEN|UNBOUNDED|CURRENT|[-0-9]+))/, }, // disable highlighting in commands CREATE AGGREGATE/COLLATION/DATABASE/OPERTOR/TEXT SEARCH .../TYPE // and in PL/pgSQL RAISE ... USING { begin: /\b(VERSION|OWNER|TEMPLATE|TABLESPACE|CONNECTION\s+LIMIT|PROCEDURE|RESTRICT|JOIN|PARSER|COPY|START|END|COLLATION|INPUT|ANALYZE|STORAGE|LIKE|DEFAULT|DELIMITER|ENCODING|COLUMN|CONSTRAINT|TABLE|SCHEMA)\s*=/, }, // PG_smth; HAS_some_PRIVILEGE { //className: 'built_in', begin: /\b(PG_\w+?|HAS_[A-Z_]+_PRIVILEGE)\b/, relevance: 10, }, // extract { begin: /\bEXTRACT\s*\(/, end: /\bFROM\b/, returnEnd: true, keywords: { //built_in: 'EXTRACT', type: "CENTURY DAY DECADE DOW DOY EPOCH HOUR ISODOW ISOYEAR MICROSECONDS " + "MILLENNIUM MILLISECONDS MINUTE MONTH QUARTER SECOND TIMEZONE TIMEZONE_HOUR " + "TIMEZONE_MINUTE WEEK YEAR", }, }, // xmlelement, xmlpi - special NAME { begin: /\b(XMLELEMENT|XMLPI)\s*\(\s*NAME/, keywords: { //built_in: 'XMLELEMENT XMLPI', keyword: "NAME", }, }, // xmlparse, xmlserialize { begin: /\b(XMLPARSE|XMLSERIALIZE)\s*\(\s*(DOCUMENT|CONTENT)/, keywords: { //built_in: 'XMLPARSE XMLSERIALIZE', keyword: "DOCUMENT CONTENT", }, }, // Sequences. We actually skip everything between CACHE|INCREMENT|MAXVALUE|MINVALUE and // nearest following numeric constant. Without with trick we find a lot of "keywords" // in 'avrasm' autodetection test... { beginKeywords: "CACHE INCREMENT MAXVALUE MINVALUE", end: hljs.C_NUMBER_RE, returnEnd: true, keywords: "BY CACHE INCREMENT MAXVALUE MINVALUE", }, // WITH|WITHOUT TIME ZONE as part of datatype { className: "type", begin: /\b(WITH|WITHOUT)\s+TIME\s+ZONE\b/, }, // INTERVAL optional fields { className: "type", begin: /\bINTERVAL\s+(YEAR|MONTH|DAY|HOUR|MINUTE|SECOND)(\s+TO\s+(MONTH|HOUR|MINUTE|SECOND))?\b/, }, // Pseudo-types which allowed only as return type { begin: /\bRETURNS\s+(LANGUAGE_HANDLER|TRIGGER|EVENT_TRIGGER|FDW_HANDLER|INDEX_AM_HANDLER|TSM_HANDLER)\b/, keywords: { keyword: "RETURNS", type: "LANGUAGE_HANDLER TRIGGER EVENT_TRIGGER FDW_HANDLER INDEX_AM_HANDLER TSM_HANDLER", }, }, // Known functions - only when followed by '(' { begin: "\\b(" + FUNCTIONS_RE + ")\\s*\\(", //keywords: { built_in: FUNCTIONS } }, // Types { begin: "\\.(" + TYPES_RE + ")\\b", // prevent highlight as type, say, 'oid' in 'pgclass.oid' }, { begin: "\\b(" + TYPES_RE + ")\\s+PATH\\b", // in XMLTABLE keywords: { keyword: "PATH", // hopefully no one would use PATH type in XMLTABLE... type: TYPES.replace("PATH ", ""), }, }, { className: "type", begin: "\\b(" + TYPES_RE + ")\\b", }, // Strings, see https://www.postgresql.org/docs/11/static/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS { className: "string", begin: "'", end: "'", contains: [{ begin: "''" }], }, { className: "string", begin: "(e|E|u&|U&)'", end: "'", contains: [{ begin: "\\\\." }], relevance: 10, }, { begin: DOLLAR_STRING, endSameAsBegin: true, contains: [ { // actually we want them all except SQL; listed are those with known implementations // and XML + JSON just in case subLanguage: [ "pgsql", "perl", "python", "tcl", "r", "lua", "java", "php", "ruby", "bash", "scheme", "xml", "json", ], endsWithParent: true, }, ], }, // identifiers in quotes { begin: '"', end: '"', contains: [{ begin: '""' }], }, // numbers hljs.C_NUMBER_MODE, // comments hljs.C_BLOCK_COMMENT_MODE, COMMENT_MODE, // PL/pgSQL staff // %ROWTYPE, %TYPE, $n { className: "meta", variants: [ { begin: "%(ROW)?TYPE", relevance: 10 }, // %TYPE, %ROWTYPE { begin: "\\$\\d+" }, // $n { begin: "^#\\w", end: "$" }, // #compiler option ], }, // <> { className: "symbol", begin: LABEL, relevance: 10, }, ], }; }, }, { name: "php", /* Language: PHP Author: Victor Karamzin Contributors: Evgeny Stepanischev , Ivan Sagalaev Category: common */ create: function (hljs) { var VARIABLE = { begin: "\\$+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*", }; var PREPROCESSOR = { className: "meta", begin: /<\?(php)?|\?>/, }; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE, PREPROCESSOR], variants: [ { begin: 'b"', end: '"', }, { begin: "b'", end: "'", }, hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), ], }; var NUMBER = { variants: [hljs.BINARY_NUMBER_MODE, hljs.C_NUMBER_MODE], }; return { aliases: ["php", "php3", "php4", "php5", "php6", "php7"], case_insensitive: true, keywords: "and include_once list abstract global private echo interface as static endswitch " + "array null if endwhile or const for endforeach self var while isset public " + "protected exit foreach throw elseif include __FILE__ empty require_once do xor " + "return parent clone use __CLASS__ __LINE__ else break print eval new " + "catch __METHOD__ case exception default die require __FUNCTION__ " + "enddeclare final try switch continue endfor endif declare unset true false " + "trait goto instanceof insteadof __DIR__ __NAMESPACE__ " + "yield finally", contains: [ hljs.HASH_COMMENT_MODE, hljs.COMMENT("//", "$", { contains: [PREPROCESSOR] }), hljs.COMMENT("/\\*", "\\*/", { contains: [ { className: "doctag", begin: "@[A-Za-z]+", }, ], }), hljs.COMMENT("__halt_compiler.+?;", false, { endsWithParent: true, keywords: "__halt_compiler", lexemes: hljs.UNDERSCORE_IDENT_RE, }), { className: "string", begin: /<<<['"]?\w+['"]?$/, end: /^\w+;?$/, contains: [ hljs.BACKSLASH_ESCAPE, { className: "subst", variants: [ { begin: /\$\w+/ }, { begin: /\{\$/, end: /\}/ }, ], }, ], }, PREPROCESSOR, { className: "keyword", begin: /\$this\b/, }, VARIABLE, { // swallow composed identifiers to avoid parsing them as keywords begin: /(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/, }, { className: "function", beginKeywords: "function", end: /[;{]/, excludeEnd: true, illegal: "\\$|\\[|%", contains: [ hljs.UNDERSCORE_TITLE_MODE, { className: "params", begin: "\\(", end: "\\)", contains: [ "self", VARIABLE, hljs.C_BLOCK_COMMENT_MODE, STRING, NUMBER, ], }, ], }, { className: "class", beginKeywords: "class interface", end: "{", excludeEnd: true, illegal: /[:\(\$"]/, contains: [ { beginKeywords: "extends implements" }, hljs.UNDERSCORE_TITLE_MODE, ], }, { beginKeywords: "namespace", end: ";", illegal: /[\.']/, contains: [hljs.UNDERSCORE_TITLE_MODE], }, { beginKeywords: "use", end: ";", contains: [hljs.UNDERSCORE_TITLE_MODE], }, { begin: "=>", // No markup, just a relevance booster }, STRING, NUMBER, ], }; }, }, { name: "plaintext", /* Language: plaintext Author: Egor Rogov (e.rogov@postgrespro.ru) Description: Plain text without any highlighting. */ create: function (hljs) { return { disableAutodetect: true, }; }, }, { name: "pony", /* Language: Pony Author: Joe Eli McIlvain Description: Pony is an open-source, object-oriented, actor-model, capabilities-secure, high performance programming language. */ create: function (hljs) { var KEYWORDS = { keyword: "actor addressof and as be break class compile_error compile_intrinsic " + "consume continue delegate digestof do else elseif embed end error " + "for fun if ifdef in interface is isnt lambda let match new not object " + "or primitive recover repeat return struct then trait try type until " + "use var where while with xor", meta: "iso val tag trn box ref", literal: "this false true", }; var TRIPLE_QUOTE_STRING_MODE = { className: "string", begin: '"""', end: '"""', relevance: 10, }; var QUOTE_STRING_MODE = { className: "string", begin: '"', end: '"', contains: [hljs.BACKSLASH_ESCAPE], }; var SINGLE_QUOTE_CHAR_MODE = { className: "string", begin: "'", end: "'", contains: [hljs.BACKSLASH_ESCAPE], relevance: 0, }; var TYPE_NAME = { className: "type", begin: "\\b_?[A-Z][\\w]*", relevance: 0, }; var PRIMED_NAME = { begin: hljs.IDENT_RE + "'", relevance: 0, }; /** * The `FUNCTION` and `CLASS` modes were intentionally removed to simplify * highlighting and fix cases like * ``` * interface Iterator[A: A] * fun has_next(): Bool * fun next(): A? * ``` * where it is valid to have a function head without a body */ return { keywords: KEYWORDS, contains: [ TYPE_NAME, TRIPLE_QUOTE_STRING_MODE, QUOTE_STRING_MODE, SINGLE_QUOTE_CHAR_MODE, PRIMED_NAME, hljs.C_NUMBER_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }; }, }, { name: "powershell", /* Language: PowerShell Author: David Mohundro Contributors: Nicholas Blumhardt , Victor Zhou , Nicolas Le Gall */ create: function (hljs) { var BACKTICK_ESCAPE = { begin: "`[\\s\\S]", relevance: 0, }; var VAR = { className: "variable", variants: [{ begin: /\$[\w\d][\w\d_:]*/ }], }; var LITERAL = { className: "literal", begin: /\$(null|true|false)\b/, }; var QUOTE_STRING = { className: "string", variants: [ { begin: /"/, end: /"/ }, { begin: /@"/, end: /^"@/ }, ], contains: [ BACKTICK_ESCAPE, VAR, { className: "variable", begin: /\$[A-z]/, end: /[^A-z]/, }, ], }; var APOS_STRING = { className: "string", variants: [ { begin: /'/, end: /'/ }, { begin: /@'/, end: /^'@/ }, ], }; var PS_HELPTAGS = { className: "doctag", variants: [ /* no paramater help tags */ { begin: /\.(synopsis|description|example|inputs|outputs|notes|link|component|role|functionality)/, }, /* one parameter help tags */ { begin: /\.(parameter|forwardhelptargetname|forwardhelpcategory|remotehelprunspace|externalhelp)\s+\S+/, }, ], }; var PS_COMMENT = hljs.inherit(hljs.COMMENT(null, null), { variants: [ /* single-line comment */ { begin: /#/, end: /$/ }, /* multi-line comment */ { begin: /<#/, end: /#>/ }, ], contains: [PS_HELPTAGS], }); return { aliases: ["ps"], lexemes: /-?[A-z\.\-]+/, case_insensitive: true, keywords: { keyword: "if else foreach return function do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch" + "ValidateNoCircleInNodeResources ValidateNodeExclusiveResources ValidateNodeManager ValidateNodeResources ValidateNodeResourceSource ValidateNoNameNodeResources ThrowError IsHiddenResource" + "IsPatternMatched ", built_in: "Add-Computer Add-Content Add-History Add-JobTrigger Add-Member Add-PSSnapin Add-Type Checkpoint-Computer Clear-Content " + "Clear-EventLog Clear-History Clear-Host Clear-Item Clear-ItemProperty Clear-Variable Compare-Object Complete-Transaction Connect-PSSession " + "Connect-WSMan Convert-Path ConvertFrom-Csv ConvertFrom-Json ConvertFrom-SecureString ConvertFrom-StringData ConvertTo-Csv ConvertTo-Html " + "ConvertTo-Json ConvertTo-SecureString ConvertTo-Xml Copy-Item Copy-ItemProperty Debug-Process Disable-ComputerRestore Disable-JobTrigger " + "Disable-PSBreakpoint Disable-PSRemoting Disable-PSSessionConfiguration Disable-WSManCredSSP Disconnect-PSSession Disconnect-WSMan " + "Disable-ScheduledJob Enable-ComputerRestore Enable-JobTrigger Enable-PSBreakpoint Enable-PSRemoting Enable-PSSessionConfiguration " + "Enable-ScheduledJob Enable-WSManCredSSP Enter-PSSession Exit-PSSession Export-Alias Export-Clixml Export-Console Export-Counter Export-Csv " + "Export-FormatData Export-ModuleMember Export-PSSession ForEach-Object Format-Custom Format-List Format-Table Format-Wide Get-Acl Get-Alias " + "Get-AuthenticodeSignature Get-ChildItem Get-Command Get-ComputerRestorePoint Get-Content Get-ControlPanelItem Get-Counter Get-Credential " + "Get-Culture Get-Date Get-Event Get-EventLog Get-EventSubscriber Get-ExecutionPolicy Get-FormatData Get-Host Get-HotFix Get-Help Get-History " + "Get-IseSnippet Get-Item Get-ItemProperty Get-Job Get-JobTrigger Get-Location Get-Member Get-Module Get-PfxCertificate Get-Process " + "Get-PSBreakpoint Get-PSCallStack Get-PSDrive Get-PSProvider Get-PSSession Get-PSSessionConfiguration Get-PSSnapin Get-Random Get-ScheduledJob " + "Get-ScheduledJobOption Get-Service Get-TraceSource Get-Transaction Get-TypeData Get-UICulture Get-Unique Get-Variable Get-Verb Get-WinEvent " + "Get-WmiObject Get-WSManCredSSP Get-WSManInstance Group-Object Import-Alias Import-Clixml Import-Counter Import-Csv Import-IseSnippet " + "Import-LocalizedData Import-PSSession Import-Module Invoke-AsWorkflow Invoke-Command Invoke-Expression Invoke-History Invoke-Item " + "Invoke-RestMethod Invoke-WebRequest Invoke-WmiMethod Invoke-WSManAction Join-Path Limit-EventLog Measure-Command Measure-Object Move-Item " + "Move-ItemProperty New-Alias New-Event New-EventLog New-IseSnippet New-Item New-ItemProperty New-JobTrigger New-Object New-Module " + "New-ModuleManifest New-PSDrive New-PSSession New-PSSessionConfigurationFile New-PSSessionOption New-PSTransportOption " + "New-PSWorkflowExecutionOption New-PSWorkflowSession New-ScheduledJobOption New-Service New-TimeSpan New-Variable New-WebServiceProxy " + "New-WinEvent New-WSManInstance New-WSManSessionOption Out-Default Out-File Out-GridView Out-Host Out-Null Out-Printer Out-String Pop-Location " + "Push-Location Read-Host Receive-Job Register-EngineEvent Register-ObjectEvent Register-PSSessionConfiguration Register-ScheduledJob " + "Register-WmiEvent Remove-Computer Remove-Event Remove-EventLog Remove-Item Remove-ItemProperty Remove-Job Remove-JobTrigger Remove-Module " + "Remove-PSBreakpoint Remove-PSDrive Remove-PSSession Remove-PSSnapin Remove-TypeData Remove-Variable Remove-WmiObject Remove-WSManInstance " + "Rename-Computer Rename-Item Rename-ItemProperty Reset-ComputerMachinePassword Resolve-Path Restart-Computer Restart-Service Restore-Computer " + "Resume-Job Resume-Service Save-Help Select-Object Select-String Select-Xml Send-MailMessage Set-Acl Set-Alias Set-AuthenticodeSignature " + "Set-Content Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-JobTrigger Set-Location Set-PSBreakpoint Set-PSDebug " + "Set-PSSessionConfiguration Set-ScheduledJob Set-ScheduledJobOption Set-Service Set-StrictMode Set-TraceSource Set-Variable Set-WmiInstance " + "Set-WSManInstance Set-WSManQuickConfig Show-Command Show-ControlPanelItem Show-EventLog Sort-Object Split-Path Start-Job Start-Process " + "Start-Service Start-Sleep Start-Transaction Start-Transcript Stop-Computer Stop-Job Stop-Process Stop-Service Stop-Transcript Suspend-Job " + "Suspend-Service Tee-Object Test-ComputerSecureChannel Test-Connection Test-ModuleManifest Test-Path Test-PSSessionConfigurationFile " + "Trace-Command Unblock-File Undo-Transaction Unregister-Event Unregister-PSSessionConfiguration Unregister-ScheduledJob Update-FormatData " + "Update-Help Update-List Update-TypeData Use-Transaction Wait-Event Wait-Job Wait-Process Where-Object Write-Debug Write-Error Write-EventLog " + "Write-Host Write-Output Write-Progress Write-Verbose Write-Warning Add-MDTPersistentDrive Disable-MDTMonitorService Enable-MDTMonitorService " + "Get-MDTDeploymentShareStatistics Get-MDTMonitorData Get-MDTOperatingSystemCatalog Get-MDTPersistentDrive Import-MDTApplication " + "Import-MDTDriver Import-MDTOperatingSystem Import-MDTPackage Import-MDTTaskSequence New-MDTDatabase Remove-MDTMonitorData " + "Remove-MDTPersistentDrive Restore-MDTPersistentDrive Set-MDTMonitorData Test-MDTDeploymentShare Test-MDTMonitorData Update-MDTDatabaseSchema " + "Update-MDTDeploymentShare Update-MDTLinkedDS Update-MDTMedia Add-VamtProductKey Export-VamtData Find-VamtManagedMachine " + "Get-VamtConfirmationId Get-VamtProduct Get-VamtProductKey Import-VamtData Initialize-VamtData Install-VamtConfirmationId " + "Install-VamtProductActivation Install-VamtProductKey Update-VamtProduct Add-CIDatastore Add-KeyManagementServer Add-NodeKeys " + "Add-NsxDynamicCriteria Add-NsxDynamicMemberSet Add-NsxEdgeInterfaceAddress Add-NsxFirewallExclusionListMember Add-NsxFirewallRuleMember " + "Add-NsxIpSetMember Add-NsxLicense Add-NsxLoadBalancerPoolMember Add-NsxLoadBalancerVip Add-NsxSecondaryManager Add-NsxSecurityGroupMember " + "Add-NsxSecurityPolicyRule Add-NsxSecurityPolicyRuleGroup Add-NsxSecurityPolicyRuleService Add-NsxServiceGroupMember " + "Add-NsxTransportZoneMember Add-PassthroughDevice Add-VDSwitchPhysicalNetworkAdapter Add-VDSwitchVMHost Add-VMHost Add-VMHostNtpServer " + "Add-VirtualSwitchPhysicalNetworkAdapter Add-XmlElement Add-vRACustomForm Add-vRAPrincipalToTenantRole Add-vRAReservationNetwork " + "Add-vRAReservationStorage Clear-NsxEdgeInterface Clear-NsxManagerTimeSettings Compress-Archive Connect-CIServer Connect-CisServer " + "Connect-HCXServer Connect-NIServer Connect-NsxLogicalSwitch Connect-NsxServer Connect-NsxtServer Connect-SrmServer Connect-VIServer " + "Connect-Vmc Connect-vRAServer Connect-vRNIServer ConvertFrom-Markdown ConvertTo-MOFInstance Copy-DatastoreItem Copy-HardDisk Copy-NsxEdge " + "Copy-VDisk Copy-VMGuestFile Debug-Runspace Disable-NsxEdgeSsh Disable-RunspaceDebug Disable-vRNIDataSource Disconnect-CIServer " + "Disconnect-CisServer Disconnect-HCXServer Disconnect-NsxLogicalSwitch Disconnect-NsxServer Disconnect-NsxtServer Disconnect-SrmServer " + "Disconnect-VIServer Disconnect-Vmc Disconnect-vRAServer Disconnect-vRNIServer Dismount-Tools Enable-NsxEdgeSsh Enable-RunspaceDebug " + "Enable-vRNIDataSource Expand-Archive Export-NsxObject Export-SpbmStoragePolicy Export-VApp Export-VDPortGroup Export-VDSwitch " + "Export-VMHostProfile Export-vRAIcon Export-vRAPackage Find-Command Find-DscResource Find-Module Find-NsxWhereVMUsed Find-Package " + "Find-PackageProvider Find-RoleCapability Find-Script Format-Hex Format-VMHostDiskPartition Format-XML Generate-VersionInfo " + "Get-AdvancedSetting Get-AlarmAction Get-AlarmActionTrigger Get-AlarmDefinition Get-Annotation Get-CDDrive Get-CIAccessControlRule " + "Get-CIDatastore Get-CINetworkAdapter Get-CIRole Get-CIUser Get-CIVApp Get-CIVAppNetwork Get-CIVAppStartRule Get-CIVAppTemplate Get-CIVM " + "Get-CIVMTemplate Get-CIView Get-Catalog Get-CisCommand Get-CisService Get-CloudCommand Get-Cluster Get-CompatibleVersionAddtionaPropertiesStr " + "Get-ComplexResourceQualifier Get-ConfigurationErrorCount Get-ContentLibraryItem Get-CustomAttribute Get-DSCResourceModules Get-Datacenter " + "Get-Datastore Get-DatastoreCluster Get-DrsClusterGroup Get-DrsRecommendation Get-DrsRule Get-DrsVMHostRule Get-DscResource Get-EdgeGateway " + "Get-EncryptedPassword Get-ErrorReport Get-EsxCli Get-EsxTop Get-ExternalNetwork Get-FileHash Get-FloppyDrive Get-Folder Get-HAPrimaryVMHost " + "Get-HCXAppliance Get-HCXApplianceCompute Get-HCXApplianceDVS Get-HCXApplianceDatastore Get-HCXApplianceNetwork Get-HCXContainer " + "Get-HCXDatastore Get-HCXGateway Get-HCXInterconnectStatus Get-HCXJob Get-HCXMigration Get-HCXNetwork Get-HCXNetworkExtension " + "Get-HCXReplication Get-HCXReplicationSnapshot Get-HCXService Get-HCXSite Get-HCXSitePairing Get-HCXVM Get-HardDisk Get-IScsiHbaTarget " + "Get-InnerMostErrorRecord Get-InstallPath Get-InstalledModule Get-InstalledScript Get-Inventory Get-ItemPropertyValue Get-KeyManagementServer " + "Get-KmipClientCertificate Get-KmsCluster Get-Log Get-LogType Get-MarkdownOption Get-Media Get-MofInstanceName Get-MofInstanceText Get-NetworkAdapter Get-NetworkPool " + "Get-NfsUser Get-NicTeamingPolicy Get-NsxApplicableMember Get-NsxApplicableSecurityAction Get-NsxBackingDVSwitch Get-NsxBackingPortGroup Get-NsxCliDfwAddrSet " + "Get-NsxCliDfwFilter Get-NsxCliDfwRule Get-NsxClusterStatus Get-NsxController Get-NsxDynamicCriteria Get-NsxDynamicMemberSet Get-NsxEdge Get-NsxEdgeBgp " + "Get-NsxEdgeBgpNeighbour Get-NsxEdgeCertificate Get-NsxEdgeCsr Get-NsxEdgeFirewall Get-NsxEdgeFirewallRule Get-NsxEdgeInterface Get-NsxEdgeInterfaceAddress " + "Get-NsxEdgeNat Get-NsxEdgeNatRule Get-NsxEdgeOspf Get-NsxEdgeOspfArea Get-NsxEdgeOspfInterface Get-NsxEdgePrefix Get-NsxEdgeRedistributionRule Get-NsxEdgeRouting " + "Get-NsxEdgeStaticRoute Get-NsxEdgeSubInterface Get-NsxFirewallExclusionListMember Get-NsxFirewallGlobalConfiguration Get-NsxFirewallPublishStatus Get-NsxFirewallRule " + "Get-NsxFirewallRuleMember Get-NsxFirewallSavedConfiguration Get-NsxFirewallSection Get-NsxFirewallThreshold Get-NsxIpPool Get-NsxIpSet Get-NsxLicense Get-NsxLoadBalancer " + "Get-NsxLoadBalancerApplicationProfile Get-NsxLoadBalancerApplicationRule Get-NsxLoadBalancerMonitor Get-NsxLoadBalancerPool Get-NsxLoadBalancerPoolMember Get-NsxLoadBalancerStats " + "Get-NsxLoadBalancerVip Get-NsxLogicalRouter Get-NsxLogicalRouterBgp Get-NsxLogicalRouterBgpNeighbour Get-NsxLogicalRouterBridge Get-NsxLogicalRouterBridging " + "Get-NsxLogicalRouterInterface Get-NsxLogicalRouterOspf Get-NsxLogicalRouterOspfArea Get-NsxLogicalRouterOspfInterface Get-NsxLogicalRouterPrefix " + "Get-NsxLogicalRouterRedistributionRule Get-NsxLogicalRouterRouting Get-NsxLogicalRouterStaticRoute Get-NsxLogicalSwitch Get-NsxMacSet Get-NsxManagerBackup " + "Get-NsxManagerCertificate Get-NsxManagerComponentSummary Get-NsxManagerNetwork Get-NsxManagerRole Get-NsxManagerSsoConfig Get-NsxManagerSyncStatus Get-NsxManagerSyslogServer " + "Get-NsxManagerSystemSummary Get-NsxManagerTimeSettings Get-NsxManagerVcenterConfig Get-NsxSecondaryManager Get-NsxSecurityGroup Get-NsxSecurityGroupEffectiveIpAddress " + "Get-NsxSecurityGroupEffectiveMacAddress Get-NsxSecurityGroupEffectiveMember Get-NsxSecurityGroupEffectiveVirtualMachine Get-NsxSecurityGroupEffectiveVnic " + "Get-NsxSecurityGroupMemberTypes Get-NsxSecurityPolicy Get-NsxSecurityPolicyHighestUsedPrecedence Get-NsxSecurityPolicyRule Get-NsxSecurityTag Get-NsxSecurityTagAssignment " + "Get-NsxSegmentIdRange Get-NsxService Get-NsxServiceDefinition Get-NsxServiceGroup Get-NsxServiceGroupMember Get-NsxServiceProfile Get-NsxSpoofguardNic Get-NsxSpoofguardPolicy " + "Get-NsxSslVpn Get-NsxSslVpnAuthServer Get-NsxSslVpnClientInstallationPackage Get-NsxSslVpnIpPool Get-NsxSslVpnPrivateNetwork Get-NsxSslVpnUser Get-NsxTransportZone " + "Get-NsxUserRole Get-NsxVdsContext Get-NsxtPolicyService Get-NsxtService Get-OSCustomizationNicMapping Get-OSCustomizationSpec Get-Org Get-OrgNetwork Get-OrgVdc " + "Get-OrgVdcNetwork Get-OvfConfiguration Get-PSCurrentConfigurationNode Get-PSDefaultConfigurationDocument Get-PSMetaConfigDocumentInstVersionInfo Get-PSMetaConfigurationProcessed " + "Get-PSReadLineKeyHandler Get-PSReadLineOption Get-PSRepository Get-PSTopConfigurationName Get-PSVersion Get-Package Get-PackageProvider Get-PackageSource Get-PassthroughDevice " + "Get-PositionInfo Get-PowerCLICommunity Get-PowerCLIConfiguration Get-PowerCLIHelp Get-PowerCLIVersion Get-PowerNsxVersion Get-ProviderVdc Get-PublicKeyFromFile " + "Get-PublicKeyFromStore Get-ResourcePool Get-Runspace Get-RunspaceDebug Get-ScsiController Get-ScsiLun Get-ScsiLunPath Get-SecurityInfo Get-SecurityPolicy Get-Snapshot " + "Get-SpbmCapability Get-SpbmCompatibleStorage Get-SpbmEntityConfiguration Get-SpbmFaultDomain Get-SpbmPointInTimeReplica Get-SpbmReplicationGroup Get-SpbmReplicationPair " + "Get-SpbmStoragePolicy Get-Stat Get-StatInterval Get-StatType Get-Tag Get-TagAssignment Get-TagCategory Get-Task Get-Template Get-TimeZone Get-Uptime Get-UsbDevice Get-VAIOFilter " + "Get-VApp Get-VDBlockedPolicy Get-VDPort Get-VDPortgroup Get-VDPortgroupOverridePolicy Get-VDSecurityPolicy Get-VDSwitch Get-VDSwitchPrivateVlan Get-VDTrafficShapingPolicy " + "Get-VDUplinkLacpPolicy Get-VDUplinkTeamingPolicy Get-VDisk Get-VIAccount Get-VICommand Get-VICredentialStoreItem Get-VIEvent Get-VIObjectByVIView Get-VIPermission Get-VIPrivilege " + "Get-VIProperty Get-VIRole Get-VM Get-VMGuest Get-VMHost Get-VMHostAccount Get-VMHostAdvancedConfiguration Get-VMHostAuthentication Get-VMHostAvailableTimeZone " + "Get-VMHostDiagnosticPartition Get-VMHostDisk Get-VMHostDiskPartition Get-VMHostFirewallDefaultPolicy Get-VMHostFirewallException Get-VMHostFirmware Get-VMHostHardware " + "Get-VMHostHba Get-VMHostModule Get-VMHostNetwork Get-VMHostNetworkAdapter Get-VMHostNtpServer Get-VMHostPatch Get-VMHostPciDevice Get-VMHostProfile " + "Get-VMHostProfileImageCacheConfiguration Get-VMHostProfileRequiredInput Get-VMHostProfileStorageDeviceConfiguration Get-VMHostProfileUserConfiguration " + "Get-VMHostProfileVmPortGroupConfiguration Get-VMHostRoute Get-VMHostService Get-VMHostSnmp Get-VMHostStartPolicy Get-VMHostStorage Get-VMHostSysLogServer Get-VMQuestion " + "Get-VMResourceConfiguration Get-VMStartPolicy Get-VTpm Get-VTpmCSR Get-VTpmCertificate Get-VasaProvider Get-VasaStorageArray Get-View Get-VirtualPortGroup Get-VirtualSwitch " + "Get-VmcSddcNetworkService Get-VmcService Get-VsanClusterConfiguration Get-VsanComponent Get-VsanDisk Get-VsanDiskGroup Get-VsanEvacuationPlan Get-VsanFaultDomain " + "Get-VsanIscsiInitiatorGroup Get-VsanIscsiInitiatorGroupTargetAssociation Get-VsanIscsiLun Get-VsanIscsiTarget Get-VsanObject Get-VsanResyncingComponent Get-VsanRuntimeInfo " + "Get-VsanSpaceUsage Get-VsanStat Get-VsanView Get-vRAApplianceServiceStatus Get-vRAAuthorizationRole Get-vRABlueprint Get-vRABusinessGroup Get-vRACatalogItem " + "Get-vRACatalogItemRequestTemplate Get-vRACatalogPrincipal Get-vRAComponentRegistryService Get-vRAComponentRegistryServiceEndpoint Get-vRAComponentRegistryServiceStatus " + "Get-vRAContent Get-vRAContentData Get-vRAContentType Get-vRACustomForm Get-vRAEntitledCatalogItem Get-vRAEntitledService Get-vRAEntitlement Get-vRAExternalNetworkProfile " + "Get-vRAGroupPrincipal Get-vRAIcon Get-vRANATNetworkProfile Get-vRANetworkProfileIPAddressList Get-vRANetworkProfileIPRangeSummary Get-vRAPackage Get-vRAPackageContent " + "Get-vRAPropertyDefinition Get-vRAPropertyGroup Get-vRARequest Get-vRARequestDetail Get-vRAReservation Get-vRAReservationComputeResource Get-vRAReservationComputeResourceMemory " + "Get-vRAReservationComputeResourceNetwork Get-vRAReservationComputeResourceResourcePool Get-vRAReservationComputeResourceStorage Get-vRAReservationPolicy " + "Get-vRAReservationTemplate Get-vRAReservationType Get-vRAResource Get-vRAResourceAction Get-vRAResourceActionRequestTemplate Get-vRAResourceMetric Get-vRAResourceOperation " + "Get-vRAResourceType Get-vRARoutedNetworkProfile Get-vRAService Get-vRAServiceBlueprint Get-vRASourceMachine Get-vRAStorageReservationPolicy Get-vRATenant Get-vRATenantDirectory " + "Get-vRATenantDirectoryStatus Get-vRATenantRole Get-vRAUserPrincipal Get-vRAUserPrincipalGroupMembership Get-vRAVersion Get-vRNIAPIVersion Get-vRNIApplication " + "Get-vRNIApplicationTier Get-vRNIDataSource Get-vRNIDataSourceSNMPConfig Get-vRNIDatastore Get-vRNIDistributedSwitch Get-vRNIDistributedSwitchPortGroup Get-vRNIEntity " + "Get-vRNIEntityName Get-vRNIFirewallRule Get-vRNIFlow Get-vRNIHost Get-vRNIHostVMKNic Get-vRNIIPSet Get-vRNIL2Network Get-vRNINSXManager Get-vRNINodes Get-vRNIProblem " + "Get-vRNIRecommendedRules Get-vRNIRecommendedRulesNsxBundle Get-vRNISecurityGroup Get-vRNISecurityTag Get-vRNIService Get-vRNIServiceGroup Get-vRNIVM Get-vRNIVMvNIC " + "Get-vRNIvCenter Get-vRNIvCenterCluster Get-vRNIvCenterDatacenter Get-vRNIvCenterFolder Grant-NsxSpoofguardNicApproval Import-CIVApp Import-CIVAppTemplate Import-NsxObject " + "Import-PackageProvider Import-PowerShellDataFile Import-SpbmStoragePolicy Import-VApp Import-VMHostProfile Import-vRAContentData Import-vRAIcon Import-vRAPackage " + "Initialize-ConfigurationRuntimeState Install-Module Install-NsxCluster Install-Package Install-PackageProvider Install-Script Install-VMHostPatch Invoke-DrsRecommendation " + "Invoke-NsxCli Invoke-NsxClusterResolveAll Invoke-NsxManagerSync Invoke-NsxRestMethod Invoke-NsxWebRequest Invoke-VMHostProfile Invoke-VMScript Invoke-XpathQuery " + "Invoke-vRADataCollection Invoke-vRARestMethod Invoke-vRATenantDirectorySync Invoke-vRNIRestMethod Join-String Mount-Tools Move-Cluster Move-Datacenter Move-Datastore Move-Folder " + "Move-HardDisk Move-Inventory Move-NsxSecurityPolicyRule Move-ResourcePool Move-Template Move-VApp Move-VDisk Move-VM Move-VMHost New-AdvancedSetting New-AlarmAction " + "New-AlarmActionTrigger New-CDDrive New-CIAccessControlRule New-CIVApp New-CIVAppNetwork New-CIVAppTemplate New-CIVM New-Cluster New-CustomAttribute New-Datacenter New-Datastore " + "New-DatastoreCluster New-DatastoreDrive New-DrsClusterGroup New-DrsRule New-DrsVMHostRule New-DscChecksum New-FloppyDrive New-Folder New-Guid New-HCXAppliance New-HCXMigration " + "New-HCXNetworkExtension New-HCXNetworkMapping New-HCXReplication New-HCXSitePairing New-HCXStaticRoute New-HardDisk New-IScsiHbaTarget New-KmipClientCertificate " + "New-NetworkAdapter New-NfsUser New-NsxAddressSpec New-NsxClusterVxlanConfig New-NsxController New-NsxDynamicCriteriaSpec New-NsxEdge New-NsxEdgeBgpNeighbour New-NsxEdgeCsr " + "New-NsxEdgeFirewallRule New-NsxEdgeInterfaceSpec New-NsxEdgeNatRule New-NsxEdgeOspfArea New-NsxEdgeOspfInterface New-NsxEdgePrefix New-NsxEdgeRedistributionRule " + "New-NsxEdgeSelfSignedCertificate New-NsxEdgeStaticRoute New-NsxEdgeSubInterface New-NsxEdgeSubInterfaceSpec New-NsxFirewallRule New-NsxFirewallSavedConfiguration " + "New-NsxFirewallSection New-NsxIpPool New-NsxIpSet New-NsxLoadBalancerApplicationProfile New-NsxLoadBalancerApplicationRule New-NsxLoadBalancerMemberSpec " + "New-NsxLoadBalancerMonitor New-NsxLoadBalancerPool New-NsxLogicalRouter New-NsxLogicalRouterBgpNeighbour New-NsxLogicalRouterBridge New-NsxLogicalRouterInterface " + "New-NsxLogicalRouterInterfaceSpec New-NsxLogicalRouterOspfArea New-NsxLogicalRouterOspfInterface New-NsxLogicalRouterPrefix New-NsxLogicalRouterRedistributionRule " + "New-NsxLogicalRouterStaticRoute New-NsxLogicalSwitch New-NsxMacSet New-NsxManager New-NsxSecurityGroup New-NsxSecurityPolicy New-NsxSecurityPolicyAssignment " + "New-NsxSecurityPolicyFirewallRuleSpec New-NsxSecurityPolicyGuestIntrospectionSpec New-NsxSecurityPolicyNetworkIntrospectionSpec New-NsxSecurityTag New-NsxSecurityTagAssignment " + "New-NsxSegmentIdRange New-NsxService New-NsxServiceGroup New-NsxSpoofguardPolicy New-NsxSslVpnAuthServer New-NsxSslVpnClientInstallationPackage New-NsxSslVpnIpPool " + "New-NsxSslVpnPrivateNetwork New-NsxSslVpnUser New-NsxTransportZone New-NsxVdsContext New-OSCustomizationNicMapping New-OSCustomizationSpec New-Org New-OrgNetwork New-OrgVdc " + "New-OrgVdcNetwork New-ResourcePool New-ScriptFileInfo New-ScsiController New-Snapshot New-SpbmRule New-SpbmRuleSet New-SpbmStoragePolicy New-StatInterval New-Tag " + "New-TagAssignment New-TagCategory New-Template New-TemporaryFile New-VAIOFilter New-VApp New-VDPortgroup New-VDSwitch New-VDSwitchPrivateVlan New-VDisk " + "New-VICredentialStoreItem New-VIInventoryDrive New-VIPermission New-VIProperty New-VIRole New-VISamlSecurityContext New-VM New-VMHostAccount New-VMHostNetworkAdapter " + "New-VMHostProfile New-VMHostProfileVmPortGroupConfiguration New-VMHostRoute New-VTpm New-VasaProvider New-VcsOAuthSecurityContext New-VirtualPortGroup New-VirtualSwitch " + "New-VsanDisk New-VsanDiskGroup New-VsanFaultDomain New-VsanIscsiInitiatorGroup New-VsanIscsiInitiatorGroupTargetAssociation New-VsanIscsiLun New-VsanIscsiTarget " + "New-vRABusinessGroup New-vRAEntitlement New-vRAExternalNetworkProfile New-vRAGroupPrincipal New-vRANATNetworkProfile New-vRANetworkProfileIPRangeDefinition New-vRAPackage " + "New-vRAPropertyDefinition New-vRAPropertyGroup New-vRAReservation New-vRAReservationNetworkDefinition New-vRAReservationPolicy New-vRAReservationStorageDefinition " + "New-vRARoutedNetworkProfile New-vRAService New-vRAStorageReservationPolicy New-vRATenant New-vRATenantDirectory New-vRAUserPrincipal New-vRNIApplication New-vRNIApplicationTier " + "New-vRNIDataSource Open-VMConsoleWindow Publish-Module Publish-NsxSpoofguardPolicy Publish-Script Register-PSRepository Register-PackageSource Remove-AdvancedSetting " + "Remove-AlarmAction Remove-AlarmActionTrigger Remove-Alias Remove-CDDrive Remove-CIAccessControlRule Remove-CIVApp Remove-CIVAppNetwork Remove-CIVAppTemplate Remove-Cluster " + "Remove-CustomAttribute Remove-Datacenter Remove-Datastore Remove-DatastoreCluster Remove-DrsClusterGroup Remove-DrsRule Remove-DrsVMHostRule Remove-FloppyDrive Remove-Folder " + "Remove-HCXAppliance Remove-HCXNetworkExtension Remove-HCXReplication Remove-HCXSitePairing Remove-HardDisk Remove-IScsiHbaTarget Remove-Inventory Remove-KeyManagementServer " + "Remove-NetworkAdapter Remove-NfsUser Remove-NsxCluster Remove-NsxClusterVxlanConfig Remove-NsxController Remove-NsxDynamicCriteria Remove-NsxDynamicMemberSet Remove-NsxEdge " + "Remove-NsxEdgeBgpNeighbour Remove-NsxEdgeCertificate Remove-NsxEdgeCsr Remove-NsxEdgeFirewallRule Remove-NsxEdgeInterfaceAddress Remove-NsxEdgeNatRule Remove-NsxEdgeOspfArea " + "Remove-NsxEdgeOspfInterface Remove-NsxEdgePrefix Remove-NsxEdgeRedistributionRule Remove-NsxEdgeStaticRoute Remove-NsxEdgeSubInterface Remove-NsxFirewallExclusionListMember " + "Remove-NsxFirewallRule Remove-NsxFirewallRuleMember Remove-NsxFirewallSavedConfiguration Remove-NsxFirewallSection Remove-NsxIpPool Remove-NsxIpSet Remove-NsxIpSetMember " + "Remove-NsxLoadBalancerApplicationProfile Remove-NsxLoadBalancerMonitor Remove-NsxLoadBalancerPool Remove-NsxLoadBalancerPoolMember Remove-NsxLoadBalancerVip " + "Remove-NsxLogicalRouter Remove-NsxLogicalRouterBgpNeighbour Remove-NsxLogicalRouterBridge Remove-NsxLogicalRouterInterface Remove-NsxLogicalRouterOspfArea " + "Remove-NsxLogicalRouterOspfInterface Remove-NsxLogicalRouterPrefix Remove-NsxLogicalRouterRedistributionRule Remove-NsxLogicalRouterStaticRoute Remove-NsxLogicalSwitch " + "Remove-NsxMacSet Remove-NsxSecondaryManager Remove-NsxSecurityGroup Remove-NsxSecurityGroupMember Remove-NsxSecurityPolicy Remove-NsxSecurityPolicyAssignment " + "Remove-NsxSecurityPolicyRule Remove-NsxSecurityPolicyRuleGroup Remove-NsxSecurityPolicyRuleService Remove-NsxSecurityTag Remove-NsxSecurityTagAssignment " + "Remove-NsxSegmentIdRange Remove-NsxService Remove-NsxServiceGroup Remove-NsxSpoofguardPolicy Remove-NsxSslVpnClientInstallationPackage Remove-NsxSslVpnIpPool " + "Remove-NsxSslVpnPrivateNetwork Remove-NsxSslVpnUser Remove-NsxTransportZone Remove-NsxTransportZoneMember Remove-NsxVdsContext Remove-OSCustomizationNicMapping " + "Remove-OSCustomizationSpec Remove-Org Remove-OrgNetwork Remove-OrgVdc Remove-OrgVdcNetwork Remove-PSReadLineKeyHandler Remove-PassthroughDevice Remove-ResourcePool " + "Remove-Snapshot Remove-SpbmStoragePolicy Remove-StatInterval Remove-Tag Remove-TagAssignment Remove-TagCategory Remove-Template Remove-UsbDevice Remove-VAIOFilter Remove-VApp " + "Remove-VDPortGroup Remove-VDSwitch Remove-VDSwitchPhysicalNetworkAdapter Remove-VDSwitchPrivateVlan Remove-VDSwitchVMHost Remove-VDisk Remove-VICredentialStoreItem " + "Remove-VIPermission Remove-VIProperty Remove-VIRole Remove-VM Remove-VMHost Remove-VMHostAccount Remove-VMHostNetworkAdapter Remove-VMHostNtpServer Remove-VMHostProfile " + "Remove-VMHostProfileVmPortGroupConfiguration Remove-VMHostRoute Remove-VTpm Remove-VasaProvider Remove-VirtualPortGroup Remove-VirtualSwitch " + "Remove-VirtualSwitchPhysicalNetworkAdapter Remove-VsanDisk Remove-VsanDiskGroup Remove-VsanFaultDomain Remove-VsanIscsiInitiatorGroup " + "Remove-VsanIscsiInitiatorGroupTargetAssociation Remove-VsanIscsiLun Remove-VsanIscsiTarget Remove-vRABusinessGroup Remove-vRACustomForm Remove-vRAExternalNetworkProfile " + "Remove-vRAGroupPrincipal Remove-vRAIcon Remove-vRANATNetworkProfile Remove-vRAPackage Remove-vRAPrincipalFromTenantRole Remove-vRAPropertyDefinition Remove-vRAPropertyGroup " + "Remove-vRAReservation Remove-vRAReservationNetwork Remove-vRAReservationPolicy Remove-vRAReservationStorage Remove-vRARoutedNetworkProfile Remove-vRAService " + "Remove-vRAStorageReservationPolicy Remove-vRATenant Remove-vRATenantDirectory Remove-vRAUserPrincipal Remove-vRNIApplication Remove-vRNIApplicationTier Remove-vRNIDataSource " + "Repair-NsxEdge Repair-VsanObject Request-vRACatalogItem Request-vRAResourceAction Restart-CIVApp Restart-CIVAppGuest Restart-CIVM Restart-CIVMGuest Restart-VM Restart-VMGuest " + "Restart-VMHost Restart-VMHostService Resume-HCXReplication Revoke-NsxSpoofguardNicApproval Save-Module Save-Package Save-Script Search-Cloud Set-AdvancedSetting " + "Set-AlarmDefinition Set-Annotation Set-CDDrive Set-CIAccessControlRule Set-CINetworkAdapter Set-CIVApp Set-CIVAppNetwork Set-CIVAppStartRule Set-CIVAppTemplate Set-Cluster " + "Set-CustomAttribute Set-Datacenter Set-Datastore Set-DatastoreCluster Set-DrsClusterGroup Set-DrsRule Set-DrsVMHostRule Set-FloppyDrive Set-Folder Set-HCXAppliance " + "Set-HCXMigration Set-HCXReplication Set-HardDisk Set-IScsiHbaTarget Set-KeyManagementServer Set-KmsCluster Set-MarkdownOption Set-NetworkAdapter Set-NfsUser Set-NicTeamingPolicy " + "Set-NodeExclusiveResources Set-NodeManager Set-NodeResourceSource Set-NodeResources Set-NsxEdge Set-NsxEdgeBgp Set-NsxEdgeFirewall Set-NsxEdgeInterface Set-NsxEdgeNat " + "Set-NsxEdgeOspf Set-NsxEdgeRouting Set-NsxFirewallGlobalConfiguration Set-NsxFirewallRule Set-NsxFirewallSavedConfiguration Set-NsxFirewallThreshold Set-NsxLoadBalancer " + "Set-NsxLoadBalancerPoolMember Set-NsxLogicalRouter Set-NsxLogicalRouterBgp Set-NsxLogicalRouterBridging Set-NsxLogicalRouterInterface Set-NsxLogicalRouterOspf " + "Set-NsxLogicalRouterRouting Set-NsxManager Set-NsxManagerRole Set-NsxManagerTimeSettings Set-NsxSecurityPolicy Set-NsxSecurityPolicyFirewallRule Set-NsxSslVpn " + "Set-OSCustomizationNicMapping Set-OSCustomizationSpec Set-Org Set-OrgNetwork Set-OrgVdc Set-OrgVdcNetwork Set-PSCurrentConfigurationNode Set-PSDefaultConfigurationDocument " + "Set-PSMetaConfigDocInsProcessedBeforeMeta Set-PSMetaConfigVersionInfoV2 Set-PSReadLineKeyHandler Set-PSReadLineOption Set-PSRepository Set-PSTopConfigurationName " + "Set-PackageSource Set-PowerCLIConfiguration Set-ResourcePool Set-ScsiController Set-ScsiLun Set-ScsiLunPath Set-SecurityPolicy Set-Snapshot Set-SpbmEntityConfiguration " + "Set-SpbmStoragePolicy Set-StatInterval Set-Tag Set-TagCategory Set-Template Set-VAIOFilter Set-VApp Set-VDBlockedPolicy Set-VDPort Set-VDPortgroup Set-VDPortgroupOverridePolicy " + "Set-VDSecurityPolicy Set-VDSwitch Set-VDTrafficShapingPolicy Set-VDUplinkLacpPolicy Set-VDUplinkTeamingPolicy Set-VDVlanConfiguration Set-VDisk Set-VIPermission Set-VIRole Set-VM " + "Set-VMHost Set-VMHostAccount Set-VMHostAdvancedConfiguration Set-VMHostAuthentication Set-VMHostDiagnosticPartition Set-VMHostFirewallDefaultPolicy Set-VMHostFirewallException " + "Set-VMHostFirmware Set-VMHostHba Set-VMHostModule Set-VMHostNetwork Set-VMHostNetworkAdapter Set-VMHostProfile Set-VMHostProfileImageCacheConfiguration " + "Set-VMHostProfileStorageDeviceConfiguration Set-VMHostProfileUserConfiguration Set-VMHostProfileVmPortGroupConfiguration Set-VMHostRoute Set-VMHostService Set-VMHostSnmp " + "Set-VMHostStartPolicy Set-VMHostStorage Set-VMHostSysLogServer Set-VMQuestion Set-VMResourceConfiguration Set-VMStartPolicy Set-VTpm Set-VirtualPortGroup Set-VirtualSwitch " + "Set-VsanClusterConfiguration Set-VsanFaultDomain Set-VsanIscsiInitiatorGroup Set-VsanIscsiLun Set-VsanIscsiTarget Set-vRABusinessGroup Set-vRACatalogItem Set-vRACustomForm " + "Set-vRAEntitlement Set-vRAExternalNetworkProfile Set-vRANATNetworkProfile Set-vRAReservation Set-vRAReservationNetwork Set-vRAReservationPolicy Set-vRAReservationStorage " + "Set-vRARoutedNetworkProfile Set-vRAService Set-vRAStorageReservationPolicy Set-vRATenant Set-vRATenantDirectory Set-vRAUserPrincipal Set-vRNIDataSourceSNMPConfig Show-Markdown " + "Start-CIVApp Start-CIVM Start-HCXMigration Start-HCXReplication Start-SpbmReplicationFailover Start-SpbmReplicationPrepareFailover Start-SpbmReplicationPromote " + "Start-SpbmReplicationReverse Start-SpbmReplicationTestFailover Start-ThreadJob Start-VApp Start-VM Start-VMHost Start-VMHostService Start-VsanClusterDiskUpdate " + "Start-VsanClusterRebalance Start-VsanEncryptionConfiguration Stop-CIVApp Stop-CIVAppGuest Stop-CIVM Stop-CIVMGuest Stop-SpbmReplicationTestFailover Stop-Task Stop-VApp Stop-VM " + "Stop-VMGuest Stop-VMHost Stop-VMHostService Stop-VsanClusterRebalance Suspend-CIVApp Suspend-CIVM Suspend-HCXReplication Suspend-VM Suspend-VMGuest Suspend-VMHost " + "Sync-SpbmReplicationGroup Test-ConflictingResources Test-HCXMigration Test-HCXReplication Test-Json Test-ModuleReloadRequired Test-MofInstanceText Test-NodeManager " + "Test-NodeResourceSource Test-NodeResources Test-ScriptFileInfo Test-VMHostProfileCompliance Test-VMHostSnmp Test-VsanClusterHealth Test-VsanNetworkPerformance " + "Test-VsanStoragePerformance Test-VsanVMCreation Test-vRAPackage Uninstall-Module Uninstall-Package Uninstall-Script Unlock-VM Unregister-PSRepository Unregister-PackageSource " + "Update-ConfigurationDocumentRef Update-ConfigurationErrorCount Update-DependsOn Update-LocalConfigManager Update-Module Update-ModuleManifest Update-ModuleVersion Update-PowerNsx " + "Update-Script Update-ScriptFileInfo Update-Tools Update-VsanHclDatabase ValidateUpdate-ConfigurationData Wait-Debugger Wait-NsxControllerJob Wait-NsxGenericJob Wait-NsxJob " + "Wait-Task Wait-Tools Write-Information Write-Log Write-MetaConfigFile Write-NodeMOFFile", nomarkup: "-ne -eq -lt -gt -ge -le -not -like -notlike -match -notmatch -contains -notcontains -in -notin -replace", }, contains: [ BACKTICK_ESCAPE, hljs.NUMBER_MODE, QUOTE_STRING, APOS_STRING, LITERAL, VAR, PS_COMMENT, ], }; }, }, { name: "processing", /* Language: Processing Author: Erik Paluka Category: graphics */ create: function (hljs) { return { keywords: { keyword: "BufferedReader PVector PFont PImage PGraphics HashMap boolean byte char color " + "double float int long String Array FloatDict FloatList IntDict IntList JSONArray JSONObject " + "Object StringDict StringList Table TableRow XML " + // Java keywords "false synchronized int abstract float private char boolean static null if const " + "for true while long throw strictfp finally protected import native final return void " + "enum else break transient new catch instanceof byte super volatile case assert short " + "package default double public try this switch continue throws protected public private", literal: "P2D P3D HALF_PI PI QUARTER_PI TAU TWO_PI", title: "setup draw", built_in: "displayHeight displayWidth mouseY mouseX mousePressed pmouseX pmouseY key " + "keyCode pixels focused frameCount frameRate height width " + "size createGraphics beginDraw createShape loadShape PShape arc ellipse line point " + "quad rect triangle bezier bezierDetail bezierPoint bezierTangent curve curveDetail curvePoint " + "curveTangent curveTightness shape shapeMode beginContour beginShape bezierVertex curveVertex " + "endContour endShape quadraticVertex vertex ellipseMode noSmooth rectMode smooth strokeCap " + "strokeJoin strokeWeight mouseClicked mouseDragged mouseMoved mousePressed mouseReleased " + "mouseWheel keyPressed keyPressedkeyReleased keyTyped print println save saveFrame day hour " + "millis minute month second year background clear colorMode fill noFill noStroke stroke alpha " + "blue brightness color green hue lerpColor red saturation modelX modelY modelZ screenX screenY " + "screenZ ambient emissive shininess specular add createImage beginCamera camera endCamera frustum " + "ortho perspective printCamera printProjection cursor frameRate noCursor exit loop noLoop popStyle " + "pushStyle redraw binary boolean byte char float hex int str unbinary unhex join match matchAll nf " + "nfc nfp nfs split splitTokens trim append arrayCopy concat expand reverse shorten sort splice subset " + "box sphere sphereDetail createInput createReader loadBytes loadJSONArray loadJSONObject loadStrings " + "loadTable loadXML open parseXML saveTable selectFolder selectInput beginRaw beginRecord createOutput " + "createWriter endRaw endRecord PrintWritersaveBytes saveJSONArray saveJSONObject saveStream saveStrings " + "saveXML selectOutput popMatrix printMatrix pushMatrix resetMatrix rotate rotateX rotateY rotateZ scale " + "shearX shearY translate ambientLight directionalLight lightFalloff lights lightSpecular noLights normal " + "pointLight spotLight image imageMode loadImage noTint requestImage tint texture textureMode textureWrap " + "blend copy filter get loadPixels set updatePixels blendMode loadShader PShaderresetShader shader createFont " + "loadFont text textFont textAlign textLeading textMode textSize textWidth textAscent textDescent abs ceil " + "constrain dist exp floor lerp log mag map max min norm pow round sq sqrt acos asin atan atan2 cos degrees " + "radians sin tan noise noiseDetail noiseSeed random randomGaussian randomSeed", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, ], }; }, }, { name: "profile", /* Language: Python profile Description: Python profiler results Author: Brian Beck */ create: function (hljs) { return { contains: [ hljs.C_NUMBER_MODE, { begin: "[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}", end: ":", excludeEnd: true, }, { begin: "(ncalls|tottime|cumtime)", end: "$", keywords: "ncalls tottime|10 cumtime|10 filename", relevance: 10, }, { begin: "function calls", end: "$", contains: [hljs.C_NUMBER_MODE], relevance: 10, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { className: "string", begin: "\\(", end: "\\)$", excludeBegin: true, excludeEnd: true, relevance: 0, }, ], }; }, }, { name: "prolog", /* Language: Prolog Description: Prolog is a general purpose logic programming language associated with artificial intelligence and computational linguistics. Author: Raivo Laanemets */ create: function (hljs) { var ATOM = { begin: /[a-z][A-Za-z0-9_]*/, relevance: 0, }; var VAR = { className: "symbol", variants: [ { begin: /[A-Z][a-zA-Z0-9_]*/ }, { begin: /_[A-Za-z0-9_]*/ }, ], relevance: 0, }; var PARENTED = { begin: /\(/, end: /\)/, relevance: 0, }; var LIST = { begin: /\[/, end: /\]/, }; var LINE_COMMENT = { className: "comment", begin: /%/, end: /$/, contains: [hljs.PHRASAL_WORDS_MODE], }; var BACKTICK_STRING = { className: "string", begin: /`/, end: /`/, contains: [hljs.BACKSLASH_ESCAPE], }; var CHAR_CODE = { className: "string", // 0'a etc. begin: /0\'(\\\'|.)/, }; var SPACE_CODE = { className: "string", begin: /0\'\\s/, // 0'\s }; var PRED_OP = { // relevance booster begin: /:-/, }; var inner = [ ATOM, VAR, PARENTED, PRED_OP, LIST, LINE_COMMENT, hljs.C_BLOCK_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, BACKTICK_STRING, CHAR_CODE, SPACE_CODE, hljs.C_NUMBER_MODE, ]; PARENTED.contains = inner; LIST.contains = inner; return { contains: inner.concat([ { begin: /\.$/ }, // relevance booster ]), }; }, }, { name: "properties", /* Language: Properties Contributors: Valentin Aitken , Egor Rogov Category: common, config */ create: function (hljs) { // whitespaces: space, tab, formfeed var WS0 = "[ \\t\\f]*"; var WS1 = "[ \\t\\f]+"; // delimiter var DELIM = "(" + WS0 + "[:=]" + WS0 + "|" + WS1 + ")"; var KEY_ALPHANUM = "([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"; var KEY_OTHER = "([^\\\\:= \\t\\f\\n]|\\\\.)+"; var DELIM_AND_VALUE = { // skip DELIM end: DELIM, relevance: 0, starts: { // value: everything until end of line (again, taking into account backslashes) className: "string", end: /$/, relevance: 0, contains: [{ begin: "\\\\\\n" }], }, }; return { case_insensitive: true, illegal: /\S/, contains: [ hljs.COMMENT("^\\s*[!#]", "$"), // key: everything until whitespace or = or : (taking into account backslashes) // case of a "normal" key { begin: KEY_ALPHANUM + DELIM, returnBegin: true, contains: [ { className: "attr", begin: KEY_ALPHANUM, endsParent: true, relevance: 0, }, ], starts: DELIM_AND_VALUE, }, // case of key containing non-alphanumeric chars => relevance = 0 { begin: KEY_OTHER + DELIM, returnBegin: true, relevance: 0, contains: [ { className: "meta", begin: KEY_OTHER, endsParent: true, relevance: 0, }, ], starts: DELIM_AND_VALUE, }, // case of an empty key { className: "attr", relevance: 0, begin: KEY_OTHER + WS0 + "$", }, ], }; }, }, { name: "protobuf", /* Language: Protocol Buffers Author: Dan Tao Description: Protocol buffer message definition format Category: protocols */ create: function (hljs) { return { keywords: { keyword: "package import option optional required repeated group oneof", built_in: "double float int32 int64 uint32 uint64 sint32 sint64 " + "fixed32 fixed64 sfixed32 sfixed64 bool string bytes", literal: "true false", }, contains: [ hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, hljs.C_LINE_COMMENT_MODE, { className: "class", beginKeywords: "message enum service", end: /\{/, illegal: /\n/, contains: [ hljs.inherit(hljs.TITLE_MODE, { starts: { endsWithParent: true, excludeEnd: true }, // hack: eating everything after the first title }), ], }, { className: "function", beginKeywords: "rpc", end: /;/, excludeEnd: true, keywords: "rpc returns", }, { begin: /^\s*[A-Z_]+/, end: /\s*=/, excludeEnd: true, }, ], }; }, }, { name: "puppet", /* Language: Puppet Author: Jose Molina Colmenero Category: config */ create: function (hljs) { var PUPPET_KEYWORDS = { keyword: /* language keywords */ "and case default else elsif false if in import enherits node or true undef unless main settings $string ", literal: /* metaparameters */ "alias audit before loglevel noop require subscribe tag " + /* normal attributes */ "owner ensure group mode name|0 changes context force incl lens load_path onlyif provider returns root show_diff type_check " + "en_address ip_address realname command environment hour monute month monthday special target weekday " + "creates cwd ogoutput refresh refreshonly tries try_sleep umask backup checksum content ctime force ignore " + "links mtime purge recurse recurselimit replace selinux_ignore_defaults selrange selrole seltype seluser source " + "souirce_permissions sourceselect validate_cmd validate_replacement allowdupe attribute_membership auth_membership forcelocal gid " + "ia_load_module members system host_aliases ip allowed_trunk_vlans description device_url duplex encapsulation etherchannel " + "native_vlan speed principals allow_root auth_class auth_type authenticate_user k_of_n mechanisms rule session_owner shared options " + "device fstype enable hasrestart directory present absent link atboot blockdevice device dump pass remounts poller_tag use " + "message withpath adminfile allow_virtual allowcdrom category configfiles flavor install_options instance package_settings platform " + "responsefile status uninstall_options vendor unless_system_user unless_uid binary control flags hasstatus manifest pattern restart running " + "start stop allowdupe auths expiry gid groups home iterations key_membership keys managehome membership password password_max_age " + "password_min_age profile_membership profiles project purge_ssh_keys role_membership roles salt shell uid baseurl cost descr enabled " + "enablegroups exclude failovermethod gpgcheck gpgkey http_caching include includepkgs keepalive metadata_expire metalink mirrorlist " + "priority protect proxy proxy_password proxy_username repo_gpgcheck s3_enabled skip_if_unavailable sslcacert sslclientcert sslclientkey " + "sslverify mounted", built_in: /* core facts */ "architecture augeasversion blockdevices boardmanufacturer boardproductname boardserialnumber cfkey dhcp_servers " + "domain ec2_ ec2_userdata facterversion filesystems ldom fqdn gid hardwareisa hardwaremodel hostname id|0 interfaces " + "ipaddress ipaddress_ ipaddress6 ipaddress6_ iphostnumber is_virtual kernel kernelmajversion kernelrelease kernelversion " + "kernelrelease kernelversion lsbdistcodename lsbdistdescription lsbdistid lsbdistrelease lsbmajdistrelease lsbminordistrelease " + "lsbrelease macaddress macaddress_ macosx_buildversion macosx_productname macosx_productversion macosx_productverson_major " + "macosx_productversion_minor manufacturer memoryfree memorysize netmask metmask_ network_ operatingsystem operatingsystemmajrelease " + "operatingsystemrelease osfamily partitions path physicalprocessorcount processor processorcount productname ps puppetversion " + "rubysitedir rubyversion selinux selinux_config_mode selinux_config_policy selinux_current_mode selinux_current_mode selinux_enforced " + "selinux_policyversion serialnumber sp_ sshdsakey sshecdsakey sshrsakey swapencrypted swapfree swapsize timezone type uniqueid uptime " + "uptime_days uptime_hours uptime_seconds uuid virtual vlans xendomains zfs_version zonenae zones zpool_version", }; var COMMENT = hljs.COMMENT("#", "$"); var IDENT_RE = "([A-Za-z_]|::)(\\w|::)*"; var TITLE = hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE }); var VARIABLE = { className: "variable", begin: "\\$" + IDENT_RE, }; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE, VARIABLE], variants: [ { begin: /'/, end: /'/ }, { begin: /"/, end: /"/ }, ], }; return { aliases: ["pp"], contains: [ COMMENT, VARIABLE, STRING, { beginKeywords: "class", end: "\\{|;", illegal: /=/, contains: [TITLE, COMMENT], }, { beginKeywords: "define", end: /\{/, contains: [ { className: "section", begin: hljs.IDENT_RE, endsParent: true, }, ], }, { begin: hljs.IDENT_RE + "\\s+\\{", returnBegin: true, end: /\S/, contains: [ { className: "keyword", begin: hljs.IDENT_RE, }, { begin: /\{/, end: /\}/, keywords: PUPPET_KEYWORDS, relevance: 0, contains: [ STRING, COMMENT, { begin: "[a-zA-Z_]+\\s*=>", returnBegin: true, end: "=>", contains: [ { className: "attr", begin: hljs.IDENT_RE, }, ], }, { className: "number", begin: "(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", relevance: 0, }, VARIABLE, ], }, ], relevance: 0, }, ], }; }, }, { name: "purebasic", /* Language: PureBASIC Author: Tristano Ajmone Description: Syntax highlighting for PureBASIC (v.5.00-5.60). No inline ASM highlighting. (v.1.2, May 2017) Credits: I've taken inspiration from the PureBasic language file for GeSHi, created by Gustavo Julio Fiorenza (GuShH). */ create: // Base deafult colors in PB IDE: background: #FFFFDF; foreground: #000000; function (hljs) { var STRINGS = { // PB IDE color: #0080FF (Azure Radiance) className: "string", begin: '(~)?"', end: '"', illegal: "\\n", }; var CONSTANTS = { // PB IDE color: #924B72 (Cannon Pink) // "#" + a letter or underscore + letters, digits or underscores + (optional) "$" className: "symbol", begin: "#[a-zA-Z_]\\w*\\$?", }; return { aliases: ["pb", "pbi"], // PB IDE color: #006666 (Blue Stone) + Bold keywords: // Keywords from all version of PureBASIC 5.00 upward ... "Align And Array As Break CallDebugger Case CompilerCase CompilerDefault " + "CompilerElse CompilerElseIf CompilerEndIf CompilerEndSelect CompilerError " + "CompilerIf CompilerSelect CompilerWarning Continue Data DataSection Debug " + "DebugLevel Declare DeclareC DeclareCDLL DeclareDLL DeclareModule Default " + "Define Dim DisableASM DisableDebugger DisableExplicit Else ElseIf EnableASM " + "EnableDebugger EnableExplicit End EndDataSection EndDeclareModule EndEnumeration " + "EndIf EndImport EndInterface EndMacro EndModule EndProcedure EndSelect " + "EndStructure EndStructureUnion EndWith Enumeration EnumerationBinary Extends " + "FakeReturn For ForEach ForEver Global Gosub Goto If Import ImportC " + "IncludeBinary IncludeFile IncludePath Interface List Macro MacroExpandedCount " + "Map Module NewList NewMap Next Not Or Procedure ProcedureC " + "ProcedureCDLL ProcedureDLL ProcedureReturn Protected Prototype PrototypeC ReDim " + "Read Repeat Restore Return Runtime Select Shared Static Step Structure " + "StructureUnion Swap Threaded To UndefineMacro Until Until UnuseModule " + "UseModule Wend While With XIncludeFile XOr", contains: [ // COMMENTS | PB IDE color: #00AAAA (Persian Green) hljs.COMMENT(";", "$", { relevance: 0 }), { // PROCEDURES DEFINITIONS className: "function", begin: "\\b(Procedure|Declare)(C|CDLL|DLL)?\\b", end: "\\(", excludeEnd: true, returnBegin: true, contains: [ { // PROCEDURE KEYWORDS | PB IDE color: #006666 (Blue Stone) + Bold className: "keyword", begin: "(Procedure|Declare)(C|CDLL|DLL)?", excludeEnd: true, }, { // PROCEDURE RETURN TYPE SETTING | PB IDE color: #000000 (Black) className: "type", begin: "\\.\\w*", // end: ' ', }, hljs.UNDERSCORE_TITLE_MODE, // PROCEDURE NAME | PB IDE color: #006666 (Blue Stone) ], }, STRINGS, CONSTANTS, ], }; }, /* ============================================================================== CHANGELOG ============================================================================== - v.1.2 (2017-05-12) -- BUG-FIX: Some keywords were accidentally joyned together. Now fixed. - v.1.1 (2017-04-30) -- Updated to PureBASIC 5.60. -- Keywords list now built by extracting them from the PureBASIC SDK's "SyntaxHilighting.dll" (from each PureBASIC version). Tokens from each version are added to the list, and renamed or removed tokens are kept for the sake of covering all versions of the language from PureBASIC v5.00 upward. (NOTE: currently, there are no renamed or deprecated tokens in the keywords list). For more info, see: -- http://www.purebasic.fr/english/viewtopic.php?&p=506269 -- https://github.com/tajmone/purebasic-archives/tree/master/syntax-highlighting/guidelines - v.1.0 (April 2016) -- First release -- Keywords list taken and adapted from GuShH's (Gustavo Julio Fiorenza) PureBasic language file for GeSHi: -- https://github.com/easybook/geshi/blob/master/geshi/purebasic.php */ }, { name: "python", /* Language: Python Category: common */ create: function (hljs) { var KEYWORDS = { keyword: "and elif is global as in if from raise for except finally print import pass return " + "exec else break not with class assert yield try while continue del or def lambda " + "async await nonlocal|10", built_in: "Ellipsis NotImplemented", literal: "False None True", }; var PROMPT = { className: "meta", begin: /^(>>>|\.\.\.) /, }; var SUBST = { className: "subst", begin: /\{/, end: /\}/, keywords: KEYWORDS, illegal: /#/, }; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE], variants: [ { begin: /(u|b)?r?'''/, end: /'''/, contains: [hljs.BACKSLASH_ESCAPE, PROMPT], relevance: 10, }, { begin: /(u|b)?r?"""/, end: /"""/, contains: [hljs.BACKSLASH_ESCAPE, PROMPT], relevance: 10, }, { begin: /(fr|rf|f)'''/, end: /'''/, contains: [hljs.BACKSLASH_ESCAPE, PROMPT, SUBST], }, { begin: /(fr|rf|f)"""/, end: /"""/, contains: [hljs.BACKSLASH_ESCAPE, PROMPT, SUBST], }, { begin: /(u|r|ur)'/, end: /'/, relevance: 10, }, { begin: /(u|r|ur)"/, end: /"/, relevance: 10, }, { begin: /(b|br)'/, end: /'/, }, { begin: /(b|br)"/, end: /"/, }, { begin: /(fr|rf|f)'/, end: /'/, contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, { begin: /(fr|rf|f)"/, end: /"/, contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, ], }; var NUMBER = { className: "number", relevance: 0, variants: [ { begin: hljs.BINARY_NUMBER_RE + "[lLjJ]?" }, { begin: "\\b(0o[0-7]+)[lLjJ]?" }, { begin: hljs.C_NUMBER_RE + "[lLjJ]?" }, ], }; var PARAMS = { className: "params", begin: /\(/, end: /\)/, contains: ["self", PROMPT, NUMBER, STRING], }; SUBST.contains = [STRING, NUMBER, PROMPT]; return { aliases: ["py", "gyp", "ipython"], keywords: KEYWORDS, illegal: /(<\/|->|\?)|=>/, contains: [ PROMPT, NUMBER, STRING, hljs.HASH_COMMENT_MODE, { variants: [ { className: "function", beginKeywords: "def" }, { className: "class", beginKeywords: "class" }, ], end: /:/, illegal: /[${=;\n,]/, contains: [ hljs.UNDERSCORE_TITLE_MODE, PARAMS, { begin: /->/, endsWithParent: true, keywords: "None", }, ], }, { className: "meta", begin: /^[\t ]*@/, end: /$/, }, { begin: /\b(print|exec)\(/, // don’t highlight keywords-turned-functions in Python 3 }, ], }; }, }, { name: "q", /* Language: Q Author: Sergey Vidyuk Description: K/Q/Kdb+ from Kx Systems */ create: function (hljs) { var Q_KEYWORDS = { keyword: "do while select delete by update from", literal: "0b 1b", built_in: "neg not null string reciprocal floor ceiling signum mod xbar xlog and or each scan over prior mmu lsq inv md5 ltime gtime count first var dev med cov cor all any rand sums prds mins maxs fills deltas ratios avgs differ prev next rank reverse iasc idesc asc desc msum mcount mavg mdev xrank mmin mmax xprev rotate distinct group where flip type key til get value attr cut set upsert raze union inter except cross sv vs sublist enlist read0 read1 hopen hclose hdel hsym hcount peach system ltrim rtrim trim lower upper ssr view tables views cols xcols keys xkey xcol xasc xdesc fkeys meta lj aj aj0 ij pj asof uj ww wj wj1 fby xgroup ungroup ej save load rsave rload show csv parse eval min max avg wavg wsum sin cos tan sum", type: "`float `double int `timestamp `timespan `datetime `time `boolean `symbol `char `byte `short `long `real `month `date `minute `second `guid", }; return { aliases: ["k", "kdb"], keywords: Q_KEYWORDS, lexemes: /(`?)[A-Za-z0-9_]+\b/, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, ], }; }, }, { name: "qml", /* Language: QML Requires: javascript.js, xml.js Author: John Foster Description: Syntax highlighting for the Qt Quick QML scripting language, based mostly off the JavaScript parser. Category: scripting */ create: function (hljs) { var KEYWORDS = { keyword: "in of on if for while finally var new function do return void else break catch " + "instanceof with throw case default try this switch continue typeof delete " + "let yield const export super debugger as async await import", literal: "true false null undefined NaN Infinity", built_in: "eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent " + "encodeURI encodeURIComponent escape unescape Object Function Boolean Error " + "EvalError InternalError RangeError ReferenceError StopIteration SyntaxError " + "TypeError URIError Number Math Date String RegExp Array Float32Array " + "Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array " + "Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require " + "module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect " + "Behavior bool color coordinate date double enumeration font geocircle georectangle " + "geoshape int list matrix4x4 parent point quaternion real rect " + "size string url variant vector2d vector3d vector4d" + "Promise", }; var QML_IDENT_RE = "[a-zA-Z_][a-zA-Z0-9\\._]*"; // Isolate property statements. Ends at a :, =, ;, ,, a comment or end of line. // Use property class. var PROPERTY = { className: "keyword", begin: "\\bproperty\\b", starts: { className: "string", end: "(:|=|;|,|//|/\\*|$)", returnEnd: true, }, }; // Isolate signal statements. Ends at a ) a comment or end of line. // Use property class. var SIGNAL = { className: "keyword", begin: "\\bsignal\\b", starts: { className: "string", end: "(\\(|:|=|;|,|//|/\\*|$)", returnEnd: true, }, }; // id: is special in QML. When we see id: we want to mark the id: as attribute and // emphasize the token following. var ID_ID = { className: "attribute", begin: "\\bid\\s*:", starts: { className: "string", end: QML_IDENT_RE, returnEnd: false, }, }; // Find QML object attribute. An attribute is a QML identifier followed by :. // Unfortunately it's hard to know where it ends, as it may contain scalars, // objects, object definitions, or javascript. The true end is either when the parent // ends or the next attribute is detected. var QML_ATTRIBUTE = { begin: QML_IDENT_RE + "\\s*:", returnBegin: true, contains: [ { className: "attribute", begin: QML_IDENT_RE, end: "\\s*:", excludeEnd: true, relevance: 0, }, ], relevance: 0, }; // Find QML object. A QML object is a QML identifier followed by { and ends at the matching }. // All we really care about is finding IDENT followed by { and just mark up the IDENT and ignore the {. var QML_OBJECT = { begin: QML_IDENT_RE + "\\s*{", end: "{", returnBegin: true, relevance: 0, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: QML_IDENT_RE }), ], }; return { aliases: ["qt"], case_insensitive: false, keywords: KEYWORDS, contains: [ { className: "meta", begin: /^\s*['"]use (strict|asm)['"]/, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, { // template string className: "string", begin: "`", end: "`", contains: [ hljs.BACKSLASH_ESCAPE, { className: "subst", begin: "\\$\\{", end: "\\}", }, ], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "number", variants: [ { begin: "\\b(0[bB][01]+)" }, { begin: "\\b(0[oO][0-7]+)" }, { begin: hljs.C_NUMBER_RE }, ], relevance: 0, }, { // "value" container begin: "(" + hljs.RE_STARTERS_RE + "|\\b(case|return|throw)\\b)\\s*", keywords: "return throw case", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.REGEXP_MODE, { // E4X / JSX begin: /\s*[);\]]/, relevance: 0, subLanguage: "xml", }, ], relevance: 0, }, SIGNAL, PROPERTY, { className: "function", beginKeywords: "function", end: /\{/, excludeEnd: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][0-9A-Za-z$_]*/, }), { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, ], illegal: /\[|%/, }, { begin: "\\." + hljs.IDENT_RE, relevance: 0, // hack: prevents detection of keywords after dots }, ID_ID, QML_ATTRIBUTE, QML_OBJECT, ], illegal: /#/, }; }, }, { name: "r", /* Language: R Author: Joe Cheng Category: scientific */ create: function (hljs) { var IDENT_RE = "([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*"; return { contains: [ hljs.HASH_COMMENT_MODE, { begin: IDENT_RE, lexemes: IDENT_RE, keywords: { keyword: "function if in break next repeat else for return switch while try tryCatch " + "stop warning require library attach detach source setMethod setGeneric " + "setGroupGeneric setClass ...", literal: "NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 " + "NA_complex_|10", }, relevance: 0, }, { // hex value className: "number", begin: "0[xX][0-9a-fA-F]+[Li]?\\b", relevance: 0, }, { // explicit integer className: "number", begin: "\\d+(?:[eE][+\\-]?\\d*)?L\\b", relevance: 0, }, { // number with trailing decimal className: "number", begin: "\\d+\\.(?!\\d)(?:i\\b)?", relevance: 0, }, { // number className: "number", begin: "\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b", relevance: 0, }, { // number with leading decimal className: "number", begin: "\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b", relevance: 0, }, { // escaped identifier begin: "`", end: "`", relevance: 0, }, { className: "string", contains: [hljs.BACKSLASH_ESCAPE], variants: [ { begin: '"', end: '"' }, { begin: "'", end: "'" }, ], }, ], }; }, }, { name: "reasonml", /* Language: ReasonML Author: Gidi Meir Morris Category: functional */ create: function (hljs) { function orReValues(ops) { return ops .map(function (op) { return op .split("") .map(function (char) { return "\\" + char; }) .join(""); }) .join("|"); } var RE_IDENT = "~?[a-z$_][0-9a-zA-Z$_]*"; var RE_MODULE_IDENT = "`?[A-Z$_][0-9a-zA-Z$_]*"; var RE_PARAM_TYPEPARAM = "'?[a-z$_][0-9a-z$_]*"; var RE_PARAM_TYPE = "\s*:\s*[a-z$_][0-9a-z$_]*(\(\s*(" + RE_PARAM_TYPEPARAM + "\s*(," + RE_PARAM_TYPEPARAM + ")*)?\s*\))?"; var RE_PARAM = RE_IDENT + "(" + RE_PARAM_TYPE + ")?(" + RE_PARAM_TYPE + ")?"; var RE_OPERATOR = "(" + orReValues([ "||", "&&", "++", "**", "+.", "*", "/", "*.", "/.", "...", "|>", ]) + "|==|===)"; var RE_OPERATOR_SPACED = "\\s+" + RE_OPERATOR + "\\s+"; var KEYWORDS = { keyword: "and as asr assert begin class constraint do done downto else end exception external" + "for fun function functor if in include inherit initializer" + "land lazy let lor lsl lsr lxor match method mod module mutable new nonrec" + "object of open or private rec sig struct then to try type val virtual when while with", built_in: "array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 ref string unit ", literal: "true false", }; var RE_NUMBER = "\\b(0[xX][a-fA-F0-9_]+[Lln]?|" + "0[oO][0-7_]+[Lln]?|" + "0[bB][01_]+[Lln]?|" + "[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)"; var NUMBER_MODE = { className: "number", relevance: 0, variants: [ { begin: RE_NUMBER, }, { begin: "\\(\\-" + RE_NUMBER + "\\)", }, ], }; var OPERATOR_MODE = { className: "operator", relevance: 0, begin: RE_OPERATOR, }; var LIST_CONTENTS_MODES = [ { className: "identifier", relevance: 0, begin: RE_IDENT, }, OPERATOR_MODE, NUMBER_MODE, ]; var MODULE_ACCESS_CONTENTS = [ hljs.QUOTE_STRING_MODE, OPERATOR_MODE, { className: "module", begin: "\\b" + RE_MODULE_IDENT, returnBegin: true, end: "\.", contains: [ { className: "identifier", begin: RE_MODULE_IDENT, relevance: 0, }, ], }, ]; var PARAMS_CONTENTS = [ { className: "module", begin: "\\b" + RE_MODULE_IDENT, returnBegin: true, end: "\.", relevance: 0, contains: [ { className: "identifier", begin: RE_MODULE_IDENT, relevance: 0, }, ], }, ]; var PARAMS_MODE = { begin: RE_IDENT, end: "(,|\\n|\\))", relevance: 0, contains: [ OPERATOR_MODE, { className: "typing", begin: ":", end: "(,|\\n)", returnBegin: true, relevance: 0, contains: PARAMS_CONTENTS, }, ], }; var FUNCTION_BLOCK_MODE = { className: "function", relevance: 0, keywords: KEYWORDS, variants: [ { begin: "\\s(\\(\\.?.*?\\)|" + RE_IDENT + ")\\s*=>", end: "\\s*=>", returnBegin: true, relevance: 0, contains: [ { className: "params", variants: [ { begin: RE_IDENT, }, { begin: RE_PARAM, }, { begin: /\(\s*\)/, }, ], }, ], }, { begin: "\\s\\(\\.?[^;\\|]*\\)\\s*=>", end: "\\s=>", returnBegin: true, relevance: 0, contains: [ { className: "params", relevance: 0, variants: [PARAMS_MODE], }, ], }, { begin: "\\(\\.\\s" + RE_IDENT + "\\)\\s*=>", }, ], }; MODULE_ACCESS_CONTENTS.push(FUNCTION_BLOCK_MODE); var CONSTRUCTOR_MODE = { className: "constructor", begin: RE_MODULE_IDENT + "\\(", end: "\\)", illegal: "\\n", keywords: KEYWORDS, contains: [ hljs.QUOTE_STRING_MODE, OPERATOR_MODE, { className: "params", begin: "\\b" + RE_IDENT, }, ], }; var PATTERN_MATCH_BLOCK_MODE = { className: "pattern-match", begin: "\\|", returnBegin: true, keywords: KEYWORDS, end: "=>", relevance: 0, contains: [ CONSTRUCTOR_MODE, OPERATOR_MODE, { relevance: 0, className: "constructor", begin: RE_MODULE_IDENT, }, ], }; var MODULE_ACCESS_MODE = { className: "module-access", keywords: KEYWORDS, returnBegin: true, variants: [ { begin: "\\b(" + RE_MODULE_IDENT + "\\.)+" + RE_IDENT, }, { begin: "\\b(" + RE_MODULE_IDENT + "\\.)+\\(", end: "\\)", returnBegin: true, contains: [ FUNCTION_BLOCK_MODE, { begin: "\\(", end: "\\)", skip: true, }, ].concat(MODULE_ACCESS_CONTENTS), }, { begin: "\\b(" + RE_MODULE_IDENT + "\\.)+{", end: "}", }, ], contains: MODULE_ACCESS_CONTENTS, }; PARAMS_CONTENTS.push(MODULE_ACCESS_MODE); return { aliases: ["re"], keywords: KEYWORDS, illegal: "(:\\-|:=|\\${|\\+=)", contains: [ hljs.COMMENT("/\\*", "\\*/", { illegal: "^(\\#,\\/\\/)" }), { className: "character", begin: "'(\\\\[^']+|[^'])'", illegal: "\\n", relevance: 0, }, hljs.QUOTE_STRING_MODE, { className: "literal", begin: "\\(\\)", relevance: 0, }, { className: "literal", begin: "\\[\\|", end: "\\|\\]", relevance: 0, contains: LIST_CONTENTS_MODES, }, { className: "literal", begin: "\\[", end: "\\]", relevance: 0, contains: LIST_CONTENTS_MODES, }, CONSTRUCTOR_MODE, { className: "operator", begin: RE_OPERATOR_SPACED, illegal: "\\-\\->", relevance: 0, }, NUMBER_MODE, hljs.C_LINE_COMMENT_MODE, PATTERN_MATCH_BLOCK_MODE, FUNCTION_BLOCK_MODE, { className: "module-def", begin: "\\bmodule\\s+" + RE_IDENT + "\\s+" + RE_MODULE_IDENT + "\\s+=\\s+{", end: "}", returnBegin: true, keywords: KEYWORDS, relevance: 0, contains: [ { className: "module", relevance: 0, begin: RE_MODULE_IDENT, }, { begin: "{", end: "}", skip: true, }, ].concat(MODULE_ACCESS_CONTENTS), }, MODULE_ACCESS_MODE, ], }; }, }, { name: "rib", /* Language: RenderMan RIB Author: Konstantin Evdokimenko Contributors: Shuen-Huei Guan Category: graphics */ create: function (hljs) { return { keywords: "ArchiveRecord AreaLightSource Atmosphere Attribute AttributeBegin AttributeEnd Basis " + "Begin Blobby Bound Clipping ClippingPlane Color ColorSamples ConcatTransform Cone " + "CoordinateSystem CoordSysTransform CropWindow Curves Cylinder DepthOfField Detail " + "DetailRange Disk Displacement Display End ErrorHandler Exposure Exterior Format " + "FrameAspectRatio FrameBegin FrameEnd GeneralPolygon GeometricApproximation Geometry " + "Hider Hyperboloid Identity Illuminate Imager Interior LightSource " + "MakeCubeFaceEnvironment MakeLatLongEnvironment MakeShadow MakeTexture Matte " + "MotionBegin MotionEnd NuPatch ObjectBegin ObjectEnd ObjectInstance Opacity Option " + "Orientation Paraboloid Patch PatchMesh Perspective PixelFilter PixelSamples " + "PixelVariance Points PointsGeneralPolygons PointsPolygons Polygon Procedural Projection " + "Quantize ReadArchive RelativeDetail ReverseOrientation Rotate Scale ScreenWindow " + "ShadingInterpolation ShadingRate Shutter Sides Skew SolidBegin SolidEnd Sphere " + "SubdivisionMesh Surface TextureCoordinates Torus Transform TransformBegin TransformEnd " + "TransformPoints Translate TrimCurve WorldBegin WorldEnd", illegal: " Website: http://roboconf.net Description: Syntax highlighting for Roboconf's DSL Category: config */ create: function (hljs) { var IDENTIFIER = "[a-zA-Z-_][^\\n{]+\\{"; var PROPERTY = { className: "attribute", begin: /[a-zA-Z-_]+/, end: /\s*:/, excludeEnd: true, starts: { end: ";", relevance: 0, contains: [ { className: "variable", begin: /\.[a-zA-Z-_]+/, }, { className: "keyword", begin: /\(optional\)/, }, ], }, }; return { aliases: ["graph", "instances"], case_insensitive: true, keywords: "import", contains: [ // Facet sections { begin: "^facet " + IDENTIFIER, end: "}", keywords: "facet", contains: [PROPERTY, hljs.HASH_COMMENT_MODE], }, // Instance sections { begin: "^\\s*instance of " + IDENTIFIER, end: "}", keywords: "name count channels instance-data instance-state instance of", illegal: /\S/, contains: ["self", PROPERTY, hljs.HASH_COMMENT_MODE], }, // Component sections { begin: "^" + IDENTIFIER, end: "}", contains: [PROPERTY, hljs.HASH_COMMENT_MODE], }, // Comments hljs.HASH_COMMENT_MODE, ], }; }, }, { name: "routeros", /* Language: Microtik RouterOS script Author: Ivan Dementev Description: Scripting host provides a way to automate some router maintenance tasks by means of executing user-defined scripts bounded to some event occurrence URL: https://wiki.mikrotik.com/wiki/Manual:Scripting */ create: // Colors from RouterOS terminal: // green - #0E9A00 // teal - #0C9A9A // purple - #99069A // light-brown - #9A9900 function (hljs) { var STATEMENTS = "foreach do while for if from to step else on-error and or not in"; // Global commands: Every global command should start with ":" token, otherwise it will be treated as variable. var GLOBAL_COMMANDS = "global local beep delay put len typeof pick log time set find environment terminal error execute parse resolve toarray tobool toid toip toip6 tonum tostr totime"; // Common commands: Following commands available from most sub-menus: var COMMON_COMMANDS = "add remove enable disable set get print export edit find run debug error info warning"; var LITERALS = "true false yes no nothing nil null"; var OBJECTS = "traffic-flow traffic-generator firewall scheduler aaa accounting address-list address align area bandwidth-server bfd bgp bridge client clock community config connection console customer default dhcp-client dhcp-server discovery dns e-mail ethernet filter firewall firmware gps graphing group hardware health hotspot identity igmp-proxy incoming instance interface ip ipsec ipv6 irq l2tp-server lcd ldp logging mac-server mac-winbox mangle manual mirror mme mpls nat nd neighbor network note ntp ospf ospf-v3 ovpn-server page peer pim ping policy pool port ppp pppoe-client pptp-server prefix profile proposal proxy queue radius resource rip ripng route routing screen script security-profiles server service service-port settings shares smb sms sniffer snmp snooper socks sstp-server system tool tracking type upgrade upnp user-manager users user vlan secret vrrp watchdog web-access wireless pptp pppoe lan wan layer7-protocol lease simple raw"; // print parameters // Several parameters are available for print command: // ToDo: var PARAMETERS_PRINT = 'append as-value brief detail count-only file follow follow-only from interval terse value-list without-paging where info'; // ToDo: var OPERATORS = '&& and ! not || or in ~ ^ & << >> + - * /'; // ToDo: var TYPES = 'num number bool boolean str string ip ip6-prefix id time array'; // ToDo: The following tokens serve as delimiters in the grammar: () [] {} : ; $ / var VAR_PREFIX = "global local set for foreach"; var VAR = { className: "variable", variants: [ { begin: /\$[\w\d#@][\w\d_]*/ }, { begin: /\$\{(.*?)}/ }, ], }; var QUOTE_STRING = { className: "string", begin: /"/, end: /"/, contains: [ hljs.BACKSLASH_ESCAPE, VAR, { className: "variable", begin: /\$\(/, end: /\)/, contains: [hljs.BACKSLASH_ESCAPE], }, ], }; var APOS_STRING = { className: "string", begin: /'/, end: /'/, }; var IPADDR = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\b"; var IPADDR_wBITMASK = IPADDR + "/(3[0-2]|[1-2][0-9]|\\d)"; ////////////////////////////////////////////////////////////////////// return { aliases: ["routeros", "mikrotik"], case_insensitive: true, lexemes: /:?[\w-]+/, keywords: { literal: LITERALS, keyword: STATEMENTS + " :" + STATEMENTS.split(" ").join(" :") + " :" + GLOBAL_COMMANDS.split(" ").join(" :"), }, contains: [ { // недопустимые конструкции variants: [ { begin: /^@/, end: /$/ }, // dns { begin: /\/\*/, end: /\*\// }, // -- comment { begin: /%%/, end: /$/ }, // -- comment { begin: /^'/, end: /$/ }, // Monkey one line comment { begin: /^\s*\/[\w-]+=/, end: /$/ }, // jboss-cli { begin: /\/\//, end: /$/ }, // Stan comment { begin: /^\[\\]$/ }, // F# class declaration? { begin: /<\//, end: />/ }, // HTML tags { begin: /^facet /, end: /\}/ }, // roboconf - лютый костыль ))) { begin: "^1\\.\\.(\\d+)$", end: /$/ }, // tap ], illegal: /./, }, hljs.COMMENT("^#", "$"), QUOTE_STRING, APOS_STRING, VAR, { // attribute=value begin: /[\w-]+\=([^\s\{\}\[\]\(\)]+)/, relevance: 0, returnBegin: true, contains: [ { className: "attribute", begin: /[^=]+/, }, { begin: /=/, endsWithParent: true, relevance: 0, contains: [ QUOTE_STRING, APOS_STRING, VAR, { className: "literal", begin: "\\b(" + LITERALS.split(" ").join("|") + ")\\b", }, /*{ // IPv4 addresses and subnets className: 'number', variants: [ {begin: IPADDR_wBITMASK+'(,'+IPADDR_wBITMASK+')*'}, //192.168.0.0/24,1.2.3.0/24 {begin: IPADDR+'-'+IPADDR}, // 192.168.0.1-192.168.0.3 {begin: IPADDR+'(,'+IPADDR+')*'}, // 192.168.0.1,192.168.0.34,192.168.24.1,192.168.0.1 ] }, // */ /*{ // MAC addresses and DHCP Client IDs className: 'number', begin: /\b(1:)?([0-9A-Fa-f]{1,2}[:-]){5}([0-9A-Fa-f]){1,2}\b/, }, //*/ { // Не форматировать не классифицированные значения. Необходимо для исключения подсветки значений как built_in. // className: 'number', begin: /("[^"]*"|[^\s\{\}\[\]]+)/, }, //*/ ], }, //*/ ], }, //*/ { // HEX values className: "number", begin: /\*[0-9a-fA-F]+/, }, //*/ { begin: "\\b(" + COMMON_COMMANDS.split(" ").join("|") + ")([\\s\[\(]|\])", returnBegin: true, contains: [ { className: "builtin-name", //'function', begin: /\w+/, }, ], }, { className: "built_in", variants: [ { begin: "(\\.\\./|/|\\s)((" + OBJECTS.split(" ").join("|") + ");?\\s)+", relevance: 10, }, { begin: /\.\./ }, ], }, //*/ ], }; }, }, { name: "rsl", /* Language: RenderMan RSL Author: Konstantin Evdokimenko Contributors: Shuen-Huei Guan Category: graphics */ create: function (hljs) { return { keywords: { keyword: "float color point normal vector matrix while for if do return else break extern continue", built_in: "abs acos ambient area asin atan atmosphere attribute calculatenormal ceil cellnoise " + "clamp comp concat cos degrees depth Deriv diffuse distance Du Dv environment exp " + "faceforward filterstep floor format fresnel incident length lightsource log match " + "max min mod noise normalize ntransform opposite option phong pnoise pow printf " + "ptlined radians random reflect refract renderinfo round setcomp setxcomp setycomp " + "setzcomp shadow sign sin smoothstep specular specularbrdf spline sqrt step tan " + "texture textureinfo trace transform vtransform xcomp ycomp zcomp", }, illegal: " Contributors: Peter Leonov , Vasily Polovnyov , Loren Segal , Pascal Hurni , Cedric Sohrauer Category: common */ create: function (hljs) { var RUBY_METHOD_RE = "[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"; var RUBY_KEYWORDS = { keyword: "and then defined module in return redo if BEGIN retry end for self when " + "next until do begin unless END rescue else break undef not super class case " + "require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor", literal: "true false nil", }; var YARDOCTAG = { className: "doctag", begin: "@[A-Za-z]+", }; var IRB_OBJECT = { begin: "#<", end: ">", }; var COMMENT_MODES = [ hljs.COMMENT("#", "$", { contains: [YARDOCTAG], }), hljs.COMMENT("^\\=begin", "^\\=end", { contains: [YARDOCTAG], relevance: 10, }), hljs.COMMENT("^__END__", "\\n$"), ]; var SUBST = { className: "subst", begin: "#\\{", end: "}", keywords: RUBY_KEYWORDS, }; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: /'/, end: /'/ }, { begin: /"/, end: /"/ }, { begin: /`/, end: /`/ }, { begin: "%[qQwWx]?\\(", end: "\\)" }, { begin: "%[qQwWx]?\\[", end: "\\]" }, { begin: "%[qQwWx]?{", end: "}" }, { begin: "%[qQwWx]?<", end: ">" }, { begin: "%[qQwWx]?/", end: "/" }, { begin: "%[qQwWx]?%", end: "%" }, { begin: "%[qQwWx]?-", end: "-" }, { begin: "%[qQwWx]?\\|", end: "\\|" }, { // \B in the beginning suppresses recognition of ?-sequences where ? // is the last character of a preceding identifier, as in: `func?4` begin: /\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/, }, { // heredocs begin: /<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/, returnBegin: true, contains: [ { begin: /<<[-~]?'?/ }, { begin: /\w+/, endSameAsBegin: true, contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, ], }, ], }; var PARAMS = { className: "params", begin: "\\(", end: "\\)", endsParent: true, keywords: RUBY_KEYWORDS, }; var RUBY_DEFAULT_CONTAINS = [ STRING, IRB_OBJECT, { className: "class", beginKeywords: "class module", end: "$|;", illegal: /=/, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: "[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?", }), { begin: "<\\s*", contains: [ { begin: "(" + hljs.IDENT_RE + "::)?" + hljs.IDENT_RE, }, ], }, ].concat(COMMENT_MODES), }, { className: "function", beginKeywords: "def", end: "$|;", contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: RUBY_METHOD_RE }), PARAMS, ].concat(COMMENT_MODES), }, { // swallow namespace qualifiers before symbols begin: hljs.IDENT_RE + "::", }, { className: "symbol", begin: hljs.UNDERSCORE_IDENT_RE + "(\\!|\\?)?:", relevance: 0, }, { className: "symbol", begin: ":(?!\\s)", contains: [STRING, { begin: RUBY_METHOD_RE }], relevance: 0, }, { className: "number", begin: "(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", relevance: 0, }, { begin: "(\\$\\W)|((\\$|\\@\\@?)(\\w+))", // variables }, { className: "params", begin: /\|/, end: /\|/, keywords: RUBY_KEYWORDS, }, { // regexp container begin: "(" + hljs.RE_STARTERS_RE + "|unless)\\s*", keywords: "unless", contains: [ IRB_OBJECT, { className: "regexp", contains: [hljs.BACKSLASH_ESCAPE, SUBST], illegal: /\n/, variants: [ { begin: "/", end: "/[a-z]*" }, { begin: "%r{", end: "}[a-z]*" }, { begin: "%r\\(", end: "\\)[a-z]*" }, { begin: "%r!", end: "![a-z]*" }, { begin: "%r\\[", end: "\\][a-z]*" }, ], }, ].concat(COMMENT_MODES), relevance: 0, }, ].concat(COMMENT_MODES); SUBST.contains = RUBY_DEFAULT_CONTAINS; PARAMS.contains = RUBY_DEFAULT_CONTAINS; var SIMPLE_PROMPT = "[>?]>"; var DEFAULT_PROMPT = "[\\w#]+\\(\\w+\\):\\d+:\\d+>"; var RVM_PROMPT = "(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>"; var IRB_DEFAULT = [ { begin: /^\s*=>/, starts: { end: "$", contains: RUBY_DEFAULT_CONTAINS, }, }, { className: "meta", begin: "^(" + SIMPLE_PROMPT + "|" + DEFAULT_PROMPT + "|" + RVM_PROMPT + ")", starts: { end: "$", contains: RUBY_DEFAULT_CONTAINS, }, }, ]; return { aliases: ["rb", "gemspec", "podspec", "thor", "irb"], keywords: RUBY_KEYWORDS, illegal: /\/\*/, contains: COMMENT_MODES.concat(IRB_DEFAULT).concat( RUBY_DEFAULT_CONTAINS, ), }; }, }, { name: "ruleslanguage", /* Language: Oracle Rules Language Author: Jason Jacobson Description: The Oracle Utilities Rules Language is used to program the Oracle Utilities Applications acquired from LODESTAR Corporation. The products include Billing Component, LPSS, Pricing Component etc. through version 1.6.1. Category: enterprise */ create: function (hljs) { return { keywords: { keyword: "BILL_PERIOD BILL_START BILL_STOP RS_EFFECTIVE_START RS_EFFECTIVE_STOP RS_JURIS_CODE RS_OPCO_CODE " + "INTDADDATTRIBUTE|5 INTDADDVMSG|5 INTDBLOCKOP|5 INTDBLOCKOPNA|5 INTDCLOSE|5 INTDCOUNT|5 " + "INTDCOUNTSTATUSCODE|5 INTDCREATEMASK|5 INTDCREATEDAYMASK|5 INTDCREATEFACTORMASK|5 " + "INTDCREATEHANDLE|5 INTDCREATEOVERRIDEDAYMASK|5 INTDCREATEOVERRIDEMASK|5 " + "INTDCREATESTATUSCODEMASK|5 INTDCREATETOUPERIOD|5 INTDDELETE|5 INTDDIPTEST|5 INTDEXPORT|5 " + "INTDGETERRORCODE|5 INTDGETERRORMESSAGE|5 INTDISEQUAL|5 INTDJOIN|5 INTDLOAD|5 INTDLOADACTUALCUT|5 " + "INTDLOADDATES|5 INTDLOADHIST|5 INTDLOADLIST|5 INTDLOADLISTDATES|5 INTDLOADLISTENERGY|5 " + "INTDLOADLISTHIST|5 INTDLOADRELATEDCHANNEL|5 INTDLOADSP|5 INTDLOADSTAGING|5 INTDLOADUOM|5 " + "INTDLOADUOMDATES|5 INTDLOADUOMHIST|5 INTDLOADVERSION|5 INTDOPEN|5 INTDREADFIRST|5 INTDREADNEXT|5 " + "INTDRECCOUNT|5 INTDRELEASE|5 INTDREPLACE|5 INTDROLLAVG|5 INTDROLLPEAK|5 INTDSCALAROP|5 INTDSCALE|5 " + "INTDSETATTRIBUTE|5 INTDSETDSTPARTICIPANT|5 INTDSETSTRING|5 INTDSETVALUE|5 INTDSETVALUESTATUS|5 " + "INTDSHIFTSTARTTIME|5 INTDSMOOTH|5 INTDSORT|5 INTDSPIKETEST|5 INTDSUBSET|5 INTDTOU|5 " + "INTDTOURELEASE|5 INTDTOUVALUE|5 INTDUPDATESTATS|5 INTDVALUE|5 STDEV INTDDELETEEX|5 " + "INTDLOADEXACTUAL|5 INTDLOADEXCUT|5 INTDLOADEXDATES|5 INTDLOADEX|5 INTDLOADEXRELATEDCHANNEL|5 " + "INTDSAVEEX|5 MVLOAD|5 MVLOADACCT|5 MVLOADACCTDATES|5 MVLOADACCTHIST|5 MVLOADDATES|5 MVLOADHIST|5 " + "MVLOADLIST|5 MVLOADLISTDATES|5 MVLOADLISTHIST|5 IF FOR NEXT DONE SELECT END CALL ABORT CLEAR CHANNEL FACTOR LIST NUMBER " + "OVERRIDE SET WEEK DISTRIBUTIONNODE ELSE WHEN THEN OTHERWISE IENUM CSV INCLUDE LEAVE RIDER SAVE DELETE " + "NOVALUE SECTION WARN SAVE_UPDATE DETERMINANT LABEL REPORT REVENUE EACH " + "IN FROM TOTAL CHARGE BLOCK AND OR CSV_FILE RATE_CODE AUXILIARY_DEMAND " + "UIDACCOUNT RS BILL_PERIOD_SELECT HOURS_PER_MONTH INTD_ERROR_STOP SEASON_SCHEDULE_NAME " + "ACCOUNTFACTOR ARRAYUPPERBOUND CALLSTOREDPROC GETADOCONNECTION GETCONNECT GETDATASOURCE " + "GETQUALIFIER GETUSERID HASVALUE LISTCOUNT LISTOP LISTUPDATE LISTVALUE PRORATEFACTOR RSPRORATE " + "SETBINPATH SETDBMONITOR WQ_OPEN BILLINGHOURS DATE DATEFROMFLOAT DATETIMEFROMSTRING " + "DATETIMETOSTRING DATETOFLOAT DAY DAYDIFF DAYNAME DBDATETIME HOUR MINUTE MONTH MONTHDIFF " + "MONTHHOURS MONTHNAME ROUNDDATE SAMEWEEKDAYLASTYEAR SECOND WEEKDAY WEEKDIFF YEAR YEARDAY " + "YEARSTR COMPSUM HISTCOUNT HISTMAX HISTMIN HISTMINNZ HISTVALUE MAXNRANGE MAXRANGE MINRANGE " + "COMPIKVA COMPKVA COMPKVARFROMKQKW COMPLF IDATTR FLAG LF2KW LF2KWH MAXKW POWERFACTOR " + "READING2USAGE AVGSEASON MAXSEASON MONTHLYMERGE SEASONVALUE SUMSEASON ACCTREADDATES " + "ACCTTABLELOAD CONFIGADD CONFIGGET CREATEOBJECT CREATEREPORT EMAILCLIENT EXPBLKMDMUSAGE " + "EXPMDMUSAGE EXPORT_USAGE FACTORINEFFECT GETUSERSPECIFIEDSTOP INEFFECT ISHOLIDAY RUNRATE " + "SAVE_PROFILE SETREPORTTITLE USEREXIT WATFORRUNRATE TO TABLE ACOS ASIN ATAN ATAN2 BITAND CEIL " + "COS COSECANT COSH COTANGENT DIVQUOT DIVREM EXP FABS FLOOR FMOD FREPM FREXPN LOG LOG10 MAX MAXN " + "MIN MINNZ MODF POW ROUND ROUND2VALUE ROUNDINT SECANT SIN SINH SQROOT TAN TANH FLOAT2STRING " + "FLOAT2STRINGNC INSTR LEFT LEN LTRIM MID RIGHT RTRIM STRING STRINGNC TOLOWER TOUPPER TRIM " + "NUMDAYS READ_DATE STAGING", built_in: "IDENTIFIER OPTIONS XML_ELEMENT XML_OP XML_ELEMENT_OF DOMDOCCREATE DOMDOCLOADFILE DOMDOCLOADXML " + "DOMDOCSAVEFILE DOMDOCGETROOT DOMDOCADDPI DOMNODEGETNAME DOMNODEGETTYPE DOMNODEGETVALUE DOMNODEGETCHILDCT " + "DOMNODEGETFIRSTCHILD DOMNODEGETSIBLING DOMNODECREATECHILDELEMENT DOMNODESETATTRIBUTE " + "DOMNODEGETCHILDELEMENTCT DOMNODEGETFIRSTCHILDELEMENT DOMNODEGETSIBLINGELEMENT DOMNODEGETATTRIBUTECT " + "DOMNODEGETATTRIBUTEI DOMNODEGETATTRIBUTEBYNAME DOMNODEGETBYNAME", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, { className: "literal", variants: [ { begin: "#\\s+[a-zA-Z\\ \\.]*", relevance: 0 }, // looks like #-comment { begin: "#[a-zA-Z\\ \\.]+" }, ], }, ], }; }, }, { name: "rust", /* Language: Rust Author: Andrey Vlasovskikh Contributors: Roman Shmatov , Kasper Andersen Category: system */ create: function (hljs) { var NUM_SUFFIX = "([ui](8|16|32|64|128|size)|f(32|64))\?"; var KEYWORDS = "abstract as async await become box break const continue crate do dyn " + "else enum extern false final fn for if impl in let loop macro match mod " + "move mut override priv pub ref return self Self static struct super " + "trait true try type typeof unsafe unsized use virtual where while yield"; var BUILTINS = // functions "drop " + // types "i8 i16 i32 i64 i128 isize " + "u8 u16 u32 u64 u128 usize " + "f32 f64 " + "str char bool " + "Box Option Result String Vec " + // traits "Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug " + "PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator " + "Extend IntoIterator DoubleEndedIterator ExactSizeIterator " + "SliceConcatExt ToString " + // macros "assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! " + "debug_assert! debug_assert_eq! env! panic! file! format! format_args! " + "include_bin! include_str! line! local_data_key! module_path! " + "option_env! print! println! select! stringify! try! unimplemented! " + "unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"; return { aliases: ["rs"], keywords: { keyword: KEYWORDS, literal: "true false Some None Ok Err", built_in: BUILTINS, }, lexemes: hljs.IDENT_RE + "!?", illegal: "", }, ], }; }, }, { name: "sas", /* Language: SAS Author: Mauricio Caceres Description: Syntax Highlighting for SAS */ create: function (hljs) { // Data step and PROC SQL statements var SAS_KEYWORDS = "" + "do if then else end until while " + "" + "abort array attrib by call cards cards4 catname continue " + "datalines datalines4 delete delim delimiter display dm drop " + "endsas error file filename footnote format goto in infile " + "informat input keep label leave length libname link list " + "lostcard merge missing modify options output out page put " + "redirect remove rename replace retain return select set skip " + "startsas stop title update waitsas where window x systask " + "" + "add and alter as cascade check create delete describe " + "distinct drop foreign from group having index insert into in " + "key like message modify msgtype not null on or order primary " + "references reset restrict select set table unique update " + "validate view where"; // Built-in SAS functions var SAS_FUN = "" + "abs|addr|airy|arcos|arsin|atan|attrc|attrn|band|" + "betainv|blshift|bnot|bor|brshift|bxor|byte|cdf|ceil|" + "cexist|cinv|close|cnonct|collate|compbl|compound|" + "compress|cos|cosh|css|curobs|cv|daccdb|daccdbsl|" + "daccsl|daccsyd|dacctab|dairy|date|datejul|datepart|" + "datetime|day|dclose|depdb|depdbsl|depdbsl|depsl|" + "depsl|depsyd|depsyd|deptab|deptab|dequote|dhms|dif|" + "digamma|dim|dinfo|dnum|dopen|doptname|doptnum|dread|" + "dropnote|dsname|erf|erfc|exist|exp|fappend|fclose|" + "fcol|fdelete|fetch|fetchobs|fexist|fget|fileexist|" + "filename|fileref|finfo|finv|fipname|fipnamel|" + "fipstate|floor|fnonct|fnote|fopen|foptname|foptnum|" + "fpoint|fpos|fput|fread|frewind|frlen|fsep|fuzz|" + "fwrite|gaminv|gamma|getoption|getvarc|getvarn|hbound|" + "hms|hosthelp|hour|ibessel|index|indexc|indexw|input|" + "inputc|inputn|int|intck|intnx|intrr|irr|jbessel|" + "juldate|kurtosis|lag|lbound|left|length|lgamma|" + "libname|libref|log|log10|log2|logpdf|logpmf|logsdf|" + "lowcase|max|mdy|mean|min|minute|mod|month|mopen|" + "mort|n|netpv|nmiss|normal|note|npv|open|ordinal|" + "pathname|pdf|peek|peekc|pmf|point|poisson|poke|" + "probbeta|probbnml|probchi|probf|probgam|probhypr|" + "probit|probnegb|probnorm|probt|put|putc|putn|qtr|" + "quote|ranbin|rancau|ranexp|rangam|range|rank|rannor|" + "ranpoi|rantbl|rantri|ranuni|repeat|resolve|reverse|" + "rewind|right|round|saving|scan|sdf|second|sign|" + "sin|sinh|skewness|soundex|spedis|sqrt|std|stderr|" + "stfips|stname|stnamel|substr|sum|symget|sysget|" + "sysmsg|sysprod|sysrc|system|tan|tanh|time|timepart|" + "tinv|tnonct|today|translate|tranwrd|trigamma|" + "trim|trimn|trunc|uniform|upcase|uss|var|varfmt|" + "varinfmt|varlabel|varlen|varname|varnum|varray|" + "varrayx|vartype|verify|vformat|vformatd|vformatdx|" + "vformatn|vformatnx|vformatw|vformatwx|vformatx|" + "vinarray|vinarrayx|vinformat|vinformatd|vinformatdx|" + "vinformatn|vinformatnx|vinformatw|vinformatwx|" + "vinformatx|vlabel|vlabelx|vlength|vlengthx|vname|" + "vnamex|vtype|vtypex|weekday|year|yyq|zipfips|zipname|" + "zipnamel|zipstate"; // Built-in macro functions var SAS_MACRO_FUN = "bquote|nrbquote|cmpres|qcmpres|compstor|" + "datatyp|display|do|else|end|eval|global|goto|" + "if|index|input|keydef|label|left|length|let|" + "local|lowcase|macro|mend|nrbquote|nrquote|" + "nrstr|put|qcmpres|qleft|qlowcase|qscan|" + "qsubstr|qsysfunc|qtrim|quote|qupcase|scan|str|" + "substr|superq|syscall|sysevalf|sysexec|sysfunc|" + "sysget|syslput|sysprod|sysrc|sysrput|then|to|" + "trim|unquote|until|upcase|verify|while|window"; return { aliases: ["sas", "SAS"], case_insensitive: true, // SAS is case-insensitive keywords: { literal: "null missing _all_ _automatic_ _character_ _infile_ " + "_n_ _name_ _null_ _numeric_ _user_ _webout_", meta: SAS_KEYWORDS, }, contains: [ { // Distinct highlight for proc , data, run, quit className: "keyword", begin: /^\s*(proc [\w\d_]+|data|run|quit)[\s\;]/, }, { // Macro variables className: "variable", begin: /\&[a-zA-Z_\&][a-zA-Z0-9_]*\.?/, }, { // Special emphasis for datalines|cards className: "emphasis", begin: /^\s*datalines|cards.*;/, end: /^\s*;\s*$/, }, { // Built-in macro variables take precedence className: "built_in", begin: "%(" + SAS_MACRO_FUN + ")", }, { // User-defined macro functions highlighted after className: "name", begin: /%[a-zA-Z_][a-zA-Z_0-9]*/, }, { className: "meta", begin: "[^%](" + SAS_FUN + ")[\(]", }, { className: "string", variants: [hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE], }, hljs.COMMENT("\\*", ";"), hljs.C_BLOCK_COMMENT_MODE, ], }; }, }, { name: "scala", /* Language: Scala Category: functional Author: Jan Berkel Contributors: Erik Osheim */ create: function (hljs) { var ANNOTATION = { className: "meta", begin: "@[A-Za-z]+" }; // used in strings for escaping/interpolation/substitution var SUBST = { className: "subst", variants: [ { begin: "\\$[A-Za-z0-9_]+" }, { begin: "\\${", end: "}" }, ], }; var STRING = { className: "string", variants: [ { begin: '"', end: '"', illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE], }, { begin: '"""', end: '"""', relevance: 10, }, { begin: '[a-z]+"', end: '"', illegal: "\\n", contains: [hljs.BACKSLASH_ESCAPE, SUBST], }, { className: "string", begin: '[a-z]+"""', end: '"""', contains: [SUBST], relevance: 10, }, ], }; var SYMBOL = { className: "symbol", begin: "'\\w[\\w\\d_]*(?!')", }; var TYPE = { className: "type", begin: "\\b[A-Z][A-Za-z0-9_]*", relevance: 0, }; var NAME = { className: "title", begin: /[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/, relevance: 0, }; var CLASS = { className: "class", beginKeywords: "class object trait type", end: /[:={\[\n;]/, excludeEnd: true, contains: [ { beginKeywords: "extends with", relevance: 10, }, { begin: /\[/, end: /\]/, excludeBegin: true, excludeEnd: true, relevance: 0, contains: [TYPE], }, { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, relevance: 0, contains: [TYPE], }, NAME, ], }; var METHOD = { className: "function", beginKeywords: "def", end: /[:={\[(\n;]/, excludeEnd: true, contains: [NAME], }; return { keywords: { literal: "true false null", keyword: "type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, STRING, SYMBOL, TYPE, METHOD, CLASS, hljs.C_NUMBER_MODE, ANNOTATION, ], }; }, }, { name: "scheme", /* Language: Scheme Description: Keywords based on http://community.schemewiki.org/?scheme-keywords Author: JP Verkamp Contributors: Ivan Sagalaev Origin: clojure.js Category: lisp */ create: function (hljs) { var SCHEME_IDENT_RE = "[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+"; var SCHEME_SIMPLE_NUMBER_RE = "(\\-|\\+)?\\d+([./]\\d+)?"; var SCHEME_COMPLEX_NUMBER_RE = SCHEME_SIMPLE_NUMBER_RE + "[+\\-]" + SCHEME_SIMPLE_NUMBER_RE + "i"; var BUILTINS = { "builtin-name": "case-lambda call/cc class define-class exit-handler field import " + "inherit init-field interface let*-values let-values let/ec mixin " + "opt-lambda override protect provide public rename require " + "require-for-syntax syntax syntax-case syntax-error unit/sig unless " + "when with-syntax and begin call-with-current-continuation " + "call-with-input-file call-with-output-file case cond define " + "define-syntax delay do dynamic-wind else for-each if lambda let let* " + "let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / " + "; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan " + "boolean? caar cadr call-with-input-file call-with-output-file " + "call-with-values car cdddar cddddr cdr ceiling char->integer " + "char-alphabetic? char-ci<=? char-ci=? char-ci>? " + "char-downcase char-lower-case? char-numeric? char-ready? char-upcase " + "char-upper-case? char-whitespace? char<=? char=? char>? " + "char? close-input-port close-output-port complex? cons cos " + "current-input-port current-output-port denominator display eof-object? " + "eq? equal? eqv? eval even? exact->inexact exact? exp expt floor " + "force gcd imag-part inexact->exact inexact? input-port? integer->char " + "integer? interaction-environment lcm length list list->string " + "list->vector list-ref list-tail list? load log magnitude make-polar " + "make-rectangular make-string make-vector max member memq memv min " + "modulo negative? newline not null-environment null? number->string " + "number? numerator odd? open-input-file open-output-file output-port? " + "pair? peek-char port? positive? procedure? quasiquote quote quotient " + "rational? rationalize read read-char real-part real? remainder reverse " + "round scheme-report-environment set! set-car! set-cdr! sin sqrt string " + "string->list string->number string->symbol string-append string-ci<=? " + "string-ci=? string-ci>? string-copy " + "string-fill! string-length string-ref string-set! string<=? string=? string>? string? substring symbol->string symbol? " + "tan transcript-off transcript-on truncate values vector " + "vector->list vector-fill! vector-length vector-ref vector-set! " + "with-input-from-file with-output-to-file write write-char zero?", }; var SHEBANG = { className: "meta", begin: "^#!", end: "$", }; var LITERAL = { className: "literal", begin: "(#t|#f|#\\\\" + SCHEME_IDENT_RE + "|#\\\\.)", }; var NUMBER = { className: "number", variants: [ { begin: SCHEME_SIMPLE_NUMBER_RE, relevance: 0 }, { begin: SCHEME_COMPLEX_NUMBER_RE, relevance: 0 }, { begin: "#b[0-1]+(/[0-1]+)?" }, { begin: "#o[0-7]+(/[0-7]+)?" }, { begin: "#x[0-9a-f]+(/[0-9a-f]+)?" }, ], }; var STRING = hljs.QUOTE_STRING_MODE; var REGULAR_EXPRESSION = { className: "regexp", begin: '#[pr]x"', end: '[^\\\\]"', }; var COMMENT_MODES = [ hljs.COMMENT(";", "$", { relevance: 0, }), hljs.COMMENT("#\\|", "\\|#"), ]; var IDENT = { begin: SCHEME_IDENT_RE, relevance: 0, }; var QUOTED_IDENT = { className: "symbol", begin: "'" + SCHEME_IDENT_RE, }; var BODY = { endsWithParent: true, relevance: 0, }; var QUOTED_LIST = { variants: [{ begin: /'/ }, { begin: "`" }], contains: [ { begin: "\\(", end: "\\)", contains: [ "self", LITERAL, STRING, NUMBER, IDENT, QUOTED_IDENT, ], }, ], }; var NAME = { className: "name", begin: SCHEME_IDENT_RE, lexemes: SCHEME_IDENT_RE, keywords: BUILTINS, }; var LAMBDA = { begin: /lambda/, endsWithParent: true, returnBegin: true, contains: [ NAME, { begin: /\(/, end: /\)/, endsParent: true, contains: [IDENT], }, ], }; var LIST = { variants: [ { begin: "\\(", end: "\\)" }, { begin: "\\[", end: "\\]" }, ], contains: [LAMBDA, NAME, BODY], }; BODY.contains = [ LITERAL, NUMBER, STRING, IDENT, QUOTED_IDENT, QUOTED_LIST, LIST, ].concat(COMMENT_MODES); return { illegal: /\S/, contains: [ SHEBANG, NUMBER, STRING, QUOTED_IDENT, QUOTED_LIST, LIST, ].concat(COMMENT_MODES), }; }, }, { name: "scilab", /* Language: Scilab Author: Sylvestre Ledru Origin: matlab.js Description: Scilab is a port from Matlab Category: scientific */ create: function (hljs) { var COMMON_CONTAINS = [ hljs.C_NUMBER_MODE, { className: "string", begin: "'|\"", end: "'|\"", contains: [hljs.BACKSLASH_ESCAPE, { begin: "''" }], }, ]; return { aliases: ["sci"], lexemes: /%?\w+/, keywords: { keyword: "abort break case clear catch continue do elseif else endfunction end for function " + "global if pause return resume select try then while", literal: "%f %F %t %T %pi %eps %inf %nan %e %i %z %s", // Scilab has more than 2000 functions. Just list the most commons built_in: "abs and acos asin atan ceil cd chdir clearglobal cosh cos cumprod deff disp error " + "exec execstr exists exp eye gettext floor fprintf fread fsolve imag isdef isempty " + "isinfisnan isvector lasterror length load linspace list listfiles log10 log2 log " + "max min msprintf mclose mopen ones or pathconvert poly printf prod pwd rand real " + "round sinh sin size gsort sprintf sqrt strcat strcmps tring sum system tanh tan " + "type typename warning zeros matrix", }, illegal: '("|#|/\\*|\\s+/\\w+)', contains: [ { className: "function", beginKeywords: "function", end: "$", contains: [ hljs.UNDERSCORE_TITLE_MODE, { className: "params", begin: "\\(", end: "\\)", }, ], }, { begin: "[a-zA-Z_][a-zA-Z_0-9]*('+[\\.']*|[\\.']+)", end: "", relevance: 0, }, { begin: "\\[", end: "\\]'*[\\.']*", relevance: 0, contains: COMMON_CONTAINS, }, hljs.COMMENT("//", "$"), ].concat(COMMON_CONTAINS), }; }, }, { name: "scss", /* Language: SCSS Author: Kurt Emch Category: css */ create: function (hljs) { var IDENT_RE = "[a-zA-Z-][a-zA-Z0-9_-]*"; var VARIABLE = { className: "variable", begin: "(\\$" + IDENT_RE + ")\\b", }; var HEXCOLOR = { className: "number", begin: "#[0-9A-Fa-f]+", }; var DEF_INTERNALS = { className: "attribute", begin: "[A-Z\\_\\.\\-]+", end: ":", excludeEnd: true, illegal: "[^\\s]", starts: { endsWithParent: true, excludeEnd: true, contains: [ HEXCOLOR, hljs.CSS_NUMBER_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "meta", begin: "!important", }, ], }, }; return { case_insensitive: true, illegal: "[=/|']", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "selector-id", begin: "\\#[A-Za-z0-9_-]+", relevance: 0, }, { className: "selector-class", begin: "\\.[A-Za-z0-9_-]+", relevance: 0, }, { className: "selector-attr", begin: "\\[", end: "\\]", illegal: "$", }, { className: "selector-tag", // begin: IDENT_RE, end: '[,|\\s]' begin: "\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b", relevance: 0, }, { begin: ":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)", }, { begin: "::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)", }, VARIABLE, { className: "attribute", begin: "\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b", illegal: "[^\\s]", }, { begin: "\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b", }, { begin: ":", end: ";", contains: [ VARIABLE, HEXCOLOR, hljs.CSS_NUMBER_MODE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, { className: "meta", begin: "!important", }, ], }, { begin: "@", end: "[{;]", keywords: "mixin include extend for if else each while charset import debug media page content font-face namespace warn", contains: [ VARIABLE, hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, HEXCOLOR, hljs.CSS_NUMBER_MODE, { begin: "\\s[A-Za-z0-9_.-]+", relevance: 0, }, ], }, ], }; }, }, { name: "shell", /* Language: Shell Session Requires: bash.js Author: TSUYUSATO Kitsune Category: common */ create: function (hljs) { return { aliases: ["console"], contains: [ { className: "meta", begin: "^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]", starts: { end: "$", subLanguage: "bash", }, }, ], }; }, }, { name: "smali", /* Language: Smali Author: Dennis Titze Description: Basic Smali highlighting */ create: function (hljs) { var smali_instr_low_prio = [ "add", "and", "cmp", "cmpg", "cmpl", "const", "div", "double", "float", "goto", "if", "int", "long", "move", "mul", "neg", "new", "nop", "not", "or", "rem", "return", "shl", "shr", "sput", "sub", "throw", "ushr", "xor", ]; var smali_instr_high_prio = [ "aget", "aput", "array", "check", "execute", "fill", "filled", "goto/16", "goto/32", "iget", "instance", "invoke", "iput", "monitor", "packed", "sget", "sparse", ]; var smali_keywords = [ "transient", "constructor", "abstract", "final", "synthetic", "public", "private", "protected", "static", "bridge", "system", ]; return { aliases: ["smali"], contains: [ { className: "string", begin: '"', end: '"', relevance: 0, }, hljs.COMMENT("#", "$", { relevance: 0, }), { className: "keyword", variants: [ { begin: "\\s*\\.end\\s[a-zA-Z0-9]*" }, { begin: "^[ ]*\\.[a-zA-Z]*", relevance: 0 }, { begin: "\\s:[a-zA-Z_0-9]*", relevance: 0 }, { begin: "\\s(" + smali_keywords.join("|") + ")" }, ], }, { className: "built_in", variants: [ { begin: "\\s(" + smali_instr_low_prio.join("|") + ")\\s", }, { begin: "\\s(" + smali_instr_low_prio.join("|") + ")((\\-|/)[a-zA-Z0-9]+)+\\s", relevance: 10, }, { begin: "\\s(" + smali_instr_high_prio.join("|") + ")((\\-|/)[a-zA-Z0-9]+)*\\s", relevance: 10, }, ], }, { className: "class", begin: "L[^\(;:\n]*;", relevance: 0, }, { begin: "[vp][0-9]+", }, ], }; }, }, { name: "smalltalk", /* Language: Smalltalk Author: Vladimir Gubarkov */ create: function (hljs) { var VAR_IDENT_RE = "[a-z][a-zA-Z0-9_]*"; var CHAR = { className: "string", begin: "\\$.{1}", }; var SYMBOL = { className: "symbol", begin: "#" + hljs.UNDERSCORE_IDENT_RE, }; return { aliases: ["st"], keywords: "self super nil true false thisContext", // only 6 contains: [ hljs.COMMENT('"', '"'), hljs.APOS_STRING_MODE, { className: "type", begin: "\\b[A-Z][A-Za-z0-9_]*", relevance: 0, }, { begin: VAR_IDENT_RE + ":", relevance: 0, }, hljs.C_NUMBER_MODE, SYMBOL, CHAR, { // This looks more complicated than needed to avoid combinatorial // explosion under V8. It effectively means `| var1 var2 ... |` with // whitespace adjacent to `|` being optional. begin: "\\|[ ]*" + VAR_IDENT_RE + "([ ]+" + VAR_IDENT_RE + ")*[ ]*\\|", returnBegin: true, end: /\|/, illegal: /\S/, contains: [{ begin: "(\\|[ ]*)?" + VAR_IDENT_RE }], }, { begin: "\\#\\(", end: "\\)", contains: [ hljs.APOS_STRING_MODE, CHAR, hljs.C_NUMBER_MODE, SYMBOL, ], }, ], }; }, }, { name: "sml", /* Language: SML Author: Edwin Dalorzo Description: SML language definition. Origin: ocaml.js Category: functional */ create: function (hljs) { return { aliases: ["ml"], keywords: { keyword: /* according to Definition of Standard ML 97 */ "abstype and andalso as case datatype do else end eqtype " + "exception fn fun functor handle if in include infix infixr " + "let local nonfix of op open orelse raise rec sharing sig " + "signature struct structure then type val with withtype where while", built_in: /* built-in types according to basis library */ "array bool char exn int list option order real ref string substring vector unit word", literal: "true false NONE SOME LESS EQUAL GREATER nil", }, illegal: /\/\/|>>/, lexemes: "[a-z_]\\w*!?", contains: [ { className: "literal", begin: /\[(\|\|)?\]|\(\)/, relevance: 0, }, hljs.COMMENT("\\(\\*", "\\*\\)", { contains: ["self"], }), { /* type variable */ className: "symbol", begin: "'[A-Za-z_](?!')[\\w']*", /* the grammar is ambiguous on how 'a'b should be interpreted but not the compiler */ }, { /* polymorphic variant */ className: "type", begin: "`[A-Z][\\w']*", }, { /* module or constructor */ className: "type", begin: "\\b[A-Z][\\w']*", relevance: 0, }, { /* don't color identifiers, but safely catch all identifiers with '*/ begin: "[a-z_]\\w*'[\\w']*", }, hljs.inherit(hljs.APOS_STRING_MODE, { className: "string", relevance: 0, }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), { className: "number", begin: "\\b(0[xX][a-fA-F0-9_]+[Lln]?|" + "0[oO][0-7_]+[Lln]?|" + "0[bB][01_]+[Lln]?|" + "[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)", relevance: 0, }, { begin: /[-=]>/, // relevance booster }, ], }; }, }, { name: "sqf", /* Language: SQF Author: Søren Enevoldsen Contributors: Marvin Saignat , Dedmen Miller Description: Scripting language for the Arma game series Requires: cpp.js */ create: function (hljs) { var CPP = hljs.getLanguage("cpp").exports; // In SQF, a variable start with _ var VARIABLE = { className: "variable", begin: /\b_+[a-zA-Z_]\w*/, }; // In SQF, a function should fit myTag_fnc_myFunction pattern // https://community.bistudio.com/wiki/Functions_Library_(Arma_3)#Adding_a_Function var FUNCTION = { className: "title", begin: /[a-zA-Z][a-zA-Z0-9]+_fnc_\w*/, }; // In SQF strings, quotes matching the start are escaped by adding a consecutive. // Example of single escaped quotes: " "" " and ' '' '. var STRINGS = { className: "string", variants: [ { begin: '"', end: '"', contains: [{ begin: '""', relevance: 0 }], }, { begin: "'", end: "'", contains: [{ begin: "''", relevance: 0 }], }, ], }; return { aliases: ["sqf"], case_insensitive: true, keywords: { keyword: "case catch default do else exit exitWith for forEach from if " + "private switch then throw to try waitUntil while with", built_in: "abs accTime acos action actionIDs actionKeys actionKeysImages actionKeysNames " + "actionKeysNamesArray actionName actionParams activateAddons activatedAddons activateKey " + "add3DENConnection add3DENEventHandler add3DENLayer addAction addBackpack addBackpackCargo " + "addBackpackCargoGlobal addBackpackGlobal addCamShake addCuratorAddons addCuratorCameraArea " + "addCuratorEditableObjects addCuratorEditingArea addCuratorPoints addEditorObject addEventHandler " + "addForce addGoggles addGroupIcon addHandgunItem addHeadgear addItem addItemCargo " + "addItemCargoGlobal addItemPool addItemToBackpack addItemToUniform addItemToVest addLiveStats " + "addMagazine addMagazineAmmoCargo addMagazineCargo addMagazineCargoGlobal addMagazineGlobal " + "addMagazinePool addMagazines addMagazineTurret addMenu addMenuItem addMissionEventHandler " + "addMPEventHandler addMusicEventHandler addOwnedMine addPlayerScores addPrimaryWeaponItem " + "addPublicVariableEventHandler addRating addResources addScore addScoreSide addSecondaryWeaponItem " + "addSwitchableUnit addTeamMember addToRemainsCollector addTorque addUniform addVehicle addVest " + "addWaypoint addWeapon addWeaponCargo addWeaponCargoGlobal addWeaponGlobal addWeaponItem " + "addWeaponPool addWeaponTurret admin agent agents AGLToASL aimedAtTarget aimPos airDensityRTD " + "airplaneThrottle airportSide AISFinishHeal alive all3DENEntities allAirports allControls " + "allCurators allCutLayers allDead allDeadMen allDisplays allGroups allMapMarkers allMines " + "allMissionObjects allow3DMode allowCrewInImmobile allowCuratorLogicIgnoreAreas allowDamage " + "allowDammage allowFileOperations allowFleeing allowGetIn allowSprint allPlayers allSimpleObjects " + "allSites allTurrets allUnits allUnitsUAV allVariables ammo ammoOnPylon and animate animateBay " + "animateDoor animatePylon animateSource animationNames animationPhase animationSourcePhase " + "animationState append apply armoryPoints arrayIntersect asin ASLToAGL ASLToATL assert " + "assignAsCargo assignAsCargoIndex assignAsCommander assignAsDriver assignAsGunner assignAsTurret " + "assignCurator assignedCargo assignedCommander assignedDriver assignedGunner assignedItems " + "assignedTarget assignedTeam assignedVehicle assignedVehicleRole assignItem assignTeam " + "assignToAirport atan atan2 atg ATLToASL attachedObject attachedObjects attachedTo attachObject " + "attachTo attackEnabled backpack backpackCargo backpackContainer backpackItems backpackMagazines " + "backpackSpaceFor behaviour benchmark binocular boundingBox boundingBoxReal boundingCenter " + "breakOut breakTo briefingName buildingExit buildingPos buttonAction buttonSetAction cadetMode " + "call callExtension camCommand camCommit camCommitPrepared camCommitted camConstuctionSetParams " + "camCreate camDestroy cameraEffect cameraEffectEnableHUD cameraInterest cameraOn cameraView " + "campaignConfigFile camPreload camPreloaded camPrepareBank camPrepareDir camPrepareDive " + "camPrepareFocus camPrepareFov camPrepareFovRange camPreparePos camPrepareRelPos camPrepareTarget " + "camSetBank camSetDir camSetDive camSetFocus camSetFov camSetFovRange camSetPos camSetRelPos " + "camSetTarget camTarget camUseNVG canAdd canAddItemToBackpack canAddItemToUniform canAddItemToVest " + "cancelSimpleTaskDestination canFire canMove canSlingLoad canStand canSuspend " + "canTriggerDynamicSimulation canUnloadInCombat canVehicleCargo captive captiveNum cbChecked " + "cbSetChecked ceil channelEnabled cheatsEnabled checkAIFeature checkVisibility className " + "clearAllItemsFromBackpack clearBackpackCargo clearBackpackCargoGlobal clearGroupIcons " + "clearItemCargo clearItemCargoGlobal clearItemPool clearMagazineCargo clearMagazineCargoGlobal " + "clearMagazinePool clearOverlay clearRadio clearWeaponCargo clearWeaponCargoGlobal clearWeaponPool " + "clientOwner closeDialog closeDisplay closeOverlay collapseObjectTree collect3DENHistory " + "collectiveRTD combatMode commandArtilleryFire commandChat commander commandFire commandFollow " + "commandFSM commandGetOut commandingMenu commandMove commandRadio commandStop " + "commandSuppressiveFire commandTarget commandWatch comment commitOverlay compile compileFinal " + "completedFSM composeText configClasses configFile configHierarchy configName configProperties " + "configSourceAddonList configSourceMod configSourceModList confirmSensorTarget " + "connectTerminalToUAV controlsGroupCtrl copyFromClipboard copyToClipboard copyWaypoints cos count " + "countEnemy countFriendly countSide countType countUnknown create3DENComposition create3DENEntity " + "createAgent createCenter createDialog createDiaryLink createDiaryRecord createDiarySubject " + "createDisplay createGearDialog createGroup createGuardedPoint createLocation createMarker " + "createMarkerLocal createMenu createMine createMissionDisplay createMPCampaignDisplay " + "createSimpleObject createSimpleTask createSite createSoundSource createTask createTeam " + "createTrigger createUnit createVehicle createVehicleCrew createVehicleLocal crew ctAddHeader " + "ctAddRow ctClear ctCurSel ctData ctFindHeaderRows ctFindRowHeader ctHeaderControls ctHeaderCount " + "ctRemoveHeaders ctRemoveRows ctrlActivate ctrlAddEventHandler ctrlAngle ctrlAutoScrollDelay " + "ctrlAutoScrollRewind ctrlAutoScrollSpeed ctrlChecked ctrlClassName ctrlCommit ctrlCommitted " + "ctrlCreate ctrlDelete ctrlEnable ctrlEnabled ctrlFade ctrlHTMLLoaded ctrlIDC ctrlIDD " + "ctrlMapAnimAdd ctrlMapAnimClear ctrlMapAnimCommit ctrlMapAnimDone ctrlMapCursor ctrlMapMouseOver " + "ctrlMapScale ctrlMapScreenToWorld ctrlMapWorldToScreen ctrlModel ctrlModelDirAndUp ctrlModelScale " + "ctrlParent ctrlParentControlsGroup ctrlPosition ctrlRemoveAllEventHandlers ctrlRemoveEventHandler " + "ctrlScale ctrlSetActiveColor ctrlSetAngle ctrlSetAutoScrollDelay ctrlSetAutoScrollRewind " + "ctrlSetAutoScrollSpeed ctrlSetBackgroundColor ctrlSetChecked ctrlSetEventHandler ctrlSetFade " + "ctrlSetFocus ctrlSetFont ctrlSetFontH1 ctrlSetFontH1B ctrlSetFontH2 ctrlSetFontH2B ctrlSetFontH3 " + "ctrlSetFontH3B ctrlSetFontH4 ctrlSetFontH4B ctrlSetFontH5 ctrlSetFontH5B ctrlSetFontH6 " + "ctrlSetFontH6B ctrlSetFontHeight ctrlSetFontHeightH1 ctrlSetFontHeightH2 ctrlSetFontHeightH3 " + "ctrlSetFontHeightH4 ctrlSetFontHeightH5 ctrlSetFontHeightH6 ctrlSetFontHeightSecondary " + "ctrlSetFontP ctrlSetFontPB ctrlSetFontSecondary ctrlSetForegroundColor ctrlSetModel " + "ctrlSetModelDirAndUp ctrlSetModelScale ctrlSetPixelPrecision ctrlSetPosition ctrlSetScale " + "ctrlSetStructuredText ctrlSetText ctrlSetTextColor ctrlSetTooltip ctrlSetTooltipColorBox " + "ctrlSetTooltipColorShade ctrlSetTooltipColorText ctrlShow ctrlShown ctrlText ctrlTextHeight " + "ctrlTextWidth ctrlType ctrlVisible ctRowControls ctRowCount ctSetCurSel ctSetData " + "ctSetHeaderTemplate ctSetRowTemplate ctSetValue ctValue curatorAddons curatorCamera " + "curatorCameraArea curatorCameraAreaCeiling curatorCoef curatorEditableObjects curatorEditingArea " + "curatorEditingAreaType curatorMouseOver curatorPoints curatorRegisteredObjects curatorSelected " + "curatorWaypointCost current3DENOperation currentChannel currentCommand currentMagazine " + "currentMagazineDetail currentMagazineDetailTurret currentMagazineTurret currentMuzzle " + "currentNamespace currentTask currentTasks currentThrowable currentVisionMode currentWaypoint " + "currentWeapon currentWeaponMode currentWeaponTurret currentZeroing cursorObject cursorTarget " + "customChat customRadio cutFadeOut cutObj cutRsc cutText damage date dateToNumber daytime " + "deActivateKey debriefingText debugFSM debugLog deg delete3DENEntities deleteAt deleteCenter " + "deleteCollection deleteEditorObject deleteGroup deleteGroupWhenEmpty deleteIdentity " + "deleteLocation deleteMarker deleteMarkerLocal deleteRange deleteResources deleteSite deleteStatus " + "deleteTeam deleteVehicle deleteVehicleCrew deleteWaypoint detach detectedMines " + "diag_activeMissionFSMs diag_activeScripts diag_activeSQFScripts diag_activeSQSScripts " + "diag_captureFrame diag_captureFrameToFile diag_captureSlowFrame diag_codePerformance " + "diag_drawMode diag_enable diag_enabled diag_fps diag_fpsMin diag_frameNo diag_lightNewLoad " + "diag_list diag_log diag_logSlowFrame diag_mergeConfigFile diag_recordTurretLimits " + "diag_setLightNew diag_tickTime diag_toggle dialog diarySubjectExists didJIP didJIPOwner " + "difficulty difficultyEnabled difficultyEnabledRTD difficultyOption direction directSay disableAI " + "disableCollisionWith disableConversation disableDebriefingStats disableMapIndicators " + "disableNVGEquipment disableRemoteSensors disableSerialization disableTIEquipment " + "disableUAVConnectability disableUserInput displayAddEventHandler displayCtrl displayParent " + "displayRemoveAllEventHandlers displayRemoveEventHandler displaySetEventHandler dissolveTeam " + "distance distance2D distanceSqr distributionRegion do3DENAction doArtilleryFire doFire doFollow " + "doFSM doGetOut doMove doorPhase doStop doSuppressiveFire doTarget doWatch drawArrow drawEllipse " + "drawIcon drawIcon3D drawLine drawLine3D drawLink drawLocation drawPolygon drawRectangle " + "drawTriangle driver drop dynamicSimulationDistance dynamicSimulationDistanceCoef " + "dynamicSimulationEnabled dynamicSimulationSystemEnabled echo edit3DENMissionAttributes editObject " + "editorSetEventHandler effectiveCommander emptyPositions enableAI enableAIFeature " + "enableAimPrecision enableAttack enableAudioFeature enableAutoStartUpRTD enableAutoTrimRTD " + "enableCamShake enableCaustics enableChannel enableCollisionWith enableCopilot " + "enableDebriefingStats enableDiagLegend enableDynamicSimulation enableDynamicSimulationSystem " + "enableEndDialog enableEngineArtillery enableEnvironment enableFatigue enableGunLights " + "enableInfoPanelComponent enableIRLasers enableMimics enablePersonTurret enableRadio enableReload " + "enableRopeAttach enableSatNormalOnDetail enableSaving enableSentences enableSimulation " + "enableSimulationGlobal enableStamina enableTeamSwitch enableTraffic enableUAVConnectability " + "enableUAVWaypoints enableVehicleCargo enableVehicleSensor enableWeaponDisassembly " + "endLoadingScreen endMission engineOn enginesIsOnRTD enginesRpmRTD enginesTorqueRTD entities " + "environmentEnabled estimatedEndServerTime estimatedTimeLeft evalObjectArgument everyBackpack " + "everyContainer exec execEditorScript execFSM execVM exp expectedDestination exportJIPMessages " + "eyeDirection eyePos face faction fadeMusic fadeRadio fadeSound fadeSpeech failMission " + "fillWeaponsFromPool find findCover findDisplay findEditorObject findEmptyPosition " + "findEmptyPositionReady findIf findNearestEnemy finishMissionInit finite fire fireAtTarget " + "firstBackpack flag flagAnimationPhase flagOwner flagSide flagTexture fleeing floor flyInHeight " + "flyInHeightASL fog fogForecast fogParams forceAddUniform forcedMap forceEnd forceFlagTexture " + "forceFollowRoad forceMap forceRespawn forceSpeed forceWalk forceWeaponFire forceWeatherChange " + "forEachMember forEachMemberAgent forEachMemberTeam forgetTarget format formation " + "formationDirection formationLeader formationMembers formationPosition formationTask formatText " + "formLeader freeLook fromEditor fuel fullCrew gearIDCAmmoCount gearSlotAmmoCount gearSlotData " + "get3DENActionState get3DENAttribute get3DENCamera get3DENConnections get3DENEntity " + "get3DENEntityID get3DENGrid get3DENIconsVisible get3DENLayerEntities get3DENLinesVisible " + "get3DENMissionAttribute get3DENMouseOver get3DENSelected getAimingCoef getAllEnvSoundControllers " + "getAllHitPointsDamage getAllOwnedMines getAllSoundControllers getAmmoCargo getAnimAimPrecision " + "getAnimSpeedCoef getArray getArtilleryAmmo getArtilleryComputerSettings getArtilleryETA " + "getAssignedCuratorLogic getAssignedCuratorUnit getBackpackCargo getBleedingRemaining " + "getBurningValue getCameraViewDirection getCargoIndex getCenterOfMass getClientState " + "getClientStateNumber getCompatiblePylonMagazines getConnectedUAV getContainerMaxLoad " + "getCursorObjectParams getCustomAimCoef getDammage getDescription getDir getDirVisual " + "getDLCAssetsUsage getDLCAssetsUsageByName getDLCs getEditorCamera getEditorMode " + "getEditorObjectScope getElevationOffset getEnvSoundController getFatigue getForcedFlagTexture " + "getFriend getFSMVariable getFuelCargo getGroupIcon getGroupIconParams getGroupIcons getHideFrom " + "getHit getHitIndex getHitPointDamage getItemCargo getMagazineCargo getMarkerColor getMarkerPos " + "getMarkerSize getMarkerType getMass getMissionConfig getMissionConfigValue getMissionDLCs " + "getMissionLayerEntities getModelInfo getMousePosition getMusicPlayedTime getNumber " + "getObjectArgument getObjectChildren getObjectDLC getObjectMaterials getObjectProxy " + "getObjectTextures getObjectType getObjectViewDistance getOxygenRemaining getPersonUsedDLCs " + "getPilotCameraDirection getPilotCameraPosition getPilotCameraRotation getPilotCameraTarget " + "getPlateNumber getPlayerChannel getPlayerScores getPlayerUID getPos getPosASL getPosASLVisual " + "getPosASLW getPosATL getPosATLVisual getPosVisual getPosWorld getPylonMagazines getRelDir " + "getRelPos getRemoteSensorsDisabled getRepairCargo getResolution getShadowDistance getShotParents " + "getSlingLoad getSoundController getSoundControllerResult getSpeed getStamina getStatValue " + "getSuppression getTerrainGrid getTerrainHeightASL getText getTotalDLCUsageTime getUnitLoadout " + "getUnitTrait getUserMFDText getUserMFDvalue getVariable getVehicleCargo getWeaponCargo " + "getWeaponSway getWingsOrientationRTD getWingsPositionRTD getWPPos glanceAt globalChat globalRadio " + "goggles goto group groupChat groupFromNetId groupIconSelectable groupIconsVisible groupId " + "groupOwner groupRadio groupSelectedUnits groupSelectUnit gunner gusts halt handgunItems " + "handgunMagazine handgunWeapon handsHit hasInterface hasPilotCamera hasWeapon hcAllGroups " + "hcGroupParams hcLeader hcRemoveAllGroups hcRemoveGroup hcSelected hcSelectGroup hcSetGroup " + "hcShowBar hcShownBar headgear hideBody hideObject hideObjectGlobal hideSelection hint hintC " + "hintCadet hintSilent hmd hostMission htmlLoad HUDMovementLevels humidity image importAllGroups " + "importance in inArea inAreaArray incapacitatedState inflame inflamed infoPanel " + "infoPanelComponentEnabled infoPanelComponents infoPanels inGameUISetEventHandler inheritsFrom " + "initAmbientLife inPolygon inputAction inRangeOfArtillery insertEditorObject intersect is3DEN " + "is3DENMultiplayer isAbleToBreathe isAgent isArray isAutoHoverOn isAutonomous isAutotest " + "isBleeding isBurning isClass isCollisionLightOn isCopilotEnabled isDamageAllowed isDedicated " + "isDLCAvailable isEngineOn isEqualTo isEqualType isEqualTypeAll isEqualTypeAny isEqualTypeArray " + "isEqualTypeParams isFilePatchingEnabled isFlashlightOn isFlatEmpty isForcedWalk isFormationLeader " + "isGroupDeletedWhenEmpty isHidden isInRemainsCollector isInstructorFigureEnabled isIRLaserOn " + "isKeyActive isKindOf isLaserOn isLightOn isLocalized isManualFire isMarkedForCollection " + "isMultiplayer isMultiplayerSolo isNil isNull isNumber isObjectHidden isObjectRTD isOnRoad " + "isPipEnabled isPlayer isRealTime isRemoteExecuted isRemoteExecutedJIP isServer isShowing3DIcons " + "isSimpleObject isSprintAllowed isStaminaEnabled isSteamMission isStreamFriendlyUIEnabled isText " + "isTouchingGround isTurnedOut isTutHintsEnabled isUAVConnectable isUAVConnected isUIContext " + "isUniformAllowed isVehicleCargo isVehicleRadarOn isVehicleSensorEnabled isWalking " + "isWeaponDeployed isWeaponRested itemCargo items itemsWithMagazines join joinAs joinAsSilent " + "joinSilent joinString kbAddDatabase kbAddDatabaseTargets kbAddTopic kbHasTopic kbReact " + "kbRemoveTopic kbTell kbWasSaid keyImage keyName knowsAbout land landAt landResult language " + "laserTarget lbAdd lbClear lbColor lbColorRight lbCurSel lbData lbDelete lbIsSelected lbPicture " + "lbPictureRight lbSelection lbSetColor lbSetColorRight lbSetCurSel lbSetData lbSetPicture " + "lbSetPictureColor lbSetPictureColorDisabled lbSetPictureColorSelected lbSetPictureRight " + "lbSetPictureRightColor lbSetPictureRightColorDisabled lbSetPictureRightColorSelected " + "lbSetSelectColor lbSetSelectColorRight lbSetSelected lbSetText lbSetTextRight lbSetTooltip " + "lbSetValue lbSize lbSort lbSortByValue lbText lbTextRight lbValue leader leaderboardDeInit " + "leaderboardGetRows leaderboardInit leaderboardRequestRowsFriends leaderboardsRequestUploadScore " + "leaderboardsRequestUploadScoreKeepBest leaderboardState leaveVehicle libraryCredits " + "libraryDisclaimers lifeState lightAttachObject lightDetachObject lightIsOn lightnings limitSpeed " + "linearConversion lineIntersects lineIntersectsObjs lineIntersectsSurfaces lineIntersectsWith " + "linkItem list listObjects listRemoteTargets listVehicleSensors ln lnbAddArray lnbAddColumn " + "lnbAddRow lnbClear lnbColor lnbCurSelRow lnbData lnbDeleteColumn lnbDeleteRow " + "lnbGetColumnsPosition lnbPicture lnbSetColor lnbSetColumnsPos lnbSetCurSelRow lnbSetData " + "lnbSetPicture lnbSetText lnbSetValue lnbSize lnbSort lnbSortByValue lnbText lnbValue load loadAbs " + "loadBackpack loadFile loadGame loadIdentity loadMagazine loadOverlay loadStatus loadUniform " + "loadVest local localize locationPosition lock lockCameraTo lockCargo lockDriver locked " + "lockedCargo lockedDriver lockedTurret lockIdentity lockTurret lockWP log logEntities logNetwork " + "logNetworkTerminate lookAt lookAtPos magazineCargo magazines magazinesAllTurrets magazinesAmmo " + "magazinesAmmoCargo magazinesAmmoFull magazinesDetail magazinesDetailBackpack " + "magazinesDetailUniform magazinesDetailVest magazinesTurret magazineTurretAmmo mapAnimAdd " + "mapAnimClear mapAnimCommit mapAnimDone mapCenterOnCamera mapGridPosition markAsFinishedOnSteam " + "markerAlpha markerBrush markerColor markerDir markerPos markerShape markerSize markerText " + "markerType max members menuAction menuAdd menuChecked menuClear menuCollapse menuData menuDelete " + "menuEnable menuEnabled menuExpand menuHover menuPicture menuSetAction menuSetCheck menuSetData " + "menuSetPicture menuSetValue menuShortcut menuShortcutText menuSize menuSort menuText menuURL " + "menuValue min mineActive mineDetectedBy missionConfigFile missionDifficulty missionName " + "missionNamespace missionStart missionVersion mod modelToWorld modelToWorldVisual " + "modelToWorldVisualWorld modelToWorldWorld modParams moonIntensity moonPhase morale move " + "move3DENCamera moveInAny moveInCargo moveInCommander moveInDriver moveInGunner moveInTurret " + "moveObjectToEnd moveOut moveTime moveTo moveToCompleted moveToFailed musicVolume name nameSound " + "nearEntities nearestBuilding nearestLocation nearestLocations nearestLocationWithDubbing " + "nearestObject nearestObjects nearestTerrainObjects nearObjects nearObjectsReady nearRoads " + "nearSupplies nearTargets needReload netId netObjNull newOverlay nextMenuItemIndex " + "nextWeatherChange nMenuItems not numberOfEnginesRTD numberToDate objectCurators objectFromNetId " + "objectParent objStatus onBriefingGroup onBriefingNotes onBriefingPlan onBriefingTeamSwitch " + "onCommandModeChanged onDoubleClick onEachFrame onGroupIconClick onGroupIconOverEnter " + "onGroupIconOverLeave onHCGroupSelectionChanged onMapSingleClick onPlayerConnected " + "onPlayerDisconnected onPreloadFinished onPreloadStarted onShowNewObject onTeamSwitch " + "openCuratorInterface openDLCPage openMap openSteamApp openYoutubeVideo or orderGetIn overcast " + "overcastForecast owner param params parseNumber parseSimpleArray parseText parsingNamespace " + "particlesQuality pickWeaponPool pitch pixelGrid pixelGridBase pixelGridNoUIScale pixelH pixelW " + "playableSlotsNumber playableUnits playAction playActionNow player playerRespawnTime playerSide " + "playersNumber playGesture playMission playMove playMoveNow playMusic playScriptedMission " + "playSound playSound3D position positionCameraToWorld posScreenToWorld posWorldToScreen " + "ppEffectAdjust ppEffectCommit ppEffectCommitted ppEffectCreate ppEffectDestroy ppEffectEnable " + "ppEffectEnabled ppEffectForceInNVG precision preloadCamera preloadObject preloadSound " + "preloadTitleObj preloadTitleRsc preprocessFile preprocessFileLineNumbers primaryWeapon " + "primaryWeaponItems primaryWeaponMagazine priority processDiaryLink productVersion profileName " + "profileNamespace profileNameSteam progressLoadingScreen progressPosition progressSetPosition " + "publicVariable publicVariableClient publicVariableServer pushBack pushBackUnique putWeaponPool " + "queryItemsPool queryMagazinePool queryWeaponPool rad radioChannelAdd radioChannelCreate " + "radioChannelRemove radioChannelSetCallSign radioChannelSetLabel radioVolume rain rainbow random " + "rank rankId rating rectangular registeredTasks registerTask reload reloadEnabled remoteControl " + "remoteExec remoteExecCall remoteExecutedOwner remove3DENConnection remove3DENEventHandler " + "remove3DENLayer removeAction removeAll3DENEventHandlers removeAllActions removeAllAssignedItems " + "removeAllContainers removeAllCuratorAddons removeAllCuratorCameraAreas " + "removeAllCuratorEditingAreas removeAllEventHandlers removeAllHandgunItems removeAllItems " + "removeAllItemsWithMagazines removeAllMissionEventHandlers removeAllMPEventHandlers " + "removeAllMusicEventHandlers removeAllOwnedMines removeAllPrimaryWeaponItems removeAllWeapons " + "removeBackpack removeBackpackGlobal removeCuratorAddons removeCuratorCameraArea " + "removeCuratorEditableObjects removeCuratorEditingArea removeDrawIcon removeDrawLinks " + "removeEventHandler removeFromRemainsCollector removeGoggles removeGroupIcon removeHandgunItem " + "removeHeadgear removeItem removeItemFromBackpack removeItemFromUniform removeItemFromVest " + "removeItems removeMagazine removeMagazineGlobal removeMagazines removeMagazinesTurret " + "removeMagazineTurret removeMenuItem removeMissionEventHandler removeMPEventHandler " + "removeMusicEventHandler removeOwnedMine removePrimaryWeaponItem removeSecondaryWeaponItem " + "removeSimpleTask removeSwitchableUnit removeTeamMember removeUniform removeVest removeWeapon " + "removeWeaponAttachmentCargo removeWeaponCargo removeWeaponGlobal removeWeaponTurret " + "reportRemoteTarget requiredVersion resetCamShake resetSubgroupDirection resize resources " + "respawnVehicle restartEditorCamera reveal revealMine reverse reversedMouseY roadAt " + "roadsConnectedTo roleDescription ropeAttachedObjects ropeAttachedTo ropeAttachEnabled " + "ropeAttachTo ropeCreate ropeCut ropeDestroy ropeDetach ropeEndPosition ropeLength ropes " + "ropeUnwind ropeUnwound rotorsForcesRTD rotorsRpmRTD round runInitScript safeZoneH safeZoneW " + "safeZoneWAbs safeZoneX safeZoneXAbs safeZoneY save3DENInventory saveGame saveIdentity " + "saveJoysticks saveOverlay saveProfileNamespace saveStatus saveVar savingEnabled say say2D say3D " + "scopeName score scoreSide screenshot screenToWorld scriptDone scriptName scudState " + "secondaryWeapon secondaryWeaponItems secondaryWeaponMagazine select selectBestPlaces " + "selectDiarySubject selectedEditorObjects selectEditorObject selectionNames selectionPosition " + "selectLeader selectMax selectMin selectNoPlayer selectPlayer selectRandom selectRandomWeighted " + "selectWeapon selectWeaponTurret sendAUMessage sendSimpleCommand sendTask sendTaskResult " + "sendUDPMessage serverCommand serverCommandAvailable serverCommandExecutable serverName serverTime " + "set set3DENAttribute set3DENAttributes set3DENGrid set3DENIconsVisible set3DENLayer " + "set3DENLinesVisible set3DENLogicType set3DENMissionAttribute set3DENMissionAttributes " + "set3DENModelsVisible set3DENObjectType set3DENSelected setAccTime setActualCollectiveRTD " + "setAirplaneThrottle setAirportSide setAmmo setAmmoCargo setAmmoOnPylon setAnimSpeedCoef " + "setAperture setApertureNew setArmoryPoints setAttributes setAutonomous setBehaviour " + "setBleedingRemaining setBrakesRTD setCameraInterest setCamShakeDefParams setCamShakeParams " + "setCamUseTI setCaptive setCenterOfMass setCollisionLight setCombatMode setCompassOscillation " + "setConvoySeparation setCuratorCameraAreaCeiling setCuratorCoef setCuratorEditingAreaType " + "setCuratorWaypointCost setCurrentChannel setCurrentTask setCurrentWaypoint setCustomAimCoef " + "setCustomWeightRTD setDamage setDammage setDate setDebriefingText setDefaultCamera setDestination " + "setDetailMapBlendPars setDir setDirection setDrawIcon setDriveOnPath setDropInterval " + "setDynamicSimulationDistance setDynamicSimulationDistanceCoef setEditorMode setEditorObjectScope " + "setEffectCondition setEngineRPMRTD setFace setFaceAnimation setFatigue setFeatureType " + "setFlagAnimationPhase setFlagOwner setFlagSide setFlagTexture setFog setFormation " + "setFormationTask setFormDir setFriend setFromEditor setFSMVariable setFuel setFuelCargo " + "setGroupIcon setGroupIconParams setGroupIconsSelectable setGroupIconsVisible setGroupId " + "setGroupIdGlobal setGroupOwner setGusts setHideBehind setHit setHitIndex setHitPointDamage " + "setHorizonParallaxCoef setHUDMovementLevels setIdentity setImportance setInfoPanel setLeader " + "setLightAmbient setLightAttenuation setLightBrightness setLightColor setLightDayLight " + "setLightFlareMaxDistance setLightFlareSize setLightIntensity setLightnings setLightUseFlare " + "setLocalWindParams setMagazineTurretAmmo setMarkerAlpha setMarkerAlphaLocal setMarkerBrush " + "setMarkerBrushLocal setMarkerColor setMarkerColorLocal setMarkerDir setMarkerDirLocal " + "setMarkerPos setMarkerPosLocal setMarkerShape setMarkerShapeLocal setMarkerSize " + "setMarkerSizeLocal setMarkerText setMarkerTextLocal setMarkerType setMarkerTypeLocal setMass " + "setMimic setMousePosition setMusicEffect setMusicEventHandler setName setNameSound " + "setObjectArguments setObjectMaterial setObjectMaterialGlobal setObjectProxy setObjectTexture " + "setObjectTextureGlobal setObjectViewDistance setOvercast setOwner setOxygenRemaining " + "setParticleCircle setParticleClass setParticleFire setParticleParams setParticleRandom " + "setPilotCameraDirection setPilotCameraRotation setPilotCameraTarget setPilotLight setPiPEffect " + "setPitch setPlateNumber setPlayable setPlayerRespawnTime setPos setPosASL setPosASL2 setPosASLW " + "setPosATL setPosition setPosWorld setPylonLoadOut setPylonsPriority setRadioMsg setRain " + "setRainbow setRandomLip setRank setRectangular setRepairCargo setRotorBrakeRTD setShadowDistance " + "setShotParents setSide setSimpleTaskAlwaysVisible setSimpleTaskCustomData " + "setSimpleTaskDescription setSimpleTaskDestination setSimpleTaskTarget setSimpleTaskType " + "setSimulWeatherLayers setSize setSkill setSlingLoad setSoundEffect setSpeaker setSpeech " + "setSpeedMode setStamina setStaminaScheme setStatValue setSuppression setSystemOfUnits " + "setTargetAge setTaskMarkerOffset setTaskResult setTaskState setTerrainGrid setText " + "setTimeMultiplier setTitleEffect setTrafficDensity setTrafficDistance setTrafficGap " + "setTrafficSpeed setTriggerActivation setTriggerArea setTriggerStatements setTriggerText " + "setTriggerTimeout setTriggerType setType setUnconscious setUnitAbility setUnitLoadout setUnitPos " + "setUnitPosWeak setUnitRank setUnitRecoilCoefficient setUnitTrait setUnloadInCombat " + "setUserActionText setUserMFDText setUserMFDvalue setVariable setVectorDir setVectorDirAndUp " + "setVectorUp setVehicleAmmo setVehicleAmmoDef setVehicleArmor setVehicleCargo setVehicleId " + "setVehicleLock setVehiclePosition setVehicleRadar setVehicleReceiveRemoteTargets " + "setVehicleReportOwnPosition setVehicleReportRemoteTargets setVehicleTIPars setVehicleVarName " + "setVelocity setVelocityModelSpace setVelocityTransformation setViewDistance " + "setVisibleIfTreeCollapsed setWantedRPMRTD setWaves setWaypointBehaviour setWaypointCombatMode " + "setWaypointCompletionRadius setWaypointDescription setWaypointForceBehaviour setWaypointFormation " + "setWaypointHousePosition setWaypointLoiterRadius setWaypointLoiterType setWaypointName " + "setWaypointPosition setWaypointScript setWaypointSpeed setWaypointStatements setWaypointTimeout " + "setWaypointType setWaypointVisible setWeaponReloadingTime setWind setWindDir setWindForce " + "setWindStr setWingForceScaleRTD setWPPos show3DIcons showChat showCinemaBorder showCommandingMenu " + "showCompass showCuratorCompass showGPS showHUD showLegend showMap shownArtilleryComputer " + "shownChat shownCompass shownCuratorCompass showNewEditorObject shownGPS shownHUD shownMap " + "shownPad shownRadio shownScoretable shownUAVFeed shownWarrant shownWatch showPad showRadio " + "showScoretable showSubtitles showUAVFeed showWarrant showWatch showWaypoint showWaypoints side " + "sideChat sideEnemy sideFriendly sideRadio simpleTasks simulationEnabled simulCloudDensity " + "simulCloudOcclusion simulInClouds simulWeatherSync sin size sizeOf skill skillFinal skipTime " + "sleep sliderPosition sliderRange sliderSetPosition sliderSetRange sliderSetSpeed sliderSpeed " + "slingLoadAssistantShown soldierMagazines someAmmo sort soundVolume spawn speaker speed speedMode " + "splitString sqrt squadParams stance startLoadingScreen step stop stopEngineRTD stopped str " + "sunOrMoon supportInfo suppressFor surfaceIsWater surfaceNormal surfaceType swimInDepth " + "switchableUnits switchAction switchCamera switchGesture switchLight switchMove " + "synchronizedObjects synchronizedTriggers synchronizedWaypoints synchronizeObjectsAdd " + "synchronizeObjectsRemove synchronizeTrigger synchronizeWaypoint systemChat systemOfUnits tan " + "targetKnowledge targets targetsAggregate targetsQuery taskAlwaysVisible taskChildren " + "taskCompleted taskCustomData taskDescription taskDestination taskHint taskMarkerOffset taskParent " + "taskResult taskState taskType teamMember teamName teams teamSwitch teamSwitchEnabled teamType " + "terminate terrainIntersect terrainIntersectASL terrainIntersectAtASL text textLog textLogFormat " + "tg time timeMultiplier titleCut titleFadeOut titleObj titleRsc titleText toArray toFixed toLower " + "toString toUpper triggerActivated triggerActivation triggerArea triggerAttachedVehicle " + "triggerAttachObject triggerAttachVehicle triggerDynamicSimulation triggerStatements triggerText " + "triggerTimeout triggerTimeoutCurrent triggerType turretLocal turretOwner turretUnit tvAdd tvClear " + "tvCollapse tvCollapseAll tvCount tvCurSel tvData tvDelete tvExpand tvExpandAll tvPicture " + "tvSetColor tvSetCurSel tvSetData tvSetPicture tvSetPictureColor tvSetPictureColorDisabled " + "tvSetPictureColorSelected tvSetPictureRight tvSetPictureRightColor tvSetPictureRightColorDisabled " + "tvSetPictureRightColorSelected tvSetText tvSetTooltip tvSetValue tvSort tvSortByValue tvText " + "tvTooltip tvValue type typeName typeOf UAVControl uiNamespace uiSleep unassignCurator " + "unassignItem unassignTeam unassignVehicle underwater uniform uniformContainer uniformItems " + "uniformMagazines unitAddons unitAimPosition unitAimPositionVisual unitBackpack unitIsUAV unitPos " + "unitReady unitRecoilCoefficient units unitsBelowHeight unlinkItem unlockAchievement " + "unregisterTask updateDrawIcon updateMenuItem updateObjectTree useAISteeringComponent " + "useAudioTimeForMoves userInputDisabled vectorAdd vectorCos vectorCrossProduct vectorDiff " + "vectorDir vectorDirVisual vectorDistance vectorDistanceSqr vectorDotProduct vectorFromTo " + "vectorMagnitude vectorMagnitudeSqr vectorModelToWorld vectorModelToWorldVisual vectorMultiply " + "vectorNormalized vectorUp vectorUpVisual vectorWorldToModel vectorWorldToModelVisual vehicle " + "vehicleCargoEnabled vehicleChat vehicleRadio vehicleReceiveRemoteTargets vehicleReportOwnPosition " + "vehicleReportRemoteTargets vehicles vehicleVarName velocity velocityModelSpace verifySignature " + "vest vestContainer vestItems vestMagazines viewDistance visibleCompass visibleGPS visibleMap " + "visiblePosition visiblePositionASL visibleScoretable visibleWatch waves waypointAttachedObject " + "waypointAttachedVehicle waypointAttachObject waypointAttachVehicle waypointBehaviour " + "waypointCombatMode waypointCompletionRadius waypointDescription waypointForceBehaviour " + "waypointFormation waypointHousePosition waypointLoiterRadius waypointLoiterType waypointName " + "waypointPosition waypoints waypointScript waypointsEnabledUAV waypointShow waypointSpeed " + "waypointStatements waypointTimeout waypointTimeoutCurrent waypointType waypointVisible " + "weaponAccessories weaponAccessoriesCargo weaponCargo weaponDirection weaponInertia weaponLowered " + "weapons weaponsItems weaponsItemsCargo weaponState weaponsTurret weightRTD WFSideText wind ", literal: "blufor civilian configNull controlNull displayNull east endl false grpNull independent lineBreak " + "locationNull nil objNull opfor pi resistance scriptNull sideAmbientLife sideEmpty sideLogic " + "sideUnknown taskNull teamMemberNull true west", }, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.NUMBER_MODE, VARIABLE, FUNCTION, STRINGS, CPP.preprocessor, ], illegal: /#|^\$ /, }; }, }, { name: "sql", /* Language: SQL Contributors: Nikolay Lisienko , Heiko August , Travis Odom , Vadimtro , Benjamin Auder Category: common */ create: function (hljs) { var COMMENT_MODE = hljs.COMMENT("--", "$"); return { case_insensitive: true, illegal: /[<>{}*]/, contains: [ { beginKeywords: "begin end start commit rollback savepoint lock alter create drop rename call " + "delete do handler insert load replace select truncate update set show pragma grant " + "merge describe use explain help declare prepare execute deallocate release " + "unlock purge reset change stop analyze cache flush optimize repair kill " + "install uninstall checksum restore check backup revoke comment values with", end: /;/, endsWithParent: true, lexemes: /[\w\.]+/, keywords: { keyword: "as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add " + "addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias " + "all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply " + "archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan " + "atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid " + "authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile " + "before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float " + "binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound " + "bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel " + "capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base " + "char_length character_length characters characterset charindex charset charsetform charsetid check " + "checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close " + "cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation " + "collect colu colum column column_value columns columns_updated comment commit compact compatibility " + "compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn " + "connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection " + "consider consistent constant constraint constraints constructor container content contents context " + "contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost " + "count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation " + "critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user " + "cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add " + "date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts " + "day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate " + "declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults " + "deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank " + "depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor " + "deterministic diagnostics difference dimension direct_load directory disable disable_all " + "disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div " + "do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable " + "editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt " + "end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors " + "escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding " + "execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external " + "external_1 external_2 externally extract failed failed_login_attempts failover failure far fast " + "feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final " + "finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign " + "form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days " + "ftp full function general generated get get_format get_lock getdate getutcdate global global_name " + "globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups " + "gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex " + "hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified " + "identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment " + "index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile " + "initial initialized initially initrans inmemory inner innodb input insert install instance instantiable " + "instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat " + "is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists " + "keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase " + "lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit " + "lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate " + "locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call " + "logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime " + "managed management manual map mapping mask master master_pos_wait match matched materialized max " + "maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans " + "md5 measures median medium member memcompress memory merge microsecond mid migration min minextents " + "minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month " + "months mount move movement multiset mutex name name_const names nan national native natural nav nchar " + "nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile " + "nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile " + "nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder " + "nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck " + "noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe " + "nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber " + "ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old " + "on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date " + "oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary " + "out outer outfile outline output over overflow overriding package pad parallel parallel_enable " + "parameters parent parse partial partition partitions pascal passing password password_grace_time " + "password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex " + "pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc " + "performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin " + "policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction " + "prediction_cost prediction_details prediction_probability prediction_set prepare present preserve " + "prior priority private private_sga privileges procedural procedure procedure_analyze processlist " + "profiles project prompt protection public publishingservername purge quarter query quick quiesce quota " + "quotename radians raise rand range rank raw read reads readsize rebuild record records " + "recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh " + "regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy " + "reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename " + "repair repeat replace replicate replication required reset resetlogs resize resource respect restore " + "restricted result result_cache resumable resume retention return returning returns reuse reverse revoke " + "right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows " + "rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll " + "sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select " + "self semi sequence sequential serializable server servererror session session_user sessions_per_user set " + "sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor " + "si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin " + "size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex " + "source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows " + "sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone " + "standby start starting startup statement static statistics stats_binomial_test stats_crosstab " + "stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep " + "stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev " + "stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate " + "subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum " + "suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate " + "sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo " + "template temporary terminated tertiary_weights test than then thread through tier ties time time_format " + "time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr " + "timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking " + "transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate " + "try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress " + "under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot " + "unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert " + "url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date " + "utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var " + "var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray " + "verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear " + "wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped " + "xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces " + "xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek", literal: "true false null unknown", built_in: "array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number " + "numeric real record serial serial8 smallint text time timestamp tinyint varchar varying void", }, contains: [ { className: "string", begin: "'", end: "'", contains: [hljs.BACKSLASH_ESCAPE, { begin: "''" }], }, { className: "string", begin: '"', end: '"', contains: [hljs.BACKSLASH_ESCAPE, { begin: '""' }], }, { className: "string", begin: "`", end: "`", contains: [hljs.BACKSLASH_ESCAPE], }, hljs.C_NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, COMMENT_MODE, hljs.HASH_COMMENT_MODE, ], }, hljs.C_BLOCK_COMMENT_MODE, COMMENT_MODE, hljs.HASH_COMMENT_MODE, ], }; }, }, { name: "stan", /* Language: Stan Author: Brendan Rocks Category: scientific Description: The Stan probabilistic programming language (http://mc-stan.org/). */ create: function (hljs) { return { contains: [ hljs.HASH_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { begin: hljs.UNDERSCORE_IDENT_RE, lexemes: hljs.UNDERSCORE_IDENT_RE, keywords: { // Stan's keywords name: "for in while repeat until if then else", // Stan's probablity distributions (less beta and gamma, as commonly // used for parameter names). So far, _log and _rng variants are not // included symbol: "bernoulli bernoulli_logit binomial binomial_logit " + "beta_binomial hypergeometric categorical categorical_logit " + "ordered_logistic neg_binomial neg_binomial_2 " + "neg_binomial_2_log poisson poisson_log multinomial normal " + "exp_mod_normal skew_normal student_t cauchy double_exponential " + "logistic gumbel lognormal chi_square inv_chi_square " + "scaled_inv_chi_square exponential inv_gamma weibull frechet " + "rayleigh wiener pareto pareto_type_2 von_mises uniform " + "multi_normal multi_normal_prec multi_normal_cholesky multi_gp " + "multi_gp_cholesky multi_student_t gaussian_dlm_obs dirichlet " + "lkj_corr lkj_corr_cholesky wishart inv_wishart", // Stan's data types "selector-tag": "int real vector simplex unit_vector ordered positive_ordered " + "row_vector matrix cholesky_factor_corr cholesky_factor_cov " + "corr_matrix cov_matrix", // Stan's model blocks title: "functions model data parameters quantities transformed " + "generated", literal: "true false", }, relevance: 0, }, // The below is all taken from the R language definition { // hex value className: "number", begin: "0[xX][0-9a-fA-F]+[Li]?\\b", relevance: 0, }, { // hex value className: "number", begin: "0[xX][0-9a-fA-F]+[Li]?\\b", relevance: 0, }, { // explicit integer className: "number", begin: "\\d+(?:[eE][+\\-]?\\d*)?L\\b", relevance: 0, }, { // number with trailing decimal className: "number", begin: "\\d+\\.(?!\\d)(?:i\\b)?", relevance: 0, }, { // number className: "number", begin: "\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b", relevance: 0, }, { // number with leading decimal className: "number", begin: "\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b", relevance: 0, }, ], }; }, }, { name: "stata", /* Language: Stata Author: Brian Quistorff Contributors: Drew McDonald Description: Syntax highlighting for Stata code. This is a fork and modification of Drew McDonald's file (https://github.com/drewmcdonald/stata-highlighting). I have also included a list of builtin commands from https://bugs.kde.org/show_bug.cgi?id=135646. Category: scientific */ create: function (hljs) { return { aliases: ["do", "ado"], case_insensitive: true, keywords: "if else in foreach for forv forva forval forvalu forvalue forvalues by bys bysort xi quietly qui capture about ac ac_7 acprplot acprplot_7 adjust ado adopath adoupdate alpha ameans an ano anov anova anova_estat anova_terms anovadef aorder ap app appe appen append arch arch_dr arch_estat arch_p archlm areg areg_p args arima arima_dr arima_estat arima_p as asmprobit asmprobit_estat asmprobit_lf asmprobit_mfx__dlg asmprobit_p ass asse asser assert avplot avplot_7 avplots avplots_7 bcskew0 bgodfrey bias binreg bip0_lf biplot bipp_lf bipr_lf bipr_p biprobit bitest bitesti bitowt blogit bmemsize boot bootsamp bootstrap bootstrap_8 boxco_l boxco_p boxcox boxcox_6 boxcox_p bprobit br break brier bro brow brows browse brr brrstat bs bs_7 bsampl_w bsample bsample_7 bsqreg bstat bstat_7 bstat_8 bstrap bstrap_7 bubble bubbleplot ca ca_estat ca_p cabiplot camat canon canon_8 canon_8_p canon_estat canon_p cap caprojection capt captu captur capture cat cc cchart cchart_7 cci cd censobs_table centile cf char chdir checkdlgfiles checkestimationsample checkhlpfiles checksum chelp ci cii cl class classutil clear cli clis clist clo clog clog_lf clog_p clogi clogi_sw clogit clogit_lf clogit_p clogitp clogl_sw cloglog clonevar clslistarray cluster cluster_measures cluster_stop cluster_tree cluster_tree_8 clustermat cmdlog cnr cnre cnreg cnreg_p cnreg_sw cnsreg codebook collaps4 collapse colormult_nb colormult_nw compare compress conf confi confir confirm conren cons const constr constra constrai constrain constraint continue contract copy copyright copysource cor corc corr corr2data corr_anti corr_kmo corr_smc corre correl correla correlat correlate corrgram cou coun count cox cox_p cox_sw coxbase coxhaz coxvar cprplot cprplot_7 crc cret cretu cretur creturn cross cs cscript cscript_log csi ct ct_is ctset ctst_5 ctst_st cttost cumsp cumsp_7 cumul cusum cusum_7 cutil d|0 datasig datasign datasigna datasignat datasignatu datasignatur datasignature datetof db dbeta de dec deco decod decode deff des desc descr descri describ describe destring dfbeta dfgls dfuller di di_g dir dirstats dis discard disp disp_res disp_s displ displa display distinct do doe doed doedi doedit dotplot dotplot_7 dprobit drawnorm drop ds ds_util dstdize duplicates durbina dwstat dydx e|0 ed edi edit egen eivreg emdef en enc enco encod encode eq erase ereg ereg_lf ereg_p ereg_sw ereghet ereghet_glf ereghet_glf_sh ereghet_gp ereghet_ilf ereghet_ilf_sh ereghet_ip eret eretu eretur ereturn err erro error esize est est_cfexist est_cfname est_clickable est_expand est_hold est_table est_unhold est_unholdok estat estat_default estat_summ estat_vce_only esti estimates etodow etof etomdy ex exi exit expand expandcl fac fact facto factor factor_estat factor_p factor_pca_rotated factor_rotate factormat fcast fcast_compute fcast_graph fdades fdadesc fdadescr fdadescri fdadescrib fdadescribe fdasav fdasave fdause fh_st file open file read file close file filefilter fillin find_hlp_file findfile findit findit_7 fit fl fli flis flist for5_0 forest forestplot form forma format fpredict frac_154 frac_adj frac_chk frac_cox frac_ddp frac_dis frac_dv frac_in frac_mun frac_pp frac_pq frac_pv frac_wgt frac_xo fracgen fracplot fracplot_7 fracpoly fracpred fron_ex fron_hn fron_p fron_tn fron_tn2 frontier ftodate ftoe ftomdy ftowdate funnel funnelplot g|0 gamhet_glf gamhet_gp gamhet_ilf gamhet_ip gamma gamma_d2 gamma_p gamma_sw gammahet gdi_hexagon gdi_spokes ge gen gene gener genera generat generate genrank genstd genvmean gettoken gl gladder gladder_7 glim_l01 glim_l02 glim_l03 glim_l04 glim_l05 glim_l06 glim_l07 glim_l08 glim_l09 glim_l10 glim_l11 glim_l12 glim_lf glim_mu glim_nw1 glim_nw2 glim_nw3 glim_p glim_v1 glim_v2 glim_v3 glim_v4 glim_v5 glim_v6 glim_v7 glm glm_6 glm_p glm_sw glmpred glo glob globa global glogit glogit_8 glogit_p gmeans gnbre_lf gnbreg gnbreg_5 gnbreg_p gomp_lf gompe_sw gomper_p gompertz gompertzhet gomphet_glf gomphet_glf_sh gomphet_gp gomphet_ilf gomphet_ilf_sh gomphet_ip gphdot gphpen gphprint gprefs gprobi_p gprobit gprobit_8 gr gr7 gr_copy gr_current gr_db gr_describe gr_dir gr_draw gr_draw_replay gr_drop gr_edit gr_editviewopts gr_example gr_example2 gr_export gr_print gr_qscheme gr_query gr_read gr_rename gr_replay gr_save gr_set gr_setscheme gr_table gr_undo gr_use graph graph7 grebar greigen greigen_7 greigen_8 grmeanby grmeanby_7 gs_fileinfo gs_filetype gs_graphinfo gs_stat gsort gwood h|0 hadimvo hareg hausman haver he heck_d2 heckma_p heckman heckp_lf heckpr_p heckprob hel help hereg hetpr_lf hetpr_p hetprob hettest hexdump hilite hist hist_7 histogram hlogit hlu hmeans hotel hotelling hprobit hreg hsearch icd9 icd9_ff icd9p iis impute imtest inbase include inf infi infil infile infix inp inpu input ins insheet insp inspe inspec inspect integ inten intreg intreg_7 intreg_p intrg2_ll intrg_ll intrg_ll2 ipolate iqreg ir irf irf_create irfm iri is_svy is_svysum isid istdize ivprob_1_lf ivprob_lf ivprobit ivprobit_p ivreg ivreg_footnote ivtob_1_lf ivtob_lf ivtobit ivtobit_p jackknife jacknife jknife jknife_6 jknife_8 jkstat joinby kalarma1 kap kap_3 kapmeier kappa kapwgt kdensity kdensity_7 keep ksm ksmirnov ktau kwallis l|0 la lab labbe labbeplot labe label labelbook ladder levels levelsof leverage lfit lfit_p li lincom line linktest lis list lloghet_glf lloghet_glf_sh lloghet_gp lloghet_ilf lloghet_ilf_sh lloghet_ip llogi_sw llogis_p llogist llogistic llogistichet lnorm_lf lnorm_sw lnorma_p lnormal lnormalhet lnormhet_glf lnormhet_glf_sh lnormhet_gp lnormhet_ilf lnormhet_ilf_sh lnormhet_ip lnskew0 loadingplot loc loca local log logi logis_lf logistic logistic_p logit logit_estat logit_p loglogs logrank loneway lookfor lookup lowess lowess_7 lpredict lrecomp lroc lroc_7 lrtest ls lsens lsens_7 lsens_x lstat ltable ltable_7 ltriang lv lvr2plot lvr2plot_7 m|0 ma mac macr macro makecns man manova manova_estat manova_p manovatest mantel mark markin markout marksample mat mat_capp mat_order mat_put_rr mat_rapp mata mata_clear mata_describe mata_drop mata_matdescribe mata_matsave mata_matuse mata_memory mata_mlib mata_mosave mata_rename mata_which matalabel matcproc matlist matname matr matri matrix matrix_input__dlg matstrik mcc mcci md0_ md1_ md1debug_ md2_ md2debug_ mds mds_estat mds_p mdsconfig mdslong mdsmat mdsshepard mdytoe mdytof me_derd mean means median memory memsize menl meqparse mer merg merge meta mfp mfx mhelp mhodds minbound mixed_ll mixed_ll_reparm mkassert mkdir mkmat mkspline ml ml_5 ml_adjs ml_bhhhs ml_c_d ml_check ml_clear ml_cnt ml_debug ml_defd ml_e0 ml_e0_bfgs ml_e0_cycle ml_e0_dfp ml_e0i ml_e1 ml_e1_bfgs ml_e1_bhhh ml_e1_cycle ml_e1_dfp ml_e2 ml_e2_cycle ml_ebfg0 ml_ebfr0 ml_ebfr1 ml_ebh0q ml_ebhh0 ml_ebhr0 ml_ebr0i ml_ecr0i ml_edfp0 ml_edfr0 ml_edfr1 ml_edr0i ml_eds ml_eer0i ml_egr0i ml_elf ml_elf_bfgs ml_elf_bhhh ml_elf_cycle ml_elf_dfp ml_elfi ml_elfs ml_enr0i ml_enrr0 ml_erdu0 ml_erdu0_bfgs ml_erdu0_bhhh ml_erdu0_bhhhq ml_erdu0_cycle ml_erdu0_dfp ml_erdu0_nrbfgs ml_exde ml_footnote ml_geqnr ml_grad0 ml_graph ml_hbhhh ml_hd0 ml_hold ml_init ml_inv ml_log ml_max ml_mlout ml_mlout_8 ml_model ml_nb0 ml_opt ml_p ml_plot ml_query ml_rdgrd ml_repor ml_s_e ml_score ml_searc ml_technique ml_unhold mleval mlf_ mlmatbysum mlmatsum mlog mlogi mlogit mlogit_footnote mlogit_p mlopts mlsum mlvecsum mnl0_ mor more mov move mprobit mprobit_lf mprobit_p mrdu0_ mrdu1_ mvdecode mvencode mvreg mvreg_estat n|0 nbreg nbreg_al nbreg_lf nbreg_p nbreg_sw nestreg net newey newey_7 newey_p news nl nl_7 nl_9 nl_9_p nl_p nl_p_7 nlcom nlcom_p nlexp2 nlexp2_7 nlexp2a nlexp2a_7 nlexp3 nlexp3_7 nlgom3 nlgom3_7 nlgom4 nlgom4_7 nlinit nllog3 nllog3_7 nllog4 nllog4_7 nlog_rd nlogit nlogit_p nlogitgen nlogittree nlpred no nobreak noi nois noisi noisil noisily note notes notes_dlg nptrend numlabel numlist odbc old_ver olo olog ologi ologi_sw ologit ologit_p ologitp on one onew onewa oneway op_colnm op_comp op_diff op_inv op_str opr opro oprob oprob_sw oprobi oprobi_p oprobit oprobitp opts_exclusive order orthog orthpoly ou out outf outfi outfil outfile outs outsh outshe outshee outsheet ovtest pac pac_7 palette parse parse_dissim pause pca pca_8 pca_display pca_estat pca_p pca_rotate pcamat pchart pchart_7 pchi pchi_7 pcorr pctile pentium pergram pergram_7 permute permute_8 personal peto_st pkcollapse pkcross pkequiv pkexamine pkexamine_7 pkshape pksumm pksumm_7 pl plo plot plugin pnorm pnorm_7 poisgof poiss_lf poiss_sw poisso_p poisson poisson_estat post postclose postfile postutil pperron pr prais prais_e prais_e2 prais_p predict predictnl preserve print pro prob probi probit probit_estat probit_p proc_time procoverlay procrustes procrustes_estat procrustes_p profiler prog progr progra program prop proportion prtest prtesti pwcorr pwd q\\s qby qbys qchi qchi_7 qladder qladder_7 qnorm qnorm_7 qqplot qqplot_7 qreg qreg_c qreg_p qreg_sw qu quadchk quantile quantile_7 que quer query range ranksum ratio rchart rchart_7 rcof recast reclink recode reg reg3 reg3_p regdw regr regre regre_p2 regres regres_p regress regress_estat regriv_p remap ren rena renam rename renpfix repeat replace report reshape restore ret retu retur return rm rmdir robvar roccomp roccomp_7 roccomp_8 rocf_lf rocfit rocfit_8 rocgold rocplot rocplot_7 roctab roctab_7 rolling rologit rologit_p rot rota rotat rotate rotatemat rreg rreg_p ru run runtest rvfplot rvfplot_7 rvpplot rvpplot_7 sa safesum sample sampsi sav save savedresults saveold sc sca scal scala scalar scatter scm_mine sco scob_lf scob_p scobi_sw scobit scor score scoreplot scoreplot_help scree screeplot screeplot_help sdtest sdtesti se search separate seperate serrbar serrbar_7 serset set set_defaults sfrancia sh she shel shell shewhart shewhart_7 signestimationsample signrank signtest simul simul_7 simulate simulate_8 sktest sleep slogit slogit_d2 slogit_p smooth snapspan so sor sort spearman spikeplot spikeplot_7 spikeplt spline_x split sqreg sqreg_p sret sretu sretur sreturn ssc st st_ct st_hc st_hcd st_hcd_sh st_is st_issys st_note st_promo st_set st_show st_smpl st_subid stack statsby statsby_8 stbase stci stci_7 stcox stcox_estat stcox_fr stcox_fr_ll stcox_p stcox_sw stcoxkm stcoxkm_7 stcstat stcurv stcurve stcurve_7 stdes stem stepwise stereg stfill stgen stir stjoin stmc stmh stphplot stphplot_7 stphtest stphtest_7 stptime strate strate_7 streg streg_sw streset sts sts_7 stset stsplit stsum sttocc sttoct stvary stweib su suest suest_8 sum summ summa summar summari summariz summarize sunflower sureg survcurv survsum svar svar_p svmat svy svy_disp svy_dreg svy_est svy_est_7 svy_estat svy_get svy_gnbreg_p svy_head svy_header svy_heckman_p svy_heckprob_p svy_intreg_p svy_ivreg_p svy_logistic_p svy_logit_p svy_mlogit_p svy_nbreg_p svy_ologit_p svy_oprobit_p svy_poisson_p svy_probit_p svy_regress_p svy_sub svy_sub_7 svy_x svy_x_7 svy_x_p svydes svydes_8 svygen svygnbreg svyheckman svyheckprob svyintreg svyintreg_7 svyintrg svyivreg svylc svylog_p svylogit svymarkout svymarkout_8 svymean svymlog svymlogit svynbreg svyolog svyologit svyoprob svyoprobit svyopts svypois svypois_7 svypoisson svyprobit svyprobt svyprop svyprop_7 svyratio svyreg svyreg_p svyregress svyset svyset_7 svyset_8 svytab svytab_7 svytest svytotal sw sw_8 swcnreg swcox swereg swilk swlogis swlogit swologit swoprbt swpois swprobit swqreg swtobit swweib symmetry symmi symplot symplot_7 syntax sysdescribe sysdir sysuse szroeter ta tab tab1 tab2 tab_or tabd tabdi tabdis tabdisp tabi table tabodds tabodds_7 tabstat tabu tabul tabula tabulat tabulate te tempfile tempname tempvar tes test testnl testparm teststd tetrachoric time_it timer tis tob tobi tobit tobit_p tobit_sw token tokeni tokeniz tokenize tostring total translate translator transmap treat_ll treatr_p treatreg trim trimfill trnb_cons trnb_mean trpoiss_d2 trunc_ll truncr_p truncreg tsappend tset tsfill tsline tsline_ex tsreport tsrevar tsrline tsset tssmooth tsunab ttest ttesti tut_chk tut_wait tutorial tw tware_st two twoway twoway__fpfit_serset twoway__function_gen twoway__histogram_gen twoway__ipoint_serset twoway__ipoints_serset twoway__kdensity_gen twoway__lfit_serset twoway__normgen_gen twoway__pci_serset twoway__qfit_serset twoway__scatteri_serset twoway__sunflower_gen twoway_ksm_serset ty typ type typeof u|0 unab unabbrev unabcmd update us use uselabel var var_mkcompanion var_p varbasic varfcast vargranger varirf varirf_add varirf_cgraph varirf_create varirf_ctable varirf_describe varirf_dir varirf_drop varirf_erase varirf_graph varirf_ograph varirf_rename varirf_set varirf_table varlist varlmar varnorm varsoc varstable varstable_w varstable_w2 varwle vce vec vec_fevd vec_mkphi vec_p vec_p_w vecirf_create veclmar veclmar_w vecnorm vecnorm_w vecrank vecstable verinst vers versi versio version view viewsource vif vwls wdatetof webdescribe webseek webuse weib1_lf weib2_lf weib_lf weib_lf0 weibhet_glf weibhet_glf_sh weibhet_glfa weibhet_glfa_sh weibhet_gp weibhet_ilf weibhet_ilf_sh weibhet_ilfa weibhet_ilfa_sh weibhet_ip weibu_sw weibul_p weibull weibull_c weibull_s weibullhet wh whelp whi which whil while wilc_st wilcoxon win wind windo window winexec wntestb wntestb_7 wntestq xchart xchart_7 xcorr xcorr_7 xi xi_6 xmlsav xmlsave xmluse xpose xsh xshe xshel xshell xt_iis xt_tis xtab_p xtabond xtbin_p xtclog xtcloglog xtcloglog_8 xtcloglog_d2 xtcloglog_pa_p xtcloglog_re_p xtcnt_p xtcorr xtdata xtdes xtfront_p xtfrontier xtgee xtgee_elink xtgee_estat xtgee_makeivar xtgee_p xtgee_plink xtgls xtgls_p xthaus xthausman xtht_p xthtaylor xtile xtint_p xtintreg xtintreg_8 xtintreg_d2 xtintreg_p xtivp_1 xtivp_2 xtivreg xtline xtline_ex xtlogit xtlogit_8 xtlogit_d2 xtlogit_fe_p xtlogit_pa_p xtlogit_re_p xtmixed xtmixed_estat xtmixed_p xtnb_fe xtnb_lf xtnbreg xtnbreg_pa_p xtnbreg_refe_p xtpcse xtpcse_p xtpois xtpoisson xtpoisson_d2 xtpoisson_pa_p xtpoisson_refe_p xtpred xtprobit xtprobit_8 xtprobit_d2 xtprobit_re_p xtps_fe xtps_lf xtps_ren xtps_ren_8 xtrar_p xtrc xtrc_p xtrchh xtrefe_p xtreg xtreg_be xtreg_fe xtreg_ml xtreg_pa_p xtreg_re xtregar xtrere_p xtset xtsf_ll xtsf_llti xtsum xttab xttest0 xttobit xttobit_8 xttobit_p xttrans yx yxview__barlike_draw yxview_area_draw yxview_bar_draw yxview_dot_draw yxview_dropline_draw yxview_function_draw yxview_iarrow_draw yxview_ilabels_draw yxview_normal_draw yxview_pcarrow_draw yxview_pcbarrow_draw yxview_pccapsym_draw yxview_pcscatter_draw yxview_pcspike_draw yxview_rarea_draw yxview_rbar_draw yxview_rbarm_draw yxview_rcap_draw yxview_rcapsym_draw yxview_rconnected_draw yxview_rline_draw yxview_rscatter_draw yxview_rspike_draw yxview_spike_draw yxview_sunflower_draw zap_s zinb zinb_llf zinb_plf zip zip_llf zip_p zip_plf zt_ct_5 zt_hc_5 zt_hcd_5 zt_is_5 zt_iss_5 zt_sho_5 zt_smp_5 ztbase_5 ztcox_5 ztdes_5 ztereg_5 ztfill_5 ztgen_5 ztir_5 ztjoin_5 ztnb ztnb_p ztp ztp_p zts_5 ztset_5 ztspli_5 ztsum_5 zttoct_5 ztvary_5 ztweib_5", contains: [ { className: "symbol", begin: /`[a-zA-Z0-9_]+'/, }, { className: "variable", begin: /\$\{?[a-zA-Z0-9_]+\}?/, }, { className: "string", variants: [ { begin: '`"[^\r\n]*?"\'' }, { begin: '"[^\r\n"]*"' }, ], }, { className: "built_in", variants: [ { begin: "\\b(abs|acos|asin|atan|atan2|atanh|ceil|cloglog|comb|cos|digamma|exp|floor|invcloglog|invlogit|ln|lnfact|lnfactorial|lngamma|log|log10|max|min|mod|reldif|round|sign|sin|sqrt|sum|tan|tanh|trigamma|trunc|betaden|Binomial|binorm|binormal|chi2|chi2tail|dgammapda|dgammapdada|dgammapdadx|dgammapdx|dgammapdxdx|F|Fden|Ftail|gammaden|gammap|ibeta|invbinomial|invchi2|invchi2tail|invF|invFtail|invgammap|invibeta|invnchi2|invnFtail|invnibeta|invnorm|invnormal|invttail|nbetaden|nchi2|nFden|nFtail|nibeta|norm|normal|normalden|normd|npnchi2|tden|ttail|uniform|abbrev|char|index|indexnot|length|lower|ltrim|match|plural|proper|real|regexm|regexr|regexs|reverse|rtrim|string|strlen|strlower|strltrim|strmatch|strofreal|strpos|strproper|strreverse|strrtrim|strtrim|strupper|subinstr|subinword|substr|trim|upper|word|wordcount|_caller|autocode|byteorder|chop|clip|cond|e|epsdouble|epsfloat|group|inlist|inrange|irecode|matrix|maxbyte|maxdouble|maxfloat|maxint|maxlong|mi|minbyte|mindouble|minfloat|minint|minlong|missing|r|recode|replay|return|s|scalar|d|date|day|dow|doy|halfyear|mdy|month|quarter|week|year|d|daily|dofd|dofh|dofm|dofq|dofw|dofy|h|halfyearly|hofd|m|mofd|monthly|q|qofd|quarterly|tin|twithin|w|weekly|wofd|y|yearly|yh|ym|yofd|yq|yw|cholesky|colnumb|colsof|corr|det|diag|diag0cnt|el|get|hadamard|I|inv|invsym|issym|issymmetric|J|matmissing|matuniform|mreldif|nullmat|rownumb|rowsof|sweep|syminv|trace|vec|vecdiag)(?=\\(|$)", }, ], }, hljs.COMMENT("^[ \t]*\\*.*$", false), hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }; }, }, { name: "step21", /* Language: STEP Part 21 Contributors: Adam Joseph Cook Description: Syntax highlighter for STEP Part 21 files (ISO 10303-21). */ create: function (hljs) { var STEP21_IDENT_RE = "[A-Z_][A-Z0-9_.]*"; var STEP21_KEYWORDS = { keyword: "HEADER ENDSEC DATA", }; var STEP21_START = { className: "meta", begin: "ISO-10303-21;", relevance: 10, }; var STEP21_CLOSE = { className: "meta", begin: "END-ISO-10303-21;", relevance: 10, }; return { aliases: ["p21", "step", "stp"], case_insensitive: true, // STEP 21 is case insensitive in theory, in practice all non-comments are capitalized. lexemes: STEP21_IDENT_RE, keywords: STEP21_KEYWORDS, contains: [ STEP21_START, STEP21_CLOSE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.COMMENT("/\\*\\*!", "\\*/"), hljs.C_NUMBER_MODE, hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), { className: "string", begin: "'", end: "'", }, { className: "symbol", variants: [ { begin: "#", end: "\\d+", illegal: "\\W", }, ], }, ], }; }, }, { name: "stylus", /* Language: Stylus Author: Bryant Williams Description: Stylus (https://github.com/LearnBoost/stylus/) Category: css */ create: function (hljs) { var VARIABLE = { className: "variable", begin: "\\$" + hljs.IDENT_RE, }; var HEX_COLOR = { className: "number", begin: "#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})", }; var AT_KEYWORDS = [ "charset", "css", "debug", "extend", "font-face", "for", "import", "include", "media", "mixin", "page", "warn", "while", ]; var PSEUDO_SELECTORS = [ "after", "before", "first-letter", "first-line", "active", "first-child", "focus", "hover", "lang", "link", "visited", ]; var TAGS = [ "a", "abbr", "address", "article", "aside", "audio", "b", "blockquote", "body", "button", "canvas", "caption", "cite", "code", "dd", "del", "details", "dfn", "div", "dl", "dt", "em", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "html", "i", "iframe", "img", "input", "ins", "kbd", "label", "legend", "li", "mark", "menu", "nav", "object", "ol", "p", "q", "quote", "samp", "section", "span", "strong", "summary", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "tr", "ul", "var", "video", ]; var TAG_END = "[\\.\\s\\n\\[\\:,]"; var ATTRIBUTES = [ "align-content", "align-items", "align-self", "animation", "animation-delay", "animation-direction", "animation-duration", "animation-fill-mode", "animation-iteration-count", "animation-name", "animation-play-state", "animation-timing-function", "auto", "backface-visibility", "background", "background-attachment", "background-clip", "background-color", "background-image", "background-origin", "background-position", "background-repeat", "background-size", "border", "border-bottom", "border-bottom-color", "border-bottom-left-radius", "border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-collapse", "border-color", "border-image", "border-image-outset", "border-image-repeat", "border-image-slice", "border-image-source", "border-image-width", "border-left", "border-left-color", "border-left-style", "border-left-width", "border-radius", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-spacing", "border-style", "border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", "border-width", "bottom", "box-decoration-break", "box-shadow", "box-sizing", "break-after", "break-before", "break-inside", "caption-side", "clear", "clip", "clip-path", "color", "column-count", "column-fill", "column-gap", "column-rule", "column-rule-color", "column-rule-style", "column-rule-width", "column-span", "column-width", "columns", "content", "counter-increment", "counter-reset", "cursor", "direction", "display", "empty-cells", "filter", "flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap", "float", "font", "font-family", "font-feature-settings", "font-kerning", "font-language-override", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-variant-ligatures", "font-weight", "height", "hyphens", "icon", "image-orientation", "image-rendering", "image-resolution", "ime-mode", "inherit", "initial", "justify-content", "left", "letter-spacing", "line-height", "list-style", "list-style-image", "list-style-position", "list-style-type", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "marks", "mask", "max-height", "max-width", "min-height", "min-width", "nav-down", "nav-index", "nav-left", "nav-right", "nav-up", "none", "normal", "object-fit", "object-position", "opacity", "order", "orphans", "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "overflow", "overflow-wrap", "overflow-x", "overflow-y", "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "page-break-after", "page-break-before", "page-break-inside", "perspective", "perspective-origin", "pointer-events", "position", "quotes", "resize", "right", "tab-size", "table-layout", "text-align", "text-align-last", "text-decoration", "text-decoration-color", "text-decoration-line", "text-decoration-style", "text-indent", "text-overflow", "text-rendering", "text-shadow", "text-transform", "text-underline-position", "top", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "unicode-bidi", "vertical-align", "visibility", "white-space", "widows", "width", "word-break", "word-spacing", "word-wrap", "z-index", ]; // illegals var ILLEGAL = [ "\\?", "(\\bReturn\\b)", // monkey "(\\bEnd\\b)", // monkey "(\\bend\\b)", // vbscript "(\\bdef\\b)", // gradle ";", // a whole lot of languages "#\\s", // markdown "\\*\\s", // markdown "===\\s", // markdown "\\|", "%", // prolog ]; return { aliases: ["styl"], case_insensitive: false, keywords: "if else for in", illegal: "(" + ILLEGAL.join("|") + ")", contains: [ // strings hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, // comments hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, // hex colors HEX_COLOR, // class tag { begin: "\\.[a-zA-Z][a-zA-Z0-9_-]*" + TAG_END, returnBegin: true, contains: [ { className: "selector-class", begin: "\\.[a-zA-Z][a-zA-Z0-9_-]*", }, ], }, // id tag { begin: "\\#[a-zA-Z][a-zA-Z0-9_-]*" + TAG_END, returnBegin: true, contains: [ { className: "selector-id", begin: "\\#[a-zA-Z][a-zA-Z0-9_-]*", }, ], }, // tags { begin: "\\b(" + TAGS.join("|") + ")" + TAG_END, returnBegin: true, contains: [ { className: "selector-tag", begin: "\\b[a-zA-Z][a-zA-Z0-9_-]*", }, ], }, // psuedo selectors { begin: "&?:?:\\b(" + PSEUDO_SELECTORS.join("|") + ")" + TAG_END, }, // @ keywords { begin: "\@(" + AT_KEYWORDS.join("|") + ")\\b", }, // variables VARIABLE, // dimension hljs.CSS_NUMBER_MODE, // number hljs.NUMBER_MODE, // functions // - only from beginning of line + whitespace { className: "function", begin: "^[a-zA-Z][a-zA-Z0-9_\-]*\\(.*\\)", illegal: "[\\n]", returnBegin: true, contains: [ { className: "title", begin: "\\b[a-zA-Z][a-zA-Z0-9_\-]*", }, { className: "params", begin: /\(/, end: /\)/, contains: [ HEX_COLOR, VARIABLE, hljs.APOS_STRING_MODE, hljs.CSS_NUMBER_MODE, hljs.NUMBER_MODE, hljs.QUOTE_STRING_MODE, ], }, ], }, // attributes // - only from beginning of line + whitespace // - must have whitespace after it { className: "attribute", begin: "\\b(" + ATTRIBUTES.reverse().join("|") + ")\\b", starts: { // value container end: /;|$/, contains: [ HEX_COLOR, VARIABLE, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.CSS_NUMBER_MODE, hljs.NUMBER_MODE, hljs.C_BLOCK_COMMENT_MODE, ], illegal: /\./, relevance: 0, }, }, ], }; }, }, { name: "subunit", /* Language: SubUnit Author: Sergey Bronnikov Website: https://bronevichok.ru/ */ create: function (hljs) { var DETAILS = { className: "string", begin: "\\[\n(multipart)?", end: "\\]\n", }; var TIME = { className: "string", begin: "\\d{4}-\\d{2}-\\d{2}(\\s+)\\d{2}:\\d{2}:\\d{2}\.\\d+Z", }; var PROGRESSVALUE = { className: "string", begin: "(\\+|-)\\d+", }; var KEYWORDS = { className: "keyword", relevance: 10, variants: [ { begin: "^(test|testing|success|successful|failure|error|skip|xfail|uxsuccess)(:?)\\s+(test)?", }, { begin: "^progress(:?)(\\s+)?(pop|push)?" }, { begin: "^tags:" }, { begin: "^time:" }, ], }; return { case_insensitive: true, contains: [DETAILS, TIME, PROGRESSVALUE, KEYWORDS], }; }, }, { name: "swift", /* Language: Swift Author: Chris Eidhof Contributors: Nate Cook , Alexander Lichter Category: system */ create: function (hljs) { var SWIFT_KEYWORDS = { keyword: "#available #colorLiteral #column #else #elseif #endif #file " + "#fileLiteral #function #if #imageLiteral #line #selector #sourceLocation " + "_ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype " + "associativity break case catch class continue convenience default defer deinit didSet do " + "dynamic dynamicType else enum extension fallthrough false fileprivate final for func " + "get guard if import in indirect infix init inout internal is lazy left let " + "mutating nil none nonmutating open operator optional override postfix precedence " + "prefix private protocol Protocol public repeat required rethrows return " + "right self Self set static struct subscript super switch throw throws true " + "try try! try? Type typealias unowned var weak where while willSet", literal: "true false nil", built_in: "abs advance alignof alignofValue anyGenerator assert assertionFailure " + "bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC " + "bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros " + "debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords " + "enumerate equal fatalError filter find getBridgedObjectiveCType getVaList " + "indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC " + "isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare " + "map max maxElement min minElement numericCast overlaps partition posix " + "precondition preconditionFailure print println quickSort readLine reduce reflect " + "reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split " + "startsWith stride strideof strideofValue swap toString transcode " + "underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap " + "unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer " + "withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers " + "withUnsafePointer withUnsafePointers withVaList zip", }; var TYPE = { className: "type", begin: "\\b[A-Z][\\w\u00C0-\u02B8']*", relevance: 0, }; // slightly more special to swift var OPTIONAL_USING_TYPE = { className: "type", begin: "\\b[A-Z][\\w\u00C0-\u02B8']*[!?]", }; var BLOCK_COMMENT = hljs.COMMENT("/\\*", "\\*/", { contains: ["self"], }); var SUBST = { className: "subst", begin: /\\\(/, end: "\\)", keywords: SWIFT_KEYWORDS, contains: [], // assigned later }; var STRING = { className: "string", contains: [hljs.BACKSLASH_ESCAPE, SUBST], variants: [ { begin: /"""/, end: /"""/ }, { begin: /"/, end: /"/ }, ], }; var NUMBERS = { className: "number", begin: "\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b", relevance: 0, }; SUBST.contains = [NUMBERS]; return { keywords: SWIFT_KEYWORDS, contains: [ STRING, hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT, OPTIONAL_USING_TYPE, TYPE, NUMBERS, { className: "function", beginKeywords: "func", end: "{", excludeEnd: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][0-9A-Za-z$_]*/, }), { begin: //, }, { className: "params", begin: /\(/, end: /\)/, endsParent: true, keywords: SWIFT_KEYWORDS, contains: [ "self", NUMBERS, STRING, hljs.C_BLOCK_COMMENT_MODE, { begin: ":" }, // relevance booster ], illegal: /["']/, }, ], illegal: /\[|%/, }, { className: "class", beginKeywords: "struct protocol class extension enum", keywords: SWIFT_KEYWORDS, end: "\\{", excludeEnd: true, contains: [ hljs.inherit(hljs.TITLE_MODE, { begin: /[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/, }), ], }, { className: "meta", // @attributes begin: "(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|" + "@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|" + "@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|" + "@infix|@prefix|@postfix|@autoclosure|@testable|@available|" + "@nonobjc|@NSApplicationMain|@UIApplicationMain)", }, { beginKeywords: "import", end: /$/, contains: [hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT], }, ], }; }, }, { name: "taggerscript", /* Language: Tagger Script Author: Philipp Wolfer Description: Syntax Highlighting for the Tagger Script as used by MusicBrainz Picard. */ create: function (hljs) { var COMMENT = { className: "comment", begin: /\$noop\(/, end: /\)/, contains: [ { begin: /\(/, end: /\)/, contains: [ "self", { begin: /\\./, }, ], }, ], relevance: 10, }; var FUNCTION = { className: "keyword", begin: /\$(?!noop)[a-zA-Z][_a-zA-Z0-9]*/, end: /\(/, excludeEnd: true, }; var VARIABLE = { className: "variable", begin: /%[_a-zA-Z0-9:]*/, end: "%", }; var ESCAPE_SEQUENCE = { className: "symbol", begin: /\\./, }; return { contains: [COMMENT, FUNCTION, VARIABLE, ESCAPE_SEQUENCE], }; }, }, { name: "tap", /* Language: Test Anything Protocol Requires: yaml.js Author: Sergey Bronnikov Website: https://bronevichok.ru/ */ create: function (hljs) { return { case_insensitive: true, contains: [ hljs.HASH_COMMENT_MODE, // version of format and total amount of testcases { className: "meta", variants: [ { begin: "^TAP version (\\d+)$" }, { begin: "^1\\.\\.(\\d+)$" }, ], }, // YAML block { begin: "(\s+)?---$", end: "\\.\\.\\.$", subLanguage: "yaml", relevance: 0, }, // testcase number { className: "number", begin: " (\\d+) ", }, // testcase status and description { className: "symbol", variants: [{ begin: "^ok" }, { begin: "^not ok" }], }, ], }; }, }, { name: "tcl", /* Language: Tcl Author: Radek Liska */ create: function (hljs) { return { aliases: ["tk"], keywords: "after append apply array auto_execok auto_import auto_load auto_mkindex " + "auto_mkindex_old auto_qualify auto_reset bgerror binary break catch cd chan clock " + "close concat continue dde dict encoding eof error eval exec exit expr fblocked " + "fconfigure fcopy file fileevent filename flush for foreach format gets glob global " + "history http if incr info interp join lappend|10 lassign|10 lindex|10 linsert|10 list " + "llength|10 load lrange|10 lrepeat|10 lreplace|10 lreverse|10 lsearch|10 lset|10 lsort|10 " + "mathfunc mathop memory msgcat namespace open package parray pid pkg::create pkg_mkIndex " + "platform platform::shell proc puts pwd read refchan regexp registry regsub|10 rename " + "return safe scan seek set socket source split string subst switch tcl_endOfWord " + "tcl_findLibrary tcl_startOfNextWord tcl_startOfPreviousWord tcl_wordBreakAfter " + "tcl_wordBreakBefore tcltest tclvars tell time tm trace unknown unload unset update " + "uplevel upvar variable vwait while", contains: [ hljs.COMMENT(";[ \\t]*#", "$"), hljs.COMMENT("^[ \\t]*#", "$"), { beginKeywords: "proc", end: "[\\{]", excludeEnd: true, contains: [ { className: "title", begin: "[ \\t\\n\\r]+(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*", end: "[ \\t\\n\\r]", endsWithParent: true, excludeEnd: true, }, ], }, { excludeEnd: true, variants: [ { begin: "\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*\\(([a-zA-Z0-9_])*\\)", end: "[^a-zA-Z0-9_\\}\\$]", }, { begin: "\\$(\\{)?(::)?[a-zA-Z_]((::)?[a-zA-Z0-9_])*", end: "(\\))?[^a-zA-Z0-9_\\}\\$]", }, ], }, { className: "string", contains: [hljs.BACKSLASH_ESCAPE], variants: [ hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null }), ], }, { className: "number", variants: [hljs.BINARY_NUMBER_MODE, hljs.C_NUMBER_MODE], }, ], }; }, }, { name: "tex", /* Language: TeX Author: Vladimir Moskva Website: http://fulc.ru/ Category: markup */ create: function (hljs) { var COMMAND = { className: "tag", begin: /\\/, relevance: 0, contains: [ { className: "name", variants: [ { begin: /[a-zA-Z\u0430-\u044f\u0410-\u042f]+[*]?/ }, { begin: /[^a-zA-Z\u0430-\u044f\u0410-\u042f0-9]/ }, ], starts: { endsWithParent: true, relevance: 0, contains: [ { className: "string", // because it looks like attributes in HTML tags variants: [ { begin: /\[/, end: /\]/ }, { begin: /\{/, end: /\}/ }, ], }, { begin: /\s*=\s*/, endsWithParent: true, relevance: 0, contains: [ { className: "number", begin: /-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/, }, ], }, ], }, }, ], }; return { contains: [ COMMAND, { className: "formula", contains: [COMMAND], relevance: 0, variants: [ { begin: /\$\$/, end: /\$\$/ }, { begin: /\$/, end: /\$/ }, ], }, hljs.COMMENT("%", "$", { relevance: 0, }), ], }; }, }, { name: "thrift", /* Language: Thrift Author: Oleg Efimov Description: Thrift message definition format Category: protocols */ create: function (hljs) { var BUILT_IN_TYPES = "bool byte i16 i32 i64 double string binary"; return { keywords: { keyword: "namespace const typedef struct enum service exception void oneway set list map required optional", built_in: BUILT_IN_TYPES, literal: "true false", }, contains: [ hljs.QUOTE_STRING_MODE, hljs.NUMBER_MODE, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "class", beginKeywords: "struct enum service exception", end: /\{/, illegal: /\n/, contains: [ hljs.inherit(hljs.TITLE_MODE, { starts: { endsWithParent: true, excludeEnd: true }, // hack: eating everything after the first title }), ], }, { begin: "\\b(set|list|map)\\s*<", end: ">", keywords: BUILT_IN_TYPES, contains: ["self"], }, ], }; }, }, { name: "tp", /* Language: TP Author: Jay Strybis Description: FANUC TP programming language (TPP). */ create: function (hljs) { var TPID = { className: "number", begin: "[1-9][0-9]*" /* no leading zeros */, relevance: 0, }; var TPLABEL = { className: "symbol", begin: ":[^\\]]+", }; var TPDATA = { className: "built_in", begin: "(AR|P|PAYLOAD|PR|R|SR|RSR|LBL|VR|UALM|MESSAGE|UTOOL|UFRAME|TIMER|" + "TIMER_OVERFLOW|JOINT_MAX_SPEED|RESUME_PROG|DIAG_REC)\\[", end: "\\]", contains: ["self", TPID, TPLABEL], }; var TPIO = { className: "built_in", begin: "(AI|AO|DI|DO|F|RI|RO|UI|UO|GI|GO|SI|SO)\\[", end: "\\]", contains: [ "self", TPID, hljs.QUOTE_STRING_MODE /* for pos section at bottom */, TPLABEL, ], }; return { keywords: { keyword: "ABORT ACC ADJUST AND AP_LD BREAK CALL CNT COL CONDITION CONFIG DA DB " + "DIV DETECT ELSE END ENDFOR ERR_NUM ERROR_PROG FINE FOR GP GUARD INC " + "IF JMP LINEAR_MAX_SPEED LOCK MOD MONITOR OFFSET Offset OR OVERRIDE " + "PAUSE PREG PTH RT_LD RUN SELECT SKIP Skip TA TB TO TOOL_OFFSET " + "Tool_Offset UF UT UFRAME_NUM UTOOL_NUM UNLOCK WAIT X Y Z W P R STRLEN " + "SUBSTR FINDSTR VOFFSET PROG ATTR MN POS", literal: "ON OFF max_speed LPOS JPOS ENABLE DISABLE START STOP RESET", }, contains: [ TPDATA, TPIO, { className: "keyword", begin: "/(PROG|ATTR|MN|POS|END)\\b", }, { /* this is for cases like ,CALL */ className: "keyword", begin: "(CALL|RUN|POINT_LOGIC|LBL)\\b", }, { /* this is for cases like CNT100 where the default lexemes do not * separate the keyword and the number */ className: "keyword", begin: "\\b(ACC|CNT|Skip|Offset|PSPD|RT_LD|AP_LD|Tool_Offset)", }, { /* to catch numbers that do not have a word boundary on the left */ className: "number", begin: "\\d+(sec|msec|mm/sec|cm/min|inch/min|deg/sec|mm|in|cm)?\\b", relevance: 0, }, hljs.COMMENT("//", "[;$]"), hljs.COMMENT("!", "[;$]"), hljs.COMMENT("--eg:", "$"), hljs.QUOTE_STRING_MODE, { className: "string", begin: "'", end: "'", }, hljs.C_NUMBER_MODE, { className: "variable", begin: "\\$[A-Za-z0-9_]+", }, ], }; }, }, { name: "twig", /* Language: Twig Requires: xml.js Author: Luke Holder Description: Twig is a templating language for PHP Category: template */ create: function (hljs) { var PARAMS = { className: "params", begin: "\\(", end: "\\)", }; var FUNCTION_NAMES = "attribute block constant cycle date dump include " + "max min parent random range source template_from_string"; var FUNCTIONS = { beginKeywords: FUNCTION_NAMES, keywords: { name: FUNCTION_NAMES }, relevance: 0, contains: [PARAMS], }; var FILTER = { begin: /\|[A-Za-z_]+:?/, keywords: "abs batch capitalize convert_encoding date date_modify default " + "escape first format join json_encode keys last length lower " + "merge nl2br number_format raw replace reverse round slice sort split " + "striptags title trim upper url_encode", contains: [FUNCTIONS], }; var TAGS = "autoescape block do embed extends filter flush for " + "if import include macro sandbox set spaceless use verbatim"; TAGS = TAGS + " " + TAGS.split(" ") .map(function (t) { return "end" + t; }) .join(" "); return { aliases: ["craftcms"], case_insensitive: true, subLanguage: "xml", contains: [ hljs.COMMENT(/\{#/, /#}/), { className: "template-tag", begin: /\{%/, end: /%}/, contains: [ { className: "name", begin: /\w+/, keywords: TAGS, starts: { endsWithParent: true, contains: [FILTER, FUNCTIONS], relevance: 0, }, }, ], }, { className: "template-variable", begin: /\{\{/, end: /}}/, contains: ["self", FILTER, FUNCTIONS], }, ], }; }, }, { name: "typescript", /* Language: TypeScript Author: Panu Horsmalahti Contributors: Ike Ku Description: TypeScript is a strict superset of JavaScript Category: scripting */ create: function (hljs) { var JS_IDENT_RE = "[A-Za-z$_][0-9A-Za-z$_]*"; var KEYWORDS = { keyword: "in if for while finally var new function do return void else break catch " + "instanceof with throw case default try this switch continue typeof delete " + "let yield const class public private protected get set super " + "static implements enum export import declare type namespace abstract " + "as from extends async await", literal: "true false null undefined NaN Infinity", built_in: "eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent " + "encodeURI encodeURIComponent escape unescape Object Function Boolean Error " + "EvalError InternalError RangeError ReferenceError StopIteration SyntaxError " + "TypeError URIError Number Math Date String RegExp Array Float32Array " + "Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array " + "Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require " + "module console window document any number boolean string void Promise", }; var DECORATOR = { className: "meta", begin: "@" + JS_IDENT_RE, }; var ARGS = { begin: "\\(", end: /\)/, keywords: KEYWORDS, contains: [ "self", hljs.QUOTE_STRING_MODE, hljs.APOS_STRING_MODE, hljs.NUMBER_MODE, ], }; var PARAMS = { className: "params", begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, keywords: KEYWORDS, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, DECORATOR, ARGS, ], }; var NUMBER = { className: "number", variants: [ { begin: "\\b(0[bB][01]+)" }, { begin: "\\b(0[oO][0-7]+)" }, { begin: hljs.C_NUMBER_RE }, ], relevance: 0, }; var SUBST = { className: "subst", begin: "\\$\\{", end: "\\}", keywords: KEYWORDS, contains: [], // defined later }; var HTML_TEMPLATE = { begin: "html`", end: "", starts: { end: "`", returnEnd: false, contains: [hljs.BACKSLASH_ESCAPE, SUBST], subLanguage: "xml", }, }; var CSS_TEMPLATE = { begin: "css`", end: "", starts: { end: "`", returnEnd: false, contains: [hljs.BACKSLASH_ESCAPE, SUBST], subLanguage: "css", }, }; var TEMPLATE_STRING = { className: "string", begin: "`", end: "`", contains: [hljs.BACKSLASH_ESCAPE, SUBST], }; SUBST.contains = [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, HTML_TEMPLATE, CSS_TEMPLATE, TEMPLATE_STRING, NUMBER, hljs.REGEXP_MODE, ]; return { aliases: ["ts"], keywords: KEYWORDS, contains: [ { className: "meta", begin: /^\s*['"]use strict['"]/, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, HTML_TEMPLATE, CSS_TEMPLATE, TEMPLATE_STRING, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, NUMBER, { // "value" container begin: "(" + hljs.RE_STARTERS_RE + "|\\b(case|return|throw)\\b)\\s*", keywords: "return throw case", contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, hljs.REGEXP_MODE, { className: "function", begin: "(\\(.*?\\)|" + hljs.IDENT_RE + ")\\s*=>", returnBegin: true, end: "\\s*=>", contains: [ { className: "params", variants: [ { begin: hljs.IDENT_RE, }, { begin: /\(\s*\)/, }, { begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, keywords: KEYWORDS, contains: [ "self", hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, ], }, ], }, ], }, ], relevance: 0, }, { className: "function", begin: "function", end: /[\{;]/, excludeEnd: true, keywords: KEYWORDS, contains: [ "self", hljs.inherit(hljs.TITLE_MODE, { begin: JS_IDENT_RE }), PARAMS, ], illegal: /%/, relevance: 0, // () => {} is more typical in TypeScript }, { beginKeywords: "constructor", end: /\{/, excludeEnd: true, contains: ["self", PARAMS], }, { // prevent references like module.id from being higlighted as module definitions begin: /module\./, keywords: { built_in: "module" }, relevance: 0, }, { beginKeywords: "module", end: /\{/, excludeEnd: true, }, { beginKeywords: "interface", end: /\{/, excludeEnd: true, keywords: "interface extends", }, { begin: /\$[(.]/, // relevance booster for a pattern common to JS libs: `$(something)` and `$.something` }, { begin: "\\." + hljs.IDENT_RE, relevance: 0, // hack: prevents detection of keywords after dots }, DECORATOR, ARGS, ], }; }, }, { name: "vala", /* Language: Vala Author: Antono Vasiljev Description: Vala is a new programming language that aims to bring modern programming language features to GNOME developers without imposing any additional runtime requirements and without using a different ABI compared to applications and libraries written in C. */ create: function (hljs) { return { keywords: { keyword: // Value types "char uchar unichar int uint long ulong short ushort int8 int16 int32 int64 uint8 " + "uint16 uint32 uint64 float double bool struct enum string void " + // Reference types "weak unowned owned " + // Modifiers "async signal static abstract interface override virtual delegate " + // Control Structures "if while do for foreach else switch case break default return try catch " + // Visibility "public private protected internal " + // Other "using new this get set const stdout stdin stderr var", built_in: "DBus GLib CCode Gee Object Gtk Posix", literal: "false true null", }, contains: [ { className: "class", beginKeywords: "class interface namespace", end: "{", excludeEnd: true, illegal: "[^,:\\n\\s\\.]", contains: [hljs.UNDERSCORE_TITLE_MODE], }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, { className: "string", begin: '"""', end: '"""', relevance: 5, }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, hljs.C_NUMBER_MODE, { className: "meta", begin: "^#", end: "$", relevance: 2, }, ], }; }, }, { name: "vbnet", /* Language: VB.NET Author: Poren Chiang */ create: function (hljs) { return { aliases: ["vb"], case_insensitive: true, keywords: { keyword: "addhandler addressof alias and andalso aggregate ansi as assembly auto binary by byref byval " /* a-b */ + "call case catch class compare const continue custom declare default delegate dim distinct do " /* c-d */ + "each equals else elseif end enum erase error event exit explicit finally for friend from function " /* e-f */ + "get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue " /* g-i */ + "join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass " /* j-m */ + "namespace narrowing new next not notinheritable notoverridable " /* n */ + "of off on operator option optional or order orelse overloads overridable overrides " /* o */ + "paramarray partial preserve private property protected public " /* p */ + "raiseevent readonly redim rem removehandler resume return " /* r */ + "select set shadows shared skip static step stop structure strict sub synclock " /* s */ + "take text then throw to try unicode until using when where while widening with withevents writeonly xor" /* t-x */, built_in: "boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype " /* b-c */ + "date decimal directcast double gettype getxmlnamespace iif integer long object " /* d-o */ + "sbyte short single string trycast typeof uinteger ulong ushort" /* s-u */, literal: "true false nothing", }, illegal: "//|{|}|endif|gosub|variant|wend|^\\$ " /* reserved deprecated keywords */, contains: [ hljs.inherit(hljs.QUOTE_STRING_MODE, { contains: [{ begin: '""' }], }), hljs.COMMENT("'", "$", { returnBegin: true, contains: [ { className: "doctag", begin: "'''|", contains: [hljs.PHRASAL_WORDS_MODE], }, { className: "doctag", begin: "", contains: [hljs.PHRASAL_WORDS_MODE], }, ], }), hljs.C_NUMBER_MODE, { className: "meta", begin: "#", end: "$", keywords: { "meta-keyword": "if else elseif end region externalsource", }, }, ], }; }, }, { name: "vbscript-html", /* Language: VBScript in HTML Requires: xml.js, vbscript.js Author: Ivan Sagalaev Description: "Bridge" language defining fragments of VBScript in HTML within <% .. %> Category: scripting */ create: function (hljs) { return { subLanguage: "xml", contains: [ { begin: "<%", end: "%>", subLanguage: "vbscript", }, ], }; }, }, { name: "vbscript", /* Language: VBScript Author: Nikita Ledyaev Contributors: Michal Gabrukiewicz Category: scripting */ create: function (hljs) { return { aliases: ["vbs"], case_insensitive: true, keywords: { keyword: "call class const dim do loop erase execute executeglobal exit for each next function " + "if then else on error option explicit new private property let get public randomize " + "redim rem select case set stop sub while wend with end to elseif is or xor and not " + "class_initialize class_terminate default preserve in me byval byref step resume goto", built_in: "lcase month vartype instrrev ubound setlocale getobject rgb getref string " + "weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency " + "conversions csng timevalue second year space abs clng timeserial fixs len asc " + "isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate " + "instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex " + "chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim " + "strcomp int createobject loadpicture tan formatnumber mid scriptenginebuildversion " + "scriptengine split scriptengineminorversion cint sin datepart ltrim sqr " + "scriptenginemajorversion time derived eval date formatpercent exp inputbox left ascw " + "chrw regexp server response request cstr err", literal: "true false null nothing empty", }, illegal: "//", contains: [ hljs.inherit(hljs.QUOTE_STRING_MODE, { contains: [{ begin: '""' }], }), hljs.COMMENT(/'/, /$/, { relevance: 0, }), hljs.C_NUMBER_MODE, ], }; }, }, { name: "verilog", /* Language: Verilog Author: Jon Evans Contributors: Boone Severson Description: Verilog is a hardware description language used in electronic design automation to describe digital and mixed-signal systems. This highlighter supports Verilog and SystemVerilog through IEEE 1800-2012. */ create: function (hljs) { var SV_KEYWORDS = { keyword: "accept_on alias always always_comb always_ff always_latch and assert assign " + "assume automatic before begin bind bins binsof bit break buf|0 bufif0 bufif1 " + "byte case casex casez cell chandle checker class clocking cmos config const " + "constraint context continue cover covergroup coverpoint cross deassign default " + "defparam design disable dist do edge else end endcase endchecker endclass " + "endclocking endconfig endfunction endgenerate endgroup endinterface endmodule " + "endpackage endprimitive endprogram endproperty endspecify endsequence endtable " + "endtask enum event eventually expect export extends extern final first_match for " + "force foreach forever fork forkjoin function generate|5 genvar global highz0 highz1 " + "if iff ifnone ignore_bins illegal_bins implements implies import incdir include " + "initial inout input inside instance int integer interconnect interface intersect " + "join join_any join_none large let liblist library local localparam logic longint " + "macromodule matches medium modport module nand negedge nettype new nexttime nmos " + "nor noshowcancelled not notif0 notif1 or output package packed parameter pmos " + "posedge primitive priority program property protected pull0 pull1 pulldown pullup " + "pulsestyle_ondetect pulsestyle_onevent pure rand randc randcase randsequence rcmos " + "real realtime ref reg reject_on release repeat restrict return rnmos rpmos rtran " + "rtranif0 rtranif1 s_always s_eventually s_nexttime s_until s_until_with scalared " + "sequence shortint shortreal showcancelled signed small soft solve specify specparam " + "static string strong strong0 strong1 struct super supply0 supply1 sync_accept_on " + "sync_reject_on table tagged task this throughout time timeprecision timeunit tran " + "tranif0 tranif1 tri tri0 tri1 triand trior trireg type typedef union unique unique0 " + "unsigned until until_with untyped use uwire var vectored virtual void wait wait_order " + "wand weak weak0 weak1 while wildcard wire with within wor xnor xor", literal: "null", built_in: "$finish $stop $exit $fatal $error $warning $info $realtime $time $printtimescale " + "$bitstoreal $bitstoshortreal $itor $signed $cast $bits $stime $timeformat " + "$realtobits $shortrealtobits $rtoi $unsigned $asserton $assertkill $assertpasson " + "$assertfailon $assertnonvacuouson $assertoff $assertcontrol $assertpassoff " + "$assertfailoff $assertvacuousoff $isunbounded $sampled $fell $changed $past_gclk " + "$fell_gclk $changed_gclk $rising_gclk $steady_gclk $coverage_control " + "$coverage_get $coverage_save $set_coverage_db_name $rose $stable $past " + "$rose_gclk $stable_gclk $future_gclk $falling_gclk $changing_gclk $display " + "$coverage_get_max $coverage_merge $get_coverage $load_coverage_db $typename " + "$unpacked_dimensions $left $low $increment $clog2 $ln $log10 $exp $sqrt $pow " + "$floor $ceil $sin $cos $tan $countbits $onehot $isunknown $fatal $warning " + "$dimensions $right $high $size $asin $acos $atan $atan2 $hypot $sinh $cosh " + "$tanh $asinh $acosh $atanh $countones $onehot0 $error $info $random " + "$dist_chi_square $dist_erlang $dist_exponential $dist_normal $dist_poisson " + "$dist_t $dist_uniform $q_initialize $q_remove $q_exam $async$and$array " + "$async$nand$array $async$or$array $async$nor$array $sync$and$array " + "$sync$nand$array $sync$or$array $sync$nor$array $q_add $q_full $psprintf " + "$async$and$plane $async$nand$plane $async$or$plane $async$nor$plane " + "$sync$and$plane $sync$nand$plane $sync$or$plane $sync$nor$plane $system " + "$display $displayb $displayh $displayo $strobe $strobeb $strobeh $strobeo " + "$write $readmemb $readmemh $writememh $value$plusargs " + "$dumpvars $dumpon $dumplimit $dumpports $dumpportson $dumpportslimit " + "$writeb $writeh $writeo $monitor $monitorb $monitorh $monitoro $writememb " + "$dumpfile $dumpoff $dumpall $dumpflush $dumpportsoff $dumpportsall " + "$dumpportsflush $fclose $fdisplay $fdisplayb $fdisplayh $fdisplayo " + "$fstrobe $fstrobeb $fstrobeh $fstrobeo $swrite $swriteb $swriteh " + "$swriteo $fscanf $fread $fseek $fflush $feof $fopen $fwrite $fwriteb " + "$fwriteh $fwriteo $fmonitor $fmonitorb $fmonitorh $fmonitoro $sformat " + "$sformatf $fgetc $ungetc $fgets $sscanf $rewind $ftell $ferror", }; return { aliases: ["v", "sv", "svh"], case_insensitive: false, keywords: SV_KEYWORDS, lexemes: /[\w\$]+/, contains: [ hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE, hljs.QUOTE_STRING_MODE, { className: "number", contains: [hljs.BACKSLASH_ESCAPE], variants: [ { begin: "\\b((\\d+'(b|h|o|d|B|H|O|D))[0-9xzXZa-fA-F_]+)", }, { begin: "\\B(('(b|h|o|d|B|H|O|D))[0-9xzXZa-fA-F_]+)" }, { begin: "\\b([0-9_])+", relevance: 0 }, ], }, /* parameters to instances */ { className: "variable", variants: [ { begin: "#\\((?!parameter).+\\)" }, { begin: "\\.\\w+", relevance: 0 }, ], }, { className: "meta", begin: "`", end: "$", keywords: { "meta-keyword": "define __FILE__ " + "__LINE__ begin_keywords celldefine default_nettype define " + "else elsif end_keywords endcelldefine endif ifdef ifndef " + "include line nounconnected_drive pragma resetall timescale " + "unconnected_drive undef undefineall", }, relevance: 0, }, ], }; // return }, }, { name: "vhdl", /* Language: VHDL Author: Igor Kalnitsky Contributors: Daniel C.K. Kho , Guillaume Savaton Description: VHDL is a hardware description language used in electronic design automation to describe digital and mixed-signal systems. */ create: function (hljs) { // Regular expression for VHDL numeric literals. // Decimal literal: var INTEGER_RE = "\\d(_|\\d)*"; var EXPONENT_RE = "[eE][-+]?" + INTEGER_RE; var DECIMAL_LITERAL_RE = INTEGER_RE + "(\\." + INTEGER_RE + ")?" + "(" + EXPONENT_RE + ")?"; // Based literal: var BASED_INTEGER_RE = "\\w+"; var BASED_LITERAL_RE = INTEGER_RE + "#" + BASED_INTEGER_RE + "(\\." + BASED_INTEGER_RE + ")?" + "#" + "(" + EXPONENT_RE + ")?"; var NUMBER_RE = "\\b(" + BASED_LITERAL_RE + "|" + DECIMAL_LITERAL_RE + ")"; return { case_insensitive: true, keywords: { keyword: "abs access after alias all and architecture array assert assume assume_guarantee attribute " + "begin block body buffer bus case component configuration constant context cover disconnect " + "downto default else elsif end entity exit fairness file for force function generate " + "generic group guarded if impure in inertial inout is label library linkage literal " + "loop map mod nand new next nor not null of on open or others out package parameter port " + "postponed procedure process property protected pure range record register reject " + "release rem report restrict restrict_guarantee return rol ror select sequence " + "severity shared signal sla sll sra srl strong subtype then to transport type " + "unaffected units until use variable view vmode vprop vunit wait when while with xnor xor", built_in: "boolean bit character " + "integer time delay_length natural positive " + "string bit_vector file_open_kind file_open_status " + "std_logic std_logic_vector unsigned signed boolean_vector integer_vector " + "std_ulogic std_ulogic_vector unresolved_unsigned u_unsigned unresolved_signed u_signed " + "real_vector time_vector", literal: "false true note warning error failure " + // severity_level "line text side width", // textio }, illegal: "{", contains: [ hljs.C_BLOCK_COMMENT_MODE, // VHDL-2008 block commenting. hljs.COMMENT("--", "$"), hljs.QUOTE_STRING_MODE, { className: "number", begin: NUMBER_RE, relevance: 0, }, { className: "string", begin: "'(U|X|0|1|Z|W|L|H|-)'", contains: [hljs.BACKSLASH_ESCAPE], }, { className: "symbol", begin: "'[A-Za-z](_?[A-Za-z0-9])*", contains: [hljs.BACKSLASH_ESCAPE], }, ], }; }, }, { name: "vim", /* Language: Vim Script Author: Jun Yang Description: full keyword and built-in from http://vimdoc.sourceforge.net/htmldoc/ Category: scripting */ create: function (hljs) { return { lexemes: /[!#@\w]+/, keywords: { keyword: // express version except: ! & * < = > !! # @ @@ "N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope " + "cp cpf cq cr cs cst cu cuna cunme cw delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu go gr grepa gu gv ha helpf helpg helpt hi hid his ia iabc if ij il im imapc " + "ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 " + "profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf quita qa rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor " + "so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew " + "tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ " + // full version "Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload " + "bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap " + "cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor " + "endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap " + "imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview " + "lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap " + "nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext " + "ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding " + "scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace " + "startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious " + "trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew " + "vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank", //built in func built_in: "synIDtrans atan2 range matcharg did_filetype asin feedkeys xor argv " + "complete_check add getwinposx getqflist getwinposy screencol " + "clearmatches empty extend getcmdpos mzeval garbagecollect setreg " + "ceil sqrt diff_hlID inputsecret get getfperm getpid filewritable " + "shiftwidth max sinh isdirectory synID system inputrestore winline " + "atan visualmode inputlist tabpagewinnr round getregtype mapcheck " + "hasmapto histdel argidx findfile sha256 exists toupper getcmdline " + "taglist string getmatches bufnr strftime winwidth bufexists " + "strtrans tabpagebuflist setcmdpos remote_read printf setloclist " + "getpos getline bufwinnr float2nr len getcmdtype diff_filler luaeval " + "resolve libcallnr foldclosedend reverse filter has_key bufname " + "str2float strlen setline getcharmod setbufvar index searchpos " + "shellescape undofile foldclosed setqflist buflisted strchars str2nr " + "virtcol floor remove undotree remote_expr winheight gettabwinvar " + "reltime cursor tabpagenr finddir localtime acos getloclist search " + "tanh matchend rename gettabvar strdisplaywidth type abs py3eval " + "setwinvar tolower wildmenumode log10 spellsuggest bufloaded " + "synconcealed nextnonblank server2client complete settabwinvar " + "executable input wincol setmatches getftype hlID inputsave " + "searchpair or screenrow line settabvar histadd deepcopy strpart " + "remote_peek and eval getftime submatch screenchar winsaveview " + "matchadd mkdir screenattr getfontname libcall reltimestr getfsize " + "winnr invert pow getbufline byte2line soundfold repeat fnameescape " + "tagfiles sin strwidth spellbadword trunc maparg log lispindent " + "hostname setpos globpath remote_foreground getchar synIDattr " + "fnamemodify cscope_connection stridx winbufnr indent min " + "complete_add nr2char searchpairpos inputdialog values matchlist " + "items hlexists strridx browsedir expand fmod pathshorten line2byte " + "argc count getwinvar glob foldtextresult getreg foreground cosh " + "matchdelete has char2nr simplify histget searchdecl iconv " + "winrestcmd pumvisible writefile foldlevel haslocaldir keys cos " + "matchstr foldtext histnr tan tempname getcwd byteidx getbufvar " + "islocked escape eventhandler remote_send serverlist winrestview " + "synstack pyeval prevnonblank readfile cindent filereadable changenr " + "exp", }, illegal: /;/, contains: [ hljs.NUMBER_MODE, { className: "string", begin: "'", end: "'", illegal: "\\n", }, /* A double quote can start either a string or a line comment. Strings are ended before the end of a line by another double quote and can contain escaped double-quotes and post-escaped line breaks. Also, any double quote at the beginning of a line is a comment but we don't handle that properly at the moment: any double quote inside will turn them into a string. Handling it properly will require a smarter parser. */ { className: "string", begin: /"(\\"|\n\\|[^"\n])*"/, }, hljs.COMMENT('"', "$"), { className: "variable", begin: /[bwtglsav]:[\w\d_]*/, }, { className: "function", beginKeywords: "function function!", end: "$", relevance: 0, contains: [ hljs.TITLE_MODE, { className: "params", begin: "\\(", end: "\\)", }, ], }, { className: "symbol", begin: /<[\w-]+>/, }, ], }; }, }, { name: "x86asm", /* Language: Intel x86 Assembly Author: innocenat Description: x86 assembly language using Intel's mnemonic and NASM syntax Category: assembler */ create: function (hljs) { return { case_insensitive: true, lexemes: "[.%]?" + hljs.IDENT_RE, keywords: { keyword: "lock rep repe repz repne repnz xaquire xrelease bnd nobnd " + "aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63", built_in: // Instruction pointer "ip eip rip " + // 8-bit registers "al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b " + // 16-bit registers "ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w " + // 32-bit registers "eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d " + // 64-bit registers "rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 " + // Segment registers "cs ds es fs gs ss " + // Floating point stack registers "st st0 st1 st2 st3 st4 st5 st6 st7 " + // MMX Registers "mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 " + // SSE registers "xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 " + "xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 " + // AVX registers "ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 " + "ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 " + // AVX-512F registers "zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 " + "zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 " + // AVX-512F mask registers "k0 k1 k2 k3 k4 k5 k6 k7 " + // Bound (MPX) register "bnd0 bnd1 bnd2 bnd3 " + // Special register "cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 " + // NASM altreg package "r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b " + "r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d " + "r0h r1h r2h r3h " + "r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l " + "db dw dd dq dt ddq do dy dz " + "resb resw resd resq rest resdq reso resy resz " + "incbin equ times " + "byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr", meta: "%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif " + "%if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep " + "%endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment " + ".nolist " + "__FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ " + "__UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend " + "align alignb sectalign daz nodaz up down zero default option assume public " + "bits use16 use32 use64 default section segment absolute extern global common cpu float " + "__utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ " + "__float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ " + "__Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e " + "float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__", }, contains: [ hljs.COMMENT(";", "$", { relevance: 0, }), { className: "number", variants: [ // Float number and x87 BCD { begin: "\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|" + "(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b", relevance: 0, }, // Hex number in $ { begin: "\\$[0-9][0-9A-Fa-f]*", relevance: 0 }, // Number in H,D,T,Q,O,B,Y suffix { begin: "\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b", }, // Number in X,D,T,Q,O,B,Y prefix { begin: "\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b", }, ], }, // Double quote string hljs.QUOTE_STRING_MODE, { className: "string", variants: [ // Single-quoted string { begin: "'", end: "[^\\\\]'" }, // Backquoted string { begin: "`", end: "[^\\\\]`" }, ], relevance: 0, }, { className: "symbol", variants: [ // Global label and local label { begin: "^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)", }, // Macro-local label { begin: "^\\s*%%[A-Za-z0-9_$#@~.?]*:" }, ], relevance: 0, }, // Macro parameter { className: "subst", begin: "%[0-9]+", relevance: 0, }, // Macro parameter { className: "subst", begin: "%!\S+", relevance: 0, }, { className: "meta", begin: /^\s*\.[\w_-]+/, }, ], }; }, }, { name: "xl", /* Language: XL Author: Christophe de Dinechin Description: An extensible programming language, based on parse tree rewriting (http://xlr.sf.net) */ create: function (hljs) { var BUILTIN_MODULES = "ObjectLoader Animate MovieCredits Slides Filters Shading Materials LensFlare Mapping VLCAudioVideo " + "StereoDecoder PointCloud NetworkAccess RemoteControl RegExp ChromaKey Snowfall NodeJS Speech Charts"; var XL_KEYWORDS = { keyword: "if then else do while until for loop import with is as where when by data constant " + "integer real text name boolean symbol infix prefix postfix block tree", literal: "true false nil", built_in: "in mod rem and or xor not abs sign floor ceil sqrt sin cos tan asin " + "acos atan exp expm1 log log2 log10 log1p pi at text_length text_range " + "text_find text_replace contains page slide basic_slide title_slide " + "title subtitle fade_in fade_out fade_at clear_color color line_color " + "line_width texture_wrap texture_transform texture scale_?x scale_?y " + "scale_?z? translate_?x translate_?y translate_?z? rotate_?x rotate_?y " + "rotate_?z? rectangle circle ellipse sphere path line_to move_to " + "quad_to curve_to theme background contents locally time mouse_?x " + "mouse_?y mouse_buttons " + BUILTIN_MODULES, }; var DOUBLE_QUOTE_TEXT = { className: "string", begin: '"', end: '"', illegal: "\\n", }; var SINGLE_QUOTE_TEXT = { className: "string", begin: "'", end: "'", illegal: "\\n", }; var LONG_TEXT = { className: "string", begin: "<<", end: ">>", }; var BASED_NUMBER = { className: "number", begin: "[0-9]+#[0-9A-Z_]+(\\.[0-9-A-Z_]+)?#?([Ee][+-]?[0-9]+)?", }; var IMPORT = { beginKeywords: "import", end: "$", keywords: XL_KEYWORDS, contains: [DOUBLE_QUOTE_TEXT], }; var FUNCTION_DEFINITION = { className: "function", begin: /[a-z][^\n]*->/, returnBegin: true, end: /->/, contains: [ hljs.inherit(hljs.TITLE_MODE, { starts: { endsWithParent: true, keywords: XL_KEYWORDS, }, }), ], }; return { aliases: ["tao"], lexemes: /[a-zA-Z][a-zA-Z0-9_?]*/, keywords: XL_KEYWORDS, contains: [ hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, DOUBLE_QUOTE_TEXT, SINGLE_QUOTE_TEXT, LONG_TEXT, FUNCTION_DEFINITION, IMPORT, BASED_NUMBER, hljs.NUMBER_MODE, ], }; }, }, { name: "xml", /* Language: HTML, XML Category: common */ create: function (hljs) { var XML_IDENT_RE = "[A-Za-z0-9\\._:-]+"; var TAG_INTERNALS = { endsWithParent: true, illegal: /`]+/ }, ], }, ], }, ], }; return { aliases: [ "html", "xhtml", "rss", "atom", "xjb", "xsd", "xsl", "plist", "wsf", ], case_insensitive: true, contains: [ { className: "meta", begin: "", relevance: 10, contains: [{ begin: "\\[", end: "\\]" }], }, hljs.COMMENT("", { relevance: 10, }), { begin: "<\\!\\[CDATA\\[", end: "\\]\\]>", relevance: 10, }, { className: "meta", begin: /<\?xml/, end: /\?>/, relevance: 10, }, { begin: /<\?(php)?/, end: /\?>/, subLanguage: "php", contains: [ // We don't want the php closing tag ?> to close the PHP block when // inside any of the following blocks: { begin: "/\\*", end: "\\*/", skip: true }, { begin: 'b"', end: '"', skip: true }, { begin: "b'", end: "'", skip: true }, hljs.inherit(hljs.APOS_STRING_MODE, { illegal: null, className: null, contains: null, skip: true, }), hljs.inherit(hljs.QUOTE_STRING_MODE, { illegal: null, className: null, contains: null, skip: true, }), ], }, { className: "tag", /* The lookahead pattern (?=...) ensures that 'begin' only matches '|$)", end: ">", keywords: { name: "style" }, contains: [TAG_INTERNALS], starts: { end: "", returnEnd: true, subLanguage: ["css", "xml"], }, }, { className: "tag", // See the comment in the $endif$ $for(css)$ $endfor$ $if(math)$ $math$ $endif$ $for(header-includes)$ $header-includes$ $endfor$ $for(include-before)$ $include-before$ $endfor$
    $body$
    $for(include-after)$ $include-after$ $endfor$ liquidsoap-2.4.2/dune000066400000000000000000000013441513273233300145530ustar00rootroot00000000000000(rule (target liquidsoap.config) (package liquidsoap) (alias install) (enabled_if %{env:LIQUIDSOAP_ENABLE_BUILD_CONFIG=true}) (deps src/libs/stdlib.liq (source_tree src/libs)) (mode (promote (only liquidsoap.config) (until-clean))) (action (progn (echo "\nCongratulation on building liquidsoap! Here are the details of your build and configuration:\n") (run %{bin:liquidsoap} --build-config) (with-stdout-to %{target} (run %{bin:liquidsoap} --opam-config))))) (alias (name default) (deps (alias_rec install))) (alias (name runtest) (deps (alias_rec install))) (alias (name citest) (deps (alias_rec perftest) (alias_rec runtest))) (alias (name doctest) (deps (alias_rec doc))) liquidsoap-2.4.2/dune-project000066400000000000000000000077741513273233300162340ustar00rootroot00000000000000(lang dune 3.18) (using menhir 2.1) (using dune_site 0.1) (name liquidsoap) (source (github savonet/liquidsoap)) (license GPL-2.0-or-later) (authors "The Savonet Team ") (maintainers "The Savonet Team ") (homepage "https://github.com/savonet/liquidsoap") (bug_reports "https://github.com/savonet/liquidsoap/issues") (maintenance_intent "(latest)") (version 2.4.2) (generate_opam_files true) (subst disabled) (opam_file_location inside_opam_directory) (executables_implicit_empty_intf true) (package (name liquidsoap) (depends (ocaml (>= 4.14)) (mm (>= 0.8.6)) (re (and (>= 1.11.0) (< 1.14.0))) curl (camomile (>= 2.0.0)) uri menhirLib (mem_usage (>= 0.1.1)) (metadata (>= 0.3.0)) magic-mime dune-build-info (liquidsoap-lang (= :version)) (ppx_string :build)) (depopts alsa ao bjack camlimages ctypes-foreign dssi faad fdkaac ffmpeg flac frei0r gd graphics inotify irc-client-unix jemalloc ladspa lame lilv lo mad memtrace ogg opus osc-unix portaudio posix-time2 posix-socket pulseaudio prometheus-liquidsoap samplerate shine soundtouch speex sqlite3 srt ssl tls-liquidsoap theora sdl-liquidsoap vorbis yaml) (conflicts (alsa (< 0.3.0)) (ao (< 0.2.0)) (bjack (< 0.1.3)) (camomile (< 1.0.0)) (dssi (< 0.1.3)) (faad (< 0.5.0)) (fdkaac (< 0.3.1)) (ffmpeg (< 1.2.8)) (ffmpeg-avutil (< 1.2.5)) (flac (< 1.0.0)) (frei0r (< 0.1.0)) (inotify (< 1.0)) (ladspa (< 0.2.0)) (lame (< 0.3.7)) (lo (< 0.2.0)) (mad (< 0.5.0)) (magic (< 0.6)) (mirage-crypto-rng (< 0.6.2)) (ogg (< 1.0.0)) (opus (< 0.2.0)) (odoc (< 3.0.0~beta1)) (portaudio (< 0.2.0)) (posix-time2 (< 2.0.2)) (posix-socket (< 2.1.0)) (pulseaudio (< 0.1.4)) (samplerate (< 0.1.5)) (shine (< 0.2.0)) (soundtouch (< 0.1.9)) (speex (< 1.0.0)) (srt (< 0.3.2)) (ssl (< 0.7.0)) (tls (< 1.0.2)) (sdl-liquidsoap (< 2)) (theora (< 1.0.0)) (vorbis (< 1.0.0)) (pandoc :with-doc) (pandoc-include :with-doc)) (synopsis "Swiss-army knife for multimedia streaming") (description "\| Liquidsoap is a powerful and flexible language for describing your "\| streams. It offers a rich collection of operators that you can combine "\| at will, giving you more power than you need for creating or "\| transforming streams. But liquidsoap is still very light and easy to "\| use, in the Unix tradition of simple strong components working "\| together. )) (package (name liquidsoap-lang) (depends (ocaml (>= 4.14)) dune-site (re (>= 1.11.0)) (ppx_string :build) (ppx_hash :build) (sedlex (>= 3.2)) (menhir (>= 20240715)) fileutils xml-light ) (sites (share libs) (share bin) (share cache) (lib_root lib_root)) (synopsis "Liquidsoap language library")) (package (name liquidsoap-js) (depends (ocaml (>= 4.14)) (liquidsoap-lang (= :version)) js_of_ocaml-ppx (js_of_ocaml (>= 5.7.2))) (conflicts (liquidsoap (<> :version))) (synopsis "Liquidsoap language - javascript wrapper")) (package (name liquidsoap-mode) (depends (liquidsoap (= :version))) (synopsis "Liquidosap emacs mode") ) (package (name tls-liquidsoap) (version 1) (allow_empty) (depends tls ca-certs mirage-crypto-rng cstruct) (synopsis "Virtual package install liquidosap dependencies for TLS optional features") ) (package (name prometheus-liquidsoap) (version 2) (allow_empty) (depends prometheus-app cohttp-lwt-unix) (synopsis "Virtual package installing liquidsoap dependencies for prometheus optional features") ) (package (name sdl-liquidsoap) (version 3) (allow_empty) (depends tsdl (tsdl-image (>= 0.3.2)) tsdl-ttf) (synopsis "Virtual package installing liquidsoap dependencies for SDL optional features") ) liquidsoap-2.4.2/liquidsoap000077500000000000000000000003531513273233300157740ustar00rootroot00000000000000#!/bin/sh # shellcheck disable=SC2068 DIR="$(dirname "$0")" export DIR opam exec dune -- exec --display=quiet --no-print-directory --root="$DIR" src/bin/liquidsoap.exe -- --disable-deprecated --stdlib "$DIR"/src/libs/stdlib.liq "$@" liquidsoap-2.4.2/opam/000077500000000000000000000000001513273233300146275ustar00rootroot00000000000000liquidsoap-2.4.2/opam/liquidsoap-js.opam000066400000000000000000000016771513273233300203040ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "2.4.2" synopsis: "Liquidsoap language - javascript wrapper" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "ocaml" {>= "4.14"} "liquidsoap-lang" {= version} "js_of_ocaml-ppx" "js_of_ocaml" {>= "5.7.2"} "odoc" {with-doc} ] conflicts: [ "liquidsoap" {!= version} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] liquidsoap-2.4.2/opam/liquidsoap-lang.opam000066400000000000000000000017221513273233300206000ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "2.4.2" synopsis: "Liquidsoap language library" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "ocaml" {>= "4.14"} "dune-site" "re" {>= "1.11.0"} "ppx_string" {build} "ppx_hash" {build} "sedlex" {>= "3.2"} "menhir" {>= "20240715"} "fileutils" "xml-light" "odoc" {with-doc} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] liquidsoap-2.4.2/opam/liquidsoap-mode.opam000066400000000000000000000021331513273233300206000ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "2.4.2" synopsis: "Liquidosap emacs mode" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "liquidsoap" {= version} "odoc" {with-doc} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] post-messages: [ "This package requires additional configuration for use in editors. Install package 'user-setup', or manually: * for Emacs, add these lines to ~/.emacs: (add-to-list 'load-path \"%{share}%/emacs/site-lisp\") (require 'emacs-mode) " {success & !user-setup:installed} ] liquidsoap-2.4.2/opam/liquidsoap-mode.opam.template000066400000000000000000000004461513273233300224170ustar00rootroot00000000000000post-messages: [ "This package requires additional configuration for use in editors. Install package 'user-setup', or manually: * for Emacs, add these lines to ~/.emacs: (add-to-list 'load-path \"%{share}%/emacs/site-lisp\") (require 'emacs-mode) " {success & !user-setup:installed} ] liquidsoap-2.4.2/opam/liquidsoap.opam000066400000000000000000000073271513273233300176700ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "2.4.2" synopsis: "Swiss-army knife for multimedia streaming" description: """ Liquidsoap is a powerful and flexible language for describing your streams. It offers a rich collection of operators that you can combine at will, giving you more power than you need for creating or transforming streams. But liquidsoap is still very light and easy to use, in the Unix tradition of simple strong components working together. """ maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "ocaml" {>= "4.14"} "mm" {>= "0.8.6"} "re" {>= "1.11.0" & < "1.14.0"} "curl" "camomile" {>= "2.0.0"} "uri" "menhirLib" "mem_usage" {>= "0.1.1"} "metadata" {>= "0.3.0"} "magic-mime" "dune-build-info" "liquidsoap-lang" {= version} "ppx_string" {build} "odoc" {with-doc} ] depopts: [ "alsa" "ao" "bjack" "camlimages" "ctypes-foreign" "dssi" "faad" "fdkaac" "ffmpeg" "flac" "frei0r" "gd" "graphics" "inotify" "irc-client-unix" "jemalloc" "ladspa" "lame" "lilv" "lo" "mad" "memtrace" "ogg" "opus" "osc-unix" "portaudio" "posix-time2" "posix-socket" "pulseaudio" "prometheus-liquidsoap" "samplerate" "shine" "soundtouch" "speex" "sqlite3" "srt" "ssl" "tls-liquidsoap" "theora" "sdl-liquidsoap" "vorbis" "yaml" ] conflicts: [ "alsa" {< "0.3.0"} "ao" {< "0.2.0"} "bjack" {< "0.1.3"} "camomile" {< "1.0.0"} "dssi" {< "0.1.3"} "faad" {< "0.5.0"} "fdkaac" {< "0.3.1"} "ffmpeg" {< "1.2.8"} "ffmpeg-avutil" {< "1.2.5"} "flac" {< "1.0.0"} "frei0r" {< "0.1.0"} "inotify" {< "1.0"} "ladspa" {< "0.2.0"} "lame" {< "0.3.7"} "lo" {< "0.2.0"} "mad" {< "0.5.0"} "magic" {< "0.6"} "mirage-crypto-rng" {< "0.6.2"} "ogg" {< "1.0.0"} "opus" {< "0.2.0"} "odoc" {< "3.0.0~beta1"} "portaudio" {< "0.2.0"} "posix-time2" {< "2.0.2"} "posix-socket" {< "2.1.0"} "pulseaudio" {< "0.1.4"} "samplerate" {< "0.1.5"} "shine" {< "0.2.0"} "soundtouch" {< "0.1.9"} "speex" {< "1.0.0"} "srt" {< "0.3.2"} "ssl" {< "0.7.0"} "tls" {< "1.0.2"} "sdl-liquidsoap" {< "2"} "theora" {< "1.0.0"} "vorbis" {< "1.0.0"} "pandoc" {with-doc} "pandoc-include" {with-doc} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] post-messages: [ """\ We're sorry that your liquidsoap install failed. Check out our installation instructions at: https://www.liquidsoap.info/doc-%{version}%/install.html#opam for more information.""" {failure} "✨ Congratulations on installing liquidsoap! ✨" {success} """\ We noticed that you did not install the ffmpeg package. This package is highly recommended for most users and provides a lot of useful features, including decoding and encoding multiple media format, sending and receiving from various inputs and outputs and more.""" {success & !ffmpeg-enabled} """\ We noticed that you did not install any ssl or tls support. Liquidsoap won't be able to use SSL encryption in its input or output operators. You might want to install one of ssl or tls-liquidsoap package.""" {success & !ssl-enabled & !tls-enabled} ] depexts: ["coreutils"] {os = "macos" & os-distribution = "homebrew"} available: os != "win32" liquidsoap-2.4.2/opam/liquidsoap.opam.template000066400000000000000000000016571513273233300215020ustar00rootroot00000000000000post-messages: [ """\ We're sorry that your liquidsoap install failed. Check out our installation instructions at: https://www.liquidsoap.info/doc-%{version}%/install.html#opam for more information.""" {failure} "✨ Congratulations on installing liquidsoap! ✨" {success} """\ We noticed that you did not install the ffmpeg package. This package is highly recommended for most users and provides a lot of useful features, including decoding and encoding multiple media format, sending and receiving from various inputs and outputs and more.""" {success & !ffmpeg-enabled} """\ We noticed that you did not install any ssl or tls support. Liquidsoap won't be able to use SSL encryption in its input or output operators. You might want to install one of ssl or tls-liquidsoap package.""" {success & !ssl-enabled & !tls-enabled} ] depexts: ["coreutils"] {os = "macos" & os-distribution = "homebrew"} available: os != "win32" liquidsoap-2.4.2/opam/prometheus-liquidsoap.opam000066400000000000000000000015751513273233300220600ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "2" synopsis: "Virtual package installing liquidsoap dependencies for prometheus optional features" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "prometheus-app" "cohttp-lwt-unix" "odoc" {with-doc} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] liquidsoap-2.4.2/opam/sdl-liquidsoap.opam000066400000000000000000000016011513273233300204350ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "3" synopsis: "Virtual package installing liquidsoap dependencies for SDL optional features" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "tsdl" "tsdl-image" {>= "0.3.2"} "tsdl-ttf" "odoc" {with-doc} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] liquidsoap-2.4.2/opam/tls-liquidsoap.opam000066400000000000000000000016031513273233300204570ustar00rootroot00000000000000# This file is generated by dune, edit dune-project instead opam-version: "2.0" version: "1" synopsis: "Virtual package install liquidosap dependencies for TLS optional features" maintainer: ["The Savonet Team "] authors: ["The Savonet Team "] license: "GPL-2.0-or-later" homepage: "https://github.com/savonet/liquidsoap" bug-reports: "https://github.com/savonet/liquidsoap/issues" depends: [ "dune" {>= "3.18"} "tls" "ca-certs" "mirage-crypto-rng" "cstruct" "odoc" {with-doc} ] build: [ [ "dune" "build" "-p" name "-j" jobs "--promote-install-files=false" "@install" "@runtest" {with-test} "@doc" {with-doc} ] ["dune" "install" "-p" name "--create-install-files" name] ] dev-repo: "git+https://github.com/savonet/liquidsoap.git" x-maintenance-intent: ["(latest)"] liquidsoap-2.4.2/scripts/000077500000000000000000000000001513273233300153625ustar00rootroot00000000000000liquidsoap-2.4.2/scripts/.gitignore000066400000000000000000000000311513273233300173440ustar00rootroot00000000000000liquidsoap-completion.el liquidsoap-2.4.2/scripts/bash-completion000066400000000000000000000020541513273233300203720ustar00rootroot00000000000000if [ -z "$BASH_VERSION" ]; then return 0; fi _liquidsoap_add() { IFS=$'\n' _liquidsoap_reply+=("$@") } _liquidsoap_add_f() { local cmd cmd=$1; shift _liquidsoap_add "$($cmd "$@" 2>/dev/null)" } _liquidsoap() { local IFS cmd cur compgen_opt cmd=${COMP_WORDS[1]} cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} compgen_opt=() _liquidsoap_reply=() case "$prev" in "-h"|"-help"|"--help") _liquidsoap_add_f liquidsoap --list-functions ;; *) _liquidsoap_reply+=("-c --check --list-settings -d --daemon --debug -h --help -i --interactive --list-functions --list-functions-md --list-extra-functions-md --list-plugins --list-protocols-md --no-stdlib --parse-only --profile --quiet -r --strict --unsafe --version -v --verbose") compgen_opt+=(-o filenames -f) ;; esac COMPREPLY=($(compgen -W "${_liquidsoap_reply[*]}" "${compgen_opt[@]}" -- "$cur")) unset _liquidsoap_reply return 0 } complete -F _liquidsoap liquidsoap liquidsoap-2.4.2/scripts/dune000066400000000000000000000014301513273233300162360ustar00rootroot00000000000000(executable (name gen_emacs_completion) (link_flags -cclib %{env:LIQ_LDFLAGS=}) (libraries liquidsoap_runtime) (modules gen_emacs_completion)) (rule (target liquidsoap-completions.el) (deps (source_tree ../src/libs) (:stdlib ../src/libs/stdlib.liq) ./gen_emacs_completion.exe) (action (with-stdout-to %{target} (run ./gen_emacs_completion.exe --stdlib %{stdlib})))) (install (section share_root) (package liquidsoap) (files (bash-completion as bash_completion/completions/liquidsoap))) (install (section share_root) (package liquidsoap-mode) (files (liquidsoap-mode.el as emacs/site-lisp/liquidsoap-mode.el) (liquidsoap-completion.el as emacs/site-lisp/liquidsoap-completion.el) (liquidsoap-completions.el as emacs/site-lisp/liquidsoap-completions.el))) liquidsoap-2.4.2/scripts/gen_emacs_completion.ml000066400000000000000000000002761513273233300220730ustar00rootroot00000000000000open Liquidsoap_runtime let () = Main.parse_options (); Main.with_toplevel ~run_streams:false (fun () -> Lang_string.kprint_string ~pager:false Doc.Value.print_emacs_completions) liquidsoap-2.4.2/scripts/liquidsoap-completion.el000066400000000000000000000021731513273233300222300ustar00rootroot00000000000000;; Inspired of ;; http://sixty-north.com/blog/writing-the-simplest-emacs-company-mode-backend ;; http://sixty-north.com/blog/a-more-full-featured-company-mode-backend.html (require 'cl-lib) (require 'company) (require 'liquidsoap-completions) (defun liquidsoap-annotation (s) (format " : %s" (get-text-property 0 :type s)) ) (defun liquidsoap-meta (s) (get-text-property 0 :description s) ) (defun company-liquidsoap-backend (command &optional arg &rest ignored) (interactive (list 'interactive)) (cl-case command (interactive (company-begin-backend 'company-liquidsoap-backend)) (prefix (and (eq major-mode 'liquidsoap-mode) ;; we don't use company-grab-symbol here because we want to match dots (company-grab-line "\\(?:^\\| \\)\\([^ ]*\\)" 1) ) ) (candidates (cl-remove-if-not (lambda (c) (string-prefix-p arg c)) liquidsoap-completions)) (annotation (liquidsoap-annotation arg)) (meta (liquidsoap-meta arg)) ) ) (defun init-liquidsoap-completion () (add-to-list 'company-backends 'company-liquidsoap-backend) ) (provide 'liquidsoap-completion) liquidsoap-2.4.2/scripts/liquidsoap-mode.el000066400000000000000000000064731513273233300210120ustar00rootroot00000000000000;; liquidsoap-mode.el -- Liquidsoap major mode ;; Copyright (C) 2003-2026 Samuel Mimram (require 'liquidsoap-completion) (defvar liquidsoap-font-lock-keywords '( ("#.*" . 'font-lock-comment-face) ("^\\(%ifdef .*\\|%ifndef .*\\|%ifencoder .*\\|%ifnencoder .*\\|%ifversion .*\\|%else .*\\|%endif\\|%include\\)" . 'font-lock-preprocessor-face) ("\\<\\(fun\\|def\\|rec\\|replaces\\|eval\\|begin\\|end\\|if\\|then\\|else\\|elsif\\|let\\|try\\|catch\\|while\\|for\\|in\\|to\\|do\\|open\\)\\>\\|->\\|;" . font-lock-keyword-face) ("\\<\\(and\\|or\\|not\\|mod\\|??\\)\\>\\|:=" . font-lock-builtin-face) ("\\<\\(true\\|false\\)\\>" . font-lock-constant-face) ("\\" st) st) "Syntax table for Liquidsoap major mode.") (defvar liquidsoap-tab-width 2) ;see http://www.emacswiki.org/emacs/ModeTutorial (defun liquidsoap-indent-line () "Indent current Liquidsoap line" (interactive) (beginning-of-line) ; At beginning, no indentation (if (bobp) (indent-line-to 0) ; not-indented is a boolean saying we found a match looking backward ; cur-indent is the current indetation (let ((not-indented t) cur-indent) ; De-indent after end (if (looking-at "^[ \t]*\\(end\\|else\\|elsif\\|then\\|%endif\\|try\\|catch\\)") (progn (save-excursion (forward-line -1) (setq cur-indent (- (current-indentation) liquidsoap-tab-width))) (if (< cur-indent 0) (setq cur-indent 0))) (save-excursion (while not-indented (forward-line -1) ; Indent as much as the last end (if (looking-at "^[ \t]*\\(end\\|%endif\\)") (progn (setq cur-indent (current-indentation)) (setq not-indented nil)) ; Increment if we find that we are in a block (if (looking-at "^[ \t]*\\(def\\|if\\|then\\|else\\|elsif\\|%ifdef\\|.*=$\\|try\\|catch\\|for\\|while\\)") (progn (setq cur-indent (+ (current-indentation) liquidsoap-tab-width)) (setq not-indented nil)) ; Same as previous line otherwise (if (bobp) (setq not-indented nil)) ) ) ) ) ) ; If we didn't see an indentation hint, then allow no indentation (if cur-indent (indent-line-to cur-indent) (indent-line-to 0)) ) ) ) (define-derived-mode liquidsoap-mode fundamental-mode "Liquidsoap" "Major mode for Liquidsoap files." :syntax-table liquidsoap-mode-syntax-table (set (make-local-variable 'comment-start) "#") (set (make-local-variable 'comment-start-skip) "#+\\s-*") (set (make-local-variable 'indent-line-function) 'liquidsoap-indent-line) (set (make-local-variable 'font-lock-defaults) '(liquidsoap-font-lock-keywords)) (setq mode-name "Liquidsoap") ) (add-hook 'liquidsoap-mode-hook 'company-mode) (add-hook 'liquidsoap-mode-hook 'init-liquidsoap-completion) (provide 'liquidsoap-mode) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.liq\\'" . liquidsoap-mode)) liquidsoap-2.4.2/scripts/liquidsoap.xml000066400000000000000000000123421513273233300202600ustar00rootroot00000000000000 ]> def eval replaces rec let fun begin end if then else elsif while for in try catch do open == != and or not ! true false bool int float string @param @category @flag liquidsoap-2.4.2/src/000077500000000000000000000000001513273233300144625ustar00rootroot00000000000000liquidsoap-2.4.2/src/bin/000077500000000000000000000000001513273233300152325ustar00rootroot00000000000000liquidsoap-2.4.2/src/bin/dune000066400000000000000000000007621513273233300161150ustar00rootroot00000000000000(executable (name liquidsoap) (public_name liquidsoap) (package liquidsoap) (link_flags -cclib %{env:LIQ_LDFLAGS=}) (libraries liquidsoap_runtime)) (rule (target liquidsoap-macos-instruments.exe) (enabled_if %{bin-available:codesign}) (deps ./liquidsoap.exe ./instruments.plist) (action (progn (copy ./liquidsoap.exe ./liquidsoap-macos-instruments.exe) (run codesign -s - -v -f --entitlements instruments.plist liquidsoap-macos-instruments.exe)))) liquidsoap-2.4.2/src/bin/instruments.plist000066400000000000000000000003531513273233300207030ustar00rootroot00000000000000com.apple.security.get-task-allow liquidsoap-2.4.2/src/bin/liquidsoap.ml000066400000000000000000000000521513273233300177330ustar00rootroot00000000000000let () = Liquidsoap_runtime.Runner.run () liquidsoap-2.4.2/src/bin/runtime/000077500000000000000000000000001513273233300167155ustar00rootroot00000000000000liquidsoap-2.4.2/src/bin/runtime/build_config.ml000066400000000000000000000131061513273233300216740ustar00rootroot00000000000000open Liquidsoap_lang let deprecated_features = [] let build_config = let path_mode = match Liquidsoap_paths.mode with | `Default -> "default" | `Standalone -> "standalone" | `Posix -> "posix" in let deprecated_features = List.filter_map (fun (name, enabled) -> if enabled then Some (" - " ^ name) else None) deprecated_features in let deprecated_features = if deprecated_features <> [] then Printf.sprintf "\n * Deprecated features:\n%s\n" (String.concat "\n" deprecated_features) else "" in [%string {| * Liquidsoap version : %{Build_config.version} %{deprecated_features} * Compilation options - Release build : %{string_of_bool (not Build_config.is_snapshot)} - Git SHA : %{Option.value ~default:"(none)" Build_config.git_sha} - OCaml version : %{Sys.ocaml_version} - OS type : %{Sys.os_type} - Libs versions : %{Configure.libs_versions ()} - architecture : %{Build_config.architecture} - host : %{Build_config.host} - target : %{Build_config.target} - system : %{Build_config.system} - ocamlopt_cflags : %{Build_config.ocamlopt_cflags} - native_c_compiler : %{Build_config.native_c_compiler} - native_c_libraries : %{Build_config.native_c_libraries} * Configured paths - mode : %{path_mode} - standard library : %{Liquidsoap_paths.liq_libs_dir_descr} - scripted binaries : %{Liquidsoap_paths.bin_dir_descr} - rundir : %{Liquidsoap_paths.rundir_descr} - logdir : %{Liquidsoap_paths.logdir_descr} - user cache : %{Liquidsoap_paths.user_cache_override_descr} (override with $LIQ_CACHE_USER_DIR) - system cache : %{Liquidsoap_paths.system_cache_override_descr} (override with $LIQ_CACHE_SYSTEM_DIR) - camomile files : %{Liquidsoap_paths.camomile_dir_descr} * Supported input formats - MP3 : %{Mad_option.detected} - AAC : %{Faad_option.detected} - Ffmpeg : %{Ffmpeg_option.detected} - Flac (native) : %{Flac_option.detected} - Flac (ogg) : %{Ogg_flac_option.detected} - Opus : %{Opus_option.detected} - Speex : %{Speex_option.detected} - Theora : %{Theora_option.detected} - Vorbis : %{Vorbis_option.detected} - WAV/AIFF : yes (native) * Supported output formats - FDK-AAC : %{Fdkaac_option.detected} - FFmpeg : %{Ffmpeg_option.detected} - MP3 : %{Lame_option.detected} - MP3 (fixed-point) : %{Shine_option.detected} - Flac (native) : %{Flac_option.detected} - Flac (ogg) : %{Ogg_flac_option.detected} - Opus : %{Opus_option.detected} - Speex : %{Speex_option.detected} - Theora : %{Theora_option.detected} - Vorbis : %{Vorbis_option.detected} - WAV/AIFF : yes (native) * Tags - AAC : %{Faad_option.detected} - FFmpeg : %{Ffmpeg_option.detected} - FLAC (native) : %{Flac_option.detected} - Flac (ogg) : %{Ogg_flac_option.detected} - Native decoder : yes - Vorbis : %{Vorbis_option.detected} * Input / output - ALSA : %{Alsa_option.detected} - AO : %{Ao_option.detected} - FFmpeg : %{Ffmpeg_option.detected} - JACK : %{Bjack_option.detected} - NDI : %{Ndi_option.detected} - OSS : %{Oss_option.detected} - Portaudio : %{Portaudio_option.detected} - Pulseaudio : %{Pulseaudio_option.detected} - SRT : %{Srt_option.detected} * Audio manipulation - FFmpeg : %{Ffmpeg_option.detected} - LADSPA : %{Ladspa_option.detected} - Lilv : %{Lilv_option.detected} - Samplerate : %{Samplerate_option.detected} - SoundTouch : %{Soundtouch_option.detected} - StereoTool : %{Stereotool_option.detected} * Video manipulation - camlimages : %{Camlimages_option.detected} - FFmpeg : %{Ffmpeg_option.detected} - frei0r : %{Frei0r_option.detected} - SDL : %{Sdl_option.detected} * MIDI manipulation - DSSI : %{Dssi_option.detected} * Visualization - GD : %{Gd_option.detected} - Graphics : %{Graphics_option.detected} - SDL : %{Sdl_option.detected} * Additional libraries - FFmpeg filters : %{Ffmpeg_option.detected} - FFmpeg devices : %{Ffmpeg_option.detected} - inotify : %{Inotify_option.detected} - irc : %{Irc_option.detected} - jemalloc : %{Jemalloc_option.detected} - lo : %{Lo_option.detected} - memtrace : %{Memtrace_option.detected} - osc : %{Osc_option.detected} - ssl : %{Ssl_option.detected} - sqlite3 : %{Sqlite3_option.detected} - tls : %{Tls_option.detected} - posix-time2 : %{Posix_time_option.detected} - windows service : %{Winsvc_option.detected} - YAML support : %{Yaml_option.detected} - XML playlists : %{Xmlplaylist_option.detected} * Monitoring - Prometheus : %{Prometheus_option.detected} |}] let opam_config = [%string {| opam-version: "2.0" variables { ffmpeg-enabled: %{string_of_bool Ffmpeg_option.enabled} ssl-enabled: %{string_of_bool Ssl_option.enabled} tls-enabled: %{string_of_bool Tls_option.enabled} } |}] liquidsoap-2.4.2/src/bin/runtime/dune000066400000000000000000000004011513273233300175660ustar00rootroot00000000000000(library (name liquidsoap_runtime) (preprocess (pps ppx_string)) (libraries liquidsoap_core liquidsoap_optionals liquidsoap_builtins liquidsoap_tooling (select runner.ml from (winsvc -> runner.winsvc.ml) (-> runner.default.ml)))) liquidsoap-2.4.2/src/bin/runtime/main.ml000066400000000000000000000702161513273233300202010ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) module Runtime = Liquidsoap_lang.Runtime module Environment = Liquidsoap_lang.Environment module Profiler = Liquidsoap_lang.Profiler module Lang_string = Liquidsoap_lang.Lang_string module Queue = Queues.Queue let usage = {|Usage : liquidsoap [OPTION, SCRIPT or EXPR]... - SCRIPT for evaluating a liquidsoap script file, - EXPR for evaluating a scripting expression, - OPTION is one of the options listed below:|} (* Set log to stdout by default. *) let () = Dtools.Log.conf_stdout#set_d (Some true); Dtools.Log.conf_file#set_d (Some false); Dtools.Log.conf_file_path#on_change (fun _ -> Dtools.Log.conf_stdout#set_d (Some false); Dtools.Log.conf_file#set_d (Some true)) (* Are we streaming? *) let run_streams = ref true (* Should we start even without active sources? *) let force_start = Dtools.Conf.bool ~p:(Dtools.Init.conf#plug "force_start") ~d:false "Start liquidsoap even without any active source" ~comments: ["This should be reserved for advanced dynamic uses of liquidsoap."] (* Should we allow to run as root? *) let allow_root = Dtools.Conf.bool ~p:(Configure.conf_init#plug "allow_root") ~d:(Lazy.force Utils.is_docker) "Allow liquidsoap to run as root" ~comments: [ "This should be reserved for advanced dynamic uses of liquidsoap "; "such as running inside an isolated environment like docker."; ] let root_error () = match (allow_root#get, Unix.geteuid (), Unix.getegid ()) with | false, 0, 0 -> Some "root euid & guid (user & group)" | false, 0, _ -> Some "root euid (user)" | false, _, 0 -> Some "root guid (group)" | _ -> None let eval_mode : [ `Parse_only | `Parse_and_type | `Eval | `Eval_toplevel ] ref = ref `Eval let print_json_term = ref false (* Should we error if stdlib is not found? *) let is_relative = Filename.is_relative Sys.argv.(0) (* Should we load the stdlib? *) let stdlib : Liquidsoap_lang.Lang_eval.stdlib ref = ref (if is_relative then `If_present else `Force) (* Shall we use the cache *) let cache = ref true (* Display cache key. *) let show_cache_key = ref false let deprecated = Liquidsoap_lang.Runtime.deprecated (* Shall we start an interactive interpreter (REPL) *) let interactive = ref false let log = Log.make ["main"] let to_load = Queue.create () let eval_script expr = let open Liquidsoap_lang in match !eval_mode with | `Parse_only -> let tm, _ = Runtime.parse expr in if !print_json_term then Printf.printf "%s\n" (Liquidsoap_lang.Json.to_string ~compact:false (Liquidsoap_tooling.Parsed_json.to_json tm)) | `Parse_and_type -> let parsed_term, term = Runtime.parse expr in ignore (Lang.type_term ~name:"main script" ~parsed_term ~stdlib:!stdlib ~trim:true ~deprecated:!deprecated term); if !show_cache_key then Printf.printf "Term cached with key %s\n" (Parsed_term.hash parsed_term) | (`Eval as v) | (`Eval_toplevel as v) -> let toplevel = v = `Eval_toplevel in let stdlib = !stdlib in ignore (Lang.eval ~toplevel ~cache:!cache ~stdlib ~deprecated:!deprecated ~name:"main script" expr); if not (Lang_eval.effective_toplevel ~stdlib toplevel) then Environment.clear_toplevel_environments () (** Evaluate the user script. *) let eval () = Lifecycle.load (); (* Register settings module. Needs to be done last to make sure every dependent OCaml module has been linked. *) Stdlib.Lazy.force Builtins_settings.settings_module; let scripts = Queue.flush_elements to_load in let script = String.concat "\n" (List.map (function | `Stdin -> "%include \"-\"" | `Expr_or_file expr when not (Sys.file_exists expr) -> Printf.sprintf "%s" expr | `Expr_or_file f -> Printf.sprintf "%%include %s" (Lang_string.quote_string f)) scripts) in let t = Sys.time () in try eval_script script; log#important "User script loaded in %.02f seconds." (Sys.time () -. t) with Liquidsoap_lang.Runtime.Error -> Dtools.Init.exec Dtools.Log.stop; flush_all (); exit 1 let with_toplevel = let do_run_streams = run_streams in let do_exit = exit in fun ?(exit = true) ?(run_streams = false) fn -> do_run_streams := run_streams; eval_mode := `Eval_toplevel; eval (); fn (); if exit then do_exit 0 let lang_doc name = with_toplevel (fun () -> try Lang_string.kprint_string ~pager:true (Doc.Value.print name) with Not_found -> Printf.printf "Plugin not found!\n%!") let process_request s = with_toplevel (fun () -> let req = Request.create ~cue_in_metadata:None ~cue_out_metadata:None s in match Request.resolve req with | `Failed -> Printf.eprintf "Request resolution failed.\n"; Request.destroy req; flush_all (); exit 2 | `Timeout -> Printf.eprintf "Request resolution timeout.\n"; Request.destroy req; flush_all (); exit 1 | `Resolved -> let metadata = Request.metadata req |> Frame.Metadata.to_list |> List.to_seq |> Seq.map (fun (k, v) -> (k, if String.length v > 1024 then "" else v)) |> Seq.map (fun (k, v) -> k ^ " = " ^ v) |> List.of_seq |> String.concat "\n" in Printf.printf "%s\n" metadata; Printf.printf "duration = %!"; begin match Request.duration ~metadata:(Request.metadata req) (Option.get (Request.get_filename req)) with | Some f -> Printf.printf "%.2f s\n" f | None -> Printf.printf "n/a\n" | exception Not_found -> Printf.printf "failed\n" end; Request.destroy req) let format_doc s = let prefix = "\t " in let indent = 8 + 2 in let max_width = 80 in let s = String.split_on_char ' ' s in let s = let rec join line width = function | [] -> [line] | hd :: tl -> let hdw = String.length hd in let w = width + 1 + hdw in if w < max_width then join (line ^ " " ^ hd) w tl else line :: join (prefix ^ hd) hdw tl in match s with | hd :: tl -> join (prefix ^ hd) (indent + String.length hd) tl | [] -> [] in String.concat "\n" s let dump_delay = ref 0.1 let options = ref ([ ( ["-"], Arg.Unit (fun () -> Queue.push to_load `Stdin), "Read script from standard input." ); ( ["-r"; "--request"], Arg.String process_request, "Process a file request and print the metadata." ); ( ["-h"], Arg.String lang_doc, "Get help about a scripting value: source, operator, builtin or \ library function, etc." ); ( ["-c"; "--check"], Arg.Unit (fun () -> run_streams := false; eval_mode := `Parse_and_type), "Parse, type-check but do not evaluate the script." ); ( ["-p"; "--parse-only"], Arg.Unit (fun () -> eval_mode := `Parse_only), "Parse script but do not type-check and run them." ); ( ["--cache-stdlib"], Arg.Unit (fun () -> with_toplevel (fun () -> ())), "Generate the standard library cache." ); ( ["--cache-only"], Arg.Unit (fun () -> run_streams := false; eval_mode := `Parse_and_type; show_cache_key := true; cache := true), "Parse, type-check and save script's cache but do no run it." ); (["--no-cache"], Arg.Clear cache, "Disable cache"); ( ["--no-external-plugins"], Arg.Clear Startup.register_external_plugins, "Disable external plugins." ); ( ["--dump-delay"], Arg.Set_float dump_delay, "Set the script's run time before dumping clocks or sources." ); ( ["--describe-sources"], Arg.Unit (fun () -> Lifecycle.after_script_parse ~name:"Disable logs" (fun () -> Dtools.Log.conf_stdout#set false); Lifecycle.on_main_loop ~name:"dump sources and exit" (fun () -> ignore (Thread.create (fun () -> Printf.printf "Running the script for %.02fs.. ⚠️ This can get \ stuck! ⚠️\n\ %!" !dump_delay; Unix.sleepf !dump_delay; Printf.printf "Sources dump:%s\n\n" (Clock.dump_all_sources ()); Printf.printf "Shutting down..\n%!"; Clock.global_stop (); Tutils.shutdown 0) ()))), "Describe the script's sources and shutdown. This an an EXPERIMENTAL \ feature." ); ( ["--describe-clocks"], Arg.Unit (fun () -> Lifecycle.after_script_parse ~name:"Disable logs" (fun () -> Dtools.Log.conf_stdout#set false); Lifecycle.on_main_loop ~name:"dump sources and exit" (fun () -> ignore (Thread.create (fun () -> Printf.printf "Running the script for %.02fs.. ⚠️ This can get \ stuck! ⚠️ \n\ %!" !dump_delay; Unix.sleepf !dump_delay; Printf.printf "Clocks dump:%s\n\n" (Clock.dump ()); Printf.printf "Shutting down..\n%!"; Clock.global_stop (); Tutils.shutdown 0) ()))), "Describe the script's clocks and shutdown. This an an EXPERIMENTAL \ feature." ); ( ["-q"; "--quiet"], Arg.Unit (fun () -> Dtools.Log.conf_stdout#set false), "Do not print log messages on standard output." ); ( ["-v"; "--verbose"], Arg.Unit (fun () -> Dtools.Log.conf_stdout#set true), "Print log messages on standard output." ); ( ["-f"; "--force-start"], Arg.Unit (fun () -> force_start#set true), "For advanced dynamic uses: force liquidsoap to start even when no \ active source is initially defined." ); ( ["--debug"], Arg.Unit (fun () -> Dtools.Log.conf_level#set (max 4 Dtools.Log.conf_level#get)), "Print debugging log messages." ); ( ["--debug-errors"], Arg.Unit (fun () -> Configure.conf_debug_errors#set true), "Debug errors (show stacktrace instead of printing a message)." ); ( ["--debug-subtyping"], Arg.Set Typing.debug_subtyping, "Debug subtyping." ); ( ["--debug-lang"], Arg.Unit (fun () -> Type.debug := true; Configure.conf_debug#set true), "Debug language implementation." ); ( ["--debug-levels"], Arg.Unit (fun () -> Type.debug_levels := true), "Debug typing levels." ); (["--profile"], Arg.Set Term.profile, "Profile execution."); ( ["--strict"], Arg.Set Runtime.strict, "Execute script code in strict mode, issuing fatal errors instead of \ warnings in some cases. Currently: unused variables and ignored \ expressions. " ); ( ["--raw-errors"], Arg.Set Runtime.raw_errors, "In normal executions, exceptions raised during the script are \ translated into user-friendly errors. Use this option to let the \ original error surface. This is useful when debugging." ); ] @ Dtools.Init.args @ Extra_args.args () @ [ ( ["-t"; "--enable-telnet"], Arg.Unit (fun _ -> Server.conf_telnet#set true), "Enable the telnet server." ); ( ["-T"; "--disable-telnet"], Arg.Unit (fun _ -> Server.conf_telnet#set false), "Disable the telnet server." ); ( ["-u"; "--enable-unix-socket"], Arg.Unit (fun _ -> Server.conf_socket#set true), "Enable the unix socket." ); ( ["-U"; "--disable-unix-socket"], Arg.Unit (fun _ -> Server.conf_socket#set false), "Disable the unix socket." ); ( ["--list-plugins"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.kprint_string ~pager:true Doc.Plug.print_string)), Printf.sprintf "List all plugins (builtin scripting values, supported formats and \ protocols)." ); ( ["--list-functions"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.kprint_string ~pager:true Doc.Value.print_functions)), Printf.sprintf "List all functions." ); ( ["--list-functions-by-category"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.kprint_string ~pager:true Doc.Value.print_functions_by_category)), Printf.sprintf "List all functions, sorted by category." ); ( ["--list-functions-md"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.kprint_string ~pager:true (Doc.Value.print_functions_md ~extra:false))), Printf.sprintf "Documentation of all functions in markdown format." ); ( ["--list-functions-json"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.print_string ~pager:true (Json.to_string ~compact:false (Doc.Value.to_json ())))), Printf.sprintf "Documentation of all functions in JSON format." ); ( ["--list-extra-functions-md"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.kprint_string ~pager:true (Doc.Value.print_functions_md ~extra:true))), Printf.sprintf "Documentation of all extra functions in markdown." ); ( ["--list-deprecated-functions-md"], Arg.Unit (fun () -> deprecated := true; with_toplevel (fun () -> Lang_string.kprint_string ~pager:true (Doc.Value.print_functions_md ~deprecated:true))), Printf.sprintf "Documentation of all deprecated functions in markdown." ); ( ["--list-protocols-md"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.kprint_string ~pager:true Doc.Protocol.print_md)), Printf.sprintf "Documentation of all protocols in markdown." ); ( ["--no-stdlib"], Arg.Unit (fun () -> stdlib := `Disabled), Printf.sprintf "Do not load stdlib script libraries (i.e., %s/*.liq)." (Configure.liq_libs_dir ()) ); ( ["--print-json-term"], Arg.Unit (fun () -> run_streams := false; eval_mode := `Parse_only; print_json_term := true), "Parse and output the script as normalized JSON. The JSON format is \ used internally to format code." ); ( ["--stdlib"], Arg.String (fun s -> stdlib := `Override s), "Override the location of the standard library." ); ( ["--disable-deprecated"], Arg.Clear deprecated, "Do not load wrappers for deprecated operators." ); ( ["--no-deprecated"], Arg.Unit (fun _ -> Printf.eprintf "`--no-deprecated` is, ahem.. deprecated! Please use \ `--disable-deprecated`!"; deprecated := false), "Deprecated: use `--disable-deprecated`" ); ( ["--enable-deprecated"], Arg.Set deprecated, "Load wrappers for deprecated operators." ); ( ["-i"], Arg.Set Liquidsoap_lang.Typechecking.display_types, "Display inferred types." ); ( ["--version"], Arg.Unit (fun () -> Printf.printf {|Liquidsoap %s Copyright (c) 2003-2026 Savonet team Liquidsoap is open-source software, released under GNU General Public License. See for more information. |} Configure.version; exit 0), "Display liquidsoap's version." ); ( ["--build-config"], Arg.Unit (fun () -> Printf.printf "%s\n" Build_config.build_config; exit 0), "Display liquidsoap's build configuration." ); ( ["--opam-config"], Arg.Unit (fun () -> Printf.printf "%s\n" Build_config.opam_config; exit 0), "Print out opam's liquidsoap.config, for internal use." ); ( ["--interactive"], Arg.Unit (fun () -> interactive := true; eval_mode := `Eval_toplevel), "Start an interactive interpreter." ); ( ["--"], Arg.Unit (fun () -> Arg.current := Array.length Shebang.argv - 1), "Stop parsing the command-line and pass subsequent items to the \ script." ); ( ["--list-settings"], Arg.Unit (fun () -> with_toplevel (fun () -> Lang_string.print_string ~pager:true (Builtins_settings.print_settings ()))), "Display configuration keys in markdown format." ); ( ["--unsafe"], Arg.Unit (fun () -> Typing.do_occur_check := false), "Faster startup using unsafe features." ); ( ["--safe"], Arg.Unit (fun () -> Typing.do_occur_check := true), "Disable the effects of --unsafe." ); ( ["--no-fallible-check"], Arg.Unit (fun () -> Output.fallibility_check := false), "Ignore fallible sources." ); ]) let expand_options options = let options = List.sort (fun (x, _, _) (y, _, _) -> compare x y) options in List.fold_left (fun l (la, b, c) -> let ta = List.hd (List.rev la) in let expand = List.map (fun a -> (a, b, if a = ta then "\n" ^ format_doc c else " ")) la in l @ expand) [] options (** Just like Arg.parse_argv but with Arg.parse's behavior on errors.. *) let parse argv l f msg = try Arg.parse_argv argv l f msg with | Arg.Bad msg -> Printf.eprintf "%s" msg; exit 2 | Arg.Help msg -> Lang_string.print_string ~pager:true msg; exit 0 let parse_options () = (* Parse command-line, and notably load scripts. *) parse Shebang.argv (expand_options !options) (fun s -> Queue.push to_load (`Expr_or_file s)) usage let absolute s = if String.length s > 0 && s.[0] <> '/' then Sys.getcwd () ^ "/" ^ s else s let () = (* Startup *) Lifecycle.before_init ~name:"main application init" (fun () -> Random.self_init (); (* Set the default values. *) Dtools.Log.conf_file_path#set_d (Some "/
    Theme:
    liquidsoap-2.4.2/src/js/interactive_js.ml000066400000000000000000000066641513273233300204550ustar00rootroot00000000000000open Js_of_ocaml open Liquidsoap_lang let () = (Hooks.liq_libs_dir := fun () -> "/static"); Runtime.load_libs ~stdlib:"stdlib_js.liq" () let execute ~throw expr = (try try Typechecking.check ~throw ~check_top_level_override:false expr; Term.check_unused ~throw ~lib:true expr; let v = Evaluation.eval expr in Format.fprintf Format.str_formatter "- : %a = %s@." Repr.print_type expr.t (Value.to_string v) with exn -> let bt = Printexc.get_raw_backtrace () in throw ~bt exn with | Runtime.Error -> () | exn -> Format.fprintf Format.str_formatter "- : error %s@." (Printexc.to_string exn)); Format.flush_str_formatter () let setOutput s = let output = Js.Unsafe.coerce (Dom_html.getElementById_exn "output") in output##.value := s let onLiqLoaded (version : Js.js_string Js.t) : unit = Js.Unsafe.fun_call (Js.Unsafe.js_expr "onLiqLoaded") [| Js.Unsafe.inject version |] let getLiqCode () = Js.Unsafe.fun_call (Js.Unsafe.js_expr "getLiqCode") [||] let setLiqCode s = Js.Unsafe.fun_call (Js.Unsafe.js_expr "setLiqCode") [| Js.Unsafe.inject s |] let formatLiqCode (s : Js.js_string Js.t) (cb : Js.js_string Js.t -> unit) : unit = Js.Unsafe.fun_call (Js.Unsafe.js_expr "formatLiqCode") [| Js.Unsafe.inject s; Js.Unsafe.inject cb |] let on_format = Dom_html.handler (fun _ -> let expr = Js.to_string (getLiqCode ()) in (try let json = Liquidsoap_tooling.Parsed_json.parse_string ~formatter:Format.str_formatter expr in let json = Liquidsoap_lang.Json.to_string json in formatLiqCode (Js.string json) setLiqCode with _ -> setOutput (Format.flush_str_formatter ())); Js._true) let on_execute = Dom_html.handler (fun _ -> let expr = Js.to_string (getLiqCode ()) in let lexbuf = Sedlexing.Utf8.from_string expr in let throw = Runtime.throw ~formatter:Format.str_formatter ~lexbuf:(Some lexbuf) () in let tokenizer = Preprocessor.mk_tokenizer lexbuf in let parsed_term = Runtime.program tokenizer in let json = Liquidsoap_tooling.Parsed_json.to_json parsed_term in let json = Liquidsoap_lang.Json.to_string json in let term = Term_reducer.to_term ~throw parsed_term in let result = execute ~throw term in formatLiqCode (Js.string json) (fun formatted -> setOutput (Printf.sprintf "%s\n%s\n" (String.trim (Js.to_bytestring formatted)) (String.trim result))); Js._true) let on_clear = Dom_html.handler (fun _ -> let output = Js.Unsafe.coerce (Dom_html.getElementById_exn "output") in output##.value := ""; onLiqLoaded (Js.string Build_config.version); Js._true) let on_load = Dom_html.handler (fun e -> Dom.preventDefault e; onLiqLoaded (Js.string Build_config.version); let execute = Dom_html.getElementById_exn "execute" in ignore (Dom_html.addEventListener execute Dom_events.Typ.click on_execute Js._true); let format = Dom_html.getElementById_exn "format" in ignore (Dom_html.addEventListener format Dom_events.Typ.click on_format Js._true); let clear = Dom_html.getElementById_exn "clear" in ignore (Dom_html.addEventListener clear Dom_events.Typ.click on_clear Js._true); Js._true) let () = Dom_html.window##.onload := on_load liquidsoap-2.4.2/src/js/runtime.js000066400000000000000000000005611513273233300171210ustar00rootroot00000000000000//Provides: caml_thread_initialize function caml_thread_initialize() {} //Provides: caml_mutex_new function caml_mutex_new() {} //Provides: caml_thread_self function caml_thread_self() {} //Provides: caml_thread_id function caml_thread_id() {} //Provides: caml_mutex_lock function caml_mutex_lock() {} //Provides: caml_mutex_unlock function caml_mutex_unlock() {} liquidsoap-2.4.2/src/js/stdlib_js.liq000066400000000000000000000004031513273233300175570ustar00rootroot00000000000000%include "error.liq" %include "null.liq" %include "ref.liq" %include "list.liq" %include "math.liq" %include "getter.liq" %include "predicate.liq" # Implementation for this is in core. def string.id(id) = id end %include "string.liq" %include "utils.liq" liquidsoap-2.4.2/src/lang/000077500000000000000000000000001513273233300154035ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/base/000077500000000000000000000000001513273233300163155ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/base/build_config.mli000066400000000000000000000004151513273233300214440ustar00rootroot00000000000000val is_snapshot : bool val git_sha : string option val version : string val ext_exe : string val architecture : string val host : string val target : string val system : string val ocamlopt_cflags : string val native_c_compiler : string val native_c_libraries : string liquidsoap-2.4.2/src/lang/base/builtins_bool.ml000066400000000000000000000073411513273233300215200ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type op = { name : string; value_op : int -> bool; ground_op : 'a. 'a -> 'a -> bool; } let operators = [ { name = "=="; value_op = (fun c -> c = 0); ground_op = (fun c c' -> c = c'); }; { name = "!="; value_op = (fun c -> c <> 0); ground_op = (fun c c' -> c <> c'); }; { name = "<"; value_op = (fun c -> c = -1); ground_op = (fun c c' -> c < c'); }; { name = "<="; value_op = (fun c -> c <> 1); ground_op = (fun c c' -> c <= c'); }; { name = ">="; value_op = (fun c -> c <> -1); ground_op = (fun c c' -> c >= c'); }; { name = ">"; value_op = (fun c -> c = 1); ground_op = (fun c c' -> c > c'); }; ] let () = let t = Lang.univ_t ~constraints:[Type.ord_constr] () in List.iter (fun { name; value_op; ground_op } -> ignore (Lang.add_builtin name ~category:`Bool ~descr:"Comparison of comparable values." [("", t, None, None); ("", t, None, None)] Lang.bool_t (fun p -> let v = Lang.assoc "" 1 p in let v' = Lang.assoc "" 2 p in Lang.bool (match (v, v') with | Custom { value = g }, Custom { value = g' } -> value_op (Term.Custom.compare g g') | Int { value = v }, Int { value = v' } -> ground_op v v' | Float { value = v }, Float { value = v' } -> ground_op v v' | String { value = v }, String { value = v' } -> ground_op v v' | Bool { value = v }, Bool { value = v' } -> ground_op v v' | _ -> value_op (Value.compare v v'))))) operators let _ = Lang.add_builtin "and" ~category:`Bool ~descr:"Return the conjunction of its arguments" [ ("", Lang.getter_t Lang.bool_t, None, None); ("", Lang.getter_t Lang.bool_t, None, None); ] Lang.bool_t (fun p -> let a = Lang.to_bool_getter (Lang.assoc "" 1 p) in let b = Lang.to_bool_getter (Lang.assoc "" 2 p) in Lang.bool (if a () then b () else false)) let _ = Lang.add_builtin "or" ~category:`Bool ~descr:"Return the disjunction of its arguments" [ ("", Lang.getter_t Lang.bool_t, None, None); ("", Lang.getter_t Lang.bool_t, None, None); ] Lang.bool_t (fun p -> let a = Lang.to_bool_getter (Lang.assoc "" 1 p) in let b = Lang.to_bool_getter (Lang.assoc "" 2 p) in Lang.bool (if a () then true else b ())) let _ = Lang.add_builtin "not" ~category:`Bool ~descr:"Returns the negation of its argument." [("", Lang.bool_t, None, None)] Lang.bool_t (fun p -> Lang.bool (not (Lang.to_bool (List.assoc "" p)))) liquidsoap-2.4.2/src/lang/base/builtins_eval.ml000066400000000000000000000031271513273233300215120ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let raise ~bt exn = Lang.raise_as_runtime ~bt ~kind:"eval" exn let _ = Lang.add_builtin ~category:`Liquidsoap "_eval_" ~descr:"Parse and evaluate a string." ~flags:[`Hidden] [("type", Value.RuntimeType.t, None, None); ("", Lang.string_t, None, None)] (Lang.univ_t ()) (fun p -> let ty = Value.RuntimeType.of_value (List.assoc "type" p) in let ty = Type.fresh ty in let s = Lang.to_string (List.assoc "" p) in try Lang.eval ~toplevel:false ~stdlib:`Disabled ~ty s with exn -> let bt = Printexc.get_raw_backtrace () in raise ~bt exn) liquidsoap-2.4.2/src/lang/base/builtins_getter.ml000066400000000000000000000101541513273233300220530ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let getter = let a = Lang.univ_t () in Lang.add_builtin ~category:`Getter "getter" ~descr:"Create a getter." [ ( "", Lang.getter_t a, None, Some "Value from which the getter should be created." ); ] (Lang.getter_t a) (fun p -> List.assoc "" p) let _ = let a = Lang.univ_t () in let b = Lang.univ_t () in Lang.add_builtin ~category:`Getter ~base:getter "case" ~descr:"Return a value depending on whether the getter is constant or not." [ ("", Lang.getter_t a, None, Some "Getter to inspect."); ("", Lang.fun_t [(false, "", a)] b, None, None); ("", Lang.fun_t [(false, "", Lang.fun_t [] a)] b, None, None); ] b (fun p -> let x = Lang.assoc "" 1 p in let f = Lang.assoc "" 2 p in let g = Lang.assoc "" 3 p in match x with | Fun { fun_args = [] } | FFI { ffi_args = []; _ } -> Lang.apply ~pos:(Lang.pos p) g [("", x)] | _ -> Lang.apply ~pos:(Lang.pos p) f [("", x)]) let _ = let a = Lang.univ_t () in Lang.add_builtin ~category:`Getter ~base:getter "get" ~descr:"Get the value of a getter." [("", Lang.getter_t a, None, None)] a (fun p -> let x = List.assoc "" p |> Lang.to_getter in x ()) let getter_map = let a = Lang.univ_t () in let b = Lang.univ_t () in Lang.add_builtin ~category:`Getter ~base:getter "map" ~descr:"Apply a function on a getter." [ ("", Lang.fun_t [(false, "", a)] b, None, Some "Function to apply."); ("", Lang.getter_t a, None, None); ] (Lang.getter_t b) (fun p -> let f = Lang.assoc "" 1 p in let x = Lang.assoc "" 2 p in match x with | Fun { fun_args = [] } | FFI { ffi_args = []; _ } -> Lang.val_fun [] (fun p' -> Lang.apply ~pos:(Lang.pos p') f [("", Lang.apply ~pos:(Lang.pos p') x [])]) | _ -> Lang.apply ~pos:(Lang.pos p) f [("", x)]) let _ = let a = Lang.univ_t ~constraints:[Type.ord_constr] () in let b = Lang.univ_t () in Lang.add_builtin ~category:`Getter ~base:getter_map "memoize" ~descr: "Apply a function on a getter. If the input value has not changed \ compared to last call, the previous result is returned without \ computing the function again." [ ("", Lang.fun_t [(false, "", a)] b, None, Some "Function to apply."); ("", Lang.getter_t a, None, None); ] (Lang.getter_t b) (fun p -> let f = Lang.assoc "" 1 p in let x = Lang.assoc "" 2 p in match x with | Fun { fun_args = [] } | FFI { ffi_args = []; _ } -> let last_x = ref (Lang.apply ~pos:(Lang.pos p) x []) in let last_y = ref (Lang.apply ~pos:(Lang.pos p) f [("", !last_x)]) in Lang.val_fun [] (fun p' -> let x = Lang.apply ~pos:(Lang.pos p') x [] in if Value.compare x !last_x = 0 then !last_y else ( let y = Lang.apply ~pos:(Lang.pos p') f [("", x)] in last_x := x; last_y := y; y)) | _ -> Lang.apply ~pos:(Lang.pos p) f [("", x)]) liquidsoap-2.4.2/src/lang/base/builtins_json.ml000066400000000000000000000370411513273233300215360ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let log = Hooks.log ["json"; "parse"] let rec json_of_value ?pos v : Json.t = let pos = match (pos, Value.pos v) with | Some p, _ -> p | None, Some p -> [p] | None, None -> [] in let m, v = Value.split_meths v in match v with | Null _ -> `Null | Int { value = i } -> `Int i | Float { value = f } -> `Float f | Bool { value = b } -> `Bool b | String { value = s } -> `String s | Custom { value = g } -> Term.Custom.to_json ~pos g | List { value = l } -> `Tuple (List.map (json_of_value ~pos) l) | Tuple { value = [] } when m <> [] -> `Assoc (List.map (fun (l, v) -> (l, json_of_value ~pos v)) m) | Tuple { value = l } -> `Tuple (List.map (json_of_value ~pos) l) | _ -> Runtime_error.raise ~message: (Printf.sprintf "Value %s cannot be represented as json" (Value.to_string v)) ~pos "json" let rec type_of_json = function | `Assoc l -> Lang.record_t (List.map (fun (lbl, j) -> (lbl, type_of_json j)) l) | `Tuple l -> Lang.tuple_t (List.map type_of_json l) | `String _ -> Lang.string_t | `Bool _ -> Lang.bool_t | `Float _ -> Lang.float_t | `Int _ -> Lang.int_t | `Null -> Lang.(nullable_t (univ_t ())) let nullable_deref ty = let ty = Type.deref ty in match ty.Type.descr with Type.Nullable ty -> (true, ty) | _ -> (false, ty) type json_ellipsis_base = [ `Assoc of (string * string option * json_ellipsis) list | `List of json_ellipsis | `Tuple of json_ellipsis list | `String | `Bool | `Float | `Int | `Null | `Ignore ] and json_ellipsis = bool * json_ellipsis_base let rec string_of_json_ellipsis (nullable, ty) = let f ty = if nullable then ty ^ "?" else ty in match ty with | `Ignore -> "_" | `Null -> f "null" | `Int -> f "int" | `Float -> f "float" | `Bool -> f "bool" | `String -> f "string" | `List ty -> f ("[" ^ string_of_json_ellipsis ty ^ "]") | `Tuple [] -> f "()" | `Tuple l -> f ("(" ^ String.concat "," (List.map string_of_json_ellipsis l) ^ ")") | `Assoc [] -> f "{}" (* We should only report the one attribute that has failed to parse. *) | `Assoc ((lbl, lbl', ty) :: _) -> let lbl = match lbl' with | Some v -> Printf.sprintf "%s as %s" (Lang_string.quote_utf8_string v) lbl | None -> lbl in f ("{" ^ Printf.sprintf "%s: %s" lbl (string_of_json_ellipsis ty) ^ ", _}") exception Failed of json_ellipsis let rec value_of_typed_json ~ty json = let nullable, _ty = nullable_deref ty in try let tm, ty = Type.split_meths _ty in let () = match ty.Type.descr with | Type.Var _ -> log#important "You are parsing a JSON value without type annotation. This has \ confusing side-effects. Consider adding a type annotation: `let \ json.parse (x : ...) = ...`" | _ -> () in match (json, ty.Type.descr) with | `Assoc l, Type.Var _ | `Assoc l, Type.Tuple [] -> Typing.(ty <: Lang.unit_t); let meth = List.map (fun Type.{ meth; optional; json_name; scheme = _, ty } -> let ty = Type.deref ty in let lbl = Option.value ~default:meth json_name in let nullable_meth = optional || fst (nullable_deref ty) in let v = match List.assoc_opt lbl l with | Some v -> ( try value_of_typed_json ~ty v with | _ when nullable_meth -> Lang.null | Failed v -> raise (Failed (nullable, `Assoc [(meth, json_name, v)])) ) | None when nullable_meth -> Lang.null | _ -> raise (Failed ( nullable, `Assoc [(meth, json_name, (nullable_meth, `Ignore))] )) in (meth, v)) tm in Lang.record meth | ( `Assoc l, Type.( List { t = { descr = Tuple [{ descr = String }; ty] }; json_repr = `Object; }) ) -> Lang.list (List.map (fun (lbl, v) -> try Lang.product (Lang.string lbl) (value_of_typed_json ~ty v) with Failed v -> raise (Failed (nullable, `Assoc [(lbl, None, v)]))) l) | `Tuple l, Type.(List { t = ty; json_repr = `Tuple }) -> Lang.list (List.map (fun v -> try value_of_typed_json ~ty v with Failed v -> raise (Failed (nullable, `List v))) l) | `Tuple l, Type.Tuple t when tm = [] -> Lang.tuple (List.mapi (fun idx ty -> try value_of_typed_json ~ty (List.nth l idx) with Failed v -> let l = List.init (List.length t) (fun _ -> (false, `Ignore)) in let l = List.mapi (fun idx' el -> if idx = idx' then v else el) l in raise (Failed (nullable, `Tuple l))) t) | `String s, Type.String -> Lang.string s | `Bool b, Type.Bool -> Lang.bool b | `Float f, Type.Float -> Lang.float f | `Int i, Type.Float -> Lang.float (float i) | `Int i, Type.Int -> Lang.int i | _, Type.Var _ -> Typing.(ty <: type_of_json json); Lang.null | _, Type.String -> raise (Failed (nullable, `String)) | _, Type.Bool -> raise (Failed (nullable, `Bool)) | _, Type.Float -> raise (Failed (nullable, `Float)) | _, Type.Int -> raise (Failed (nullable, `Int)) | _ -> assert false with _ when nullable -> Lang.null let value_of_typed_json ~ty json = try value_of_typed_json ~ty json with | Failed v -> let bt = Printexc.get_raw_backtrace () in Runtime_error.raise ~bt ~message: (Printf.sprintf "Parsing error: json value cannot be parsed as type %s" (string_of_json_ellipsis v)) ~pos:(match ty.Type.pos with Some p -> [p] | None -> []) "json" | _ -> Runtime_error.raise ~message: (Printf.sprintf "Parsing error: json value cannot be parsed as type %s" (Type.to_string ty)) ~pos:(match ty.Type.pos with Some p -> [p] | None -> []) "json" module JsonSpecs = struct type content = [ `Object of (string, Lang.value) Hashtbl.t | `Value of Lang.value ] let name = "json" let to_string _ = "json" let to_json ~pos = function | `Object v -> `Assoc (Hashtbl.fold (fun k v l -> (k, json_of_value ~pos v) :: l) v []) | `Value v -> json_of_value v let compare = Stdlib.compare end module JsonValue = Value.MkCustom (JsonSpecs) let json = Lang.add_module "json" let _ = Lang.add_builtin ~base:json "value" ~category:`String ~descr:"Create a generic json value" [("", Lang.univ_t (), None, None)] JsonValue.t (fun p -> JsonValue.to_value (`Value (List.assoc "" p))) let _ = let val_t = Lang.univ_t () in let var = match val_t.Type.descr with | Type.Var { contents = Type.Free v } -> v | _ -> assert false in let meth = [ ( "add", ( [var], Lang.fun_t [(false, "", Lang.string_t); (false, "", val_t)] Lang.unit_t ), "Add or replace a new `key`/`value` pair to the object.", fun v -> Lang.val_fun [("", "", None); ("", "", None)] (fun p -> let key = Lang.to_string (Lang.assoc "" 1 p) in let value = Lang.assoc "" 2 p in Hashtbl.replace v key value; Lang.unit) ); ( "remove", ([], Lang.fun_t [(false, "", Lang.string_t)] Lang.unit_t), "Remove a key from the object. Does not nothing if the key does not \ exist.", fun v -> Lang.val_fun [("", "", None)] (fun p -> let key = Lang.to_string (List.assoc "" p) in Hashtbl.remove v key; Lang.unit) ); ( "stringify", ( [], Lang.fun_t [(true, "compact", Lang.bool_t); (true, "json5", Lang.bool_t)] Lang.string_t ), "Render object as json string.", fun v -> Lang.val_fun [ ("compact", "compact", Some (Lang.bool false)); ("json5", "json5", Some (Lang.bool false)); ] (fun p -> let compact = Lang.to_bool (List.assoc "compact" p) in let json5 = Lang.to_bool (List.assoc "json5" p) in Lang.string (Json.to_string ~compact ~json5 (JsonSpecs.to_json ~pos:(Lang.pos p) (`Object v)))) ); ] in let t = Lang.method_t JsonValue.t (List.map (fun (name, typ, doc, _) -> (name, typ, doc)) meth) in Lang.add_builtin ~base:json "object" ~category:`String ~descr:"Create a generic json object" [] t (fun _ -> let v = Hashtbl.create 10 in let meth = List.map (fun (name, _, _, fn) -> (name, fn v)) meth in Lang.meth (JsonValue.to_value (`Object v)) meth) let _ = Lang.add_builtin ~base:json "stringify" ~category:`String ~descr: "Convert a value to JSON. If the value cannot be represented as JSON \ (for instance a function), a `error.json` exception is raised." [ ( "compact", Lang.bool_t, Some (Lang.bool false), Some "Output compact text." ); ( "json5", Lang.bool_t, Some (Lang.bool false), Some "Use json5 extended spec." ); ("", Lang.univ_t (), None, None); ] Lang.string_t (fun p -> let v = List.assoc "" p in let compact = Lang.to_bool (List.assoc "compact" p) in let json5 = Lang.to_bool (List.assoc "json5" p) in let v = Json.to_string ~compact ~json5 (json_of_value v) in Lang.string v) let _ = Lang.add_builtin "_internal_json_parser_" ~category:`String ~flags:[`Hidden] ~descr:"Internal json parser" [ ("type", Value.RuntimeType.t, None, Some "Runtime type"); ( "json5", Lang.bool_t, Some (Lang.bool false), Some "Use json5 extended spec." ); ("", Lang.string_t, None, None); ] (Lang.univ_t ()) (fun p -> let s = Lang.to_string (List.assoc "" p) in let ty = Value.RuntimeType.of_value (List.assoc "type" p) in let ty = Type.fresh ty in let json5 = Lang.to_bool (List.assoc "json5" p) in try let json = Json.from_string ~json5 s in value_of_typed_json ~ty json with exn -> ( let bt = Printexc.get_raw_backtrace () in match exn with | Runtime_error.Runtime_error _ -> Printexc.raise_with_backtrace exn bt | _ -> Runtime_error.raise ~bt ~pos:(Lang.pos p) ~message: (Printf.sprintf "Parse error: %s" (Printexc.to_string exn)) "json")) exception DeprecatedFailed let () = Printexc.register_printer (function | DeprecatedFailed -> Some "Liquidsoap count not parse JSON string" | _ -> None) (* We compare the default's type with the parsed json value and return if they match. This comes with json.stringify in Builtin. *) let rec deprecated_of_json d j = let m, d = Value.split_meths d in Lang.( match (d, j) with | Tuple { value = [] }, `Null -> unit | Bool _, `Bool b -> bool b (* JSON specs do not differentiate between ints and floats. Therefore, we should parse int as floats when required. *) | Int _, `Int i -> int i | Float _, `Int i -> float (float_of_int i) | Float _, `Float x -> float x | String _, `String s -> string s (* Be liberal and allow casting basic types to string. *) | String _, `Int i -> string (string_of_int i) | String _, `Float x -> string (Utils.string_of_float x) | String _, `Bool b -> string (string_of_bool b) | List { value = [] }, `Tuple [] -> list [] | List { value = d :: _ }, `Tuple l -> (* TODO: we could also try with other elements of the default list... *) let l = List.map (deprecated_of_json d) l in list l | Tuple { value = dd }, `Tuple jj when List.length dd = List.length jj -> tuple (List.map2 deprecated_of_json dd jj) | ( List { value = Tuple { value = [String { value = _ }; d] } :: _ }, `Assoc l ) -> (* Try to convert the object to a list of pairs, dropping fields that cannot be parsed. This requires the target type to be [(string*'a)], currently it won't work if it is [?T] which would be obtained with of_json(default=[],...). *) let l = List.fold_left (fun cur (x, y) -> try product (string x) (deprecated_of_json d y) :: cur with _ -> cur) [] l in list l (* Parse records. *) | Tuple { value = [] }, `Assoc a when m <> [] -> ( try List.fold_left (fun parsed (key, meth) -> let json_meth = List.assoc key a in let parsed_meth = deprecated_of_json meth json_meth in Value.map_methods parsed (Methods.add key parsed_meth)) unit m with Not_found -> raise DeprecatedFailed) | Tuple { value = [] }, `Assoc _ -> unit | _ -> raise DeprecatedFailed) let _ = let t = Lang.univ_t () in Lang.add_builtin ~category:`String ~flags:[`Deprecated; `Hidden] ~descr:"Deprecated: use `let json.parse ..`" ~base:json "parse" [ ("default", t, None, Some "Default value if string cannot be parsed."); ("", Lang.string_t, None, None); ] t (fun p -> log#important "WARNING: \"json.parse\" is deprecated and will be removed in future \ version. Please use \"let json.parse ...\" instead"; let default = List.assoc "default" p in let s = Lang.to_string (List.assoc "" p) in try let json = Json.from_string s in deprecated_of_json default json with e -> log#important "JSON parsing failed: %s" (Printexc.to_string e); default) liquidsoap-2.4.2/src/lang/base/builtins_lang.ml000066400000000000000000000220751513273233300215070ustar00rootroot00000000000000let _ = Lang.add_builtin ~base:Lang.error_module "methods" ~descr:"Decorate an error with all its methods" ~category:`Liquidsoap [("", Lang.error_t, None, None)] Lang.error_meths_t (fun p -> List.assoc "" p) let _ = Lang.add_builtin "ignore" ~descr:"Convert anything to unit, preventing warnings." ~category:`Programming [("", Lang.univ_t (), None, None)] Lang.unit_t (fun _ -> Lang.unit) let _ = Lang.add_builtin "position" ~descr:"Return the current position in the script" ~category:`Programming [] Lang_core.Position.t (fun p -> match Lang.pos p with | [] -> Lang.raise_error ~pos:[] ~message:"Unknown position" "eval" | p :: _ -> Lang_core.Position.to_value p) let _ = let t = Lang.univ_t () in Lang.add_builtin "if" ~category:`Programming ~descr:"The basic conditional." ~flags:[`Hidden] [ ("", Lang.bool_t, None, None); ("then", Lang.fun_t [] t, None, None); ("else", Lang.fun_t [] t, None, None); ] t (fun p -> let c = List.assoc "" p in let fy = List.assoc "then" p in let fn = List.assoc "else" p in let c = Lang.to_bool c in Lang.apply ~pos:(Lang.pos p) (if c then fy else fn) []) (** Operations on products. *) let _ = let t1 = Lang.univ_t () in let t2 = Lang.univ_t () in Lang.add_builtin "fst" ~category:`Programming ~descr:"Get the first component of a pair." [("", Lang.product_t t1 t2, None, None)] t1 (fun p -> fst (Lang.to_product (Lang.assoc "" 1 p))) let _ = let t1 = Lang.univ_t () in let t2 = Lang.univ_t () in Lang.add_builtin "snd" ~category:`Programming ~descr:"Get the second component of a pair." [("", Lang.product_t t1 t2, None, None)] t2 (fun p -> snd (Lang.to_product (Lang.assoc "" 1 p))) let _ = Lang.add_builtin "print" ~category:`Programming ~descr:"Print on standard output." [ ( "newline", Lang.bool_t, Some (Lang.bool true), Some "If true, a newline is added after displaying the value." ); ("", Lang.univ_t (), None, None); ] Lang.unit_t (fun p -> let nl = Lang.to_bool (List.assoc "newline" p) in let v = List.assoc "" p in let v = match v with | String { value = s; flags } when not (Flags.has flags Flags.binary) -> s | _ -> Value.to_string v in print_string v; if nl then print_string "\n"; flush stdout; Lang.unit) (** Loops. *) let _ = Lang.add_builtin "while" ~category:`Programming ~descr:"A while loop." [ ("", Lang.getter_t Lang.bool_t, None, Some "Condition guarding the loop."); ("", Lang.fun_t [] Lang.unit_t, None, Some "Function to execute."); ] Lang.unit_t (fun p -> let c = Lang.to_bool_getter (Lang.assoc "" 1 p) in let f = Lang.to_fun (Lang.assoc "" 2 p) in while c () do ignore (f []) done; Lang.unit) let _ = let a = Lang.univ_t () in Lang.add_builtin "for" ~category:`Programming ~descr:"A for loop." ~flags:[`Hidden] [ ("", Lang.fun_t [] (Lang.nullable_t a), None, Some "Values to iterate on."); ( "", Lang.fun_t [(false, "", a)] Lang.unit_t, None, Some "Function to execute." ); ] Lang.unit_t (fun p -> let i = Lang.to_fun (Lang.assoc "" 1 p) in let f = Lang.to_fun (Lang.assoc "" 2 p) in let rec aux () = match Lang.to_option (i []) with | Some i -> ignore (f [("", i)]); aux () | None -> Lang.unit in aux ()) let _ = Lang.add_builtin ~base:Modules.iterator "int" ~category:`Programming ~descr:"Iterator on integers." ~flags:[`Hidden] [ ("", Lang.int_t, None, Some "First value."); ("", Lang.int_t, None, Some "Last value (included)."); ] (Lang.fun_t [] (Lang.nullable_t Lang.int_t)) (fun p -> let a = Lang.to_int (Lang.assoc "" 1 p) in let b = Lang.to_int (Lang.assoc "" 2 p) in let i = ref a in let f _ = let ans = !i in incr i; if ans > b then Lang.null else Lang.int ans in Lang.val_fun [] f) let liquidsoap = Modules.liquidsoap let liquidsoap_cache = Lang.add_builtin ~category:`Configuration ~descr:"Liquidsoap cache directory." ~base:liquidsoap "cache" [ ( "mode", Lang.string_t, None, Some "Cache mode, one of: \"user\" or \"system\"" ); ] (Lang.nullable_t Lang.string_t) (fun p -> let mode = List.assoc "mode" p in let dirtype = match Lang.to_string mode with | "system" -> `System | "user" -> `User | _ -> raise (Error.Invalid_value ( mode, "Invalid mode. Should be one of: \"user\" or \"system\"" )) in match Cache.dir dirtype with | None -> Lang.null | Some dir -> Lang.string dir) let _ = Lang.add_builtin ~category:`Configuration ~descr:"Execute cache maintenance routine." ~base:liquidsoap_cache "maintenance" [ ( "mode", Lang.string_t, None, Some "Cache mode, one of: \"user\" or \"system\"" ); ] Lang.unit_t (fun p -> let mode = List.assoc "mode" p in let dirtype = match Lang.to_string mode with | "system" -> `System | "user" -> `User | _ -> raise (Error.Invalid_value ( mode, "Invalid mode. Should be one of: \"user\" or \"system\"" )) in let fn = !Hooks.cache_maintenance in fn dirtype; Lang.unit) let liquidsoap_version = Lang.add_builtin_base ~category:`Configuration ~descr:"Liquidsoap version string." ~base:liquidsoap "version" (`String Build_config.version) Lang.string_t let _ = Lang.add_builtin_base ~base:liquidsoap "executable" ~category:`Liquidsoap ~descr:"Path to the Liquidsoap executable." (`String Sys.executable_name) Lang.string_t let liquidsoap_functions = Lang.add_module ~base:liquidsoap "functions" let _ = Lang.add_builtin ~base:liquidsoap_functions "count" ~category:`Liquidsoap ~descr:"Number of functions registered in the standard library." [] Lang.int_t (fun _ -> Doc.Value.count () |> Lang.int) let _ = Lang.add_builtin_base ~category:`System ~descr:"Type of OS running liquidsoap." ~base:Modules.os "type" (`String Sys.os_type) Lang.string_t let _ = Lang.add_builtin_base ~category:`System ~descr:"Executable file extension." "exe_ext" (`String Build_config.ext_exe) Lang.string_t let _ = Lang.add_builtin ~category:`Liquidsoap ~descr:"Ensure that Liquidsoap version is greater or equal to given one." ~base:liquidsoap_version "at_least" [("", Lang.string_t, None, Some "Minimal version.")] Lang.bool_t (fun p -> let v = List.assoc "" p |> Lang.to_string in Lang.bool (Lang_string.Version.compare (Lang_string.Version.of_string v) (Lang_string.Version.of_string Build_config.version) <= 0)) let liquidsoap_script = Lang.add_module ~base:liquidsoap "script" let _ = Lang.add_builtin_base ~category:`Liquidsoap ~descr:"Path to the current script, if available" ~base:liquidsoap_script "path" `Null (Lang.nullable_t Lang.string_t) let liquidsoap_build_config = Lang.add_module ~base:liquidsoap "build_config" let _ = Lang.add_builtin_base ~category:`Configuration ~descr:"OCaml version used to compile liquidspap." ~base:liquidsoap_build_config "ocaml_version" (`String Sys.ocaml_version) Lang.string_t let _ = Lang.add_builtin_base ~category:`Configuration ~descr:"Git sha used to compile liquidsoap." ~base:liquidsoap_build_config "git_sha" (match Build_config.git_sha with None -> `Null | Some sha -> `String sha) Lang.(nullable_t string_t) let _ = Lang.add_builtin_base ~category:`Configuration ~descr:"Is this build a development snapshot?" ~base:liquidsoap_build_config "is_snapshot" (`Bool Build_config.is_snapshot) Lang.bool_t let _ = Lang.add_builtin_base ~category:`Configuration ~descr:"Is this build a release build?" ~base:liquidsoap_build_config "is_release" (`Bool (not Build_config.is_snapshot)) Lang.bool_t let () = List.iter (fun (name, value) -> ignore (Lang.add_builtin_base ~category:`Configuration ~descr:("Build-time configuration value for " ^ name) ~base:liquidsoap_build_config name (`String value) Lang.string_t)) [ ("architecture", Build_config.architecture); ("host", Build_config.host); ("target", Build_config.target); ("system", Build_config.system); ("ocamlopt_cflags", Build_config.ocamlopt_cflags); ("native_c_compiler", Build_config.native_c_compiler); ("native_c_libraries", Build_config.native_c_libraries); ] let _ = Lang.add_builtin ~category:`Programming ~descr:"Return any value with a fresh universal type for testing purposes." ~flags:[`Hidden] "💣" [("", Lang.univ_t (), Some Lang.null, None)] (Lang.univ_t ()) (fun p -> List.assoc "" p) liquidsoap-2.4.2/src/lang/base/builtins_list.ml000066400000000000000000000236211513273233300215370ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let list = Modules.list let _ = let a = Lang.univ_t ~constraints:[Type.ord_constr] () in Lang.add_builtin "_[_]" ~category:`List ~descr: "l[k] returns the first v such that (k,v) is in the list l (or \"\" if \ no such v exists)." [ ("", Lang.list_t (Lang.product_t a Lang.string_t), None, None); ("", a, None, None); ] Lang.string_t (fun p -> let l = List.map Lang.to_product (Lang.to_list (Lang.assoc "" 1 p)) in let k = Lang.assoc "" 2 p in let ans = try Lang.to_string (snd (List.find (fun (k', _) -> Value.compare k k' = 0) l)) with _ -> "" in Lang.string ans) let _ = let a = Lang.univ_t () in let b = Lang.univ_t () in Lang.add_builtin ~base:list "case" ~category:`List ~descr: "Define a function by case analysis, depending on whether a list is \ empty or not." [ ("", Lang.list_t a, None, Some "List to perform case analysis on."); ("", b, None, Some "Result when the list is empty."); ( "", Lang.fun_t [(false, "", a); (false, "", Lang.list_t a)] b, None, Some "Result when the list is non-empty." ); ] b (fun p -> let l, e, f = (Lang.assoc "" 1 p, Lang.assoc "" 2 p, Lang.assoc "" 3 p) in match Lang.to_list l with | [] -> e | x :: l -> Lang.apply ~pos:(Lang.pos p) f [("", x); ("", Lang.list l)]) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "length" ~category:`List ~descr:"Compute the length of a list, i.e., the number of its elements." [("", Lang.list_t a, None, None)] Lang.int_t (fun p -> Lang.int (List.length (Lang.to_list (List.assoc "" p)))) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "nth" ~category:`List ~descr: "Get the n-th element of a list (the first element is at position 0), or \ `default` if element does not exist." [ ( "default", Lang.nullable_t a, Some Lang.null, Some "Default element. Raises `error.not_found` if `null` and no element \ can be found in the list." ); ("", Lang.list_t a, None, None); ("", Lang.int_t, None, None); ] a (fun p -> let default = Lang.to_option (List.assoc "default" p) in let l = Lang.to_list (Lang.assoc "" 1 p) in let n = Lang.to_int (Lang.assoc "" 2 p) in try List.nth l n with _ -> ( match default with | Some v -> v | None -> Runtime_error.raise ~pos:(Lang.pos p) ~message:"no default value for list.nth" "not_found")) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "slice" ~category:`List ~descr: "Return the sublist of length `length` starting with the element at \ index `offset`." [ ( "offset", Lang.int_t, Some (Lang.int 0), Some "Index of the first element." ); ( "length", Lang.nullable_t Lang.int_t, Some Lang.null, Some "Length of the returned list. Include all elements from `offset` if \ `null`." ); ("", Lang.list_t a, None, None); ] (Lang.list_t a) (fun p -> let start = Lang.to_int (List.assoc "offset" p) in let length = Lang.to_valued_option Lang.to_int (List.assoc "length" p) in let l = Lang.to_list (List.assoc "" p) in let stop = match length with Some l -> start + l | None -> List.length l in Lang.list (List.filteri (fun pos _ -> start <= pos && pos < stop) l)) let _ = let a = Lang.univ_t () in let b = Lang.univ_t () in Lang.add_builtin ~base:list "ind" ~category:`List ~descr: "Define a function by induction on a list. This is slightly more \ efficient than defining a recursive function. The list is scanned from \ the right." [ ("", Lang.list_t a, None, Some "List to perform induction on."); ("", b, None, Some "Result when the list is empty."); ( "", Lang.fun_t [(false, "", a); (false, "", Lang.list_t a); (false, "", b)] b, None, Some "Result when the list is non-empty, given the current element, the \ tail and the result of the recursive call on the tail." ); ] b (fun p -> let l, e, f = (Lang.assoc "" 1 p, Lang.assoc "" 2 p, Lang.assoc "" 3 p) in let rec aux k = function | [] -> k e | x :: l -> aux (fun r -> k (Lang.apply ~pos:(Lang.pos p) f [("", x); ("", Lang.list l); ("", r)])) l in aux (fun r -> r) (Lang.to_list l)) let _ = let a = Lang.univ_t () in List.iter (fun (base, name) -> ignore (Lang.add_builtin ?base name ~category:`List ~descr:"Add an element at the top of a list." [("", a, None, None); ("", Lang.list_t a, None, None)] (Lang.list_t a) (fun p -> let x, l = (Lang.assoc "" 1 p, Lang.assoc "" 2 p) in let l = Lang.to_list l in Lang.list (x :: l)))) [(Some list, "add"); (None, "_::_")] let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "sort" ~category:`List ~descr:"Sort a list according to a comparison function." [ ( "", Lang.fun_t [(false, "", a); (false, "", a)] Lang.int_t, None, Some "Comparison function f such that f(x,y)<0 when x0 when x>y." ); ("", Lang.list_t a, None, Some "List to sort."); ] (Lang.list_t a) (fun p -> let f = Lang.assoc "" 1 p in let sort x y = Lang.to_int (Lang.apply ~pos:(Lang.pos p) f [("", x); ("", y)]) in let l = Lang.assoc "" 2 p in Lang.list (List.sort sort (Lang.to_list l))) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "init" ~category:`List ~descr:"Initialize a list." [ ("", Lang.int_t, None, Some "Number of elements in the list."); ( "", Lang.fun_t [(false, "", Lang.int_t)] a, None, Some "Function such that `f i` is the `i`th element." ); ] (Lang.list_t a) (fun p -> let n = Lang.to_int (Lang.assoc "" 1 p) in let fn = Lang.assoc "" 2 p in let apply n = Lang.apply ~pos:(Lang.pos p) fn [("", Lang.int n)] in Lang.list (List.init n apply)) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "iteri" ~category:`List ~descr:"Call a function on every element of a list, along with its index." [ ( "", Lang.fun_t [(false, "", Lang.int_t); (false, "", a)] Lang.unit_t, None, None ); ("", Lang.list_t a, None, None); ] Lang.unit_t (fun p -> let fn = Lang.assoc "" 1 p in let l = Lang.to_list (Lang.assoc "" 2 p) in List.iteri (fun pos v -> ignore (Lang.apply ~pos:(Lang.pos p) fn [("", Lang.int pos); ("", v)])) l; Lang.unit) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "append" ~category:`List ~descr:"Concatenate two lists." [("", Lang.list_t a, None, None); ("", Lang.list_t a, None, None)] (Lang.list_t a) (fun p -> Lang.list (List.append (Lang.to_list (Lang.assoc "" 1 p)) (Lang.to_list (Lang.assoc "" 2 p)))) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "rev" ~category:`List ~descr:"Revert list order." [("", Lang.list_t a, None, None)] (Lang.list_t a) (fun p -> Lang.list (List.rev (Lang.to_list (List.assoc "" p)))) let _ = let a = Lang.univ_t () in let b = Lang.univ_t () in Lang.add_builtin ~base:list "map" ~category:`List ~descr:"Map a function on every element of a list." [ ("", Lang.fun_t [(false, "", a)] b, None, None); ("", Lang.list_t a, None, None); ] (Lang.list_t b) (fun p -> let fn = Lang.assoc "" 1 p in let l = Lang.to_list (Lang.assoc "" 2 p) in Lang.list (List.map (fun v -> Lang.apply ~pos:(Lang.pos p) fn [("", v)]) l)) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:list "remove" ~category:`List ~descr:"Remove the first occurrence of a value from a list." [("", a, None, None); ("", Lang.list_t a, None, None)] (Lang.list_t a) (fun p -> let v = Lang.assoc "" 1 p in let lv = Lang.assoc "" 2 p in let l = Lang.to_list lv in try let v = List.find (fun v' -> Value.compare v v' == 0) l in Lang.list (List.filter (fun v' -> v' != v) l) with Not_found -> lv) let _ = let t = Lang.list_t (Lang.univ_t ()) in Lang.add_builtin ~base:list "shuffle" ~category:`List ~descr: "Shuffle the content of a list. The function returns a list with the \ same elements but in different, random, order." [("", t, None, None)] t (fun p -> List.assoc "" p |> Lang.to_list |> Extralib.List.shuffle |> Lang.list) liquidsoap-2.4.2/src/lang/base/builtins_math.ml000066400000000000000000000245361513273233300215230ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let log = lazy (!Hooks.make_log ["math"]) let () = let add op name descr = let t = Lang.float_t in ignore (Lang.add_builtin name ~category:`Math ~descr [("", t, None, None)] t (fun p -> let a = Lang.to_float (List.assoc "" p) in Lang.float (op a))) in add sqrt "sqrt" "Square root."; add exp "exp" "Exponential."; add Stdlib.log "ln" "Natural logarithm."; add log10 "log10" "Base 10 logarithm."; add sin "sin" "Sine. Argument is in radians."; add cos "cos" "Cosine. Argument is in radians."; add tan "tan" "Tangent. Argument is in radians."; add acos "acos" "Arc cosine. The argument must fall within the range [-1.0, 1.0]. Result \ is in radians and is between 0.0 and pi."; add asin "asin" "Arc sine. The argument must fall within the range [-1.0, 1.0]. Result is \ in radians and is between -pi/2 and pi/2."; add atan "atan" "Arc tangent. Result is in radians and is between -pi/2 and pi/2."; add cosh "cosh" "Hyperbolic cosine. Argument is in radians."; add sinh "sinh" "Hyperbolic sine. Argument is in radians."; add tanh "tanh" "Hyperbolic tangent. Argument is in radians." let _ = let t = Lang.univ_t ~constraints:[Type.num_constr] () in Lang.add_builtin "~-" ~category:`Math ~descr:"Returns the opposite of its argument." [("", t, None, None)] t (fun p -> match Lang.to_num (List.assoc "" p) with | `Int i -> Lang.int ~-i | `Float i -> Lang.float ~-.i) let _ = let t = Lang.univ_t ~constraints:[Type.num_constr] () in Lang.add_builtin "abs" ~category:`Math ~descr:"Absolute value." [("", t, None, None)] t (fun p -> match Lang.to_num (List.assoc "" p) with | `Int i -> Lang.int (abs i) | `Float i -> Lang.float (abs_float i)) let () = let register_op doc name op_int op_float = let t = Lang.univ_t ~constraints:[Type.num_constr] () in ignore (Lang.add_builtin name ~category:`Math ~descr:(Printf.sprintf "%s of numbers." doc) [("", t, None, None); ("", t, None, None)] t (fun p -> let a = Lang.to_num (Lang.assoc "" 1 p) in let b = Lang.to_num (Lang.assoc "" 2 p) in match (a, b) with | `Int a, `Int b -> Lang.int (op_int a b) | `Float a, `Float b -> Lang.float (op_float a b) | _ -> assert false)) in register_op "Multiplication" "*" ( * ) ( *. ); register_op "Division" "/" ( / ) ( /. ); register_op "Addition" "+" ( + ) ( +. ); register_op "Subtraction " "-" ( - ) ( -. ); register_op "Exponentiation" "pow" (fun a b -> int_of_float (float_of_int a ** float_of_int b)) ( ** ); register_op "Remainder of division" "mod" ( mod ) mod_float let float = let t = Lang.univ_t ~constraints:[Type.num_constr] () in Lang.add_builtin "float" ~category:`Math ~descr:"Convert a number to a float." [("", t, None, None)] Lang.float_t (fun p -> let x = List.assoc "" p |> Lang.to_num in let x = match x with `Int x -> float x | `Float x -> x in Lang.float x) let _ = Lang.add_builtin ~base:float "is_nan" ~category:`Math ~descr:"Return `true` if the floating point number is `NaN`." [("", Lang.float_t, None, None)] Lang.bool_t (fun p -> Lang.bool (Float.is_nan (Lang.to_float (List.assoc "" p)))) let _ = Lang.add_builtin ~base:float "is_infinite" ~category:`Math ~descr:"Return `true` if the floating point number is infinite." [("", Lang.float_t, None, None)] Lang.bool_t (fun p -> Lang.bool (Float.is_infinite (Lang.to_float (List.assoc "" p)))) let _ = let t = Lang.univ_t ~constraints:[Type.num_constr] () in Lang.add_builtin "int" ~category:`Math ~descr:"Convert a number to an integer." [ ("", t, None, None); ( "raise", Lang.bool_t, Some (Lang.bool false), Some "Raise `error.invalid` if number is `NaN` or `+/-infinity`." ); ] Lang.int_t (fun p -> let x = List.assoc "" p |> Lang.to_num in let raise = List.assoc "raise" p |> Lang.to_bool in let pos = Lang.pos p in let log = Lazy.force log in let x = match x with | `Int x -> x | `Float x when Float.is_infinite x -> if raise then Runtime_error.raise ~pos ~message: "Infinite floating point number cannot be converted to \ integers!" "invalid"; log#important "At %s: floating point number is infinite!" (Pos.Option.to_string (match pos with p :: _ -> Some p | [] -> None)); if x < 0. then min_int else max_int | `Float x when Float.is_nan x -> if raise then Runtime_error.raise ~pos ~message: "NaN floating point number cannot be converted to integers!" "invalid"; log#important "At %s: floating point number is `NaN`!" (Pos.Option.to_string (try Some (List.hd pos) with _ -> None)); 0 | `Float x -> int_of_float x in Lang.int x) let _ = Lang.add_builtin ~base:Modules.random "float" ~category:`Math ~descr: "Generate a random value between `min` (included) and `max` (excluded)." [ ("min", Lang.float_t, Some (Lang.float 0.), None); ("max", Lang.float_t, Some (Lang.float 1.), None); ] Lang.float_t (fun p -> let min = Lang.to_float (List.assoc "min" p) in let max = Lang.to_float (List.assoc "max" p) in Lang.float (Random.float (max -. min) +. min)) let _ = Lang.add_builtin ~base:Modules.random "int" ~category:`Math ~descr: "Generate a random value between `min` (included) and `max` (excluded)." [ ("min", Lang.int_t, Some (Lang.int (1 - (1 lsl 29))), None); ("max", Lang.int_t, Some (Lang.int (1 lsl 29)), None); ] Lang.int_t (fun p -> let min = Lang.to_int (List.assoc "min" p) in let max = Lang.to_int (List.assoc "max" p) in Lang.int (Random.int (max - min) + min)) let _ = Lang.add_builtin ~base:Modules.random "bool" ~category:`Bool ~descr:"Generate a random boolean." [] Lang.bool_t (fun _ -> Lang.bool (Random.bool ())) let _ = Lang.add_builtin_base ~category:`Math ~descr:"Maximal representable integer." "max_int" (`Int max_int) Lang.int_t let _ = Lang.add_builtin_base ~category:`Math ~descr:"Minimal representable integer." "min_int" (`Int min_int) Lang.int_t let _ = Lang.add_builtin_base ~category:`Math ~descr:"Float representation of infinity." "infinity" (`Float infinity) Lang.float_t let _ = Lang.add_builtin_base ~category:`Math ~descr: "A special floating-point value denoting the result of an undefined \ operation such as 0.0 /. 0.0. Stands for 'not a number'. Any \ floating-point operation with nan as argument returns nan as result. As \ for floating-point comparisons, `==`, `<`, `<=`, `>` and `>=` return \ `false` and `!=` returns `true` if one or both of their arguments is \ `nan`." "nan" (`Float nan) Lang.float_t let _ = Lang.add_builtin "lsl" ~category:`Math ~descr:"Logical shift left." [ ("", Lang.int_t, None, Some "Number to shift."); ("", Lang.int_t, None, Some "Number of bits to shift."); ] Lang.int_t (fun p -> let n = Lang.to_int (Lang.assoc "" 1 p) in let b = Lang.to_int (Lang.assoc "" 2 p) in Lang.int (n lsl b)) let _ = Lang.add_builtin "lsr" ~category:`Math ~descr:"Logical shift right." [ ("", Lang.int_t, None, Some "Number to shift."); ("", Lang.int_t, None, Some "Number of bits to shift."); ] Lang.int_t (fun p -> let n = Lang.to_int (Lang.assoc "" 1 p) in let b = Lang.to_int (Lang.assoc "" 2 p) in Lang.int (n lsr b)) let _ = Lang.add_builtin "ceil" ~category:`Math ~descr: "Round above to an integer value. `ceil(x)` returns the least integer \ whose value is greater than or equal to `x`. The result is returned as \ a float." [("", Lang.float_t, None, None)] Lang.float_t (fun p -> let f = Lang.to_float (List.assoc "" p) in Lang.float (Float.ceil f)) let _ = Lang.add_builtin "floor" ~category:`Math ~descr: "Round below to an integer value. `floor(x)` returns the greatest \ integer whose value is less than or equal to `x`. The result is \ returned as a float." [("", Lang.float_t, None, None)] Lang.float_t (fun p -> let f = Lang.to_float (List.assoc "" p) in Lang.float (Float.floor f)) let _ = Lang.add_builtin "round" ~category:`Math ~descr: "Rounds `x` to the nearest integer with ties (fractional values of \ `0.5`) rounded away from zero, regardless of the current rounding \ direction. If `x` is an integer, `+0.`, `-0.`, `nan`, or `infinite`, \ `x` itself is returned." [("", Lang.float_t, None, None)] Lang.float_t (fun p -> let f = Lang.to_float (List.assoc "" p) in Lang.float (Float.floor f)) let _ = Lang.add_builtin "sign" ~category:`Math ~descr:"Return `1.` if the argument is positive and `-1.` otherwise." [("", Lang.float_t, None, None)] Lang.float_t (fun p -> let f = Lang.to_float (List.assoc "" p) in Lang.float (if Float.sign_bit f then -1. else 1.)) liquidsoap-2.4.2/src/lang/base/builtins_null.ml000066400000000000000000000051531513273233300215360ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let null = let a = Lang.univ_t () in Lang.add_builtin "_null" ~category:`Programming ~descr:"Create a nullable value." [("", Lang.nullable_t a, Some Lang.null, Some "Value to make nullable.")] (Lang.nullable_t a) (fun p -> match Lang.to_option (List.assoc "" p) with | Some x -> x | None -> Lang.null) let _ = let a = Lang.univ_t () in let b = Lang.univ_t () in Lang.add_builtin ~base:null "case" ~category:`Programming ~descr:"Return a result dending on whether a value is nothing or not." [ ("", Lang.nullable_t a, None, Some "Value to reason by case analysis on."); ( "", Lang.fun_t [] b, None, Some "Value to return in case we have nothing." ); ( "", Lang.fun_t [(false, "", a)] b, None, Some "Value to return in case we have something." ); ] b (fun p -> let x = Lang.assoc "" 1 p in let d = Lang.to_fun (Lang.assoc "" 2 p) in let f = Lang.to_fun (Lang.assoc "" 3 p) in match Lang.to_option x with None -> d [] | Some x -> f [("", x)]) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:null "default" ~category:`Programming ~descr:"Return a result dending on whether a value is nothing or not." [ ("", Lang.nullable_t a, None, Some "Value to reason by case analysis on."); ( "", Lang.fun_t [] a, None, Some "Value to return in case we have nothing." ); ] a (fun p -> let x = Lang.assoc "" 1 p in let d = Lang.assoc "" 2 p in match Lang.to_option x with None -> Lang.apply d [] | Some x -> x) liquidsoap-2.4.2/src/lang/base/builtins_profiler.ml000066400000000000000000000041431513273233300224040ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let profiler = Modules.profiler let profiler_stats = Modules.profiler_stats let _ = Lang.add_builtin ~base:profiler "enable" ~category:`Liquidsoap ~descr:"Record profiling statistics." [] Lang.unit_t (fun _ -> Term.profile := true; Lang.unit) let _ = Lang.add_builtin ~base:profiler "disable" ~category:`Liquidsoap ~descr:"Record profiling statistics." [] Lang.unit_t (fun _ -> Term.profile := false; Lang.unit) let _ = let a = Lang.univ_t () in Lang.add_builtin ~base:profiler "run" ~category:`Liquidsoap ~descr:"Time a function with the profiler." [ ("", Lang.string_t, None, Some "Name of the profiled function."); ("", Lang.fun_t [] a, None, Some "Function to profile."); ] a (fun p -> let name = Lang.to_string (Lang.assoc "" 1 p) in let f = Lang.assoc "" 2 p in let f () = Lang.apply ~pos:(Lang.pos p) f [] in Profiler.time name f ()) let _ = Lang.add_builtin ~base:profiler_stats "string" ~category:`Liquidsoap ~descr:"Profiling statistics." [] Lang.string_t (fun _ -> Lang.string (Profiler.stats ())) liquidsoap-2.4.2/src/lang/base/builtins_ref.ml000066400000000000000000000025551513273233300213430ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let ref = let a = Lang.univ_t () in Lang.add_builtin "ref" ~category:`Programming ~descr:"Create a reference, i.e. a value which can be modified." [("", a, None, None)] (Lang.ref_t a) (fun p -> let x = List.assoc "" p |> Atomic.make in let get () = Atomic.get x in let set v = Atomic.set x v in Lang.reference get set) liquidsoap-2.4.2/src/lang/base/builtins_regexp.ml000066400000000000000000000215671513273233300220650ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type regexp = { descr : string; flags : [ `i | `g | `s | `m ] list; regexp : Re.re; } let all_regexp_flags = [`i; `g; `m] let string_of_regexp_flag = function | `i -> "i" | `g -> "g" | `s -> "s" | `m -> "m" let regexp_flag_of_string = function | "i" -> `i | "g" -> `g | "s" -> `s | "m" -> `m | _ -> assert false let escape_regex_descr = let escape_regex_formatter = Lang_string.escape ~special_char:(fun s pos len -> if List.mem s.[pos] ['\''; '/'] && len = 1 then true else Lang_string.utf8_special_char s pos len) ~escape_char:(fun s pos len -> if s.[pos] = '/' && len = 1 then "\\/" else Lang_string.escape_utf8_char ~strict:false s pos len) ~next:Lang_string.utf8_next in Lang_string.escape_string escape_regex_formatter let string_of_regexp { descr; flags } = Printf.sprintf "r/%s/%s" (escape_regex_descr descr) (String.concat "" (List.sort Stdlib.compare (List.map string_of_regexp_flag flags))) module RegExp = Value.MkCustom (struct type content = regexp let name = "regexp" let to_string = string_of_regexp let to_json ~pos _ = Runtime_error.raise ~pos ~message:"Regexp cannot be represented as json" "json" let compare r r' = Stdlib.compare (r.descr, List.sort Stdlib.compare r.flags) (r'.descr, List.sort Stdlib.compare r'.flags) end) let test_t = Lang_core.fun_t [(false, "", Lang_core.string_t)] Lang_core.bool_t let test_fun ~flags:_ ~descr:_ rex = Lang_core.val_fun [("", "", None)] (fun p -> let string = Lang_core.to_string (List.assoc "" p) in Lang_core.bool (Re.Pcre.pmatch ~rex string)) let split_t = Lang_core.fun_t [(false, "", Lang_core.string_t)] (Lang_core.list_t Lang_core.string_t) let split_fun ~flags:_ ~descr rex = Lang_core.val_fun [("", "", None)] (fun p -> let string = Lang_core.to_string (List.assoc "" p) in Lang_core.list (match (descr, string) with (* See: https://github.com/ocaml/ocaml-re/issues/232 *) | "", _ -> List.map (fun c -> Lang_core.string (Printf.sprintf "%c" c)) (List.of_seq (String.to_seq string)) (* See: https://github.com/ocaml/ocaml-re/issues/215 *) | _, "" -> [Lang_core.string ""] | _ -> List.map Lang_core.string (Re.Pcre.split ~rex string))) let exec_t = let matches_t = Lang_core.list_t (Lang_core.product_t Lang_core.int_t Lang_core.string_t) in Lang_core.fun_t [(false, "", Lang_core.string_t)] (Lang_core.method_t matches_t [ ( "groups", ( [], Lang_core.list_t (Lang_core.product_t Lang_core.string_t Lang_core.string_t) ), "Named captures" ); ]) let exec_fun ~flags:_ ~descr:_ rex = Lang_core.val_fun [("", "", None)] (fun p -> let string = Lang_core.to_string (List.assoc "" p) in try let sub = Re.Pcre.exec ~rex string in let matches = let matches = Array.to_list @@ Array.init (Re.Group.nb_groups sub + 1) (Re.Group.get_opt sub) in Lang_core.list (List.fold_left (fun matches (pos, value) -> match value with | None -> matches | Some value -> Lang_core.product (Lang_core.int pos) (Lang_core.string value) :: matches) [] (List.mapi (fun pos v -> (pos, v)) matches)) in Lang_core.meth matches [ ( "groups", Lang_core.list (List.fold_left (fun groups name -> try Lang_core.product (Lang_core.string name) (Lang_core.string (Re.Pcre.get_named_substring rex name sub)) :: groups with Not_found -> groups) [] (Array.to_list (Re.Pcre.names rex))) ); ] with | Not_found -> Lang_core.meth (Lang_core.list []) [("groups", Lang_core.list [])] | exn -> Runtime_error.raise ~pos:(Lang_core.pos p) ~message: (Printf.sprintf "Error while executing regular exception: %s" (Printexc.to_string exn)) "string") let replace_t = Lang_core.fun_t [ ( false, "", Lang_core.fun_t [(false, "", Lang_core.string_t)] Lang_core.string_t ); (false, "", Lang_core.string_t); ] Lang_core.string_t let replace_fun ~flags ~descr:_ regexp = Lang_core.val_fun [("", "", None); ("", "", None)] (fun p -> let subst = Lang_core.assoc "" 1 p in let subst s = let ret = Lang_core.apply subst [("", Lang_core.string s)] in Lang_core.to_string ret in let string = Lang_core.to_string (Lang_core.assoc "" 2 p) in let string = try Re.replace ~all:(List.mem `g flags) ~f:(fun g -> subst (Re.Group.get g 0)) regexp string with exn -> Runtime_error.raise ~message: (Printf.sprintf "Error while executing regular expression: %s" (Printexc.to_string exn)) ~pos:(Lang_core.pos p) "string" in Lang_core.string string) let _ = let meth = [ ("test", ([], test_t), "Match a string with the expressionn.", test_fun); ( "split", ([], split_t), "Split a string on the given regular expression.", split_fun ); ( "exec", ([], exec_t), "Extract substrings from a string. Returns a list of (index,value). If \ the list does not have a pair associated to some index, it means that \ the corresponding pattern was not found.", exec_fun ); ( "replace", ([], replace_t), "Replace substrings matched by the regexp by another string returned \ by a function.", replace_fun ); ] in let t = Lang_core.method_t RegExp.t (List.map (fun (name, typ, doc, _) -> (name, typ, doc)) meth) in Lang_core.add_builtin "regexp" ~category:`String ~descr:"Create a regular expression" [ ( "flags", Lang_core.list_t Lang_core.string_t, Some (Lang_core.list []), Some (Printf.sprintf "List of flags. Valid flags: %s." (String.concat ", " (List.map (fun f -> Printf.sprintf "`\"%s\"`" (string_of_regexp_flag f)) all_regexp_flags))) ); ("", Lang_core.string_t, None, None); ] t (fun p -> let flags = List.map (fun v -> try regexp_flag_of_string (Lang_core.to_string v) with _ -> raise (Error.Invalid_value (v, "Invalid regexp flag"))) (Lang_core.to_list (List.assoc "flags" p)) in let descr = Lang_core.to_string (List.assoc "" p) in let regexp = let flags = List.fold_left (fun l f -> match f with | `i -> `CASELESS :: l (* `g is handled at the call level. *) | `g -> l | `s -> `DOTALL :: l | `m -> `MULTILINE :: l) [] flags in match Re.Pcre.regexp ~flags descr with | v -> v | exception exn -> Runtime_error.raise ~message: (Printf.sprintf "Error while creating regular expression: %s" (Printexc.to_string exn)) ~pos:(Lang_core.pos p) "string" in let v = RegExp.to_value { descr; flags; regexp } in let meth = List.map (fun (name, _, _, fn) -> (name, fn ~flags ~descr regexp)) meth in Lang_core.meth v meth) liquidsoap-2.4.2/src/lang/base/builtins_string.ml000066400000000000000000000537131513273233300220770ustar00rootroot00000000000000let string = Lang.add_builtin "string" ~category:`String ~descr:"Return the representation of a value." [ ( "fields", Lang.bool_t, Some (Lang.bool false), Some "Show toplevel fields around the value." ); ( "print_binary", Lang.bool_t, Some (Lang.bool true), Some "When `false`, strings marked as binary are masked and returned as \ ``" ); ("", Lang.univ_t (), None, None); ] Lang.string_t (fun p -> let show_fields = Lang.to_bool (List.assoc "fields" p) in let v = List.assoc "" p in let dv = Lang.demeth v in (* Always show fields for records. *) let show_fields = if Value.is_unit dv then true else show_fields in let v = if show_fields then v else dv in let print_binary = Lang.to_bool (List.assoc "print_binary" p) in match v with | String { value = s; flags } when (not (Flags.has flags Flags.binary)) || print_binary -> Lang.string s | v -> Lang.string (Value.to_string v)) let flags = Lang.add_module ~base:string "flags" let _ = Lang.add_builtin "binary" ~base:flags ~category:`String ~descr:"Get or set the string binary flag." [("", Lang.string_t, None, None)] Lang.(ref_t bool_t) (fun p -> let v = List.assoc "" p in Lang.reference (fun () -> Lang.bool (Value.has_flag v Flags.binary)) (fun f -> if Lang.to_bool f then Value.add_flag v Flags.binary else Value.remove_flag v Flags.binary)) let _ = Lang.add_builtin "^" ~category:`String ~descr:"Concatenate strings." [("", Lang.string_t, None, None); ("", Lang.string_t, None, None)] Lang.string_t (fun p -> let s1 = Lang.to_string (Lang.assoc "" 1 p) in let s2 = Lang.to_string (Lang.assoc "" 2 p) in Lang.string (s1 ^ s2)) let _ = Lang.add_builtin ~base:string "compare" ~category:`String ~descr:"Compare strings in lexicographical order." [("", Lang.string_t, None, None); ("", Lang.string_t, None, None)] Lang.int_t (fun p -> let s1 = Lang.to_string (Lang.assoc "" 1 p) in let s2 = Lang.to_string (Lang.assoc "" 2 p) in Lang.int (String.compare s1 s2)) let _ = Lang.add_builtin ~base:string "digest" ~category:`String ~descr:"Return an MD5 digest for the given string." [("", Lang.string_t, None, None)] Lang.string_t (fun p -> let data = Lang.to_string (List.assoc "" p) in Lang.string Digest.(to_hex (string data))) let _ = Lang.add_builtin ~base:string "concat" ~category:`String ~descr:"Concatenate strings." [ ("separator", Lang.string_t, Some (Lang.string ""), None); ("", Lang.list_t Lang.string_t, None, None); ] Lang.string_t (fun p -> let sep = Lang.to_string (List.assoc "separator" p) in let l = Lang.to_list (List.assoc "" p) in let l = List.map Lang.to_string l in Lang.string (String.concat sep l)) let default_encoding = ref `Utf8 let encoding_option = ( "encoding", Lang.nullable_t Lang.string_t, Some Lang.null, Some "Encoding used to split characters. Should be one of: `\"utf8\"` or \ `\"ascii\"`" ) let get_encoding p = match Lang.to_valued_option Lang.to_string (List.assoc "encoding" p) with | None -> ("utf8", !default_encoding) | Some "utf8" -> ("utf8", `Utf8) | Some "ascii" -> ("ascii", `Ascii) | _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message:"Invalid encoding!" "invalid" let _ = Lang.add_builtin ~base:string "chars" ~category:`String ~descr:"Split string into characters. Raises `error.invalid` on errors." [encoding_option; ("", Lang.string_t, None, None)] (Lang.list_t Lang.string_t) (fun p -> let enc, encoding = get_encoding p in let s = Lang.to_string (List.assoc "" p) in try Lang.list (List.map Lang.string (Lang_string.split ~encoding s)) with _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message: (Printf.sprintf "String cannot be split using encoding `\"%s\"`!" enc) "invalid") let _ = Lang.add_builtin ~base:string "length" ~category:`String ~descr: "Return the string's length using the given encoding. Raises \ `error.invalid` on errors." [encoding_option; ("", Lang.string_t, None, None)] Lang.int_t (fun p -> let enc, encoding = get_encoding p in let s = Lang.to_string (List.assoc "" p) in try Lang.int (Lang_string.length ~encoding s) with _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message: (Printf.sprintf "String cannot be split using encoding `\"%s\"`!" enc) "invalid") let _ = Lang.add_builtin ~base:string "nth" ~category:`String ~descr: "Retrieve a character in a string. Raises `error.not_found` if character \ does not exist." ~examples: [ {| c = string.nth("abcde", 2) print(c) # should print 99 which is the ascii code for "c" |}; ] [ ("", Lang.string_t, None, Some "String to look into."); ("", Lang.int_t, None, Some "Index of the character."); ] Lang.int_t (fun p -> try let s = Lang.to_string (Lang.assoc "" 1 p) in let n = Lang.to_int (Lang.assoc "" 2 p) in Lang.int (int_of_char s.[n]) with _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message:"string.nth: character not found!" "not_found") let _ = Lang.add_builtin ~base:string "char" ~category:`String ~descr:"Create a string with one character." [("", Lang.int_t, None, Some "Code of the character.")] Lang.string_t (fun p -> List.assoc "" p |> Lang.to_int |> Char.chr |> String.make 1 |> Lang.string) let string_escape = Lang.add_builtin ~base:string "escape" ~category:`String ~descr: "Escape special characters in an string. By default, the string is \ assumed to be `\"utf8\"` encoded and is escaped following JSON and \ javascript specification." [ ( "special_char", Lang.nullable_t (Lang.fun_t [(false, "encoding", Lang.string_t); (false, "", Lang.string_t)] Lang.bool_t), Some Lang.null, Some "Return `true` if the given character (passed as a string) should be \ escaped. Defaults to control characters for `\"utf8\"` and control \ characters and any character above `\\x7E` (non-printable \ characters) for `\"ascii\"`." ); ( "escape_char", Lang.nullable_t (Lang.fun_t [(false, "encoding", Lang.string_t); (false, "", Lang.string_t)] Lang.string_t), Some Lang.null, Some "Function used to escape a character. Defaults to `\\xxx` octal \ notation for `\"ascii\"` and `\\uxxxx` hexadecimal notation for \ `\"utf8\"`." ); ( "encoding", Lang.nullable_t Lang.string_t, Some Lang.null, Some "One of: `\"ascii\"` or `\"utf8\"`. If `null`, `utf8` is tried first \ and `ascii` is used as a fallback if this fails." ); ("", Lang.string_t, None, None); ] Lang.string_t (fun p -> let s = Lang.to_string (List.assoc "" p) in let encoding = List.assoc "encoding" p in let encoding : [ `Default | `Ascii | `Utf8 ] = match Lang.to_valued_option Lang.to_string encoding with | None -> `Default | Some "ascii" -> `Ascii | Some "utf8" -> `Utf8 | Some _ -> raise (Error.Invalid_value ( encoding, "Encoding should be one of: \"ascii\" or \"utf8\"." )) in let exec encoding = let encoding_string = match encoding with `Utf8 -> "utf8" | `Ascii -> "ascii" in let special_char = match (Lang.to_option (List.assoc "special_char" p), encoding) with | Some f, _ -> fun s ofs len -> Lang.to_bool (Lang.apply f [ ("encoding", Lang.string encoding_string); ("", Lang.string (String.sub s ofs len)); ]) | None, `Ascii -> Lang_string.ascii_special_char | None, `Utf8 -> Lang_string.utf8_special_char in let escape_char = match (Lang.to_option (List.assoc "escape_char" p), encoding) with | Some f, _ -> fun s ofs len -> Lang.to_string (Lang.apply f [ ("encoding", Lang.string encoding_string); ("", Lang.string (String.sub s ofs len)); ]) | None, `Ascii -> Lang_string.escape_hex_char | None, `Utf8 -> Lang_string.escape_utf8_char ~strict:false in let next = match encoding with | `Ascii -> Lang_string.ascii_next | `Utf8 -> Lang_string.utf8_next in try Lang.string (Lang_string.escape_string (Lang_string.escape ~special_char ~escape_char ~next) s) with _ -> let bt = Printexc.get_raw_backtrace () in Runtime_error.raise ~bt ~pos:(Lang.pos p) ~message: (Printf.sprintf "Error while escaping %s string.%s" (match encoding with `Utf8 -> "utf8" | `Ascii -> "ascii") (if encoding <> `Ascii then " If you are not sure about the string's encoding, you \ should use `\"ascii\"` as this encoding never fails." else "")) "string" in match encoding with | `Default -> ( try exec `Utf8 with _ -> exec `Ascii) | (`Ascii | `Utf8) as encoding -> exec encoding) let _ = Lang.add_builtin ~base:string_escape "all" ~descr: "Escape each character in the given string using a specific escape \ sequence." ~category:`String [ ( "format", Lang.string_t, Some (Lang.string "utf8"), Some "Escape format. One of: `\"octal\"`, `\"hex\"` or `\"utf8\"`." ); ("", Lang.string_t, None, None); ] Lang.string_t (fun p -> let format = List.assoc "format" p in let escape_char, next = match Lang.to_string format with | "octal" -> (Lang_string.escape_octal_char, Lang_string.ascii_next) | "hex" -> (Lang_string.escape_hex_char, Lang_string.ascii_next) | "utf8" -> (Lang_string.escape_utf8_char ~strict:false, Lang_string.utf8_next) | _ -> raise (Error.Invalid_value ( format, "Format should be one of: `\"octal\"`, `\"hex\"` or \ `\"utf8\"`." )) in let s = Lang.to_string (List.assoc "" p) in Lang.string (Lang_string.escape_string (Lang_string.escape ~special_char:(fun _ _ _ -> true) ~escape_char ~next) s)) let _ = Lang.add_builtin ~base:string_escape "special_char" ~descr: "Default function to detect characters to escape. See `string.escape` \ for more details." ~category:`String [ ( "encoding", Lang.string_t, Some (Lang.string "utf8"), Some "One of: `\"ascii\"` or `\"utf8\"`." ); ("", Lang.string_t, None, None); ] Lang.bool_t (fun p -> let s = Lang.to_string (List.assoc "" p) in let len = String.length s in let encoding = List.assoc "encoding" p in match Lang.to_string encoding with | "ascii" -> Lang.bool (Lang_string.ascii_special_char s 0 len) | "utf8" -> Lang.bool (Lang_string.utf8_special_char s 0 len) | _ -> raise (Error.Invalid_value (encoding, "Encoding should be one of: \"ascii\" or \"utf8\"."))) let _ = Lang.add_builtin ~base:string "unescape" ~descr:"This function is the inverse of `string.escape`." ~category:`String [("", Lang.string_t, None, None)] Lang.string_t (fun p -> let s = Lang.to_string (List.assoc "" p) in Lang.string (Lang_string.unescape_string s)) let _ = Lang.add_builtin ~base:string "sub" ~category:`String ~descr: "Get a substring of a string. Returns \"\" if no such substring exists." [ ("", Lang.string_t, None, None); ( "start", Lang.int_t, None, Some "Return a sub string starting at this position. First position is 0." ); encoding_option; ( "length", Lang.int_t, None, Some "Return a sub string of `length` characters." ); ] Lang.string_t (fun p -> let start = Lang.to_int (List.assoc "start" p) in let len = Lang.to_int (List.assoc "length" p) in let _, encoding = get_encoding p in let string = Lang.to_string (List.assoc "" p) in let s = match encoding with | `Ascii -> ( try String.sub string start len with Invalid_argument _ -> "") | `Utf8 -> ( try let chars = Lang_string.split ~encoding string in if List.length chars < len + start then "" else String.concat "" (List.filteri (fun pos _ -> start <= pos && pos < start + len) chars) with _ -> "") in Lang.string s) let _ = Lang.add_builtin ~base:string "index" ~category:`String ~descr: "Index where a substring occurs in a string. The function returns `-1` \ if the substring is not present" [ ("substring", Lang.string_t, None, Some "Substring to look for."); ("", Lang.string_t, None, Some "String in which to look."); ] Lang.int_t (fun p -> let t = List.assoc "substring" p |> Lang.to_string in let s = List.assoc "" p |> Lang.to_string in let ans = let m = String.length t in let n = String.length s in let ans = ref (-1) in try for i = 0 to n - m do if String.sub s i m = t then ( ans := i; raise Exit) done; -1 with Exit -> !ans in Lang.int ans) let _ = Lang.add_builtin ~base:string "case" ~category:`String ~descr:"Convert a string to lower or upper case." [ ( "lower", Lang.bool_t, Some (Lang.bool true), Some "Convert to lower case if true and uppercase otherwise." ); ("", Lang.string_t, None, None); ] Lang.string_t (fun p -> let lower = Lang.to_bool (List.assoc "lower" p) in let string = Lang.to_string (List.assoc "" p) in Lang.string (if lower then String.lowercase_ascii string else String.uppercase_ascii string)) let _ = Lang.add_builtin ~base:string "trim" ~category:`String ~descr:"Return a string without leading and trailing whitespace." [("", Lang.string_t, None, None)] Lang.string_t (fun p -> Lang.string (String.trim (Lang.to_string (List.assoc "" p)))) let _ = Lang.add_builtin ~base:string "capitalize" ~category:`String ~descr: "Return a string with the first character set to upper case \ (capitalize), or to lower case (uncapitalize)." [ ( "capitalize", Lang.bool_t, Some (Lang.bool true), Some "Capitalize if true, uncapitalize otherwise" ); ( "space_sensitive", Lang.bool_t, Some (Lang.bool true), Some "Capitalize each space separated sub-string." ); ("", Lang.string_t, None, None); ] Lang.string_t (fun p -> let cap = Lang.to_bool (List.assoc "capitalize" p) in let space_sensitive = Lang.to_bool (List.assoc "space_sensitive" p) in let string = Lang.to_string (List.assoc "" p) in let f s = if cap then String.capitalize_ascii s else String.uncapitalize_ascii s in Lang.string (if space_sensitive then ( let l = String.split_on_char ' ' string in let l = List.map f l in String.concat " " l) else f string)) let _ = Lang.add_builtin ~base:string "hex_of_int" ~category:`String ~descr:"Hexadecimal representation of an integer." [ ( "pad", Lang.int_t, Some (Lang.int 0), Some "Minimum length in digits (pad on the left with zeros in order to \ reach it)." ); ("", Lang.int_t, None, None); ] Lang.string_t (fun p -> let pad = Lang.to_int (List.assoc "pad" p) in let n = Lang.to_int (List.assoc "" p) in let s = Printf.sprintf "%x" n in let s = let len = String.length s in if len < pad then String.make (pad - len) '0' ^ s else s in Lang.string s) (** Data conversions. *) let () = (* Register as [name] the function which composes [in_value],[func] and [out_value], and returns [default] in exceptional cases -- which MUST not occur when default is not supplied. *) let register_tt doc name category func ~needs_default in_type in_value out_value out_type = let raise_doc = if needs_default then [%string {| Raises `error.failure("%{doc}")` if conversion fails and default is `null`|}] else "" in ignore (Lang.add_builtin name ~category ~descr:[%string {|Convert %{doc}.%{raise_doc}|}] ([("", in_type, None, None)] @ if needs_default then [("default", Lang.nullable_t out_type, Some Lang.null, None)] else []) out_type (fun p -> try out_value (func (in_value (List.assoc "" p))) with _ -> ( try Option.get (Lang.to_option (List.assoc "default" p)) with _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message:name "failure"))) in let register_tts name func out_value out_type = register_tt ~needs_default:true ("a string to a " ^ name) (name ^ "_of_string") `String func Lang.string_t Lang.to_string out_value out_type in let register_tti name func out_value out_type = register_tt ~needs_default:false ("an int to a " ^ name) (name ^ "_of_int") `Math func Lang.int_t Lang.to_int out_value out_type in let register_ttf name func out_value out_type = register_tt ~needs_default:false ("a float to a " ^ name) (name ^ "_of_float") `Math func Lang.float_t Lang.to_float out_value out_type in register_tts "int" int_of_string (fun v -> Lang.int v) Lang.int_t; register_tts "float" float_of_string (fun v -> Lang.float v) Lang.float_t; register_tts "bool" bool_of_string (fun v -> Lang.bool v) Lang.bool_t; register_tti "float" float_of_int (fun v -> Lang.float v) Lang.float_t; register_tti "bool" (fun v -> v = 1) (fun v -> Lang.bool v) Lang.bool_t; register_ttf "int" int_of_float (fun v -> Lang.int v) Lang.int_t; register_ttf "bool" (fun v -> v = 1.) (fun v -> Lang.bool v) Lang.bool_t let _ = Lang.add_builtin ~base:string "float" ~category:`String ~descr:"String representation of a float." [ ( "decimal_places", Lang.nullable_t Lang.int_t, Some Lang.null, Some "Number of decimal places." ); ("", Lang.float_t, None, None); ] Lang.string_t (fun p -> let dp = List.assoc "decimal_places" p |> Lang.to_option |> Option.map Lang.to_int in let x = List.assoc "" p |> Lang.to_float in let s = match dp with | Some d -> Printf.sprintf "%.*f" d x | None -> Utils.string_of_float x in Lang.string s) let _ = Lang.add_builtin ~base:string "make" ~category:`String ~descr:"Create a string of a given length using the given character." [ ( "char_code", Lang.int_t, Some (Lang.int (Char.code ' ')), Some "Character code." ); ("", Lang.int_t, None, Some "String length."); ] Lang.string_t (fun p -> let n = Lang.to_int (List.assoc "" p) in if n < 0 then Runtime_error.raise ~pos:(Lang.pos p) ~message:"Invalid string length!" "invalid"; let c = try Char.chr (Lang.to_int (List.assoc "char_code" p)) with _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message:"Invalid character code!" "invalid" in Lang.string (String.make n c)) let string_base64 = Lang.add_module ~base:string "base64" let _ = Lang.add_builtin ~base:string_base64 "decode" ~category:`String ~descr:"Decode a Base64 encoded string." [("", Lang.string_t, None, None)] Lang.string_t (fun p -> let string = Lang.to_string (List.assoc "" p) in try Lang.string (Lang_string.decode64 string) with _ -> Runtime_error.raise ~pos:(Lang.pos p) ~message:"Invalid base64 string!" "invalid") let _ = Lang.add_builtin ~base:string_base64 "encode" ~category:`String ~descr:"Encode a string in Base64." [("", Lang.string_t, None, None)] Lang.string_t (fun p -> let string = Lang.to_string (List.assoc "" p) in Lang.string (Lang_string.encode64 string)) let url = Modules.url let _ = Lang.add_builtin ~base:url "decode" ~category:`String ~descr:"Decode an encoded url (e.g. \"%20\" becomes \" \")." [ ("plus", Lang.bool_t, Some (Lang.bool true), None); ("", Lang.string_t, None, None); ] Lang.string_t (fun p -> let plus = Lang.to_bool (List.assoc "plus" p) in let string = Lang.to_string (List.assoc "" p) in Lang.string (Lang_string.url_decode ~plus string)) let _ = Lang.add_builtin ~base:url "encode" ~category:`String ~descr:"Encode an url (e.g. \" \" becomes \"%20\")." [ ("plus", Lang.bool_t, Some (Lang.bool true), None); ("", Lang.string_t, None, None); ] Lang.string_t (fun p -> let plus = Lang.to_bool (List.assoc "plus" p) in let string = Lang.to_string (List.assoc "" p) in Lang.string (Lang_string.url_encode ~plus string)) liquidsoap-2.4.2/src/lang/base/builtins_xml.ml000066400000000000000000000163001513273233300213600ustar00rootroot00000000000000let rec methods_of_xml = function | Xml.PCData s -> ( "xml_text", Lang.meth (Lang.string s) (xml_node ~text:s ~params:[] ~children:[] ()) ) | Xml.Element (name, params, ([Xml.PCData s] as children)) -> (name, Lang.meth (Lang.string s) (xml_node ~text:s ~params ~children ())) | Xml.Element (name, params, children) -> ( name, Lang.record (Methods.bindings (List.fold_left (fun methods el -> let name, v = methods_of_xml el in let v = match Methods.find_opt name methods with | None -> v | Some (Value.Tuple { value = [] } as value) -> Lang.tuple [value; v] | Some (Value.Tuple { value }) -> Lang.tuple (value @ [v]) | Some value -> Lang.tuple [value; v] in Methods.add name v methods) (Methods.from_list (xml_node ~params ~children ())) children)) ) and xml_node ?text ~params ~children () = [ ("xml_text", match text with None -> Lang.null | Some s -> Lang.string s); ( "xml_params", Lang.meth (Lang.list (List.map (fun (k, v) -> Lang.product (Lang.string k) (Lang.string v)) params)) (List.map (fun (k, v) -> (k, Lang.string v)) params) ); ("xml_children", Lang.list (List.map (fun v -> value_of_xml v) children)); ] and value_of_xml v = let name, methods = methods_of_xml v in Lang.meth (Lang.tuple [Lang.string name; methods]) [(name, methods)] let rec check_value v ty = let typ_meths, ty = Type.split_meths ty in let meths, v = Value.split_meths v in let v = match (v, ty.Type.descr) with | Value.Tuple { value = [] }, Type.Nullable _ -> Lang.null | _, Type.Tuple [] -> Lang.tuple [] | Value.Tuple { value }, Type.Tuple l -> Lang.tuple (List.mapi (fun idx v -> check_value v (List.nth l idx)) value) | Value.List { value = l }, Type.List { t = ty } -> Lang.list (List.map (fun v -> check_value v ty) l) | Value.String { value = s }, Type.Int -> Lang.int (int_of_string s) | Value.String { value = s }, Type.Float -> Lang.float (float_of_string s) | Value.String { value = s }, Type.Bool -> Lang.bool (bool_of_string s) | Value.String _, Type.Nullable ty -> check_value v ty | _, Type.Var _ | Value.String _, Type.String -> v | _ -> assert false in let meths = List.fold_left (fun checked_meths { Type.meth; scheme = _, ty } -> let v = List.assoc meth meths in (meth, check_value v ty) :: checked_meths) [] typ_meths in let v = Lang.meth v meths in v let _ = Lang.add_builtin "_internal_xml_parser_" ~category:`String ~flags:[`Hidden] ~descr:"Internal xml parser" [ ("type", Value.RuntimeType.t, None, Some "Runtime type"); ("", Lang.string_t, None, None); ] (Lang.univ_t ()) (fun p -> let s = Lang.to_string (List.assoc "" p) in let ty = Value.RuntimeType.of_value (List.assoc "type" p) in let ty = Type.fresh ty in try let xml = Xml.parse_string s in let value = value_of_xml xml in check_value value ty with exn -> ( let bt = Printexc.get_raw_backtrace () in match exn with | _ -> Runtime_error.raise ~bt ~pos:(Lang.pos p) ~message: (Printf.sprintf "Parse error: xml value cannot be parsed as type: %s" (Type.to_string ty)) "xml")) let xml = Lang.add_module "xml" let string_of_ground v = match v with | Value.String { value = s } -> s | Value.Bool { value = b } -> string_of_bool b | Value.Float { value = f } -> Utils.string_of_float f | Value.Int { value = i; flags } -> Value.string_of_int_value ~flags i | _ -> assert false let params_of_xml_params v = match Lang.split_meths v with | [], Value.List { value = params } -> List.map (function | Value.Tuple { value = [Value.String { value = s }; v] } -> (s, string_of_ground v) | _ -> assert false) params | params, Value.Tuple { value = [] } -> List.map (fun (s, v) -> (s, string_of_ground v)) params | _ -> assert false let params_of_optional_params = function | None -> [] | Some params -> params_of_xml_params params let rec xml_of_value = function | Value.Tuple { value = [Value.String { value = name }; Value.Tuple { value = []; methods }]; } -> xml_of_node ~name (Methods.bindings methods) | Value.Tuple { value = []; methods } -> ( match Methods.bindings methods with | [(name, Value.Tuple { value = []; methods })] -> xml_of_node ~name (Methods.bindings methods) | [(name, (Value.String { methods } as v))] | [(name, (Value.Float { methods } as v))] | [(name, (Value.Int { methods } as v))] | [(name, (Value.Bool { methods } as v))] -> xml_of_node ~xml_text:(string_of_ground v) ~name (Methods.bindings methods) | _ -> assert false) | _ -> assert false and xml_of_node ?xml_text ~name meths = let xml_text = match xml_text with | Some s -> Some s | None -> Option.map Lang.to_string (List.assoc_opt "xml_text" meths) in let xml_children = Option.map Lang.to_list (List.assoc_opt "xml_children" meths) in let xml_params = List.assoc_opt "xml_params" meths in let meths = List.filter (fun (k, _) -> not (List.mem k ["xml_text"; "xml_children"; "xml_params"])) meths in match (name, xml_params, xml_children, xml_text, meths) with | "xml_text", None, None, Some s, [] -> Xml.PCData s | name, xml_params, None, Some s, [] -> Xml.Element (name, params_of_optional_params xml_params, [Xml.PCData s]) | name, xml_params, Some nodes, None, [] -> Xml.Element ( name, params_of_optional_params xml_params, List.map xml_of_value nodes ) | name, xml_params, None, None, nodes -> Xml.Element ( name, params_of_optional_params xml_params, List.map (fun (name, value) -> xml_of_value (Lang.record [(name, value)])) nodes ) | _ -> assert false let _ = Lang.add_builtin ~base:xml "stringify" ~category:`String ~descr: "Convert a value to XML. If the value cannot be represented as XML (for \ instance a function), a `error.xml` exception is raised." [ ( "compact", Lang.bool_t, Some (Lang.bool false), Some "Output compact text." ); ("", Lang.univ_t (), None, None); ] Lang.string_t (fun p -> let v = List.assoc "" p in let compact = Lang.to_bool (List.assoc "compact" p) in try let xml = xml_of_value v in Lang.string (if compact then Xml.to_string xml else Xml.to_string_fmt xml) with _ -> let bt = Printexc.get_raw_backtrace () in Runtime_error.raise ~bt ~pos:(Lang.pos p) ~message:"Value could not be converted to XML!" "xml") liquidsoap-2.4.2/src/lang/base/builtins_yaml.ml000066400000000000000000000033321513273233300215230ustar00rootroot00000000000000type yaml = [ `Null | `Bool of bool | `Float of float | `String of string | `A of yaml list | `O of (string * yaml) list ] let yaml_parser : (string -> yaml) Atomic.t = Atomic.make (fun _ -> Runtime_error.raise ~message: "YAML support not enabled! Please re-compile liquidsoap with the \ `yaml` module to enable YAML parsing and rendering." ~pos:[] "not_found") let rec json_of_yaml = function | `O l -> `Assoc (List.map (fun (lbl, v) -> (lbl, json_of_yaml v)) l) | `A l -> `Tuple (List.map json_of_yaml l) | `String s -> `String s | `Bool b -> `Bool b | `Float f -> `Float f | `Null -> `Null let _ = Lang.add_builtin "_internal_yaml_parser_" ~category:`String ~flags:[`Hidden] ~descr:"Internal yaml parser" [ ("type", Value.RuntimeType.t, None, Some "Runtime type"); ("", Lang.string_t, None, None); ] (Lang.univ_t ()) (fun p -> let s = Lang.to_string (List.assoc "" p) in let ty = Value.RuntimeType.of_value (List.assoc "type" p) in let ty = Type.fresh ty in let parser = Atomic.get yaml_parser in try let yaml = parser s in Builtins_json.value_of_typed_json ~ty (json_of_yaml yaml) with exn -> ( let bt = Printexc.get_raw_backtrace () in match exn with | Runtime_error.Runtime_error e when e.Runtime_error.kind = "not_found" -> Printexc.raise_with_backtrace exn bt | _ -> Runtime_error.raise ~bt ~pos:(Lang.pos p) ~message: (Printf.sprintf "Parse error: yaml value cannot be parsed as type: %s" (Type.to_string ty)) "yaml")) liquidsoap-2.4.2/src/lang/base/cache.ml000066400000000000000000000112711513273233300177140ustar00rootroot00000000000000type dirtype = [ `System | `User ] let enabled () = try let venv = Unix.getenv "LIQ_CACHE" in venv = "1" || venv = "true" with Not_found -> true let system_dir_override = ref (fun () -> None) let user_dir_override = ref (fun () -> None) let system_dir_perms = ref 0o755 let system_file_perms = ref 0o644 let user_dir_perms = ref 0o700 let user_file_perms = ref 0o600 let default_user_dir () = try Some (Unix.getenv "LIQ_CACHE_USER_DIR") with Not_found -> ( let fn = !user_dir_override in match fn () with | Some d -> Some d | _ -> Some (Filename.concat (Filename.concat (Unix.getenv "HOME") ".cache") "liquidsoap")) let default_system_dir () = try Some (Unix.getenv "LIQ_CACHE_SYSTEM_DIR") with Not_found -> ( let fn = !system_dir_override in match (fn (), Sites.Sites.cache) with | Some d, _ | _, d :: _ -> Some d | _ -> None) let rec recmkdir ~dirtype dir = let perms = match dirtype with `System -> !system_dir_perms | `User -> !user_dir_perms in if not (Sys.file_exists dir) then ( recmkdir ~dirtype (Filename.dirname dir); Sys.mkdir dir perms) let dir dirtype = if enabled () then ( match let fn = match dirtype with | `User -> default_user_dir | `System -> default_system_dir in fn () with | None -> Startup.message "Could not find default cache directory! You can set it using the \ `$LIQ_CACHE_DIR` environment variable."; None | Some _ as v -> v) else ( Startup.message "Cache disabled!"; None) let retrieve ?name ~dirtype filename = try match dir dirtype with | None -> None | Some dir -> let filename = Filename.concat dir filename in if Sys.file_exists filename then ( let ic = open_in_bin filename in Fun.protect ~finally:(fun () -> close_in_noerr ic) (fun () -> let value = Marshal.from_channel ic in (match name with | Some name -> Startup.message "Loading %s from cache!" name | None -> ()); Some value)) else None with | Failure msg when String.starts_with ~prefix:"input_value: unknown code module" msg -> (match name with | Some name -> Startup.message "Liquidsoap binary changed: %s cache invalidated!" name | None -> ()); None | exn -> let bt = Printexc.get_backtrace () in let exn = Printexc.to_string exn in if Sys.getenv_opt "LIQ_DEBUG_CACHE" <> None then Startup.message "Error while loading cache: %s\n%s" exn bt else Startup.message "Error while loading cache: %s" exn; None let store ~dirtype filename value = try match dir dirtype with | None -> () | Some dir -> recmkdir ~dirtype dir; let filename = Filename.concat dir filename in let perms = match dirtype with | `User -> !user_file_perms | `System -> !system_file_perms in let tmp_file, oc = Filename.open_temp_file ~mode:[Open_binary] ~temp_dir:(Filename.dirname filename) ~perms "tmp" ".liq-cache" in Fun.protect ~finally:(fun () -> close_out_noerr oc; if Sys.file_exists tmp_file then Sys.remove tmp_file) (fun () -> Marshal.to_channel oc value [Marshal.Closures]; close_out_noerr oc; Sys.rename tmp_file filename); let fn = !Hooks.cache_maintenance in fn dirtype with exn -> let bt = Printexc.get_backtrace () in let exn = Printexc.to_string exn in if Sys.getenv_opt "LIQ_DEBUG_CACHE" <> None then Startup.message "Error while loading cache: %s\n%s" exn bt else Startup.message "Error while loading cache: %s" exn (** A key-value table in cache. *) module Table = struct module Map = Map.Make (String) type 'a t = { fname : string; mutable table : 'a Map.t; mutable changed : bool; } let load ?name ~dirtype fname = { fname; table = Option.value ~default:Map.empty (retrieve ?name ~dirtype fname); changed = false; } (* Get an element, and provide a function to compute it if not cached. *) let get t k f = match Map.find_opt k t.table with | Some v -> v | None -> let v = f () in t.table <- Map.add k v t.table; t.changed <- true; v let store ~dirtype t = if t.changed then store ~dirtype t.fname t.table end liquidsoap-2.4.2/src/lang/base/cache.mli000066400000000000000000000012031513273233300200570ustar00rootroot00000000000000type dirtype = [ `System | `User ] val user_file_perms : int ref val user_dir_perms : int ref val system_file_perms : int ref val system_dir_perms : int ref val enabled : unit -> bool val user_dir_override : (unit -> string option) ref val system_dir_override : (unit -> string option) ref val dir : dirtype -> string option val retrieve : ?name:string -> dirtype:dirtype -> string -> 'a option val store : dirtype:dirtype -> string -> 'a -> unit module Table : sig type 'a t val load : ?name:string -> dirtype:dirtype -> string -> 'a t val get : 'a t -> string -> (unit -> 'a) -> 'a val store : dirtype:dirtype -> 'a t -> unit end liquidsoap-2.4.2/src/lang/base/doc.ml000066400000000000000000000601061513273233300174170ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) module Map = Map.Make (struct type t = string let compare (x : string) (y : string) = compare x y end) (** Documentation for plugs. *) module Plug = struct type t = { name : string; description : string; mutable items : (string * string) list; (** an item with given name and description *) } let db = ref [] let create ~doc name = let d = { name; description = doc; items = [] } in db := d :: !db; d let add d ~doc name = assert (not (List.mem_assoc name d.items)); d.items <- (name, doc) :: d.items let db () = List.sort compare !db let print_md print = List.iter (fun p -> Printf.ksprintf print "# %s\n\n%s\n\n" p.name p.description; List.iter (fun (name, description) -> print ("- " ^ name ^ ": " ^ description ^ "\n")) p.items; print "\n") (db ()) let print_string = print_md end (** Documenentation for values. *) module Value = struct (** Documentation flags. *) type flag = [ `Hidden | `Deprecated | `Experimental | `Extra ] let string_of_flag : flag -> string = function | `Hidden -> "hidden" | `Deprecated -> "deprecated" | `Experimental -> "experimental" | `Extra -> "extra" let flag_of_string = function | "hidden" -> Some `Hidden | "deprecated" -> Some `Deprecated | "experimental" -> Some `Experimental | "extra" -> Some `Extra | _ -> None (** Kind of source. *) type source = [ `Input | `Output | `Conversion | `FFmpegFilter | `Track | `Audio | `Video | `MIDI | `Visualization | `Synthesis | `Testing | `Fade | `Liquidsoap ] type category = [ `Source of source | `Track of source | `System | `File | `Math | `String | `List | `Bool | `Getter | `Time | `Liquidsoap | `Metadata | `Programming | `Interaction | `Internet | `Configuration | `Settings | `None ] let categories : (category * string) list = [ (`Source `Input, "Source / Input"); (`Source `Output, "Source / Output"); (`Source `Conversion, "Source / Conversion"); (`Source `FFmpegFilter, "Source / FFmpeg filter"); (`Source `Track, "Source / Track processing"); (`Source `Audio, "Source / Audio processing"); (`Source `Video, "Source / Video processing"); (`Source `MIDI, "Source / MIDI processing"); (`Source `Synthesis, "Source / Sound synthesis"); (`Source `Visualization, "Source / Visualization"); (`Source `Liquidsoap, "Source / Liquidsoap"); (`Source `Fade, "Source / Fade"); (`Source `Testing, "Source / Testing"); (`Track `Input, "Track / Input"); (`Track `Output, "Track / Output"); (`Track `Conversion, "Track / Conversion"); (`Track `FFmpegFilter, "Track / FFmpeg filter"); (`Track `Track, "Track / Track processing"); (`Track `Audio, "Track / Audio processing"); (`Track `Video, "Track / Video processing"); (`Track `MIDI, "Track / MIDI processing"); (`Track `Synthesis, "Track / Sound synthesis"); (`Track `Visualization, "Track / Visualization"); (`Track `Liquidsoap, "Track / Liquidsoap"); (`Track `Fade, "Track / Fade"); (`System, "System"); (`Configuration, "Configuration"); (`Settings, "Settings"); (`File, "File"); (`Math, "Math"); (`String, "String"); (`List, "List"); (`Bool, "Bool"); (`Getter, "Getter"); (`Liquidsoap, "Liquidsoap"); (`Metadata, "Metadata"); (`Programming, "Programming"); (`Interaction, "Interaction"); (`Internet, "Internet"); (`Time, "Time"); (`None, "Uncategorized"); ] let string_of_category c = List.assoc c categories let category_of_string s = let rec aux = function | (c, s') :: _ when s = s' -> Some c | _ :: l -> aux l | [] -> None in aux categories type argument = { arg_type : string; arg_default : string option; (** default value *) arg_description : string option; } type meth = { meth_type : string; meth_description : string option } (** Documentation for a function. *) type t = { typ : string; category : category; flags : flag list; description : string; examples : string list; arguments : (string option * argument) list; methods : (string * meth) list; callbacks : (string * meth) list; } let db = ref Map.empty let add (name : string) (doc : t Lazy.t) = let name = Re.replace ~all:true ~f:(fun _ -> "null") (Re.Pcre.regexp "^_null") name in db := Map.add name doc !db let get name = Lazy.force (Map.find name !db) let count () = Map.cardinal !db (** Only print function names. *) let print_functions print = Map.iter (fun f _ -> print f; print "\n") !db let print_functions_by_category print = let categories = categories |> List.map (fun (c, s) -> (s, c)) |> List.sort compare in List.iter (fun (category_name, category) -> print ("# " ^ category_name ^ "\n\n"); Map.iter (fun f d -> let d = Lazy.force d in if d.category = category && not (List.mem `Hidden d.flags) then print ("- " ^ f ^ "\n")) !db; print "\n") categories let colorize = Console.colorize let title_color = colorize [`bold] let type_color = colorize [`yellow] let default_color = colorize [`red; `bold] let label_color = colorize [`cyan; `bold] let print name print = let f = get name in let reflow ?(indent = 0) ?(cols = 70) s = let buf = Buffer.create 1024 in (* Did we just see a backtick? *) let backtick = ref false in (* Are we allowed to reflow? *) let protected = ref false in (* Length of the current line *) let n = ref 0 in let indent () = for _ = 1 to indent do Buffer.add_char buf ' ' done in let newline () = Buffer.add_char buf '\n'; indent (); n := 0 in let char c = Buffer.add_char buf c; incr n in let space () = if !n >= cols then newline () else char ' ' in indent (); String.iter (fun c -> if c = '`' then ( if not !backtick then protected := not !protected; backtick := true) else backtick := false; if (not !protected) && c = ' ' then space () else if c = '\n' then newline () else char c) s; Buffer.contents buf in print (title_color f.description); print "\n\n"; print (title_color "Type: "); print f.typ; print "\n\n"; print (title_color "Category: " ^ string_of_category f.category ^ "\n\n"); if f.flags <> [] then ( let flags = f.flags |> List.map string_of_flag |> String.concat "," in print (title_color "Flags: " ^ flags ^ "\n\n")); List.iter (fun e -> print (title_color "Example:\n\n"); print e; print "\n\n") f.examples; print (title_color "Arguments:\n\n"); List.iter (fun (l, a) -> let l = Option.value ~default:"(unlabeled)" l in let l = label_color l in let default = match a.arg_default with | Some d -> " (default: " ^ default_color d ^ ")" | None -> "" in print (" * " ^ l ^ " : " ^ type_color a.arg_type ^ default ^ "\n"); Option.iter (fun d -> print (reflow ~indent:5 d)) a.arg_description; print "\n\n") (List.stable_sort (fun v v' -> match (v, v') with | (None, _), (None, _) -> 0 | (None, _), _ -> 1 | _, (None, _) -> -1 | (l, _), (l', _) -> Stdlib.compare l l') f.arguments); if f.methods <> [] then ( print (title_color "Methods:\n\n"); List.iter (fun (l, m) -> print (" * " ^ label_color l ^ " : " ^ type_color m.meth_type ^ "\n"); Option.iter (fun d -> print (reflow ~indent:5 d)) m.meth_description; print "\n\n") (List.sort compare f.methods)); if f.callbacks <> [] then ( print (title_color "Callbacks:\n\n"); List.iter (fun (l, m) -> print (" * " ^ label_color l ^ " : " ^ type_color m.meth_type ^ "\n"); Option.iter (fun d -> print (reflow ~indent:5 d)) m.meth_description; print "\n\n") (List.sort compare f.callbacks)) let to_json () : Json.t = !db |> Map.to_seq |> Seq.map (fun (l, f) -> let f = Lazy.force f in let arguments = List.map (fun (l, a) -> ( Option.value ~default:"" l, `Assoc [ ("type", `String a.arg_type); ( "default", Option.fold ~none:`Null ~some:(fun d -> `String d) a.arg_default ); ( "description", `String (Option.value ~default:"" a.arg_description) ); ] )) f.arguments in let arguments = `Assoc arguments in let methods, callbacks = match List.map (fun m -> `Assoc (List.map (fun (l, m) -> ( l, `Assoc [ ("type", `String m.meth_type); ( "description", `String (Option.value ~default:"" m.meth_description) ); ] )) m)) [f.methods; f.callbacks] with | [x; v] -> (x, v) | _ -> assert false in ( l, `Assoc [ ("type", `String f.typ); ("category", `String (string_of_category f.category)); ( "flags", `Tuple (List.map string_of_flag f.flags |> List.map (fun s -> `String s)) ); ("description", `String f.description); ("examples", `Tuple (List.map (fun s -> `String s) f.examples)); ("arguments", arguments); ("methods", methods); ("callbacks", callbacks); ] )) |> List.of_seq |> fun l -> `Assoc l let print_functions_md ?extra ?deprecated print = let should_show ~category d = let d = Lazy.force d in (not (List.mem `Hidden d.flags)) && ((not (deprecated = Some true)) || List.mem `Deprecated d.flags) && (deprecated = Some true || not (List.mem `Deprecated d.flags)) && d.category = category && ((not (extra = Some true)) || List.mem `Extra d.flags) && ((not (extra = Some false)) || not (List.mem `Extra d.flags)) in let categories = categories |> List.map (fun (c, s) -> (s, c)) |> List.filter (fun (_, category) -> Map.exists (fun _ d -> should_show ~category d) !db) |> List.sort compare in List.iter (fun (category, _) -> let slug s = let s = String.lowercase_ascii s in let r = Str.regexp "[ /]+" in Str.global_replace r "-" s in Printf.ksprintf print "- [%s](#%s)\n" category (slug category)) categories; print "\n"; List.iter (fun (category_name, category) -> print ("## " ^ category_name ^ "\n\n"); Map.iter (fun f d -> if should_show ~category d then ( let d = Lazy.force d in print ("### `" ^ f ^ "`\n\n"); print d.description; print "\n\n"; print "Type:\n\n```\n"; print d.typ; print "\n```\n\n"; List.iter (fun e -> print "Example:\n\n"; Printf.ksprintf print "```liquidsoap\n%s\n```\n\n" e) d.examples; if d.arguments <> [] then ( print "Arguments:\n\n"; List.iter (fun (l, a) -> let l = Option.value ~default:"(unlabeled)" l in let t = a.arg_type in let d = match a.arg_default with | None -> "" | Some d -> ", which defaults to `" ^ d ^ "`" in let s = match a.arg_description with | None -> "" | Some s -> ": " ^ s in Printf.ksprintf print "- `%s` (of type `%s`%s)%s\n" l t d s) d.arguments; print "\n"); if d.methods <> [] then ( print "Methods:\n\n"; List.iter (fun (l, m) -> let t = m.meth_type in let s = match m.meth_description with | None -> "" | Some s -> ": " ^ s in Printf.ksprintf print "- `%s` (of type `%s`)%s\n" l t s) (List.sort compare d.methods); print "\n"); if d.callbacks <> [] then ( print "Callbacks:\n\n"; List.iter (fun (l, m) -> let t = m.meth_type in let s = match m.meth_description with | None -> "" | Some s -> ": " ^ s in Printf.ksprintf print "- `%s` (of type `%s`)%s\n" l t s) (List.sort compare d.callbacks); print "\n"); if List.mem `Experimental d.flags then print "This function is experimental.\n\n")) !db) categories let print_emacs_completions print = print "(defconst liquidsoap-completions '(\n"; Map.iter (fun name f -> let f = Lazy.force f in if not (List.mem `Hidden f.flags || List.mem `Deprecated f.flags) then ( let t = String.map (fun c -> if c = '\n' then ' ' else c) f.typ in Printf.ksprintf print "#(\"%s\" 0 1 (:type \"%s\" :description \"%s\"))\n" name t (String.escaped f.description))) !db; print "))\n\n"; print "(provide 'liquidsoap-completions)\n" end type doc_type = [ `Full | `Argsof of string list ] type doc = { main : string list; special : [ `Category of string | `Flag of string ] list; params : (string option * string) list; methods : (string * string) list; callbacks : (string * string) list; } let parse_doc ~pos doc = let doc = String.split_on_char '\n' doc in let doc = List.map (fun x -> Re.replace ~all:true ~f:(fun _ -> "") (Re.Pcre.regexp "^\\s*#\\s?") x) doc in if doc = [] then None else ( let rec parse_doc ({ main; special; params; methods; callbacks } as _doc) = function | [] -> _doc | line :: lines -> ( try let sub = Re.Pcre.exec ~rex: (Re.Pcre.regexp "^\\s*@(category|docof|flag|param|method|argsof)\\s*(.*)$") line in let s = Re.Pcre.get_substring sub 2 in match Re.Pcre.get_substring sub 1 with | "docof" -> let doc = Value.get s in let main = if doc.description <> "" then doc.description :: main else main in let params = List.filter_map (fun (l, a) -> match a.Value.arg_description with | Some d -> Some (l, d) | None -> None) doc.arguments @ params in let doc_specials = `Category (Value.string_of_category doc.category) :: List.map (fun f -> `Flag (Value.string_of_flag f)) doc.flags in parse_doc { _doc with main; special = doc_specials @ special; params } lines | "argsof" -> let s, only, except = try let sub = Re.Pcre.exec ~rex: (Re.Pcre.regexp "^\\s*([^\\[]+)\\[([^\\]]+)\\]\\s*$") s in let s = Re.Pcre.get_substring sub 1 in let args = List.filter (fun s -> s <> "") (List.map String.trim (String.split_on_char ',' (Re.Pcre.get_substring sub 2))) in let only, except = List.fold_left (fun (only, except) v -> if String.length v > 0 && v.[0] = '!' then ( only, String.sub v 1 (String.length v - 1) :: except ) else (v :: only, except)) ([], []) args in (s, only, except) with Not_found -> (s, [], []) in let doc = Value.get s in let args = List.filter (fun (n, _) -> match n with | None -> false | Some n -> ( match (only, except) with | [], except -> not (List.mem n except) | only, except -> List.mem n only && not (List.mem n except))) doc.arguments in let args = List.filter_map (fun (n, a) -> Option.map (fun d -> (n, d)) a.Value.arg_description) args in parse_doc { _doc with main; params = args @ params } lines | "category" -> parse_doc { _doc with special = `Category s :: special } lines | "flag" -> parse_doc { _doc with special = `Flag s :: special } lines | "param" -> let sub = Re.Pcre.exec ~rex:(Re.Pcre.regexp "^(~?[a-zA-Z0-9_.]+)\\s*(.*)$") s in let label = Re.Pcre.get_substring sub 1 in let descr = Re.Pcre.get_substring sub 2 in let label = if label.[0] = '~' then Some (String.sub label 1 (String.length label - 1)) else None in let rec parse_descr descr lines = match lines with | [] -> raise Not_found | line :: lines -> let line = Re.replace ~all:true ~f:(fun _ -> "") (Re.Pcre.regexp "^ *") line in let n = String.length line - 1 in if line.[n] = '\\' then ( let descr = String.sub line 0 n :: descr in parse_descr descr lines) else ( let descr = List.rev (line :: descr) in (String.concat "" descr, lines)) in let descr, lines = parse_descr [] (descr :: lines) in parse_doc { _doc with params = (label, descr) :: params } lines | "method" -> let sub = Re.Pcre.exec ~rex:(Re.Pcre.regexp "^(~?[a-zA-Z0-9_.]+)\\s*(.*)$") s in let label = Re.Pcre.get_substring sub 1 in let descr = Re.Pcre.get_substring sub 2 in parse_doc { _doc with methods = (label, descr) :: methods } lines | "callback" -> let sub = Re.Pcre.exec ~rex:(Re.Pcre.regexp "^(~?[a-zA-Z0-9_.]+)\\s*(.*)$") s in let label = Re.Pcre.get_substring sub 1 in let descr = Re.Pcre.get_substring sub 2 in parse_doc { _doc with callbacks = (label, descr) :: callbacks } lines | d -> failwith ("Unknown documentation item: " ^ d) with Not_found -> parse_doc { _doc with main = line :: main } lines) in let { main; special; params; methods; callbacks } = parse_doc { main = []; special = []; params = []; methods = []; callbacks = [] } doc in let main = List.rev main in let params = List.map (fun (l, d) -> ( l, Value. { arg_type = "???"; arg_default = None; arg_description = Some d } )) (List.rev params) in let methods = List.map (fun (l, d) -> (l, Value.{ meth_type = "???"; meth_description = Some d })) (List.rev methods) in let callbacks = List.map (fun (l, d) -> (l, Value.{ meth_type = "???"; meth_description = Some d })) (List.rev callbacks) in let main = String.concat "\n" main in let main = Lang_string.unbreak_md main in (* let main = String.concat "\n" main in *) let category, flags = List.fold_left (fun (c, f) s -> match s with `Category c -> (c, f) | `Flag flag -> (c, flag :: f)) ("Uncategorized", []) special in let category = String.trim category in let category = match Value.category_of_string category with | Some c -> c | None -> failwith (Printf.sprintf "Unknown category: %s (%s)." category (Pos.to_string pos)) in let flags = let f f = match Value.flag_of_string f with | Some f -> f | None -> failwith (Printf.sprintf "Unknown flag: %s (%s)." f (Pos.to_string pos)) in List.map f flags in Some Value. { (* filled in later on *) typ = "???"; category; flags; description = main; (* TODO *) examples = []; arguments = params; methods; callbacks; }) liquidsoap-2.4.2/src/lang/base/dune000066400000000000000000000050051513273233300171730ustar00rootroot00000000000000(include_subdirs unqualified) (generate_sites_module (module sites) (sites liquidsoap-lang)) (menhir (modules parser json_parser) (flags --unused-token NULLDOT --unused-token DOTVAR --unused-token PP_ELSE --unused-token PP_ENDIF --unused-token PP_ENDL --unused-token PP_IFDEF --unused-token PP_IFENCODER --unused-token PP_IFNDEF --unused-token PP_IFNENCODER --unused-token PP_IFVERSION --unused-token PP_INT_DOT_LCUR --unused-token PP_REGEXP --unused-token PP_STRING --unused-token REPLACES --unused-token SLASH)) (rule (target build_config.ml) (action (with-stdout-to %{target} (progn (echo "let is_snapshot = %{env:IS_SNAPSHOT=true}\n") (echo "let version = \"%{version:liquidsoap-lang}\"\n") (echo "let version_len = String.length version\n") (echo "let parsed_sha = String.trim \"") (with-accepted-exit-codes (or 0 128) (run git rev-parse --short HEAD)) (echo "\"\n") (echo "let parsed_sha_len = String.length parsed_sha\n") (echo "let dune_sha_len = 7\n") (echo "let git_sha = match is_snapshot, parsed_sha with \n") (echo " | false, _ | _, \"\" -> None\n") (echo " | _, _ when dune_sha_len <= version_len && dune_sha_len <= parsed_sha_len && String.sub parsed_sha 0 dune_sha_len = String.sub version (version_len - dune_sha_len) dune_sha_len -> None\n") (echo " | _, s -> Some s\n") (echo "let git_sha = match Sys.getenv_opt \"LIQ_GIT_SHA\" with None -> git_sha | Some v -> Some v \n") (echo "let version = version ^ (if not is_snapshot then \"\" else (match git_sha with None -> \"+dev\" | Some sha -> \"+git@\" ^ sha))\n") (echo "let version = match Sys.getenv_opt \"LIQ_VERSION\" with None -> version | Some v -> v \n") (echo "let ext_exe = \"%{ocaml-config:ext_exe}\"\n") (echo "let architecture = \"%{ocaml-config:architecture}\"\n") (echo "let host = \"%{ocaml-config:host}\"\n") (echo "let target = \"%{ocaml-config:target}\"\n") (echo "let system = \"%{ocaml-config:system}\"\n") (echo "let ocamlopt_cflags = \"%{ocaml-config:ocamlopt_cflags}\"\n") (echo "let native_c_compiler = \"%{ocaml-config:native_c_compiler}\"\n") (echo "let native_c_libraries = \"%{ocaml-config:native_c_libraries}\"\n"))))) (library (name liquidsoap_lang) (public_name liquidsoap-lang) (preprocess (pps sedlex.ppx ppx_string ppx_hash)) (libraries liquidsoap-lang.console liquidsoap-lang.stdlib dune-site fileutils re str menhirLib xml-light)) liquidsoap-2.4.2/src/lang/base/environment.ml000066400000000000000000000121301513273233300212100ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** {1 Evaluation environment} *) module Env = Value.Methods let type_environment : Type.scheme Env.t ref = ref Env.empty let value_environment : Value.t Env.t ref = ref Env.empty let default_environment () = Env.bindings !value_environment let default_typing_environment () = Env.bindings !type_environment (* Just like builtins but we register a.b under the name "a.b" (instead of adding a field b to a). It is used only for [has_builtins] and [get_builtins]. *) let flat_enviroment : (string * (Type.scheme * Value.t)) list ref = ref [] let clear_toplevel_environments () = type_environment := Env.empty; flat_enviroment := [] let has_builtin name = List.mem_assoc name !flat_enviroment let get_builtin name = List.assoc_opt name !flat_enviroment let add_builtin ?(override = false) ?(register = true) ?doc name ((g, t), v) = if register && doc <> None then Doc.Value.add (String.concat "." name) (Option.get doc); flat_enviroment := (String.concat "." name, ((g, t), v)) :: !flat_enviroment; match name with | [name] -> (* Don't allow overriding builtins. *) if (not override) && Env.mem name !type_environment then failwith ("Trying to override builtin " ^ name); type_environment := Env.add name (g, t) !type_environment; value_environment := Env.add name v !value_environment | x :: ll -> let (g0, t0), v0 = try (Env.find x !type_environment, Env.find x !value_environment) with Not_found -> failwith ("Could not find builtin variable " ^ x) in (* x.l1.l2.l3 = v means x = (x where l1 = (x.l1 where l2 = (x.l1.l2 where l3 = v))) *) (* Inductive step: we compute the new type scheme and value of x.l1...li. The variable prefix contains [li; ...; l1] and the second argument is [li+1; ...; ln]. *) let rec aux (g0, t0) v0 = function | l :: [] -> let t = Type.make ?pos:t.Type.pos Type.( Meth ( { meth = l; optional = false; scheme = (g, t); doc = { meth_descr = ""; category = `Method }; json_name = None; }, t0 )) in ((g0, t), Value.map_methods v0 (Methods.add l v)) | l :: ll -> let (vg, vt), v = aux (Type.invoke t0 l) (Value.invoke v0 l) ll in let t = Type.make ?pos:t.Type.pos Type.( Meth ( { meth = l; optional = false; scheme = (vg, vt); doc = { meth_descr = ""; category = `Method }; json_name = None; }, t0 )) in ((g0, t), Value.map_methods v0 (Methods.add l v)) | [] -> ((g, t), v) in let (g, t), v = aux (g0, t0) v0 ll in assert (g == g0); type_environment := Env.add x (g0, t) !type_environment; value_environment := Env.add x v !value_environment | [] -> assert false (** Declare a module. *) let add_module name = (* Ensure that it does not already exist. *) (match name with | [] -> assert false | [x] -> if Env.mem x !type_environment then failwith ("Module " ^ String.concat "." name ^ " already declared") | x :: mm -> ( let mm = List.rev mm in let l = List.hd mm in let mm = List.rev (List.tl mm) in let e = try Value.invokes (Env.find x !value_environment) mm with _ -> failwith ("Could not find the parent module of " ^ String.concat "." name) in try ignore (Value.invoke e l); failwith ("Module " ^ String.concat "." name ^ " already exists") with _ -> ())); add_builtin ~register:false name (([], Type.make Type.unit), Value.(make unit)) liquidsoap-2.4.2/src/lang/base/environment.mli000066400000000000000000000033651513273233300213730ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** {1 Builtins} *) (** Whether a given builtin was declared. *) val has_builtin : string -> bool (** Retrieve a builtin definition. *) val get_builtin : string -> (Type.scheme * Value.t) option (** Add a builtin value. *) val add_builtin : ?override:bool -> ?register:bool -> ?doc:Doc.Value.t Lazy.t -> string list -> Type.scheme * Value.t -> unit (** Declare a module. *) val add_module : string list -> unit (** {1 Environments} *) (** Initial typing environment (with builtins). *) val default_typing_environment : unit -> (string * Type.scheme) list (** Initial environment (with builtins). *) val default_environment : unit -> (string * Value.t) list (** Clear all environments. *) val clear_toplevel_environments : unit -> unit liquidsoap-2.4.2/src/lang/base/error.ml000066400000000000000000000042101513273233300177750ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Runtime error, should eventually disappear. *) exception Invalid_value of Value.t * string exception Clock_conflict of (Pos.Option.t * string * string) exception Clock_loop of (Pos.Option.t * string * string) type clock_main = { pos : Pos.Option.t; left_main : string; left_child : string; right_main : string; right_child : string; } exception Clock_main of clock_main let () = Printexc.register_printer (function | Clock_conflict (pos, a, b) -> let pos = Pos.Option.to_string pos in Some (Printf.sprintf "Clock unification error: At position: %s, clocks %s and %s \ cannot be unified" pos a b) | Clock_main { pos; left_main; left_child; right_main; right_child } -> let pos = Pos.Option.to_string pos in Some (Printf.sprintf "Clock unification error: At position: %s, clocks %s and %s \ cannot be unified: clock %s is controlled by clock %s while \ clock %s is controlled by %s." pos left_child right_child left_child left_main right_child right_main) | _ -> None) liquidsoap-2.4.2/src/lang/base/error.mli000066400000000000000000000026051513273233300201540ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Runtime error, should eventually disappear. *) exception Invalid_value of Value.t * string exception Clock_conflict of (Pos.Option.t * string * string) exception Clock_loop of (Pos.Option.t * string * string) type clock_main = { pos : Pos.Option.t; left_main : string; left_child : string; right_main : string; right_child : string; } exception Clock_main of clock_main liquidsoap-2.4.2/src/lang/base/evaluation.ml000066400000000000000000000460051513273233300210230ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** {1 Evaluation} *) open Term (** [remove_first f l] removes the first element [e] of [l] such that [f e], and returns [e,l'] where [l'] is the list without [e]. Asserts that there is such an element. *) let remove_first filter = let rec aux acc = function | [] -> assert false | hd :: tl -> if filter hd then (hd, List.rev_append acc tl) else aux (hd :: acc) tl in aux [] let eval_pat pat v = let aux env pat v = match (pat, v) with | `PVar x, v -> (x, v) :: env | `PTuple pl, Value.Tuple { value = l } -> List.fold_left2 (fun env lbl v -> ([lbl], v) :: env) env pl l | _ -> assert false in aux [] pat v module Env = struct type t = Value.env (** Find the value of a variable in the environment. *) let lookup (env : t) var = try List.assoc var env with Not_found -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Failure (Printf.sprintf "Internal error: variable %s not in environment." var)) bt (** Restrict an environment to a given set of variables. *) let restrict (env : t) vars = let vars = ref vars in let mem x = if Vars.mem x !vars then ( vars := Vars.remove x !vars; true) else false in List.filter (fun (x, _) -> mem x) env (** Bind a variable to a lazy value in an environment. *) let add (env : t) x v : t = (x, v) :: env (** Bind multiple variables in an environment. *) let adds env binds = List.fold_right (fun (x, v) env -> add env x v) binds env end let rec prepare_fun fv ~eval_check p env = (* Unlike OCaml we always evaluate default values, and we do that early. I think the only reason is homogeneity with FFI, which are declared with values as defaults. *) let p = List.map (function | { label; as_variable; default = Some v } -> ( label, Option.value ~default:label as_variable, Some (eval ~eval_check env v) ) | { label; as_variable; default = None } -> (label, Option.value ~default:label as_variable, None)) p in (* Keep only once the variables we might use in the environment. *) let env = Env.restrict env fv in (p, env) and apply ?(pos = []) ~eval_check f l = let apply_pos = match pos with [] -> None | p :: _ -> Some p in (* Extract the components of the function, whether it's explicit or foreign. *) let p, f = match f with | Value.Fun { fun_args = p; fun_env = e; fun_body = body } -> ( p, fun pe -> let env = Env.adds e pe in eval ~eval_check env body ) | Value.FFI { ffi_args = p; ffi_fn = f } -> (p, fun pe -> f (List.rev pe)) | _ -> assert false in (* Record error positions. *) let f pe = try f pe with | Runtime_error.Runtime_error err -> let bt = Printexc.get_raw_backtrace () in Runtime_error.raise ~bt ~pos:(Option.to_list apply_pos @ err.pos) ~message:err.Runtime_error.msg err.Runtime_error.kind | Internal_error (poss, e) -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Internal_error (Option.to_list apply_pos @ poss, e)) bt in (* Provide given arguments. *) let pe, p = List.fold_left (fun (pe, p) (lbl, v) -> let (_, var, _), p = remove_first (fun (l, _, _) -> l = lbl) p in ((var, v) :: pe, p)) ([], p) l in (* Add default values for remaining arguments. *) let pe = List.fold_left (fun pe (_, var, v) -> (* Typing should ensure that there are no mandatory arguments remaining. *) assert (v <> None); ( var, (* Set the position information on FFI's default values. Cf. r5008: if an Invalid value is raised on a default value, which happens with the mount/name params of output.icecast.*, the printing of the error should succeed at getting a position information. *) let v = Option.get v in Value.set_pos v apply_pos ) :: pe) pe p in (* Add position *) let pe = pe @ [(Lang_core.pos_var, Lang_core.Stacktrace.to_value pos)] in let v = f pe in (* Similarly here, the result of an FFI call should have some position information. For example, if we build a fallible source and pass it to an operator that expects an infallible one, an error is issued about that FFI-made value and a position is needed. *) Value.set_pos v apply_pos and eval_base_term ~eval_check (env : Env.t) tm = let mk v = Value.make ?pos:tm.t.Type.pos ~flags:tm.flags v in match tm.term with | `Int i -> mk (`Int i) | `Float f -> mk (`Float f) | `Bool b -> mk (`Bool b) | `String s -> mk (`String s) | `Custom g -> mk (`Custom g) | `Cache_env _ -> assert false | `Encoder (e, p) -> let pos = tm.t.Type.pos in let rec eval_param p = List.map (fun t -> match t with | `Anonymous s -> `Anonymous s | `Labelled (l, t) -> `Labelled (l, eval ~eval_check env t) | `Encoder (l, p) -> `Encoder (l, eval_param p)) p in let p = eval_param p in !Hooks.make_encoder ~pos (e, p) | `List l -> mk (`List (List.map (eval ~eval_check env) l)) | `Tuple l -> mk (`Tuple (List.map (fun a -> eval ~eval_check env a) l)) | `Null -> mk `Null | `Hide (tm, methods) -> ( let v = eval ~eval_check env tm in let v = Value.map_methods v (Methods.filter (fun n _ -> not (List.mem n methods))) in match v with | Value.Custom ({ dynamic_methods = Some d } as p) -> Value.Custom { p with dynamic_methods = Some { d with hidden_methods = List.sort_uniq Stdlib.compare (methods @ d.hidden_methods); }; } | v -> v) | `Cast { cast = e } -> Value.set_pos (eval ~eval_check env e) tm.t.Type.pos | `Invoke { invoked = t; invoke_default; meth } -> ( let v = eval ~eval_check env t in let invoked_value = match (Value.Methods.find_opt meth (Value.methods v), v) with | Some v, _ -> Some v | ( None, Value.Custom { dynamic_methods = Some { hidden_methods; methods } } ) when not (List.mem meth hidden_methods) -> methods meth | _ -> None in match (invoked_value, invoke_default) with (* If method returns `null` and a default is provided, pick default. *) | Some (Value.Null { methods }), Some default when Methods.is_empty methods -> eval ~eval_check env default | Some v, _ -> v | None, Some default -> eval ~eval_check env default | _ -> raise (Internal_error ( Option.to_list tm.t.Type.pos, "invoked method `" ^ meth ^ "` not found" ))) | `Open (t, u) -> let t = eval ~eval_check env t in let env = Methods.fold (fun key meth env -> Env.add env key meth) (Value.methods t) env in eval ~eval_check env u | `Let { pat; replace; def = v; body = b; _ } -> let v = eval ~eval_check env v in let penv = List.map (fun (ll, v) -> match ll with | [] -> assert false | [x] -> let v = if replace then Value.remeth (Env.lookup env x) v else v in (x, v) | l :: ll -> (* Add method ll with value v to t *) let rec meths ll v t = match ll with | [] -> assert false | [l] -> Value.map_methods t (Methods.add l v) | l :: ll -> Value.map_methods t (Methods.add l (meths ll v (Value.invoke t l))) in let v = let t = Env.lookup env l in let v = (* When replacing, keep previous methods. *) if replace then Value.remeth (Value.invokes t ll) v else v in meths ll v t in (l, v)) (eval_pat pat v) in let env = Env.adds env penv in eval ~eval_check env b | `Fun ({ name; arguments; body } as p) -> ( let fv = Term.free_fun_vars p in let p, env = prepare_fun ~eval_check fv arguments env in match name with | None -> mk (`Fun { Value.fun_args = p; fun_env = env; fun_body = body }) | Some name -> let rec ffi_fn env = let args = List.map (fun (n, n', default) -> let v = match List.assoc_opt n' env with | Some v -> v | None -> Option.get default in (n, v)) p in apply ~eval_check (mk_fun ()) args and mk_fun () = let ffi = mk (`FFI { Value.ffi_args = p; ffi_fn }) in let env = Env.add env name ffi in mk (`Fun { Value.fun_args = p; fun_env = env; fun_body = body }) in mk_fun ()) | `Var var -> Env.lookup env var | `Seq (a, b) -> ignore (eval ~eval_check env a); eval ~eval_check env b | `App (f, l) -> let ans () = let f = eval ~eval_check env f in let l = List.map (fun (l, t) -> (l, eval ~eval_check env t)) l in let pos = match tm.t.Type.pos with | None -> [] | Some p -> p :: (try List.map Lang_core.Position.of_value (Lang_core.to_list (List.assoc Lang_core.pos_var env)) with _ -> []) in apply ~pos ~eval_check f l in if !profile then ( match f.term with | `Var fname -> Profiler.time fname ans () | _ -> ans ()) else ans () and eval_term ~eval_check env tm = let v = eval_base_term ~eval_check env tm in if Methods.is_empty tm.methods then v else Value.map_methods v (Methods.fold (fun k tm m -> Methods.add k (eval ~eval_check env tm) m) tm.methods) and eval ~eval_check env tm = let v = eval_term ~eval_check env tm in eval_check ~env ~tm v; v let apply ?pos t p = let eval_check = !Hooks.eval_check in apply ?pos ~eval_check t p let eval ?env tm = let env = match env with | Some env -> env | None -> Environment.default_environment () in let env = List.map (fun (x, v) -> (x, v)) env in let eval_check = !Hooks.eval_check in eval ~eval_check env tm (** Add toplevel definitions to [builtins] so they can be looked during the evaluation of the next scripts. Also try to generate a structured documentation from the source code. *) let toplevel_add ?doc pat ~t v = let generalized, t = t in let doc = match doc with | None -> None | Some doc -> let doc () = (* FIll in types and default values. *) let arguments = (* Type for parameters. *) let rec ptypes t = match (Type.deref t).Type.descr with | Type.Arrow (p, _) -> p | Type.Meth (_, t) -> ptypes t | _ -> [] in let ptypes = ref (ptypes t) in (* Default values for parameters. *) let pvalues v = match v with | Value.Fun { fun_args = p } -> List.map (fun (l, _, o) -> (l, o)) p | _ -> [] in let pvalues = ref (pvalues v) in let doc_arguments = ref doc.Doc.Value.arguments in let arguments = ref [] in List.iter (fun (_, label, t) -> let label = if label = "" then None else Some label in let description = match List.assoc_opt label !doc_arguments with | Some argument -> doc_arguments := List.remove_assoc label !doc_arguments; argument.arg_description | None -> None in let t = Repr.string_of_type ~generalized t in let default = let label = Option.value ~default:"" label in match List.assoc_opt label !pvalues with | Some value -> pvalues := List.remove_assoc label !pvalues; value | None -> None in let default = Option.map Value.to_string default in arguments := ( label, Doc.Value. { arg_type = t; arg_default = default; arg_description = description; } ) :: !arguments) !ptypes; (* List.iter (fun (s, _) -> Printf.eprintf "WARNING: Unused @param %S for %s %s\n" s (string_of_pat pat) (Pos.Option.to_string (Value.pos v))) !doc_arguments; *) List.rev !arguments in let methods, callbacks, t = let methods, t = let methods, t = Type.split_meths t in match (Type.deref t).Type.descr with | Type.Arrow (p, a) -> let methods, a = Type.split_meths a in (* Note that in case we have a function, we drop the methods around, the reason being that we expect that they are registered on their own in the documentation. For instance, we don't want the field recurrent to appear in the doc of thread.run: it is registered as thread.run.recurrent anyways. *) (methods, Type.make ?pos:t.Type.pos (Type.Arrow (p, a))) | _ -> (methods, t) in let methods, callbacks = List.fold_left (fun (methods, callbacks) m -> let l = m.Type.meth in (* Override description by the one given in comment if it exists. *) let d = match List.assoc_opt l doc.Doc.Value.methods with | Some m -> m.meth_description | None -> Some m.doc.meth_descr in let t = Repr.string_of_scheme m.scheme in let entry = (l, Doc.Value.{ meth_type = t; meth_description = d }) in match m.doc.category with | `Method -> (entry :: methods, callbacks) | `Callback -> (methods, entry :: callbacks)) ([], []) methods in (methods, callbacks, t) in let typ = Repr.string_of_type ~generalized t in { doc with typ; arguments; methods; callbacks } in Some (Lazy.from_fun doc) in let env, pa = Typechecking.type_of_pat ~level:max_int ~pos:None pat in Typing.(t <: pa); List.iter (fun (x, v) -> let t = List.assoc x env in Environment.add_builtin ~override:true ?doc x ((generalized, t), v)) (eval_pat pat v) let rec eval_toplevel ?(interactive = false) t = match t.term with | `Let { doc; gen = generalized; replace; pat; def; body } -> let def_t, def = if not replace then (def.t, eval def) else ( match pat with | `PVar [] -> assert false | `PVar (x :: l) -> let old_t, old = ( List.assoc x (Environment.default_typing_environment ()), List.assoc x (Environment.default_environment ()) ) in let old_t = snd old_t in let old_t = snd (Type.invokes old_t l) in let old = Value.invokes old l in (Type.remeth old_t def.t, Value.remeth old (eval def)) | `PTuple _ -> assert false) in toplevel_add ?doc pat ~t:(generalized, def_t) def; if Lazy.force debug then Printf.eprintf "Added toplevel %s : %s\n%!" (string_of_pat pat) (Type.to_string ~generalized def_t); let var = string_of_pat pat in if interactive && var <> "_" && try ignore (Scanf.sscanf var "_%dpat" (fun v -> v)); false with _ -> true then Format.printf "@[<2>%s :@ %a =@ %s@]@." var (fun f t -> Repr.print_scheme f (generalized, t)) def_t (Value.to_string def); eval_toplevel ~interactive body | `Seq (a, b) -> ignore (let v = eval_toplevel a in if Value.pos v = None then Value.set_pos v a.t.Type.pos else v); eval_toplevel ~interactive b | _ -> let v = eval t in if interactive && t.term <> unit then Format.printf "- : %a = %s@." Repr.print_type t.t (Value.to_string v); v liquidsoap-2.4.2/src/lang/base/evaluation.mli000066400000000000000000000024771513273233300212010ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Evaluate a term in a given environment. *) val eval : ?env:(string * Value.t) list -> Term.t -> Value.t (** Evaluate a toplevel term. *) val eval_toplevel : ?interactive:bool -> Term.t -> Value.t (** Apply a function to arguments. *) val apply : ?pos:Pos.t list -> Value.t -> (string * Value.t) list -> Value.t liquidsoap-2.4.2/src/lang/base/extralib.ml000066400000000000000000000041221513273233300204600ustar00rootroot00000000000000let id x = x module Array = struct include Array (** Perfect Fisher-Yates shuffle (http://www.nist.gov/dads/HTML/fisherYatesShuffle.html). *) let shuffle a = let permute i j = let tmp = a.(i) in a.(i) <- a.(j); a.(j) <- tmp in let l = Array.length a in for i = 0 to l - 1 do permute i (i + Random.int (l - i)) done end module List = struct include List let rec assoc_nth l n = function | [] -> raise Not_found | (x, v) :: t when x = l -> if n = 0 then v else assoc_nth l (n - 1) t | _ :: t -> assoc_nth l n t let assoc_all x l = filter_map (fun (y, v) -> if x = y then Some v else None) l let rec last = function [x] -> x | _ :: l -> last l | [] -> raise Not_found let rec prefix n l = match l with | [] -> [] | x :: l -> if n = 0 then [] else x :: prefix (n - 1) l let shuffle l = let a = Array.of_list l in Array.shuffle a; Array.to_list a end module String = struct include String let ends_with s a = let ls = String.length s in let la = String.length a in ls >= la && String.sub s (ls - la) la = a let split_char c s = let rec aux res n = try let n' = index_from s n c in let s0 = sub s n (n' - n) in aux (s0 :: res) (n' + 1) with Not_found -> (if n = 0 then s else sub s n (length s - n)) :: res in List.rev (aux [] 0) end let read_retry read buf off len = let r = ref 0 in let loop = ref true in while !loop do let n = read buf (off + !r) (len - !r) in r := !r + n; loop := !r <> 0 && !r < len && n <> 0 done; !r module Unix = struct include Unix let read_retry fd = read_retry (read fd) end module Int = struct include Int let find p = let ans = ref 0 in try while true do if p !ans then raise Exit else incr ans done; assert false with Exit -> !ans end module Fun = struct include Fun (** Execute a function at most once. *) let once = let already = ref false in fun f -> if not !already then ( already := true; f ()) end liquidsoap-2.4.2/src/lang/base/flags.ml000066400000000000000000000004371513273233300177470ustar00rootroot00000000000000type flags = int type flag = int let empty = 0 let octal_int = 1 let hex_int = 1 lsl 1 let checked_value = 1 lsl 2 let itered_value = 1 lsl 3 let binary = 1 lsl 4 let has flags flag = flags land flag <> 0 let add flags flag = flags lor flag let remove flags flag = flags land lnot flag liquidsoap-2.4.2/src/lang/base/flags.mli000066400000000000000000000003701513273233300201140ustar00rootroot00000000000000type flags type flag val empty : flags val octal_int : flag val hex_int : flag val checked_value : flag val itered_value : flag val binary : flag val has : flags -> flag -> bool val add : flags -> flag -> flags val remove : flags -> flag -> flags liquidsoap-2.4.2/src/lang/base/hooks.ml000066400000000000000000000035161513273233300177770ustar00rootroot00000000000000let type_of_encoder = ref (fun ~pos:_ _ -> failwith "Encoders are not implemented!") type encoder_params = [ `Anonymous of string | `Encoder of encoder | `Labelled of string * Value.t ] list and encoder = string * encoder_params let make_encoder = ref (fun ~pos:_ _ -> failwith "Encoders are not implemented!") let has_encoder = ref (fun _ -> false) let liq_libs_dir = ref (fun () -> raise Not_found) let log_path = ref None type dirtype = [ `User | `System ] let cache_maintenance = ref (fun _ -> ()) type log = < f : 'a. int -> ('a, unit, string, unit) format4 -> 'a ; critical : 'a. ('a, unit, string, unit) format4 -> 'a ; severe : 'a. ('a, unit, string, unit) format4 -> 'a ; important : 'a. ('a, unit, string, unit) format4 -> 'a ; info : 'a. ('a, unit, string, unit) format4 -> 'a ; debug : 'a. ('a, unit, string, unit) format4 -> 'a > let make_log = ref (fun name -> let name = String.concat "." name in object (self : log) method f lvl = let time = Unix.gettimeofday () in Printf.ksprintf (fun s -> List.iter (Printf.printf "%f [%s:%d]: %s" time name lvl) (String.split_on_char '\n' s)) method critical = self#f 1 method severe = self#f 2 method important = self#f 3 method info = self#f 4 method debug = self#f 5 end) let log name = object (self : log) method f lvl = (!make_log name)#f lvl method critical = self#f 1 method severe = self#f 2 method important = self#f 3 method info = self#f 4 method debug = self#f 5 end let eval_check = ref (fun ~env:_ ~tm:_ _ -> ()) let mk_source_ty = ref (fun ?pos:_ _ _ -> assert false) let mk_clock_ty = ref (fun ?pos:_ () -> assert false) let source_methods_t = ref (fun _ -> assert false) let getpwnam = Lang_string.getpwnam liquidsoap-2.4.2/src/lang/base/hooks.mli000066400000000000000000000024631513273233300201500ustar00rootroot00000000000000(* Language essentials *) type log = < f : 'a. int -> ('a, unit, string, unit) format4 -> 'a ; critical : 'a. ('a, unit, string, unit) format4 -> 'a ; severe : 'a. ('a, unit, string, unit) format4 -> 'a ; important : 'a. ('a, unit, string, unit) format4 -> 'a ; info : 'a. ('a, unit, string, unit) format4 -> 'a ; debug : 'a. ('a, unit, string, unit) format4 -> 'a > val make_log : (string list -> log) ref val log : string list -> log val liq_libs_dir : (unit -> string) ref val log_path : string option ref type dirtype = [ `User | `System ] val cache_maintenance : (dirtype -> unit) ref (* Media-specific dependencies. *) val eval_check : (env:(string * Value.t) list -> tm:Term.t -> Value.t -> unit) ref type encoder_params = [ `Anonymous of string | `Encoder of encoder | `Labelled of string * Value.t ] list and encoder = string * encoder_params val make_encoder : (pos:Pos.Option.t -> encoder -> Value.t) ref val type_of_encoder : (pos:Pos.Option.t -> Term.encoder -> Type.t) ref val has_encoder : (Value.t -> bool) ref val mk_source_ty : (?pos:Term_base.parsed_pos -> string -> Parsed_term.source_annotation -> Type.t) ref val mk_clock_ty : (?pos:Term_base.parsed_pos -> unit -> Type.t) ref val source_methods_t : (unit -> Type.t) ref val getpwnam : (string -> Unix.passwd_entry) ref liquidsoap-2.4.2/src/lang/base/json.ml000066400000000000000000000114631513273233300176250ustar00rootroot00000000000000include Json_base let sedlexing_error ~pos ~message lexbuf = let lexbuf_position = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf) in let message = Printf.sprintf "In json data %s: %s" (Pos.to_string lexbuf_position) message in Runtime_error.raise ~message ~pos "json" let json5_processor = MenhirLib.Convert.Simplified.traditional2revised Json_parser.json5 let json_processor = MenhirLib.Convert.Simplified.traditional2revised Json_parser.json let from_string ?(pos = []) ?(json5 = false) s = let processor = if json5 then json5_processor else json_processor in let lexbuf = let gen = let pos = ref (-1) in let len = String.length s in fun () -> incr pos; if !pos < len then Some s.[!pos] else None in Sedlexing.Utf8.from_gen gen in let tokenizer () = let token = if json5 then Json_lexer.json5_token lexbuf else Json_lexer.json_token lexbuf in let startp, endp = Sedlexing.lexing_bytes_positions lexbuf in (token, startp, endp) in try processor tokenizer with | Runtime_error.Runtime_error _ as exn -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace exn bt | Json_base.Parse_error { pos = lexbuf_pos; message } -> let message = Printf.sprintf "In json data %s: %s" (Pos.to_string lexbuf_pos) message in Runtime_error.raise ~message ~pos "json" | Sedlexing.InvalidCodepoint c -> sedlexing_error ~pos ~message:(Printf.sprintf "Invalid codepoint: %d" c) lexbuf | Sedlexing.MalFormed -> sedlexing_error ~pos ~message:"Malformed input" lexbuf | _ -> Runtime_error.raise ~message:"Parse error" ~pos "json" (* Special version of utf8 quoting that uses [Uchar.rep] when a character cannot be escaped and fallback to ascii escaping with json escaping patterns.. *) let quote_string = let escape_char = let utf8_char_code s pos len = try Lang_string.utf8_char_code s pos len with _ -> Uchar.to_int Uchar.rep in Lang_string.escape_char ~escape_fun:(fun s pos len -> Printf.sprintf "\\u%04X" (utf8_char_code s pos len)) in let escape_formatter ~next special_char = Lang_string.escape ~special_char:(fun s pos len -> if s.[pos] = '\'' && len = 1 then false else special_char s pos len) ~escape_char ~next in let escape_utf8_formatter = escape_formatter ~next:Lang_string.utf8_next Lang_string.utf8_special_char in let escape_ascii_formatter = escape_formatter ~next:Lang_string.ascii_next Lang_string.ascii_special_char in fun s -> Printf.sprintf "\"%s\"" (try Lang_string.escape_string escape_utf8_formatter s with _ -> Lang_string.escape_string escape_ascii_formatter s) let rec to_string_compact ~json5 = function | `Null -> "null" | `String s -> quote_string s | `Bool b -> string_of_bool b | `Int i -> string_of_int i | `Float f -> ( match classify_float f with | FP_infinite when json5 -> if f < 0. then "-Infinity" else "Infinity" | FP_nan when json5 -> if f < 0. then "-NaN" else "NaN" | FP_infinite | FP_nan -> Runtime_error.raise ~pos:[] ~message: "Infinite or Nan number cannot be represented in JSON. You \ might want to consider using the `json5` representation." "json" | _ -> let s = Utils.string_of_float f in let s = Printf.sprintf "%s" s in if s.[String.length s - 1] = '.' then Printf.sprintf "%s0" s else s) | `Tuple l -> "[" ^ String.concat "," (List.map (to_string_compact ~json5) l) ^ "]" | `Assoc l -> let l = List.map (fun (l, v) -> Printf.sprintf "\"%s\":%s" l (to_string_compact ~json5 v)) l in Printf.sprintf "{%s}" (String.concat "," l) let pp_list sep ppx f l = let pp_sep f () = Format.fprintf f "%s@ " sep in Format.pp_print_list ~pp_sep ppx f l let rec to_string_pp ~json5 f v = match v with | `Tuple [] -> Format.fprintf f "[]" | `Tuple l -> Format.fprintf f "[@;<1 0>%a@;<1 -2>]" (pp_list "," (to_string_pp ~json5)) l | `Assoc [] -> Format.fprintf f "{}" | `Assoc l -> let format_field f (k, v) = Format.fprintf f "@[%s: %a@]" (Lang_string.quote_string k) (to_string_pp ~json5) v in Format.fprintf f "{@;<1 0>%a@;<1 -2>}" (pp_list "," format_field) l | `Null | `String _ | `Int _ | `Float _ | `Bool _ -> Format.fprintf f "%s" (to_string_compact ~json5 v) let to_string_pp ~json5 v = Format.asprintf "@[%a@]" (to_string_pp ~json5) v let to_string ?(compact = true) ?(json5 = false) v = if compact then to_string_compact ~json5 v else to_string_pp ~json5 v liquidsoap-2.4.2/src/lang/base/json.mli000066400000000000000000000005571513273233300200000ustar00rootroot00000000000000type t = [ `Assoc of (string * t) list | `Tuple of t list | `String of string | `Bool of bool | `Float of float | `Int of int | `Null ] type parse_error = { pos : Pos.t; message : string } exception Parse_error of parse_error val from_string : ?pos:Pos.t list -> ?json5:bool -> string -> t val to_string : ?compact:bool -> ?json5:bool -> t -> string liquidsoap-2.4.2/src/lang/base/json_base.ml000066400000000000000000000003621513273233300206130ustar00rootroot00000000000000type parse_error = { pos : Pos.t; message : string } exception Parse_error of parse_error type t = [ `Assoc of (string * t) list | `Tuple of t list | `String of string | `Bool of bool | `Float of float | `Int of int | `Null ] liquidsoap-2.4.2/src/lang/base/json_lexer.ml000066400000000000000000000255171513273233300210310ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Json_parser open Json_base open Lang_string (* Json specs *) let control_char = [%sedlex.regexp? 0x00 .. 0x1f] let unicode_escape_sequence = [%sedlex.regexp? ( '\\', 'u', ascii_hex_digit, ascii_hex_digit, ascii_hex_digit, ascii_hex_digit )] let decimal_digit = [%sedlex.regexp? '0' .. '9'] let one_nine = [%sedlex.regexp? '1' .. '9'] let non_null_integer = [%sedlex.regexp? one_nine, Star decimal_digit] let sign = [%sedlex.regexp? Opt '-'] let integer = [%sedlex.regexp? sign, ('0' | non_null_integer)] let exponent = [%sedlex.regexp? ('e' | 'E'), Opt ('+' | '-'), Plus decimal_digit] let fractional_part = [%sedlex.regexp? '.', Plus decimal_digit] let float_extension = [%sedlex.regexp? fractional_part, Opt exponent | exponent] let float = [%sedlex.regexp? integer, float_extension] let skipped = [%sedlex.regexp? Plus (' ' | '\r' | '\n' | '\t')] let rec json_token lexbuf = match%sedlex lexbuf with | skipped -> json_token lexbuf | "true" -> BOOL true | "false" -> BOOL false | "null" -> NULL | float -> FLOAT (float_of_string (Sedlexing.Utf8.lexeme lexbuf)) | integer -> ( let m = Sedlexing.Utf8.lexeme lexbuf in try INT (int_of_string m) with _ -> FLOAT (float_of_string m)) | '{' -> LCUR | '}' -> RCUR | '[' -> LBRA | ']' -> RBRA | ',' -> COMMA | ':' -> COLON | '"' -> STRING (read_string (fst (Sedlexing.lexing_bytes_positions lexbuf)) (Buffer.create 17) lexbuf) | eof -> EOF | _ -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "Parse error"; }) and read_string pos buf lexbuf = (* See: https://en.wikipedia.org/wiki/Escape_sequences_in_C *) match%sedlex lexbuf with | '\\', ('"' | '\\' | '/') -> let matched = Sedlexing.Utf8.lexeme lexbuf in Buffer.add_char buf matched.[1]; read_string pos buf lexbuf | '\\', 'b' -> Buffer.add_char buf '\b'; read_string pos buf lexbuf | '\\', 'f' -> Buffer.add_char buf '\x0c'; read_string pos buf lexbuf | '\\', 'n' -> Buffer.add_char buf '\n'; read_string pos buf lexbuf | '\\', 'r' -> Buffer.add_char buf '\r'; read_string pos buf lexbuf | '\\', 't' -> Buffer.add_char buf '\t'; read_string pos buf lexbuf | unicode_escape_sequence -> let matched = Sedlexing.Utf8.lexeme lexbuf in Buffer.add_string buf (unescape_utf8_char matched); read_string pos buf lexbuf | Plus (Compl (control_char | '"' | '\\')) -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_string pos buf lexbuf | '"' -> Buffer.contents buf | eof -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "String is not terminated"; }) | _ -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = Printf.sprintf "Illegal string character: %S" (Sedlexing.Utf8.lexeme lexbuf); }) (* Json 5 extension *) (* From: https://262.ecma-international.org/5.1/#sec-7.6 *) let unicode_letter = [%sedlex.regexp? lu | ll | lt | lm | lo | nl] let unicode_combining_mark = [%sedlex.regexp? mn | mc] let unicode_digit = [%sedlex.regexp? nd] let unicode_connector_punctuation = [%sedlex.regexp? pc] let identifier_start = [%sedlex.regexp? unicode_letter | '$' | '_' | unicode_escape_sequence] let identifier_part = [%sedlex.regexp? ( identifier_start | unicode_combining_mark | unicode_digit | unicode_connector_punctuation | 0x200C | 0x200D )] let identifier = [%sedlex.regexp? identifier_start, Star identifier_part] (* From https://262.ecma-international.org/5.1/#sec-7.3 *) let line_terminator = [%sedlex.regexp? 0xA | 0xD | 0x2028 | 0x2029] let line_terminator_sequence = [%sedlex.regexp? 0xA | 0xD | 0x2028 | 0x2029 | 0xD, 0xA] let hex_integer = [%sedlex.regexp? '0', ('x' | 'X'), Plus ascii_hex_digit] let json5_sign = [%sedlex.regexp? Opt ('+' | '-')] let json5_decimal_integer = [%sedlex.regexp? '0' | non_null_integer] let json5_integer = [%sedlex.regexp? json5_sign, (json5_decimal_integer | hex_integer)] let infinity = [%sedlex.regexp? "Infinity"] let nan = [%sedlex.regexp? "NaN"] let json5_integer_exponent = [%sedlex.regexp? json5_decimal_integer, exponent] let json5_float_integer = [%sedlex.regexp? json5_integer, '.', Opt exponent] let json5_float_decimal = [%sedlex.regexp? Opt json5_integer, '.', Plus decimal_digit, Opt exponent] let json5_float_number = [%sedlex.regexp? json5_integer_exponent | json5_float_integer | json5_float_decimal] let json5_float = [%sedlex.regexp? json5_sign, (json5_float_number | nan | infinity)] (* From: https://spec.json5.org/#white-space *) let json5_skipped = [%sedlex.regexp? 0x9 | 0xA | 0xb | 0xc | 0xd | 0x20 | 0xA0 | 0x2028 | 0x2029 | 0xFEFF | zs] let rec json5_token lexbuf = match%sedlex lexbuf with | Plus json5_skipped -> json5_token lexbuf | json5_float -> FLOAT (float_of_string (Sedlexing.Utf8.lexeme lexbuf)) | json5_integer -> ( let m = Sedlexing.Utf8.lexeme lexbuf in try INT (int_of_string m) with _ -> FLOAT (float_of_string m)) | "true" -> BOOL true | "false" -> BOOL false | "null" -> NULL | '{' -> LCUR | '}' -> RCUR | '[' -> LBRA | ']' -> RBRA | ',' -> COMMA | ':' -> COLON | identifier -> IDENTIFIER (Lang_string.unescape_string (Sedlexing.Utf8.lexeme lexbuf)) | "//" -> read_single_line_comment (fst (Sedlexing.lexing_bytes_positions lexbuf)) lexbuf; json5_token lexbuf | "/*" -> read_multiline_comment (fst (Sedlexing.lexing_bytes_positions lexbuf)) lexbuf; json5_token lexbuf | '"' -> STRING (read_json5_string '"' (fst (Sedlexing.lexing_bytes_positions lexbuf)) (Buffer.create 17) lexbuf) | '\'' -> STRING (read_json5_string '\'' (fst (Sedlexing.lexing_bytes_positions lexbuf)) (Buffer.create 17) lexbuf) | eof -> EOF | _ -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "Parse error"; }) and read_single_line_comment pos lexbuf = match%sedlex lexbuf with | eof | line_terminator_sequence -> () | Plus (Compl line_terminator) -> read_single_line_comment pos lexbuf | _ -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "Parse error"; }) and read_multiline_comment pos lexbuf = match%sedlex lexbuf with | Plus '*', '/' -> () | '*', Compl '/' | Plus (Compl '*') -> read_multiline_comment pos lexbuf | eof -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "Comment is not terminated"; }) | _ -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "Parse error"; }) and read_json5_string sep pos buf lexbuf = (* See: https://en.wikipedia.org/wiki/Escape_sequences_in_C *) match%sedlex lexbuf with | '\\', ('"' | '\\' | '/') -> let matched = Sedlexing.Utf8.lexeme lexbuf in Buffer.add_char buf matched.[1]; read_json5_string sep pos buf lexbuf | '\\', 'b' -> Buffer.add_char buf '\b'; read_json5_string sep pos buf lexbuf | '\\', 'f' -> Buffer.add_char buf '\x0c'; read_json5_string sep pos buf lexbuf | '\\', 'n' -> Buffer.add_char buf '\n'; read_json5_string sep pos buf lexbuf | '\\', 'r' -> Buffer.add_char buf '\r'; read_json5_string sep pos buf lexbuf | '\\', 't' -> Buffer.add_char buf '\t'; read_json5_string sep pos buf lexbuf | '\\', '0' -> Buffer.add_char buf '\000'; read_json5_string sep pos buf lexbuf | '\\', line_terminator_sequence -> read_json5_string sep pos buf lexbuf | '\\', Compl one_nine -> let matched = Sedlexing.Utf8.lexeme lexbuf in Buffer.add_substring buf matched 1 (String.length matched - 1); read_json5_string sep pos buf lexbuf | 0x2028 -> Buffer.add_utf_8_uchar buf (Uchar.of_int 0x2028); read_json5_string sep pos buf lexbuf | 0x2029 -> Buffer.add_utf_8_uchar buf (Uchar.of_int 0x2029); read_json5_string sep pos buf lexbuf | unicode_escape_sequence -> let matched = Sedlexing.Utf8.lexeme lexbuf in Buffer.add_string buf (unescape_utf8_char matched); read_json5_string sep pos buf lexbuf | Plus (Compl ('"' | '\'' | '\\' | line_terminator)) -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_json5_string sep pos buf lexbuf | '"' | '\'' -> let m = Sedlexing.Utf8.lexeme lexbuf in if m.[0] = sep then Buffer.contents buf else ( Buffer.add_string buf m; read_json5_string sep pos buf lexbuf) | eof -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "String is not terminated"; }) | _ -> raise (Parse_error { pos = Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf); message = "Parse error"; }) liquidsoap-2.4.2/src/lang/base/json_parser.mly000066400000000000000000000057651513273233300214020ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) %token LCUR RCUR LBRA RBRA %token COLON %token COMMA %token IDENTIFIER %token STRING %token INT %token FLOAT %token BOOL %token NULL %token EOF %start json %type json %type json_value %type json_object_entry %type <(string * Json_base.t) list> json_object %type json_array %start json5 %type json5 %type json5_value %type json5_object_entry %type <(string * Json_base.t) list> json5_object %type json5_array %% json: | json_value EOF { $1 } json5: | json5_value EOF { $1 } json_value: | NULL { `Null } | STRING { `String $1 } | INT { `Int $1 } | FLOAT { `Float $1 } | BOOL { `Bool $1 } | LBRA RBRA { `Tuple [] } | LBRA json_array RBRA { `Tuple $2 } | LCUR RCUR { `Assoc [] } | LCUR json_object RCUR { `Assoc $2 } json_array: | json_value { [$1] } | json_value COMMA json_array { $1::$3 } json_object_entry: | STRING COLON json_value { ($1, $3) } json_object: | json_object_entry { [$1] } | json_object_entry COMMA json_object { $1::$3 } json5_value: | NULL { `Null } | STRING { `String $1 } | INT { `Int $1 } | FLOAT { `Float $1 } | BOOL { `Bool $1 } | LBRA json5_array RBRA { `Tuple $2 } | LCUR json5_object RCUR { `Assoc $2 } json5_array: | { [] } | json5_value { [$1] } | json5_value COMMA json5_array { $1::$3 } json5_object_entry: | IDENTIFIER COLON json5_value { ($1, $3) } | STRING COLON json5_value { ($1, $3) } json5_object: | { [] } | json5_object_entry { [$1] } | json5_object_entry COMMA json5_object { $1::$3 } liquidsoap-2.4.2/src/lang/base/lang.ml000066400000000000000000000001131513273233300175630ustar00rootroot00000000000000include Lang_core include Lang_error include Lang_regexp include Lang_eval liquidsoap-2.4.2/src/lang/base/lang.mli000066400000000000000000000165541513273233300177540ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Values and types of the liquidsoap language. *) (** The type of a value. *) type t = Type.t type module_name type scheme = Type.scheme type regexp = Builtins_regexp.regexp (** {2 Values} *) module Custom = Value.Custom module Methods = Term.Methods type in_value = Value.in_value type env = Value.env type value = Value.t val demeth : value -> value val split_meths : value -> (string * value) list * value val error_module : module_name (** {2 Computation} *) (** Multiapply a value to arguments. The argument [t] is the type of the result of the application. *) val apply : ?pos:Pos.t list -> value -> env -> value (** {3 Helpers for source builtins} *) type proto = (string * t * value option * string option) list type 'a meth = { name : string; scheme : scheme; descr : string; value : 'a } (** Add a builtin to the language, high-level version for functions. *) val add_builtin : category:Doc.Value.category -> descr:string -> ?flags:Doc.Value.flag list -> ?meth:value meth list -> ?examples:string list -> ?base:module_name -> string -> proto -> t -> (env -> value) -> module_name (** Add a builtin value to the language *) val add_builtin_value : category:Doc.Value.category -> descr:string -> ?flags:Doc.Value.flag list -> ?base:module_name -> string -> value -> t -> module_name (** Add a builtin to the language, more rudimentary version. *) val add_builtin_base : category:Doc.Value.category -> descr:string -> ?flags:Doc.Value.flag list -> ?base:module_name -> string -> Value.in_value -> t -> module_name (** Declare a new module. *) val add_module : ?base:module_name -> string -> module_name val module_name : module_name -> string (** {2 Manipulation of values} *) val to_unit : value -> unit val to_bool : value -> bool val to_bool_getter : value -> unit -> bool val to_string : value -> string val to_string_getter : value -> unit -> string val to_regexp : value -> regexp val to_float : value -> float val to_float_getter : value -> unit -> float val to_error : value -> Runtime_error.runtime_error val to_int : value -> int val to_int_getter : value -> unit -> int val to_num : value -> [ `Int of int | `Float of float ] val to_list : value -> value list val to_option : value -> value option val to_valued_option : (value -> 'a) -> value -> 'a option val to_default_option : default:'a -> (value -> 'a) -> value -> 'a val to_product : value -> value * value val to_tuple : value -> value list val to_ref : value -> (unit -> value) * (value -> unit) val to_valued_ref : (value -> 'a) -> ('a -> value) -> value -> (unit -> 'a) * ('a -> unit) val to_string_list : value -> string list val to_int_list : value -> int list val to_fun : value -> (string * value) list -> value val is_fun : value -> bool val to_getter : value -> unit -> value (** [assoc x n l] returns the [n]-th [y] such that [(x,y)] is in the list [l]. This is useful for retrieving arguments of a function. *) val assoc : 'a -> int -> ('a * 'b) list -> 'b val int_t : t val unit_t : t val float_t : t val bool_t : t val string_t : t val regexp_t : t val product_t : t -> t -> t val of_product_t : t -> t * t val tuple_t : t list -> t val of_tuple_t : t -> t list val record_t : (string * t) list -> t val optional_record_t : (string * t) list -> t val method_t : t -> (string * scheme * string) list -> t val optional_method_t : t -> (string * scheme * string) list -> t val list_t : t -> t val of_list_t : t -> t val nullable_t : t -> t val ref_t : t -> t val error_t : t val error_meths_t : t (** [fun_t args r] is the type of a function taking [args] as parameters and returning values of type [r]. The elements of [r] are of the form [(b,l,t)] where [b] indicates if the argument is optional, [l] is the label of the argument ([""] means no label) and [t] is the type of the argument. *) val fun_t : (bool * string * t) list -> t -> t val univ_t : ?constraints:Type.constr list -> unit -> t (** A getter on an arbitrary type. *) val getter_t : t -> t val unit : value val int : int -> value val octal_int : int -> value val hex_int : int -> value val bool : bool -> value val float : float -> value val string : string -> value val regexp : regexp -> value val list : value list -> value val null : value val error : Runtime_error.runtime_error -> value val product : value -> value -> value val tuple : value list -> value val meth : value -> (string * value) list -> value val record : (string * value) list -> value val reference : (unit -> value) -> (value -> unit) -> value (** Build a function from an OCaml function. Items in the prototype indicate the label and optional values. Second string value is used when renaming argument name, e.g. `fun (foo=_, ...) -> ` *) val val_fun : (string * string * value option) list -> (env -> value) -> value (** Build a function from a term. *) val term_fun : (string * string * value option) list -> Term.t -> value (** Build a constant function. It is slightly less opaque and allows the printing of the closure when the constant is ground. *) val val_cst_fun : (string * value option) list -> value -> value (** Extract position from the environment. Used inside function execution. *) val pos : env -> Pos.t list (** Raise an error. *) val raise_error : ?bt:Printexc.raw_backtrace -> ?message:string -> pos:Pos.List.t -> string -> 'a (** Convert an exception into a runtime error. *) val runtime_error_of_exception : bt:Printexc.raw_backtrace -> kind:string -> exn -> Runtime_error.runtime_error (** Re-raise an error as a runtime error. *) val raise_as_runtime : bt:Printexc.raw_backtrace -> kind:string -> exn -> 'a (** Return the process' environment. *) val environment : unit -> (string * string) list (** Return an unescaped string description of a regexp, i.e. ^foo/bla$ *) val descr_of_regexp : regexp -> string (** Return a string description of a regexp value i.e. r/^foo\/bla$/g *) val string_of_regexp : regexp -> string type stdlib = [ `Disabled | `If_present | `Force | `Override of string ] (** Type a term, possibly returning the cached term instead. *) val type_term : ?name:string -> ?cache:bool -> ?trim:bool -> ?deprecated:bool -> ?ty:t -> stdlib:stdlib -> parsed_term:Parsed_term.t -> Term.t -> Term.t (** Evaluate a term. *) val eval : ?toplevel:bool -> ?typecheck:bool -> ?cache:bool -> ?deprecated:bool -> ?ty:t -> ?name:string -> stdlib:stdlib -> string -> value liquidsoap-2.4.2/src/lang/base/lang_core.ml000066400000000000000000000353301513273233300206040ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) include Value module Custom = Term.Custom module Methods = Term.Methods type t = Type.t type module_name = string type scheme = Type.scheme type value = Value.t (** Type construction *) let int_t = Type.make Type.Int let unit_t = Type.make Type.unit let float_t = Type.make Type.Float let bool_t = Type.make Type.Bool let string_t = Type.make Type.String let tuple_t l = Type.make (Type.Tuple l) let product_t a b = tuple_t [a; b] let rec record_t = function | [] -> unit_t | (l, t) :: r -> Type.meth l ([], t) (record_t r) let rec optional_record_t = function | [] -> unit_t | (l, t) :: r -> Type.meth ~optional:true l ([], t) (optional_record_t r) let rec method_t t0 = function | [] -> t0 | (l, t, doc) :: r -> Type.meth l t ~doc (method_t t0 r) let rec optional_method_t t0 = function | [] -> t0 | (l, t, doc) :: r -> Type.meth l t ~doc ~optional:true (optional_method_t t0 r) let of_tuple_t t = match (Type.deref t).Type.descr with Type.Tuple l -> l | _ -> assert false let of_product_t t = match of_tuple_t t with [a; b] -> (a, b) | _ -> assert false let fun_t p b = Type.make (Type.Arrow (p, b)) let list_t t = Type.make Type.(List { t; json_repr = `Tuple }) let of_list_t t = match (Type.deref t).Type.descr with | Type.(List { t }) -> t | _ -> assert false let nullable_t t = Type.make (Type.Nullable t) let univ_t ?(constraints = []) () = Type.var ~constraints () let getter_t a = Type.make (Type.Getter a) let ref_t a = Type.reference a (** Value construction *) let mk = Value.make let unit = mk Value.unit let int i = mk (`Int i) let octal_int i = mk ~flags:Flags.(add empty octal_int) (`Int i) let hex_int i = mk ~flags:Flags.(add empty hex_int) (`Int i) let bool i = mk (`Bool i) let float i = mk (`Float i) let string i = mk (`String i) let tuple l = mk (`Tuple l) let product a b = tuple [a; b] let list l = mk (`List l) let null = mk `Null let meth v l = Value.map_methods v (fun methods -> List.fold_left (fun v (k, m) -> Methods.add k m v) methods l) let record = meth (mk (`Tuple [])) let val_fun p f = mk (`FFI { ffi_args = p; ffi_fn = f }) let term_fun p tm = mk (`Fun { fun_args = p; fun_env = []; fun_body = tm }) let val_cst_fun p c = let p = List.map (fun (l, d) -> (l, "_", d)) p in let f t tm = let tm = Term.make ~t tm in mk (`Fun { fun_args = p; fun_env = []; fun_body = tm }) in let mkg g = Type.make g in (* Convert the value into a term if possible, to enable introspection, mostly for printing. *) match c with | Null _ -> f (Type.var ()) `Null | Tuple { value = [] } -> f (Type.make Type.unit) Term.unit | Int { value = i } -> f (mkg Type.Int) (`Int i) | Bool { value = i } -> f (mkg Type.Bool) (`Bool i) | Float { value = i } -> f (mkg Type.Float) (`Float i) | String { value = i } -> f (mkg Type.String) (`String i) | _ -> mk (`FFI { ffi_args = p; ffi_fn = (fun _ -> c) }) let reference get set = let get = val_fun [] (fun _ -> get ()) in let set = val_fun [("", "", None)] (fun p -> List.assoc "" p |> set; unit) in meth get [("set", set)] (** Helpers for defining builtin functions. *) type proto = (string * t * value option * string option) list let builtin_type p t = Type.make (Type.Arrow (List.map (fun (lbl, t, opt, _) -> (opt <> None, lbl, t)) p, t)) let meth_fun = meth let mk_module_name ?base name = if String.index_opt name '.' <> None then failwith ("module name " ^ name ^ " has a dot in it!"); match base with None -> name | Some b -> b ^ "." ^ name type 'a meth = { name : string; scheme : scheme; descr : string; value : 'a } let add_builtin ~category ~descr ?(flags = []) ?(meth = []) ?(examples = []) ?base name proto return_t f = let name = mk_module_name ?base name in let return_t = let meth = List.map (fun { name; scheme; descr } -> (name, scheme, descr)) meth in method_t return_t meth in let f = if meth = [] then f else ( let meth = List.map (fun { name; value } -> (name, value)) meth in fun p -> meth_fun (f p) meth) in let t = builtin_type proto return_t in let value = mk (`FFI { ffi_args = List.map (fun (lbl, _, opt, _) -> (lbl, lbl, opt)) proto; ffi_fn = f; }) in let doc () = let meth, return_t = Type.split_meths return_t in let callbacks, meth = List.partition (fun (m : Type.meth) -> m.doc.category = `Callback) meth in let t = builtin_type proto return_t in let generalized = Typing.filter_vars (fun _ -> true) t in let examples = List.map (fun e -> (* Remove leading and trailing newline *) let e = if e.[0] = '\n' then String.sub e 1 (String.length e - 1) else e in let e = if e.[String.length e - 1] = '\n' then String.sub e 0 (String.length e - 1) else e in e) examples in let arguments = List.map (fun (l, t, d, doc) -> ( (if l = "" then None else Some l), Doc.Value. { arg_type = Repr.string_of_scheme (generalized, t); arg_default = Option.map Value.to_string d; arg_description = doc; } )) proto in let methods = List.map (fun (m : Type.meth) -> let d = m.doc.meth_descr in let d = if d = "" then None else Some d in ( m.meth, Doc.Value. { meth_type = Repr.string_of_scheme m.scheme; meth_description = d; } )) meth in let callbacks = List.map (fun (m : Type.meth) -> let d = m.doc.meth_descr in let d = if d = "" then None else Some d in ( m.meth, Doc.Value. { meth_type = Repr.string_of_scheme m.scheme; meth_description = d; } )) callbacks in Doc.Value. { typ = Repr.string_of_scheme (generalized, t); category; flags; description = descr; examples; arguments; methods; callbacks; } (* to_plugin_doc category flags examples descr proto return_t *) in let doc = Lazy.from_fun doc in let generalized = Typing.filter_vars (fun _ -> true) t in Environment.add_builtin ~doc (String.split_on_char '.' name) ((generalized, t), value); name let add_builtin_value ~category ~descr ?(flags = []) ?base name value t = let name = mk_module_name ?base name in let generalized = Typing.filter_vars (fun _ -> true) t in let doc () = Doc.Value. { typ = Repr.string_of_scheme (generalized, t); category; flags; description = descr; examples = []; arguments = []; methods = []; callbacks = []; } in Environment.add_builtin ~doc:(Lazy.from_fun doc) (String.split_on_char '.' name) ((generalized, t), value); name let add_builtin_base ~category ~descr ?flags ?base name value t = add_builtin_value ~category ~descr ?flags ?base name (make value) t let add_module ?base name = let name = mk_module_name ?base name in Environment.add_module (String.split_on_char '.' name); name let module_name name = name (* Delay this function in order not to have Lang depend on Evaluation. *) let apply_fun : (?pos:Pos.t list -> value -> env -> value) ref = ref (fun ?pos:_ _ -> assert false) let apply ?pos f p = !apply_fun ?pos f p [@@inline always] (** {1 High-level manipulation of values} *) let to_unit = function Tuple { value = [] } -> () | _ -> assert false [@@inline always] let to_bool = function Bool { value = b } -> b | _ -> assert false [@@inline always] let to_bool_getter = function | Bool { value = b } -> fun () -> b | (Fun _ as v) | (FFI _ as v) -> ( fun () -> match apply v [] with Bool { value = b } -> b | _ -> assert false) | _ -> assert false [@@inline always] let to_fun v = apply v [@@inline always] let is_fun = function Fun _ | FFI _ -> true | _ -> false let to_string = function String { value = s } -> s | _ -> assert false [@@inline always] let to_string_getter = function | String { value = s } -> fun () -> s | (Fun _ as v) | (FFI _ as v) -> ( fun () -> match apply v [] with String { value = s } -> s | _ -> assert false) | _ -> assert false [@@inline always] let to_float = function Float { value = f } -> f | _ -> assert false [@@inline always] let to_float_getter = function | Float { value = f } -> fun () -> f | (Fun _ as v) | (FFI _ as v) -> ( fun () -> match apply v [] with Float { value = f } -> f | _ -> assert false) | _ -> assert false [@@inline always] let to_int = function Int { value = i } -> i | _ -> assert false [@@inline always] let to_int_getter = function | Int { value = i } -> fun () -> i | (Fun _ as v) | (FFI _ as v) -> ( fun () -> match apply v [] with Int { value = i } -> i | _ -> assert false) | _ -> assert false [@@inline always] let to_num = function | Int { value = i } -> `Int i | Float { value = f } -> `Float f | _ -> assert false [@@inline always] let to_list = function List { value = l } -> l | _ -> assert false [@@inline always] let to_tuple = function Tuple { value = l } -> l | _ -> assert false [@@inline always] let to_option = function Null _ -> None | v -> Some v [@@inline always] let to_valued_option convert v = Option.map convert (to_option v) [@@inline always] let to_default_option ~default convert v = Option.value ~default (to_valued_option convert v) [@@inline always] let to_product = function | Tuple { value = [a; b] } -> (a, b) | _ -> assert false [@@inline always] let to_string_list l = List.map to_string (to_list l) [@@inline always] let to_int_list l = List.map to_int (to_list l) [@@inline always] let to_getter = function | (Fun { fun_args = [] } as v) | (FFI { ffi_args = []; _ } as v) -> fun () -> apply v [] | v -> fun () -> v [@@inline always] let to_ref t = let m, t = split_meths t in let get = to_getter t in let set = let f = List.assoc "set" m in fun x -> ignore (apply f [("", x)]) in (get, set) let to_valued_ref getc setc t = let get, set = to_ref t in ((fun () -> getc (get ())), fun x -> set (setc x)) (** [assoc lbl n l] returns the [n]th element in [l] of which the first component is [lbl]. *) let rec assoc label n = function | [] -> raise Not_found | (l, e) :: tl -> if l = label then if n = 1 then e else assoc label (n - 1) tl else assoc label n tl let raise_error = Runtime_error.raise let runtime_error_of_exception ~bt ~kind exn = match exn with | Runtime_error.Runtime_error error -> error | _ -> let pos = match Printexc.backtrace_slots bt with | None -> [] | Some entries -> List.fold_left (fun pos slot -> match Printexc.Slot.location slot with | None -> pos | Some { Printexc.filename = pos_fname; line_number = pos_lnum; start_char = pos_bol; end_char = pos_cnum; } -> let p = { Lexing.pos_fname; pos_lnum; pos_bol; pos_cnum } in Pos.of_lexing_pos (p, p) :: pos) [] (List.rev (Array.to_list entries)) in Runtime_error.make ~pos ~message:(Printexc.to_string exn) kind let raise_as_runtime ~bt ~kind exn = match exn with | Runtime_error.Runtime_error _ -> Printexc.raise_with_backtrace exn bt | _ -> Printexc.raise_with_backtrace (Runtime_error.Runtime_error (runtime_error_of_exception ~bt ~kind exn)) bt let environment () = let l = Unix.environment () in (* Split at first occurrence of '='. Return v,"" if no '=' could be found. *) let split s = try let pos = String.index s '=' in (String.sub s 0 pos, String.sub s (pos + 1) (String.length s - pos - 1)) with _ -> (s, "") in let l = Array.to_list l in List.map split l module Position = struct let t = method_t unit_t [ ("filename", ([], string_t), "Filename"); ("lstart", ([], int_t), "Starting line"); ("lstop", ([], int_t), "Stopping line"); ("cstart", ([], int_t), "Starting character"); ("cstop", ([], int_t), "Stopping character"); ( "to_string", ([], fun_t [(true, "prefix", string_t)] string_t), "Render as string" ); ] let to_value pos = let { Pos.fname; lstart; lstop; cstart; cstop } = Pos.unpack pos in meth unit [ ("filename", string fname); ("lstart", int lstart); ("lstop", int lstop); ("cstart", int cstart); ("cstop", int cstop); ( "to_string", val_fun [("prefix", "prefix", Some (string "At "))] (fun p -> let prefix = to_string (List.assoc "prefix" p) in string (Pos.to_string ~prefix pos)) ); ] let of_value v = let fname = to_string (invoke v "filename") in let lstart = to_int (invoke v "lstart") in let lstop = to_int (invoke v "lstop") in let cstart = to_int (invoke v "cstart") in let cstop = to_int (invoke v "cstop") in Pos.pack { fname; lstart; lstop; cstart; cstop } end module Stacktrace = struct let t = list_t Position.t let to_value l = list (List.map Position.to_value l) let of_value v = List.map Position.of_value (to_list v) end let pos_var = "_pos_" let pos env = Stacktrace.of_value (List.assoc pos_var env) liquidsoap-2.4.2/src/lang/base/lang_error.ml000066400000000000000000000143631513273233300210100ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) include Runtime_error exception Encoder_error of (Pos.Option.t * string) let error_module = Lang_core.add_module "error" type error = Runtime_error.runtime_error = private { kind : string; msg : string; pos : Pos.List.t; } module ErrorDef = struct type content = error let name = "error" let to_string { kind; msg; pos } = let pos = if pos <> [] then Printf.sprintf ",positions=%s" (Lang_string.quote_string (Pos.List.to_string ~newlines:false pos)) else "" in Printf.sprintf "error(kind=%s,message=%s%s)" (Lang_string.quote_string kind) (Lang_string.quote_string msg) pos let to_json ~pos _ = Runtime_error.raise ~pos ~message:"Error cannot be represented as json" "json" let compare = Stdlib.compare end module Error = struct include Value.MkCustom (ErrorDef : Value.CustomDef) let meths = [ ( "kind", ([], Lang_core.string_t), "Error kind.", fun { kind } -> Lang_core.string kind ); ( "message", ([], Lang_core.string_t), "Error message.", fun { msg } -> Lang_core.string msg ); ( "trace", ([], Lang_core.Stacktrace.t), "Error stacktrace.", fun { pos } -> Lang_core.Stacktrace.to_value pos ); ] let base_t = t let t = Lang_core.method_t t (List.map (fun (lbl, t, descr, _) -> (lbl, t, descr)) meths) let to_value err = Lang_core.meth (to_value err) (List.map (fun (lbl, _, _, m) -> (lbl, m err)) meths) end let _ = Lang_core.add_builtin ~base:error_module "register" ~category:`Programming ~descr:"Register an error of the given kind" [("", Lang_core.string_t, None, Some "Kind of the error")] Error.t (fun p -> let kind = Lang_core.to_string (List.assoc "" p) in Error.to_value (Runtime_error.make ~pos:[] kind)) let _ = Lang_core.add_builtin ~base:error_module "raise" ~category:`Programming ~descr:"Raise an error." [ ("", Error.base_t, None, Some "Error kind."); ( "", Lang_core.string_t, Some (Lang_core.string ""), Some "Description of the error." ); ] (Lang_core.univ_t ()) (fun p -> let { kind; pos } = Error.of_value (Lang_core.assoc "" 1 p) in let message = Lang_core.to_string (Lang_core.assoc "" 2 p) in Runtime_error.raise ~pos:(Lang_core.pos p @ pos) ~message kind) let _ = Lang_core.add_builtin ~base:error_module "on_error" ~category:`Programming ~descr: "Register a callback to monitor errors raised during the execution of \ the program. The callback is allow to re-raise a different error if \ needed. The callback passed to this function is called on every errors, \ not just uncaught errors." [("", Lang_core.fun_t [(false, "", Error.t)] Lang_core.unit_t, None, None)] Lang_core.unit_t (fun p -> let fn = List.assoc "" p in let fn err = ignore (Lang_core.apply fn [("", Error.to_value err)]) in Runtime_error.on_error fn; Lang_core.unit) let error_t = Error.base_t let error_meths_t = Error.t let error = Error.to_value let to_error = Error.of_value let _ = let a = Lang_core.univ_t () in Lang_core.add_builtin ~base:error_module "catch" ~category:`Programming ~flags:[`Hidden] ~descr:"Execute a function, catching eventual exceptions." [ ( "errors", Lang_core.nullable_t (Lang_core.list_t Error.base_t), None, Some "Kinds of errors to catch. Catches all errors if not set." ); ("body", Lang_core.fun_t [] a, None, Some "Function to execute."); ( "catch", Lang_core.fun_t [(false, "", Error.t)] a, None, Some "Error handler." ); ( "finally", Lang_core.fun_t [] Lang_core.unit_t, None, Some "Invariant handler." ); ] a (fun p -> let errors = Option.map (fun v -> List.map Error.of_value (Lang_core.to_list v)) (Lang_core.to_option (List.assoc "errors" p)) in let body = Lang_core.to_fun (List.assoc "body" p) in let catch = Lang_core.to_fun (List.assoc "catch" p) in let finally = Lang_core.to_fun (List.assoc "finally" p) in let finally_called = Atomic.make false in try let v = body [] in Atomic.set finally_called true; ignore (finally []); v with | Runtime_error.(Runtime_error { kind; msg }) when errors = None || List.exists (fun err -> err.kind = kind) (Option.get errors) -> let v = try catch [ ( "", Error.to_value (Runtime_error.make ~pos:(Lang_core.pos p) ~message:msg kind) ); ] with exn when not (Atomic.get finally_called) -> let bt = Printexc.get_raw_backtrace () in ignore (finally []); Printexc.raise_with_backtrace exn bt in if not (Atomic.get finally_called) then ignore (finally []); v | exn when Atomic.get finally_called -> let bt = Printexc.get_raw_backtrace () in ignore (finally []); Printexc.raise_with_backtrace exn bt) liquidsoap-2.4.2/src/lang/base/lang_eval.ml000066400000000000000000000033501513273233300206000ustar00rootroot00000000000000type check_stdlib = [ `If_present | `Force | `Override of string ] type stdlib = [ `Disabled | check_stdlib ] let type_term ?name ?(cache = true) ?(trim = true) ?(deprecated = false) ?ty ~stdlib ~parsed_term term = let parsed_term, stdlib = match stdlib with | `Disabled -> (parsed_term, None) | #check_stdlib as stdlib -> let parsed_term, env = let stdlib, error_on_no_stdlib = match stdlib with | `Override s -> (Some s, true) | `If_present -> (None, false) | `Force -> (None, true) in Term_stdlib.prepare ~stdlib ~cache ~error_on_no_stdlib ~deprecated parsed_term in (parsed_term, Some env) in Runtime.type_term ?name ?stdlib ?ty ~term ~cache ~trim ~lib:false parsed_term let effective_toplevel ~stdlib toplevel = (* Registering defined operators at top-level prevents reclaiming memory from unused operators so we try to avoid it by default. We need it for all documentation. Also, as soon as the user script contains [let eval ...], we have to retain values at top-level as the string being parsed will also expect the standard library to be available. *) match (stdlib, Term_reducer.needs_toplevel ()) with | `Disabled, _ -> toplevel | _, true -> true | _ -> toplevel let eval ?(toplevel = false) ?(typecheck = true) ?cache ?deprecated ?ty ?name ~stdlib s = let parsed_term, term = Runtime.parse s in let toplevel = effective_toplevel ~stdlib toplevel in let term = if typecheck then type_term ?name ?cache ?deprecated ?ty ~trim:(not toplevel) ~stdlib ~parsed_term term else term in Runtime.eval_term ?name ~toplevel term liquidsoap-2.4.2/src/lang/base/lang_regexp.ml000066400000000000000000000004461513273233300211460ustar00rootroot00000000000000type regexp = Builtins_regexp.regexp let regexp_t = Builtins_regexp.RegExp.t let to_regexp = Builtins_regexp.RegExp.of_value let regexp = Builtins_regexp.RegExp.to_value ?pos:None let descr_of_regexp { Builtins_regexp.descr; _ } = descr let string_of_regexp = Builtins_regexp.string_of_regexp liquidsoap-2.4.2/src/lang/base/lang_string.ml000066400000000000000000000427651513273233300211740ustar00rootroot00000000000000let getpwnam = ref (fun _ -> raise Not_found) (* Generic escaping function. Returns a list of escaping substitutions. *) let escape ~special_char ~next ~escape_char s = let orig_len = String.length s in let rec f (segments, total_len) pos = let next_pos = next s (pos : int) in let len = next_pos - pos in let ret = if special_char s pos len then ( let s = escape_char s pos len in let len = String.length s in (`Subst (s, len) :: segments, total_len + len)) else ( (match segments with | `Orig (pos, s_len) :: segments -> `Orig (pos, s_len + len) :: segments | segments -> `Orig (pos, len) :: segments), total_len + len ) in if next_pos < orig_len then f ret next_pos else ret in if orig_len > 0 then f ([], 0) 0 else ([], 0) let utf8_next s i = match s.[i] with | '\000' .. '\127' -> i + 1 | '\192' .. '\223' -> i + 2 | '\224' .. '\239' -> i + 3 | '\240' .. '\247' -> i + 4 | _ -> failwith "invalid utf8" (* Return the utf8 char code of the first utf8 character in the given string. *) let utf8_char_code s pos len = if len < 1 then failwith "invalid utf8"; match s.[pos] with | '\000' .. '\127' as c -> Char.code c | '\192' .. '\223' as c -> if len < 2 then failwith "invalid utf8"; let n1 = Char.code c in let n2 = Char.code s.[pos + 1] in if n2 lsr 6 != 0b10 then failwith "invalid utf8"; ((n1 land 0x1f) lsl 6) lor (n2 land 0x3f) | '\224' .. '\239' as c -> if len < 3 then failwith "invalid utf8"; let n1 = Char.code c in let n2 = Char.code s.[pos + 1] in let n3 = Char.code s.[pos + 2] in if n2 lsr 6 != 0b10 || n3 lsr 6 != 0b10 then failwith "invalid utf8"; let p = ((n1 land 0x0f) lsl 12) lor ((n2 land 0x3f) lsl 6) lor (n3 land 0x3f) in if p >= 0xd800 && p <= 0xdf00 then failwith "invalid utf8"; p | '\240' .. '\247' as c -> if len < 4 then failwith "invalid utf8"; let n1 = Char.code c in let n2 = Char.code s.[pos + 1] in let n3 = Char.code s.[pos + 2] in let n4 = Char.code s.[pos + 3] in if n2 lsr 6 != 0b10 || n3 lsr 6 != 0b10 || n4 lsr 6 != 0b10 then failwith "invalid utf8"; ((n1 land 0x07) lsl 18) lor ((n2 land 0x3f) lsl 12) lor ((n3 land 0x3f) lsl 6) lor (n4 land 0x3f) | _ -> failwith "invalid utf8" (* End of Extlib code *) let is_valid_utf8_code_point s pos len = try ignore (utf8_char_code s pos len); true with _ -> false let ascii_special_char s pos len = match (s.[pos], len) with | '\'', 1 | '"', 1 | '\\', 1 (* DEL *) | '\x7F', 1 -> true (* Control chars *) | c, 1 when Char.code c <= 0x1F -> true | c, 1 when Char.code c > 0x7E -> true | _ -> false let utf8_special_char s ofs len = (not (is_valid_utf8_code_point s ofs len)) || (len = 1 && ascii_special_char s ofs len) let ascii_next _ i = i + 1 let escape_char ~escape_fun s pos len = match (s.[pos], len) with | '\b', 1 -> "\\b" | '\n', 1 -> "\\n" | '\r', 1 -> "\\r" | '\t', 1 -> "\\t" | '\\', 1 -> "\\\\" | '"', 1 -> "\\\"" | '\'', 1 -> "\\'" | _ -> escape_fun s pos len let escape_utf8_char ~strict = let utf8_char_code s pos len = try utf8_char_code s pos len with _ when not strict -> Uchar.to_int Uchar.rep in escape_char ~escape_fun:(fun s pos len -> Printf.sprintf "\\u%04X" (utf8_char_code s pos len)) let escape_utf8_formatter ?(strict = false) ?(special_char = utf8_special_char) = escape ~special_char ~escape_char:(escape_utf8_char ~strict) ~next:utf8_next let escape_hex_char = escape_char ~escape_fun:(fun s pos len -> assert (len = 1); Printf.sprintf "\\x%02X" (int_of_char s.[pos])) let escape_octal_char = escape_char ~escape_fun:(fun s pos len -> assert (len = 1); Printf.sprintf "\\%03o" (int_of_char s.[pos])) (* We use the \xhh syntax to make sure the resulting string is also consistently readable in OCaml. \nnn can be tricky since they are octal sequences in liquidsoap and digital sequences in OCaml. *) let escape_ascii_formatter ?(special_char = ascii_special_char) = escape ~special_char ~escape_char:escape_hex_char ~next:ascii_next let has_subst = List.exists (function `Subst _ -> true | _ -> false) let escape_string escape s = let segments, len = escape s in if not (has_subst segments) then s else ( let b = Bytes.create len in ignore (List.fold_left (fun b_pos segment -> match segment with | `Orig (s_pos, len) -> let b_pos = b_pos - len in Bytes.blit_string s s_pos b b_pos len; b_pos | `Subst (subst, len) -> let b_pos = b_pos - len in Bytes.blit_string subst 0 b b_pos len; b_pos) len segments); Bytes.unsafe_to_string b) let escape_utf8_string ?strict ?special_char = escape_string (escape_utf8_formatter ?strict ?special_char) let escape_ascii_string ?special_char = escape_string (escape_ascii_formatter ?special_char) let quote_utf8_string ?strict s = Printf.sprintf "\"%s\"" (escape_utf8_string ?strict ~special_char:(fun s pos len -> if s.[pos] = '\'' && len = 1 then false else utf8_special_char s pos len) s) let quote_ascii_string s = Printf.sprintf "\"%s\"" (escape_ascii_string ~special_char:(fun s pos len -> if s.[pos] = '\'' && len = 1 then false else ascii_special_char s pos len) s) let quote_string s = try quote_utf8_string ~strict:true s with _ -> quote_ascii_string s let unescape_utf8_pattern = "\\\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]" let unescape_hex_pattern = "\\\\x[0-9a-fA-F][0-9a-fA-F]" let unescape_octal_pattern = "\\\\[0-9][0-9][0-9]" let unescape_patterns = [ "\\\\a"; "\\\\b"; "\\\\e"; "\\\\f"; "\\\\n"; "\\\\r"; "\\\\t"; "\\\\v"; "\\\\/"; "\\\\\\\\"; "\\\\\""; "\\\\\'"; "\\\\\\?"; unescape_octal_pattern; unescape_hex_pattern; unescape_utf8_pattern; ] let unescape_octal_char s = let s = String.sub s 1 3 in Printf.sprintf "%c" (Char.chr (int_of_string ("0o" ^ s))) let unescape_hex_char s = let s = String.sub s 2 2 in Printf.sprintf "%c" (Char.chr (int_of_string ("0x" ^ s))) let unescape_utf8_char s = let b = Buffer.create 1 in let s = String.sub s 2 4 in let c = try Uchar.of_int (int_of_string ("0x" ^ s)) with _ -> Uchar.rep in Buffer.add_utf_8_uchar b c; Buffer.contents b let unescape_char = function | "\\a" -> "\x07" | "\\b" -> "\b" | "\\e" -> "\x1b" | "\\f" -> "\x0c" | "\\n" -> "\n" | "\\r" -> "\r" | "\\t" -> "\t" | "\\v" -> "\x0b" | "\\/" -> "/" | "\\\\" -> "\\" | "\\\"" -> "\"" | "\\'" -> "'" | "\\?" -> "\x3f" | s when String.length s = 6 -> unescape_utf8_char s | s when String.length s = 4 && s.[1] = 'x' -> unescape_hex_char s | s when String.length s = 4 -> unescape_octal_char s | _ -> assert false let unescape_string = Re.replace ~all:true ~f:(fun g -> unescape_char (Re.Group.get g 0)) (Re.Pcre.regexp (String.concat "|" unescape_patterns)) (** String representation of a matrix of strings. *) let string_of_matrix a = let height = Array.length a in let width = Array.fold_left (fun h a -> max h (Array.length a)) 0 a in let len = Array.make width 0 in for j = 0 to height - 1 do for i = 0 to Array.length a.(j) - 1 do len.(i) <- max len.(i) (String.length a.(j).(i)) done done; let ans = Buffer.create 10 in for j = 0 to height - 1 do for i = 0 to Array.length a.(j) - 1 do let s = a.(j).(i) in if i <> 0 then Buffer.add_string ans " "; Buffer.add_string ans s; Buffer.add_string ans (String.make (len.(i) - String.length s) ' ') done; Buffer.add_string ans "\n" done; Buffer.contents ans (** Remove line breaks from markdown text. This is useful for reflowing markdown such as when printing doc. *) let unbreak_md md = let must_break = function | "" :: _ -> true | "```" :: _ -> true | line :: _ when line.[0] = '-' (* itemize *) -> true | _ -> false in let rec text = function | [] -> "" | [line] -> line | "```" :: lines -> "```\n" ^ verb lines | line :: lines when line = "" || must_break lines -> line ^ "\n" ^ text lines | line :: lines -> line ^ " " ^ text lines and verb = function | "```" :: lines -> "```\n" ^ text lines | line :: lines -> line ^ "\n" ^ verb lines | [] -> "```" in text (String.split_on_char '\n' md) (* From dbuenzli/cmdliner *) let find_cmd cmds = let test, null = match Sys.os_type with | "Win32" -> ("where", " NUL") | _ -> ("type", "/dev/null") in let rec cmd = function | (c, args) :: l -> if Sys.command (Printf.sprintf "%s %s 1>%s 2>%s" test c null null) = 0 then Some (if args = "" then c else c ^ " " ^ args) else cmd l | [] -> None in cmd cmds let print_string ?(pager = false) s = let pager = if Sys.win32 || Sys.getenv_opt "TERM" = None || Sys.getenv_opt "PAGER" = Some "none" then false else pager in let default = output_string stdout in let cmd = let cmds = [("less", "-F -X -r -f"); ("more", "")] in let cmds = try (Sys.getenv "PAGER", "") :: cmds with Not_found -> cmds in let cmds = try (Sys.getenv "MANPAGER", "") :: cmds with Not_found -> cmds in find_cmd cmds in match (pager, cmd) with | false, _ | _, None -> default s | true, Some pager -> ( let fname, oc = Filename.open_temp_file "liquidsoap" ".txt" in try ignore (Sys.command "clear"); output_string oc s; flush oc; if Sys.command (Printf.sprintf "%s %s" pager fname) <> 0 then default s; raise Exit with _ -> close_out oc; Unix.unlink fname) let kprint_string ?(pager = false) f = if not pager then f (print_string ~pager) else ( let ans = Buffer.create 10 in f (Buffer.add_string ans); print_string ~pager (Buffer.contents ans)) (** Operations on versions of Liquidsoap. *) module Version = struct open Term_hash type t = int list * string [@@deriving hash] (* We assume something like, 2.0.0+git@7e211ffd *) let of_string s : t = let rex = Re.Pcre.regexp "(?:rolling-release-v)?([\\.\\dx]+)([^\\.]+)?" in let sub = Re.Pcre.exec ~rex s in let num = Re.Pcre.get_substring sub 1 in let str = try Re.Pcre.get_substring sub 2 with _ -> "" in let num = let int_of_string s = if s = "x" then max_int else int_of_string s in String.split_on_char '.' num |> List.map int_of_string in (num, str) (** Number part. *) let num (v : t) = fst v (** String part. *) let str (v : t) = snd v let to_string v = Printf.sprintf "%s%s" (String.concat "." (List.map string_of_int (num v))) (str v) (** Compare two versions. 2.0.0~foo is less than 2.0.0 *) let compare v w = let compare_suffixes = match (str v, str w) with | v, w when String.length v > 1 && String.length w > 1 && v.[0] = '~' && w.[0] = '~' -> String.compare v w | v, w when String.length v > 1 && String.length w > 1 && v.[0] = '~' && w.[0] <> '~' -> -1 | v, w when String.length v > 1 && String.length w > 1 && v.[0] <> '~' && w.[0] = '~' -> 1 | v, "" when String.length v > 1 && v.[0] = '~' -> -1 | "", w when String.length w > 1 && w.[0] = '~' -> 1 | v, w -> String.compare v w in let rec aux v w = match (v, w) with | m :: _, n :: _ when m < n -> -1 | m :: _, n :: _ when m > n -> 1 | _ :: v, _ :: w -> aux v w | [], [] -> compare_suffixes | [], _ -> -1 | _, [] -> 1 in aux (num v) (num w) end (** Expand ~ notation in filenames. *) let home_unrelate = let home = Sys.getenv_opt "HOME" in let unrel s = let len = String.length s in if len < 2 then (match home with Some h when s = "~" -> h | _ -> s) else ( match (home, s.[0] = '~', s.[1] = '/') with | Some home, true, true -> (* Something like ~/data/file *) Filename.concat home (String.sub s 2 (len - 2)) | _, true, false -> ( (* Something like ~bob/data/file *) let index = try String.index s '/' with Not_found -> len in let user = String.sub s 1 (index - 1) in try let home = (!getpwnam user).Unix.pw_dir in Filename.concat home (String.sub s index (len - index)) with Not_found -> s) | _ -> s) in unrel (** Decode Base64-encoded data *) let decode64 s = let padding = ref 0 in let to_int c = match c with | 'A' .. 'Z' -> int_of_char c - int_of_char 'A' | 'a' .. 'z' -> int_of_char c - int_of_char 'a' + 26 | '0' .. '9' -> int_of_char c - int_of_char '0' + 52 | '+' -> 62 | '/' -> 63 | '=' -> incr padding; 0 | _ -> failwith "decode64: invalid encoding" in let result = ref [] in let add x = result := char_of_int x :: !result in for i = 0 to (String.length s / 4) - 1 do (* Read 4 64-digits, i.e. 3 bytes. *) let c n = to_int s.[(i * 4) + n] in let i = c 3 + (c 2 lsl 6) + (c 1 lsl 12) + (c 0 lsl 18) in add ((i land 0xff0000) lsr 16); add ((i land 0x00ff00) lsr 8); add (i land 0x0000ff) done; let result = (* Remove up to two bytes depending on the padding. *) match !padding with | 0 -> !result | 1 -> List.tl !result | 2 -> List.tl (List.tl !result) | _ -> failwith "decode64: invalid encoding" in let len = List.length result in let s = Bytes.make len ' ' in ignore (List.fold_left (fun i c -> Bytes.set s i c; i - 1) (len - 1) result); Bytes.unsafe_to_string s (** Base 64 encoding. *) let encode64 s = let digit = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" in let extra = String.length s mod 3 in let s = match extra with 1 -> s ^ "\000\000" | 2 -> s ^ "\000" | _ -> s in let n = String.length s in let dst = Bytes.create (4 * (n / 3)) in for i = 0 to (n / 3) - 1 do let ( := ) j v = Bytes.set dst ((i * 4) + j) digit.[v] in let c j = int_of_char s.[(i * 3) + j] in let c0 = c 0 and c1 = c 1 and c2 = c 2 in 0 := c0 lsr 2; 1 := (c0 lsl 4) land 63 lor (c1 lsr 4); 2 := (c1 lsl 2) land 63 lor (c2 lsr 6); 3 := c2 land 63 done; if extra = 1 then ( Bytes.set dst ((4 * (n / 3)) - 2) '='; Bytes.set dst ((4 * (n / 3)) - 1) '=') else if extra = 2 then Bytes.set dst ((4 * (n / 3)) - 1) '='; Bytes.unsafe_to_string dst (* URL encoding/decoding according to RFC 1738, RFC 1630. Borrowed from ocamlnet. *) (** Converts k to a 2-digit hexadecimal string. *) let to_hex2 = let hex_digits = [| '0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'; 'A'; 'B'; 'C'; 'D'; 'E'; 'F'; |] in fun k -> let s = Bytes.create 2 in Bytes.set s 0 hex_digits.((k lsr 4) land 15); Bytes.set s 1 hex_digits.(k land 15); Bytes.unsafe_to_string s let url_encode ?(plus = true) s = Re.replace ~all:true ~f:(fun g -> let x = Re.Group.get g 0 in if plus && x = " " then "+" else ( let k = Char.code x.[0] in "%" ^ to_hex2 k)) (Re.Pcre.regexp "[^A-Za-z0-9_.!*-]") s let of_hex1 c = match c with | '0' .. '9' -> Char.code c - Char.code '0' | 'A' .. 'F' -> Char.code c - Char.code 'A' + 10 | 'a' .. 'f' -> Char.code c - Char.code 'a' + 10 | _ -> failwith "invalid url" let url_decode ?(plus = true) s = let rex = Re.Pcre.regexp "\\+|%..|%.|%" in Re.replace ~all:true ~f:(fun g -> let s = Re.Group.get g 0 in if s = "+" then if plus then " " else "+" else ( (* Assertion: s.[0] = '%' *) if String.length s < 3 then failwith "invalid url"; let k1 = of_hex1 s.[1] in let k2 = of_hex1 s.[2] in String.make 1 (Char.chr ((k1 lsl 4) lor k2)))) rex s let split ~encoding s = let buf = Buffer.create 1 in let to_string add c = Buffer.clear buf; add buf c; Buffer.contents buf in let get = match encoding with | `Ascii -> fun pos -> (to_string Buffer.add_char (String.get s pos), 1) | `Utf8 -> fun pos -> let d = String.get_utf_8_uchar s pos in if not (Uchar.utf_decode_is_valid d) then failwith "Decoding failed!"; ( to_string Buffer.add_utf_8_uchar (Uchar.utf_decode_uchar d), Uchar.utf_decode_length d ) in let len = String.length s in let rec f chars pos = if pos = len then List.rev chars else ( let char, len = get pos in f (char :: chars) (pos + len)) in f [] 0 let length ~encoding s = match encoding with | `Ascii -> String.length s | `Utf8 -> let len = String.length s in let rec f count pos = if pos = len then count else ( let d = String.get_utf_8_uchar s pos in if not (Uchar.utf_decode_is_valid d) then failwith "Decoding failed!"; f (count + 1) (pos + Uchar.utf_decode_length d)) in f 0 0 liquidsoap-2.4.2/src/lang/base/lang_string.mli000066400000000000000000000054051513273233300213330ustar00rootroot00000000000000val getpwnam : (string -> Unix.passwd_entry) ref val escape : special_char:(string -> int -> int -> bool) -> next:(string -> int -> int) -> escape_char:(string -> int -> int -> string) -> string -> [> `Orig of int * int | `Subst of string * int ] list * int val utf8_next : string -> int -> int val utf8_char_code : string -> int -> int -> int val ascii_special_char : string -> int -> int -> bool val utf8_special_char : string -> int -> int -> bool val ascii_next : 'a -> int -> int val escape_char : escape_fun:(string -> int -> int -> string) -> string -> int -> int -> string val escape_utf8_char : strict:bool -> string -> int -> int -> string val escape_utf8_formatter : ?strict:bool -> ?special_char:(string -> int -> int -> bool) -> string -> [> `Orig of int * int | `Subst of string * int ] list * int val escape_hex_char : string -> int -> int -> string val escape_octal_char : string -> int -> int -> string val escape_ascii_formatter : ?special_char:(string -> int -> int -> bool) -> string -> [> `Orig of int * int | `Subst of string * int ] list * int val has_subst : [ `Orig of int * int | `Subst of string * int ] list -> bool val escape_string : (string -> [ `Orig of int * int | `Subst of string * int ] list * int) -> string -> string val escape_utf8_string : ?strict:bool -> ?special_char:(string -> int -> int -> bool) -> string -> string val escape_ascii_string : ?special_char:(string -> int -> int -> bool) -> string -> string val quote_utf8_string : ?strict:bool -> string -> string val quote_ascii_string : string -> string val quote_string : string -> string val unescape_utf8_pattern : string val unescape_hex_pattern : string val unescape_octal_pattern : string val unescape_patterns : string list val unescape_octal_char : string -> string val unescape_hex_char : string -> string val unescape_utf8_char : string -> string val unescape_char : string -> string val unescape_string : string -> string val string_of_matrix : string array array -> string val unbreak_md : string -> string val find_cmd : (string * string) list -> string option val print_string : ?pager:bool -> string -> unit val kprint_string : ?pager:bool -> ((string -> unit) -> unit) -> unit module Version : sig type t = int list * string val hash_fold_t : Term_hash.state -> t -> Term_hash.state val of_string : string -> t val to_string : t -> string val num : t -> int list val str : t -> string val compare : t -> t -> int end val home_unrelate : string -> string val encode64 : string -> string val decode64 : string -> string val url_encode : ?plus:bool -> string -> string val url_decode : ?plus:bool -> string -> string val split : encoding:[ `Ascii | `Utf8 ] -> string -> string list val length : encoding:[ `Ascii | `Utf8 ] -> string -> int liquidsoap-2.4.2/src/lang/base/lexer.ml000066400000000000000000000455141513273233300177770ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Parser module String = struct include String (** Find character satisfying a predicate from a particular index. *) let indexp_from s n p = let l = String.length s in let n = ref n in try while !n < l do if p s.[!n] then raise Exit; incr n done; raise Not_found with Exit -> !n let rindexp s p = let n = ref (String.length s - 1) in try while !n >= 0 do if p s.[!n] then raise Exit; decr n done; raise Not_found with Exit -> !n end let parse_time t = let g sub n = (function | None | Some "" -> None | Some s -> Some (int_of_string (String.sub s 0 (String.length s - 1)))) (try Some (Re.Pcre.get_substring sub n) with _ -> None) in try let rex = Re.Pcre.regexp "^((?:\\d+w)?)((?:\\d+h)?)((?:\\d+m)?)((?:\\d+s)?)$" in let sub = Re.Pcre.exec ~rex t in let g = g sub in { Parsed_term.week = g 1; hours = g 2; minutes = g 3; seconds = g 4 } with Not_found -> let rex = Re.Pcre.regexp "^((?:\\d+w)?)(\\d+h)(\\d+)$" in let sub = Re.Pcre.exec ~rex t in let g = g sub in { Parsed_term.week = g 1; hours = g 2; minutes = Some (int_of_string (Re.Pcre.get_substring sub 3)); seconds = None; } (* See: https://en.wikipedia.org/wiki/Whitespace_character *) let line_break = [%sedlex.regexp? '\n' | '\r' | '\x0C' | '\x0B' | 0x2028 | 0x2029] let white_space = [%sedlex.regexp? Sub (white_space, line_break)] let skipped = [%sedlex.regexp? white_space | '\r' | '\t'] let decimal_digit = [%sedlex.regexp? '0' .. '9'] let decimal_literal = [%sedlex.regexp? decimal_digit, Star (decimal_digit | '_')] let hex_literal = [%sedlex.regexp? '0', ('x' | 'X'), ascii_hex_digit, Star (ascii_hex_digit | '_')] let oct_digit = [%sedlex.regexp? '0' .. '7'] let oct_literal = [%sedlex.regexp? '0', ('o' | 'O'), oct_digit, Star (oct_digit | '_')] let bin_digit = [%sedlex.regexp? '0' | '1'] let bin_literal = [%sedlex.regexp? '0', ('b' | 'B'), bin_digit, Star (bin_digit | '_')] let int_literal = [%sedlex.regexp? decimal_literal | hex_literal | oct_literal | bin_literal] let var_char = [%sedlex.regexp? alphabetic] let var_underscore = [%sedlex.regexp? '_', Plus '_'] let var_lit = [%sedlex.regexp? ( var_underscore | Star '_', var_char, Star (var_char | decimal_digit | '_' | '\'') )] let var = [%sedlex.regexp? var_lit | so] let encoder = [%sedlex.regexp? '%', Plus (var_char | decimal_digit | '.' | '_')] let time = [%sedlex.regexp? ( Opt (decimal_literal, 'w'), decimal_literal, 'h', decimal_literal | ( decimal_literal, 'w', Opt (decimal_literal, 'h'), Opt (decimal_literal, 'm'), Opt (decimal_literal, 's') ) | ( Opt (decimal_literal, 'w'), decimal_literal, 'h', Opt (decimal_literal, 'm'), Opt (decimal_literal, 's') ) | ( Opt (decimal_literal, 'w'), Opt (decimal_literal, 'h'), decimal_literal, 'm', Opt (decimal_literal, 's') ) | ( Opt (decimal_literal, 'w'), Opt (decimal_literal, 'h'), Opt (decimal_literal, 'm'), decimal_literal, 's' ) )] let rec token lexbuf = match%sedlex lexbuf with | Plus skipped -> token lexbuf | Star white_space, '#', '<' -> let buf = Buffer.create 1024 in let ((startp, _) as pos) = Sedlexing.lexing_bytes_positions lexbuf in Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_multiline_comment pos buf lexbuf; let _, endp = Sedlexing.lexing_bytes_positions lexbuf in Parser_helper.append_comment ~pos:(startp, endp) (Buffer.contents buf); token lexbuf | "#", Star white_space -> let buf = Buffer.create 1024 in let ((startp, _) as pos) = Sedlexing.lexing_bytes_positions lexbuf in Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); let _, endp = read_comment pos buf lexbuf in Parser_helper.append_comment ~pos:(startp, endp) (Buffer.contents buf); token lexbuf | line_break -> PP_ENDL | "%ifdef" -> PP_IFDEF false | "%ifndef" -> PP_IFDEF true | "%ifversion" -> PP_IFVERSION | "%ifencoder" -> PP_IFENCODER false | "%ifnencoder" -> PP_IFENCODER true | "%else" -> PP_ELSE | "%endif" -> PP_ENDIF | "%include_extra", Star (white_space | '\t'), '"', Star (Compl '"'), '"' -> let matched = Sedlexing.Utf8.lexeme lexbuf in let n = String.index matched '"' in let r = String.rindex matched '"' in let file = String.sub matched (n + 1) (r - n - 1) in INCLUDE { inc_type = `Extra; inc_name = file; inc_pos = Sedlexing.lexing_positions lexbuf; } | "%include", Star (white_space | '\t'), '"', Star (Compl '"'), '"' -> let matched = Sedlexing.Utf8.lexeme lexbuf in let n = String.index matched '"' in let r = String.rindex matched '"' in let file = String.sub matched (n + 1) (r - n - 1) in INCLUDE { inc_type = `Default; inc_name = file; inc_pos = Sedlexing.lexing_positions lexbuf; } | "%include", Star (white_space | '\t'), '<', Star (Compl '>'), '>' -> let matched = Sedlexing.Utf8.lexeme lexbuf in let n = String.index matched '<' in let r = String.rindex matched '>' in let file = String.sub matched (n + 1) (r - n - 1) in INCLUDE { inc_type = `Lib; inc_name = file; inc_pos = Sedlexing.lexing_positions lexbuf; } | "%argsof" -> ARGS_OF | '#', Star (Compl '\n'), eof -> EOF | eof -> EOF | "def", Plus skipped, "rec", Plus skipped -> DEF `Recursive | "def", Plus skipped, "replaces", Plus skipped -> DEF `Replaces | "def" -> DEF `None | "try" -> TRY | "finally" -> FINALLY | "catch" -> CATCH | "do" -> DO | "let", Plus skipped, "replaces", Plus skipped -> LET `Replaces | "let", Plus skipped, "eval", Plus skipped -> LET `Eval | "let", Plus skipped, "json.parse", Star skipped, '[' -> LETLBRA `Json_parse | "let", Plus skipped, "json.parse", Plus skipped -> LET `Json_parse | "let", Plus skipped, "yaml.parse", Plus skipped -> LET `Yaml_parse | "let", Plus skipped, "sqlite.row", Plus skipped -> LET `Sqlite_row | "let", Plus skipped, "sqlite.query", Plus skipped -> LET `Sqlite_query | "let", Plus skipped, "xml.parse", Plus skipped -> LET `Xml_parse | "let" -> LET `None | "fun" -> FUN | '=' -> GETS | "end" -> END | "begin" -> BEGIN | "if" -> IF | "then" -> THEN | "else" -> ELSE | "elsif" -> ELSIF | "->" -> YIELDS | "null", "." -> NULLDOT | "null" -> NULL | encoder -> let e = Sedlexing.Utf8.lexeme lexbuf in let e = String.sub e 1 (String.length e - 1) in ENCODER e | '.', Star skipped, var -> let m = Sedlexing.Utf8.lexeme lexbuf in let lexbuf = Sedlexing.Utf8.from_string m in let rec f () = match%sedlex lexbuf with | '.', Star skipped -> f () | var -> DOTVAR (Sedlexing.Utf8.lexeme lexbuf) | _ -> assert false in f () | '.' -> DOT | "..." -> DOTDOTDOT | '[' -> LBRA | ']' -> RBRA | '(' -> LPAR | ')' -> RPAR | '{' -> LCUR | '}' -> RCUR | ',' -> COMMA | '@' -> AT | "::" -> COLONCOLON | ':' -> COLON | ';' -> SEQ | ";;" -> SEQSEQ | "~" -> TILD | "?." -> QUESTION_DOT | "?" -> QUESTION | "-" -> MINUS | "while" -> WHILE | "for" -> FOR | "to" -> TO | "do" -> DO | "not" -> NOT | "open" -> OPEN | "and" -> AND | "or" -> OR | "!=" | "==" | "<" | "<=" | ">" | ">=" -> BIN1 (Sedlexing.Utf8.lexeme lexbuf) | "+" | "%" | "^" | "+." | "-." -> BIN2 (Sedlexing.Utf8.lexeme lexbuf) | "/" | "*." | "/." -> BIN3 (Sedlexing.Utf8.lexeme lexbuf) | "mod" -> BIN3 (Sedlexing.Utf8.lexeme lexbuf) | "??" -> COALESCE | "*" -> TIMES | "!" -> GET | ":=" -> SET | '_' -> UNDERSCORE | "true" -> BOOL true | "false" -> BOOL false | int_literal -> INT (Sedlexing.Utf8.lexeme lexbuf) | decimal_literal, ".{" -> let matched = Sedlexing.Utf8.lexeme lexbuf in let matched = String.sub matched 0 (String.length matched - 2) in PP_INT_DOT_LCUR matched | ( Star decimal_literal, Star ('.', Star decimal_literal), ('e' | 'E'), Star ('+' | '-'), decimal_literal ) -> FLOAT (Sedlexing.Utf8.lexeme lexbuf) | Star decimal_literal, '.', Star decimal_literal -> FLOAT (Sedlexing.Utf8.lexeme lexbuf) | ( decimal_literal, ".", decimal_literal, ".", decimal_literal, Star (Compl (white_space | line_break)) ) -> VERSION (Lang_string.Version.of_string (Sedlexing.Utf8.lexeme lexbuf)) | time -> TIME (parse_time (Sedlexing.Utf8.lexeme lexbuf)) | time, Star skipped, '-', Star skipped, time -> let matched = Sedlexing.Utf8.lexeme lexbuf in let idx = String.index matched '-' in let t1 = String.sub matched 0 idx in let t1 = String.trim t1 in let t2 = String.sub matched (idx + 1) (String.length matched - idx - 1) in let t2 = String.trim t2 in INTERVAL (parse_time t1, parse_time t2) | var -> VAR (Sedlexing.Utf8.lexeme lexbuf) | '"' -> let startp, _ = Sedlexing.lexing_bytes_positions lexbuf in let s = read_string '"' startp (Buffer.create 17) lexbuf in let _, endp = Sedlexing.lexing_bytes_positions lexbuf in PP_STRING ('"', s, (startp, endp)) | '\'' -> let startp, _ = Sedlexing.lexing_bytes_positions lexbuf in let s = read_string '\'' startp (Buffer.create 17) lexbuf in let _, endp = Sedlexing.lexing_bytes_positions lexbuf in PP_STRING ('\'', s, (startp, endp)) | "r/" -> let startp, _ = Sedlexing.lexing_bytes_positions lexbuf in let regexp = read_string '/' startp (Buffer.create 17) lexbuf in let flags = read_regexp_flags [] lexbuf in let _, endp = Sedlexing.lexing_bytes_positions lexbuf in PP_REGEXP (regexp, flags, (startp, endp)) | any -> raise (Term_base.Parse_error ( Sedlexing.lexing_bytes_positions lexbuf, "Parse error: " ^ Sedlexing.Utf8.lexeme lexbuf )) | _ -> failwith "Internal error" and read_regexp_flags flags lexbuf = match%sedlex lexbuf with | 'g' | 'i' | 's' | 'm' | 'u' -> let matched = Sedlexing.Utf8.lexeme lexbuf in read_regexp_flags (matched.[0] :: flags) lexbuf | _ -> Sedlexing.rollback lexbuf; flags and read_comment_end ((startp, _) as pos) buf lexbuf = match%sedlex lexbuf with | '\n', Star white_space -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); let _, endp = Sedlexing.lexing_bytes_positions lexbuf in (startp, endp) | _ -> raise (Term_base.Parse_error (pos, "Illegal character: " ^ Sedlexing.Utf8.lexeme lexbuf)) and read_comment ((startp, _) as pos) buf lexbuf = match%sedlex lexbuf with | '\n', Star white_space, '#', '<' -> Sedlexing.rollback lexbuf; read_comment_end pos buf lexbuf | '\n', Star white_space, '#', Star white_space -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); let _, endp = Sedlexing.lexing_bytes_positions lexbuf in read_comment (startp, endp) buf lexbuf | '\n' -> pos | Plus (Compl '\n') -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); let _, endp = Sedlexing.lexing_bytes_positions lexbuf in read_comment (startp, endp) buf lexbuf | eof -> pos | _ -> raise (Term_base.Parse_error (pos, "Illegal character: " ^ Sedlexing.Utf8.lexeme lexbuf)) and read_multiline_comment ?(level = 0) pos buf lexbuf = match%sedlex lexbuf with | '>', '#' -> Buffer.add_string buf ">#"; if level = 0 then () else read_multiline_comment ~level:(level - 1) pos buf lexbuf | '#', '<' -> Buffer.add_string buf "#<"; read_multiline_comment ~level:(level + 1) pos buf lexbuf | '#' | '>' -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_multiline_comment ~level pos buf lexbuf | Plus (Intersect (Compl '>', Compl '#')) -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_multiline_comment ~level pos buf lexbuf | eof -> raise (Term_base.Parse_error (pos, "Multiline comment not terminated!")) | _ -> raise (Term_base.Parse_error (pos, "Illegal character: " ^ Sedlexing.Utf8.lexeme lexbuf)) and read_string c pos buf lexbuf = match%sedlex lexbuf with | '\\', Opt any -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_string c pos buf lexbuf | Plus (Compl ('"' | '\'' | '\\' | '/')) -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); read_string c pos buf lexbuf | '"' | '\'' | '/' -> let matched = Sedlexing.Utf8.lexeme lexbuf in let c' = matched.[0] in if c = c' then Buffer.contents buf else ( Buffer.add_char buf c'; read_string c pos buf lexbuf) | eof -> let msg = if c = '/' then "Regexp not terminated" else "String is not terminated" in raise (Term_base.Parse_error ((pos, snd (Sedlexing.lexing_bytes_positions lexbuf)), msg)) | _ -> let msg = if c = '/' then "Illegal regexp character: " else "Illegal string character: " in raise (Term_base.Parse_error ( (pos, snd (Sedlexing.lexing_bytes_positions lexbuf)), msg ^ Sedlexing.Utf8.lexeme lexbuf )) let render_string ~pos ~sep s = let buf = Buffer.create (String.length s) in let lexbuf = Sedlexing.Utf8.from_string (Printf.sprintf "%s%c" s sep) in let rec render_string () = (* See: https://en.wikipedia.org/wiki/Escape_sequences_in_C *) match%sedlex lexbuf with | '\\', 'a' -> Buffer.add_char buf '\x07'; render_string () | '\\', 'b' -> Buffer.add_char buf '\b'; render_string () | '\\', 'e' -> Buffer.add_char buf '\x1b'; render_string () | '\\', 'f' -> Buffer.add_char buf '\x0c'; render_string () | '\\', 'n' -> Buffer.add_char buf '\n'; render_string () | '\\', 'r' -> Buffer.add_char buf '\r'; render_string () | '\\', 't' -> Buffer.add_char buf '\t'; render_string () | '\\', 'v' -> Buffer.add_char buf '\x0b'; render_string () | '\\', ('"' | '\'' | '/' | '\\') -> let matched = Sedlexing.Utf8.lexeme lexbuf in (* For regexp, we want to make sure these are kept as-is and does not need any further escaping. *) if sep = '/' && matched.[1] <> '/' then Buffer.add_char buf matched.[0]; Buffer.add_char buf matched.[1]; render_string () | '\\', '?' -> (* For regexp, we want to make sure \? is kept as-is and does not need any further escaping. *) if sep = '/' then ( Buffer.add_char buf '\\'; Buffer.add_char buf '?'; render_string ()) else ( Buffer.add_char buf '\x3f'; render_string ()) | '\\', 'x', ascii_hex_digit, ascii_hex_digit -> let matched = Sedlexing.Utf8.lexeme lexbuf in let idx = String.index matched 'x' in let code = String.sub matched (idx + 1) 2 in let code = int_of_string (Printf.sprintf "0x%s" code) in Buffer.add_char buf (Char.chr code); render_string () | '\\', oct_digit, oct_digit, oct_digit -> let matched = Sedlexing.Utf8.lexeme lexbuf in let idx = String.index matched '\\' in let code = String.sub matched (idx + 1) 3 in let code = min 255 (int_of_string (Printf.sprintf "0o%s" code)) in Buffer.add_char buf (Char.chr code); render_string () | ( '\\', 'u', ascii_hex_digit, ascii_hex_digit, ascii_hex_digit, ascii_hex_digit ) -> let matched = Sedlexing.Utf8.lexeme lexbuf in Buffer.add_string buf (Lang_string.unescape_utf8_char matched); render_string () (* Multiline string support: some text \ Some more text *) | '\\', '\n', Star skipped -> render_string () | '\\', any -> if sep <> '/' then ( let pos = Pos.(to_string (of_lexing_pos pos)) in Printf.printf "Warning at position %s: illegal backslash escape in string.\n" pos); Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); render_string () | Plus (Compl ('"' | '\'' | '\\' | '/')) -> Buffer.add_string buf (Sedlexing.Utf8.lexeme lexbuf); render_string () | '"' | '\'' | '/' -> let matched = Sedlexing.Utf8.lexeme lexbuf in let c' = matched.[0] in if sep = c' then Buffer.contents buf else ( Buffer.add_char buf c'; render_string ()) | eof -> let msg = if sep = '/' then "Regexp not terminated" else "String is not terminated" in raise (Term_base.Parse_error (pos, msg)) | _ -> let msg = if sep = '/' then "Illegal regexp character: " else "Illegal string character: " in raise (Term_base.Parse_error (pos, msg ^ Sedlexing.Utf8.lexeme lexbuf)) in render_string () let () = Parser_helper.render_string_ref := fun ~pos (sep, s) -> render_string ~pos ~sep s liquidsoap-2.4.2/src/lang/base/methods.ml000066400000000000000000000034551513273233300203210ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (* Immutable fast hash *) open Term_hash type ('a, 'b) t = ('a * 'b) list [@@deriving hash] let is_empty h = h = [] let bindings h = h let empty = [] let cardinal = List.length let fold fn h r = List.fold_left (fun r (k, v) -> fn k v r) r h let find = List.assoc let find_opt = List.assoc_opt let mem = List.mem_assoc let mapi fn = List.map (fun (k, v) -> (k, fn k v)) let map fn = List.map (fun (k, v) -> (k, fn v)) let filter fn = List.filter (fun (k, v) -> fn k v) let remove k h = List.filter (fun (k', _) -> k <> k') h let add k v h = (k, v) :: remove k h let append l l' = List.fold_left (fun m (k, v) -> add k v m) l l' let from_list l = append [] l let iter fn = List.iter (fun (k, v) -> fn k v) let for_all fn = List.for_all (fun (k, v) -> fn k v) let exists fn = List.exists (fun (k, v) -> fn k v) liquidsoap-2.4.2/src/lang/base/methods.mli000066400000000000000000000040161513273233300204640ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (* Immutable fast hash *) type ('a, 'b) t val hash_fold_t : (Term_hash.state -> 'a -> Term_hash.state) -> (Term_hash.state -> 'b -> Term_hash.state) -> Term_hash.state -> ('a, 'b) t -> Term_hash.state val from_list : ('a * 'b) list -> ('a, 'b) t val is_empty : ('a, 'b) t -> bool val empty : ('a, 'b) t val cardinal : ('a, 'b) t -> int val fold : ('a -> 'b -> 'c -> 'c) -> ('a, 'b) t -> 'c -> 'c val bindings : ('a, 'b) t -> ('a * 'b) list val find : 'a -> ('a, 'b) t -> 'b val find_opt : 'a -> ('a, 'b) t -> 'b option val mem : 'a -> ('a, 'b) t -> bool val remove : 'a -> ('a, 'b) t -> ('a, 'b) t val append : ('a, 'b) t -> ('a, 'b) t -> ('a, 'b) t val add : 'a -> 'b -> ('a, 'b) t -> ('a, 'b) t val mapi : ('a -> 'b -> 'c) -> ('a, 'b) t -> ('a, 'c) t val map : ('b -> 'c) -> ('a, 'b) t -> ('a, 'c) t val filter : ('a -> 'b -> bool) -> ('a, 'b) t -> ('a, 'b) t val for_all : ('a -> 'b -> bool) -> ('a, 'b) t -> bool val exists : ('a -> 'b -> bool) -> ('a, 'b) t -> bool val iter : ('a -> 'b -> unit) -> ('a, 'b) t -> unit liquidsoap-2.4.2/src/lang/base/modules.ml000066400000000000000000000026601513273233300203230ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (* Declare general modules. *) let debug = Lang.add_module "debug" let list = Lang.add_module "list" let liquidsoap = Lang.add_module "liquidsoap" let iterator = Lang.add_module "iterator" let os = Lang.add_module "os" let profiler = Lang.add_module "profiler" let profiler_stats = Lang.add_module ~base:profiler "stats" let random = Lang.add_module "random" let url = Lang.add_module "url" let record = Lang.add_module "record" liquidsoap-2.4.2/src/lang/base/parser.mly000066400000000000000000000656321513273233300203500ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) %{ open Parsed_term (* All auxiliary functions for parser are there *) open Parser_helper %} %token VAR %token VARLPAR %token VARLBRA %token VERSION %token PP_STRING %token PP_REGEXP %token STRING %token REGEXP %token INT PP_INT_DOT_LCUR %token FLOAT %token NULL %token NULLDOT %token BOOL %token TIME %token INTERVAL %token ENCODER %token EOF %token LET %token LETLBRA %token BEGIN END GETS TILD QUESTION %token QUESTION_DOT (* name, arguments, methods *) %token DEF %token REPLACES %token COALESCE %token TRY CATCH FINALLY DO %token IF THEN ELSE ELSIF %token SLASH %token OPEN %token LPAR RPAR COMMA SEQ SEQSEQ COLON COLONCOLON DOT %token DOTVAR %token LBRA RBRA LCUR RCUR %token FUN YIELDS %token DOTDOTDOT %token AND OR %token BIN1 %token BIN2 %token BIN3 %token AT %token TIMES %token MINUS UMINUS %token UNDERSCORE %token NOT %token GET SET %token PP_IFDEF %token PP_IFVERSION %token ARGS_OF %token PP_IFENCODER %token PP_ELSE PP_ENDIF %token PP_ENDL %token BEGIN_INTERPOLATION %token END_INTERPOLATION %token INTERPOLATED_STRING %token INCLUDE %token WHILE FOR TO %nonassoc YIELDS (* fun x -> (x+x) *) %right SET (* expr := (expr + expr), expr := (expr := expr) *) %nonassoc QUESTION (* x ? y : z *) %left AND (* ((x+(y*z))==3) or ((not a)==b) *) %left OR %nonassoc NOT %left BIN1 AT %left BIN2 MINUS %left BIN3 TIMES %nonassoc COALESCE (* (x ?? y) == z *) %nonassoc QUESTION_DOT (* (x ?. y) == z *) %right COLONCOLON %nonassoc GET (* (!x)+2 *) %left DOT %nonassoc COLON (* Read %ogg(...) as one block, shifting LPAR rather than reducing %ogg *) %nonassoc no_app %nonassoc LPAR %nonassoc UMINUS %start program %type program %start interactive %type interactive %start annotate %type <(string * string) list> annotate %start annotate_metadata_entry %type annotate_metadata_entry %start time_predicate %type time_predicate %start plain_encoder_params %type plain_encoder_params %type _let %type annotate_key %type <(string * string) list> annotate_metadata %type annotate_value %type app_list %type app_list_elem %type arglist %type args_of_params %type argsty %type argty %type explicit_binding %type binding %type encoder_params %type expr %type exprs %type simple_fun_body %type g %type <(Term.t * Term.t) list * Term.t option> if_elsif %type in_subfield %type in_subfield_lbra %type inner_list %type inner_list_item %type inner_tuple %type let_opt %type let_opt_el %type meth_ty %type opt %type optvar %type pattern %type pattern_list %type record %type record_ty %type s %type spread %type subfield %type subfield_lbra %type ty %type ty_content %type ty_content_arg %type ty_source %type ty_source_tracks %type ty_tuple %type varlist %type subfield_lpar %% program: | error { raise (Term_base.Parse_error ($loc, "Syntax error!")) } | EOF { mk ~pos:$loc `Eof } | exprs EOF { $1 } interactive: | error { raise (Term_base.Parse_error ($loc, "Syntax error!")) } | exprs SEQSEQ { $1 } | EOF { raise End_of_file } s: | {} | SEQ {} g: | {} | GETS {} exprs: | OPEN expr s exprs { mk ~pos:$loc (`Open ($2,$4)) } | expr s { $1 } | expr s exprs { mk ~pos:$loc (`Seq ($1,$3)) } | binding s { mk_let ~pos:$loc($1) $1 (mk ~pos:$loc `Eof) } | binding s exprs { mk_let ~pos:$loc($1) $1 $3 } (* Simple fun body, syntax: { ... }. Same as expressions except initial x = 1 which conflicts with record declaration: { x = 1 } *) simple_fun_body: | OPEN expr s exprs { mk ~pos:$loc (`Open ($2,$4)) } | expr s { $1 } | expr s exprs { mk ~pos:$loc (`Seq ($1,$3)) } | explicit_binding s { mk_let ~pos:$loc($1) $1 (mk ~pos:$loc unit) } | explicit_binding s exprs { mk_let ~pos:$loc($1) $1 $3 } (* General expressions. *) expr: | INCLUDE { mk ~pos:$loc (`Include $1) } | if_def { mk ~pos:$loc (`If_def $1) } | if_encoder { mk ~pos:$loc (`If_encoder $1) } | if_version { mk ~pos:$loc (`If_version $1) } | LPAR expr COLON ty RPAR { mk ~pos:$loc (`Cast {cast = $2; typ = $4}) } | UMINUS expr { mk ~pos:$loc (`Negative $2) } | LPAR expr RPAR { mk ~pos:$loc (`Parenthesis $2) } | INT { mk ~pos:$loc (`Int $1) } | NOT expr { mk ~pos:$loc (`Not $2) } | BOOL { mk ~pos:$loc (`Bool $1) } | FLOAT { mk ~pos:$loc (`Float $1) } | STRING { mk ~pos:$loc (`String $1) } | string_interpolation { mk ~pos:$loc (`String_interpolation $1) } | VAR { mk ~pos:$loc (`Var $1) } | varlist { mk ~pos:$loc (`List $1) } | GET expr { mk ~pos:$loc (`Get $2) } | expr SET expr { mk ~pos:$loc (`Set ($1, $3)) } | ENCODER encoder_opt { mk_encoder ~pos:$loc $1 $2 } | LPAR RPAR { mk ~pos:$loc (`Tuple []) } | LPAR inner_tuple RPAR { mk ~pos:$loc (`Tuple $2) } | expr DOT LCUR record RCUR { mk ~pos:$loc (`Methods (Some $1, $4)) } | expr DOT LCUR record optional_comma RCUR { mk ~pos:$loc (`Methods (Some $1, $4)) } | NULL { mk ~pos:$loc `Null } | LCUR record RCUR { mk ~pos:$loc (`Methods (None, $2)) } | LCUR record optional_comma RCUR { mk ~pos:$loc (`Methods (None, $2)) } | LCUR RCUR { mk ~pos:$loc (`Methods (None, [])) } | expr QUESTION_DOT invoke { mk ~pos:$loc (`Invoke { invoked = $1; meth = $3; optional = true }) } | expr DOT invoke { mk ~pos:$loc (`Invoke { invoked = $1; meth = $3; optional = false }) } | VARLPAR app_list RPAR { mk ~pos:$loc (`App (mk ~pos:$loc($1) (`Var $1), $2)) } | expr COLONCOLON expr { mk ~pos:$loc (`Append ($1, $3)) } | VARLBRA expr RBRA { mk ~pos:$loc (`Assoc (mk ~pos:$loc($1) (`Var $1), $2)) } | expr DOT VARLBRA expr RBRA { let src = mk ~pos:($startpos($1),$endpos($3)) (`Invoke ({invoked = $1; optional = false; meth = `String $3})) in mk ~pos:$loc (`Assoc (src, $4)) } | BEGIN exprs END { mk ~pos:$loc (`Block $2) } | FUN LPAR arglist RPAR YIELDS expr{ mk_fun ~pos:$loc $3 $6 } | LCUR simple_fun_body RCUR { mk ~pos:$loc (`Simple_fun $2) } | WHILE expr DO exprs END { mk ~pos:$loc (`While {while_condition = $2; while_loop = $4 }) } | FOR optvar GETS expr TO expr DO exprs END { mk ~pos:$loc (`For { for_variable = $2; for_from = $4; for_to = $6; for_loop = $8 }) } | FOR optvar GETS expr DO exprs END { mk ~pos:$loc (`Iterable_for { iterable_for_variable = $2; iterable_for_iterator = $4; iterable_for_loop = $6 } ) } | expr COALESCE expr { mk ~pos:$loc (`Coalesce ($1, $3)) } | TRY exprs FINALLY exprs END { mk_try ~pos:$loc ~ensure:$4 ~variable:"_" ~body:$2 () } | TRY exprs CATCH optvar COLON varlist DO exprs END { mk_try ~pos:$loc ~handler:$8 ~errors_list:(mk ~pos:$loc($6) (`List $6)) ~variable:$4 ~body:$2 () } | TRY exprs CATCH optvar COLON varlist DO exprs FINALLY exprs END { mk_try ~pos:$loc ~ensure:$10 ~handler:$8 ~errors_list:(mk ~pos:$loc($6) (`List $6)) ~variable:$4 ~body:$2 () } | TRY exprs CATCH optvar DO exprs END { mk_try ~pos:$loc ~handler:$6 ~variable:$4 ~body:$2 () } | TRY exprs CATCH optvar DO exprs FINALLY exprs END { mk_try ~pos:$loc ~ensure:$8 ~handler:$6 ~variable:$4 ~body:$2 () } | IF exprs THEN exprs if_elsif END { mk ~pos:$loc (`If {if_condition = $2; if_then = $4; if_elsif = fst $5; if_else = snd $5 }) } | REGEXP { mk ~pos:$loc (`Regexp $1) } | expr QUESTION expr COLON expr { mk ~pos:$loc (`Inline_if {if_condition = $1; if_then = $3; if_elsif = []; if_else = Some $5}) } | expr AND expr { match $1.term, $3.term with | `BoolOp ("and", l), `BoolOp ("and", l') -> mk ~pos:$loc (`BoolOp ("and", l@l')) | `BoolOp ("and", l), _ -> mk ~pos:$loc (`BoolOp ("and", l@[$3])) | _, `BoolOp ("and", l) -> mk ~pos:$loc (`BoolOp ("and", $1::l)) | _ -> mk ~pos:$loc (`BoolOp ("and", [$1; $3])) } | expr OR expr { match $1.term, $3.term with | `BoolOp ("or", l), `BoolOp ("or", l') -> mk ~pos:$loc (`BoolOp ("or", l@l')) | `BoolOp ("or", l), _ -> mk ~pos:$loc (`BoolOp ("or", l@[$3])) | _, `BoolOp ("or", l) -> mk ~pos:$loc (`BoolOp ("or", $1::l)) | _ -> mk ~pos:$loc (`BoolOp ("or", [$1; $3])) } | expr BIN1 expr { mk ~pos:$loc (`Infix ($1, $2, $3)) } | expr BIN2 expr { mk ~pos:$loc (`Infix ($1, $2, $3)) } | expr BIN3 expr { mk ~pos:$loc (`Infix ($1, $2, $3)) } | expr TIMES expr { mk ~pos:$loc (`Infix ($1, "*", $3)) } | expr MINUS expr { mk ~pos:$loc (`Infix ($1, "-", $3)) } | expr AT expr { mk ~pos:$loc (`At ($1, $3)) } | time_predicate { $1 } invoke: | VAR { `String $1 } | VARLPAR app_list RPAR { `App ($1, $2) } time_predicate: | INTERVAL { mk ~pos:$loc (`Time_interval $1) } | TIME { mk ~pos:$loc (`Time $1) } ty: | UNDERSCORE { `Named "_" } | VAR { `Named $1 } | ty QUESTION { `Nullable $1 } | LBRA ty RBRA { `List $2 } | LBRA ty RBRA VAR VAR DOT VAR { mk_json_assoc_object_ty ~pos:$loc ($2,$4,$5,$7) } | LPAR ty_tuple RPAR { `Tuple $2 } | LPAR argsty RPAR YIELDS ty { `Arrow ($2,$5) } | LCUR record_ty RCUR { `Record $2 } | ty DOT VAR { `Invoke ($1, $3) } | ty QUESTION_DOT LCUR record_ty RCUR { `Method (`Nullable $1, $4) } | ty DOT LCUR record_ty RCUR { `Method ($1, $4) } | ty_source { `Source $1 } record_ty: | { [] } | meth_ty { [$1] } | meth_ty COMMA record_ty { $1::$3 } meth_ty: | VAR COLON ty { { optional_meth = false; name = $1; typ = $3; json_name = None } } | VAR QUESTION COLON ty { { optional_meth = true; name = $1; typ = $4; json_name = None } } | STRING VAR VAR COLON ty { match $2 with |"as" -> { optional_meth = false; name = $3; typ = $5; json_name = Some (render_string ~pos:$loc $1) } | _ -> raise (Term_base.Parse_error ($loc, "Invalid type constructor")) } | STRING VAR VAR QUESTION COLON ty { match $2 with |"as" -> { optional_meth = true; name = $3; typ = $6; json_name = Some (render_string ~pos:$loc $1) } | _ -> raise (Term_base.Parse_error ($loc, "Invalid type constructor")) } ty_source: | VARLPAR RPAR { $1, { extensible = false; tracks = [] } } | VARLPAR ty_source_tracks RPAR { $1, $2 } ty_source_tracks: | VAR GETS ty_content { { extensible = false; tracks = [{track_name = $1; track_type = fst $3; track_params = snd $3}] } } | DOTDOTDOT { { extensible = true; tracks = [] } } | VAR GETS ty_content COMMA ty_source_tracks { { $5 with tracks = { track_name = $1; track_type = fst $3; track_params = snd $3}::$5.tracks } } ty_content: | VAR { $1, [] } | VAR DOT VAR { $1 ^ "." ^ $3, [] } | VAR DOT VAR DOT VAR { $1 ^ "." ^ $3 ^ "." ^ $5, [] } | VARLPAR ty_content_args RPAR { $1, $2 } | VAR DOT VARLPAR ty_content_args RPAR { $1 ^ "." ^ $3, $4 } | VAR DOT VAR DOT VARLPAR ty_content_args RPAR { $1 ^ "." ^ $3 ^ "." ^ $5, $6 } ty_content_args: | { [] } | ty_content_arg { [$1] } | ty_content_arg COMMA ty_content_args { $1::$3 } ty_content_arg: | VAR { ("", `Verbatim $1) } | INT { ("", `Verbatim $1) } | FLOAT { ("", `Verbatim $1) } | STRING { ("", `String ($loc($1), $1)) } | VAR GETS VAR { ($1, `Verbatim $3) } | VAR GETS STRING { ($1, `String ($loc($3), $3)) } | VAR GETS INT { ($1, `Verbatim $3) } | VAR GETS FLOAT { ($1, `Verbatim $3) } ty_tuple: | ty TIMES ty { [$1; $3] } | ty TIMES ty_tuple { $1::$3 } argty: | ty { false,"",$1 } | VAR COLON ty { false,$1,$3 } | QUESTION VAR COLON ty { true,$2,$4 } argsty: | { [] } | argty { [$1] } | argty COMMA argsty { $1::$3 } varlist: | LBRA inner_list RBRA { $2 } inner_list: | inner_list_item COMMA inner_list { $1::$3 } | inner_list_item { [$1] } | { [] } inner_list_item: | DOTDOTDOT expr { `Ellipsis $2 } | expr { `Term $1 } inner_tuple: | expr COMMA expr { [$1;$3] } | expr COMMA inner_tuple { $1::$3 } app_list_elem: | VAR GETS expr { `Term ($1,$3) } | expr { `Term ("",$1) } | ARGS_OF LPAR VAR RPAR { `Argsof {only = []; except = []; source = $3 } } | ARGS_OF LPAR subfield RPAR { `Argsof {only = []; except = []; source = String.concat "." $3 } } | ARGS_OF LPAR VARLBRA args_of_params RBRA RPAR { `Argsof {only = fst $4; except = snd $4; source = $3 } } | ARGS_OF LPAR subfield_lbra args_of_params RBRA RPAR { `Argsof {only = fst $4; except = snd $4; source = String.concat "." $3} } app_list: | { [] } | app_list_elem { [$1] } | app_list_elem COMMA app_list { $1::$3 } optvar: | VAR { $1 } | UNDERSCORE { "_" } pattern_list: | { [] } | pattern { [$1] } | pattern_list COMMA pattern { $1@[$3] } spread: | DOTDOTDOT { "_" } | DOTDOTDOT optvar { $2 } pattern_list_with_spread: | spread { [], Some ($loc, $1), [] } | pattern_list { $1, None, [] } | spread COMMA pattern_list { [], Some ($loc($1), $1), $3 } | pattern_list COMMA spread { $1, Some ($loc($3), $3), [] } | pattern_list COMMA spread COMMA pattern_list { $1, Some ($loc($3), $3), $5 } tuple_pattern: | LPAR pattern_list RPAR { `PTuple $2 } list_pattern: | LBRA pattern_list_with_spread RBRA { `PList $2 } meth_pattern_el: | VAR { $1, `None } | VAR QUESTION { $1, `Nullable } | VAR GETS pattern { $1, `Pattern $3 } meth_pattern_list: | { [] } | meth_pattern_el { [$1] } | meth_pattern_el COMMA meth_pattern_list { $1::$3 } record_pattern: | LCUR meth_pattern_list RCUR { $2 } meth_spread_list: | DOTDOTDOT { Some ({ pat_pos = $loc; pat_entry = `PVar ["_"] }), [] } | DOTDOTDOT optvar { Some ({ pat_pos = $loc($2); pat_entry = `PVar [$2] }), [] } | meth_pattern_el COMMA meth_spread_list { fst $3, $1::(snd $3) } record_spread_pattern: | LCUR meth_spread_list RCUR { $2 } meth_pattern: | record_spread_pattern { `PMeth $1 } | record_pattern { `PMeth (None, $1) } | VAR DOT record_pattern { let pat = { pat_pos = $loc($1); pat_entry = `PVar [$1] } in `PMeth (Some pat, $3) } | UNDERSCORE DOT record_pattern { let pat = { pat_pos = $loc; pat_entry = `PVar ["_"] } in `PMeth (Some pat, $3) } | tuple_pattern DOT record_pattern { let pat = { pat_pos = $loc($1); pat_entry = $1 } in `PMeth (Some pat, $3) } | list_pattern DOT record_pattern { let pat = { pat_pos = $loc($1); pat_entry = $1 } in `PMeth (Some pat, $3) } var_pattern: | optvar { `PVar [$1] } pattern: | var_pattern { { pat_pos = $loc; pat_entry = $1 } } | tuple_pattern { { pat_pos = $loc; pat_entry = $1 } } | list_pattern { { pat_pos = $loc; pat_entry = $1 } } | meth_pattern { { pat_pos = $loc; pat_entry = $1 } } subfield: | VAR DOT in_subfield { $1::$3 } in_subfield: | VAR { [$1] } | VAR DOT in_subfield { $1::$3 } let_opt_el: | VAR { $1, mk ~pos:$loc (`Var $1) } | VAR GETS expr { $1, $3 } let_opt: | let_opt_el { [$1] } | let_opt_el COMMA let_opt { $1::$3 } _let: | LET { Parser_helper.let_decoration_of_lexer_let_decoration $1 } | LETLBRA let_opt RBRA { match $1 with | `Json_parse -> `Json_parse (Parser_helper.args_of_json_parse ~pos:$loc $2) | _ -> raise (Term_base.Parse_error ($loc, "Invalid let constructor")) } def: | DEF { Parser_helper.let_decoration_of_lexer_let_decoration $1 } explicit_binding: | _let pattern GETS expr { `Let Parser_helper.(let_args ~decoration:$1 ~pat:$2 ~def:$4 ()) } | _let LPAR pattern COLON ty RPAR GETS expr { `Let Parser_helper.(let_args ~decoration:$1 ~pat:$3 ~def:$8 ~cast:$5 ()) } | _let subfield GETS expr { `Let Parser_helper.(let_args ~decoration:$1 ~pat:({ pat_pos = $loc($2); pat_entry = `PVar $2 }) ~def:$4 ()) } | def optvar g exprs END { `Def Parser_helper.(let_args ~decoration:$1 ~pat:({ pat_pos = $loc($2); pat_entry = `PVar [$2] }) ~def:$4 ()) } | def LPAR optvar COLON ty RPAR g exprs END { `Def Parser_helper.(let_args ~decoration:$1 ~pat:({ pat_pos = $loc($3); pat_entry =`PVar [$3] }) ~def:$8 ~cast:$5 ()) } | def subfield g exprs END { `Def Parser_helper.(let_args ~decoration:$1 ~pat:({ pat_pos = $loc($2); pat_entry = `PVar $2 }) ~def:$4 ()) } | def subfield_lpar arglist RPAR g exprs END { `Def Parser_helper.(let_args ~decoration:$1 ~pat:({ pat_pos = $loc($2); pat_entry = `PVar $2 }) ~arglist:$3 ~def:$6 ()) } binding: | optvar GETS expr { `Binding Parser_helper.(let_args ~decoration:`None ~pat:({ pat_pos = $loc($1); pat_entry = `PVar [$1] }) ~def:$3 ()) } | explicit_binding { ($1 :> binding) } subfield_lpar: | VARLPAR { [$1] } | VAR DOT subfield_lpar { $1::$3 } arglist: | { [] } | arg { [$1] } | arg COMMA arglist { $1::$3 } arg: | TILD VAR opt { `Term {label = $2; as_variable = None; typ = None; default = $3; annotations = []; pos = $loc($2) } } | TILD LPAR VAR COLON ty RPAR opt { `Term {label = $3; as_variable = None; typ = Some $5; default = $7; annotations = []; pos = $loc($3) } } | TILD VAR GETS UNDERSCORE opt { `Term {label = $2; as_variable = Some { pat_pos = $loc($4); pat_entry = `PVar ["_"] }; typ = None; default = $5; annotations = [`Deprecated (Printf.sprintf "Use `~%s:_`" $2)]; pos = $loc($2) } } | TILD VAR COLON pattern opt { `Term {label = $2; as_variable = Some $4; typ = None; default = $5; annotations = []; pos = $loc($4) } } | TILD VAR COLON LPAR pattern COLON ty RPAR opt { `Term {label = $2; as_variable = Some $5; typ = Some $7; default = $9; annotations = []; pos = $loc($5) } } | pattern opt { `Term {label = ""; as_variable = Some $1; typ = None; default = $2; annotations = []; pos = $loc($1)} } | LPAR pattern COLON ty RPAR opt { `Term {label = ""; as_variable = Some $2; typ = Some $4; default = $6; annotations = []; pos = $loc($2) } } | ARGS_OF LPAR VAR RPAR { `Argsof {only = []; except = []; source = $3 } } | ARGS_OF LPAR subfield RPAR { `Argsof {only = []; except = []; source = String.concat "." $3 } } | ARGS_OF LPAR VARLBRA args_of_params RBRA RPAR { `Argsof {only = fst $4; except = snd $4; source = $3 } } | ARGS_OF LPAR subfield_lbra args_of_params RBRA RPAR { `Argsof {only = fst $4; except = snd $4; source = String.concat "." $3 } } opt: | GETS expr { Some $2 } | { None } args_of_params: | VAR { [$1], [] } | GET VAR { [], [$2] } | VAR COMMA args_of_params { $1::(fst $3), (snd $3) } | GET VAR COMMA args_of_params { (fst $4), $2::(snd $4) } subfield_lbra: | VAR DOT in_subfield_lbra { $1::$3 } in_subfield_lbra: | VARLBRA { [$1] } | VAR DOT in_subfield_lbra { $1::$3 } if_elsif: | ELSIF exprs THEN exprs if_elsif { ($2, $4)::(fst $5), snd $5 } | ELSE exprs { [], Some $2 } | { [], None } encoder_opt: | %prec no_app { [] } | LPAR encoder_params RPAR { $2 } encoder_param: | VAR GETS expr { `Labelled (`Verbatim $1, $3) } | STRING GETS expr { `Labelled (`String ($loc($1), $1), $3) } | VAR { `Anonymous (`Verbatim $1) } | STRING { `Anonymous (`String (($loc($1), $1))) } | ENCODER encoder_opt { `Encoder ($1, $2) } encoder_params: | { [] } | encoder_param { [$1] } | encoder_param COMMA encoder_params { $1::$3 } plain_encoder_params: | LPAR encoder_params RPAR { $2 } optional_comma: | COMMA {} record: | VAR GETS expr { [`Method ($1, $3)] } | DOTDOTDOT expr { [`Ellipsis $2] } | record COMMA VAR GETS expr { $1@[`Method ($3,$5)] } | record COMMA DOTDOTDOT expr { $1@[`Ellipsis $4] } string_interpolation: | BEGIN_INTERPOLATION string_interpolation_elems END_INTERPOLATION { $1, $2 } string_interpolation_elem: | INTERPOLATED_STRING { `String $1 } | expr { `Term $1 } string_interpolation_elems: | string_interpolation_elem { [$1] } | string_interpolation_elem string_interpolation_elems { $1::$2 } if_def_var: | VAR { [$1] } | VAR DOT if_def_var { $1::$3 } if_def: | PP_IFDEF if_def_var exprs PP_ENDIF { { if_def_negative = $1; if_def_condition = String.concat "." $2; if_def_then = $3; if_def_else = None } } | PP_IFDEF if_def_var exprs PP_ELSE exprs PP_ENDIF { { if_def_negative = $1; if_def_condition = String.concat "." $2; if_def_then = $3; if_def_else = Some $5; } } if_encoder: | PP_IFENCODER ENCODER exprs PP_ENDIF { { if_encoder_negative = $1; if_encoder_condition = $2; if_encoder_then = $3; if_encoder_else = None } } | PP_IFENCODER ENCODER exprs PP_ELSE exprs PP_ENDIF { { if_encoder_negative = $1; if_encoder_condition = $2; if_encoder_then = $3; if_encoder_else = Some $5; } } if_version_op: | BIN1 { match $1 with | "==" -> `Eq | ">=" -> `Geq | "<=" -> `Leq | "<" -> `Lt | ">" -> `Gt | _ -> raise (Term_base.Parse_error ($loc, "invalid %ifversion operand")) } if_version_version: | VERSION { $1 } | INT { Lang_string.Version.of_string $1 } | FLOAT { Lang_string.Version.of_string $1 } if_version: | PP_IFVERSION if_version_op if_version_version exprs PP_ENDIF { { if_version_op = $2; if_version_version = $3; if_version_then = $4; if_version_else = None } } | PP_IFVERSION if_version_op if_version_version exprs PP_ELSE exprs PP_ENDIF { { if_version_op = $2; if_version_version = $3; if_version_then = $4; if_version_else = Some $6; } } annotate: | annotate_metadata COLON { $1 } annotate_metadata: | annotate_metadata_entry annotate_metadata { $1::$2 } | annotate_key GETS annotate_value { [$1, $3] } annotate_metadata_entry: | annotate_key GETS annotate_value COMMA { $1, $3 } annotate_key: | VAR { $1 } | STRING { render_string ~pos:$loc $1 } annotate_value: | INT { $1 } | FLOAT { $1 } | BOOL { string_of_bool $1 } | VAR { $1 } | STRING { render_string ~pos:$loc $1 } liquidsoap-2.4.2/src/lang/base/parser_helper.ml000066400000000000000000000115471513273233300215120ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Helper functions for the parser. *) open Parsed_term module Term = Parsed_term module Vars = Term_base.Vars type arglist = Parsed_term.fun_arg list type pos = Parsed_term.pos type lexer_let_decoration = [ `None | `Recursive | `Replaces | `Eval | `Json_parse | `Yaml_parse | `Xml_parse | `Sqlite_row | `Sqlite_query ] type explicit_binding = [ `Def of Term._let | `Let of Term._let ] type binding = [ explicit_binding | `Binding of Term._let ] let render_string_ref = ref (fun ~pos:_ _ -> assert false) (* This is filled by Lexer to make it possible to use this function in the parser. *) let render_string ~pos s = let fn = !render_string_ref in fn ~pos s let pending_comments = ref [] let clear_comments () = pending_comments := [] let append_comment ~pos c = let comments = List.map String.trim (String.split_on_char '\n' c) in pending_comments := (pos, comments) :: !pending_comments let comment_distance term_pos comment_pos = if (fst comment_pos).Lexing.pos_lnum = (snd term_pos).Lexing.pos_lnum then (`Before, 0) else ( let before_distance = (fst term_pos).Lexing.pos_lnum - (snd comment_pos).Lexing.pos_lnum in let after_distance = (fst comment_pos).Lexing.pos_lnum - (snd term_pos).Lexing.pos_lnum in if 0 <= after_distance && (before_distance < 0 || after_distance < before_distance) then (`After, after_distance) else (`Before, before_distance)) let sort_comments comments = List.sort (fun (p, _) (p', _) -> Stdlib.compare (fst p).Lexing.pos_cnum (fst p').Lexing.pos_cnum) comments let attach_comments term = List.iter (fun (comment_pos, c) -> let closest_term = ref term in let distance = ref (comment_distance term.pos comment_pos) in Parsed_term.iter_term (fun term -> match (comment_distance term.pos comment_pos, !distance) with | (t, d), (t', d') when 0 <= d && (d' < 0 || if t = `Before && t' = `After then d <= d' else d < d' ) -> distance := (t, d); closest_term := term | _ -> ()) term; let comment = match !distance with `Before, _ -> `Before c | `After, _ -> `After c in !closest_term.comments <- sort_comments ((comment_pos, comment) :: !closest_term.comments)) !pending_comments; pending_comments := [] let let_args ~decoration ~pat ?arglist ~def ?cast () = { decoration; pat; arglist; def; cast } let mk_json_assoc_object_ty ~pos = function | `Tuple [`Named "string"; ty], "as", "json", "object" -> `Json_object ty | _ -> raise (Term_base.Parse_error (pos, "Invalid type constructor")) type let_opt_el = string * Term.t let let_decoration_of_lexer_let_decoration = function | `Json_parse -> `Json_parse [] | `Yaml_parse -> `Yaml_parse | `Xml_parse -> `Xml_parse | `Sqlite_query -> `Sqlite_query | `Sqlite_row -> `Sqlite_row | `Eval -> `Eval | `Recursive -> `Recursive | `None -> `None | `Replaces -> `Replaces let args_of_json_parse ~pos = function | [] -> [] | [("json5", v)] -> [("json5", v)] | (lbl, _) :: _ -> raise (Term_base.Parse_error (pos, "Invalid argument " ^ lbl ^ " for json.parse let constructor")) let mk = Parsed_term.make let mk_fun ~pos arguments body = mk ~pos (`Fun (arguments, body)) let mk_try ?ensure ?handler ?errors_list ~variable ~body ~pos () = mk ~pos (`Try { try_body = body; try_variable = variable; try_errors_list = errors_list; try_handler = handler; try_finally = ensure; }) let mk_let ~pos _let body = let ast = match _let with | `Let v -> `Let (v, body) | `Def v -> `Def (v, body) | `Binding v -> `Binding (v, body) in mk ~pos ast let mk_encoder ~pos e p = mk ~pos (`Encoder (e, p)) liquidsoap-2.4.2/src/lang/base/parser_helper.mli000066400000000000000000000052751513273233300216640ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Helper functions for the parser. *) module Term = Parsed_term module Vars = Term_base.Vars type arglist = Term.fun_arg list type pos = Parsed_term.pos type lexer_let_decoration = [ `Eval | `Json_parse | `None | `Recursive | `Replaces | `Yaml_parse | `Xml_parse | `Sqlite_row | `Sqlite_query ] type explicit_binding = [ `Def of Term._let | `Let of Term._let ] type binding = [ explicit_binding | `Binding of Term._let ] type let_opt_el = string * Term.t val clear_comments : unit -> unit val append_comment : pos:pos -> string -> unit val attach_comments : Term.t -> unit val mk_let : pos:pos -> [< `Binding of Term._let | `Def of Term._let | `Let of Term._let ] -> Term.t -> Term.t val let_args : decoration:Term.let_decoration -> pat:Term.pattern -> ?arglist:arglist -> def:Term.t -> ?cast:Term.type_annotation -> unit -> Term._let val let_decoration_of_lexer_let_decoration : lexer_let_decoration -> Term.let_decoration val mk_json_assoc_object_ty : pos:pos -> Parsed_term.type_annotation * string * string * string -> Term.type_annotation val mk : ?comments:(pos * Parsed_term.comment) list -> ?annotations:Parsed_term.term_annotation list -> pos:pos -> Term.parsed_ast -> Term.t val mk_try : ?ensure:Term.t -> ?handler:Term.t -> ?errors_list:Term.t -> variable:string -> body:Term.t -> pos:pos -> unit -> Term.t val mk_fun : pos:pos -> arglist -> Term.t -> Term.t val mk_encoder : pos:pos -> string -> Term.encoder_params -> Term.t val args_of_json_parse : pos:pos -> (string * 'a) list -> (string * 'a) list val render_string_ref : (pos:pos -> char * string -> string) ref val render_string : pos:pos -> char * string -> string liquidsoap-2.4.2/src/lang/base/plug.ml000066400000000000000000000067201513273233300176230ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** A plug is something where plug-ins plug. *) (* class ['a] plug ?(register_hook = fun _ -> ()) doc insensitive duplicates = object inherit Doc.item doc val mutable plugins : (string * 'a) list = [] val mutable aliases : (string * 'a) list = [] method register plugin ?plugin_aliases ?doc ?sdoc v = let plugin = if insensitive then String.uppercase_ascii plugin else plugin in let doc () = match (doc, sdoc) with | Some d, _ -> Lazy.force d | None, None -> Doc.trivial "(no doc)" | None, Some s -> Doc.trivial s in if duplicates then ( subsections <- (plugin, Lazy.from_fun doc) :: subsections; plugins <- (plugin, v) :: plugins) else ( subsections <- (plugin, Lazy.from_fun doc) :: List.filter (fun (k, _) -> k <> plugin) subsections; plugins <- (plugin, v) :: List.filter (fun (k, _) -> k <> plugin) plugins); register_hook (plugin, v); match plugin_aliases with | Some l -> aliases <- List.map (fun alias -> (alias, v)) l @ aliases | None -> () method is_registered a = List.mem_assoc a plugins method keys = List.fold_left (fun l (k, _) -> k :: l) [] plugins method iter ?(rev = false) f = let plugins = if rev then List.rev plugins else plugins in List.iter (fun (k, v) -> f k v) plugins method get_all = plugins method get plugin = let plugin = if insensitive then String.uppercase_ascii plugin else plugin in try Some (List.assoc plugin plugins) with Not_found -> ( try Some (List.assoc plugin aliases) with Not_found -> None) end *) (** A plug. *) type 'a t = { name : string; doc : Doc.Plug.t; register_hook : string -> 'a -> unit; mutable items : (string * 'a) list; } (** Create a plug. *) let create ?(register_hook = fun _ _ -> ()) ~doc name = { name; doc = Doc.Plug.create ~doc name; register_hook; items = [] } let register plug name ~doc value = if List.mem_assoc name plug.items then failwith ("Plugin already registered in " ^ plug.name ^ ": " ^ name); Doc.Plug.add plug.doc ~doc name; plug.items <- (name, value) :: plug.items; plug.register_hook name value let get plug name = List.assoc_opt name plug.items (** List all the plugins. *) let list plug = plug.items let iter plug f = List.iter (fun (k, v) -> f k v) plug.items let find plug f = List.find_opt (fun (k, v) -> f k v) plug.items liquidsoap-2.4.2/src/lang/base/pos.ml000066400000000000000000000064121513273233300174530ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Operations on positions (in source files). *) (** A position. *) type t = bytes type pos = { fname : string; lstart : int; lstop : int; cstart : int; cstop : int; } let pack_offset = 8 let pack { fname; lstart; lstop; cstart; cstop } = let b = Buffer.create (String.length fname + pack_offset) in Buffer.add_uint16_ne b lstart; Buffer.add_uint16_ne b lstop; Buffer.add_uint16_ne b cstart; Buffer.add_uint16_ne b cstop; Buffer.add_string b fname; Buffer.to_bytes b let unpack s = let fname_len = Bytes.length s - pack_offset in let fname = Bytes.create fname_len in Bytes.blit s pack_offset fname 0 fname_len; let lstart = Bytes.get_uint16_ne s 0 in let lstop = Bytes.get_uint16_ne s 2 in let cstart = Bytes.get_uint16_ne s 4 in let cstop = Bytes.get_uint16_ne s 6 in { fname = Bytes.unsafe_to_string fname; lstart; lstop; cstart; cstop } let of_lexing_pos (start, stop) = let f l = (l.Lexing.pos_lnum, l.Lexing.pos_cnum - l.Lexing.pos_bol) in let lstart, cstart = f start in let lstop, cstop = f stop in pack { fname = start.Lexing.pos_fname; lstart; cstart; lstop; cstop } let to_string ?(prefix = "at ") pos = let { fname; lstart; lstop; cstart; cstop } = unpack pos in let prefix = match fname with "" -> prefix | file -> prefix ^ file ^ ", " in if lstart = lstop then if cstop = cstart + 1 then Printf.sprintf "%sline %d, char %d" prefix lstart cstart else Printf.sprintf "%sline %d, char %d-%d" prefix lstart cstart cstop else Printf.sprintf "%sline %d char %d - line %d char %d" prefix lstart cstart lstop cstop let string_of_pos = to_string module Option = struct type base = t type t = base option let to_string ?prefix : t -> string = function | Some pos -> to_string ?prefix pos | None -> "unknown position" end module List = struct type base = t type t = base list (** The most relevant position in a call stack. *) let rec to_pos = function | [x] -> x | _ :: l -> to_pos l | [] -> raise Not_found let rec to_string ?(newlines = false) ?prefix = function | [] -> "unknown position" | [pos] -> string_of_pos ?prefix pos | pos :: l -> to_string ~newlines ?prefix l ^ (if newlines then ",\n" else ", ") ^ string_of_pos ?prefix pos end liquidsoap-2.4.2/src/lang/base/pos.mli000066400000000000000000000032011513273233300176150ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Operations on positions (in source files). *) type t type pos = { fname : string; lstart : int; lstop : int; cstart : int; cstop : int; } val pack_offset : int val pack : pos -> t val unpack : t -> pos val of_lexing_pos : Lexing.position * Lexing.position -> t val to_string : ?prefix:string -> t -> string val string_of_pos : ?prefix:string -> t -> string module Option : sig type base = t type t = base option val to_string : ?prefix:string -> t -> string end module List : sig type base = t type t = base list val to_pos : t -> base val to_string : ?newlines:bool -> ?prefix:string -> t -> string end liquidsoap-2.4.2/src/lang/base/preprocessor.ml000066400000000000000000000155741513273233300214110ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type tokenizer = unit -> Parser.token * Term_base.parsed_pos let mk_tokenizer ?(fname = "") lexbuf = Sedlexing.set_filename lexbuf fname; fun () -> match Lexer.token lexbuf with | Parser.PP_STRING (c, s, pos) -> (Parser.STRING (c, s), pos) | Parser.PP_REGEXP (r, flags, pos) -> (Parser.REGEXP (r, flags), pos) | token -> (token, Sedlexing.lexing_bytes_positions lexbuf) (* The expander turns "bla #{e} bli" into ("bla "^string(e)^" bli"). *) type exp_item = String of string | Expr of tokenizer | End exception Found_interpolation let expand_string ?fname tokenizer = let state = Queue.create () in let add pos x = Queue.add (x, pos) state in let pop () = ignore (Queue.take state) in let clear () = Queue.clear state in let is_interpolating () = try Queue.iter (function Expr _, _ -> raise Found_interpolation | _ -> ()) state; false with Found_interpolation -> true in let parse ~sep s pos = let rex = Re.Pcre.regexp "#\\{([^}]*)\\}" in let l = Re.Pcre.full_split ~rex s in let l = if l = [] then [Re.Pcre.Text s] else l in let add = add pos in let rec parse = function | Re.Pcre.Group (_, x) :: l -> let x = Lexer.render_string ~pos ~sep x in let lexbuf = Sedlexing.Utf8.from_string x in let tokenizer = mk_tokenizer ?fname lexbuf in let tokenizer () = (fst (tokenizer ()), pos) in add (Expr tokenizer); parse l | Re.Pcre.Text x :: l -> add (String x); parse l | Re.Pcre.NoGroup :: l | Re.Pcre.Delim _ :: l -> parse l | [] -> add End in parse l in let rec token () = if Queue.is_empty state then ( match tokenizer () with | (Parser.STRING (sep, s), pos) as tok -> parse ~sep s pos; if is_interpolating () then (Parser.BEGIN_INTERPOLATION sep, pos) else ( clear (); tok) | x -> x) else ( let el, pos = Queue.peek state in match el with | String s -> pop (); (Parser.INTERPOLATED_STRING s, pos) | Expr tokenizer -> ( match tokenizer () with | Parser.EOF, _ -> pop (); token () | x, _ -> (x, pos)) | End -> pop (); (Parser.END_INTERPOLATION, pos)) in token (** Special token in order to avoid 3.{s = "a"} to be parsed as a float followed by a record. *) let int_meth tokenizer = let q = Queue.create () in let fill () = match tokenizer () with | Parser.PP_INT_DOT_LCUR n, (spos, epos) -> let a n pos = { pos with Lexing.pos_cnum = pos.Lexing.pos_cnum - n } in Queue.add_seq q (List.to_seq [ (Parser.INT n, (spos, a 2 epos)); (Parser.DOT, (a 2 spos, a 1 epos)); (Parser.LCUR, (a 1 spos, epos)); ]) | t -> Queue.add t q in let token () = if Queue.is_empty q then fill (); Queue.pop q in token (* Replace DOTVAR v with DOT, VAR v and NULLDOT with "_null", DOT *) let dotter tokenizer = let state = ref None in let token () = match !state with | Some t -> state := None; t | None -> ( match tokenizer () with | Parser.NULLDOT, pos -> state := Some (Parser.DOT, pos); (Parser.VAR "_null", pos) | Parser.DOTVAR v, pos -> state := Some (Parser.VAR v, pos); (Parser.DOT, pos) | t -> t) in token (** Change MINUS to UMINUS if the minus is not preceded by a number (or an expression which could produce a number). *) let uminus tokenizer = let no_uminus = ref false in let token () = match tokenizer () with | ( Parser.INT _, _ | Parser.FLOAT _, _ | Parser.VAR _, _ | Parser.RPAR, _ | Parser.RCUR, _ ) as t -> no_uminus := true; t | Parser.MINUS, pos when not !no_uminus -> no_uminus := false; (Parser.UMINUS, pos) | t -> no_uminus := false; t in token (* Last but not least: remove new lines and merge some tokens around them in order to remove some ambiguities, typically between: def foo \n (x,y) ... << Normal definition, starting with a couple def foo(x,y) ... << Definition of the function foo *) let strip_newlines tokenizer = let state = ref None in let rec token () = let inject_varlpar var v = match tokenizer () with | Parser.LPAR, (_, endp) -> state := None; let startp = fst (snd v) in (Parser.VARLPAR var, (startp, endp)) | Parser.LBRA, (_, endp) when var <> "in" -> state := None; let startp = fst (snd v) in (Parser.VARLBRA var, (startp, endp)) | Parser.PP_ENDL, _ -> state := None; v | x -> state := Some x; v in match !state with | None -> ( match tokenizer () with | Parser.PP_ENDL, _ -> token () | ((Parser.NULL, _) as v) | ((Parser.UNDERSCORE, _) as v) | ((Parser.VAR _, _) as v) -> state := Some v; token () | x -> x) | Some ((Parser.VAR var, _) as v) -> inject_varlpar var v | Some ((Parser.UNDERSCORE, _) as v) -> inject_varlpar "_" v | Some ((Parser.NULL, _) as v) -> inject_varlpar "_null" v | Some x -> state := None; x in token (* Wrap the lexer with its extensions *) let mk_tokenizer ?fname lexbuf = let tokenizer = mk_tokenizer ?fname lexbuf |> expand_string ?fname |> int_meth |> dotter |> uminus |> strip_newlines in fun () -> let t, (startp, endp) = tokenizer () in (t, startp, endp) liquidsoap-2.4.2/src/lang/base/profiler.ml000066400000000000000000000054551513273233300205020ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Function call information. *) type t = { total_time : float; (** Time spent in the function. *) self_time : float; (** Time spent in the function excluding children. *) } let calls = ref [] (* Call stack. *) let stack = ref [] (* Time spent in children. *) let children = ref [ref 0.] (** Indicate the time spent in a given function. *) let add f t = calls := (f, t) :: !calls (** Measure time for a given function. *) let time fname f x = stack := fname :: !stack; children := ref 0. :: !children; let t0 = Unix.gettimeofday () in let ans = f x in let t1 = Unix.gettimeofday () in stack := List.tl !stack; let children_time = !(List.hd !children) in children := List.tl !children; let dt = t1 -. t0 in List.hd !children := !(List.hd !children) +. dt; (* TODO: time is counted multiple times in recursive calls. *) let total_time = dt in let self_time = dt -. children_time in let t = { total_time; self_time } in add fname t; ans module M = Map.Make (struct type t = string let compare = compare end) let stats () = let m = ref M.empty in List.iter (fun (f, t) -> m := M.update f (function Some l -> Some (t :: l) | None -> Some [t]) !m) !calls; let m = !m in let l = M.bindings m in let l = List.map (fun (f, t) -> ( f, ( List.fold_left (fun x t -> x +. t.self_time) 0. t, List.fold_left (fun x t -> x +. t.total_time) 0. t, List.length t ) )) l in let l = List.sort (fun (_, t) (_, t') -> -compare t t') l in let l = List.map (fun (f, (self, total, n)) -> [| f; string_of_float self; string_of_float total; string_of_int n |]) l in let l = [| "function"; "self"; "total"; "calls" |] :: [||] :: l in let l = Array.of_list l in Lang_string.string_of_matrix l liquidsoap-2.4.2/src/lang/base/repr.ml000066400000000000000000000460431513273233300176260ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** User-friendly representation of types. *) (** Show generalized variables in records. *) let show_record_schemes = ref true (** Use globally unique names for existential variables. *) let global_evar_names = ref false open Type_base include R type t = Type_base.constr R.t (** Given a position, find the relevant excerpt. *) let excerpt pos = let { Pos.fname; lstart; lstop; cstart; cstop } = Pos.unpack pos in try let lines = let ic = open_in fname in Fun.protect ~finally:(fun () -> close_in ic) (fun () -> let n = ref 1 in while !n < lstart do ignore (input_line ic); incr n done; let lines = ref [] in while !n <= lstop do lines := input_line ic :: !lines; incr n done; lines) in let lines = Array.of_list (List.rev !lines) in let lines = let n = Array.length lines in if Array.length lines > 5 then [| lines.(0); lines.(1); "..."; lines.(n - 2); lines.(n - 1) |] else lines in let insert_at x n s = let s1 = String.sub s 0 n in let s2 = String.sub s n (String.length s - n) in s1 ^ x ^ s2 in (* The order is important here because both lines might be the same. *) lines.(Array.length lines - 1) <- insert_at (Console.stop_color ()) cstop lines.(Array.length lines - 1); lines.(0) <- insert_at (Console.start_color [`red]) cstart lines.(0); let lines = Array.to_list lines in let s = String.concat "\n" lines ^ "\n" in Some s with _ -> None let excerpt_opt = function Some pos -> excerpt pos | None -> None (** Given a strictly positive integer, generate a name in [a-z]+: a, b, ... z, aa, ab, ... az, ba, ... *) let name = let base = 26 in let c i = char_of_int (int_of_char 'a' + i - 1) in let add i suffix = Printf.sprintf "%c%s" (c i) suffix in let rec n suffix i = if i <= base then add i suffix else ( let head = i mod base in let head = if head = 0 then base else head in n (add head suffix) ((i - head) / base)) in n "" (** Generate a globally unique name for evars (used for debugging only). *) let evar_global_name = let evars = Hashtbl.create 10 in let n = ref 0 in fun i -> try Hashtbl.find evars i with Not_found -> incr n; let name = String.uppercase_ascii (name !n) in Hashtbl.replace evars i name; name (** Compute the structure that a term represents, given the list of universally quantified variables. Also takes care of computing the printing name of variables, including constraint symbols, which are removed from constraint lists. It supports a mechanism for filtering out parts of the type, which are then translated as `Ellipsis. *) let make ?(filter_out = fun _ -> false) ?(generalized = []) t : t = let split_constr c = List.fold_left (fun (s, constraints) c -> (s, c :: constraints)) ("", []) c in let uvar g var = let constr_symbols, c = split_constr (Constraints.elements var.constraints) in let rec index n = function | v :: tl -> if Var.eq v var then Printf.sprintf "'%s%s" constr_symbols (name n) else index (n + 1) tl | [] -> assert false in let v = index 1 (List.rev g) in (* let v = Printf.sprintf "'%d" i in *) `UVar (v, Constraints.of_list c) in let counter = let c = ref 0 in fun () -> incr c; !c in let evars = Hashtbl.create 10 in let evar var = let constr_symbols, c = split_constr (Constraints.elements var.constraints) in if !global_evar_names || !debug || !debug_levels then ( let v = Printf.sprintf "'%s%s" constr_symbols (evar_global_name var.name) in let v = if !debug_levels then ( let level = var.level in let level = if level = max_int then "∞" else string_of_int level in Printf.sprintf "%s[%s]" v level) else v in `EVar (v, Constraints.of_list c)) else ( let s = try Hashtbl.find evars var.name with Not_found -> let name = String.uppercase_ascii (name (counter ())) in Hashtbl.replace evars var.name name; name in `EVar (Printf.sprintf "'%s%s" constr_symbols s, Constraints.of_list c)) in let rec repr g t = if filter_out t then `Ellipsis else ( match t.descr with | Int -> `Constr ("int", []) | Float -> `Constr ("float", []) | String -> `Constr ("string", []) | Bool -> `Constr ("bool", []) | Never -> `Constr ("never", []) | Custom c -> c.repr repr g c.typ | Getter t -> `Getter (repr g t) | List { t; json_repr } -> `List (repr g t, json_repr) | Tuple l -> `Tuple (List.map (repr g) l) | Nullable t -> `Nullable (repr g t) | Meth ({ meth = l; optional; scheme = g', u; json_name }, v) -> let gen = List.map (fun v -> match uvar (g' @ g) v with `UVar v -> v) (List.sort_uniq compare g') in `Meth ( R. { name = l; optional; scheme = (gen, repr (g' @ g) u); json_name; }, repr g v ) | Constr { constructor; params } -> `Constr (constructor, List.map (fun (l, t) -> (l, repr g t)) params) | Arrow (args, t) -> `Arrow ( List.map (fun (opt, lbl, t) -> (opt, lbl, repr g t)) args, repr g t ) | Var { contents = Free var } -> if List.exists (Var.eq var) g then uvar g var else evar var | Var { contents = Link (`Covariant, t) } when !debug || !debug_variance -> `Debug ("[>", repr g t, "]") | Var { contents = Link (_, t) } -> repr g t) in repr generalized t (** Print a type representation. Unless in debug mode, variable identifiers are not shown, and variable names are generated. Names are only meaningful over one printing, as they are re-used. *) let print f t = (* Display the type and return the list of variables that occur in it. The [par] params tells whether (..)->.. should be surrounded by parenthesis or not. *) let rec print ~par vars : t -> DS.t = function | `Constr (name, [(_, (`Meth _ as record_type))]) when name = "source" || name = "format" -> Format.open_box (1 + String.length name); Format.fprintf f "%s(" name; let rec extract fields = function | `Meth ({ R.name = field }, base_type) when List.mem_assoc (Some field) fields -> extract fields base_type | `Meth ({ R.scheme = _, `Constr ("never", _) }, base_type) -> extract fields base_type | `Meth (R.{ name = field; optional; scheme = _, ty }, base_type) -> extract ((Some field, (optional, ty)) :: fields) base_type | base_type -> (fields, base_type) in let fields, base_type = extract [] record_type in let fields = List.sort (fun (l, _) (l', _) -> Stdlib.compare l l') fields in let fields = match (base_type, fields) with | `Tuple [], _ -> fields | v, _ -> fields @ [(None, (false, v))] in let _, vars = List.fold_left (fun (first, vars) (lbl, (optional, t)) -> if not first then Format.fprintf f ",@ "; ignore (Option.map (Format.fprintf f "%s%s=" (if optional then "?" else "")) lbl); let vars = print ~par:false vars t in (false, vars)) (true, vars) fields in Format.fprintf f ")"; Format.close_box (); vars | `Constr (name, []) -> Format.fprintf f "%s" name; vars | `Constr ("none", _) -> Format.fprintf f "none"; vars | `Constr (name, params) -> Format.open_box (1 + String.length name); Format.fprintf f "%s(" name; let vars = print_list vars params in Format.fprintf f ")"; Format.close_box (); vars | `Tuple [] -> Format.fprintf f "unit"; vars | `Tuple l -> if par then Format.fprintf f "@[<1>(" else Format.fprintf f "@[<0>"; let rec aux vars = function | [a] -> print ~par:true vars a | a :: l -> let vars = print ~par:true vars a in Format.fprintf f " *@ "; aux vars l | [] -> assert false in let vars = aux vars l in if par then Format.fprintf f ")@]" else Format.fprintf f "@]"; vars | `Nullable t -> let vars = print ~par:true vars t in Format.fprintf f "?"; vars | `Meth (R.{ name = l; scheme = _, a }, b) as t -> if not !debug then ( (* Find all methods. *) let rec aux = function | `Meth (R.{ name = l; optional; scheme = t; json_name }, u) -> let m, u = aux u in ((l, optional, t, json_name) :: m, u) | u -> ([], u) in let m, t = aux t in (* Filter out duplicates. *) let rec aux = function | (l, o, t, json_name) :: m -> (l, o, t, json_name) :: aux (List.filter (fun (l', _, _, _) -> l <> l') m) | [] -> [] in let m = aux m in (* Put latest addition last. *) (* let m = List.rev m in *) (* Sort methods according to label. *) let m = List.sort (fun (l, _, _, _) (l', _, _, _) -> compare l l') m in (* First print the main value. *) let vars = if t = `Tuple [] then ( Format.fprintf f "@,@[{@,"; vars) else ( let vars = print ~par:true vars t in Format.fprintf f "@,@[.{@,"; vars) in let vars = if m = [] then vars else ( let rec gen = function | (x, _) :: g -> x ^ "." ^ gen g | [] -> "" in let gen g = if !show_record_schemes then gen (List.sort compare g) else "" in let rec aux vars = function | [(l, optional, (g, t), Some json_name)] -> let optional = if optional then "?" else "" in Format.fprintf f "%s%s as %s%s : %s" (Lang_string.quote_utf8_string json_name) optional l optional (gen g); print ~par:true vars t | [(l, optional, (g, t), None)] -> let optional = if optional then "?" else "" in Format.fprintf f "%s%s : %s" l optional (gen g); print ~par:false vars t | (l, optional, (g, t), Some json_name) :: m -> let optional = if optional then "?" else "" in Format.fprintf f "%s%s as %s%s : %s" (Lang_string.quote_utf8_string json_name) optional l optional (gen g); let vars = print ~par:false vars t in Format.fprintf f ",@ "; aux vars m | (l, optional, (g, t), None) :: m -> let optional = if optional then "?" else "" in Format.fprintf f "%s%s : %s" l optional (gen g); let vars = print ~par:false vars t in Format.fprintf f ",@ "; aux vars m | [] -> assert false in aux vars m) in Format.fprintf f "@]@,}"; vars) else ( let vars = print ~par:true vars b in Format.fprintf f ".{%s = " l; let vars = print ~par:false vars a in Format.fprintf f "}"; vars) | `List (t, `Tuple) -> Format.fprintf f "@[<1>["; let vars = print ~par:false vars t in Format.fprintf f "]@]"; vars | `List (t, `Object) -> Format.fprintf f "@[<1>["; let vars = print ~par:false vars t in Format.fprintf f "] as json.object@]"; vars | `Getter t -> Format.fprintf f "{"; let vars = print ~par:false vars t in Format.fprintf f "}"; vars | (`EVar (_, c) | `UVar (_, c)) when Constraints.cardinal c = 1 && (Constraints.choose c).univ_descr <> None -> let constr = Constraints.choose c in Format.fprintf f "%s" (Option.get constr.univ_descr); vars | `EVar (name, c) | `UVar (name, c) -> Format.fprintf f "%s" name; if not (Constraints.is_empty c) then DS.add (name, c) vars else vars | `Arrow (p, t) -> if par then Format.fprintf f "@[(" else Format.fprintf f "@["; Format.fprintf f "@[<1>("; let _, vars = List.fold_left (fun (first, vars) (opt, lbl, kind) -> if not first then Format.fprintf f ",@ "; if opt then Format.fprintf f "?"; if lbl <> "" then Format.fprintf f "%s : " lbl; let vars = print ~par:true vars kind in (false, vars)) (true, vars) p in Format.fprintf f ")@] ->@ "; let vars = print ~par:false vars t in if par then Format.fprintf f ")@]" else Format.fprintf f "@]"; vars | `Ellipsis -> Format.fprintf f "_"; vars | `Range_Ellipsis -> Format.fprintf f "..."; vars | `Debug (a, b, c) -> Format.fprintf f "%s" a; let vars = print ~par:false vars b in Format.fprintf f "%s" c; vars and print_list ?(first = true) ?(acc = []) vars = function | [] -> vars | (_, x) :: l -> if not first then Format.fprintf f ","; let vars = print ~par:false vars x in print_list ~first:false ~acc:(x :: acc) vars l in Format.fprintf f "@["; begin match t with (* We're only printing a variable: ignore its [repr]esentation. *) | `EVar (_, c) when not (Constraints.is_empty c) -> Format.fprintf f "something that is %s" (String.concat " and " (List.map string_of_constr (Constraints.elements c))) | `UVar (_, c) when not (Constraints.is_empty c) -> Format.fprintf f "anything that is %s" (String.concat " and " (List.map string_of_constr (Constraints.elements c))) (* Print the full thing, then display constraints *) | _ -> let constraints = print ~par:false DS.empty t in let constraints = DS.elements constraints in if constraints <> [] then ( let constraints = List.map (fun (name, c) -> ( name, String.concat " and " (List.map string_of_constr (Constraints.elements c)) )) constraints in let constraints = List.stable_sort (fun (_, a) (_, b) -> compare a b) constraints in let group : ('a * 'b) list -> ('a list * 'b) list = function | [] -> [] | (i, c) :: l -> let rec group prev acc = function | [] -> [(List.rev acc, prev)] | (i, c) :: l -> if prev = c then group c (i :: acc) l else (List.rev acc, prev) :: group c [i] l in group c [i] l in let constraints = group constraints in let constraints = List.map (fun (ids, c) -> String.concat ", " ids ^ " is " ^ c) constraints in Format.fprintf f "@ @[<2>where@ "; Format.fprintf f "%s" (List.hd constraints); List.iter (fun s -> Format.fprintf f ",@ %s" s) (List.tl constraints); Format.fprintf f "@]") end; Format.fprintf f "@]" let to_string t = print Format.str_formatter t; Format.fprintf Format.str_formatter "@?"; Format.flush_str_formatter () let print_type f t = print f (make t) let print_scheme f (generalized, t) = if !debug then List.iter (fun v -> print f (make ~generalized (Type_base.make (Var { id = 0; contents = Free v }))); Format.fprintf f ".") generalized; print f (make ~generalized t) (** String representation of a type. *) let string_of_type ?generalized t = to_string (make ?generalized t) (* This is filled in there in order to avoid cyclic dependencies. *) let () = Type_base.to_string_fun := string_of_type (** String representation of a type scheme. *) let string_of_scheme (g, t) = string_of_type ~generalized:g t type explanation = bool * Type_base.t * Type_base.t * t * t exception Type_error of explanation let print_type_error ~formatter error_header ((flipped, ta, tb, a, b) : explanation) = error_header ta.pos; match b with | `Meth (R.{ name = l; scheme = [], `Ellipsis }, `Ellipsis) when not flipped -> Format.fprintf formatter "this value has no method `%s`@.@[<2> Its type is %s.@]@." l (string_of_type ta) | _ -> let inferred_pos a = let dpos = (deref a).pos in if a.pos = dpos then "" else ( match dpos with | None -> "" | Some p -> " (inferred at " ^ Pos.to_string ~prefix:"" p ^ ")") in let ta, tb, a, b = if flipped then (tb, ta, b, a) else (ta, tb, a, b) in Format.fprintf formatter "this value has type@.@[<2> %a@]%s@ " print a (inferred_pos ta); Format.fprintf formatter "but it should be a %stype of%s@.@[<2> %a@]%s@]@." (if flipped then "super" else "sub") (match tb.pos with | None -> "" | Some p -> Printf.sprintf " the type of the value at %s" (Pos.to_string ~prefix:"" p)) print b (inferred_pos tb) liquidsoap-2.4.2/src/lang/base/runtime.ml000066400000000000000000000432721513273233300203420ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** {1 Running} *) let () = Printexc.record_backtrace true let () = Lang_core.apply_fun := Evaluation.apply type stdlib = { full_term : Term.t; checked_term : Term.t; env : Typing.env } type append_stdlib = unit -> stdlib (** {1 Error reporting} *) let error = Console.colorize [`red; `bold] "Error" let warning = Console.colorize [`magenta; `bold] "Warning" let position pos = Console.colorize [`bold] (String.capitalize_ascii pos) let strict = ref false let deprecated = ref true let () = Array.iter (function | "--disable-deprecated" -> deprecated := false | "--enable-deprecated" -> deprecated := true | _ -> ()) Sys.argv let raw_errors = ref false let error_header ~formatter idx pos = let e = Option.value (Repr.excerpt_opt pos) ~default:"" in let pos = Pos.Option.to_string pos in Format.fprintf formatter "@[%s:\n%s\n%s %i: " (position pos) e error idx let warning_header ~formatter idx pos = let e = Option.value (Repr.excerpt_opt pos) ~default:"" in let pos = Pos.Option.to_string pos in Format.fprintf formatter "@[%s:\n%s\n%s %i: " (position pos) e warning idx (** Exception raised by report_error after an error has been displayed. Unknown errors are re-raised, so that their content is not totally lost. *) exception Error exception Warning of string let () = Printexc.register_printer (function | Error -> Some "Liquidsoap Error" | Warning s -> Some (Printf.sprintf "Warning: %s" s) | _ -> None) let rec throw ?(formatter = Format.std_formatter) ~lexbuf ~bt () = let print_error ~formatter idx error = flush_all (); let pos = match lexbuf with | Some lexbuf -> Some (Pos.of_lexing_pos (Sedlexing.lexing_bytes_positions lexbuf)) | None -> None in error_header ~formatter idx pos; Format.fprintf formatter "%s\n@]@." error in function | exn when !raw_errors -> Printexc.raise_with_backtrace exn bt | Term_preprocessor.Includer_error (exn, lexbuf, bt) -> throw ~formatter ~lexbuf:(Some lexbuf) ~bt () exn (* Warnings *) | Term.Ignored tm when Type.is_fun tm.Term.t -> flush_all (); warning_header ~formatter 1 tm.Term.t.Type.pos; let typ = Type.to_string tm.Term.t in Format.fprintf formatter "Trying to ignore a function,@ which is of type %s.@ Did you forget to \ apply it to arguments?@]@." typ; if !strict then Printexc.raise_with_backtrace (Warning (Printf.sprintf "Trying to ignore a function, which is of type %s. Did you \ forget to apply it to arguments?" typ)) bt | Term.Ignored tm when Type.is_source tm.Term.t -> flush_all (); warning_header ~formatter 2 tm.Term.t.Type.pos; Format.fprintf formatter "This source is unused, maybe it needs to@ be connected to an \ output.@]@."; if !strict then Printexc.raise_with_backtrace (Warning "This source is unused, maybe it needs to be connected to an \ output.") bt | Term.Ignored tm -> flush_all (); warning_header ~formatter 3 tm.Term.t.Type.pos; Format.fprintf formatter "This expression is returning a value that is ignored. Do you need to \ use its return value? If not, you can use the `ignore()` \ operator.@]@."; if !strict then Printexc.raise_with_backtrace (Warning "This expression should have type unit.") bt | Term.Unused_variable (s, pos) -> flush_all (); warning_header ~formatter 4 (Some pos); Format.fprintf formatter "Unused variable %s@]@." s; if !strict then Printexc.raise_with_backtrace (Warning (Printf.sprintf "Unused variable %s" s)) bt | Term.Deprecated (s, pos) -> flush_all (); warning_header ~formatter 5 (Some pos); Format.fprintf formatter "Deprecated: %s@]@." s; if !strict then Printexc.raise_with_backtrace (Warning (Printf.sprintf "Deprecated: %s" s)) bt | Typechecking.Top_level_override (s, pos) -> flush_all (); warning_header ~formatter 6 pos; Format.fprintf formatter "Top-level variable %s is overridden!@]@." s; if !strict then Printexc.raise_with_backtrace (Warning (Printf.sprintf "Top-level variable %s is overridden!" s)) bt (* Errors *) | Failure s when s = "lexing: empty token" -> print_error ~formatter 1 "Empty token"; Printexc.raise_with_backtrace Error bt | Parser.Error | Parsing.Parse_error -> print_error ~formatter 2 "Parse error"; Printexc.raise_with_backtrace Error bt | Term_base.Parse_error (pos, s) -> error_header ~formatter 3 (Some (Pos.of_lexing_pos pos)); Format.fprintf formatter "%s@]@." s; Printexc.raise_with_backtrace Error bt | Term.Unbound (pos, s) -> error_header ~formatter 4 pos; Format.fprintf formatter "Undefined variable %s@]@." s; Printexc.raise_with_backtrace Error bt | Repr.Type_error explain -> flush_all (); Repr.print_type_error ~formatter (error_header ~formatter 5) explain; Printexc.raise_with_backtrace Error bt | Typechecking.No_method (name, typ) -> error_header ~formatter 5 typ.Type.pos; Format.fprintf formatter "This value has type %s, it cannot have method %s.@]@." (Repr.string_of_type typ) name; Printexc.raise_with_backtrace Error bt | Term.No_label (f, lbl, first, x) -> let pos_f = Pos.Option.to_string f.Term.t.Type.pos in flush_all (); error_header ~formatter 6 x.Term.t.Type.pos; Format.fprintf formatter "Cannot apply that parameter because the function %s@ has %s@ %s!@]@." pos_f (if first then "no" else "no more") (if lbl = "" then "unlabeled argument" else Format.sprintf "argument labeled %S" lbl); Printexc.raise_with_backtrace Error bt | Term.Duplicate_label (pos, lbl) -> error_header ~formatter 6 pos; Format.fprintf formatter "Function has multiple arguments with the same label: %s@]@." lbl; Printexc.raise_with_backtrace Error bt | Error.Invalid_value (v, msg) -> error_header ~formatter 7 (Value.pos v); Format.fprintf formatter "Invalid value:@ %s@]@." msg; Printexc.raise_with_backtrace Error bt | Lang_error.Encoder_error (pos, s) -> error_header ~formatter 8 pos; Format.fprintf formatter "%s@]@." (String.capitalize_ascii s); Printexc.raise_with_backtrace Error bt | Failure s -> print_error ~formatter 9 (Printf.sprintf "Failure: %s\n%s" s (Printexc.raw_backtrace_to_string bt)); Printexc.raise_with_backtrace Error bt | Error.Clock_conflict (pos, a, b) -> (* TODO better printing of clock errors: we don't have position * information, use the source's ID *) error_header ~formatter 10 pos; Format.fprintf formatter "A source cannot belong to two clocks (%s,@ %s).@]@." a b; Printexc.raise_with_backtrace Error bt | Error.Clock_loop (pos, a, b) -> error_header ~formatter 11 pos; Format.fprintf formatter "Cannot unify two nested clocks@ (%s,@ %s).@ Do you need to set@ \ `settings.output.use_default_clock := false`?@]@." a b; raise Error | Term.Unsupported_encoder (pos, fmt) -> error_header ~formatter 12 pos; (if Sys.unix then Format.fprintf formatter "Unsupported encoder: %s.@ You must be missing an optional \ dependency.@]@." else Format.fprintf formatter "Unsupported encoder: %s.@ Please note that, on windows, %%mp3, \ %%vorbis and many other encoders are not available. Instead, you \ should use the %%ffmpeg encoder.@]@.") fmt; Printexc.raise_with_backtrace Error bt | Term.Internal_error (pos, e) -> (* Bad luck, error 13 should never have happened. *) error_header ~formatter 13 (try Some (Pos.List.to_pos pos) with _ -> None); let pos = Pos.List.to_string ~newlines:true pos in Format.fprintf formatter "Internal error: %s,@ stack:\n%s\n@]@." e pos; Printexc.raise_with_backtrace Error bt | Runtime_error.(Runtime_error { kind; msg; pos }) -> error_header ~formatter 14 (try Some (Pos.List.to_pos pos) with _ -> None); let pos = Pos.List.to_string ~newlines:true pos in Format.fprintf formatter "Uncaught runtime error:@ type: %s,@ message: %s,@\nstack: %s\n@]@." kind (Lang_string.quote_string msg) pos; Printexc.raise_with_backtrace Error bt | Sedlexing.MalFormed -> print_error ~formatter 15 "Malformed UTF8 content." | Term.Missing_arguments (pos, args) -> let args = List.map (fun (l, t) -> (if l = "" then "" else l ^ " : ") ^ Type.to_string t) args |> String.concat ", " in error_header ~formatter 15 pos; Format.fprintf formatter "Missing arguments in function application: %s.@]@." args; Printexc.raise_with_backtrace Error bt | Type.Exists (pos, typ) -> error_header ~formatter 16 pos; Format.fprintf formatter "Type %s already exists.@]@." typ; Printexc.raise_with_backtrace Error bt | End_of_file -> Printexc.raise_with_backtrace End_of_file bt | e -> error_header ~formatter (-1) None; Format.fprintf formatter "Exception raised: %s@.%s@]@." (Printexc.to_string e) (Printexc.raw_backtrace_to_string bt); Printexc.raise_with_backtrace Error bt (* This is not great but it works for now. The problem being that we are relying on exception raising and catching to transmit language error, translate them into human readable errors and optionally ignore them with a warning. But, in some cases, we still want to return afterward so the return value has to be something else than [unit] in those cases. Essentially, this means that [default] becomes [fun () -> raise Error] to keep typechecking consistent.. *) let report : 'a. ?default:(unit -> 'a) -> lexbuf:Sedlexing.lexbuf option -> (throw:(bt:Printexc.raw_backtrace -> exn -> unit) -> unit -> 'a) -> 'a = fun ?(default = fun () -> raise Error) ~lexbuf f -> let throw = throw ~lexbuf () in if !Term.conf_debug_errors then f ~throw () else ( try f ~throw () with exn -> let bt = Printexc.get_raw_backtrace () in throw ~bt exn; default ()) let type_term ?name ?stdlib ?term ?ty ?cache_dirtype ~cache ~trim ~lib parsed_term = let cached_term = if cache then Term_cache.retrieve ?name ?dirtype:cache_dirtype ~trim parsed_term else None in match cached_term with | Some term -> term | None -> if Lazy.force Term.debug then Printf.eprintf "Type checking...\n%!"; (* Type checking *) let time fn = match name with | None -> fn () | Some name -> Startup.time (Printf.sprintf "Typechecking %s" name) fn in let full_term, checked_term, env = match stdlib with | Some fn -> let { full_term; checked_term; env } = fn () in (full_term, checked_term, Some env) | None -> let term = match term with | None -> report ~lexbuf:None ~default:(fun () -> raise Error) (fun ~throw () -> Term_reducer.to_term ~throw parsed_term) | Some tm -> tm in (term, term, None) in let checked_term = match ty with | None -> checked_term | Some typ -> Term.make ~pos:(Pos.of_lexing_pos parsed_term.Parsed_term.pos) (`Cast { cast = checked_term; typ }) in time (fun () -> report ~lexbuf:None ~default:(fun () -> ()) (fun ~throw () -> Typechecking.check ?env ~check_top_level_override:(stdlib <> None) ~throw checked_term)); if Lazy.force Term.debug then Printf.eprintf "Checking for unused variables...\n%!"; (* Check for unused variables, relies on types *) report ~lexbuf:None ~default:(fun () -> ()) (fun ~throw () -> Term.check_unused ~throw ~lib full_term); let full_term = if trim then Term_trim.trim_term full_term else full_term in if cache then Term_cache.cache ?dirtype:cache_dirtype ~trim ~parsed_term full_term; full_term let eval_term ?name ~toplevel ast = let eval () = report ~lexbuf:None ~default:(fun () -> assert false) (fun ~throw:_ () -> if toplevel then Evaluation.eval_toplevel ast else Evaluation.eval ast) in if Lazy.force Term.debug then Printf.eprintf "Evaluating...\n%!"; match name with | None -> eval () | Some name -> Startup.time (Printf.sprintf "Evaluating %s%s" name (if toplevel then " at toplevel" else "")) eval (** {1 Parsing} *) let program = Term_reducer.program let interactive = MenhirLib.Convert.Simplified.traditional2revised Parser.interactive let mk_expr ?fname processor lexbuf = report ~lexbuf:(Some lexbuf) ~default:(fun () -> raise Error) (fun ~throw () -> let parsed_term = Term_reducer.mk_expr ?fname processor lexbuf in (parsed_term, Term_reducer.to_term ~throw parsed_term)) let parse s = let lexbuf = Sedlexing.Utf8.from_string s in mk_expr program lexbuf let interactive () = Format.printf "\n\ Welcome to the liquidsoap interactive loop.\n\n\ You may enter any sequence of expressions, terminated by \";;\".\n\ Each input will be fully processed: parsing, type-checking,\n\ evaluation (forces default types), output startup (forces default clock).\n\ @."; (match !Hooks.log_path with | None -> () | Some path -> Format.printf "Logs can be found in %s.\n@." (Lang_string.quote_string path)); let lexbuf = (* See ocaml-community/sedlex#45 *) let chunk_size = 512 in let buf = Bytes.create chunk_size in let cached = ref (-1) in let position = ref (-1) in let rec gen () = match (!position, !cached) with | _, 0 -> None | -1, _ -> position := 0; cached := input stdin buf 0 chunk_size; gen () | len, c when len = c -> position := -1; (* This means that the last read was a full chunk. Safe to try a new one right away. *) if len = chunk_size then gen () else None | len, _ -> position := len + 1; Some (Bytes.get buf len) in Sedlexing.Utf8.from_gen gen in let rec loop () = Format.printf "# %!"; if try report ~lexbuf:(Some lexbuf) ~default:(fun () -> ()) (fun ~throw () -> let _, expr = mk_expr interactive lexbuf in Typechecking.check ~throw ~check_top_level_override:true expr; Term.check_unused ~throw ~lib:true expr; ignore (Evaluation.eval_toplevel ~interactive:true expr)); true with | End_of_file -> Format.printf "Bye bye!@."; false | Error -> true | e -> let e = Console.colorize [`bold] (Printexc.to_string e) in Format.printf "Exception: %s!@." e; true then loop () in loop () let libs ?(error_on_no_stdlib = true) ?(deprecated = true) ~stdlib () = let libs = if not (Sys.file_exists stdlib) then if error_on_no_stdlib then failwith (Printf.sprintf "Could not find default %s library!" stdlib) else [] else [stdlib] in let dir = Filename.dirname stdlib in let file = Filename.concat (Filename.concat dir "extra") "deprecations.liq" in if deprecated && Sys.file_exists file then libs @ [file] else libs let load_libs ~stdlib () = List.iter (fun fname -> let filename = Lang_string.home_unrelate fname in let ic = open_in filename in Fun.protect ~finally:(fun () -> close_in ic) (fun () -> let lexbuf = Sedlexing.Utf8.from_channel ic in Sedlexing.set_filename lexbuf fname; let parsed_term = report ~lexbuf:(Some lexbuf) ~default:(fun () -> raise Error) (fun ~throw:_ () -> Term_reducer.mk_expr ~fname program lexbuf) in let term = type_term ~name:"stdlib" ~trim:true ~cache:true ~lib:true parsed_term in ignore (eval_term ~name:"stdlib" ~toplevel:true term))) (libs ~stdlib ()) liquidsoap-2.4.2/src/lang/base/runtime.mli000066400000000000000000000052631513273233300205110ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** {1 Main script evaluation} *) exception Error type stdlib = { full_term : Term.t; checked_term : Term.t; env : Typing.env } type append_stdlib = unit -> stdlib (** Typecheck a term and return it. Might return a cached value! *) val type_term : ?name:string -> ?stdlib:append_stdlib -> ?term:Term.t -> ?ty:Type.t -> ?cache_dirtype:Cache.dirtype -> cache:bool -> trim:bool -> lib:bool -> Parsed_term.t -> Term.t (** Evaluate a term. *) val eval_term : ?name:string -> toplevel:bool -> Term.t -> Value.t (** Raise errors for warnings. *) val strict : bool ref (** Register deprecated arguments and functions. *) val deprecated : bool ref (** Raise raw errors. *) val raw_errors : bool ref (** Return the list of external libraries. *) val libs : ?error_on_no_stdlib:bool -> ?deprecated:bool -> stdlib:string -> unit -> string list (** Load the external libraries. *) val load_libs : stdlib:string -> unit -> unit (* Wrapper for format language errors. Re-raises [Error] after printing language errors. *) val throw : ?formatter:Format.formatter -> lexbuf:Sedlexing.lexbuf option -> bt:Printexc.raw_backtrace -> unit -> exn -> unit val program : (unit -> Parser.token * Lexing.position * Lexing.position) -> Parsed_term.t (** Interactive loop: read from command line, eval, print and loop. *) val interactive : unit -> unit (** Parse a string. *) val parse : string -> Parsed_term.t * Term.t val error_header : formatter:Format.formatter -> int -> Pos.Option.t -> unit (** Report language errors. *) val report : ?default:(unit -> 'a) -> lexbuf:Sedlexing.lexbuf option -> (throw:(bt:Printexc.raw_backtrace -> exn -> unit) -> unit -> 'a) -> 'a liquidsoap-2.4.2/src/lang/base/runtime_error.ml000066400000000000000000000037071513273233300215520ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** An error at runtime. *) type runtime_error = { kind : string; msg : string; pos : Pos.t list } exception Runtime_error of runtime_error let () = Printexc.register_printer (function | Runtime_error { kind; msg; pos } -> Some (Printf.sprintf "Lang.Runtime_error { kind: %s, msg: %s, pos: [%s] }" (Lang_string.quote_string kind) (Lang_string.quote_string msg) (String.concat ", " (List.map (fun pos -> Pos.to_string pos) pos))) | _ -> None) let listeners = Atomic.make [] let on_error fn = Atomic.set listeners (fn :: Atomic.get listeners) let make ?(message = "") ~pos kind = { kind; msg = message; pos } let raise ?bt ?(message = "") ~pos kind = let err = { kind; msg = message; pos } in List.iter (fun fn -> fn err) (Atomic.get listeners); let e = Runtime_error err in let bt = match bt with None -> Printexc.get_callstack 0 | Some bt -> bt in Printexc.raise_with_backtrace e bt liquidsoap-2.4.2/src/lang/base/runtime_error.mli000066400000000000000000000025671513273233300217260ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** An error at runtime. *) type runtime_error = private { kind : string; msg : string; pos : Pos.t list } exception Runtime_error of runtime_error val on_error : (runtime_error -> unit) -> unit val make : ?message:string -> pos:Pos.t list -> string -> runtime_error val raise : ?bt:Printexc.raw_backtrace -> ?message:string -> pos:Pos.t list -> string -> 'a liquidsoap-2.4.2/src/lang/base/startup.ml000066400000000000000000000026751513273233300203630ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) let messages = Atomic.make [] (* Add a startup message. *) let message fmt = Printf.ksprintf (fun s -> Atomic.set messages (s :: Atomic.get messages)) fmt (* Time a startup function. *) let time name f = let t = Sys.time () in let ans = f () in message "%s: %.02fs" name (Sys.time () -. t); ans let messages () = Atomic.get messages |> List.rev (* Should we register external plugins? *) let register_external_plugins = ref true liquidsoap-2.4.2/src/lang/base/term.ml000066400000000000000000000000221513273233300176100ustar00rootroot00000000000000include Term_base liquidsoap-2.4.2/src/lang/base/term.mli000066400000000000000000000053731513273233300177770ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) include module type of Runtime_term exception Internal_error of (Pos.t list * string) exception Unsupported_encoder of (Pos.t option * string) val conf_debug : bool ref val conf_debug_errors : bool ref val debug : bool Lazy.t val profile : bool ref val ref_t : ?pos:Pos.t -> Type.t -> Type.t module Custom = Term_base.Custom type encoder_params = [ `Anonymous of string | `Encoder of encoder | `Labelled of string * t ] list and encoder = string * encoder_params val unit : ast val is_ground : t -> bool val string_of_pat : pattern -> string val to_string : t -> string val make : ?pos:Pos.t -> ?t:Type.t -> ?flags:Flags.flags -> ?methods:t Methods.t -> ast -> t val free_vars : ?bound:Vars.elt list -> t -> Vars.t val free_fun_vars : (t, Type.t) func -> Vars.t val can_ignore : Type.t -> bool val fresh : handler:Type.Fresh.mapper -> t -> t exception Unbound of Pos.Option.t * string exception Ignored of t exception No_label of t * string * bool * t exception Duplicate_label of Pos.Option.t * string exception Missing_arguments of Pos.Option.t * (string * Type.t) list exception Unused_variable of (string * Pos.t) exception Deprecated of (string * Pos.t) val check_unused : throw:(bt:Printexc.raw_backtrace -> exn -> unit) -> lib:bool -> t -> unit module type Custom = sig type content val t : Type.t val to_custom : content -> Custom.t val of_custom : Custom.t -> content val is_custom : Custom.t -> bool val to_term : content -> t val of_term : t -> content end module type CustomDef = sig type content val name : string val to_string : content -> string val to_json : pos:Pos.t list -> content -> Json.t val compare : content -> content -> int end module MkCustom (Def : CustomDef) : Custom with type content = Def.content liquidsoap-2.4.2/src/lang/base/term/000077500000000000000000000000001513273233300172645ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/base/term/parsed_term.ml000066400000000000000000000247511513273233300221340ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Term_hash include Runtime_term module Custom = Term_base.Custom type comment = [ `Before of string list | `After of string list ] type pos = Term_base.parsed_pos type term_annotation = [ `Deprecated of string ] type string_param = [ `Verbatim of string | `String of (pos[@hash.ignore]) * (char * string) ] [@@deriving hash] type track_annotation = string * string_param [@@deriving hash] type inc_type = [ `Lib | `Extra | `Default ] [@@deriving hash] type inc = { inc_type : inc_type; inc_name : string; inc_pos : pos; [@hash.ignore] } [@@deriving hash] type pattern = { pat_pos : pos; [@hash.ignore] pat_entry : pattern_entry } [@@deriving hash] and pattern_entry = [ `PVar of string list (** a field *) | `PTuple of pattern list (** a tuple *) | `PList of pattern list * ((pos[@hash.ignore]) * string) option * pattern list (** a list *) | `PMeth of pattern option * (string * meth_term_default) list (** a value with methods *) ] and meth_term_default = [ `Nullable | `Pattern of pattern | `None ] type _of = { only : string list; except : string list; source : string } [@@deriving hash] type _if = { if_condition : t; if_then : t; if_elsif : (t * t) list; if_else : t option; } and _while = { while_condition : t; while_loop : t } and _for = { for_variable : string; for_from : t; for_to : t; for_loop : t } and iterable_for = { iterable_for_variable : string; iterable_for_iterator : t; iterable_for_loop : t; } and _try = { try_body : t; try_variable : string; try_errors_list : t option; try_handler : t option; try_finally : t option; } and let_decoration = [ `None | `Recursive | `Replaces | `Eval | `Sqlite_query | `Sqlite_row | `Yaml_parse | `Xml_parse | `Json_parse of (string * t) list ] and _let = { decoration : let_decoration; pat : pattern; arglist : fun_arg list option; cast : type_annotation option; def : t; } and invoke = { invoked : t; optional : bool; meth : invoke_meth } and invoke_meth = [ `String of string | `App of string * app_arg list ] and app_arg = [ `Term of string * t | `Argsof of _of ] and parsed_func_argument = { label : string; as_variable : pattern option; default : t option; typ : type_annotation option; pos : pos; [@hash.ignore] annotations : term_annotation list; [@hash.ignore] } and fun_arg = [ `Term of parsed_func_argument | `Argsof of _of ] and list_el = [ `Term of t | `Ellipsis of t ] and if_def = { if_def_negative : bool; if_def_condition : string; if_def_then : t; if_def_else : t option; } and if_version = { if_version_op : [ `Eq | `Geq | `Leq | `Gt | `Lt ]; if_version_version : Lang_string.Version.t; if_version_then : t; if_version_else : t option; } and if_encoder = { if_encoder_negative : bool; if_encoder_condition : string; if_encoder_then : t; if_encoder_else : t option; } and time_el = { week : int option; hours : int option; minutes : int option; seconds : int option; } and meth_annotation = { optional_meth : bool; name : string; typ : type_annotation; json_name : string option; } and source_track_annotation = { track_name : string; track_type : string; track_params : track_annotation list; } and source_annotation = { extensible : bool; tracks : source_track_annotation list; } and argument = bool * string * type_annotation and type_annotation = [ `Named of string | `Nullable of type_annotation | `List of type_annotation | `Json_object of type_annotation | `Tuple of type_annotation list | `Arrow of argument list * type_annotation | `Record of meth_annotation list | `Method of type_annotation * meth_annotation list | `Invoke of type_annotation * string | `Source of string * source_annotation ] (* These terms are reduced at runtime *) and parsed_ast = [ `If of _if | `Inline_if of _if | `If_def of if_def | `If_version of if_version | `If_encoder of if_encoder | `While of _while | `For of _for | `Iterable_for of iterable_for | `List of list_el list | `Try of _try | `Regexp of string * char list | `Time_interval of time_el * time_el | `Time of time_el | `Def of _let * t | `Let of _let * t | `Binding of _let * t | `App of t * app_arg list | `Invoke of invoke | `Fun of fun_arg list * t | `RFun of string * fun_arg list * t | `Not of t | `Get of t | `Set of t * t | `Methods of t option * methods list | `Negative of t | `Append of t * t | `Assoc of t * t | `Infix of t * string * t | `BoolOp of string * t list | `Coalesce of t * t | `At of t * t | `Simple_fun of t | `String_interpolation of char * string_interpolation list | `Include of inc | `Int of string | `Bool of bool | `Float of string | `String of char * string | `Block of t | `Parenthesis of t | `Encoder of encoder | `Eof | (t, type_annotation) common_ast ] and t = { term : parsed_ast; pos : pos; [@hash.ignore] mutable comments : (pos * comment) list; [@hash.ignore] annotations : term_annotation list; [@hash.ignore] } [@@deriving hash] and methods = [ `Ellipsis of t | `Method of string * t ] and string_interpolation = [ `String of string | `Term of t ] and encoder_params = [ `Anonymous of string_param | `Encoder of encoder | `Labelled of string_param * t ] list and encoder = string * encoder_params let unit = `Tuple [] let make ?(comments = []) ?(annotations = []) ~pos term = { pos; term; comments; annotations } let rec iter_term fn ({ term } as tm) = if term <> `Eof then fn tm; match term with | `If p | `Inline_if p -> ( iter_term fn p.if_condition; iter_term fn p.if_then; List.iter (fun (t, t') -> iter_term fn t; iter_term fn t') p.if_elsif; match p.if_else with None -> () | Some t -> iter_term fn t) | `If_def { if_def_then; if_def_else } -> ( iter_term fn if_def_then; match if_def_else with None -> () | Some term -> iter_term fn term) | `If_version { if_version_then; if_version_else } -> ( iter_term fn if_version_then; match if_version_else with None -> () | Some term -> iter_term fn term) | `If_encoder { if_encoder_then; if_encoder_else } -> ( iter_term fn if_encoder_then; match if_encoder_else with None -> () | Some term -> iter_term fn term) | `While { while_condition; while_loop } -> iter_term fn while_condition; iter_term fn while_loop | `For { for_from; for_to; for_loop } -> iter_term fn for_from; iter_term fn for_to; iter_term fn for_loop | `Iterable_for { iterable_for_iterator; iterable_for_loop } -> iter_term fn iterable_for_iterator; iter_term fn iterable_for_loop | `List l -> List.iter (function `Term tm | `Ellipsis tm -> iter_term fn tm) l | `Try { try_body; try_errors_list; try_handler; try_finally } -> ( iter_term fn try_body; (match try_errors_list with None -> () | Some tm -> iter_term fn tm); (match try_handler with Some tm -> iter_term fn tm | None -> ()); match try_finally with Some tm -> iter_term fn tm | None -> ()) | `Regexp _ -> () | `Time_interval _ -> () | `Time _ -> () | `Def (_let, tm) | `Let (_let, tm) | `Binding (_let, tm) -> (match _let.arglist with | Some args -> iter_fun_args fn args | None -> ()); iter_term fn _let.def; iter_term fn tm | `Cast { cast } -> iter_term fn cast | `App (tm, args) -> iter_term fn tm; List.iter (function `Term (_, tm) -> iter_term fn tm | `Argsof _ -> ()) args | `Invoke { invoked; meth } -> ( iter_term fn invoked; match meth with | `String _ -> () | `App (_, args) -> List.iter (function `Argsof _ -> () | `Term (_, tm) -> iter_term fn tm) args) | `Fun (args, tm) | `RFun (_, args, tm) -> iter_fun_args fn args; iter_term fn tm | `Not tm -> iter_term fn tm | `Get tm -> iter_term fn tm | `Set (tm, tm') -> iter_term fn tm; iter_term fn tm' | `Negative tm -> iter_term fn tm | `Append (tm, tm') -> iter_term fn tm; iter_term fn tm' | `Assoc (tm, tm') -> iter_term fn tm; iter_term fn tm' | `Infix (tm, _, tm') -> iter_term fn tm; iter_term fn tm' | `BoolOp (_, l) -> List.iter (iter_term fn) l | `Coalesce (tm, tm') -> iter_term fn tm; iter_term fn tm' | `At (tm, tm') -> iter_term fn tm; iter_term fn tm' | `Simple_fun tm -> iter_term fn tm | `Include _ -> () | `Custom _ -> () | `Encoder _ -> () | `Tuple l -> List.iter (iter_term fn) l | `Null -> () | `Open (tm, tm') -> iter_term fn tm; iter_term fn tm' | `Parenthesis tm -> iter_term fn tm | `Block tm -> iter_term fn tm | `Methods (base, methods) -> (match base with None -> () | Some tm -> iter_term fn tm); List.iter (function `Method (_, tm) | `Ellipsis tm -> iter_term fn tm) methods | `Int _ -> () | `Float _ -> () | `String _ -> () | `Bool _ -> () | `Var _ -> () | `Eof -> () | `String_interpolation (_, l) -> List.iter (function `String _ -> () | `Term tm -> iter_term fn tm) l | `Seq (tm, tm') -> iter_term fn tm; iter_term fn tm' and iter_fun_args fn args = List.iter (function | `Term tm -> ( match tm.default with Some tm -> iter_term fn tm | None -> ()) | `Argsof _ -> ()) args liquidsoap-2.4.2/src/lang/base/term/runtime_term.ml000066400000000000000000000043321513273233300223320ustar00rootroot00000000000000open Term_hash (** Sets of variables. *) module Vars = Set.Make (String) module Methods = struct include Methods type nonrec 'a t = (string, 'a) t [@@deriving hash] end type custom [@@deriving hash] type custom_handler = { name : string; to_string : custom -> string; [@hash.ignore] to_json : pos:Pos.t list -> custom -> Json.t; [@hash.ignore] compare : custom -> custom -> int; [@hash.ignore] typ : Type.t; [@hash.ignore] } [@@deriving hash] type custom_term = { value : custom; handler : custom_handler } [@@deriving hash] type 'a term = { t : Type.t; term : 'a; flags : Flags.flags; methods : 'a term Methods.t; } let has_flag { flags } flag = Flags.has flags flag (* ~l1:x1 .. ?li:(xi=defi) .. *) type ('a, 'b) func_argument = { label : string; as_variable : string option; default : 'a option; typ : 'b; [@hash.ignore] pos : Pos.t option; [@hash.ignore] } [@@deriving hash] type ('a, 'b) func = { mutable free_vars : Vars.t option; name : string option; arguments : ('a, 'b) func_argument list; body : 'a; } type 'a app = 'a * (string * 'a) list type ('a, 'b) cast = { cast : 'a; typ : 'b } [@@deriving hash] type ('a, 'b) common_ast = [ `Custom of custom_term | `Tuple of 'a list | `Null | `Cast of ('a, 'b) cast | `Open of 'a * 'a | `Var of string | `Seq of 'a * 'a ] [@@deriving hash] type 'a invoke = { invoked : 'a; invoke_default : 'a option; meth : string } type 'a encoder_params = [ `Anonymous of string | `Encoder of 'a encoder | `Labelled of string * 'a ] list and 'a encoder = string * 'a encoder_params type pattern = [ `PVar of string list | `PTuple of string list ] type 'a let_t = { doc : Doc.Value.t option; replace : bool; pat : pattern; mutable gen : Type.var list; def : 'a; body : 'a; } type cached_env = { var_name : int; var_id : int; env : Typing.env } type 'a runtime_ast = [ `Int of int | `Cache_env of cached_env ref | `Float of float | `String of string | `Bool of bool | `Let of 'a let_t | `List of 'a list | `App of 'a * (string * 'a) list | `Invoke of 'a invoke | `Hide of 'a * string list | `Encoder of 'a encoder | `Fun of ('a, Type.t) func ] type t = ast term and ast = [ (t, Type.t) common_ast | t runtime_ast ] liquidsoap-2.4.2/src/lang/base/term/term_base.ml000066400000000000000000000436261513273233300215720ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Terms and values in the Liquidsoap language. *) include Runtime_term type encoder = t Runtime_term.encoder type encoder_params = t Runtime_term.encoder_params type parsed_pos = Lexing.position * Lexing.position (** An internal error. Those should not happen in theory... *) exception Internal_error of (Pos.t list * string) (** A parsing error. *) exception Parse_error of (parsed_pos * string) (** Unsupported encoder *) exception Unsupported_encoder of (Pos.t option * string) let () = Printexc.register_printer (function | Internal_error (pos, e) -> Some (Printf.sprintf "Lang_values.Internal_error %s: %s" (Pos.List.to_string pos) e) | Parse_error (pos, e) -> Some (Printf.sprintf "Term_base.Parse_error %s: %s" Pos.(to_string (of_lexing_pos pos)) e) | Unsupported_encoder (pos, e) -> Some (Printf.sprintf "Lang_values.Unsupported_encoder at %s: %s" (Pos.Option.to_string pos) e) | _ -> None) let conf_debug = ref false let conf_debug_errors = ref false (** Are we in debugging mode? *) let debug = Lazy.from_fun (fun () -> try ignore (Sys.getenv "LIQUIDSOAP_DEBUG_LANG"); true with Not_found -> !conf_debug) (* We want to keep this a reference and not a dtools and not something more complicated (e.g. dtools) in order not to impact performances. *) let profile = ref false let ref_t ?pos t = Type.make ?pos (* The type has to be invariant because we don't want the sup mechanism to be used here, see #2806. *) (Type.Constr { Type.constructor = "ref"; params = [(`Invariant, t)] }) (** {2 Terms} *) module Custom = Term_custom let unit = `Tuple [] (* Only used for printing very simple functions. *) let rec is_ground x = match x.term with | `List l | `Tuple l -> List.for_all is_ground l | `Null | `Int _ | `Float _ | `String _ | `Bool _ -> true | _ -> false let string_of_pat = function | `PVar l -> String.concat "." l | `PTuple l -> "(" ^ String.concat ", " l ^ ")" (** String representation of terms, (almost) assuming they are in normal form. *) let rec to_string (v : t) = let to_base_string (v : t) = match v.term with | `Cache_env _ -> "" | `Custom c -> Custom.to_string c | `Int i -> if has_flag v Flags.octal_int then Printf.sprintf "0o%o" i else if has_flag v Flags.hex_int then Printf.sprintf "0x%x" i else string_of_int i | `Float f -> Utils.string_of_float f | `Bool b -> string_of_bool b | `String s -> Lang_string.quote_string s | `Encoder e -> let rec aux (e, p) = let p = p |> List.map (function | `Anonymous s -> s | `Encoder e -> aux e | `Labelled (l, v) -> l ^ "=" ^ to_string v) |> String.concat ", " in "%" ^ e ^ "(" ^ p ^ ")" in aux e | `List l -> "[" ^ String.concat ", " (List.map to_string l) ^ "]" | `Tuple l -> "(" ^ String.concat ", " (List.map to_string l) ^ ")" | `Null -> "null" | `Hide (tm, l) -> "{" ^ String.concat ", " (List.map (Printf.sprintf "%s = _") l) ^ ", ..." ^ to_string tm ^ "}" | `Cast { cast; typ } -> "(" ^ to_string cast ^ " : " ^ Type.to_string typ ^ ")" | `Invoke { invoked = e; meth = l; invoke_default } -> ( match invoke_default with | None -> to_string e ^ "." ^ l | Some v -> "(" ^ to_string e ^ "." ^ l ^ " ?? " ^ to_string v ^ ")" ) | `Open (m, e) -> "open " ^ to_string m ^ " " ^ to_string e | `Fun { name = None; arguments = []; body = v } when is_ground v -> "{" ^ to_string v ^ "}" | `Fun _ -> "" | `Var s -> s | `App (hd, tl) -> let tl = List.map (fun (lbl, v) -> (if lbl = "" then "" else lbl ^ " = ") ^ to_string v) tl in to_string hd ^ "(" ^ String.concat "," tl ^ ")" (* | Let _ | Seq _ -> assert false *) | `Let { pat; def; body } -> Printf.sprintf "let %s = %s in %s" (string_of_pat pat) (to_string def) (to_string body) | `Seq (e, e') -> to_string e ^ "; " ^ to_string e' in let term = to_base_string v in if Methods.is_empty v.methods then term else ( let methods = Methods.bindings v.methods in (if v.term = `Tuple [] then "" else term ^ ".") ^ "{" ^ String.concat ", " (List.map (fun (l, meth_term) -> l ^ "=" ^ to_string meth_term) methods) ^ "}") (** Create a new value. *) let id = let counter = Atomic.make 0 in fun () -> Atomic.fetch_and_add counter 1 let make ?pos ?t ?(flags = Flags.empty) ?(methods = Methods.empty) e = let t = match t with Some t -> t | None -> Type.var ?pos () in { t; term = e; methods; flags } let rec free_vars_pat = function | `PVar [] -> assert false | `PVar [_] -> Vars.empty | `PVar (x :: _) -> Vars.singleton x | `PTuple l -> List.fold_left Vars.union Vars.empty (List.map free_vars_pat l) | `PList (l, spread, l') -> List.fold_left Vars.union Vars.empty (List.map free_vars_pat (l @ (match spread with None -> [] | Some v -> [`PVar [v]]) @ l')) | `PMeth (pat, l) -> List.fold_left Vars.union (match pat with None -> Vars.empty | Some pat -> free_vars_pat pat) (List.map free_vars_pat (List.fold_left (fun cur (lbl, pat) -> [`PVar [lbl]] @ (match pat with | `None | `Nullable -> [] | `Pattern pat -> [pat]) @ cur) [] l)) let bound_vars_pat = function | `PVar [] -> assert false | `PVar [x] -> Vars.singleton x | `PVar _ -> Vars.empty | `PTuple l -> Vars.of_list l let rec free_term_vars tm = let root_free_vars = function | `Cache_env _ | `Int _ | `Float _ | `String _ | `Bool _ | `Custom _ -> Vars.empty | `Var x -> Vars.singleton x | `Tuple l -> List.fold_left (fun v a -> Vars.union v (free_vars a)) Vars.empty l | `Null -> Vars.empty | `Encoder e -> let rec enc (_, p) = List.fold_left (fun v t -> match t with | `Anonymous _ -> v | `Labelled (_, t) -> Vars.union v (free_vars t) | `Encoder e -> Vars.union v (enc e)) Vars.empty p in enc e | `Cast { cast = e } -> free_vars e | `Seq (a, b) -> Vars.union (free_vars a) (free_vars b) | `Hide (tm, l) -> free_vars { tm with methods = Methods.filter (fun n _ -> not (List.mem n l)) tm.methods; } | `Invoke { invoked = e; invoke_default } -> Vars.union (free_vars e) (match invoke_default with | None -> Vars.empty | Some d -> free_vars d) | `Open (a, b) -> Vars.union (free_vars a) (free_vars b) | `List l -> List.fold_left (fun v t -> Vars.union v (free_vars t)) Vars.empty l | `App (hd, l) -> List.fold_left (fun v (_, t) -> Vars.union v (free_vars t)) (free_vars hd) l | `Fun p -> free_fun_vars p | `Let l -> Vars.union (match l.pat with | `PVar (x :: _ :: _) -> Vars.singleton x | _ -> Vars.empty) (Vars.union (free_vars l.def) (Vars.diff (free_vars l.body) (bound_vars_pat l.pat))) in Methods.fold (fun _ meth_term fv -> Vars.union fv (free_vars meth_term)) tm.methods (root_free_vars tm.term) and free_fun_vars = function | { free_vars = Some fv } -> fv | { arguments; body } as p -> let bound = List.map (fun { label; as_variable } -> Option.value ~default:label as_variable) arguments in let fv = List.fold_left (fun fv -> function | { default = Some d } -> Vars.union fv (free_vars d) | _ -> fv) Vars.empty arguments in let fv = Vars.union fv (free_vars ~bound body) in p.free_vars <- Some fv; fv and free_vars ?(bound = []) body : Vars.t = Vars.diff (free_term_vars body) (Vars.of_list bound) (** Values which can be ignored (and will thus not raise a warning if ignored). *) let can_ignore t = match (Type.demeth t).Type.descr with | Type.Tuple [] | Type.Var _ -> true | _ -> false (** {1 Basic checks and errors} *) (** Trying to use an unbound variable. *) exception Unbound of Pos.Option.t * string (** Silently discarding a meaningful value. *) exception Ignored of t (** [No_label (f,lbl,first,x)] indicates that the parameter [x] could not be passed to the function [f] because the latter has no label [lbl]. The [first] information tells whether [lbl=x] is the first parameter with label [lbl] in the considered application, which makes the message a bit more helpful. *) exception No_label of t * string * bool * t (** A function defines multiple arguments with the same label. *) exception Duplicate_label of Pos.Option.t * string (** Some mandatory arguments with given label and typed were not passed to the function during an application. *) exception Missing_arguments of Pos.Option.t * (string * Type.t) list (** Check that all let-bound variables are used. No check is performed for variable arguments. This cannot be done at parse-time (as for the computation of the free variables of functions) because we need types, as well as the ability to distinguish toplevel and inner let-in terms. *) exception Unused_variable of (string * Pos.t) exception Deprecated of (string * Pos.t) let check_unused ~throw ~lib tm = let rec check ?(toplevel = false) v tm = let v = Methods.fold (fun _ meth_term e -> check e meth_term) tm.methods v in match tm.term with | `Var s -> Vars.remove s v | `Cache_env _ | `Int _ | `Float _ | `String _ | `Bool _ -> v | `Custom _ -> v | `Tuple l -> List.fold_left (fun a -> check a) v l | `Null -> v | `Hide (tm, l) -> check v { tm with methods = Methods.filter (fun n _ -> not (List.mem n l)) tm.methods; } | `Cast { cast = e } -> check v e | `Invoke { invoked = e } -> check v e | `Open (a, b) -> check (check v a) b | `Seq (a, b) -> check ~toplevel (check v a) b | `List l -> List.fold_left (fun x y -> check x y) v l | `Encoder e -> let rec enc v (_, p) = List.fold_left (fun v t -> match t with | `Anonymous _ -> v | `Labelled (_, t) -> check v t | `Encoder e -> enc v e) v p in enc v e | `App (hd, l) -> let v = check v hd in List.fold_left (fun v (_, t) -> check v t) v l | `Fun { arguments; body } -> let v = List.fold_left (fun v -> function { default = Some d } -> check v d | _ -> v) v arguments in let bound = List.fold_left (fun v { label; as_variable } -> Vars.add (Option.value ~default:label as_variable) v) Vars.empty arguments in let masked = Vars.inter v bound in let v = Vars.union v bound in let v = check v body in Vars.iter (fun x -> if Vars.mem x v && x <> "_" then ( let bt = Printexc.get_callstack 0 in throw ~bt (Unused_variable (x, Option.get tm.t.Type.pos)))) bound; (* Restore masked variables. The masking variables have been used but it does not count for the ones they masked. Bound variables have been handled above. *) Vars.union masked (Vars.diff v bound) | `Let { pat; def; body; _ } -> let v = check v def in let bvpat = bound_vars_pat pat in let mask = Vars.inter v bvpat in let v = Vars.union v bvpat in let v = check ~toplevel v body in if (* Do not check for anything at toplevel in libraries *) not (toplevel && lib) then Vars.iter (fun s -> (* Do we have an unused definition? *) if Vars.mem s v then (* There are exceptions: unit and functions when at toplevel (sort of a lib situation...) *) if s <> "_" && not (can_ignore def.t || (toplevel && Type.is_fun def.t)) then ( let bt = Printexc.get_callstack 0 in throw ~bt (Unused_variable (s, Option.get tm.t.Type.pos)))) bvpat; Vars.union v mask in (* Unused free variables may remain *) ignore (check ~toplevel:true Vars.empty tm) (* Custom types. *) module type Custom = sig type content val t : Type.t val to_custom : content -> Custom.t val of_custom : Custom.t -> content val is_custom : Custom.t -> bool val to_term : content -> t val of_term : t -> content end module type CustomDef = sig type content val name : string val to_string : content -> string val to_json : pos:Pos.t list -> content -> Json.t val compare : content -> content -> int end module MkCustom (Def : CustomDef) = struct module T = Type.Custom.Make (struct type content = unit let name = Def.name let copy_with _ _ = () let occur_check _ _ = () let filter_vars _ l _ = l let subtype _ c c' = assert (c = c') let sup _ c c' = assert (c = c'); c let repr _ _ _ = `Constr (name, []) let to_string _ = name end) let descr = Type.Custom (T.handler ()) let t = Type.make descr let () = Type.register_type Def.name (fun () -> Type.make descr) include Custom.Make (struct include Def let t = t end) let of_term t = match t.term with `Custom c -> of_custom c | _ -> assert false let to_term c = { t = Type.make descr; term = `Custom (to_custom c); methods = Methods.empty; flags = Flags.empty; } end (** Create a new value. *) let make ?pos ?t ?flags ?methods e = let term = make ?pos ?t ?flags ?methods e in let t = match t with Some t -> t | None -> Type.var ?pos () in if Lazy.force debug then Printf.eprintf "%s (%s): assigned type var %s\n" (Pos.Option.to_string t.Type.pos) (try to_string term with _ -> "") (Repr.string_of_type t); term let rec fresh ~handler { t; term; methods; flags } = let term = match term with | `Cache_env _ | `Int _ | `String _ | `Float _ | `Bool _ | `Custom _ -> term | `Tuple l -> `Tuple (List.map (fresh ~handler) l) | `Null -> `Null | `Open (t, t') -> `Open (fresh ~handler t, fresh ~handler t') | `Var s -> `Var s | `Seq (t, t') -> `Seq (fresh ~handler t, fresh ~handler t') | `Let { doc; replace; pat; gen; def; body } -> `Let { doc; replace; pat; gen = List.map (Type.Fresh.make_var handler) gen; def = fresh ~handler def; body = fresh ~handler body; } | `List l -> `List (List.map (fresh ~handler) l) | `Cast { cast = t; typ } -> `Cast { cast = fresh ~handler t; typ = Type.Fresh.make handler typ } | `App (t, l) -> `App ( fresh ~handler t, List.map (fun (lbl, t) -> (lbl, fresh ~handler t)) l ) | `Hide (tm, l) -> `Hide (fresh ~handler tm, l) | `Invoke { invoked; invoke_default; meth } -> `Invoke { invoked = fresh ~handler invoked; invoke_default = Option.map (fresh ~handler) invoke_default; meth; } | `Encoder encoder -> let rec map_encoder (lbl, params) = ( lbl, List.map (function | `Anonymous s -> `Anonymous s | `Encoder enc -> `Encoder (map_encoder enc) | `Labelled (lbl, t) -> `Labelled (lbl, fresh ~handler t)) params ) in `Encoder (map_encoder encoder) | `Fun { free_vars; name; arguments; body } -> `Fun { free_vars; name; arguments = List.map (fun { label; as_variable; default; typ; pos } -> { label; as_variable; default = Option.map (fresh ~handler) default; typ = Type.Fresh.make handler typ; pos; }) arguments; body = fresh ~handler body; } in { t = Type.Fresh.make handler t; term; methods = Methods.map (fresh ~handler) methods; flags; } liquidsoap-2.4.2/src/lang/base/term/term_cache.ml000066400000000000000000000017221513273233300217120ustar00rootroot00000000000000open Term_hash type t = { env : (string * Value.t) list; trim : bool; parsed_term : Parsed_term.t; } [@@deriving hash] let cache_filename ?name ~trim parsed_term = let report fn = match name with | None -> fn () | Some name -> Startup.time (Printf.sprintf "%s hash computation" name) fn in let hash = report (fun () -> hash { env = Environment.default_environment (); trim; parsed_term }) in Printf.sprintf "%s.liq-cache" hash let retrieve ?name ?(dirtype = `User) ~trim parsed_term : Term.t option = if Cache.enabled () then ( let report fn = match name with | None -> fn () | Some name -> Startup.time (Printf.sprintf "%s cache retrieval" name) fn in report (fun () -> Cache.retrieve ?name ~dirtype (cache_filename ?name ~trim parsed_term))) else None let cache ?(dirtype = `User) ~trim ~parsed_term term = Cache.store ~dirtype (cache_filename ~trim parsed_term) term liquidsoap-2.4.2/src/lang/base/term/term_cache.mli000066400000000000000000000003311513273233300220560ustar00rootroot00000000000000val retrieve : ?name:string -> ?dirtype:Cache.dirtype -> trim:bool -> Parsed_term.t -> Term.t option val cache : ?dirtype:Cache.dirtype -> trim:bool -> parsed_term:Parsed_term.t -> Term.t -> unit liquidsoap-2.4.2/src/lang/base/term/term_custom.ml000066400000000000000000000047331513273233300221660ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Runtime_term type custom = Runtime_term.custom type t = Runtime_term.custom_term let to_string { value; handler } = handler.to_string value let to_json ~pos { value; handler } = handler.to_json ~pos value let compare { value = v; handler = h } { value = v'; handler = h' } = if h.name <> h.name then Stdlib.compare h.name h'.name else h.compare v v' let custom_terms = ref [] module type Specs = sig type content val name : string val t : Type.t val to_string : content -> string val to_json : pos:Pos.t list -> content -> Json.t val compare : content -> content -> int end module type Implementation = sig type content val to_custom : content -> t val of_custom : t -> content val is_custom : t -> bool end module Make (S : Specs) = struct type content = S.content let () = if List.mem S.name !custom_terms then failwith "custom term exist!"; custom_terms := S.name :: !custom_terms let to_custom : content -> custom = Obj.magic let to_content : custom -> content = Obj.magic let to_string v = S.to_string (to_content v) let to_json ~pos v = S.to_json ~pos (to_content v) let compare v v' = S.compare (to_content v) (to_content v') let handler = { Runtime_term.typ = S.t; name = S.name; to_string; to_json; compare } let to_custom v = { value = to_custom v; handler } let of_custom { value; handler = { name } } = assert (S.name = name); to_content value let is_custom { handler = { name } } = S.name = name end liquidsoap-2.4.2/src/lang/base/term/term_custom.mli000066400000000000000000000031271513273233300223330ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type custom = Runtime_term.custom type t = Runtime_term.custom_term val to_string : t -> string val to_json : pos:Pos.t list -> t -> Json.t val compare : t -> t -> int module type Specs = sig type content val name : string val t : Type.t val to_string : content -> string val to_json : pos:Pos.t list -> content -> Json.t val compare : content -> content -> int end module type Implementation = sig type content val to_custom : content -> t val of_custom : t -> content val is_custom : t -> bool end module Make (S : Specs) : Implementation with type content = S.content liquidsoap-2.4.2/src/lang/base/term/term_hash.ml000066400000000000000000000013311513273233300215660ustar00rootroot00000000000000module Specs = struct let description = "md5" type state = string let fold_int s i = Digest.string (s ^ string_of_int i) let fold_int64 s i = Digest.string (s ^ Int64.to_string i) let fold_float s f = Digest.string (s ^ string_of_float f) let fold_string s v = Digest.string (s ^ v) type seed = string let alloc () = "" let reset ?(seed = "") _ = seed type hash_value = string let get_hash_value = Digest.to_hex module For_tests = struct let compare_state = Stdlib.compare let state_to_string s = s end end module Ppx_hash_lib = struct module Std = struct module Hash = Ppx_hash_lib.Std.Hash.F (Specs) end end include Ppx_hash_lib.Std.Hash include Ppx_hash_lib.Std.Hash.Builtin liquidsoap-2.4.2/src/lang/base/term/term_hash.mli000066400000000000000000000005301513273233300217370ustar00rootroot00000000000000type state module Ppx_hash_lib : sig module Std : sig module Hash : Ppx_hash_lib.Std.Hash.Full with type state = state and type seed = string and type hash_value = string end end include module type of Ppx_hash_lib.Std.Hash with type state := state include module type of Ppx_hash_lib.Std.Hash.Builtin liquidsoap-2.4.2/src/lang/base/term/term_preprocessor.ml000066400000000000000000000302411513273233300233730ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Parsed_term type processor = ( Parser.token * Lexing.position * Lexing.position, Parsed_term.t ) MenhirLib.Convert.revised exception Includer_error of (exn * Sedlexing.lexbuf * Printexc.raw_backtrace) let program = MenhirLib.Convert.Simplified.traditional2revised Parser.program let let_script_path ~filename tm = Parser_helper.mk ~pos:tm.Parsed_term.pos (`Let ( { Parsed_term.decoration = `None; pat = { pat_pos = tm.pos; pat_entry = `PVar ["liquidsoap"; "script"; "path"]; }; arglist = None; cast = None; def = Parser_helper.mk ~pos:tm.pos filename; }, tm )) let mk_expr ?fname processor lexbuf = let tokenizer = Preprocessor.mk_tokenizer ?fname lexbuf in Parser_helper.clear_comments (); let parsed_term = processor tokenizer in Parser_helper.attach_comments parsed_term; match fname with (* This happens with the interactive top-level. *) | None when processor != program -> parsed_term | None -> let_script_path ~filename:`Null parsed_term | Some fname -> let_script_path ~filename:(`String ('"', Lang_string.escape_utf8_string fname)) parsed_term exception No_extra let rec concat_term (t : t) (t' : t) = match t with | { term = `Let (def, body) } -> { t with term = `Let (def, concat_term body t') } | { term = `Def (def, body) } -> { t with term = `Def (def, concat_term body t') } | { term = `Binding (def, body) } -> { t with term = `Binding (def, concat_term body t') } | { term = `Seq (t1, t2) } as tm -> { tm with term = `Seq (t1, concat_term t2 t') } | { term = `Eof } | { term = `Tuple [] } -> t' | _ -> Parsed_term.make ~pos:t.Parsed_term.pos (`Seq (t, t')) let includer_reducer ~pos = function | `Include { inc_type; inc_name; inc_pos } -> ( try let fname = match inc_type with | `Lib -> Filename.concat (!Hooks.liq_libs_dir ()) inc_name | v -> ( try let current_dir = Filename.dirname (fst inc_pos).Lexing.pos_fname in Utils.check_readable ~current_dir ~pos:[Pos.of_lexing_pos inc_pos] inc_name with _ when v = `Extra -> raise No_extra) in let fname = match fname with "-" -> fname | _ -> FilePath.reduce fname in let ic = if fname = "-" then stdin else open_in fname in Fun.protect ~finally:(fun () -> if fname <> "-" then close_in ic) (fun () -> let lexbuf = Sedlexing.Utf8.from_channel ic in if fname <> "-" then Sedlexing.set_filename lexbuf fname; try mk_expr ~fname program lexbuf with (Parser.Error | Parsing.Parse_error) as exn -> let bt = Printexc.get_raw_backtrace () in raise (Includer_error (exn, lexbuf, bt))) with No_extra -> Parsed_term.make ~pos (`Tuple [])) let rec expand_encoder (lbl, params) = ( lbl, List.map (function | `Encoder enc -> `Encoder (expand_encoder enc) | `Labelled (lbl, t) -> `Labelled (lbl, expand_term t) | `Anonymous _ as v -> v) params ) and expand_term tm = let expand_decoration = function | `None -> `None | `Recursive -> `Recursive | `Replaces -> `Replaces | `Eval -> `Eval | `Sqlite_query -> `Sqlite_query | `Sqlite_row -> `Sqlite_row | `Yaml_parse -> `Yaml_parse | `Xml_parse -> `Xml_parse | `Json_parse l -> `Json_parse (List.map (fun (lbl, t) -> (lbl, expand_term t)) l) in let expand_func_arg ({ default } as arg) = { arg with default = Option.map expand_term default } in let expand_arglist = List.map (function | `Term arg -> `Term (expand_func_arg arg) | `Argsof _ as el -> el) in let expand_app_arg = List.map (function | `Argsof _ as el -> el | `Term (lbl, t) -> `Term (lbl, expand_term t)) in let expand_fun_args = List.map (function | `Term arg -> `Term (expand_func_arg arg) | `Argsof _ as v -> v) in let comments = ref tm.Parsed_term.comments in let term = match tm.Parsed_term.term with | `Include _ as ast -> let { term; comments = expanded_comments } = expand_term (includer_reducer ~pos:tm.Parsed_term.pos ast) in comments := expanded_comments; term | `Seq ({ pos; term = `Include _ as ast }, t') -> let t = expand_term (includer_reducer ~pos ast) in let t' = expand_term t' in let { term; comments = expanded_comments } = concat_term t t' in comments := expanded_comments; term | `Seq (t, t') -> `Seq (expand_term t, expand_term t') | `If_def ({ if_def_then; if_def_else } as if_def) -> `If_def { if_def with if_def_then = expand_term if_def_then; if_def_else = Option.map expand_term if_def_else; } | `If_version ({ if_version_then; if_version_else } as if_version) -> `If_version { if_version with if_version_then = expand_term if_version_then; if_version_else = Option.map expand_term if_version_else; } | `If_encoder ({ if_encoder_then; if_encoder_else } as if_encoder) -> `If_encoder { if_encoder with if_encoder_then = expand_term if_encoder_then; if_encoder_else = Option.map expand_term if_encoder_else; } | `If { if_condition; if_then; if_elsif; if_else } -> `If { if_condition = expand_term if_condition; if_then = expand_term if_then; if_elsif = List.map (fun (t, t') -> (expand_term t, expand_term t')) if_elsif; if_else = Option.map expand_term if_else; } | `Inline_if { if_condition; if_then; if_elsif; if_else } -> `If { if_condition = expand_term if_condition; if_then = expand_term if_then; if_elsif = List.map (fun (t, t') -> (expand_term t, expand_term t')) if_elsif; if_else = Option.map expand_term if_else; } | `While { while_condition; while_loop } -> `While { while_condition = expand_term while_condition; while_loop = expand_term while_loop; } | `For ({ for_from; for_to; for_loop } as _for) -> `For { _for with for_from = expand_term for_from; for_to = expand_term for_to; for_loop = expand_term for_loop; } | `Iterable_for ({ iterable_for_iterator; iterable_for_loop } as _for) -> `Iterable_for { _for with iterable_for_iterator = expand_term iterable_for_iterator; iterable_for_loop = expand_term iterable_for_loop; } | `List l -> `List (List.map (function | `Term t -> `Term (expand_term t) | `Ellipsis t -> `Ellipsis (expand_term t)) l) | `Try ({ try_body; try_errors_list; try_handler; try_finally } as _try) -> `Try { _try with try_body = expand_term try_body; try_errors_list = Option.map expand_term try_errors_list; try_handler = Option.map expand_term try_handler; try_finally = Option.map expand_term try_finally; } | `Regexp _ as ast -> ast | `Time_interval _ as ast -> ast | `Time _ as ast -> ast | `Def (({ decoration; arglist; def } as _let), body) -> `Def ( { _let with decoration = expand_decoration decoration; def = expand_term def; arglist = Option.map expand_arglist arglist; }, expand_term body ) | `Let (({ decoration; arglist; def } as _let), body) -> `Let ( { _let with decoration = expand_decoration decoration; def = expand_term def; arglist = Option.map expand_arglist arglist; }, expand_term body ) | `Binding (({ decoration; arglist; def } as _let), body) -> `Binding ( { _let with decoration = expand_decoration decoration; def = expand_term def; arglist = Option.map expand_arglist arglist; }, expand_term body ) | `Cast { cast = t; typ } -> `Cast { cast = expand_term t; typ } | `App (t, app_arg) -> `App (expand_term t, expand_app_arg app_arg) | `Invoke ({ invoked; meth } as invoke) -> `Invoke { invoke with invoked = expand_term invoked; meth = (match meth with | `String _ as s -> s | `App (n, app_arg) -> `App (n, expand_app_arg app_arg)); } | `Fun (fun_args, t) -> `Fun (expand_fun_args fun_args, expand_term t) | `RFun (name, fun_args, t) -> `RFun (name, expand_fun_args fun_args, expand_term t) | `Not t -> `Not (expand_term t) | `Get t -> `Get (expand_term t) | `Set (t, t') -> `Set (expand_term t, expand_term t') | `Methods (t, methods) -> `Methods ( Option.map expand_term t, List.map (function | `Ellipsis t -> `Ellipsis (expand_term t) | `Method (name, t) -> `Method (name, expand_term t)) methods ) | `Negative t -> `Negative (expand_term t) | `Append (t, t') -> `Append (expand_term t, expand_term t') | `Assoc (t, t') -> `Assoc (expand_term t, expand_term t') | `Infix (t, op, t') -> `Infix (expand_term t, op, expand_term t') | `BoolOp (b, l) -> `BoolOp (b, List.map expand_term l) | `Coalesce (t, t') -> `Coalesce (expand_term t, expand_term t') | `At (t, t') -> `At (expand_term t, expand_term t') | `Simple_fun t -> `Simple_fun (expand_term t) | `String_interpolation (c, l) -> `String_interpolation ( c, List.map (function | `String _ as s -> s | `Term t -> `Term (expand_term t)) l ) | `Int _ as ast -> ast | `Float _ as ast -> ast | `String _ as ast -> ast | `Bool _ as ast -> ast | `Eof -> `Eof | `Block t -> `Block (expand_term t) | `Parenthesis t -> `Parenthesis (expand_term t) | `Encoder enc -> `Encoder (expand_encoder enc) | `Custom _ as ast -> ast | `Open (t, t') -> `Open (expand_term t, expand_term t') | `Tuple l -> `Tuple (List.map expand_term l) | `Var _ as ast -> ast | `Null -> `Null in { tm with Parsed_term.term; comments = !comments } liquidsoap-2.4.2/src/lang/base/term/term_reducer.ml000066400000000000000000001427621513273233300223120ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type processor = Term_preprocessor.processor open Parsed_term include Runtime_term let report_annotations ~throw ~pos annotations = List.iter (function | `Deprecated s -> let bt = Printexc.get_callstack 0 in throw ~bt (Term.Deprecated (s, Pos.of_lexing_pos pos))) annotations let parse_error ~pos msg = raise (Term_base.Parse_error (pos, msg)) let render_string ~pos ~sep s = Lexer.render_string ~pos ~sep s let mk ?pos = Term.make ?pos:(Option.map Pos.of_lexing_pos pos) let mk_ty ?pos = Type.make ?pos:(Option.map Pos.of_lexing_pos pos) let mk_var ?pos = Type.var ?pos:(Option.map Pos.of_lexing_pos pos) let mk_parsed = Parsed_term.make let mk_fun ~pos arguments body = mk ~pos (`Fun Term.{ free_vars = None; name = None; arguments; body }) let mk_source_ty ?pos name args = let fn = !Hooks.mk_source_ty in fn ?pos name args let mk_clock_ty ?pos () = let fn = !Hooks.mk_clock_ty in fn ?pos () let mk_named_ty ?pos = function | "_" -> Type.var ?pos:(Option.map Pos.of_lexing_pos pos) () | "unit" -> Type.make ?pos:(Option.map Pos.of_lexing_pos pos) Type.unit | "never" -> Type.make ?pos:(Option.map Pos.of_lexing_pos pos) Type.Never | "bool" -> Type.make ?pos:(Option.map Pos.of_lexing_pos pos) Type.Bool | "int" -> Type.make ?pos:(Option.map Pos.of_lexing_pos pos) Type.Int | "float" -> Type.make ?pos:(Option.map Pos.of_lexing_pos pos) Type.Float | "string" -> Type.make ?pos:(Option.map Pos.of_lexing_pos pos) Type.String | "ref" -> Type.reference (Type.var ()) | "clock" -> mk_clock_ty ?pos () | "source" -> mk_source_ty ?pos "source" { extensible = true; tracks = [] } | "source_methods" -> !Hooks.source_methods_t () | name -> ( match Type.find_opt_typ name with | Some c -> c () | None -> let pos = Option.value ~default:(Lexing.dummy_pos, Lexing.dummy_pos) pos in raise (Term_base.Parse_error (pos, "Unknown type constructor: " ^ name ^ "."))) let typecheck = ref (fun ?env:_ _ -> assert false) let rec mk_parsed_ty ?pos ~env ~to_term = function | `Named s -> mk_named_ty ?pos s | `Nullable t -> Type.( make ?pos:(Option.map Pos.of_lexing_pos pos) (Nullable (mk_parsed_ty ?pos ~env ~to_term t))) | `List t -> Type.( make ?pos:(Option.map Pos.of_lexing_pos pos) (List { t = mk_parsed_ty ?pos ~env ~to_term t; json_repr = `Tuple })) | `Json_object t -> Type.( make (List { t = mk_parsed_ty ?pos ~env ~to_term (`Tuple [`Named "string"; t]); json_repr = `Object; })) | `Tuple l -> Type.( make ?pos:(Option.map Pos.of_lexing_pos pos) (Tuple (List.map (mk_parsed_ty ~env ~to_term ?pos) l))) | `Arrow (args, t) -> Type.( make (Arrow ( List.map (fun (optional, name, t) -> (optional, name, mk_parsed_ty ~env ~to_term ?pos t)) args, mk_parsed_ty ?pos ~env ~to_term t ))) | `Record l -> List.fold_left (mk_meth_ty ?pos ~env ~to_term) Type.(make (Tuple [])) l | `Method (t, l) -> List.fold_left (mk_meth_ty ?pos ~env ~to_term) (mk_parsed_ty ?pos ~env ~to_term t) l | `Invoke (t, s) -> snd (Type.invoke (mk_parsed_ty ?pos ~env ~to_term t) s) | `Source (s, p) -> mk_source_ty ?pos s p and mk_meth_ty ?pos ~env ~to_term base { Parsed_term.name; optional_meth = optional; typ; json_name } = Type.( make (Meth ( { meth = name; optional; scheme = ([], mk_parsed_ty ?pos ~env ~to_term typ); doc = { meth_descr = ""; category = `Method }; json_name; }, base ))) let program = Term_preprocessor.program let mk_expr ?fname processor lexbuf = let parsed_term = Term_preprocessor.mk_expr ?fname processor lexbuf in Term_preprocessor.expand_term parsed_term let pp_if_reducer ~env ~pos = function | `If_def { if_def_negative; if_def_condition; if_def_then; if_def_else } -> ( let if_def_else = Option.value ~default:(mk_parsed ~pos (`Tuple [])) if_def_else in match ( List.mem_assoc if_def_condition env || Environment.has_builtin if_def_condition, if_def_negative ) with | true, false | false, true -> if_def_then | _ -> if_def_else) | `If_version { if_version_op; if_version_version; if_version_then; if_version_else } -> ( let if_version_else = Option.value ~default:(mk_parsed ~pos (`Tuple [])) if_version_else in let current_version = Lang_string.Version.of_string Build_config.version in match ( if_version_op, Lang_string.Version.compare current_version if_version_version ) with | `Eq, 0 -> if_version_then | `Geq, v when v >= 0 -> if_version_then | `Leq, v when v <= 0 -> if_version_then | `Gt, v when v > 0 -> if_version_then | `Lt, v when v < 0 -> if_version_then | _ -> if_version_else) | `If_encoder { if_encoder_negative; if_encoder_condition; if_encoder_then; if_encoder_else; } -> ( let if_encoder_else = Option.value ~default:(mk_parsed ~pos (`Tuple [])) if_encoder_else in try let encoder = !Hooks.make_encoder ~pos:None (if_encoder_condition, []) in match (!Hooks.has_encoder encoder, if_encoder_negative) with | true, false | false, true -> if_encoder_then | _ -> if_encoder_else with _ -> if_encoder_else) let pat_var_name = let idx = ref 1 in fun () -> incr idx; Printf.sprintf "_%d_pat" !idx let rec pattern_reducer (pat : Parsed_term.pattern) = let mk = mk ~pos:pat.pat_pos in match pat.pat_entry with | `PVar _ as pat -> fun ?doc ?(replace = false) ~body def -> `Let { doc; replace; pat; gen = []; def; body } | `PTuple l -> fun ?doc ?(replace = false) ~body def -> let vars, body = List.fold_left (fun (vars, body) pat -> match pat.pat_entry with | `PVar [var] -> (var :: vars, body) (* let (, pattern, ...) = .. in .. becomes: let (, _1_pat, ...) = .. in let pattern = _1_pat in .. *) | _ -> let var = pat_var_name () in let mk_term = pattern_reducer pat in let body = mk (mk_term ~body (mk (`Var var))) in (var :: vars, body)) ([], body) l in `Let { doc; replace; pat = `PTuple (List.rev vars); gen = []; def; body } (* let [x, y, ...spread, z, t] = ... in ... becomes: let _1_pat = ... in let _2_pat = list.length(l) in if _2_pat < 4 then error.raise(error.register("not_found", ...)) end let x = list.nth(0, _1_pat) in let y = list.nth(1, _1_pat) in let spread = list.slice(offset=2, length=_2_pat-4) in let z = list.nth(_2_pat-1, l) in let t = list.nth(_2_pat-2, l) in ... *) | `PList (prefix, spread, suffix) -> fun ?doc ?replace ~body def -> let list_var_name = pat_var_name () in let list_var = mk (`Var list_var_name) in let list = mk (`Var "list") in let nth_op = mk (`Invoke { invoked = list; meth = "nth"; invoke_default = None }) in let len_op = mk (`Invoke { invoked = list; meth = "length"; invoke_default = None }) in let len_app = mk (`App (len_op, [("", list_var)])) in let len_var_name = pat_var_name () in let len_var = mk (`Var len_var_name) in let minus = mk (`Var "-") in let len_minus pos = mk (`App (minus, [("", len_var); ("", mk (`Int pos))])) in let body, suffix_len = match suffix with | [] -> (body, 0) | suffix -> let _, body = List.fold_left (fun (idx, body) pat -> let mk_term = pattern_reducer pat in let body = mk (mk_term ~body (mk (`App ( nth_op, [("", list_var); ("", len_minus idx)] )))) in (idx + 1, body)) (1, body) (List.rev suffix) in (body, List.length suffix) in let body, prefix_len = match prefix with | [] -> (body, 0) | prefix -> let _, body = List.fold_left (fun (idx, body) pat -> let mk_term = pattern_reducer pat in let body = mk (mk_term ~body (Term.make (`App ( nth_op, [("", list_var); ("", mk (`Int idx))] )))) in (idx + 1, body)) (0, body) prefix in (body, List.length prefix) in let body = match spread with | None -> body | Some (pos, var) -> let op = mk (`Invoke { invoked = list; meth = "slice"; invoke_default = None; }) in let def = mk (`App ( op, [ ("offset", mk (`Int prefix_len)); ("length", len_minus (prefix_len + suffix_len)); ("", list_var); ] )) in let mk_term = pattern_reducer { pat_pos = pos; pat_entry = `PVar [var] } in mk (mk_term ~body def) in let body = let if_op = mk (`Var "if") in let lt_op = mk (`Var "<") in let condition = mk (`App ( lt_op, [("", len_var); ("", mk (`Int (prefix_len + suffix_len)))] )) in let error = mk (`Var "error") in let raise = mk (`Invoke { invoked = error; meth = "raise"; invoke_default = None }) in let register = mk (`Invoke { invoked = error; meth = "register"; invoke_default = None }) in let not_found = mk (`App (register, [("", mk (`String "not_found"))])) in let _then = mk_fun ~pos:pat.pat_pos [] (mk (`App ( raise, [ ("", not_found); ( "", mk (`String "List value does not have enough elements to \ fit the extraction pattern!") ); ] ))) in let check_len = mk (`App ( if_op, [ ("", condition); ("then", _then); ("else", mk_fun ~pos:pat.pat_pos [] (mk (`Tuple []))); ] )) in mk (`Seq (check_len, body)) in let mk_term = pattern_reducer { pat with pat_entry = `PVar [len_var_name] } in let body = mk (mk_term ~body len_app) in let mk_term = pattern_reducer { pat with pat_entry = `PVar [list_var_name] } in mk_term ?doc ?replace ~body def (* let .{m = ; p?; q } = .. in .. becomes: let _1_pat = .. in let _2_pat = _.m in let = _2_pat in let p = _1_pat?.p in let p = _1_pat.q in let = _1_pat in ... *) | `PMeth (base, meths) -> fun ?doc ?replace ~body def -> let base_var_name = pat_var_name () in let base_var = mk (`Var base_var_name) in let body = List.fold_left (fun body (name, default) -> let invoke invoke_default = mk (`Invoke { invoked = base_var; meth = name; invoke_default }) in match default with | `None -> let mk_term = pattern_reducer { pat with pat_entry = `PVar [name] } in mk (mk_term ~body (invoke None)) | `Nullable -> let mk_term = pattern_reducer { pat with pat_entry = `PVar [name] } in mk (mk_term ~body (invoke (Some (mk `Null)))) | `Pattern pat -> let mk_term = pattern_reducer pat in mk (mk_term ~body (invoke None))) body (List.rev meths) in let body = match base with | None -> body | Some pat -> let mk_term = pattern_reducer pat in let body_var = mk (`Var base_var_name) in mk (mk_term ~body (mk (`Hide (body_var, List.map (fun (name, _) -> name) meths)))) in let mk_term = pattern_reducer { pat with pat_entry = `PVar [base_var_name] } in mk_term ?doc ?replace ~body def let pattern_reducer ?doc ?replace ~body ~pat def = pattern_reducer ~body pat ?doc ?replace def (** Time intervals *) let time_units = [| 7 * 24 * 60 * 60; 24 * 60 * 60; 60 * 60; 60; 1 |] (** Given a date specified as a list of four values (whms), return a date in seconds from the beginning of the week. *) let date ~pos = let to_int = function None -> 0 | Some i -> i in let rec aux = function | None :: tl -> aux tl | [] -> parse_error ~pos "Invalid time." | l -> let a = Array.of_list l in let n = Array.length a in let tu = time_units and tn = Array.length time_units in Array.fold_left ( + ) 0 (Array.mapi (fun i s -> let s = if n = 4 && i = 0 then to_int s mod 7 else to_int s in tu.(tn - 1 + i - n + 1) * s) a) in aux (** Give the index of the first non-None value in the list. *) let last_index l = let rec last_index n = function | x :: tl -> if x = None then last_index (n + 1) tl else n | [] -> n in last_index 0 l (** Give the precision of a date-as-list. For example, the precision of Xs is 1, XmYs is 60, XhYmZs 3600, etc. *) let precision d = time_units.(last_index d) (** Give the duration of a data-as-list. For example, the duration of Xs is 1, Xm 60, XhYm 60, etc. *) let duration d = time_units.(Array.length time_units - 1 - last_index (List.rev d)) let between ~pos d1 d2 = let d1 = [d1.week; d1.hours; d1.minutes; d1.seconds] in let d2 = [d2.week; d2.hours; d2.minutes; d2.seconds] in let p1 = precision d1 in let p2 = precision d2 in let t1 = date ~pos d1 in let t2 = date ~pos d2 in if p1 <> p2 then parse_error ~pos "Invalid time interval: precisions differ."; (t1, t2, p1) let during ~pos d = let d = [d.week; d.hours; d.minutes; d.seconds] in let t, d, p = (date ~pos d, duration d, precision d) in (t, t + d, p) let mk_time_pred ~pos (a, b, c) = let args = List.map (fun x -> ("", mk ~pos (`Int x))) [a; b; c] in `App (mk ~pos (`Var "time_in_mod"), args) let rec get_env_args ~pos t args = let get_arg_type t name = match (Type.deref t).Type.descr with | Type.Arrow (l, _) -> let _, _, t = List.find (fun (_, n, _) -> n = name) l in t | _ -> parse_error ~pos (Printf.sprintf "Cannot get argument type of %s, this is not a function, it has \ type: %s." name (Type.to_string t)) in List.map (fun (n, n', v) -> let t = mk_ty ~pos (get_arg_type t n).Type.descr in let as_variable = if n = n' then None else Some n' in { label = n; as_variable; typ = t; default = Option.map (term_of_value ~pos ~name:n t) v; pos = t.pos; }) args and term_of_value_base ~pos t v = let get_list_type () = match (Type.deref t).Type.descr with | Type.(List { t }) -> t | _ -> assert false in let get_tuple_type pos = match (Type.deref t).Type.descr with | Type.Tuple t -> List.nth t pos | _ -> assert false in let process_value ~t v = let mk_tm ?(flags = Flags.empty) term = mk ~flags ~t:(mk_ty ~pos t.Type.descr) term in match v with | Value.Int { value = i; flags } -> mk_tm ~flags (`Int i) | Value.Float { value = f } -> mk_tm (`Float f) | Value.Bool { value = b } -> mk_tm (`Bool b) | Value.String { value = s } -> mk_tm (`String s) | Value.Custom { value = g } -> mk_tm (`Custom g) | Value.List { value = l } -> mk_tm (`List (List.map (term_of_value_base ~pos (get_list_type ())) l)) | Value.Tuple { value = l } -> mk_tm (`Tuple (List.mapi (fun idx v -> term_of_value_base ~pos (get_tuple_type idx) v) l)) | Value.Null _ -> mk_tm `Null (* Ignoring env is not correct here but this is an internal operator so we have to trust that devs using it via %argsof now that they are doing. *) | Value.Fun { fun_args = args; fun_body = body } -> let body = mk ~t:(mk_ty ~pos body.t.Type.descr) ~methods:body.Term.methods body.Term.term in mk_tm (`Fun { Term_base.name = None; arguments = get_env_args ~pos t args; body; free_vars = None; }) | _ -> assert false in let meths, _ = Type.split_meths t in let tm = process_value ~t v in mk ~t:tm.Term.t ~methods: (Methods.mapi (fun key meth -> let { Type.scheme = _, t } = List.find (fun { Type.meth } -> meth = key) meths in process_value ~t meth) Value.(methods v)) tm.Term.term and term_of_value ~pos ~name t v = try term_of_value_base ~pos t v with _ -> parse_error ~pos (Printf.sprintf "Argument %s: value %s cannot be represented as a term" name (Value.to_string v)) let builtin_args_of ~only ~except ~pos name = match Environment.get_builtin name with | Some ((_, t), Value.Fun { fun_args = args }) | Some ((_, t), Value.FFI { ffi_args = args; _ }) -> let filtered_args = List.filter (fun (n, _, _) -> n <> "") args in let filtered_args = if only <> [] then List.map (fun n -> try List.find (fun (n', _, _) -> n = n') filtered_args with Not_found -> parse_error ~pos (Printf.sprintf "Builtin %s does not have an argument named %s" name n)) only else filtered_args in List.iter (fun n -> match List.find_opt (fun (n', _, _) -> n = n') args with | Some _ -> () | None -> parse_error ~pos (Printf.sprintf "Builtin %s does not have an argument named %s" name n)) except; let filtered_args = List.filter (fun (n, _, _) -> not (List.mem n except)) filtered_args in get_env_args ~pos t filtered_args | Some _ -> parse_error ~pos (Printf.sprintf "Builtin %s is not a function!" name) | None -> parse_error ~pos (Printf.sprintf "Builtin %s is not registered!" name) let args_of ~only ~except ~pos ~env name = match List.assoc_opt name env with | Some { term = `Fun { arguments } } -> let filtered_args = List.filter (fun { label } -> label <> "") arguments in let filtered_args = if only <> [] then List.map (fun n -> try List.find (fun { label } -> label = n) filtered_args with Not_found -> parse_error ~pos (Printf.sprintf "%s does not have an argument named %s" name n)) only else filtered_args in List.iter (fun n -> match List.find_opt (fun { label } -> label = n) filtered_args with | Some _ -> () | None -> parse_error ~pos (Printf.sprintf "%s does not have an argument named %s" name n)) except; List.filter (fun { label } -> not (List.mem label except)) filtered_args | Some _ -> parse_error ~pos (Printf.sprintf "%s is not a function!" name) | None -> builtin_args_of ~only ~except ~pos name let expand_argsof ~pos ~env ~to_term ~throw args = let anonymous_var_id = ref 0 in let mk_def, args = List.fold_left (fun (mk_def, args) -> function | `Argsof { only; except; source } -> (mk_def, List.rev (args_of ~pos ~env ~only ~except source) @ args) | `Term { label; as_variable; default; typ; annotations; pos } -> report_annotations ~throw ~pos annotations; let mk_def, as_variable = match as_variable with | None -> (mk_def, None) | Some { pat_entry = `PVar [v] } -> (mk_def, Some v) | Some pat -> incr anonymous_var_id; let v = Printf.sprintf "_ann_%d" !anonymous_var_id in let mk_def def = mk_def (mk ~pos (pattern_reducer ~body:def ~pat (mk (`Var v)))) in (mk_def, Some v) in ( mk_def, { label; as_variable; typ = (match typ with | None -> mk_var () | Some typ -> mk_parsed_ty ~env ~to_term typ); default = Option.map (to_term ~env) default; pos = Some (Pos.of_lexing_pos pos); } :: args )) ((fun b -> b), []) args in (mk_def, List.rev args) let app_of ~pos ~only ~except ~env source = let args = args_of ~pos ~only ~except ~env source in List.map (fun { label } -> (label, mk ~t:(mk_var ~pos ()) (`Var label))) args let expand_appof ~pos ~env ~to_term args = List.rev (List.fold_left (fun args -> function | `Argsof { only; except; source } -> List.rev (app_of ~pos ~only ~except ~env source) @ args | `Term (l, v) -> (l, to_term ~env v) :: args) [] args) (** When doing chained calls, we want to update all nested defaults so that, e.g. in: `x?.foo.gni.bla(123)?.gno.gni`, the default for `x.foo` becomes: `any.{gni = any.{ bla = fun (_) -> any.{ gno = any.{ gni = null }}}}` we also need to keep track of which methods are optional in the default value's type to make sure it doesn't force optional methods to be mandatory during type checking. *) let mk_app_invoke_default ~pos ~args body = let app_args = List.map (fun (label, _) -> { Term_base.label; as_variable = None; typ = mk_var (); default = None; pos = None; }) args in mk_fun ~pos app_args body let mk_any ~pos () = let op = mk ~pos (`Var "💣") in mk ~pos (`App (op, [])) let rec mk_invoke_default ~pos ~optional ~name value { invoked; meth; invoke_default } = let t = Type.meth ~pos:(Pos.of_lexing_pos pos) ~optional name ([], mk_var ~pos ()) (mk_var ~pos ()) in let tm = mk_any ~pos () in let value = mk ~t ~methods:(Methods.add name value Term.Methods.empty) tm.Term.term in ( value, update_invoke_default ~pos ~optional:(invoke_default <> None) invoked meth value ) and update_invoke_default ~pos ~optional expr name value = match expr.term with | `Invoke ({ meth; invoke_default } as invoked) -> let value, invoked = mk_invoke_default ~pos ~name ~optional value invoked in mk ~t:expr.Term.t ~methods:expr.Term.methods (`Invoke { invoked; meth; invoke_default = Option.map (fun _ -> value) invoke_default; }) | `App ({ term = `Invoke ({ meth; invoke_default } as invoked) }, args) -> let value, invoked = let invoke_default = match invoke_default with | Some { term = `Fun { Term_base.body } } -> Some body | None -> Some (mk_any ~pos ()) | _ -> assert false in mk_invoke_default ~pos ~name ~optional value { invoked with invoke_default } in mk ~t:expr.Term.t ~methods:expr.Term.methods (`App ( mk ~pos (`Invoke { invoked; meth; invoke_default = Option.map (fun _ -> mk_app_invoke_default ~pos ~args value) invoke_default; }), args )) | _ -> expr let mk_invoke ?(default : Parsed_term.t option) ~pos ~env ~to_term expr v = let expr = to_term ~env expr in let default = Option.map (to_term ~env) default in let optional, value = match default with Some v -> (true, v) | None -> (false, mk ~pos `Null) in match v with | `String meth -> let expr = update_invoke_default ~pos ~optional expr meth value in `Invoke { invoked = expr; invoke_default = Option.map (fun _ -> value) default; meth; } | `App (meth, args) -> let args = expand_appof ~pos ~env ~to_term args in let value = mk_app_invoke_default ~pos ~args value in let expr = update_invoke_default ~pos ~optional expr meth value in `App ( mk ~pos (`Invoke { invoked = expr; invoke_default = Option.map (fun _ -> value) default; meth; }), args ) let mk_coalesce ~pos ~(default : Parsed_term.t) ~env ~to_term (computed : Parsed_term.t) = match computed.term with | `Invoke { invoked; meth = `String m } -> mk_invoke ~pos ~env ~default ~to_term invoked (`String m) | _ -> let null = mk ~pos (`Var "_null") in let op = mk ~pos (`Invoke { invoked = null; invoke_default = None; meth = "default" }) in let handler = mk_fun ~pos [] (to_term ~env default) in `App (op, [("", to_term ~env computed); ("", handler)]) let get_reducer ~pos ~env ~to_term = function | `Get tm -> Printf.eprintf "Warning, %s: the notation !x for references is deprecated, please use \ x() instead.\n\ %!" Pos.(to_string (of_lexing_pos pos)); `App (to_term ~env tm, []) let set_reducer ~pos ~env ~to_term = function | `Set (tm, v) -> let op = mk ~pos (`Invoke { invoked = to_term ~env tm; invoke_default = None; meth = "set" }) in `Cast { cast = mk ~pos (`App (op, [("", to_term ~env v)])); typ = mk_ty ~pos Type.unit; } let if_reducer ~pos ~env ~to_term = function | `Inline_if { if_condition; if_then; if_elsif; if_else } | `If { if_condition; if_then; if_elsif; if_else } -> let if_else = match if_else with | None -> mk ~pos (`Tuple []) | Some t -> to_term ~env t in let term = List.fold_left (fun if_else (condition, _then) -> let op = mk ~pos (`Var "if") in mk ~pos (`App ( op, [ ("", to_term ~env condition); ("then", mk_fun ~pos [] (to_term ~env _then)); ("else", mk_fun ~pos [] if_else); ] ))) if_else (List.rev ((if_condition, if_then) :: if_elsif)) in term.term let while_reducer ~pos ~env ~to_term = function | `While { while_condition; while_loop } -> let op = mk ~pos (`Var "while") in let while_condition = mk_fun ~pos [] (to_term ~env while_condition) in let while_loop = mk_fun ~pos [] (to_term ~env while_loop) in `App (op, [("", while_condition); ("", while_loop)]) let base_for_reducer ~pos for_variable for_iterator for_loop = let for_op = mk ~pos (`Var "for") in let for_loop = mk_fun ~pos [ { label = ""; as_variable = Some for_variable; typ = mk_var (); default = None; pos = None; }; ] for_loop in `App (for_op, [("", for_iterator); ("", for_loop)]) let iterable_for_reducer ~pos ~env ~to_term = function | `Iterable_for { iterable_for_variable; iterable_for_iterator; iterable_for_loop } -> base_for_reducer ~pos iterable_for_variable (to_term ~env iterable_for_iterator) (to_term ~env iterable_for_loop) let for_reducer ~pos ~env ~to_term = function | `For { for_variable; for_from; for_to; for_loop } -> let to_op = mk ~pos (`Var "iterator") in let to_op = mk ~pos (`Invoke { invoked = to_op; invoke_default = None; meth = "int" }) in let for_condition = mk ~pos (`App (to_op, [("", to_term ~env for_from); ("", to_term ~env for_to)])) in base_for_reducer ~pos for_variable for_condition (to_term ~env for_loop) let infix_reducer ~pos ~env ~to_term = function | `Infix (tm, op, tm') -> let op = mk ~pos (`Var op) in `App (op, [("", to_term ~env tm); ("", to_term ~env tm')]) let bool_op_reducer ~pos ~env ~to_term = function | `BoolOp (op, tm :: terms) -> List.fold_left (fun tm tm' -> let op = mk ~pos (`Var op) in let tm = mk_fun ~pos [] (mk ~pos tm) in let tm' = mk_fun ~pos [] (to_term ~env tm') in `App (op, [("", tm); ("", tm')])) (to_term ~env tm).term terms | `BoolOp (_, []) -> assert false let simple_fun_reducer ~pos:_ ~env ~to_term = function | `Simple_fun tm -> `Fun { name = None; arguments = []; body = to_term ~env tm; free_vars = None; } let negative_reducer ~pos ~env ~to_term = function | `Negative tm -> let op = mk ~pos (`Var "~-") in `App (op, [("", to_term ~env tm)]) let not_reducer ~pos ~env ~to_term = function | `Not tm -> let op = mk ~pos (`Var "not") in `App (op, [("", to_term ~env tm)]) let append_term ~pos a b = let op = mk ~pos (`Var "_::_") in `App (op, [("", a); ("", b)]) let append_reducer ~pos ~env ~to_term = function | `Append (tm, tm') -> append_term ~pos (to_term ~env tm) (to_term ~env tm') let rec list_reducer ~pos ?(cur = `List []) ~env ~to_term l = match (l, cur) with | [], cur -> cur | `Term v :: rem, `List cur -> list_reducer ~cur:(`List (to_term ~env v :: cur)) ~pos ~env ~to_term rem | `Term v :: rem, cur -> let cur = append_term ~pos (to_term ~env v) (mk ~pos cur) in list_reducer ~pos ~cur ~env ~to_term rem | `Ellipsis v :: rem, cur -> let list = mk ~pos (`Var "list") in let op = mk ~pos (`Invoke { invoked = list; invoke_default = None; meth = "append" }) in let cur = `App (op, [("", to_term ~env v); ("", mk ~pos cur)]) in list_reducer ~pos ~cur ~env ~to_term rem let assoc_reducer ~pos ~env ~to_term = function | `Assoc (tm, tm') -> let op = mk ~pos (`Var "_[_]") in `App (op, [("", to_term ~env tm); ("", to_term ~env tm')]) let regexp_reducer ~pos ~env:_ ~to_term:_ = function | `Regexp (regexp, flags) -> let regexp = render_string ~pos ~sep:'/' regexp in let regexp = mk ~pos (`String regexp) in let flags = List.map Char.escaped flags in let flags = List.map (fun s -> mk ~pos (`String s)) flags in let flags = mk ~pos (`List flags) in let op = mk ~pos (`Var "regexp") in `App (op, [("", regexp); ("flags", flags)]) let try_reducer ~pos ~env ~to_term = function | `Try { try_body; try_variable; try_errors_list; try_handler; try_finally } -> let try_body = mk_fun ~pos [] (to_term ~env try_body) in let err_arg = [ { label = ""; as_variable = Some try_variable; typ = mk_var (); default = None; pos = None; }; ] in let finally_pos, finally = match try_finally with | None -> (pos, mk ~pos (`Tuple [])) | Some tm -> (tm.pos, to_term ~env tm) in let finally = mk_fun ~pos:finally_pos [] finally in let handler_pos, handler = match try_handler with | None -> (pos, mk ~pos (`Tuple [])) | Some tm -> (tm.pos, to_term ~env tm) in let handler = mk_fun ~pos:handler_pos err_arg handler in let error_module = mk ~pos (`Var "error") in let try_errors_list = match try_errors_list with | None -> mk ~pos `Null | Some tm -> to_term ~env tm in let op = mk ~pos (`Invoke { invoked = error_module; invoke_default = None; meth = "catch" }) in `App ( op, [ ("errors", try_errors_list); ("body", try_body); ("catch", handler); ("finally", finally); ] ) let mk_let_json_parse ~pos (args, pat, def, cast) body = let ty = match cast with Some ty -> ty | None -> mk_var ~pos () in let tty = Value.RuntimeType.to_term ty in let json5 = match List.assoc_opt "json5" args with | Some v -> v | None -> Term.(make (`Bool false)) in let parser = mk ~pos (`Var "_internal_json_parser_") in let def = mk ~pos (`App (parser, [("json5", json5); ("type", tty); ("", def)])) in let def = mk ~pos (`Cast { cast = def; typ = ty }) in pattern_reducer ~body ~pat def let mk_let_xml_parse ~pos (pat, def, cast) body = let ty = match cast with Some ty -> ty | None -> mk_var ~pos () in let tty = Value.RuntimeType.to_term ty in let parser = mk ~pos (`Var "_internal_xml_parser_") in let def = mk ~pos (`App (parser, [("type", tty); ("", def)])) in let def = mk ~pos (`Cast { cast = def; typ = ty }) in pattern_reducer ~body ~pat def let mk_let_yaml_parse ~pos (pat, def, cast) body = let ty = match cast with Some ty -> ty | None -> mk_var ~pos () in let tty = Value.RuntimeType.to_term ty in let parser = mk ~pos (`Var "_internal_yaml_parser_") in let def = mk ~pos (`App (parser, [("type", tty); ("", def)])) in let def = mk ~pos (`Cast { cast = def; typ = ty }) in pattern_reducer ~body ~pat def let mk_let_sqlite_row ~pos (pat, def, cast) body = let ty = match cast with Some ty -> ty | None -> mk_var ~pos () in let tty = Value.RuntimeType.to_term ty in let parser = mk ~pos (`Var "_sqlite_row_parser_") in let def = mk ~pos (`App (parser, [("type", tty); ("", def)])) in let def = mk ~pos (`Cast { cast = def; typ = ty }) in pattern_reducer ~body ~pat def let mk_let_sqlite_query ~pos (pat, def, cast) body = let ty = match cast with Some ty -> ty | None -> mk_var ~pos () in let inner_list_ty = mk_var ~pos () in Typing.( ty <: mk_ty ~pos (Type.List { Type.t = inner_list_ty; json_repr = `Tuple })); let tty = Value.RuntimeType.to_term inner_list_ty in let parser = mk ~pos (`Var "_sqlite_row_parser_") in let mapper = let query = mk ~pos (`Var "query") in mk ~pos (`App (parser, [("type", tty); ("", query)])) in let mapper = mk ~pos (`Fun { free_vars = None; name = None; arguments = [ { label = ""; as_variable = Some "query"; default = None; typ = mk_var ~pos (); pos = None; }; ]; body = mapper; }) in let list = mk ~pos (`Var "list") in let map = mk ~pos (`Invoke { invoked = list; invoke_default = None; meth = "map" }) in let def = mk ~pos (`App (map, [("", mapper); ("", def)])) in let def = mk ~pos (`Cast { cast = def; typ = ty }) in pattern_reducer ~body ~pat def let mk_rec_fun ~pos pat arguments body = let name = match pat with | `PVar l when l <> [] -> List.hd (List.rev l) | _ -> assert false in mk ~pos (`Fun { name = Some name; arguments; body; free_vars = None }) let needs_toplevel = ref false let mk_eval ~pos (pat, def, body, cast) = needs_toplevel := true; let ty = match cast with Some ty -> ty | None -> mk_var ~pos () in let tty = Value.RuntimeType.to_term ty in let eval = mk ~pos (`Var "_eval_") in let def = mk ~pos (`App (eval, [("type", tty); ("", def)])) in let def = mk ~pos (`Cast { cast = def; typ = ty }) in pattern_reducer ~body ~pat def let needs_toplevel () = !needs_toplevel let string_of_let_decoration = function | `None -> "" | `Recursive -> "rec" | `Replaces -> "replaces" | `Eval -> "eval" | `Sqlite_query -> "sqlite.query" | `Sqlite_row -> "sqlite.row" | `Yaml_parse -> "yaml.parse" | `Xml_parse -> "xml.parse" | `Json_parse _ -> "json.parse" let mk_let ~env ~pos ~to_term ~comments ~throw ({ decoration; pat; arglist; def; cast }, body) = let def = to_term ~env def in let mk_body def = let env = match pat.pat_entry with | `PVar path -> let path = String.concat "." path in let env = if decoration <> `Replaces then List.filter (fun (p, _) -> not (String.starts_with ~prefix:(path ^ ".") p)) env else env in (path, def) :: env | _ -> env in to_term ~env body in let cast = Option.map (mk_parsed_ty ~pos ~env ~to_term) cast in let arglist = Option.map (expand_argsof ~throw ~pos ~env ~to_term) arglist in let doc = match List.rev (List.filter_map (function pos, `Before c -> Some (pos, c) | _ -> None) comments) with | (pos, doc) :: _ -> Doc.parse_doc ~pos:(Pos.of_lexing_pos pos) (String.concat "\n" doc) | _ -> None in match (arglist, decoration) with | Some (mk_def, arglist), `None | Some (mk_def, arglist), `Replaces -> let replace = decoration = `Replaces in let def = mk_def def in let def = mk_fun ~pos arglist def in let def = match cast with | Some ty -> mk ~pos (`Cast { cast = def; typ = ty }) | None -> def in let body = mk_body def in pattern_reducer ?doc ~body ~pat ~replace def | Some (mk_def, arglist), `Recursive -> let def = mk_def def in let def = mk_rec_fun ~pos pat.pat_entry arglist def in let def = match cast with | Some ty -> mk ~pos (`Cast { cast = def; typ = ty }) | None -> def in let body = mk_body def in pattern_reducer ?doc ~body ~pat def | None, `None | None, `Replaces -> let replace = decoration = `Replaces in let def = match cast with | Some ty -> mk ~pos (`Cast { cast = def; typ = ty }) | None -> def in let body = mk_body def in pattern_reducer ?doc ~body ~pat ~replace def | None, `Eval -> let body = mk_body def in mk_eval ~pos (pat, def, body, cast) | None, `Json_parse args -> let args = List.map (fun (l, v) -> (l, to_term ~env v)) args in let body = mk_body def in mk_let_json_parse ~pos (args, pat, def, cast) body | None, `Yaml_parse -> let body = mk_body def in mk_let_yaml_parse ~pos (pat, def, cast) body | None, `Xml_parse -> let body = mk_body def in mk_let_xml_parse ~pos (pat, def, cast) body | None, `Sqlite_row -> let body = mk_body def in mk_let_sqlite_row ~pos (pat, def, cast) body | None, `Sqlite_query -> let body = mk_body def in mk_let_sqlite_query ~pos (pat, def, cast) body | Some _, v -> parse_error ~pos (string_of_let_decoration v ^ " does not apply to function assignments") | None, v -> parse_error ~pos (string_of_let_decoration v ^ " only applies to function assignments") let to_encoder_string = function | `Verbatim s -> s | `String (pos, (sep, s)) -> render_string ~pos ~sep s let rec to_encoder_params ~env ~to_term l = List.map (function | `Anonymous s -> `Anonymous (to_encoder_string s) | `Labelled (s, t) -> `Labelled (to_encoder_string s, to_term ~env t) | `Encoder e -> `Encoder (to_encoder ~to_term ~env e)) l and to_encoder ~env ~to_term (lbl, params) = (lbl, to_encoder_params ~env ~to_term params) let rec to_ast ~throw ~env ~pos ~comments ast = let to_ast = to_ast ~throw in let to_term = to_term ~throw in match ast with | `Methods _ | `Block _ | `Parenthesis _ | `Eof | `Include _ -> assert false | (`If_def _ as ast) | (`If_encoder _ as ast) | (`If_version _ as ast) -> (to_term ~env (pp_if_reducer ~pos ~env ast)).term | `Get _ as ast -> get_reducer ~pos ~env ~to_term ast | `Set _ as ast -> set_reducer ~pos ~env ~to_term ast | `Inline_if _ as ast -> if_reducer ~pos ~env ~to_term ast | `If _ as ast -> if_reducer ~pos ~env ~to_term ast | `While _ as ast -> while_reducer ~pos ~env ~to_term ast | `For _ as ast -> for_reducer ~pos ~env ~to_term ast | `Iterable_for _ as ast -> iterable_for_reducer ~pos ~env ~to_term ast | `Not _ as ast -> not_reducer ~pos ~env ~to_term ast | `Negative _ as ast -> negative_reducer ~pos ~env ~to_term ast | `Append _ as ast -> append_reducer ~pos ~env ~to_term ast | `Assoc _ as ast -> assoc_reducer ~pos ~env ~to_term ast | `Infix _ as ast -> infix_reducer ~pos ~env ~to_term ast | `Bool _ as ast -> ast | `BoolOp _ as ast -> bool_op_reducer ~pos ~env ~to_term ast | `Simple_fun _ as ast -> simple_fun_reducer ~pos ~env ~to_term ast | `Regexp _ as ast -> regexp_reducer ~pos ~env ~to_term ast | `Try _ as ast -> try_reducer ~pos ~env ~to_term ast | `String_interpolation (sep, l) -> let l = List.map (function | `String s -> `Term (mk_parsed ~pos (`String (sep, s))) | `Term tm -> `Term (mk_parsed ~pos (`App (mk_parsed ~pos (`Var "string"), [`Term ("", tm)])))) l in let op = mk_parsed ~pos (`Invoke { invoked = mk_parsed ~pos (`Var "string"); meth = `String "concat"; optional = false; }) in to_ast ~env ~pos ~comments (`App (op, [`Term ("", mk_parsed ~pos (`List l))])) | `Def p | `Let p | `Binding p -> mk_let ~throw ~pos ~env ~to_term ~comments p | `Coalesce (t, default) -> mk_coalesce ~pos ~env ~to_term ~default t | `At (t, t') -> `App (to_term ~env t', [("", to_term ~env t)]) | `Time t -> mk_time_pred ~pos (during ~pos t) | `Time_interval (t, t') -> mk_time_pred ~pos (between ~pos t t') | `Custom _ as ast -> ast | `Encoder e -> `Encoder (to_encoder ~to_term ~env e) | `List l -> list_reducer ~pos ~env ~to_term (List.rev l) | `Tuple l -> `Tuple (List.map (to_term ~env) l) | `String (sep, s) -> `String (render_string ~pos ~sep s) | `Int i -> `Int (int_of_string i) | `Float f -> ( try `Float (Scanf.sscanf f "%f" (fun v -> v)) with _ -> parse_error ~pos (Printf.sprintf "Invalid float value: %s" f)) | `Null -> `Null | `Cast { cast = t; typ } -> `Cast { cast = to_term ~env t; typ = mk_parsed_ty ~pos ~env ~to_term typ } | `Invoke { invoked; optional; meth } -> let default = if optional then Some (mk_parsed ~pos `Null) else None in mk_invoke ~pos ~env ?default ~to_term invoked meth | `Open (t, t') -> `Open (to_term ~env t, to_term ~env t') | `Var s -> `Var s | `Seq (t, t') -> `Seq (to_term ~env t, to_term ~env t') | `App (t, args) -> (match (t, args) with | { term = `Var "_null"; pos }, [] -> let bt = Printexc.get_callstack 0 in throw ~bt (Term.Deprecated ("use `null`", Pos.of_lexing_pos pos)) | _ -> ()); let args = expand_appof ~pos ~env ~to_term args in `App (to_term ~env t, args) | `Fun (args, body) -> `Fun (to_func ~throw ~pos ~env ~to_term args body) | `RFun (name, args, body) -> `Fun (to_func ~throw ~pos ~env ~to_term ~name args body) and to_func ~pos ~env ~to_term ~throw ?name arguments body = let mk_def, arguments = expand_argsof ~throw ~pos ~env ~to_term arguments in { name; arguments; body = mk_def (to_term ~env body); free_vars = None } and to_term ~throw ~env (tm : Parsed_term.t) : Term.t = let to_term = to_term ~throw in report_annotations ~throw ~pos:tm.pos tm.annotations; match tm.term with | `Seq ({ pos; term = `If_def _ as ast }, t') | `Seq ({ pos; term = `If_encoder _ as ast }, t') | `Seq ({ pos; term = `If_version _ as ast }, t') -> let t = pp_if_reducer ~pos ~env ast in to_term ~env (Term_preprocessor.concat_term t t') | `Block tm -> to_term ~env tm | `Parenthesis tm -> to_term ~env tm | `Eof -> to_term ~env { tm with term = `Tuple [] } | `Methods (base, methods) -> (* let _ = src in let replaces _ = dst in _ *) let replace_methods ~src dst = mk ~pos:tm.pos (`Let { doc = None; replace = false; pat = `PVar ["_"]; gen = []; def = src; body = mk ~pos:tm.pos (`Let { doc = None; replace = true; pat = `PVar ["_"]; gen = []; def = dst; body = mk ~pos:tm.pos (`Var "_"); }); }) in let term = match base with | None -> mk ~pos:tm.pos (`Tuple []) | Some tm -> to_term ~env tm in List.fold_left (fun term -> function | `Ellipsis src -> replace_methods ~src:(to_term ~env src) term | `Method (name, tm) -> { term with methods = Methods.add name (to_term ~env tm) term.methods; }) term methods | term -> let flags = match term with | `Int i when String.length i >= 2 && String.(lowercase_ascii (sub i 0 2)) = "0x" -> Flags.(add empty hex_int) | `Int i when String.length i >= 2 && String.(lowercase_ascii (sub i 0 2)) = "0o" -> Flags.(add empty octal_int) | _ -> Flags.empty in let term = to_ast ~throw ~env ~pos:tm.pos ~comments:tm.comments term in { t = mk_var ~pos:tm.pos (); term; methods = Methods.empty; flags } let to_encoder_params ~throw = let to_term = to_term ~throw in to_encoder_params ~env:[] ~to_term let to_term ~throw tm = to_term ~throw ~env:[] tm liquidsoap-2.4.2/src/lang/base/term/term_reducer.mli000066400000000000000000000031401513273233300224450ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Runtime reducer from parsed terms to runtime terms. *) type processor = ( Parser.token * Lexing.position * Lexing.position, Parser_helper.Term.t ) MenhirLib.Convert.revised val program : processor val typecheck : (?env:Typing.env -> Term.t -> unit) ref val mk_expr : ?fname:string -> processor -> Sedlexing.lexbuf -> Parsed_term.t val to_term : throw:(bt:Printexc.raw_backtrace -> exn -> unit) -> Parsed_term.t -> Term.t val to_encoder_params : throw:(bt:Printexc.raw_backtrace -> exn -> unit) -> Parsed_term.encoder_params -> Term.encoder_params val needs_toplevel : unit -> bool liquidsoap-2.4.2/src/lang/base/term/term_stdlib.ml000066400000000000000000000067641513273233300221430ustar00rootroot00000000000000let rec append_ref = let open Runtime_term in function | { term = `Let ({ body } as _let) } as tm -> { tm with term = `Let { _let with body = append_ref body } } | { term = `Seq (t1, t2) } as tm -> { tm with term = `Seq (t1, append_ref t2) } | tm -> Term.make ?pos:tm.t.Type.pos (`Seq ( tm, Term.make ?pos:tm.t.Type.pos (`Cache_env (ref { var_name = 0; var_id = 0; env = [] })) )) let rec extract_ref = let open Runtime_term in function | { term = `Let { body } } -> extract_ref body | { term = `Seq (_, tm) } -> extract_ref tm | { term = `Cache_env ref } -> !ref | _ -> assert false let rec prepend_stdlib ~term = let open Runtime_term in function | { term = `Let ({ body } as _let) } as tm -> { tm with term = `Let { _let with body = prepend_stdlib ~term body } } | { term = `Seq (t1, t2) } as tm -> { tm with term = `Seq (t1, prepend_stdlib ~term t2) } | { term = `Cache_env _ } -> term | _ -> assert false let rec prepend_parsed_stdlib = let open Parsed_term in fun ~parsed_term -> function | { term = `Let (def, body) } as tm -> { tm with term = `Let (def, prepend_parsed_stdlib ~parsed_term body) } | { term = `Def (def, body) } as tm -> { tm with term = `Def (def, prepend_parsed_stdlib ~parsed_term body) } | { term = `Binding (def, body) } as tm -> { tm with term = `Binding (def, prepend_parsed_stdlib ~parsed_term body); } | { term = `Seq (t1, t2) } as tm -> { tm with term = `Seq (t1, prepend_parsed_stdlib ~parsed_term t2) } | tm -> Parsed_term.make ~pos:tm.pos (`Seq (tm, parsed_term)) (** To be cacheable, the standard library is parsed and converted to a term. We add [`Cache_env] to it and type-check it. This results in having the full standard library env stored in it. We can then cache this term and retrieve the full env using it. Next, we append the user script to the resulting term and typecheck the user script only using the standard library environment. *) let prepare ~stdlib ~cache ~error_on_no_stdlib ~deprecated parsed_term = let stdlib = match stdlib with | Some stdlib -> stdlib | None -> let dir = !Hooks.liq_libs_dir () in Filename.concat dir "stdlib.liq" in let libs = Runtime.libs ~stdlib ~error_on_no_stdlib ~deprecated () in let script = List.fold_left (Printf.sprintf "%s\n%%include %S") "" libs in let lexbuf = Sedlexing.Utf8.from_string script in let parsed_stdlib, stdlib = Runtime.report ~lexbuf:(Some lexbuf) ~default:(fun () -> raise Runtime.Error) (fun ~throw () -> let parsed_stdlib = Term_reducer.mk_expr Term_reducer.program lexbuf in (parsed_stdlib, Term_reducer.to_term ~throw parsed_stdlib)) in let append () = let stdlib = append_ref stdlib in let stdlib = Runtime.type_term ~cache_dirtype:`System ~name:"stdlib" ~cache ~trim:false ~lib:true ~term:stdlib parsed_stdlib in let { Runtime_term.var_name; var_id; env } = extract_ref stdlib in Atomic.set Type_base.var_name_atom var_name; Atomic.set Type_base.var_id_atom var_id; let checked_term = Runtime.report ~lexbuf:None ~default:(fun () -> raise Runtime.Error) (fun ~throw () -> Term_reducer.to_term ~throw parsed_term) in let full_term = prepend_stdlib ~term:checked_term stdlib in { Runtime.checked_term; full_term; env } in (prepend_parsed_stdlib ~parsed_term parsed_stdlib, append) liquidsoap-2.4.2/src/lang/base/term/term_stdlib.mli000066400000000000000000000002451513273233300223000ustar00rootroot00000000000000val prepare : stdlib:string option -> cache:bool -> error_on_no_stdlib:bool -> deprecated:bool -> Parsed_term.t -> Parsed_term.t * Runtime.append_stdlib liquidsoap-2.4.2/src/lang/base/term/term_trim.ml000066400000000000000000000061771513273233300216330ustar00rootroot00000000000000open Runtime_term let rec trim_type t = let open Type in match t with | { descr = Arrow (args, ret_t) } as t -> { t with descr = Arrow ( List.map (fun (b, s, p) -> (b, s, trim_type p)) args, trim_type ret_t ); } | { descr = Getter g } as t -> { t with descr = Getter (trim_type g) } | { descr = Nullable n } as t -> { t with descr = Nullable (trim_type n) } | { descr = Meth (_, t) } -> trim_type t | { descr = List repr } as t -> { t with descr = List { repr with t = trim_type repr.t } } | { descr = Tuple l } as t -> { t with descr = Tuple (List.map trim_type l) } | { descr = Var { contents = Link (_, t) } } -> trim_type t | { descr = Var { contents = Free _ } } as t -> t | ( { descr = Constr _ } | { descr = Custom _ } | { descr = String } | { descr = Int } | { descr = Float } | { descr = Bool } | { descr = Never } ) as t -> t let trim_type t = { (trim_type t) with pos = t.pos } let rec trim_encoder_params params = List.map (function | `Anonymous _ as v -> v | `Encoder enc -> `Encoder (trim_encoder enc) | `Labelled (lbl, t) -> `Labelled (lbl, trim_term t)) params and trim_encoder (name, params) = (name, trim_encoder_params params) and trim_ast tm = match tm with | `Custom _ | `Var _ | `Null | `Int _ | `Float _ | `Bool _ | `String _ | `Cache_env _ -> tm | `Tuple l -> `Tuple (List.map trim_term l) | `Open (t, t') -> `Open (trim_term t, trim_term t') | `Seq (t, t') -> `Seq (trim_term t, trim_term t') | `Let ({ def; body } as _let) -> `Let { _let with def = trim_term def; body = trim_term body } | `List l -> `List (List.map trim_term l) | `Cast c -> `Cast { c with cast = trim_term c.cast } | `App (t, l) -> `App ( { (trim_term t) with methods = Methods.empty }, List.map (fun (lbl, t) -> (lbl, trim_term t)) l ) | `Invoke { invoked; invoke_default; meth } -> let invoked = trim_term invoked in `Invoke { invoked = { invoked with methods = Methods.filter (fun lbl _ -> lbl = meth) invoked.methods; }; invoke_default = Option.map trim_term invoke_default; meth; } | `Hide (tm, l) -> let tm = trim_term tm in `Hide ( { tm with methods = Methods.filter (fun lbl _ -> not (List.mem lbl l)) tm.methods; }, l ) | `Encoder enc -> `Encoder (trim_encoder enc) | `Fun ({ body; arguments } as _fun) -> `Fun { _fun with body = trim_term body; arguments = List.map (fun arg -> { arg with default = Option.map trim_term arg.default }) arguments; } and trim_term ({ t; term; methods } as tm) = { tm with t = trim_type t; term = trim_ast term; methods = Term.Methods.map trim_term methods; } liquidsoap-2.4.2/src/lang/base/type.ml000066400000000000000000000050631513273233300176340ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) include Type_base include Ref_type module Custom = Type_custom let record_constr = { constr_descr = "a record type"; univ_descr = None; satisfied = (fun ~subtype:_ ~satisfies b -> let m, b = split_meths b in match b.descr with | Var _ -> satisfies b | Tuple [] when m = [] -> raise Unsatisfied_constraint | Tuple [] -> () | _ -> raise Unsatisfied_constraint); } let num_constr = { constr_descr = "a number type"; univ_descr = None; satisfied = (fun ~subtype:_ ~satisfies b -> let b = demeth b in match b.descr with | Var _ -> satisfies b | Never | Int | Float -> () | _ -> raise Unsatisfied_constraint); } let ord_constr = { constr_descr = "an orderable type"; univ_descr = None; satisfied = (fun ~subtype:_ ~satisfies b -> let m, b = split_meths b in match b.descr with | Var _ -> satisfies b | Custom _ | Int | Float | String | Bool | Never -> () | Constr c -> List.iter (fun (_, t) -> satisfies t) c.params | Tuple [] -> (* For records, we want to ensure that all fields are ordered. *) List.iter (fun { scheme = v, a } -> if v <> [] then raise Unsatisfied_constraint; satisfies a) m | Tuple l -> List.iter satisfies l | List { t = b } -> satisfies b | Nullable b -> satisfies b | _ -> raise Unsatisfied_constraint); } liquidsoap-2.4.2/src/lang/base/type.mli000066400000000000000000000127101513273233300200020ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) val debug : bool ref val debug_levels : bool ref val debug_variance : bool ref (** {2 Types} *) open Type_base type variance = [ `Covariant | `Invariant ] type t = Type_base.t = { pos : Pos.Option.t; descr : descr } type custom = Type_base.custom type custom_handler = Type_base.custom_handler = { typ : custom; custom_name : string; copy_with : (t -> t) -> custom -> custom; occur_check : (t -> unit) -> custom -> unit; filter_vars : (var list -> t -> var list) -> var list -> custom -> var list; repr : (var list -> t -> constr R.t) -> var list -> custom -> constr R.t; subtype : (t -> t -> unit) -> custom -> custom -> unit; sup : (t -> t -> t) -> custom -> custom -> custom; to_string : custom -> string; } type invar = Type_base.invar = Free of var | Link of variance * t type var_t = Type_base.var_t = { id : int; mutable contents : invar } type descr = Type_base.descr = | String | Int | Float | Bool | Never | Custom of custom_handler | Constr of constructed | Getter of t (** a getter: something that is either a t or () -> t *) | List of repr_t | Tuple of t list | Nullable of t (** something that is either t or null *) | Meth of meth * t (** t with a method added *) | Arrow of t argument list * t (** a function *) | Var of var_t (** a type variable *) type constr = Type_base.constr = { constr_descr : string; univ_descr : string option; satisfied : subtype:(t -> t -> unit) -> satisfies:(t -> unit) -> t -> unit; } module Constraints = Type_base.Constraints type constructed = Type_base.constructed = { constructor : string; params : (variance * t) list; } type var = Type_base.var = { name : int; mutable level : int; mutable constraints : Constraints.t; } type scheme = var list * t type meth_doc = Type_base.meth_doc = { meth_descr : string; category : [ `Method | `Callback ]; } type meth = Type_base.meth = { meth : string; optional : bool; scheme : scheme; doc : meth_doc; json_name : string option; } type repr_t = Type_base.repr_t = { t : t; json_repr : [ `Tuple | `Object ] } val string_of_constr : constr -> string val record_constr : constr val num_constr : constr val ord_constr : constr module R = Type_base.R type 'a argument = bool * string * 'a exception NotImplemented exception Exists of Pos.Option.t * string exception Unsatisfied_constraint val unit : descr module Var = Type_base.Var module Vars = Type_base.Vars (** Generate fresh types from existing types. *) module Fresh : sig type mapper = Type_base.Fresh.mapper (* Use [selector] to pick variables to be re-freshed. If [level] is passed, all new variables are created with the given level. *) val init : ?preserve_positions:bool -> ?selector:(var -> bool) -> ?level:int -> unit -> mapper (* Generate a fresh var using the parameters passed when initializing the corresponding handler. Generated variables are memoized. *) val make_var : mapper -> var -> var (* Generate a fresh type using the parameters passed when initializing the corresponding handler. *) val make : mapper -> t -> t end (* Generate a fully refreshed type. Shared variables are mapped to shared fresh variables. *) val fresh : t -> t val make : ?pos:Pos.t -> descr -> t val deref : t -> t val demeth : t -> t val remeth : t -> t -> t val invoke : t -> string -> scheme val has_meth : t -> string -> bool val invokes : t -> string list -> var list * t val meth : ?pos:Pos.t -> ?json_name:string -> ?category:[ `Method | `Callback ] -> ?optional:bool -> string -> scheme -> ?doc:string -> t -> t (** Type of references on a given type. *) val reference : ?pos:Pos.t -> t -> t (** [invoke] is used to raise proper exception in [Typechecking]. *) val meths : ?invoke:(t -> string -> scheme) -> ?pos:Pos.t -> string list -> scheme -> t -> t val split_meths : t -> meth list * t val hide_meth : string -> t -> t val opt_meth : string -> t -> t val get_meth : string -> t -> meth val filter_meths : t -> (meth -> bool) -> t val map_meths : t -> (meth -> meth) -> t val var : ?constraints:constr list -> ?level:int -> ?pos:Pos.t -> unit -> t val mk_invariant : t -> unit val to_string_fun : (?generalized:var list -> t -> string) ref val to_string : ?generalized:var list -> t -> string val string_of_scheme : scheme -> string val is_fun : t -> bool val is_source : t -> bool module Custom = Type_custom val register_type : string -> (unit -> t) -> unit val find_opt_typ : string -> (unit -> t) option liquidsoap-2.4.2/src/lang/base/typechecking.ml000066400000000000000000000451221513273233300213300ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Term open Typing exception No_method of string * Type.t exception Top_level_override of string * Pos.t option let debug = ref false (** {1 Type checking / inference} *) (** Can a function return type be generalized after being applied? Current implementation considers it safe to generalize when all universal types in the function return type are specified by a non-optional argument. For instance, - this can be generalized: [def f() = fun (x) -> x end; fn = f() : fun ('a) -> 'a ] - but this cannot: [s = single() : source('A) ] - and this cannot either: [def f() = fun (x=null) -> ref(x) end; fn = f() : (?'A?) -> ref('A) ] When all universal types in the return are specified by a non-optional argument, we are assured that anything that would be generalized is later specified when the required argument is passed. If this argument is still of a universal type, then the argument's universal type takes over the type that was generalized. *) let function_app_value_restriction fn = let rec filter_app_vars l t = let t = Type.deref t in match t.descr with | Int | Float | String | Bool | Never -> l | Custom c -> c.filter_vars filter_app_vars l c.typ | Getter t -> filter_app_vars l t | List { t } | Nullable t -> filter_app_vars l t | Tuple aa -> List.fold_left filter_app_vars l aa | Meth ({ scheme = g, t }, u) -> let l = List.filter (fun v -> not (List.mem v g)) (filter_app_vars l t) in filter_app_vars l u | Constr c -> List.fold_left (fun l (_, t) -> filter_app_vars l t) l c.params | Var { contents = Free var } -> var :: l | Var { contents = Link _ } -> assert false | Arrow (p, t) -> let l = filter_app_vars l t in let pl = List.fold_left (fun pl -> function | true, _, _ -> pl | false, _, t -> filter_app_vars pl t) [] p in List.filter (fun v -> not (List.memq v pl)) l in match Type.demeth fn.Term.t with | { Type.descr = Arrow (_, t) } -> filter_app_vars [] t = [] | _ -> false (** Terms for which generalization is safe. *) let value_restriction t = let rec value_restriction t = match t.term with | `Var _ -> true | `Fun _ -> true | `Null -> true | `App (fn, _) -> function_app_value_restriction fn | `List l | `Tuple l -> List.for_all value_restriction l | `Int _ | `Float _ | `String _ | `Bool _ | `Custom _ -> true | `Let l -> value_restriction l.def && value_restriction l.body | `Cast { cast = t } -> value_restriction t (* | Invoke (t, _) -> value_restriction t *) | _ -> false in value_restriction t && Methods.for_all (fun _ meth_term -> value_restriction meth_term) t.methods (** A simple mechanism for delaying printing toplevel tasks as late as possible, to avoid seeing too many unknown variables. *) let add_task, pop_tasks = let q = Queue.create () in ( (fun f -> Queue.add f q), fun () -> try while true do (Queue.take q) () done with Queue.Empty -> () ) (** Generate a type with fresh variables for a pattern. *) let type_of_pat ~level ~pos = function | `PVar x -> let a = Type.var ~level ?pos () in ([(x, a)], a) | `PTuple l -> let env, l = List.fold_left (fun (env, l) var -> let a = Type.var ~level ?pos () in (([var], a) :: env, a :: l)) ([], []) l in let l = List.rev l in (env, Type.make ?pos (Type.Tuple l)) (* Type-check an expression. *) let rec check ?(print_toplevel = false) ~throw ~level ~env e = let check = check ~throw in if !debug then Printf.printf "\n# %s : ?\n\n%!" (Term.to_string e); let check ?print_toplevel ~level ~env e = check ?print_toplevel ~level ~env e; if !debug then Printf.printf "\n# %s : %s\n\n%!" (Term.to_string e) (Type.to_string e.t) in (* The toplevel position of the (un-dereferenced) type is the actual parsing position of the value. When we synthesize a type against which the type of the term is unified, we have to set the position information in order not to loose it. *) let pos = e.t.Type.pos in let mk t = Type.make ?pos t in let check_fun ~env e { arguments; body } = let base_check = check ~level ~env in let proto_t, env = List.fold_left (fun (p, env) -> function | { label; as_variable; typ; default = None; pos } -> update_level level typ; ( (false, label, typ) :: p, env#add ~pos (Option.value ~default:label as_variable) ([], typ) ) | { label; as_variable; typ; default = Some v; pos } -> update_level level typ; base_check v; v.t <: typ; ( (true, label, typ) :: p, env#add ~pos (Option.value ~default:label as_variable) ([], typ) )) ([], env) arguments in let proto_t = List.rev proto_t in (* Ensure that we don't have the same label twice. *) List.fold_left (fun labels (_, l, _) -> if l = "" then labels else ( if List.mem l labels then raise (Duplicate_label (e.t.Type.pos, l)); l :: labels)) [] proto_t |> ignore; check ~level ~env body; e.t >: mk (Type.Arrow (proto_t, body.t)) in let base_type = Type.var () in let () = match e.term with | `Cache_env r -> r := { var_name = Atomic.get Type_base.var_name_atom; var_id = Atomic.get Type_base.var_id_atom; env = env#current; }; base_type >: mk (Tuple []) | `Int _ -> base_type >: mk Int | `Float _ -> base_type >: mk Float | `String _ -> base_type >: mk String | `Bool _ -> base_type >: mk Bool | `Custom h -> base_type >: mk h.handler.typ.Type.descr | `Encoder f -> (* Ensure that we only use well-formed terms. *) let rec check_enc (_, p) = List.iter (function | `Labelled (_, t) -> check ~level ~env t | `Anonymous _ -> () | `Encoder e -> check_enc e) p in check_enc f; let t = try !Hooks.type_of_encoder ~pos f with Not_found -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Unsupported_encoder (pos, Term.to_string e)) bt in base_type >: t | `List l -> List.iter (fun x -> check ~level ~env x) l; let t = Type.var ~level ?pos () in List.iter (fun e -> e.t <: t) l; base_type >: mk Type.(List { t; json_repr = `Tuple }) | `Tuple l -> List.iter (fun a -> check ~level ~env a) l; base_type >: mk (Type.Tuple (List.map (fun a -> a.t) l)) | `Null -> base_type >: mk (Type.Nullable (Type.var ~level ?pos ())) | `Cast { cast = a; typ = t } -> check ~level ~env a; a.t <: t; base_type >: t | `Hide (a, methods) -> check ~level ~env a; let ty = List.fold_left (fun ty name -> Type.make ?pos Type.( Meth ( { meth = name; optional = true; scheme = ([], Type.make ?pos Type.Never); doc = { meth_descr = ""; category = `Method }; json_name = None; }, ty ))) a.t methods in base_type >: ty | `Invoke { invoked = a; invoke_default; meth = l } -> check ~level ~env a; let rec aux t = match (Type.deref t).Type.descr with | Type.( Meth ({ meth = l'; scheme = (_, { descr }) as s; optional }, _)) when l = l' && (optional = false || descr = Never) -> (fst s, Typing.instantiate ~level s) | Type.(Meth (_, c)) -> aux c | _ -> (* We did not find the method, the type we will infer is not the most general one (no generalization), but this is safe and enough for records. *) let x = Type.var ~level ?pos () in let y = Type.var ~level ?pos () in a.t <: mk Type.( Meth ( { meth = l; optional = invoke_default <> None; scheme = ([], x); doc = { meth_descr = ""; category = `Method }; json_name = None; }, y )); ([], x) in let vars, typ = aux a.t in let typ = match (invoke_default, Type.deref typ) with | None, { descr = Never } -> raise (No_method (l, a.t)) | None, _ -> typ | Some v, _ -> ( check ~level ~env v; let v_t = Typing.instantiate ~level (vars, v.t) in match typ.Type.descr with | Never -> v_t | _ -> (* We want to make sure that: x?.foo types as: { foo?: 'a } *) let typ = match (Type.deref v.t).descr with | Type.Nullable _ -> mk Type.(Nullable typ) | _ -> typ in v_t <: typ; typ) in base_type >: typ | `Open (a, b) -> check ~level ~env a; a.t <: mk Type.unit; let rec aux env t = match (Type.deref t).Type.descr with | Type.(Meth ({ meth = l; scheme = g, u }, t)) -> aux (env#add ~pos l (g, u)) t | _ -> env in let env = aux env a.t in check ~level ~env b; base_type >: b.t | `Seq (a, b) -> check ~env ~level a; if not (can_ignore a.t) then ( let bt = Printexc.get_callstack 0 in throw ~bt (Ignored a)); check ~print_toplevel ~level ~env b; base_type >: b.t | `App (a, l) -> ( check ~level ~env a; List.iter (fun (_, b) -> check ~env ~level b) l; (* If [a] is known to have a function type, manually dig through it for better error messages. Otherwise generate its type and unify -- in that case the optionality can't be guessed and mandatory is the default. *) match (Type.demeth a.t).Type.descr with | Type.Arrow (ap, t) -> (* Find in l the first arg labeled lbl, return it together with the remaining of the list. *) let get_arg lbl l = let rec aux acc = function | [] -> None | (o, lbl', t) :: l -> if lbl = lbl' then Some (o, t, List.rev_append acc l) else aux ((o, lbl', t) :: acc) l in aux [] l in let _, ap = (* Remove the applied parameters, check their types on the fly. *) List.fold_left (fun (already, ap) (lbl, v) -> match get_arg lbl ap with | None -> let first = not (List.mem lbl already) in raise (No_label (a, lbl, first, v)) | Some (_, t, ap') -> (match (a.term, lbl) with | `Var "if", "then" | `Var "if", "else" -> ( match ( (Type.deref v.t).descr, (Type.deref t).descr ) with | Type.Arrow ([], vt), Type.Arrow ([], t) -> vt <: t | _ -> assert false) | _ -> v.t <: t); (lbl :: already, ap')) ([], ap) l in (* See if any mandatory argument is missing. *) let mandatory = List.filter_map (fun (o, l, t) -> if o then None else Some (l, t)) ap in if mandatory <> [] then raise (Term.Missing_arguments (pos, mandatory)); base_type >: t | _ -> let p = List.map (fun (lbl, b) -> (false, lbl, b.t)) l in a.t <: Type.make (Type.Arrow (p, base_type))) | `Fun p -> let env = match p.name with | None -> env | Some name -> env#add ~pos name ([], base_type) in check_fun ~env e p | `Var var -> let s = env#get ~pos var in base_type >: Typing.instantiate ~level s; if Lazy.force Term.debug then Printf.eprintf "Instantiate %s : %s becomes %s\n" var (Type.string_of_scheme s) (Type.to_string base_type) | `Let ({ pat; replace; def; body; _ } as l) -> check ~level:(level + 1) ~env def; let generalized = if value_restriction def then fst (generalize ~level def.t) else [] in let penv, pa = type_of_pat ~level ~pos pat in def.t <: pa; let env = List.fold_left (fun env (ll, a) -> match ll with | [] -> assert false | [x] -> let a = if replace then Type.remeth (snd (env#get ~pos x)) a else a in if !debug then Printf.printf "\nLET %s : %s\n%!" x (Repr.string_of_scheme (generalized, a)); env#add ~pos x (generalized, a) | l :: ll -> let g, t = env#get ~pos l in let a = (* If we are replacing the value, we keep the previous methods. *) if replace then Type.remeth (snd (Type.invokes t ll)) a else a in let invoke t l = try Type.invoke t l with Not_found -> raise (No_method (l, { t with Type.pos })) in env#override l (g, Type.meths ?pos ~invoke ll (generalized, a) t)) env penv in l.gen <- generalized; if print_toplevel then add_task (fun () -> Format.printf "@[<2>%s :@ %a@]@." (let name = string_of_pat pat in let l = String.length name and max = 5 in if l >= max then name else name ^ String.make (max - l) ' ') (fun f t -> Repr.print_scheme f (generalized, t)) def.t); check ~print_toplevel ~level ~env body; base_type >: body.t in e.t >: Methods.fold (fun meth meth_term t -> check ~level ~env meth_term; Type.make ?pos (Type.Meth ( { Type.meth; optional = false; scheme = Typing.generalize ~level meth_term.t; doc = { meth_descr = ""; category = `Method }; json_name = None; }, t ))) e.methods base_type let display_types = ref false (* The simple definition for external use. *) let check ?env ~check_top_level_override ~throw e = let print_toplevel = !display_types in try let env = match env with | Some env -> env | None -> Environment.default_typing_environment () in let top_level_variables = if check_top_level_override then List.map fst env else [] in let env = object (self) val env = env val top_level_variables = top_level_variables method current = env method check_top_level_override ~pos var = if List.mem var top_level_variables then ( let bt = Printexc.get_callstack 1 in throw ~bt (Top_level_override (var, pos))) method add ~pos var v = self#check_top_level_override ~pos var; self#override var v method override var v = { v <> var) top_level_variables>} method get ~pos var = try List.assoc var env with Not_found -> raise (Unbound (pos, var)) end in check ~print_toplevel ~throw ~level:0 ~env e; if print_toplevel && (Type.deref e.t).Type.descr <> Type.unit then add_task (fun () -> Format.printf "@[<2>- :@ %a@]@." Repr.print_type e.t); pop_tasks () with e -> let bt = Printexc.get_raw_backtrace () in pop_tasks (); Printexc.raise_with_backtrace e bt liquidsoap-2.4.2/src/lang/base/typechecking.mli000066400000000000000000000007711513273233300215020ustar00rootroot00000000000000exception No_method of string * Type.t exception Top_level_override of string * Pos.t option val debug : bool ref val display_types : bool ref val value_restriction : Term.t -> bool val add_task : (unit -> unit) -> unit val pop_tasks : unit -> unit val type_of_pat : level:int -> pos:Pos.Option.t -> Term.pattern -> (string list * Type.t) list * Type.t val check : ?env:Typing.env -> check_top_level_override:bool -> throw:(bt:Printexc.raw_backtrace -> exn -> unit) -> Term.t -> unit liquidsoap-2.4.2/src/lang/base/types/000077500000000000000000000000001513273233300174615ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/base/types/ref_type.ml000066400000000000000000000023351513273233300216330ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Type_base let reference ?pos a = let get = make ?pos (Arrow ([], a)) in let set = make ?pos (Arrow ([(false, "", a)], make ?pos unit)) in meth ?pos "set" ([], set) ~doc:"Set the value of the reference." get liquidsoap-2.4.2/src/lang/base/types/type_base.ml000066400000000000000000000335701513273233300217760ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Show debugging information. *) let debug = ref (Sys.getenv_opt "LIQUIDSOAP_DEBUG_LANG" <> None) (** Show variables levels. *) let debug_levels = ref false let debug_variance = ref false (** Type information comes attached to the AST from the parsing, with appropriate sharing of the type variables. Then the type inference performs in-place unification. In order to report precise type error messages, we put very dense parsing location information in the type. Every layer of it can have a location. Destructive unification introduces links in such a way that the old location is still accessible. The level annotation represents the number of abstractions which surround the type in the AST -- function arguments and let-in definitions. It is used to safely generalize types. Finally, constraints can be attached to existential (unknown, '_a) and universal ('a) type variables. *) (** {2 Types} *) (** Type description *) type variance = [ `Covariant | `Invariant ] type 'a argument = bool * string * 'a module R = struct type 'a meth = { name : string; optional : bool; scheme : 'a var list * 'a t; json_name : string option; } and 'a t = [ `Constr of string * (variance * 'a t) list | `List of 'a t * [ `Object | `Tuple ] | `Tuple of 'a t list | `Nullable of 'a t | `Meth of 'a meth * 'a t (* label, type scheme, JSON name, base type *) | `Arrow of 'a t argument list * 'a t | `Getter of 'a t | `EVar of 'a var (* existential variable *) | `UVar of 'a var (* universal variable *) | `Ellipsis (* omitted sub-term *) | `Range_Ellipsis (* omitted sub-terms (in a list, e.g. list of args) *) | `Debug of string * 'a t * string (* add annotations before / after, mostly used for debugging *) ] and 'a var = string * 'a Type_constraints.t end type custom type meth_doc = { meth_descr : string; category : [ `Method | `Callback ] } type t = { pos : Pos.Option.t; descr : descr } and constr = { constr_descr : string; univ_descr : string option; satisfied : subtype:(t -> t -> unit) -> satisfies:(t -> unit) -> t -> unit; } (** A type constructor applied to arguments (e.g. source). *) and constructed = { constructor : string; params : (variance * t) list } (** Contents of a variable. *) and var = { name : int; mutable level : int; mutable constraints : constr Type_constraints.t; } and invar = | Free of var (** the variable is free *) | Link of variance * t (** the variable has bee substituted *) (** A type scheme (i.e. a type with universally quantified variables). *) and scheme = var list * t (** A method. *) and meth = { meth : string; (** name of the method *) optional : bool; (** is the method optional? *) scheme : scheme; (** type scheme *) doc : meth_doc; (** documentation *) json_name : string option; (** name when represented as JSON *) } and repr_t = { t : t; json_repr : [ `Tuple | `Object ] } and custom_handler = { typ : custom; custom_name : string; copy_with : (t -> t) -> custom -> custom; occur_check : (t -> unit) -> custom -> unit; filter_vars : (var list -> t -> var list) -> var list -> custom -> var list; repr : (var list -> t -> constr R.t) -> var list -> custom -> constr R.t; subtype : (t -> t -> unit) -> custom -> custom -> unit; sup : (t -> t -> t) -> custom -> custom -> custom; to_string : custom -> string; } and var_t = { id : int; mutable contents : invar } and descr = | String | Int | Float | Bool | Never | Custom of custom_handler | Constr of constructed | Getter of t (** a getter: something that is either a t or () -> t *) | List of repr_t | Tuple of t list | Nullable of t (** something that is either t or null *) | Meth of meth * t (** t with a method added *) | Arrow of t argument list * t (** a function *) | Var of var_t (** a type variable *) module Constraints = struct include Type_constraints type nonrec t = constr Type_constraints.t end module DS = Set.Make (struct type nonrec t = string * Constraints.t let compare (s, v) (s', v') = match Stdlib.compare s s' with 0 -> Constraints.compare v v' | x -> x end) let string_of_constr c = c.constr_descr exception NotImplemented exception Exists of Pos.Option.t * string exception Unsatisfied_constraint let unit = Tuple [] (** Operations on variables. *) module Var = struct type t = var (** Compare two variables for equality. This comparison should always be used to compare variables (as opposed to =). *) let eq v v' = v.name = v'.name let compare v v' = compare v.name v'.name end (** Sets of variables. *) module Vars = struct include Set.Make (Var) let add_list l v = add_seq (List.to_seq l) v end (** Create a type from its value. *) let make ?pos d = { pos; descr = d } (** Dereferencing gives you the meaning of a term, going through links created by instantiations. One should (almost) never work on a non-dereferenced type. *) let rec deref t = match t.descr with Var { contents = Link (_, t) } -> deref t | _ -> t (** Remove methods. This function also removes links. *) let rec demeth t = let t = deref t in match t.descr with Meth (_, t) -> demeth t | _ -> t let rec filter_meths t fn = let t = deref t in match t.descr with | Meth (m, t) when not (fn m) -> filter_meths t fn | Meth (m, t) -> { t with descr = Meth (m, filter_meths t fn) } | _ -> t let rec map_meths t fn = let t = deref t in match t.descr with | Meth (m, t) -> { t with descr = Meth (fn m, map_meths t fn) } | _ -> t (** Put the methods of the first type around the second type. *) let rec remeth t u = let t = deref t in match t.descr with | Meth (m, t) -> { t with descr = Meth (m, remeth t u) } | _ -> u (** Type of a method in a type. *) let rec invoke t l = match (deref t).descr with | Meth (m, _) when m.meth = l -> m.scheme | Meth (_, t) -> invoke t l | _ -> raise Not_found (** Do we have a method with given label? *) let has_meth t l = try ignore (invoke t l); true with Not_found -> false (** Type of a submethod in a type. *) let rec invokes t = function | l :: ll -> let g, t = invoke t l in if ll = [] then (g, t) else invokes t ll | [] -> ([], t) (** Add a method to a type. *) let meth ?pos ?json_name ?(category = `Method) ?(optional = false) meth scheme ?(doc = "") t = make ?pos (Meth ( { meth; optional; scheme; doc = { meth_descr = doc; category }; json_name; }, t )) (** Add a submethod to a type. *) let rec meths ?(invoke = invoke) ?pos l v t = match l with | [] -> assert false | [l] -> meth ?pos l v t | l :: ll -> let g, tl = invoke t l in let v = meths ?pos ~invoke ll v tl in meth ?pos l (g, v) t (** Split the methods from the type. *) let split_meths t = let rec aux hide t = let t = deref t in match t.descr with | Meth (m, t) -> let meth, t = aux (m.meth :: hide) t in let meth = if List.mem m.meth hide then meth else m :: meth in (meth, t) | _ -> ([], t) in aux [] t (** Create a fresh variable. *) let var_name_atom = Atomic.make (-1) let var_name () = Atomic.fetch_and_add var_name_atom 1 let var_id_atom = Atomic.make (-1) let var_id () = Atomic.fetch_and_add var_id_atom 1 let var ?(constraints = []) ?(level = max_int) ?pos () = let constraints = Constraints.of_list constraints in let name = var_name () in make ?pos (Var { id = var_id (); contents = Free { name; level; constraints } }) module Fresh = struct type mapper = { level : int option; preserve_positions : bool; selector : var -> bool; var_maps : (var, var) Hashtbl.t; link_maps : (int, var_t) Hashtbl.t; } let init ?(preserve_positions = false) ?(selector = fun _ -> true) ?level () = { level; preserve_positions; selector; var_maps = Hashtbl.create 10; link_maps = Hashtbl.create 10; } let make_var { level; selector; var_maps } var = if not (selector var) then var else ( try Hashtbl.find var_maps var with Not_found -> let level = Option.value ~default:var.level level in let new_var = { var with name = var_name (); level } in Hashtbl.replace var_maps var new_var; new_var) let make ({ preserve_positions; selector; link_maps } as h) t = let map_var = make_var h in let map_descr map = function | Int -> Int | Float -> Float | String -> String | Bool -> Bool | Never -> Never | Custom c -> Custom { c with typ = c.copy_with map c.typ } | Constr { constructor; params } -> Constr { constructor; params = List.map (fun (v, t) -> (v, map t)) params } | Getter t -> Getter (map t) | List { t; json_repr } -> List { t = map t; json_repr } | Tuple l -> Tuple (List.map map l) | Nullable t -> Nullable (map t) | Meth ({ meth; optional; scheme = vars, t; doc; json_name }, t') -> Meth ( { meth; optional; scheme = (List.map map_var vars, map t); doc; json_name; }, map t' ) | Arrow (args, t) -> Arrow (List.map (fun (b, s, t) -> (b, s, map t)) args, map t) (* Here we keep all links. While it could be tempting to deref, we are using links to compute type supremum in type unification so we are better off keeping them. Also, we need to create fresh links to make sure that a suppremum computation in the refreshed type does not impact the original type. *) | Var { id; contents = Link (v, t) } -> Var (try Hashtbl.find link_maps id with Not_found -> let new_link = { id = var_id (); contents = Link (v, map t) } in Hashtbl.replace link_maps id new_link; new_link) | Var { id; contents = Free var } as descr -> if not (selector var) then descr else Var (try Hashtbl.find link_maps id with Not_found -> let new_link = { id = var_id (); contents = Free (map_var var) } in Hashtbl.replace link_maps id new_link; new_link) in let rec map { pos; descr } = { pos = (if preserve_positions then pos else None); descr = map_descr map descr; } in map t end let fresh t = Fresh.make (Fresh.init ()) t let to_string_fun = ref (fun ?(generalized : var list option) _ -> ignore generalized; failwith "Type.to_string not defined yet") (** String representation of a type. *) let to_string ?generalized (t : t) : string = !to_string_fun ?generalized t let string_of_scheme (g, t) = to_string ~generalized:g t let is_fun t = match (demeth t).descr with Arrow _ -> true | _ -> false let is_source t = match (demeth t).descr with | Constr { constructor = "source"; _ } -> true | _ -> false let custom_types : (string, unit -> t) Hashtbl.t = Hashtbl.create 10 let register_type name custom = let mk_typ = match Hashtbl.find_opt custom_types name with | Some mk_typ -> fun () -> remeth (mk_typ ()) (custom ()) | None -> custom in Hashtbl.replace custom_types name mk_typ let register_type name custom = match String.split_on_char '.' name with | [] -> assert false | name :: [] -> register_type name custom | root :: names -> let default_mk_typ () = make unit in let root_mk_typ = Option.value ~default:default_mk_typ (Hashtbl.find_opt custom_types root) in let rec f root_typ = function | [] -> assert false | name :: [] -> meth name ([], custom ()) root_typ | name :: names -> let typ = try snd (invoke root_typ name) with _ -> default_mk_typ () in meth name ([], f typ names) root_typ in Hashtbl.replace custom_types root (fun () -> f (root_mk_typ ()) names) let find_opt_typ = Hashtbl.find_opt custom_types let rec mk_invariant t = match t with | { descr = Var ({ contents = Link (_, t) } as c) } -> c.contents <- Link (`Invariant, t); mk_invariant t | _ -> () let rec hide_meth l a = match (deref a).descr with | Meth ({ meth = l' }, u) when l' = l -> hide_meth l u | Meth (m, u) -> make ?pos:a.pos (Meth (m, hide_meth l u)) | _ -> a let rec opt_meth l a = match (deref a).descr with | Meth (({ meth = l' } as m), u) when l' = l -> make ?pos:a.pos (Meth ({ m with optional = true }, u)) | Meth (m, u) -> make ?pos:a.pos (Meth (m, opt_meth l u)) | _ -> a let rec get_meth l a = match (deref a).descr with | Meth (({ meth = l' } as meth), _) when l = l' -> meth | Meth (_, a) -> get_meth l a | _ -> assert false liquidsoap-2.4.2/src/lang/base/types/type_constraints.ml000066400000000000000000000026311513273233300234250ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type 'a t = 'a list let create () = [] let add el set = el :: List.filter (fun e -> e != el) set let mem = List.memq let of_list l = List.fold_left (fun ret el -> if List.memq el ret then ret else el :: ret) [] l let compare = Stdlib.compare let elements l = l let cardinal = List.length let choose = function [] -> raise Not_found | x :: _ -> x let is_empty = function [] -> true | _ -> false liquidsoap-2.4.2/src/lang/base/types/type_constraints.mli000066400000000000000000000024171513273233300236000ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type 'a t val create : unit -> 'a t val add : 'a -> 'a t -> 'a t val mem : 'a -> 'a t -> bool val compare : 'a t -> 'a t -> int val of_list : 'a list -> 'a t val elements : 'a t -> 'a list val cardinal : 'a t -> int val choose : 'a t -> 'a val is_empty : 'a t -> bool liquidsoap-2.4.2/src/lang/base/types/type_custom.ml000066400000000000000000000051221513273233300223660ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Type_base type custom = Type_base.custom module type Specs = sig type content val name : string val copy_with : (t -> t) -> content -> content val occur_check : (t -> unit) -> content -> unit val filter_vars : (var list -> t -> var list) -> var list -> content -> var list val repr : (var list -> t -> Repr.t) -> var list -> content -> Repr.t val subtype : (t -> t -> unit) -> content -> content -> unit val sup : (t -> t -> t) -> content -> content -> content val to_string : content -> string end module type Implementation = sig type content val handler : content -> Type_base.custom_handler val to_content : custom -> content end let custom_types = ref [] module Make (S : Specs) = struct type content = S.content let () = if List.mem S.name !custom_types then failwith "custom type exist!"; custom_types := S.name :: !custom_types let to_custom : content -> custom = Obj.magic let to_content : custom -> content = Obj.magic let copy_with fn v = to_custom (S.copy_with fn (to_content v)) let occur_check fn v = S.occur_check fn (to_content v) let filter_vars fn vars v = S.filter_vars fn vars (to_content v) let repr fn vars v = S.repr fn vars (to_content v) let subtype fn v v' = S.subtype fn (to_content v) (to_content v') let sup fn v v' = to_custom (S.sup fn (to_content v) (to_content v')) let to_string v = S.to_string (to_content v) let handler v = { typ = to_custom v; custom_name = S.name; copy_with; occur_check; filter_vars; repr; subtype; sup; to_string; } end liquidsoap-2.4.2/src/lang/base/types/type_custom.mli000066400000000000000000000033541513273233300225440ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Type_base type custom = Type_base.custom module type Specs = sig type content val name : string val copy_with : (t -> t) -> content -> content val occur_check : (t -> unit) -> content -> unit val filter_vars : (var list -> t -> var list) -> var list -> content -> var list val repr : (var list -> t -> Repr.t) -> var list -> content -> Repr.t val subtype : (t -> t -> unit) -> content -> content -> unit val sup : (t -> t -> t) -> content -> content -> content val to_string : content -> string end module type Implementation = sig type content val handler : content -> Type_base.custom_handler val to_content : custom -> content end module Make (S : Specs) : Implementation with type content = S.content liquidsoap-2.4.2/src/lang/base/typing.ml000066400000000000000000000552601513273233300201710ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Typing. *) open Type let () = Type.debug := false let () = Type.debug_levels := false let () = Type.debug_variance := false let () = Repr.global_evar_names := false let debug_subtyping = ref false (** Allow functions to forget arguments during subtyping. This would not be a good idea if we had de Bruijn indices for instance. *) let forget_arguments = true type env = (string * scheme) list (** {1 Type generalization and instantiation} We don't have type schemes per se, but we compute generalizable variables and keep track of them in the AST. This is simple and useful because in any case we need to distinguish two 'a variables bound at different places. Indeed, we might instantiate one in a term where the second is bound, and we don't want to merge the two when going under the binder. When generalizing we need to know what can be generalized in the outermost type but also in the inner types of the term forming a let-definition. Indeed those variables will have to be instantiated by fresh ones for every instance. If the value restriction applies, then we have some (fun (...) -> ...) and any type variable of higher level can be generalized, whether it's in the outermost type or not. *) (** Find all the free variables satisfying a predicate. *) let filter_vars f t = let rec aux l t = let t = deref t in match t.descr with | Int | Float | String | Bool | Never -> l | Custom c -> c.filter_vars aux l c.typ | Getter t -> aux l t | List { t } | Nullable t -> aux l t | Tuple aa -> List.fold_left aux l aa | Meth ({ scheme = g, t }, u) -> let l = List.filter (fun v -> not (List.mem v g)) (aux l t) in aux l u | Constr c -> List.fold_left (fun l (_, t) -> aux l t) l c.params | Arrow (p, t) -> aux (List.fold_left (fun l (_, _, t) -> aux l t) l p) t | Var { contents = Free var } -> if f var && not (List.exists (Var.eq var) l) then var :: l else l | Var { contents = Link _ } -> assert false in aux [] t (** Return a list of generalizable variables in a type. This is performed after type inference on the left-hand side of a let-in, with [level] being the level of that let-in. Uses the simple method of ML, to be associated with a value restriction. *) let generalizable ~level t = filter_vars (fun v -> v.level > level) t let generalize ~level t : scheme = (generalizable ~level t, t) (** Instantiate a type scheme, given as a type together with a list of generalized variables. This erases position information, since they usually become irrelevant. *) let instantiate ~level ((generalized, t) : scheme) = if generalized = [] then t else ( let mapper = Type.Fresh.init ~selector:(fun v -> List.memq v generalized) ~level () in Type.Fresh.make mapper t) (** {1 Assignation} *) (** This exception can be raised when attempting to assign a variable. *) exception Occur_check of var * t (** Check that [a] (a dereferenced type variable) does not occur in [b] and prepare the instantiation [a<-b] by adjusting the levels. *) let occur_check (a : var) = let rec occur_check = function | { descr = Int } | { descr = Float } | { descr = String } | { descr = Bool } | { descr = Never } -> () | { descr = Constr c } -> List.iter (fun (_, x) -> occur_check x) c.params | { descr = Tuple l } -> List.iter occur_check l | { descr = Getter t } -> occur_check t | { descr = List { t } } -> occur_check t | { descr = Nullable t } -> occur_check t | { descr = Meth ({ scheme = g, t }, u) } -> (* We assume that a is not a generalized variable of t. *) (* TODO: we should not lower the level of bound variables, but this complicates the code and has little effect. *) assert (not (List.exists (Var.eq a) g)); occur_check t; occur_check u | { descr = Arrow (p, t) } -> List.iter (fun (_, _, t) -> occur_check t) p; occur_check t | { descr = Custom c } -> c.occur_check occur_check c.typ | { descr = Var { contents = Free x } } as b -> if Type.Var.eq a x then raise (Occur_check (a, b)); x.level <- min a.level x.level | { descr = Var { contents = Link (_, b) } } -> occur_check b in occur_check let do_occur_check = ref true let occur_check a t = if !do_occur_check then occur_check a t (** Lower all type variables to given level. *) let update_level level a = let x = Type.var ~level () in let x = match x.descr with Var { contents = Free x } -> x | _ -> assert false in occur_check x a (** {1 Subtype checking/inference} *) exception Incompatible (** Approximated supremum of two types. We grow the second argument so that it has a chance be be greater than the first. No binding is performed by this function so that it should always be followed by a subtyping. *) let rec sup ~pos a b = (* Printf.printf " sup: %s \\/ %s\n%!" (Type.to_string a) (Type.to_string b); *) let sup = sup ~pos in let mk descr = Type.make ?pos descr in let scheme_sup t t' = match (t, t') with ([], t), ([], t') -> ([], sup t t') | _ -> t' in let rec meth_type l a = match (deref a).descr with | Meth ({ meth = l'; optional; scheme = t }, _) when l = l' -> Some (t, optional) | Meth (_, a) -> meth_type l a | _ -> None in let meth_sup m a b = let a = hide_meth m.meth a in let mb = meth_type m.meth b in let b = hide_meth m.meth b in match mb with | Some (t', optional) -> ( try mk (Meth ( { m with optional = m.optional || optional; scheme = scheme_sup t' m.scheme; }, sup a b )) with Incompatible -> sup a b) | None -> mk (Meth ({ m with optional = true }, sup a b)) in let a = deref a in let b = deref b in if a == b then a else ( match (a.descr, b.descr) with | v, v' when v == v' -> a | Var { contents = Free _ }, _ -> b | _, Var { contents = Free _ } -> a | Nullable a, Nullable b -> mk (Nullable (sup a b)) | Nullable a, _ -> mk (Nullable (sup a b)) | _, Nullable b -> mk (Nullable (sup a b)) | List { t = a }, List { t = b } -> mk (List { t = sup a b; json_repr = `Tuple }) | Arrow (p, a), Arrow (q, b) -> if List.length p <> List.length q then raise Incompatible; mk (Arrow (q, sup a b)) | Tuple l, Tuple m -> if List.length l <> List.length m then raise Incompatible; mk (Tuple (List.map2 sup l m)) | Custom c, Custom c' -> ( try mk (Custom { c with typ = c.sup sup c.typ c'.typ }) with _ -> raise Incompatible) | Meth (m, a), _ -> meth_sup m a b | _, Meth (m, b) -> meth_sup m b a | Constr { constructor = "source" }, _ | _, Constr { constructor = "source" } | Constr { constructor = "format" }, _ | _, Constr { constructor = "format" } -> b | ( Constr { constructor = c; params = a }, Constr { constructor = d; params = b } ) -> if c <> d || List.length a <> List.length b then raise Incompatible; let params = List.map2 (fun (v, a) (v', b) -> if v <> v' then raise Incompatible; (v, sup a b)) a b in mk (Constr { constructor = c; params }) | Getter a, Getter b -> mk (Getter (sup a b)) | Getter a, Arrow ([], b) -> mk (Getter (sup a b)) | Getter a, _ -> mk (Getter (sup a b)) | Arrow ([], a), Getter b -> mk (Getter (sup a b)) | _, Getter b -> mk (Getter (sup a b)) | _, _ -> if !debug_subtyping then failwith (Printf.sprintf "\nFailed sup: %s \\/ %s\n\n%!" (Type.to_string a) (Type.to_string b)) else raise Incompatible) let sup ~pos a b = let b' = sup ~pos a b in if !debug_subtyping && b' != b then Printf.printf "sup: %s \\/ %s = %s\n%! " (Type.to_string a) (Type.to_string b) (Type.to_string b'); b' exception Error of (Repr.t * Repr.t) let () = Printexc.register_printer (function | Error (a, b) -> Some (Printf.sprintf "Typing error: %s vs %s" (Repr.to_string a) (Repr.to_string b)) | _ -> None) (** Ensure that a type satisfies a given constraint, i.e. morally that b <: c. *) let rec satisfies_constraint b c = match (deref b).descr with | Var { contents = Free v } -> v.constraints <- Constraints.add c v.constraints | _ -> c.satisfied ~subtype:( <: ) ~satisfies:(fun b -> satisfies_constraint b c) b and satisfies_constraints b = List.iter (satisfies_constraint b) (** Make a variable link to given type. *) and bind ?(variance = `Invariant) a b = let a0 = a in let v, a = match a.descr with | Var ({ contents = Free a } as v) -> (v, a) | _ -> assert false in if !debug then Printf.printf "\n%s := %s\n%!" (Type.to_string a0) (Type.to_string b); let b = deref b in occur_check a b; (* update_level a.level b; *) satisfies_constraints b (Constraints.elements a.constraints); let b = if b.pos = None then Type.make ?pos:a0.pos b.Type.descr else b in v.contents <- Link (variance, b) (** Ensure that the type for the method [l] in [a] is a subtype of the one for the same method in [b]. *) and unify_meth a b l = let { optional = optional1; meth = meth1; scheme = s1; json_name = json_name1; } = get_meth l a in let { optional = optional2; meth = meth2; scheme = s2; json_name = json_name2; } = get_meth l b in assert (meth1 = l && meth2 = l); (* Handle explicitly this case in order to avoid #1842. *) ((* We want to allow: - {foo:int?} <: {foo?:int} - {foo?:int?} <: {foo?:int} - {foo?:never} <: {foo?:int} and prohibit: - {foo?:int} <: {foo:int?} *) let s1 = match (optional1, optional2, (deref (snd s1)).descr) with | true, true, Never -> s2 | _, true, Nullable t -> (fst s1, t) | true, false, _ -> raise (Error (Repr.make a, Repr.make b)) | _ -> s1 in (* TODO: we should perform proper type scheme subtyping, but this is a good approximation for now... *) try instantiate ~level:(-1) s1 <: instantiate ~level:(-1) s2 with Error (a, b) -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Error ( `Meth ( R. { name = l; optional = optional1; scheme = ([], a); json_name = json_name1; }, `Ellipsis ), `Meth ( R. { name = l; optional = optional2; scheme = ([], b); json_name = json_name2; }, `Ellipsis ) )) bt); try hide_meth l a <: hide_meth l b with Error (a, b) -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Error ( `Meth ( R. { name = l; optional = optional1; scheme = ([], `Ellipsis); json_name = json_name1; }, a ), `Meth ( R. { name = l; optional = optional2; scheme = ([], `Ellipsis); json_name = json_name2; }, b ) )) bt (** Ensure that a<:b, perform unification if needed. In case of error, generate an explanation. We recall that A <: B means that any value of type A can be passed where a value of type B can. This relation must be transitive. *) and ( <: ) a b = if !debug || !debug_subtyping then Printf.printf "\n%s <: %s\n%!" (Type.to_string a) (Type.to_string b); if a != b then ( match (a.descr, b.descr) with | a, b when a == b -> () | Var { contents = Free v }, Var { contents = Free v' } when Var.eq v v' -> () | _, Var ({ contents = Link (`Covariant, b') } as var) -> (* When the variable is covariant, we take the opportunity here to correct bad choices. For instance, if we took int, but then have a 'a?, we change our mind and use int? instead. *) let b'' = try sup ~pos:b'.pos a b' with Incompatible -> b' in (try b' <: b'' (* The sup is allowed to return something invalid. See: https://github.com/savonet/liquidsoap/pull/3472 *) with e when !debug -> failwith (Printf.sprintf "invalid sup: %s !< %s (%s)" (Type.to_string b') (Type.to_string b'') (Printexc.to_string e))); if b'' != b' then var.contents <- Link (`Covariant, b''); a <: b'' | Var ({ contents = Link (`Covariant, a') } as var), _ -> var.contents <- Link (`Invariant, a'); a <: b | _, Var { contents = Link (_, b) } -> a <: b | Var { contents = Link (_, a) }, _ -> a <: b | Constr c1, Constr c2 when c1.constructor = c2.constructor -> let rec aux pre p1 p2 = match (p1, p2) with | (v1, h1) :: t1, (v2, h2) :: t2 -> begin try let v = if v1 = v2 then v1 else `Invariant in match v with | `Covariant -> h1 <: h2 | `Invariant -> mk_invariant h2; h1 <: h2; mk_invariant h2 with Error (a, b) -> let bt = Printexc.get_raw_backtrace () in let post = List.map (fun (v, _) -> (v, `Ellipsis)) t1 in Printexc.raise_with_backtrace (Error ( `Constr (c1.constructor, pre @ [(v1, a)] @ post), `Constr (c1.constructor, pre @ [(v2, b)] @ post) )) bt end; aux ((v1, `Ellipsis) :: pre) t1 t2 | [], [] -> () | _ -> assert false (* same name => same arity *) in aux [] c1.params c2.params | List { t = t1; json_repr = repr1 }, List { t = t2; json_repr = repr2 } -> ( try t1 <: t2 with Error (a, b) -> raise (Error (`List (a, repr1), `List (b, repr2)))) | Nullable t1, Nullable t2 -> ( try t1 <: t2 with Error (a, b) -> raise (Error (`Nullable a, `Nullable b))) | Tuple l, Tuple m -> if List.length l <> List.length m then ( let l = List.map (fun _ -> `Ellipsis) l in let m = List.map (fun _ -> `Ellipsis) m in raise (Error (`Tuple l, `Tuple m))); let n = ref 0 in List.iter2 (fun a b -> incr n; try a <: b with Error (a, b) -> let bt = Printexc.get_raw_backtrace () in let l = List.init (!n - 1) (fun _ -> `Ellipsis) in let l' = List.init (List.length m - !n) (fun _ -> `Ellipsis) in Printexc.raise_with_backtrace (Error (`Tuple (l @ [a] @ l'), `Tuple (l @ [b] @ l'))) bt) l m | Arrow (l12, t), Arrow (l, t') -> (* Here, it must be that l12 = l1@l2 where l1 is essentially l modulo order and either l2 is erasable and t<:t'. *) let ellipsis = (false, "", `Range_Ellipsis) in let elide (o, l, _) = (o, l, `Ellipsis) in let l1, l2 = List.fold_left (* Start with [l2:=l12], [l1:=[]] and move each param [o,lbl] required by [l] from [l2] to [l1]. *) (fun (l1, l2) (o, lbl, t) -> (* Search for a param with label lbl. Returns the first matching parameter and the list without it. *) let rec get_param acc = function | [] -> raise (Error ( `Arrow ( List.rev_append l1 (List.map elide l2), `Ellipsis ), `Arrow ( List.rev (ellipsis :: (o, lbl, `Ellipsis) :: l1), `Ellipsis ) )) | (o', lbl', t') :: tl -> if lbl = lbl' then ((o', lbl', t'), List.rev_append acc tl) else get_param ((o', lbl', t') :: acc) tl in let (o', lbl, t'), l2' = get_param [] l2 in (* Check on-the-fly that the types match. *) begin try if (not o') && o then raise (Error (`Ellipsis, `Ellipsis)); t <: t' with Error (t, t') -> let bt = Printexc.get_raw_backtrace () in let make o t = `Arrow (List.rev (ellipsis :: (o, lbl, t) :: l1), `Ellipsis) in Printexc.raise_with_backtrace (Error (make o' t', make o t)) bt end; ((o, lbl, `Ellipsis) :: l1, l2')) ([], l12) l in let l1 = List.rev l1 in ignore l1; if l2 = [] || (forget_arguments && List.for_all (fun (o, _, _) -> o) l2) then ( try t <: t' with Error (t, t') -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Error (`Arrow ([ellipsis], t), `Arrow ([ellipsis], t'))) bt) else ( let l2 = List.map (fun (o, l, t) -> (o, l, Repr.make t)) l2 in raise (Error ( `Arrow (l2 @ [ellipsis], `Ellipsis), `Arrow ([ellipsis], `Ellipsis) ))) | Custom c, Custom c' -> ( try c.subtype ( <: ) c.typ c'.typ with _ -> raise (Error (Repr.make a, Repr.make b))) | Getter t1, Getter t2 -> ( try t1 <: t2 with Error (a, b) -> raise (Error (`Getter a, `Getter b))) | Arrow ([], t1), Getter t2 -> ( try t1 <: t2 with Error (a, b) -> raise (Error (`Arrow ([], a), `Getter b))) | Never, Var { contents = Free _ } | Var { contents = Free _ }, Never -> raise (Error (Repr.make a, Repr.make b)) | Var { contents = Free _ }, _ -> ( try bind a b with Occur_check _ | Unsatisfied_constraint -> (* Can't do more concise than a full representation, as the problem isn't local. *) raise (Error (Repr.make a, Repr.make b))) | _, Var { contents = Free _ } -> ( try bind ~variance:`Covariant b a with Occur_check _ | Unsatisfied_constraint -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Error (Repr.make a, Repr.make b)) bt) | _, Nullable t2 -> ( try a <: t2 with Error (a, b) -> raise (Error (a, `Nullable b))) | Meth ({ meth = l }, _), _ when Type.has_meth b l -> unify_meth a b l | _, Meth ({ meth = l }, _) when Type.has_meth a l -> unify_meth a b l | _, Meth ({ meth = l; optional; scheme = g2, t2; json_name }, c) -> ( let a' = demeth a in match a'.descr with | Var { contents = Free _ } -> let optional, t2 = match (deref t2).descr with | Type.(Nullable t) -> (true, t) | _ -> (optional, t2) in a' <: make (Meth ( { meth = l; optional; scheme = (g2, t2); doc = { meth_descr = ""; category = `Method }; json_name = None; }, var () )); a <: b | _ when optional || (deref t2).descr = Never -> a <: hide_meth l c | _ -> raise (Error ( Repr.make a, `Meth ( R. { name = l; optional; scheme = ([], `Ellipsis); json_name; }, `Ellipsis ) ))) | Meth (m, u1), _ -> opt_meth m.meth u1 <: b | _, Getter t2 -> ( try a <: t2 with Error (a, b) -> raise (Error (a, `Getter b))) | _, _ -> (* The superficial representation is enough for explaining the mismatch. *) let filter () = let already = ref false in function | { descr = Var { contents = Link _ }; _ } -> false | _ -> let x = !already in already := true; x in let a = Repr.make ~filter_out:(filter ()) a in let b = Repr.make ~filter_out:(filter ()) b in raise (Error (a, b))) let ( >: ) a b = try b <: a with Error (y, x) -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Repr.Type_error (true, b, a, y, x)) bt let ( <: ) a b = try a <: b with Error (x, y) -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace (Repr.Type_error (false, a, b, x, y)) bt liquidsoap-2.4.2/src/lang/base/typing.mli000066400000000000000000000036571513273233300203450ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** {1 Functions for typing} *) open Type (** Print debugging messages for subtyping. *) val debug_subtyping : bool ref (** Find all the free variables satisfying a predicate. *) val filter_vars : (var -> bool) -> t -> var list (** A typing environment. *) type env = (string * scheme) list (** Instantiate a type. *) val instantiate : level:int -> scheme -> t (** Find all generalizable variables. *) val generalize : level:int -> t -> scheme (** Lower all type variables to given level. *) val update_level : int -> t -> unit (** Subtyping. *) val ( <: ) : t -> t -> unit (** Suptyping. *) val ( >: ) : t -> t -> unit (** Supremeum of two types. *) val sup : pos:Pos.Option.t -> t -> t -> t (** Bind a variable *) val bind : ?variance:Type.variance -> t -> t -> unit (** Ensure that a type satisfies a given constraint, i.e. morally that b <: c. *) val satisfies_constraint : t -> constr -> unit val do_occur_check : bool ref liquidsoap-2.4.2/src/lang/base/unifier.ml000066400000000000000000000033621513273233300203140ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (* Simple unification module for variables with no unknown value *) type 'a t = [ `Value of 'a | `Link of 'a t Atomic.t ] let make v = `Link (Atomic.make (`Value v)) let rec deref x = match x with `Value v -> v | `Link x -> deref (Atomic.get x) let set x v = let rec f x = match Atomic.get x with | `Value _ -> Atomic.set x (`Value v) | `Link x -> f x in match x with `Value _ -> assert false | `Link x -> f x let ( <-- ) x x' = let rec f x x' = match (Atomic.get x, Atomic.get x') with | `Link x, _ -> f x x' | _, `Link x' -> f x x' | _ when x != x' -> Atomic.set x (`Link x') | _ -> () in match (x, x') with | `Value _, _ | _, `Value _ -> assert false | `Link x, `Link x' -> f x x' liquidsoap-2.4.2/src/lang/base/unifier.mli000066400000000000000000000021721513273233300204630ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type 'a t val make : 'a -> 'a t val deref : 'a t -> 'a val set : 'a t -> 'a -> unit val ( <-- ) : 'a t -> 'a t -> unit liquidsoap-2.4.2/src/lang/base/utils.ml000066400000000000000000000044041513273233300200110ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (* Resolve a path. *) let resolve_path ?current_dir path = match path with | "-" -> "-" | path -> let path = Lang_string.home_unrelate path in let current_dir = match current_dir with | None -> Sys.getcwd () | Some current_dir -> current_dir in if Filename.is_relative path then Filename.concat current_dir path else path let readable f = try let c = open_in f in close_in c; true with _ -> false let check_readable ?current_dir ~pos path = match path with | "-" -> "-" | path -> let resolved_path = resolve_path ?current_dir path in let details = if path = resolved_path then "Given path: " ^ path else "Given path: " ^ path ^ ", resolved path: " ^ resolved_path in if not (Sys.file_exists resolved_path) then Runtime_error.raise ~pos ~message:("File not found! " ^ details) "not_found"; if not (readable resolved_path) then Runtime_error.raise ~pos ~message:("File is not readable!" ^ details) "not_found"; resolved_path let string_of_float f = let s = string_of_float f in if s.[String.length s - 1] = '.' then s ^ "0" else s liquidsoap-2.4.2/src/lang/base/value.ml000066400000000000000000000337141513273233300177730ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (** Values are untyped normal forms of terms. *) open Term_hash module Custom = Term.Custom module Methods = Runtime_term.Methods (** We derive a hash of the environment to invalidate the cache when the builtin env change. We mostly keep name and methods. *) type env = (string * t) list and dynamic_methods = { hidden_methods : string list; methods : string -> t option; [@hash.ignore] } and t = | Int of { pos : Pos.Option.t; [@hash.ignore] value : int; methods : t Methods.t; mutable flags : Flags.flags; [@hash.ignore] } | Float of { pos : Pos.Option.t; [@hash.ignore] value : float; methods : t Methods.t; } | String of { pos : Pos.Option.t; [@hash.ignore] value : string; methods : t Methods.t; mutable flags : Flags.flags; [@hash.ignore] } | Bool of { pos : Pos.Option.t; [@hash.ignore] value : bool; methods : t Methods.t; } | Null of { pos : Pos.Option.t; [@hash.ignore] methods : t Methods.t } | Custom of { pos : Pos.Option.t; [@hash.ignore] value : Custom.t; [@hash.ignore] methods : t Methods.t; dynamic_methods : dynamic_methods option; mutable flags : Flags.flags; [@hash.ignore] } | List of { pos : Pos.Option.t; [@hash.ignore] value : t list; methods : t Methods.t; mutable flags : Flags.flags; [@hash.ignore] } | Tuple of { pos : Pos.Option.t; [@hash.ignore] value : t list; methods : t Methods.t; mutable flags : Flags.flags; [@hash.ignore] } | (* Function with given list of argument name, argument variable and default value, the (relevant part of the) closure, and the body. *) Fun of { id : int; pos : Pos.Option.t; [@hash.ignore] fun_args : (string * string * t option) list; fun_env : env; [@hash.ignore] fun_body : Term.t; [@hash.ignore] methods : t Methods.t; mutable flags : Flags.flags; [@hash.ignore] } | (* For a foreign function only the arguments are visible, the closure doesn't capture anything in the environment. *) FFI of { id : int; pos : Pos.Option.t; [@hash.ignore] ffi_args : (string * string * t option) list; mutable ffi_fn : env -> t; [@hash.ignore] methods : t Methods.t; mutable flags : Flags.flags; [@hash.ignore] } [@@deriving hash] type fun_v = { fun_args : (string * string * t option) list; fun_env : env; fun_body : Term.t; } type ffi = { ffi_args : (string * string * t option) list; ffi_fn : env -> t } type in_value = [ `Int of int | `Float of float | `String of string | `Bool of bool | `Null | `Custom of Custom.t | `List of t list | `Tuple of t list | `Fun of fun_v | `FFI of ffi ] let methods = function | Int { methods } | Float { methods } | String { methods } | Bool { methods } | Custom { methods } | Null { methods } | Tuple { methods } | List { methods } | Fun { methods } | FFI { methods } -> methods [@@inline always] let map_methods v fn = match v with | Int ({ methods } as p) -> Int { p with methods = fn methods } | Float ({ methods } as p) -> Float { p with methods = fn methods } | String ({ methods } as p) -> String { p with methods = fn methods } | Bool ({ methods } as p) -> Bool { p with methods = fn methods } | Custom ({ methods } as p) -> Custom { p with methods = fn methods } | Null ({ methods } as p) -> Null { p with methods = fn methods } | Tuple ({ methods } as p) -> Tuple { p with methods = fn methods } | List ({ methods } as p) -> List { p with methods = fn methods } | Fun ({ methods } as p) -> Fun { p with methods = fn methods } | FFI ({ methods } as p) -> FFI { p with methods = fn methods } [@@inline always] let pos = function | Int { pos } | Float { pos } | String { pos } | Bool { pos } | Custom { pos } | Null { pos } | Tuple { pos } | List { pos } | Fun { pos } | FFI { pos } -> pos [@@inline always] let set_pos v pos = match v with | Int p -> Int { p with pos } | Float p -> Float { p with pos } | String p -> String { p with pos } | Bool p -> Bool { p with pos } | Custom p -> Custom { p with pos } | Null p -> Null { p with pos } | Tuple p -> Tuple { p with pos } | List p -> List { p with pos } | Fun p -> Fun { p with pos } | FFI p -> FFI { p with pos } [@@inline always] let has_flag v flag = match v with | Float _ | Bool _ | Null _ -> false | String { flags } | Int { flags } | Custom { flags } | Tuple { flags } | List { flags } | Fun { flags } | FFI { flags } -> Flags.has flags flag [@@inline always] let add_flag v flag = match v with | Float _ | Bool _ | Null _ -> assert false | String p -> p.flags <- Flags.add p.flags flag | Int p -> p.flags <- Flags.add p.flags flag | Custom p -> p.flags <- Flags.add p.flags flag | Tuple p -> p.flags <- Flags.add p.flags flag | List p -> p.flags <- Flags.add p.flags flag | Fun p -> p.flags <- Flags.add p.flags flag | FFI p -> p.flags <- Flags.add p.flags flag [@@inline always] let remove_flag v flag = match v with | Float _ | Bool _ | Null _ -> assert false | String p -> p.flags <- Flags.remove p.flags flag | Int p -> p.flags <- Flags.remove p.flags flag | Custom p -> p.flags <- Flags.remove p.flags flag | Tuple p -> p.flags <- Flags.remove p.flags flag | List p -> p.flags <- Flags.remove p.flags flag | Fun p -> p.flags <- Flags.remove p.flags flag | FFI p -> p.flags <- Flags.remove p.flags flag [@@inline always] let unit = `Tuple [] let is_unit = function Tuple { value = [] } -> true | _ -> false let fun_id = Atomic.make 0 let make ?pos ?(methods = Methods.empty) ?(flags = Flags.empty) : in_value -> t = function | `Int i -> Int { pos; methods; flags; value = i } | `Float f -> Float { pos; methods; value = f } | `String s -> String { pos; methods; flags; value = s } | `Bool b -> Bool { pos; methods; value = b } | `Custom c -> Custom { pos; methods; flags; dynamic_methods = None; value = c } | `Null -> Null { pos; methods } | `Tuple l -> Tuple { pos; methods; flags; value = l } | `List l -> List { pos; methods; flags; value = l } | `Fun { fun_args; fun_env; fun_body } -> Fun { id = Atomic.fetch_and_add fun_id 1; pos; methods; flags; fun_args; fun_env; fun_body; } | `FFI { ffi_args; ffi_fn } -> FFI { id = Atomic.fetch_and_add fun_id 1; pos; methods; flags; ffi_args; ffi_fn; } let string_of_int_value ~flags i = if Flags.has flags Flags.octal_int then Printf.sprintf "0o%o" i else if Flags.has flags Flags.hex_int then Printf.sprintf "0x%x" i else string_of_int i let rec to_string v = let base_string v = match v with | Int { value = i; flags } -> string_of_int_value ~flags i | Float { value = f } -> Utils.string_of_float f | Bool { value = b } -> string_of_bool b | String { flags } when Flags.has flags Flags.binary -> "" | String { value = s } -> Lang_string.quote_string s | Custom { value = c } -> Custom.to_string c | List { value = l } -> "[" ^ String.concat ", " (List.map to_string l) ^ "]" | Tuple { value = l } -> "(" ^ String.concat ", " (List.map to_string l) ^ ")" | Null _ -> "null" | Fun { fun_args = []; fun_body = x } when Term.is_ground x -> "{" ^ Term.to_string x ^ "}" | Fun { fun_args = l; fun_body = x } when Term.is_ground x -> let f (label, _, value) = match (label, value) with | "", None -> "_" | "", Some v -> Printf.sprintf "_=%s" (to_string v) | label, Some v -> Printf.sprintf "~%s=%s" label (to_string v) | label, None -> Printf.sprintf "~%s=_" label in let args = List.map f l in Printf.sprintf "fun (%s) -> %s" (String.concat "," args) (Term.to_string x) | Fun _ | FFI _ -> "" in let s = base_string v in let methods = methods v in if Methods.is_empty methods then s else ( let methods = Methods.bindings methods in (if is_unit v then "" else s ^ ".") ^ "{" ^ String.concat ", " (List.map (fun (l, meth_term) -> l ^ "=" ^ to_string meth_term) methods) ^ "}") (** Find a method in a value. *) let invoke x l = try match (Methods.find_opt l (methods x), x) with | Some v, _ -> v | None, Custom { dynamic_methods = Some { hidden_methods; methods } } when not (List.mem l hidden_methods) -> Option.get (methods l) | _ -> raise Not_found with _ -> failwith ("Could not find method " ^ l ^ " of " ^ to_string x) (** Perform a sequence of invokes: invokes x [l1;l2;l3;...] is x.l1.l2.l3... *) let rec invokes x = function l :: ll -> invokes (invoke x l) ll | [] -> x let demeth e = map_methods (match e with | Custom p -> Custom { p with methods = Methods.empty; dynamic_methods = None } | _ -> e) (fun _ -> Methods.empty) let remeth t u = match t with | Custom { dynamic_methods = Some _ } -> Runtime_error.raise ~pos:(match pos u with None -> [] | Some p -> [p]) ~message: "Spread and method replacements are not supported for values with \ dynamic methods. Most likely, you are trying to define new source \ tracks as: `{...source.tracks(s), video=..}`. Please use: \ `source.tracks(s).{ video=.. }` instead." "invalid" | _ -> let t_methods = methods t in map_methods u (fun u_methods -> Methods.fold Methods.add t_methods u_methods) let split_meths e = (Methods.bindings (methods e), demeth e) let compare a b = let rec aux = function | Int { value = i }, Int { value = i' } -> Stdlib.compare i i' | Float { value = f }, Float { value = f' } -> Stdlib.compare f f' | Bool { value = b }, Bool { value = b' } -> Stdlib.compare b b' | String { value = s }, String { value = s' } -> Stdlib.compare s s' | Custom { value = a }, Custom { value = b } -> Custom.compare a b | Tuple { value = l }, Tuple { value = m } -> List.fold_left2 (fun cmp a b -> if cmp <> 0 then cmp else compare a b) 0 l m | List { value = l1 }, List { value = l2 } -> let rec cmp = function | [], [] -> 0 | [], _ -> -1 | _, [] -> 1 | h1 :: l1, h2 :: l2 -> let c = compare h1 h2 in if c = 0 then cmp (l1, l2) else c in cmp (l1, l2) | Fun { id = i }, Fun { id = i' } | FFI { id = i }, FFI { id = i' } | Fun { id = i }, FFI { id = i' } | FFI { id = i }, Fun { id = i' } -> Stdlib.compare i i' | Null _, Null _ -> 0 | Null _, _ -> -1 | _, Null _ -> 1 | v, v' -> failwith (Printf.sprintf "Cannot compare %s and %s" (to_string v) (to_string v')) and compare a b = (* For records, we compare the list ["label", field; ..] of common fields. *) if is_unit a && is_unit b then ( let r a = let m, _ = split_meths a in m in let a = r a in let b = r b in (* Keep only common fields: with subtyping it might happen that some fields are ignored. *) let a = List.filter (fun (l, _) -> List.exists (fun (l', _) -> l = l') b) a in let b = List.filter (fun (l, _) -> List.exists (fun (l', _) -> l = l') a) b in let a = List.sort (fun x x' -> Stdlib.compare (fst x) (fst x')) a in let b = List.sort (fun x x' -> Stdlib.compare (fst x) (fst x')) b in let a = make (`Tuple (List.map (fun (lbl, v) -> make (`Tuple [make (`String lbl); v])) a)) in let b = make (`Tuple (List.map (fun (lbl, v) -> make (`Tuple [make (`String lbl); v])) b)) in aux (a, b)) else aux (a, b) in compare a b (* Custom values. *) module type Custom = sig include Term.Custom val to_value : ?pos:Pos.t -> content -> t val of_value : t -> content val is_value : t -> bool end module type CustomDef = Term.CustomDef module MkCustomFromTerm (Term : Term.Custom) = struct include Term let to_value ?pos c = make ?pos (`Custom (to_custom c)) let of_value v = match v with Custom { value = c } -> of_custom c | _ -> assert false let is_value v = match v with Custom { value = c } -> is_custom c | _ -> false end module MkCustom (Def : CustomDef) = struct module Term = Term.MkCustom (Def) include MkCustomFromTerm (Term) end module RuntimeType = MkCustom (struct type content = Type.t let name = "type" let to_string _ = "type" let to_json ~pos _ = Runtime_error.raise ~pos ~message:"Types cannot be represented as json" "json" let compare = Stdlib.compare end) liquidsoap-2.4.2/src/lang/console/000077500000000000000000000000001513273233300170455ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/console/console.ml000066400000000000000000000044641513273233300210510ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) (* Some of the code below was borrowed from opam. *) let dumb_term = lazy (try Sys.getenv "TERM" = "dumb" with Not_found -> Sys.win32) type color_conf = [ `Always | `Never | `Auto ] let color_conf : color_conf ref = ref `Auto let color = let auto = lazy (try Unix.isatty Unix.stdout && not (Lazy.force dumb_term) with _ -> false) in fun () -> match !color_conf with | `Always -> true | `Never -> false | `Auto -> Lazy.force auto type text_style = [ `bold | `underline | `crossed | `black | `red | `green | `yellow | `blue | `magenta | `cyan | `white ] let style_code (c : text_style) = match c with | `bold -> "01" | `underline -> "04" | `crossed -> "09" | `black -> "30" | `red -> "31" | `green -> "32" | `yellow -> "33" | `blue -> "1;34" (* most terminals make blue unreadable unless bold *) | `magenta -> "35" | `cyan -> "36" | `white -> "37" let colorize styles s = if not (color ()) then s else Printf.sprintf "\027[%sm%s\027[0m" (String.concat ";" (List.map style_code styles)) s let start_color styles = if not (color ()) then "" else Printf.sprintf "\027[%sm" (String.concat ";" (List.map style_code styles)) let stop_color () = if not (color ()) then "" else "\027[0m" liquidsoap-2.4.2/src/lang/console/console.mli000066400000000000000000000025441513273233300212170ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) type text_style = [ `bold | `underline | `crossed | `black | `red | `green | `yellow | `blue | `magenta | `cyan | `white ] val colorize : text_style list -> string -> string val start_color : text_style list -> string val stop_color : unit -> string type color_conf = [ `Always | `Never | `Auto ] val color_conf : color_conf ref liquidsoap-2.4.2/src/lang/console/dune000066400000000000000000000001231513273233300177170ustar00rootroot00000000000000(library (name console) (public_name liquidsoap-lang.console) (libraries unix)) liquidsoap-2.4.2/src/lang/stdlib/000077500000000000000000000000001513273233300166645ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/stdlib/dune000066400000000000000000000001201513273233300175330ustar00rootroot00000000000000(library (name stdlib) (public_name liquidsoap-lang.stdlib) (wrapped false)) liquidsoap-2.4.2/src/lang/stdlib/list.ml000066400000000000000000000007131513273233300201720ustar00rootroot00000000000000include Stdlib.List let[@tail_mod_cons] rec map f = function | [] -> [] | [a1] -> let r1 = f a1 in [r1] | a1 :: a2 :: l -> let r1 = f a1 in let r2 = f a2 in r1 :: r2 :: map f l let[@tail_mod_cons] rec mapi i f = function | [] -> [] | [a1] -> let r1 = f i a1 in [r1] | a1 :: a2 :: l -> let r1 = f i a1 in let r2 = f (i + 1) a2 in r1 :: r2 :: mapi (i + 2) f l let mapi f l = mapi 0 f l liquidsoap-2.4.2/src/lang/stdlib/list.mli000066400000000000000000000000431513273233300203370ustar00rootroot00000000000000include module type of Stdlib.List liquidsoap-2.4.2/src/lang/tooling/000077500000000000000000000000001513273233300170565ustar00rootroot00000000000000liquidsoap-2.4.2/src/lang/tooling/dune000066400000000000000000000003161513273233300177340ustar00rootroot00000000000000(env (release (ocamlopt_flags (:standard -O2))) (dev (flags (:standard -w -9)))) (library (name liquidsoap_tooling) (public_name liquidsoap-lang.tooling) (libraries sedlex liquidsoap_lang)) liquidsoap-2.4.2/src/lang/tooling/parsed_json.ml000066400000000000000000000521261513273233300217250ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Liquidsoap_lang open Parsed_term let json_of_position { Lexing.pos_fname; pos_lnum; pos_bol; pos_cnum } : Json.t = `Assoc [ ("fname", `String pos_fname); ("lnum", `Int pos_lnum); ("bol", `Int pos_bol); ("cnum", `Int pos_cnum); ] let json_of_positions (p, p') = `Tuple [json_of_position p; json_of_position p'] let json_of_if_def ~to_json { if_def_negative; if_def_condition; if_def_then; if_def_else } = [ ("negative", `Bool if_def_negative); ("condition", `String if_def_condition); ("then", to_json if_def_then); ("else", match if_def_else with None -> `Null | Some t -> to_json t); ] let json_of_if_encoder ~to_json { if_encoder_negative; if_encoder_condition; if_encoder_then; if_encoder_else; } = [ ("negative", `Bool if_encoder_negative); ("condition", `String if_encoder_condition); ("then", to_json if_encoder_then); ("else", match if_encoder_else with None -> `Null | Some t -> to_json t); ] let json_of_if_version ~to_json { if_version_op; if_version_version; if_version_then; if_version_else } = [ ( "opt", `String (match if_version_op with | `Eq -> "==" | `Geq -> ">=" | `Leq -> "<=" | `Gt -> ">" | `Lt -> "<") ); ("version", `String (Lang_string.Version.to_string if_version_version)); ("then", to_json if_version_then); ("else", match if_version_else with None -> `Null | Some t -> to_json t); ] let json_of_while ~to_json { while_condition; while_loop } = [("condition", to_json while_condition); ("loop", to_json while_loop)] let json_of_for ~to_json { for_variable; for_from; for_to; for_loop } = [ ("variable", `String for_variable); ("from", to_json for_from); ("to", to_json for_to); ("loop", to_json for_loop); ] let json_of_iterable_for ~to_json { iterable_for_variable; iterable_for_iterator; iterable_for_loop } = [ ("variable", `String iterable_for_variable); ("iterator", to_json iterable_for_iterator); ("loop", to_json iterable_for_loop); ] let json_of_try ~to_json { try_body; try_variable; try_errors_list; try_handler; try_finally } = [ ("body", to_json try_body); ("variable", `String try_variable); ( "errors_list", match try_errors_list with None -> `Null | Some tm -> to_json tm ); ("handler", match try_handler with None -> `Null | Some tm -> to_json tm); ("finally", match try_finally with None -> `Null | Some tm -> to_json tm); ] let type_node ~typ ?(extra = []) value = `Assoc ([ ("type", `String "type_annotation"); ("subtype", `String typ); ("value", value); ] @ extra) let ast_node ~typ value = ("type", `String typ) :: value let json_of_annotated_string = function | `Verbatim s -> ast_node ~typ:"var" [("value", `String s)] | `String (_, (sep, s)) -> ast_node ~typ:"ground" [("value", `String (Printf.sprintf "%c%s%c" sep s sep))] let rec json_of_type_annotation = function | `Named n -> type_node ~typ:"named" (`String n) | `Nullable t -> type_node ~typ:"nullable" (json_of_type_annotation t) | `List t -> type_node ~typ:"list" (json_of_type_annotation t) | `Json_object t -> type_node ~typ:"json_object" (json_of_type_annotation t) | `Tuple l -> type_node ~typ:"tuple" (`Tuple (List.map json_of_type_annotation l)) | `Arrow (args, t) -> type_node ~typ:"arrow" ~extra:[("args", `Tuple (List.map json_of_type_fun_arg args))] (json_of_type_annotation t) | `Record l -> type_node ~typ:"record" (`Tuple (List.map json_of_meth_annotation l)) | `Method (t, l) -> type_node ~typ:"method" ~extra:[("base", json_of_type_annotation t)] (`Tuple (List.map json_of_meth_annotation l)) | `Invoke (t, s) -> type_node ~typ:"invoke" ~extra:[("method", `String s)] (json_of_type_annotation t) | `Source (n, t) -> type_node ~typ:"source" ~extra:[("base", `String n)] (json_of_source_annotation t) and json_of_type_fun_arg (b, s, t) = type_node ~typ:"fun_arg" ~extra:[("optional", `Bool b); ("label", `String s)] (json_of_type_annotation t) and json_of_meth_annotation { optional_meth; name; typ; json_name } = type_node ~typ:"method_annotation" ~extra: [ ("optional", `Bool optional_meth); ("name", `String name); ("json_name", match json_name with None -> `Null | Some n -> `String n); ] (json_of_type_annotation typ) and json_of_source_annotation { extensible; tracks } = type_node ~typ:"source_annotation" ~extra:[("extensible", `Bool extensible)] (`Tuple (List.map json_of_source_track_annotation tracks)) and json_of_source_track_annotation { track_name; track_type; track_params } = type_node ~typ:"source_track_annotation" ~extra: [ ("name", `String track_name); ( "params", `Tuple (List.map (fun (l, v) -> `Assoc (ast_node ~typ:"app_arg" [ ("label", `String l); ("value", `Assoc (json_of_annotated_string v)); ])) track_params) ); ] (`String track_type) let json_of_if ~to_json { if_condition; if_then; if_elsif; if_else } = [ ("condition", to_json if_condition); ("then", to_json if_then); ( "elsif", `Tuple (List.map (fun (t, t') -> `Assoc (ast_node ~typ:"elsif" [("condition", to_json t); ("then", to_json t')])) if_elsif) ); ( "else", match if_else with None -> `Null | Some if_else -> to_json if_else ); ] let rec base_json_of_pat = function | `PVar l -> ast_node ~typ:"pvar" [("value", `Tuple (List.map (fun v -> `String v) l))] | `PTuple l -> ast_node ~typ:"ptuple" [("value", `Tuple (List.map json_of_pat l))] | `PList (l, v, l') -> ast_node ~typ:"plist" [ ("left", `Tuple (List.map json_of_pat l)); ("middle", match v with None -> `Null | Some (_, s) -> `String s); ("right", `Tuple (List.map json_of_pat l')); ] | `PMeth (ellipsis, methods) -> ast_node ~typ:"pmeth" [ ( "value", `Tuple (List.map (function | var, `None -> `Assoc (ast_node ~typ:"var" [("value", `String var)]) | var, `Nullable -> `Assoc (ast_node ~typ:"var" [("value", `String (var ^ "?"))]) | var, `Pattern pat -> `Assoc (ast_node ~typ:"infix" [ ( "left", `Assoc (ast_node ~typ:"var" [("value", `String var)]) ); ("op", `String "="); ("right", json_of_pat pat); ])) methods @ match ellipsis with | None -> [] | Some pat -> [ `Assoc (ast_node ~typ:"ellipsis" [("value", json_of_pat pat)]); ]) ); ] and json_of_pat p = `Assoc (base_json_of_pat p.pat_entry) let json_of_of { only; except; source } = [ ("only", `Tuple (List.map (fun s -> `String s) only)); ("except", `Tuple (List.map (fun s -> `String s) except)); ("source", `String source); ] let json_of_fun_arg ~to_json : Parsed_term.fun_arg -> (string * Json.t) list = function | `Argsof _of -> ast_node ~typ:"argsof" (json_of_of _of) | `Term { Parsed_term.label; as_variable; typ; default } -> ast_node ~typ:"term" [ ( "value", `Assoc (ast_node ~typ:"fun_arg" [ ("label", `String label); ( "as_variable", match as_variable with | None -> `Null | Some pat -> json_of_pat pat ); ( "typ", match typ with | None -> `Null | Some typ -> json_of_type_annotation typ ); ( "default", match default with None -> `Null | Some d -> to_json d ); ]) ); ] let json_of_fun ~to_json arguments body = [ ( "arguments", `Tuple (List.map (fun arg -> `Assoc (json_of_fun_arg ~to_json arg)) arguments) ); ("body", to_json body); ] let json_of_let_decoration ~to_json : Parsed_term.let_decoration -> Json.t = function | `None -> `Null | `Recursive -> `Assoc (ast_node ~typ:"var" [("value", `String "rec")]) | `Replaces -> `Assoc (ast_node ~typ:"var" [("value", `String "replaces")]) | `Eval -> `Assoc (ast_node ~typ:"var" [("value", `String "eval")]) | `Sqlite_query -> `Assoc (ast_node ~typ:"var" [("value", `String "sqlite.query")]) | `Sqlite_row -> `Assoc (ast_node ~typ:"var" [("value", `String "sqlite.row")]) | `Yaml_parse -> `Assoc (ast_node ~typ:"var" [("value", `String "yaml.parse")]) | `Xml_parse -> `Assoc (ast_node ~typ:"var" [("value", `String "xml.parse")]) | `Json_parse [] -> `Assoc (ast_node ~typ:"var" [("value", `String "json.parse")]) | `Json_parse args -> `Assoc (ast_node ~typ:"app" [ ( "op", `Assoc (ast_node ~typ:"var" [("value", `String "json.parse")]) ); ( "args", `Tuple (List.map (fun (l, t) -> `Assoc (ast_node ~typ:"term" [ ( "value", `Assoc (ast_node ~typ:"app_arg" [("label", `String l); ("value", to_json t)]) ); ])) args) ); ]) let args_of_json_let ~to_json { decoration; pat; arglist; cast; def } = [ ("decoration", json_of_let_decoration ~to_json decoration); ("pat", json_of_pat pat); ( "arglist", match arglist with | None -> `Null | Some arglist -> `Tuple (List.map (fun arg -> `Assoc (json_of_fun_arg ~to_json arg)) arglist) ); ( "cast", match cast with None -> `Null | Some t -> json_of_type_annotation t ); ("definition", to_json def); ] let json_of_let ~to_json ast = let typ, args, body = match ast with | `Def (p, body) -> ("def", args_of_json_let ~to_json p, body) | `Let (p, body) -> ("let", args_of_json_let ~to_json p, body) | `Binding (p, body) -> ("binding", args_of_json_let ~to_json p, body) in ast_node ~typ (("body", to_json body) :: args) let json_of_app_arg ~to_json = function | `Term (l, v) -> ast_node ~typ:"term" [ ( "value", `Assoc (ast_node ~typ:"app_arg" [("label", `String l); ("value", to_json v)]) ); ] | `Argsof _of -> ast_node ~typ:"argsof" (json_of_of _of) let json_of_app_args ~to_json args = `Tuple (List.map (fun arg -> `Assoc (json_of_app_arg ~to_json arg)) args) let json_of_invoke_meth ~to_json = function | `String s -> ast_node ~typ:"var" [("value", `String s)] | `App (s, args) -> ast_node ~typ:"app" [ ("op", `Assoc (ast_node ~typ:"var" [("value", `String s)])); ("args", json_of_app_args ~to_json args); ] let json_of_list_el ~to_json = function | `Term t -> ast_node ~typ:"term" [("value", to_json t)] | `Ellipsis t -> ast_node ~typ:"ellipsis" [("value", to_json t)] let json_of_time_el { week; hours; minutes; seconds } = let to_int = function None -> `Null | Some i -> `Int i in [ ("week", to_int week); ("hours", to_int hours); ("minutes", to_int minutes); ("seconds", to_int seconds); ] let rec to_ast_json ~to_json = function | `Get t -> ast_node ~typ:"get" [("value", to_json t)] | `Set (t, t') -> ast_node ~typ:"infix" [("left", to_json t); ("op", `String ":="); ("right", to_json t')] | `Inline_if p -> ast_node ~typ:"inline_if" (json_of_if ~to_json p) | `If p -> ast_node ~typ:"if" (json_of_if ~to_json p) | `If_def p -> ast_node ~typ:"if_def" (json_of_if_def ~to_json p) | `If_version p -> ast_node ~typ:"if_version" (json_of_if_version ~to_json p) | `If_encoder p -> ast_node ~typ:"if_encoder" (json_of_if_encoder ~to_json p) | `While p -> ast_node ~typ:"while" (json_of_while ~to_json p) | `For p -> ast_node ~typ:"for" (json_of_for ~to_json p) | `Iterable_for p -> ast_node ~typ:"iterable_for" (json_of_iterable_for ~to_json p) | `Not t -> ast_node ~typ:"not" [("value", to_json t)] | `Negative t -> ast_node ~typ:"negative" [("value", to_json t)] | `String_interpolation (c, l) -> let l = `String (Printf.sprintf "%c" c) :: (l @ [`String (Printf.sprintf "%c" c)]) in let l = List.map (function | `String s -> `Assoc (ast_node ~typ:"interpolated_string" [("value", `String s)]) | `Term tm -> `Assoc (ast_node ~typ:"interpolated_term" [("value", to_json tm)])) l in ast_node ~typ:"string_interpolation" [("value", `Tuple l)] | `Append (t, t') -> ast_node ~typ:"append" [("left", to_json t); ("right", to_json t')] | `Assoc (t, t') -> ast_node ~typ:"assoc" [("left", to_json t); ("right", to_json t')] | `Infix (t, op, t') -> ast_node ~typ:"infix" [("left", to_json t); ("op", `String op); ("right", to_json t')] | `BoolOp (op, l) -> ast_node ~typ:"bool" [("op", `String op); ("value", `Tuple (List.map to_json l))] | `Simple_fun t -> ast_node ~typ:"simple_fun" [("value", to_json t)] | `Time t -> ast_node ~typ:"time" (json_of_time_el t) | `Time_interval (t, t') -> ast_node ~typ:"time_interval" [ ("left", `Assoc (ast_node ~typ:"time" (json_of_time_el t))); ("right", `Assoc (ast_node ~typ:"time" (json_of_time_el t'))); ] | `Regexp (name, flags) -> ast_node ~typ:"regexp" [ ("name", `String name); ( "flags", `Tuple (List.sort Stdlib.compare (List.map (fun c -> `String (Char.escaped c)) flags)) ); ] | `Try p -> ast_node ~typ:"try" (json_of_try ~to_json p) | `Custom g -> ast_node ~typ:"ground" [ ( "value", `String (Json.to_string (Term_base.Custom.to_json ~pos:[] g)) ); ] | `Bool b -> ast_node ~typ:"ground" [("value", `String (string_of_bool b))] | `Int i -> ast_node ~typ:"ground" [("value", `String i)] | `Float v -> ast_node ~typ:"ground" [("value", `String v)] | `Parenthesis tm -> ast_node ~typ:"parenthesis" [("value", to_json tm)] | `Block tm -> ast_node ~typ:"block" [("value", to_json tm)] | `String (c, s) -> ast_node ~typ:"string" [("value", `String (Printf.sprintf "%c%s%c" c s c))] | `Encoder e -> ast_node ~typ:"encoder" (to_encoder_json ~to_json e) | `List l -> ast_node ~typ:"list" [ ( "value", `Tuple (List.map (fun p -> `Assoc (json_of_list_el ~to_json p)) l) ); ] | `Tuple l -> ast_node ~typ:"tuple" [("value", `Tuple (List.map to_json l))] | `Null -> ast_node ~typ:"null" [] | `Cast { cast = t; typ } -> ast_node ~typ:"cast" [("left", to_json t); ("right", json_of_type_annotation typ)] | `Invoke { invoked; optional; meth } -> ast_node ~typ:"invoke" [ ("invoked", to_json invoked); ("optional", `Bool optional); ("meth", `Assoc (json_of_invoke_meth ~to_json meth)); ] | `Methods (base, methods) -> let base, base_methods = match base with None -> (`Null, []) | Some t -> (to_json t, []) in ast_node ~typ:"methods" [ ("base", base); ( "methods", `Tuple (List.map (function | `Ellipsis v -> `Assoc (ast_node ~typ:"ellipsis" [("value", to_json v)]) | `Method (k, v) -> `Assoc (ast_node ~typ:"method" [("name", `String k); ("value", to_json v)])) methods @ base_methods) ); ] | `Eof -> ast_node ~typ:"eof" [] | `Open (t, t') -> ast_node ~typ:"open" [("left", to_json t); ("right", to_json t')] | `Let _ as ast -> json_of_let ~to_json ast | `Def _ as ast -> json_of_let ~to_json ast | `Binding _ as ast -> json_of_let ~to_json ast | `Include { inc_type = `Lib; inc_name } -> ast_node ~typ:"include_lib" [("value", `String inc_name)] | `Include { inc_type = `Default; inc_name } -> ast_node ~typ:"include" [("value", `String inc_name)] | `Include { inc_type = `Extra; inc_name } -> ast_node ~typ:"include_extra" [("value", `String inc_name)] | `Coalesce (t, t') -> ast_node ~typ:"coalesce" [("left", to_json t); ("right", to_json t')] | `At (t, t') -> ast_node ~typ:"infix" [("left", to_json t); ("op", `String "@"); ("right", to_json t')] | `Var s -> ast_node ~typ:"var" [("value", `String s)] | `Seq (t, t') -> ast_node ~typ:"seq" [("left", to_json t); ("right", to_json t')] | `App (t, args) -> ast_node ~typ:"app" [("op", to_json t); ("args", json_of_app_args ~to_json args)] | `Fun (args, body) -> ast_node ~typ:"fun" (json_of_fun ~to_json args body) | `RFun (lbl, args, body) -> ast_node ~typ:"rfun" (("name", `String lbl) :: json_of_fun ~to_json args body) and to_encoder_json ~to_json (lbl, params) = [ ("label", `String lbl); ("params", `Tuple (List.map (to_encoder_param_json ~to_json) params)); ] and to_encoder_param_json ~to_json = function | `Encoder e -> `Assoc (ast_node ~typ:"encoder" (to_encoder_json ~to_json e)) | `Labelled (lbl, v) -> `Assoc (ast_node ~typ:"infix" [ ("left", `Assoc (json_of_annotated_string lbl)); ("op", `String "="); ("right", to_json v); ]) | `Anonymous s -> `Assoc (json_of_annotated_string s) let rec to_json { pos; term; comments } : Json.t = let before_comments, after_comments = List.fold_left (fun (before_comments, after_comments) -> function | p, `Before c -> ((p, c) :: before_comments, after_comments) | p, `After c -> (before_comments, (p, c) :: after_comments)) ([], []) comments in let ast_comments = `Assoc [ ( "before", `Tuple (List.map (fun (p, c) -> `Assoc (ast_node ~typ:"comment" [ ("position", json_of_positions p); ("value", `Tuple (List.map (fun c -> `String c) c)); ])) (List.rev before_comments)) ); ( "after", `Tuple (List.map (fun (p, c) -> `Assoc (ast_node ~typ:"comment" [ ("position", json_of_positions p); ("value", `Tuple (List.map (fun c -> `String c) c)); ])) (List.rev after_comments)) ); ] in `Assoc ([("ast_comments", ast_comments); ("position", json_of_positions pos)] @ to_ast_json ~to_json term) let parse_string ?(formatter = Format.err_formatter) content = let lexbuf = Sedlexing.Utf8.from_string content in let throw = Runtime.throw ~formatter ~lexbuf:(Some lexbuf) () in try let tokenizer = Preprocessor.mk_tokenizer lexbuf in Parser_helper.clear_comments (); let term = Runtime.program tokenizer in Parser_helper.attach_comments term; to_json term with exn -> let bt = Printexc.get_raw_backtrace () in throw ~bt exn; exit 1 liquidsoap-2.4.2/src/lang/tooling/parsed_json.mli000066400000000000000000000022011513273233300220630ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable stream generator. Copyright 2003-2026 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************) open Liquidsoap_lang val to_json : Parsed_term.t -> Json.t val parse_string : ?formatter:Format.formatter -> string -> Json.t liquidsoap-2.4.2/src/libs/000077500000000000000000000000001513273233300154135ustar00rootroot00000000000000liquidsoap-2.4.2/src/libs/.gitignore000066400000000000000000000000111513273233300173730ustar00rootroot00000000000000dune.inc liquidsoap-2.4.2/src/libs/audio.liq000066400000000000000000000313721513273233300172310ustar00rootroot00000000000000audio = () let audio.encode = () # Encode audio track to pcm_s16 # @category Source / Audio processing def audio.encode.pcm_s16(~id=null("audio.encode.pcm_s16"), s) = let {audio, ...tracks} = source.tracks(s) source(id=id, tracks.{audio=track.encode.audio.pcm_s16(audio)}) end # Encode audio track to pcm_f32 # @category Source / Audio processing def audio.encode.pcm_f32(~id=null("audio.encode.pcm_f32"), s) = let {audio, ...tracks} = source.tracks(s) source(id=id, tracks.{audio=track.encode.audio.pcm_f32(audio)}) end let audio.decode = () # Decode audio track to pcm_s16 # @category Source / Audio processing def audio.decode.pcm_s16(~id=null("audio.decode.pcm_s16"), s) = let {audio, ...tracks} = source.tracks(s) source(id=id, tracks.{audio=track.decode.audio.pcm_s16(audio)}) end # Decode audio track to pcm_f32 # @category Source / Audio processing def audio.decode.pcm_f32(~id=null("audio.decode.pcm_f32"), s) = let {audio, ...tracks} = source.tracks(s) source(id=id, tracks.{audio=track.decode.audio.pcm_f32(audio)}) end # Samplerate for audio. # @category Settings def audio.samplerate = settings.frame.audio.samplerate end # Channels for audio. # @category Settings def audio.channels = settings.frame.audio.channels end # Multiply the amplitude of the signal. # @category Source / Audio processing # @param f Multiplicative factor. # @argsof track.audio.amplify def amplify(~id=null("amplify"), %argsof(track.audio.amplify[!id]), f, s) = tracks = source.tracks(s) source( id=id, tracks.{ audio= track.audio.amplify( id=null("track.amplify"), %argsof(track.audio.amplify[!id]), f, tracks.audio ) } ) end # Clip samples, i.e. ensure that all values are between # `-1` and `1`: values lower than `-1` become `-1` and # values higher than `1` become `1`. `nan` values become `0`. # @category Source / Audio processing # @argsof track.audio.clip def clip(~id=null("clip"), %argsof(track.audio.clip[!id]), s) = tracks = source.tracks(s) source( id=id, tracks.{audio=track.audio.clip(%argsof(track.audio.clip), tracks.audio)} ) end lufs_builtin = lufs # Normalization the volume of a stream (this is also called _automatic gain # control_). Dynamic normalization of the signal is sometimes the only option # (for instance, for live sources), and can make a listening experience much # nicer. However, its dynamic aspect implies some limitations which can go as # far as creating saturation in some extreme cases. If possible, consider using # some track-based normalization techniques such as those based on # ReplayGain. The implementation of Liquidsoap < 2.0 was renamed to # `normalize.old`. # @category Source / Audio processing # @param ~id Force the value of the source ID. # @param ~gain_max Maximal gain value (dB). # @param ~gain_min Minimal gain value (dB). # @param ~down Characteristic time to go down. # @param ~up Characteristic time to go up. # @param ~lookahead How much time to look ahead of the signal (second). Setting a positive value delays the output by the corresponding amount of time. # @param ~lufs Use LUFS instead of RMS to compute intensity. # @param ~target Desired RMS (dB). # @param ~threshold Minimal RMS for activaing gain control (dB). # @param ~window Duration of the window used to compute the current RMS power (second). # @param ~enabled Whether normalization is enabled or not. # @param ~debug How often to print debug messages, in seconds, useful to finetune the parameters. You should set `set("log.level", 5)` to see them. # @param s Source to normalize. # @method gain Current amplification coefficient (in linear scale). # @method target_gain Current target amplification coefficient (in linear scale). # @method rms Current rms (in linear scale). def replaces normalize( ~id=null, ~target=getter(-13.), ~up=getter(10.), ~down=getter(.1), ~gain_min=-12., ~gain_max=12., ~lufs=false, ~lookahead=getter(0.), ~window=getter(.5), ~threshold=getter(-40.), ~track_sensitive=true, ~enabled=getter(true), ~debug=null, s ) = let (s, rms) = if lufs then s = lufs_builtin(id=id, window=window, s) (s, {lin_of_dB(s.lufs())}) else s = rms.smooth(id=id, duration=window, s) (s, s.rms) end v = ref(1.) frame = frame.duration() gain_min = lin_of_dB(gain_min) gain_max = lin_of_dB(gain_max) def update() = if not (getter.get(enabled)) then v := 1. else target = lin_of_dB(getter.get(target)) threshold = lin_of_dB(getter.get(threshold)) rms = rms() if rms >= threshold then if v() * rms <= target then up = 1. - exp(0. - frame / getter.get(up)) v := v() + up * ((target / rms) - v()) else down = 1. - exp(0. - frame / getter.get(down)) v := v() + down * ((target / rms) - v()) end v := max(gain_min, min(gain_max, v())) end end end def target_gain() = lin_of_dB(getter.get(target)) / rms() end s = if null.defined(debug) then source.run( s, every=null.get(debug), { log.debug( "rms: #{rms()} / #{lin_of_dB(getter.get(target))}\tgain: #{v()} / #{ target_gain() }" ) } ) else s end s = source.methods(s) s.on_frame(synchronous=true, update) if track_sensitive then s.on_track(synchronous=true, fun (_) -> v := 1.) end amplify(id=id, {v()}, delay_line(lookahead, s)).{ rms=rms, gain={v()}, target_gain=target_gain } end # Swap two channels of a stereo source. # @category Source / Conversion def swap(id=null("swap"), s) = tracks = source.tracks(s) source(id=id, tracks.{audio=track.audio.swap(tracks.audio)}) end # Produce mono audio by taking the mean of all audio channels. # @category Source / Conversion # @argsof track.audio.mean def mean(~id=null("mean"), %argsof(track.audio.mean[!id]), s) = tracks = source.tracks(s) source( id=id, tracks.{audio=track.audio.mean(%argsof(track.audio.mean), tracks.audio)} ) end # Convert any pcm audio source into a stereo source. # @category Source / Conversion def stereo(~id=null("stereo"), s) = tracks = source.tracks(s) source(id=id, tracks.{audio=track.audio.stereo(tracks.audio)}) end # Extract the left channel of a stereo track # @category Source / Conversion # @param t Track to extract from def track.audio.stereo.left(~id=null("track.audio.stereo.left"), t) = track.audio.amplify( id=id, override=null, 2., track.audio.mean(track.audio.stereo.pan(-1., t)) ) end # Extract the left channel of a stereo source # @category Source / Conversion # @param s Source to extract from def stereo.left(~id=null("stereo.left"), s) = tracks = source.tracks(s) source(id=id, tracks.{audio=track.audio.stereo.left(tracks.audio)}) end # Extract the right channel of a stereo track # @category Source / Conversion # @param s Track to extract from def track.audio.stereo.right(~id=null("track.audio.stereo.right"), t) = track.audio.amplify( id=id, override=null, 2., track.audio.mean(track.audio.stereo.pan(1., t)) ) end # Extract the right channel of a stereo source # @category Source / Conversion # @param s Source to extract from def stereo.right(~id=null("stereo.right"), s) = tracks = source.tracks(s) source(id=id, tracks.{audio=track.audio.stereo.right(tracks.audio)}) end # Spacializer which allows controlling the width of the signal. # @category Source / Audio processing # @param w Width of the signal (-1: mono, 0.: original, 1.: wide stereo). def stereo.width(~id=null("stereo.width"), w=getter(0.), (s:source)) = tracks = source.tracks(s) source(id=id, tracks.{audio=track.audio.stereo.width(w, tracks.audio)}) end # Pan a stereo sound. # @category Source / Audio processing # @argsof track.audio.stereo.pan # @param pan Pan value. Should be between `-1` (left side) and `1` (right side). def stereo.pan( ~id=null("stereo.pan"), %argsof(track.audio.stereo.pan[!id]), pan, (s:source) ) = tracks = source.tracks(s) source( id=id, tracks.{ audio= track.audio.stereo.pan( %argsof(track.audio.stereo.pan), pan, tracks.audio ) } ) end # Slow down or accelerate an audio stream by stretching (sounds lower) or squeezing it (sounds higher). # @category Source / Audio processing # @argsof track.audio.stretch def stretch( ~id=null("stretch"), %argsof(track.audio.stretch[!id]), (s:source) ) = tracks = source.tracks(s) source.audio( id=id, track.audio.stretch(%argsof(track.audio.stretch), tracks.audio) ) end let stereo.ms = () # Decode mid+side stereo (M/S) to left+right stereo. # @category Source / Audio processing # @argsof track.audio.stereo.ms.decode def stereo.ms.decode( ~id=null("stereo.ms.decode"), %argsof(track.audio.stereo.ms.decode[!id]), (s:source) ) = tracks = source.tracks(s) source( id=id, tracks.{ audio= track.audio.stereo.ms.decode( %argsof(track.audio.stereo.ms.decode), tracks.audio ) } ) end # Encode left+right stereo to mid+side stereo (M/S). # @category Source / Audio processing # @argsof track.audio.stereo.ms.encode def stereo.ms.encode( ~id=null("stereo.ms.encode"), %argsof(track.audio.stereo.ms.encode[!id]), (s:source) ) = tracks = source.tracks(s) source( id=id, tracks.{ audio= track.audio.stereo.ms.encode( %argsof(track.audio.stereo.ms.encode), tracks.audio ) } ) end %ifdef track.audio.stereotool # Process an audio source using stereotool # @argsof track.audio.stereotool # @category Source / Audio processing def stereotool( ~id=null("stereotool"), %argsof(track.audio.stereotool[!id]), s ) = let {audio, metadata, track_marks, ..._} = source.tracks(s) s = track.audio.stereotool(%argsof(track.audio.stereotool[!id]), audio) let replaces s = source(id=id, {audio=(s : pcm), metadata=metadata, track_marks=track_marks}) s end # Output an audio source using stereotool # @argsof track.audio.stereotool[!active] # @argsof output.dummy[!id] # @category Source / Output def output.stereotool( ~id=null("output.stereotool"), %argsof(track.audio.stereotool[!id,!active]), %argsof(output.dummy[!id]), s ) = s = stereotool(%argsof(track.audio.stereotool[!id,!active]), active=false, s) let replaces s = output.dummy(id=id, %argsof(output.dummy[!id]), s) s end %endif # Defer the source's audio track by a given amount of time. Source will be # available when the given `delay` has been fully buffered. Use this operator # instead of `buffer` when buffering large amount of data as initial delay. # # This operator encodes and decodes the audio content. See `defer.pcm_s16` for # a low-level operator using directly the `pcm_s16` format. # @argsof track.audio.defer[!id] # @param ~id Force the source's ID def defer(~id=null("defer"), %argsof(track.audio.defer[!id]), s) = let {audio = a} = source.tracks(s) a = track.encode.audio.pcm_s16(a) a = track.audio.defer(%argsof(track.audio.defer[!id]), a) a = track.decode.audio.pcm_s16(a) source( id=id, {audio=a, metadata=track.metadata(a), track_marks=track.track_marks(a)} ) end # Defer the source's audio track by a given amount of time. Source will be # available when the given `delay` has been fully buffered. Use this operator # instead of `buffer` when buffering large amount of data as initial delay. # # This operator uses a source already using `pcm_s16` audio data. It can # be used to prevent unneeded data copy. Typically, decoders that know # how to decode to `pcm_s16` (like `ffmpeg`) will decode directly # into the format and encoders who support it (also `%ffmpeg`) # will encoder directly from the `pcm_s16` data. Use `defer` if you # prefer a more user-friendly operator. # @argsof track.audio.defer[!id] # @param ~id Force the source's ID def defer.pcm_s16( ~id=null("defer.pcm_s16"), %argsof(track.audio.defer[!id]), (s:source(audio=pcm_s16)) ) = let {audio = a} = source.tracks(s) a = track.audio.defer(%argsof(track.audio.defer[!id]), a) source( id=id, {audio=a, metadata=track.metadata(a), track_marks=track.track_marks(a)} ) end let settings.normalize_track_gain_metadata = settings.make( description="Metadata used to store track gain normalization metadata", "liq_normalize_track_gain" ) # Amplify source tracks according to track gain normalization metadata. This operator does not # compute that value. You can use integrated LUFS track gain or ReplayGain to compute it. # @category Source / Audio processing # @param ~id Force the value of the source ID. # @param s Source to be amplified. def normalize_track_gain(~id=null, s) = amplify(id=id, override=settings.normalize_track_gain_metadata(), 1., s) end liquidsoap-2.4.2/src/libs/autocue.liq000066400000000000000000000742031513273233300175750ustar00rootroot00000000000000# Initialize settings for autocue let settings.autocue = {internal=()} let settings.autocue.implementations = settings.make( description="All available autocue implementations", [] ) let settings.autocue.metadata = () let settings.autocue.metadata.priority = settings.make( description="Priority for the autocue metadata resolver. Default value \ allows it to override both file and request metadata.", 10 ) let settings.autocue.preferred = settings.make( description="Preferred autocue", "internal" ) let settings.autocue.amplify_behavior = settings.make( description="How to proceed with loudness adjustment. Set to `\"override\"` to always prefer the value provided by the `autocue` provider. Set to `\"ignore\"` to ignore all loudness correction provided via the `autocue` provider. Set to `\"keep\"` to always prefer user-provided values (via request annotation or file tags) over values provided by the `autocue` provider.", "override" ) let settings.autocue.amplify_aliases = settings.make( description="List of metadata to treat as amplify aliases when applying the \ `amplify_behavior` policy.", ["replaygain_track_gain"] ) let settings.autocue.internal.metadata_override = settings.make( description="Disable processing when one of these metadata is found", [ "liq_cue_in", "liq_cue_out", "liq_fade_in", "liq_fade_in_delay", "liq_fade_out", "liq_fade_out_delay", "liq_disable_autocue" ] ) let settings.autocue.internal.lufs_target = settings.make( description="Loudness target", -14.0 ) let settings.autocue.internal.cue_in_threshold = settings.make( description="Cue in threshold", -34.0 ) let settings.autocue.internal.cue_out_threshold = settings.make( description="Cue out threshold", -42.0 ) let settings.autocue.internal.cross_threshold = settings.make( description="Crossfade start threshold", -7.0 ) let settings.autocue.internal.max_overlap = settings.make( description="Maximum allowed overlap/crossfade in seconds", 6.0 ) let settings.autocue.internal.sustained_endings_enabled = settings.make( description="Try to optimize crossfade point on sustained endings", true ) let settings.autocue.internal.sustained_endings_dropoff = settings.make( description="Max. loudness drop off immediately after crossfade point to \ consider it as relevant ending [percentage]", 15.0 ) let settings.autocue.internal.sustained_endings_slope = settings.make( description="Max. loudness difference between crossfade point and cue out to \ consider it as relevant ending [percentage]", 20.0 ) let settings.autocue.internal.sustained_endings_min_duration = settings.make( description="Minimum duration to consider it the ending as sustained \ [seconds]", 1.0 ) let settings.autocue.internal.sustained_endings_threshold_limit = settings.make( description="Max reduction of dB thresholds compared to initial value \ [multiplying factor]", 2.0 ) let settings.autocue.internal.ratio = settings.make( description="Maximum real time ratio to control speed of LUFS data analysis", 70. ) let settings.autocue.internal.timeout = settings.make( description="Maximum allowed processing time (estimated)", 10. ) let autocue = {internal=()} # Register an `autocue` implementation. # @category Source / Audio processing # @param ~name Name of the implementation def autocue.register(~name, fn) = current_implementations = settings.autocue.implementations() if list.assoc.mem(name, current_implementations) then error.raise( error.invalid, "Autocue implementation #{name} already exists!" ) end settings.autocue.implementations := [(name, fn), ...current_implementations] end # Get frames from ffmpeg.filter.ebur128 # @flag hidden def autocue.internal.ebur128(~duration, ~ratio=50., ~timeout=10., filename) = ignore(ratio) ignore(timeout) ignore(filename) ignore(duration) %ifdef ffmpeg.filter.ebur128 estimated_processing_time = duration / ratio if estimated_processing_time > timeout or duration <= 0. then log( level=2, label="autocue.internal", "Estimated processing duration is too long, autocue disabled! #{ duration } / #{ratio} = #{estimated_processing_time} (Duration / Ratio = Processing \ duration; max. allowed: #{timeout})" ) [] else r = request.create(resolve_metadata=false, filename) frames = ref([]) def process(s) = def ebur128(s) = def mk_filter(graph) = let {audio = a} = source.tracks(s) a = ffmpeg.filter.audio.input(graph, a) let ([a], _) = ffmpeg.filter.ebur128(metadata=true, graph, a) # ebur filter seems to generate invalid PTS. a = ffmpeg.filter.asetpts(expr="N/SR/TB", graph, a) a = ffmpeg.filter.audio.output(id="filter_output", graph, a) source({audio=a, metadata=track.metadata(a)}) end ffmpeg.filter.create(mk_filter) end s = ebur128(s) s.on_metadata(synchronous=true, fun (m) -> frames := [...frames(), m]) s end request.process(ratio=ratio, process=process, r) frames() end %else ignore(ratio) ignore(timeout) ignore(filename) log( level=2, label="autocue.internal", "ffmpeg.filter.ebur128 is not available, autocue disabled!" ) [] %endif end # Compute autocue data # @flag hidden def autocue.internal.implementation( ~request_metadata, ~file_metadata, filename ) = lufs_target = settings.autocue.internal.lufs_target() cue_in_threshold = settings.autocue.internal.cue_in_threshold() cue_out_threshold = settings.autocue.internal.cue_out_threshold() cross_threshold = settings.autocue.internal.cross_threshold() max_overlap = settings.autocue.internal.max_overlap() sustained_endings_enabled = settings.autocue.internal.sustained_endings_enabled() sustained_endings_dropoff = settings.autocue.internal.sustained_endings_dropoff() sustained_endings_slope = settings.autocue.internal.sustained_endings_slope() sustained_endings_min_duration = settings.autocue.internal.sustained_endings_min_duration() sustained_endings_threshold_limit = settings.autocue.internal.sustained_endings_threshold_limit() ratio = settings.autocue.internal.ratio() timeout = settings.autocue.internal.timeout() metadata_overrides = settings.autocue.internal.metadata_override() metadata = [...request_metadata, ...file_metadata] if list.exists(fun (el) -> list.mem(fst(el), metadata_overrides), metadata) then log( level=2, label="autocue.internal.metadata", "Override metadata detected for #{filename}, disabling autocue!" ) null else log( level=4, label="autocue.internal", "Starting to process #{filename}" ) %ifdef request.duration.ffmpeg duration = request.duration.ffmpeg(resolve_metadata=false, filename) %else duration = null %endif if duration == null then log( level=2, label="autocue.internal", "Could not get request duration, internal autocue disabled!" ) null else duration = null.get(duration) frames = autocue.internal.ebur128( duration=duration, ratio=ratio, timeout=timeout, filename ) if list.length(frames) < 2 then log( level=2, label="autocue.internal", "Autocue computation failed!" ) null else # Get the 2nd last frame which is the last with loudness data frame = list.nth(frames, list.length(frames) - 2) # Get the Integrated Loudness from the last frame (overall loudness) lufs = float_of_string( list.assoc(default=string(lufs_target), "lavfi.r128.I", frame) ) # Calc LUFS difference to target for liq_amplify lufs_correction = lufs_target - lufs # Create dB thresholds relative to LUFS target lufs_cue_in_threshold = lufs + cue_in_threshold lufs_cue_out_threshold = lufs + cue_out_threshold lufs_cross_threshold = lufs + cross_threshold log( level=4, label="autocue.internal", "Processing results for #{filename}" ) log( level=4, label="autocue.internal", "lufs_correction: #{lufs_correction}" ) log( level=4, label="autocue.internal", "lufs_cue_in_threshold: #{lufs_cue_in_threshold}" ) log( level=4, label="autocue.internal", "lufs_cue_out_threshold: #{lufs_cue_out_threshold}" ) log( level=4, label="autocue.internal", "lufs_cross_threshold: #{lufs_cross_threshold}" ) # Set cue/fade defaults cue_in = ref(0.) cue_out = ref(0.) cross_cue = ref(0.) fade_in = ref(0.) fade_out = ref(0.) # Extract timestamps for cue points # Iterate over loudness data frames and set cue points based on db thresholds last_ts = ref(0.) current_ts = ref(0.) cue_found = ref(false) cross_start_idx = ref(0.) cross_stop_idx = ref(0.) cross_mid_idx = ref(0.) cross_frame_length = ref(0.) ending_fst_db = ref(0.) ending_snd_db = ref(0.) reset_iter_values = ref(true) frames_rev = list.rev(frames) total_frames_length = float_of_int(list.length(frames)) frame_idx = ref(total_frames_length - 1.) lufs_cross_threshold_sustained = ref(lufs_cross_threshold) lufs_cue_out_threshold_sustained = ref(lufs_cue_out_threshold) err = error.register("assoc") def find_cues( frame, ~reverse_order=false, ~sustained_ending_check=false, ~sustained_ending_recalc=false ) = if reset_iter_values() then last_ts := 0. current_ts := 0. cue_found := false end # Get current frame loudness level and timestamp db_level = list.assoc(default="nan", string("lavfi.r128.M"), frame) current_ts := float_of_string(list.assoc(default="0.", "lavfi.liq.pts", frame)) # Process only valid level values if db_level != "nan" then db_level = float_of_string(db_level) if not sustained_ending_check and not sustained_ending_recalc then # Run regular cue point calc reset_iter_values := false if not reverse_order then # Search for cue in if db_level > lufs_cue_in_threshold then # First time exceeding threshold cue_in := last_ts() # Break error.raise( err, "break list.iter" ) end else # Search for cue out and crossfade point starting from the end (reversed) if db_level > lufs_cue_out_threshold and not cue_found() then # Cue out cue_out := last_ts() cross_stop_idx := frame_idx() cue_found := true elsif db_level > lufs_cross_threshold then # Absolute crossfade cue cross_cue := last_ts() cross_start_idx := frame_idx() # Break error.raise( err, "break list.iter" ) end frame_idx := frame_idx() - 1. end elsif sustained_ending_check then # Check regular crossfade data for sustained ending if reset_iter_values() then frame_idx := total_frames_length - 1. cross_start_idx := cross_start_idx() + 5. cross_stop_idx := cross_stop_idx() - 5. cross_frame_length := cross_stop_idx() - cross_start_idx() cross_mid_idx := cross_stop_idx() - (cross_frame_length() / 2.) end reset_iter_values := false if frame_idx() < cross_start_idx() or cross_frame_length() < sustained_endings_min_duration * 10. then error.raise( err, "break list.iter" ) end if frame_idx() < cross_stop_idx() and frame_idx() > cross_mid_idx() then if ending_snd_db() < 0. then ending_snd_db := (ending_snd_db() + db_level) / 2. else ending_snd_db := db_level end end if frame_idx() > cross_start_idx() and frame_idx() < cross_mid_idx() then if ending_fst_db() < 0. then ending_fst_db := (ending_fst_db() + db_level) / 2. else ending_fst_db := db_level end end frame_idx := frame_idx() - 1. elsif sustained_ending_recalc then # Recalculate crossfade on sustained ending if reset_iter_values() then cue_out := 0. cross_cue := 0. end reset_iter_values := false if db_level > lufs_cue_out_threshold_sustained() and not cue_found() then # Cue out cue_out := last_ts() cue_found := true end if db_level > lufs_cross_threshold_sustained() then # Absolute crossfade cue cross_cue := current_ts() error.raise( err, "break list.iter" ) end end # Update last timestamp value with current last_ts := current_ts() end end # Search for cue_in first reset_iter_values := true def cue_iter_fwd(frame) = find_cues(frame) end try list.iter(cue_iter_fwd, frames) catch _ do log( level=4, label="autocue.internal", "cue_iter_fwd completed." ) end # Reverse frames and search in reverse order for cross_cue and cue_out reset_iter_values := true def cue_iter_rev(frame) = find_cues(frame, reverse_order=true) end try list.iter(cue_iter_rev, frames_rev) catch _ do log( level=4, label="autocue.internal", "cue_iter_rev completed." ) end if sustained_endings_enabled then # Check for sustained ending reset_iter_values := true def sustained_ending_check_iter(frame) = find_cues(frame, sustained_ending_check=true) end try list.iter(sustained_ending_check_iter, frames_rev) catch _ do log( level=4, label="autocue.internal.sustained_ending", "sustained_ending_check_iter completed." ) end log( level=4, label="autocue.internal.sustained_ending", "Analysis frame length: #{cross_frame_length()}" ) log( level=4, label="autocue.internal.sustained_ending", "Avg. ending loudness: #{ending_fst_db()} => #{ending_snd_db()}" ) # Check whether data indicate a sustained ending if ending_fst_db() < 0. then slope = ref(0.) dropoff = lufs_cross_threshold / ending_fst_db() if ending_snd_db() < 0. then slope := ending_fst_db() / ending_snd_db() end log( level=4, label="autocue.internal.sustained_ending", "Drop off: #{(1. - dropoff) * 100.}%" ) log( level=4, label="autocue.internal.sustained_ending", "Slope: #{(1. - slope()) * 100.}%" ) detect_slope = slope() > 1. - sustained_endings_slope / 100. detect_dropoff = ending_fst_db() > lufs_cross_threshold * (sustained_endings_dropoff / 100. + 1.) if detect_slope or detect_dropoff then log( level=3, label="autocue.internal.sustained_ending", "Sustained ending detected (drop off: #{detect_dropoff} / slope: \ #{detect_slope})" ) if detect_slope then lufs_cross_threshold_sustained := max( lufs_cross_threshold * sustained_endings_threshold_limit, ending_snd_db() - 0.5 ) else lufs_cross_threshold_sustained := max( lufs_cross_threshold * sustained_endings_threshold_limit, ending_fst_db() - 0.5 ) end lufs_cue_out_threshold_sustained = ref( max( lufs_cue_out_threshold * sustained_endings_threshold_limit, lufs_cue_out_threshold + (lufs_cross_threshold_sustained() - lufs_cross_threshold) ) ) log( level=4, label="autocue.internal.sustained_ending", "Changed crossfade threshold: #{lufs_cross_threshold} => #{ lufs_cross_threshold_sustained() }" ) log( level=4, label="autocue.internal.sustained_ending", "Changed cue out threshold: #{lufs_cue_out_threshold} => #{ lufs_cue_out_threshold_sustained() }" ) cross_cue_init = cross_cue() cue_out_init = cue_out() reset_iter_values := true def sustained_ending_recalc_iter(frame) = find_cues(frame, sustained_ending_recalc=true) end try list.iter(sustained_ending_recalc_iter, frames_rev) catch _ do log( level=4, label="autocue.internal", "sustained_ending_recalc_iter completed." ) end log( level=4, label="autocue.internal.sustained_ending", "Changed crossfade point: #{cross_cue_init} => #{cross_cue()}" ) log( level=4, label="autocue.internal.sustained_ending", "Changed cue out point: #{cue_out_init} => #{cue_out()}" ) else log( level=3, label="autocue.internal.sustained_ending", "No sustained ending detected." ) end else log( level=3, label="autocue.internal.sustained_ending", "No sustained ending detected." ) end end # Finalize cue/cross/fade values now... if cue_out() == 0. then cue_out := duration end # Calc cross/overlap duration if cross_cue() + 0.1 < cue_out() then fade_out := cue_out() - cross_cue() end # Add some margin to cue in cue_in := cue_in() - 0.1 # Avoid hard cuts on cue in if cue_in() > 0.2 then fade_in := 0.2 cue_in := cue_in() - 0.2 end # Ignore super short cue in if cue_in() <= 0.2 then fade_in := 0. cue_in := 0. end # Limit overlap duration to maximum if max_overlap < fade_in() then fade_in := max_overlap end if max_overlap < fade_out() then cue_shift = fade_out() - max_overlap cue_out := cue_out() - cue_shift fade_out := max_overlap fade_out := max_overlap end ( { amplify= "#{lufs_correction} dB", cue_in=cue_in(), cue_out=cue_out(), fade_in=fade_in(), fade_out=fade_out() } : { amplify?: string, cue_in: float, cue_out: float, fade_in: float, fade_in_type?: string, fade_in_curve?: float, fade_out: float, fade_out_type?: string, fade_out_curve?: float, start_next?: float, extra_metadata?: [(string * string)] } ) end end end end autocue.register(name="internal", autocue.internal.implementation) # Translate autocue values into internal metadara # @flag hidden def autocue.metadata(~implementation, autocue) = let {cue_in, cue_out, fade_in, fade_out} = autocue extra_metadata = autocue.extra_metadata ?? [] amplify = autocue?.amplify fade_in_type = autocue?.fade_in_type fade_in_curve = autocue?.fade_in_curve fade_out_type = autocue?.fade_out_type fade_out_curve = autocue?.fade_out_curve fade_out_start = cue_out - fade_out let (fade_out, fade_out_start) = if fade_out_start < 0. then log( level=2, label="autocue", "Invalid cue_out/fade_out values: #{cue_out}/#{fade_out}" ) (0., cue_out) else (fade_out, fade_out_start) end start_next = autocue.start_next ?? fade_out_start start_next = if start_next < cue_in or cue_out < start_next then log( level=2, label="autocue", "Invalid start_next: #{start_next}" ) fade_out_start else start_next end fade_out_start_next = if fade_out_start < start_next then start_next - fade_out_start else 0. end let fade_out_delay = if start_next < fade_out_start then fade_out_start - start_next else 0. end total_fade_out = fade_out + fade_out_delay max_start_duration = cue_out - cue_in - total_fade_out opt_arg = fun (lbl, v) -> null.defined(v) ? [(lbl, string(v))] : [] [ ("liq_autocue", implementation), ...opt_arg("liq_amplify", amplify), ("liq_cue_in", string(cue_in)), ("liq_cue_out", string(cue_out)), ("liq_cross_start_duration", string(fade_in)), ("liq_cross_max_start_duration", string(max_start_duration)), ("liq_cross_end_duration", string(total_fade_out)), ("liq_fade_in", string(fade_in)), ...opt_arg("liq_fade_in_type", fade_in_type), ...opt_arg("liq_fade_in_curve", fade_in_curve), ("liq_fade_out", string(fade_out)), ("liq_fade_out_start_next", string(fade_out_start_next)), ("liq_fade_out_delay", string(fade_out_delay)), ...opt_arg("liq_fade_out_type", fade_out_type), ...opt_arg("liq_fade_out_curve", fade_out_curve), ...extra_metadata ] end let file.autocue = () # Return the file's autocue values as metadata suitable for metadata override. # @category Source / Audio processing def file.autocue.metadata(~request_metadata, uri) = preferred_implementation = settings.autocue.preferred() implementations = settings.autocue.implementations() autocue_metadata = autocue.metadata let (implementation_name, implementation) = if list.assoc.mem(preferred_implementation, implementations) then log( level=4, label="autocue", "Using preferred #{preferred_implementation} autocue implementation." ) ( preferred_implementation, list.assoc(preferred_implementation, implementations) ) elsif list.length(implementations) > 0 then let [(name, implementation)] = implementations log( level=4, label="autocue", "Using first available #{name} autocue implementation." ) (name, implementation) else error.raise( error.not_found, "No autocue implementation found!" ) end r = request.create( excluded_metadata_resolvers=decoder.metadata.reentrant(), uri ) if not request.resolve(r) then request.destroy(r) log( level=2, label="autocue", "Couldn't resolve uri: #{uri}" ) [] else autocue = try autocue = implementation( request_metadata=request_metadata, file_metadata=request.metadata(r), request.filename(r) ) request.destroy(r) autocue catch err do request.destroy(r) log( level=2, label="autocue", "Error while processing autocue: #{err}" ) error.raise(err) end if null.defined(autocue) then autocue_metadata(implementation=implementation_name, null.get(autocue)) else log( level=2, label="autocue.metadata", "No autocue data returned for file #{uri}" ) [] end end end # Enable autocue metadata resolver. This resolver will process any file # decoded by Liquidsoap and add cue-in/out and crossfade metadata when these # values can be computed. This function sets `settings.request.prefetch` to `2` # to account for the latency introduced by the `autocue` computation when resolving # reausts. For a finer-grained processing, use the `autocue:` protocol. # @category Liquidsoap def enable_autocue_metadata() = if settings.request.prefetch() == 1 then settings.request.prefetch := 2 end def autocue_metadata(~metadata, fname) = metadata_overrides = settings.autocue.internal.metadata_override() if list.exists(fun (el) -> list.mem(fst(el), metadata_overrides), metadata) then log( level=2, label="autocue.metadata", "Override metadata detected for #{fname}, disabling autocue!" ) [] else autocue_metadata = file.autocue.metadata(request_metadata=metadata, fname) all_amplify = [...settings.autocue.amplify_aliases(), "liq_amplify"] user_supplied_amplify = list.filter_map( fun (el) -> if list.mem(fst(el), all_amplify) then fst(el) else null end, metadata ) user_supplied_amplify_labels = string.concat( separator=", ", user_supplied_amplify ) autocue_metadata = if settings.autocue.amplify_behavior() == "ignore" then [...list.assoc.remove("liq_amplify", autocue_metadata)] else if user_supplied_amplify != [] then if settings.autocue.amplify_behavior() == "keep" then log( level=3, label="autocue.metadata", "User-supplied amplify metadata detected: #{ user_supplied_amplify_labels }, keeping user-provided data." ) list.assoc.remove("liq_amplify", autocue_metadata) elsif settings.autocue.amplify_behavior() == "override" then log( level=3, label="autocue.metadata", "User-supplied amplify metadata detected: #{ user_supplied_amplify_labels }, overriding with autocue data." ) [ ...autocue_metadata, # This replaces all user-provided tags with the value returned by # the autocue implementation. ...list.map( fun (lbl) -> (lbl, autocue_metadata["liq_amplify"]), user_supplied_amplify ) ] else log( level=2, label="autocue.metadata", "Invalid value for `settings.autocue.amplify_behavior`: #{ settings.autocue.amplify_behavior() }" ) autocue_metadata end else autocue_metadata end end log(level=4, label="autocue.metadata", "#{autocue_metadata}") autocue_metadata end end %ifdef settings.decoder.mime_types.ffmpeg mime_types = settings.decoder.mime_types.ffmpeg() file_extensions = settings.decoder.file_extensions.ffmpeg() %else mime_types = null file_extensions = null %endif decoder.metadata.add( mime_types=mime_types, file_extensions=file_extensions, priority=settings.autocue.metadata.priority, reentrant=true, "autocue", autocue_metadata ) end # Define autocue protocol # @flag hidden def protocol.autocue(~rlog:_, ~maxtime:_, arg) = cue_metadata = file.autocue.metadata(request_metadata=[], arg) if cue_metadata != [] then cue_metadata = list.map(fun (el) -> "#{fst(el)}=#{string.quote(snd(el))}", cue_metadata) cue_metadata = string.concat(separator=",", cue_metadata) "annotate:#{cue_metadata}:#{arg}" else log( level=2, label="autocue.protocol", "No autocue data returned for URI #{arg}!" ) arg end end protocol.add( "autocue", protocol.autocue, doc="Adding automatically computed cues/crossfade metadata", syntax="autocue:uri" ) liquidsoap-2.4.2/src/libs/clock.liq000066400000000000000000000015371513273233300172230ustar00rootroot00000000000000# Create a new clock and assign it to a list of sources. # @category Liquidsoap # @param ~sync Synchronization mode. One of: `"auto"`, `"cpu"`, `"passive"` or \ # `"none"`. Defaults to `"auto"`, which synchronizes with the CPU \ # clock if none of the active sources are attached to their own \ # clock (e.g. ALSA input, etc). `"cpu"` always synchronizes with \ # the CPU clock. `"none"` removes all synchronization control. # @param ~on_error Error callback executed when a streaming error occurs. \ # When passed, all streaming errors are silenced. Intended \ # mostly for debugging purposes. def clock.assign_new(~sync="auto", ~id=null, ~on_error=null, sources) = c = clock.create(id=id, sync=sync, on_error=on_error) list.iter(fun (s) -> c.unify(s.clock), sources) end liquidsoap-2.4.2/src/libs/cron.liq000066400000000000000000000030401513273233300170600ustar00rootroot00000000000000let cron.tab = ref([]) # Add an entry to the cron tab. # @param ~id Optional task ID. # @param c Cron entry # @param handler Function to execute # @method id ID to be used to remove the task. # @category Programming def cron.add(~id=null, c, handler) = id = id ?? string.id.default(default="cron.task", null) if list.exists(fun ({id = i}) -> i == id, cron.tab()) then error.raise( error.invalid, "Cron tab entry with ID #{id} already exists!" ) end let {test} = cron.parse(c) log.info( label="cron", "Adding cron.tab entry #{c} (cron id: #{id})" ) cron.tab := [...cron.tab(), {id=id, cron=c, test=test, handler=handler}] {id=id} end # Remove a cron tab entry. ID is returned during # the task's registration # @category Programming def cron.remove(id) = if list.exists(fun ({id = i}) -> i == id, cron.tab()) then log.info( label="cron", "Removing cron.tab entry #{id}" ) cron.tab := list.filter(fun ({id = i}) -> i != id, cron.tab()) else log.important( label="cron", "Cannot remove cron.tab entry #{id}: entry does not exist!" ) end end # Main cron thread. thread.when( fast=false, every=20., {0s-30s}, { list.iter( fun ({id, cron, test, handler}) -> begin if test() then log.info( label="cron", "Executing cron.tab entry #{cron} (id: #{id})" ) thread.run(fast=false, handler) end end, cron.tab() ) } ) liquidsoap-2.4.2/src/libs/dune000066400000000000000000000003371513273233300162740ustar00rootroot00000000000000(install (section (site (liquidsoap-lang libs))) (package liquidsoap) (files (glob_files *.liq))) (install (section (site (liquidsoap-lang libs))) (package liquidsoap) (files (glob_files extra/*.liq))) liquidsoap-2.4.2/src/libs/error.liq000066400000000000000000000023051513273233300172530ustar00rootroot00000000000000let error.assertion = error.register("assertion") let error.clock = error.register("clock") let error.eval = error.register("eval") let error.file = error.register("file") let error.file.cross_device = error.register("file.cross_device") let error.http = error.register("http") let error.invalid = error.register("invalid") let error.json = error.register("json") let error.not_found = error.register("not_found") let error.output = error.register("output") let error.socket = error.register("socket") let error.string = error.register("string") # Ensure that a condition is satisfied (raise `error.assertion` exception # otherwise). # @category Programming # @param c Condition which should be satisfied. def assert(c) = if not c then error.raise( error.assertion, "Assertion failed." ) end end let error.failure = error.register("failure") # Major failure. # @category Programming # @param msg Explanation about the failure. def failwith(msg) = error.raise(error.failure, msg) end # Return error kind # @category Programming def error.kind(err) = error.methods(err).kind end # Return error message # @category Programming def error.message(err) = error.methods(err).message end liquidsoap-2.4.2/src/libs/extra/000077500000000000000000000000001513273233300165365ustar00rootroot00000000000000liquidsoap-2.4.2/src/libs/extra/audio.liq000066400000000000000000000360241513273233300203530ustar00rootroot00000000000000# Compand the signal. # @category Source / Audio processing # @flag extra # @argsof track.audio.compand def compand(~id=null("compand"), %argsof(track.audio.compand[!id]), s) = tracks = source.tracks(s) source( id=id, tracks.{ audio=track.audio.compand(%argsof(track.audio.compand), tracks.audio) } ) end # Comb filter # @category Source / Audio processing # @argsof track.audio.comb # @flag extra def comb(~id=null("comb"), %argsof(track.audio.comb[!id]), s) = tracks = source.tracks(s) source( id=id, tracks.{audio=track.audio.comb(%argsof(track.audio.comb), tracks.audio)} ) end # Compress the signal. # @category Source / Audio processing # @argsof track.audio.compress # @flag extra def compress(%argsof(track.audio.compress), s) = tracks = source.tracks(s) let {gain, rms, ...audio} = track.audio.compress(%argsof(track.audio.compress), tracks.audio) source(id=id, tracks.{audio=audio}).{gain=gain, rms=rms} end # Exponential compressor. # @category Source / Audio processing # @argsof track.audio.compress.exponential # @flag extra def compress.exponential(%argsof(track.audio.compress.exponential), s) = tracks = source.tracks(s) source( id=id, tracks.{ audio= track.audio.compress.exponential( %argsof(track.audio.compress.exponential), tracks.audio ) } ) end # A limiter. This is a `compress` with tweaked parameters. # @category Source / Audio processing # @flag extra def limit( ~id=null, ~attack=getter(50.), ~release=getter(200.), ~ratio=getter(20.), ~threshold=getter(-2.), ~pre_gain=getter(0.), ~gain=getter(0.), s ) = compress( id=id, attack=attack, release=release, ratio=ratio, threshold=threshold, pre_gain=pre_gain, gain=gain, s ) end let limiter = limit # A bandpass filter obtained by chaining a low-pass and a high-pass filter. # @category Source / Audio processing # @flag extra # @param id Force the value of the source ID. # @param ~low Lower frequency of the bandpass filter. # @param ~high Higher frequency of the bandpass filter. # @param ~q Q factor. def filter.iir.eq.low_high(~id=null, ~low, ~high, ~q=1., s) = s = if not (getter.is_constant(high) and getter.get(high) == infinity) then filter.iir.eq.low(id=id, frequency=high, q=q, s) else s end s = if not (getter.is_constant(low) and getter.get(low) == 0.) then filter.iir.eq.high(id=id, frequency=low, q=q, s) else s end s end # Multiband compression. The list in argument specifies # - the `frequency` below which we should apply compression (it is above previous band) # - the `attack` time (ms) # - the `release` time (ms) # - the compression `ratio` # - the `threshold` for compression # - the `gain` for the band # @category Source / Audio processing # @param ~limit Also apply limiting to bands. # @param l Parameters for compression bands. # @param s Source on which multiband compression should be applied. # @flag extra def compress.multiband(~limit=true, ~wet=getter(1.), s, l) = # Check that the bands are with increasing frequencies. for i = 0 to list.length(l) - 2 do if getter.get(list.nth(l, i + 1).frequency) < getter.get(list.nth(l, i).frequency) then failwith( "Bands should be sorted." ) end end # Process a band def band(low, band) = high = if getter.is_constant(band.frequency) and getter.get(band.frequency) >= float_of_int(audio.samplerate()) / 2. then infinity else band.frequency end s = filter.iir.eq.low_high(low=low, high=high, s) s = compress( attack=band.attack, release=band.release, threshold=band.threshold, ratio=band.ratio, gain=band.gain, s ) if limit then limiter(s) else s end end ls = list.mapi( fun (i, b) -> band(if i == 0 then 0. else list.nth(l, i - 1).frequency end, b), l ) c = add(normalize=false, ls) s = if not getter.is_constant(wet) or getter.get(wet) != 1. then add( normalize=false, [amplify({1. - getter.get(wet)}, s), amplify(wet, c)] ) else c end # Seal l element type if false then () else list.hd(l) end # Limit to avoid bad surprises limiter(s) end # Compress and normalize, producing a more uniform and "full" sound. # @category Source / Audio processing # @flag extra # @param s The input source. def nrj(s) = compress(threshold=-15., ratio=3., gain=3., normalize(s)) end # Multiband-compression. # @category Source / Audio processing # @flag extra # @param s The input source. def sky(s) = # 3-band crossover low = fun (s) -> filter.iir.eq.low(frequency=168., s) mh = fun (s) -> filter.iir.eq.high(frequency=100., s) mid = fun (s) -> filter.iir.eq.low(frequency=1800., s) high = fun (s) -> filter.iir.eq.high(frequency=1366., s) # Add back add( normalize=false, [ compress( attack=100., release=200., threshold=-20., ratio=6., gain=6.7, knee=0.3, low(s) ), compress( attack=100., release=200., threshold=-20., ratio=6., gain=6.7, knee=0.3, mid(mh(s)) ), compress( attack=100., release=200., threshold=-20., ratio=6., gain=6.7, knee=0.3, high(s) ) ] ) end # Add some bass to the sound. # @category Source / Audio processing # @param ~frequency Frequency below which sound is considered as bass. # @param ~gain Amount of boosting (dB). # @param s Source whose bass should be boosted # @flag extra def bass_boost(~frequency=getter(200.), ~gain=getter(10.), s) = bass = limit(pre_gain=gain, filter.iir.eq.low(frequency=frequency, s)) add([s, bass]) end %ifdef soundtouch # Increases the pitch, making voices sound like on helium. # @category Source / Audio processing # @flag extra # @param s The input source. def helium(s) = soundtouch(pitch=1.5, s) end %endif # Remove low frequencies often produced by microphones. # @flag extra # @category Source / Audio processing # @param ~frequency Frequency under which sound should be lowered. # @param s The input source. def mic_filter(~frequency=200., s) = filter(freq=frequency, q=1., mode="high", s) end # Mix between dry and wet sources. Useful for testing effects. Typically: # ``` # c = interactive.float("wetness", min=0., max=1., 1.) # s = dry_wet(c, s, effect(s)) # ``` # and vary `c` to hear the difference between the source without and with # the effect. # @flag extra # @category Source / Audio processing # @param ~power If `true` use constant power mixing. # @param wetness Wetness coefficient, from 0 (fully dry) to 1 (fully wet). # @param dry Dry source. # @param wet Wet source. def dry_wet(~power=false, wetness, dry, wet) = add( power=power, weights=[getter.map(fun (x) -> 1. - x, wetness), wetness], [dry, wet] ) end # Generate DTMF tones. # @flag extra # @category Source / Sound synthesis # @param ~duration Duration of a tone (in seconds). # @param ~delay Dealy between two successive tones (in seconds). # @param dtmf String describing DTMF tones to generates: it should contains characters 0 to 9, A to D, or * or #. def replaces dtmf(~duration=0.1, ~delay=0.05, dtmf) = l = ref([]) for i = 0 to string.bytes.length(dtmf) - 1 do c = string.sub(encoding="ascii", dtmf, start=i, length=1) let (row, col) = if c == "1" then (697., 1209.) elsif c == "2" then (697., 1336.) elsif c == "3" then (697., 1477.) elsif c == "A" then (697., 1633.) elsif c == "4" then (770., 1209.) elsif c == "5" then (770., 1336.) elsif c == "6" then (770., 1477.) elsif c == "B" then (770., 1633.) elsif c == "7" then (852., 1209.) elsif c == "8" then (852., 1336.) elsif c == "9" then (852., 1477.) elsif c == "C" then (852., 1633.) elsif c == "*" then (941., 1209.) elsif c == "0" then (941., 1336.) elsif c == "#" then (941., 1477.) elsif c == "D" then (941., 1633.) else (0., 0.) end s = add([sine(row, duration=duration), sine(col, duration=duration)]) l := blank(duration=delay)::l() l := s::l() end l = list.rev(l()) sequence(l) end # Mixing table controllable via source methods and optional # server/telnet commands. # @flag extra # @category Source / Audio processing # @param ~id Force the value of the source ID. # @param ~register_server_commands Register corresponding server commands # @param ~normalize Normalize source's volume by the number of mixed sources. def mix(~id=null, ~register_server_commands=true, ~normalize=false, sources) = id = string.id.default(default="mixer", id) inputs = list.map( fun (s) -> begin volume = ref(1.) is_selected = ref(false) is_single = ref(false) {volume=volume, selected=is_selected, single=is_single, source=s} end, sources ) insert_metadata_fn = ref(fun (_) -> ()) sources = list.map( fun (input) -> begin s = amplify(input.volume, input.source) s.on_track( synchronous=true, fun (_) -> if input.single() then input.selected := false end ) s.on_metadata( synchronous=true, fun (m) -> begin fn = insert_metadata_fn() fn(m) end ) switch([(input.selected, s)]) end, inputs ) s = add(normalize=normalize, sources) let {metadata = _, ...tracks} = source.tracks(s) s = source(tracks) insert_metadata_fn := s.insert_metadata let {track_marks = _, ...tracks} = source.tracks(s) s = source(id=id, tracks) if register_server_commands then def status(input) = "ready=#{source.is_ready(input.source)} selected=#{input.selected()} \ single=#{input.single()} volume=#{int_of_float(input.volume() * 100.)}% \ remaining=#{source.remaining(input.source)}" end s.register_command( description="Skip current track on all enabled sources.", "skip", fun (_) -> begin list.iter( fun (input) -> if input.selected() then source.skip(input.source) end, inputs ) "OK" end ) s.register_command( description="Set volume for a given source.", usage="volume ", "volume", fun (v) -> begin try let [i, v] = r/\s/.split(v) input = list.nth(inputs, int_of_string(i)) input.volume := float_of_string(v) status(input) catch _ do "Usage: volume " end end ) s.register_command( description="Enable/disable a source.", usage="select ", "select", fun (arg) -> begin try let [i, b] = r/\s/.split(arg) input = list.nth(inputs, int_of_string(i)) input.selected := (b == "true") status(input) catch _ do "Usage: select " end end ) s.register_command( description="Enable/disable automatic stop at the end of track.", usage="single ", "single", fun (arg) -> begin try let [i, b] = r/\s/.split(arg) input = list.nth(inputs, int_of_string(i)) input.single := (b == "true") status(input) catch _ do "Usage: single " end end ) s.register_command( description="Display current status.", "status", fun (i) -> begin try status(list.nth(inputs, int_of_string(i))) catch _ do "Usage: status " end end ) s.register_command( description="Print the list of input sources.", "inputs", fun (_) -> string.concat( separator=" ", list.map(fun (input) -> source.id(input.source), inputs) ) ) end s.{inputs=inputs} end # Indicate beats. # @category Source / Sound synthesis # @param ~frequency Frequency of the sound. # @param bpm Number of beats per minute. # @flag extra def metronome(~frequency=440., bpm=60.) = volume_down = 0. beat_duration = 0.1 s = sine(frequency) def f() = if s.time() mod (60. / bpm) <= beat_duration then 1. else volume_down end end amplify(f, s) end # Mixes two streams, with faded transitions between the state when only the # normal stream is available and when the special stream gets added on top of # it. # @category Source / Fade # @flag extra # @param ~duration Duration of the fade in seconds. # @param ~p Portion of amplitude of the normal source in the mix. # @param ~normal The normal source, which could be called the carrier too. # @param ~special The special source. def smooth_add(~duration=1., ~p=getter(0.2), ~normal, ~special) = p = getter.function(p) last_p = ref(p()) def c(fn, s) = def v() = fn = fn() fn() end fade.scale(v, s) end special_volume = ref(fun () -> 0.) special = c(special_volume, special) normal_volume = ref(fun () -> 1.) normal = c(normal_volume, normal) def to_special(_, special) = last_p := p() q = 1. - last_p() normal_volume := mkfade(start=1., stop=last_p(), duration=duration, normal) special_volume := mkfade(stop=q, duration=duration, special) special end def to_blank(special, b) = normal_volume := mkfade(start=last_p(), duration=duration, normal) special_volume := mkfade(start=1. - last_p(), duration=duration, special) sequence([special, b]) end special = fallback( track_sensitive=false, transitions=[to_special, to_blank], [special, blank()] ) add(normalize=false, [normal, special]) end %ifencoder %ffmpeg # Output an MPEG-DASH playlist. # @category Source / Output # @flag extra # @param ~id Force the value of the source ID. # @param ~codec Codec to use for audio (following FFmpeg's conventions). # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If true, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. # @param ~playlist Playlist name # @param ~directory Directory to write to def output.file.dash( ~id=null, ~fallible=false, ~codec="libmp3lame", ~bitrate=128, ~start=true, ~playlist="stream.mpd", ~directory, s ) = enc = %ffmpeg(format = "dash", %audio(codec = codec, b = "#{bitrate}k")) output.file( id=id, fallible=fallible, start=start, enc, "#{(directory : string)}/#{playlist}", s ) end %endif liquidsoap-2.4.2/src/libs/extra/audioscrobbler.liq000066400000000000000000000267231513273233300222560ustar00rootroot00000000000000let error.audioscrobbler = error.register("audioscrobbler") let settings.audioscrobbler = settings.make.void( "Audioscrobbler settings" ) let settings.audioscrobbler.api_key = settings.make( description="Default API key for audioscrobbler", "" ) let settings.audioscrobbler.api_secret = settings.make( description="Default API secret for audioscrobbler", "" ) audioscrobbler = () def audioscrobbler.request( ~base_url="http://ws.audioscrobbler.com/2.0", ~api_key=null, ~api_secret=null, params ) = api_key = api_key ?? settings.audioscrobbler.api_key() api_secret = api_secret ?? settings.audioscrobbler.api_secret() if api_key == "" or api_secret == "" then error.raise( error.audioscrobbler, "`api_key` or `api_secret` missing!" ) end params = [("api_key", api_key), ...params] sig_params = list.sort(fun (v, v') -> string.compare(fst(v), fst(v')), params) sig_params = list.map(fun (v) -> "#{fst(v)}#{(snd(v) : string)}", sig_params) sig_params = string.concat(separator="", sig_params) api_sig = string.digest("#{sig_params}#{api_secret}") http.post( base_url, headers=[("Content-Type", "application/x-www-form-urlencoded")], data=http.www_form_urlencoded([...params, ("api_sig", api_sig)]) ) end def audioscrobbler.check_response(resp) = let xml.parse ({lfm = {xml_params = {status}}} : {lfm: {xml_params: {status: string}}} ) = resp if (status == "failed") then error_ref = error let xml.parse ({lfm = {error = {xml_params = {code}}}} : {lfm: {error: string.{ xml_params: {code: int} }}} ) = resp error_ref.raise( error_ref.audioscrobbler, "Error #{code}: #{error}" ) end end def audioscrobbler.auth(~username, ~password, ~api_key=null, ~api_secret=null) = resp = audioscrobbler.request( api_key=api_key, api_secret=api_secret, [ ("method", "auth.getMobileSession"), ("username", username), ("password", password) ] ) audioscrobbler.check_response(resp) try let xml.parse ({lfm = {session = {key}}} : {lfm: {session: {name: string, key: string}}} ) = resp key catch err do error.raise( error.invalid, "Invalid response: #{resp}, error: #{err}" ) end end let audioscrobbler.api = {track=()} # Submit a track to the audioscrobbler # `track.updateNowPlaying` API. # @category Interaction def audioscrobbler.api.track.updateNowPlaying( ~username, ~password, ~session_key=null, ~api_key=null, ~api_secret=null, ~artist, ~track, ~album=null, ~context=null, ~trackNumber=null, ~mbid=null, ~albumArtist=null, ~duration=null ) = session_key = session_key ?? audioscrobbler.auth( username=username, password=password, api_key=api_key, api_secret=api_secret ) params = [ ("track", track), ("artist", artist), ...(null.defined(album) ? [("album", null.get(album))] : [] ), ...(null.defined(context) ? [("context", null.get(context))] : [] ), ...( null.defined(trackNumber) ? [("trackNumber", string((null.get(trackNumber) : int)))] : [] ), ...(null.defined(mbid) ? [("mbid", null.get(mbid))] : [] ), ...( null.defined(albumArtist) ? [("albumArtist", null.get(albumArtist))] : [] ), ...( null.defined(duration) ? [("duration", string((null.get(duration) : int)))] : [] ) ] log.info( label="audioscrobbler.api.track.updateNowPlaying", "Submitting updateNowPlaying with: #{params}" ) resp = audioscrobbler.request( api_key=api_key, api_secret=api_secret, [...params, ("method", "track.updateNowPlaying"), ("sk", session_key)] ) audioscrobbler.check_response(resp) try let xml.parse (v : { lfm: { nowplaying: { track: string.{ xml_params: {corrected: int} }, artist: string.{ xml_params: {corrected: int} }, album: string?.{ xml_params: {corrected: int} }, albumArtist: string?.{ xml_params: {corrected: int} }, ignoredMessage: {xml_params: {code: int}} }, xml_params: {status: string} } } ) = resp log.info( label="audioscrobbler.api.track.updateNowPlaying", "Done submitting updateNowPlaying with: #{params}" ) v catch err do error.raise( error.invalid, "Invalid response: #{resp}, error: #{err}" ) end end # @flag hidden def audioscrobbler.api.apply_meta( ~name, ~username, ~password, ~api_key, ~api_secret, ~session_key, fn, m ) = def c(v) = v == "" ? null : v end track = m["title"] artist = m["artist"] if track == "" or artist == "" then log.info( label=name, "No artist or track present: metadata submission disabled!" ) else album = c(m["album"]) trackNumber = try null.map(int_of_string, c(m["tracknumber"])) catch _ do null end albumArtist = c(m["albumartist"]) ignore( fn( username=username, password=password, api_key=api_key, api_secret=api_secret, session_key=session_key, track=track, artist=artist, album=album, trackNumber=trackNumber, albumArtist=albumArtist ) ) end end # Submit a track using its metadata to the audioscrobbler # `track.updateNowPlaying` API. # @category Interaction def audioscrobbler.api.track.updateNowPlaying.metadata( ~username, ~password, ~session_key=null, ~api_key=null, ~api_secret=null, m ) = audioscrobbler.api.apply_meta( username=username, password=password, session_key=session_key, api_key=api_key, api_secret=api_secret, name="audioscrobbler.api.track.updateNowPlaying", audioscrobbler.api.track.updateNowPlaying, m ) end # Submit a track to the audioscrobbler # `track.scrobble` API. # @category Interaction def audioscrobbler.api.track.scrobble( ~username, ~password, ~session_key=null, ~api_key=null, ~api_secret=null, ~artist, ~track, ~timestamp=null, ~album=null, ~context=null, ~streamId=null, ~chosenByUser=true, ~trackNumber=null, ~mbid=null, ~albumArtist=null, ~duration=null ) = session_key = session_key ?? audioscrobbler.auth( username=username, password=password, api_key=api_key, api_secret=api_secret ) params = [ ("track", track), ("artist", artist), ("timestamp", string(timestamp ?? time())), ...(null.defined(album) ? [("album", null.get(album))] : [] ), ...(null.defined(context) ? [("context", null.get(context))] : [] ), ...(null.defined(streamId) ? [("streamId", null.get(streamId))] : [] ), ("chosenByUser", chosenByUser ? "1" : "0" ), ...( null.defined(trackNumber) ? [("trackNumber", string((null.get(trackNumber) : int)))] : [] ), ...(null.defined(mbid) ? [("mbid", null.get(mbid))] : [] ), ...( null.defined(albumArtist) ? [("albumArtist", null.get(albumArtist))] : [] ), ...( null.defined(duration) ? [("duration", string((null.get(duration) : int)))] : [] ) ] log.info( label="audioscrobbler.api.track.scrobble", "Submitting updateNowPlaying with: #{params}" ) resp = audioscrobbler.request( api_key=api_key, api_secret=api_secret, [...params, ("method", "track.scrobble"), ("sk", session_key)] ) audioscrobbler.check_response(resp) try let xml.parse (v : { lfm: { scrobbles: { scrobble: { track: string.{ xml_params: {corrected: int} }, artist: string.{ xml_params: {corrected: int} }, album: string?.{ xml_params: {corrected: int} }, albumArtist: string?.{ xml_params: {corrected: int} }, timestamp: float, ignoredMessage: {xml_params: {code: int}} }, xml_params: {ignored: int, accepted: int} }, xml_params: {status: string} } } ) = resp log.info( label="audioscrobbler.api.track.scrobble", "Done submitting scrobble with: #{params}" ) v catch err do error.raise( error.invalid, "Invalid response: #{resp}, error: #{err}" ) end end # Submit a track to the audioscrobbler # `track.scrobble` API using its metadata. # @category Interaction def audioscrobbler.api.track.scrobble.metadata( ~username, ~password, ~session_key=null, ~api_key=null, ~api_secret=null, m ) = audioscrobbler.api.apply_meta( username=username, password=password, session_key=session_key, api_key=api_key, api_secret=api_secret, name="audioscrobbler.api.track.scrobble", audioscrobbler.api.track.scrobble, m ) end # Submit songs using audioscrobbler, respecting the full protocol: # First signal song as now playing when starting, and # then submit song when it ends. # @category Interaction # @flag extra # @param ~source Source for tracks. Should be one of: "broadcast", "user", "recommendation" or "unknown". Since liquidsoap is intended for radio broadcasting, this is the default. Sources other than user don't need duration to be set. # @param ~delay Submit song when there is only this delay left, in seconds. # @param ~force If remaining time is null, the song will be assumed to be skipped or cut, and not submitted. Set this to `true` to prevent this behavior # @param ~metadata_preprocessor Metadata pre-processor callback. Can be used to change metadata on-the-fly before sending to nowPlaying/scrobble. If returning an empty metadata, nothing is sent at all. def audioscrobbler.submit( ~username, ~password, ~api_key=null, ~api_secret=null, ~delay=10., ~force=false, ~metadata_preprocessor=fun (m) -> m, s ) = session_key = audioscrobbler.auth( username=username, password=password, api_key=api_key, api_secret=api_secret ) def now_playing(m) = try audioscrobbler.api.track.updateNowPlaying.metadata( username=username, password=password, api_key=api_key, api_secret=api_secret, session_key=session_key, metadata_preprocessor(m) ) catch err do log.important( "Error while submitting nowplaying info for #{source.id(s)}: #{err}" ) end end s = source.methods(s) s.on_metadata(synchronous=false, now_playing) f = fun (rem, m) -> # Avoid skipped songs if rem > 0. or force then thread.run( delay=0., { try audioscrobbler.api.track.scrobble.metadata( username=username, password=password, api_key=api_key, api_secret=api_secret, session_key=session_key, metadata_preprocessor(m) ) catch err do log.important( "Error while submitting scrobble info for #{source.id(s)}: #{ err }" ) end } ) else log( label="audioscrobbler.submit", level=4, "Remaining time null: will not submit song (song skipped ?)" ) end s.on_position(synchronous=true, remaining=true, position=delay, f) (s : source) end liquidsoap-2.4.2/src/libs/extra/deprecations.liq000066400000000000000000000544501513273233300217350ustar00rootroot00000000000000# Deprecated APIs. # Mark a function as deprecated. # @flag deprecated # @category Liquidsoap # @param old Old function name. # @param new New function name. def deprecated(old, new) = new = if new == "" then "" else " Please use \"#{new}\" instead." end log.severe( label="lang.deprecated", "WARNING: \"#{old}\" is deprecated and will be removed in future version.#{ new }" ) end %ifdef input.external.rawaudio # Deprecated: this function has been replaced by `input.external.rawaudio`. # @flag deprecated def replaces input.external(%argsof(input.external.rawaudio), cmd) = deprecated("input.external", "input.external.rawaudio") input.external.rawaudio(%argsof(input.external.rawaudio), cmd) end %endif # Deprecated: this function has been replaced by `string.quote`. # @flag deprecated def quote(s) = deprecated("quote", "string.quote") string.quote(s) end let string.utf8 = () # Deprecated: this function has been replaced by `string.escape`. # @flag deprecated def string.utf8.escape(s) = deprecated("string.utf8.escape", "string.escape") string.escape(s) end # Deprecated: use mksafe and playlist instead. # @flag deprecated def playlist.safe( ~id=null, ~mime_type="", ~mode="randomize", ~on_track={()}, ~prefix="", ~reload=0, ~reload_mode="seconds", uri ) = deprecated("playlist.safe", "") ignore(on_track) mksafe( playlist( id=id, mime_type=mime_type, mode=mode, prefix=prefix, reload=reload, reload_mode=reload_mode, uri ) ) end # Deprecated: this function has been replaced by `thread.run.recurrent`. # @flag deprecated def add_timeout(~fast=true, delay, f) = deprecated("add_timeout", "thread.run.recurrent") thread.run.recurrent(fast=fast, delay=delay, f) end # Deprecated: this function has been replaced by `thread.when`. # @flag deprecated def exec_at(~freq=1., ~pred, f) = deprecated("exec_at", "thread.when") thread.when(every=freq, pred, f) end # Deprecated: this function has been replaced by `file.which`. # @flag deprecated def which(f) = deprecated("which", "file.which") file.which(f) ?? "" end base64 = () # Deprecated: this function has been replaced by `string.base64.decode`. # @flag deprecated def base64.decode(s) = deprecated("base64.decode", "string.base64.decode") string.base64.decode(s) end # Deprecated: this function has been replaced by `string.base64.encode`. # @flag deprecated def base64.encode(s) = deprecated("base64.encode", "string.base64.encode") string.base64.encode(s) end # Deprecated: this function has been replaced with `playlist`, setting # `reload_mode` argument to `"never"` and `loop` to `false`. # @flag deprecated def playlist.once( ~id=null, ~random=false, ~reload_mode="", ~prefetch=1, ~filter=fun (_) -> true, uri ) = deprecated("playlist.once", "playlist") mode = if random then "randomize" else "normal" end reload_mode = if reload_mode == "" then "never" else reload_mode end playlist( reload_mode=reload_mode, loop=false, id=id, mode=mode, prefetch=prefetch, check_next=filter, uri ) end # Deprecated: use metadata.map # @flag deprecated def map_metadata(%argsof(metadata.map), fn, s) = deprecated("map_metadata", "metadata.map") metadata.map(%argsof(metadata.map), fn, s) end # Deprecated: this function has been replaced by `metadata.map`. # @flag deprecated def rewrite_metadata(l, ~insert_missing=true, ~update=true, ~strip=false, s) = deprecated("rewrite_metadata", "metadata.map") def map(m) = def apply(x) = label = fst(x) value = snd(x) (label, value % m) end list.map(apply, l) end metadata.map( map, insert_missing=insert_missing, update=update, strip=strip, s ) end # Deprecated: this function will be removed in a future release # @flag deprecated def id(~id=null, s) = deprecated("id", "") ignore(id) s end # Deprecated: flow is no longer maintained # Register a radio on Liquidsoap Flows. # @category Liquidsoap # @flag deprecated # @param ~radio Name of the radio. # @param ~website URL of the website of the radio. # @param ~description Description of the radio. # @param ~genre Genre of the radio (rock or rap or etc.). # @param ~streams List of streams for the radio described by \ # a pair of strings consisting of the format of the stream \ # and the url of the stream. The format should be \ # of the form "ogg/128k" consisting of the codec and \ # the bitrate, separated by "/". def register_flow( ~server="", ~user="default", ~password="default", ~email="", ~radio, ~website, ~description, ~genre, ~streams, s ) = deprecated("register_flow", "") # If the server is "", we get the server from sf.net server = if server == "" then let data = http.get("http://liquidsoap.info/flows_server") if data.status_code == 200 then data else # If sf is down, we use the hardcoded server "http://savonet.rastageeks.org/liqflows.py" end else server end log( level=4, "Flows server: #{server}" ) # Initial variables ping_period = 600. # Pinging period in seconds # Fix default parameters # and set request function. base_params = [ ("v", "0.0"), ("user", user), ("password", password), ("email", email), ("radio", radio) ] def request(~cmd, ~params) = def log(~level, x) = log(label=radio, level=level, x) end log( level=4, "Processing command #{cmd} with arguments:" ) def log_arg(x) = let (label, value) = x log( level=4, " #{label}: #{value}" ) end list.iter(log_arg, params) cmd = url.encode(cmd) params = list.append(base_params, params) def f(z) = let (x, y) = z y = url.encode(y) "#{x}=#{y}" end params = string.concat(separator="&", list.map(f, params)) url = "#{server}?cmd=#{cmd}&#{params}" # TODO: do something with errors! answer = http.get(url) log( level=4, "Response status: #{answer.http_version} #{answer.status_code} #{ answer.status_message }" ) log( level=4, "Response headers:" ) list.iter(log_arg, answer.headers) log( level=4, "Response content: #{answer}" ) end # Register radio params = [ ("radio_website", website), ("radio_description", description), ("radio_genre", genre) ] request( cmd="add radio", params=params ) # Ping def ping() = ignore( request( cmd="ping radio", params=[] ) ) ping_period end thread.run.recurrent(fast=false, delay=ping_period, ping) # Register streams def register_stream(format_url) = let (format, url) = format_url params = [("stream_format", format), ("stream_url", url)] request( cmd="add stream", params=params ) end request( cmd="clear streams", params=[] ) list.iter(register_stream, streams) # Metadata update def metadata(m) = artist = m["artist"] title = m["title"] params = [("m_title", title), ("m_artist", artist)] request(cmd="metadata", params=params) end s.on_metadata(metadata) end # Deprecated: this function has been replaced by `source.fail`. # @flag deprecated def empty(~id=null) = deprecated("empty", "source.fail") source.fail(id=id) end # Deprecated: use `request.create` instead. # @flag deprecated def request.create.raw(%argsof(request.create), uri) = deprecated("request.create.raw", "request.create") request.create(%argsof(request.create), uri) end # Deprecated: use `file.remove` instead. # @flag deprecated def file.unlink(filename) = deprecated("file.unlink", "file.remove") file.remove(filename) end # Deprecated: was designed for transitions only and is not # needed anymore. Use `file.out` instead. # @flag deprecated def fade.final(~id="fade.final", ~duration=3., ~type="lin", s) = deprecated("fade.final", "fade.out") fn = mkfade(start=1., stop=0., type=type, duration=duration, s) should_play = ref(true) def fn() = v = fn() if v == 0. then should_play := false end v end s = fade.scale(id=id, fn, s) switch(track_sensitive=false, [(should_play, s)]) end # Deprecated: was designed for transitions only and is not # needed anymore. Use `fade.in` instead. # @flag deprecated def fade.initial(~id="fade.initial", ~duration=3., ~type="lin", s) = deprecated("fade.initial", "fade.in") fn = mkfade(start=0., stop=1., type=type, duration=duration, s) fade.scale(id=id, fn, s) end # Deprecated: use `process.read` instead. # @flag deprecated def get_process_output(%argsof(process.read), cmd) = deprecated("get_process_output", "process.read") process.read(%argsof(process.read), cmd) end # Deprecated: use `process.read.lines` instead. # @flag deprecated def get_process_lines(%argsof(process.read.lines), cmd) = deprecated("get_process_lines", "process.read.lines") process.read.lines(%argsof(process.read.lines), cmd) end # Deprecated: use `process.test` instead. # @flag deprecated def test_process(%argsof(process.test), cmd) = deprecated("test_process", "process.test") process.test(%argsof(process.test), cmd) end # Deprecated: use `process.run` instead. # @flag deprecated def system(command) = deprecated("system", "process.run") process.run(command) end # Deprecated: use `blank.detect` instead # @flag deprecated def on_blank(%argsof(blank.detect), f, s) = deprecated("on_blank", "blank.detect") s = blank.detect(%argsof(blank.detect), s) s.on_blank(synchronous=true, f) s end # Deprecated: use `blank.skip` instead # @flag deprecated def skip_blank(%argsof(blank.skip), s) = deprecated("skip_blank", "blank.skip") blank.skip(%argsof(blank.skip), s) end # Deprecated: use `blank.eat` instead # @flag deprecated def eat_blank(%argsof(blank.eat), s) = deprecated("eat_blank", "blank.eat") blank.eat(%argsof(blank.eat), s) end # Deprecated: use `blank.strip` instead # @flag deprecated def strip_blank(%argsof(blank.strip), s) = deprecated("strip_blank", "blank.strip") blank.strip(%argsof(blank.strip), s) end # Deprecated: use `time.local` instead # @flag deprecated def localtime(t) = deprecated("localtime", "time.local") time.local(t) end # Deprecated: use `time.utc` instead # @flag deprecated def gmtime(t) = deprecated("gmtime", "time.utc") time.utc(t) end # Deprecated: use `time` instead # @flag deprecated def gettimeofday() = deprecated("gettimeofday", "time") time() end # @flag deprecated def output.preferred(~id=null, ~fallible=false, ~start=true, s) = deprecated("output.preferred", "output") output(id=id, fallible=fallible, start=start, s) end # Deprecated: use `output` instead. # @flag deprecated def out(s) = deprecated("out", "output") output(mksafe(s)) end # Deprecated: use `input` instead. # @flag deprecated def in(~id=null, ~start=true, ~fallible=false) = deprecated("in", "input") input(id=id, start=start, fallible=fallible) end # Deprecated: use `source.available` instead. # @flag deprecated def mkavailable( ~id="mkavailable", ~track_sensitive=getter(true), ~active=getter(false), ~available=getter(true), s ) = deprecated("mkavailable", "source.available") output.dummy(switch([(getter.function(active), s)]), fallible=true) switch( id=id, track_sensitive=getter.function(track_sensitive), [(getter.function(available), s)] ) end # Deprecated: use `at` instead. # @flag deprecated def at(pred, s) = deprecated("at", "source.available") source.available(s, pred) end https = () # Deprecated: use `http.get` instead. # @flag deprecated def https.get(%argsof(http.get), url) = deprecated("https.get", "http.get") http.get(%argsof(http.get), url) end # Deprecated: use `http.put` instead. # @flag deprecated def https.put(%argsof(http.put), url) = deprecated("https.put", "http.put") http.put(%argsof(http.put), url) end # Deprecated: use `http.post` instead. # @flag deprecated def https.post(%argsof(http.post), url) = deprecated("https.post", "http.post") http.post(%argsof(http.post), url) end # Deprecated: use `http.head` instead. # @flag deprecated def https.head(%argsof(http.head), url) = deprecated("https.head", "http.head") http.head(%argsof(http.head), url) end # Deprecated: use `http.delete` instead. # @flag deprecated def https.delete(%argsof(http.delete), url) = deprecated("https.delete", "http.delete") http.delete(%argsof(http.delete), url) end %ifdef input.http # Deprecated: use `input.http` instead # @flag deprecated def input.https(%argsof(input.http), url) = deprecated("input.https", "input.http") input.http(%argsof(input.http), url) end %endif %ifdef source.say_metadata # Deprecated: use `source.say_metadata` instead # @flag deprecated def say_metadata(s, ~pattern) = def pattern(m) = pattern % m end source.say_metadata(pattern=pattern, s) end %endif # Deprecated: use `playlist.parse.register` instead # @flag deprecated def add_playlist_parser(%argsof(playlist.parse.register), s) = deprecated("add_playlist_parser", "playlist.parse.register") playlist.parse.register(%argsof(playlist.parse.register), s) end # Deprecated: use `json.stringify` instead # @flag deprecated # @argsof json.stringify def json_of(%argsof(json.stringify), v) = deprecated("json_of", "json.stringify") json.stringify(%argsof(json.stringify), v) end # Deprecated: use `json.parse` instead # @flag deprecated # @argsof json.parse def of_json(%argsof(json.parse), v) = deprecated("of_json", "json.parse") json.parse(%argsof(json.parse), v) end # Deprecated: use `playlist` instead # @flag deprecated def playlist.reloadable( ~id=null, ~mime_type="", ~mode="randomize", ~on_track={()}, ~prefix="", ~reload=0, ~reload_mode="seconds", uri ) = deprecated("playlist.reloadable", "playlist") ignore(on_track) playlist( id=id, mime_type=mime_type, mode=mode, prefix=prefix, reload=reload, reload_mode=reload_mode, uri ) end # Deprecated: use `request.dynamic` instead # @flag deprecated def request.dynamic.list(%argsof(request.dynamic), f) = deprecated("request.dynamic.list", "request.dynamic") add = ref(fun (_) -> ()) def f() = l = f() if l != [] then let [r, ...q] = list.rev(l) r = (r : request) list.iter(add(), list.rev(q)) r else null end end s = request.dynamic(%argsof(request.dynamic), f) add := fun (r) -> ignore(s.add(r)) s end # Deprecated: use `process.run` instead # @flag deprecated def run_process(%argsof(process.run), p) = deprecated("run_process", "process.run") x = process.run(%argsof(process.run), p) (x.stdout, x.stderr, ("#{x.status}", x.status.description)) end # Deprecated: use `list.assoc.mem` instead # @flag deprecated def list.mem_assoc(x, y) = deprecated("list.mem_assoc", "list.assoc.mem") list.assoc.mem(x, y) end # Deprecated: use `runtime.gc.full_major` instead # @flag deprecated def garbage_collect() = deprecated("garbage_collect", "runtime.gc.full_major") runtime.gc.full_major() end # Deprecated: use `video.alpha.of_color` # @flag deprecated def video.transparent(%argsof(video.alpha.of_color), s) = deprecated("video.transparent", "video.alpha.of_color") video.alpha.of_color(%argsof(video.alpha.of_color), s) end # Deprecated: use `request.resolved` # @flag deprecated def request.ready(r) = deprecated("request.ready", "request.resolved") request.resolved(r) end # Deprecated: use `environment.get` # @flag deprecated def getenv(default="", v) = deprecated("getenv", "environment.get") environment.get(default=default, v) end # Deprecated: use `environment.set` # @flag deprecated def setenv(k, v) = deprecated("setenv", "environment.set") environment.set(k, v) end # Deprecated: use `getpid` # @flag deprecated def getpid() = deprecated("getpid", "process.pid") process.pid() end # Deprecated: use `file.mime` # @flag deprecated def get_mime(fname) = deprecated("get_mime", "file.mime") file.mime(fname) ?? "" end # Deprecated: use `string`. # @flag deprecated def string_of(s) = deprecated("string_of", "string") string(s) end # Deprecated: use `string.float`. # @flag deprecated def string_of_float(x) = deprecated("string_of_float", "string.float") string.float(x) end # Deprecated: use `protocol.add`. # @flag deprecated def add_protocol(%argsof(protocol.add), name, fn) = deprecated("add_protocol", "protocol.add") protocol.add(%argsof(protocol.add), name, fn) end # Deprecated: use `decoder.metadata.add`. # @flag deprecated def add_metadata_resolver(name, fn) = deprecated("add_metadata_resolver", "decoder.metadata.add") decoder.metadata.add(name, fn) end # Deprecated: use `source.mux.audio`. # @flag deprecated def mux_audio(%argsof(source.mux.audio), s) = deprecated("mux_audio", "source.mux.audio") source.mux.audio(%argsof(source.mux.audio), s) end # Deprecated: use `source.mux.video`. # @flag deprecated def mux_video(%argsof(source.mux.video), s) = deprecated("mux_video", "source.mux.video") source.mux.video(%argsof(source.mux.video), s) end # Deprecated: use `source.mux.midi` # @flag deprecated def mux_midi(%argsof(source.mux.midi), s) = deprecated("mux_midi", "source.mux.midi") source.mux.midi(%argsof(source.mux.midi), s) end # Deprecated: use `source.drop.audio` # @flag deprecated def drop_audio(%argsof(source.drop.audio), s) = deprecated("drop_audio", "source.drop.audio") source.drop.audio(%argsof(source.drop.audio), s) end # Deprecated: use `source.drop.video` # @flag deprecated def drop_video(%argsof(source.drop.video), s) = deprecated("drop_video", "source.drop.video") source.drop.video(%argsof(source.drop.video), s) end # Deprecated: use `source.drop.midi` # @flag deprecated def drop_midi(%argsof(source.drop.midi), s) = deprecated("drop_midi", "source.drop.midi") source.drop.midi(%argsof(source.drop.midi), s) end # Deprecated: use `source.drop.metadata` # @flag deprecated def drop_metadata(%argsof(source.drop.metadata), s) = deprecated("drop_metadata", "source.drop.metadata") source.drop.metadata(%argsof(source.drop.metadata), s) end # Deprecated: use `source.stereo` # @flag deprecated def audio_to_stereo(~id=null, s) = deprecated("audio_to_stereo", "stereo") stereo(id=id, s) end # Deprecated: use `source.tracks` and `source` # @flag deprecated def merge_tracks(~id=null, s) = deprecated("merge_tracks", "source") let {track_marks = _, ...tracks} = source.tracks(s) source(id=id, tracks) end %ifdef compress # Deprecated: use `compress` # @flag deprecated def compress.old(~id=null, s) = deprecated("compress.old", "compress") compress(id=id, s) end %endif # Deprecated: use `thread.pause` # @flag deprecated def sleep(s) = deprecated("sleep", "thread.pause") thread.pause(s) end # Deprecated: use `video.add_rectangle`. # @flag deprecated def video.rectangle(%argsof(video.add_rectangle), s) = video.add_rectangle(%argsof(video.add_rectangle), s) end # Deprecated: use `video.add_line`. # @flag deprecated def video.line(%argsof(video.add_line), src, tgt, s) = video.add_line(%argsof(video.add_line), src, tgt, s) end # Deprecated: integrated into requests resolution. # @flag deprecated def cue_cut( ~id=null(""), ~cue_in_metadata="", ~cue_out_metadata="", ~on_cue_in=(fun () -> ()), ~on_cue_out=(fun () -> ()), s ) = ignore(id) ignore(cue_in_metadata) ignore(cue_out_metadata) ignore(on_cue_in) ignore(on_cue_out) log.severe( label="lang.deprecated", "WARNING: cue_cut has been removed and integrated directly into requests \ resolution! This operator can be safely removed from your script now." ) s end # Deprecated: use the on_metadata source method # @flag deprecated def on_metadata(~id:_, s, fn) = log.severe( label="lang.deprecated", "Use the on_metadata source method!" ) source.methods(s).on_metadata(synchronous=true, fn) s end # Deprecated: use the on_track source method # @flag deprecated def on_track(~id:_, s, fn) = log.severe( label="lang.deprecated", "Use the on_track source method!" ) source.methods(s).on_track(synchronous=true, fn) s end # Deprecated: use the last_metadata source method # @flag deprecated def source.last_metadata(~id:_, s) = log.severe( label="lang.deprecated", "Use the last_metadata source method!" ) source.methods(s).last_metadata() end # Deprecated: use the on_frame source method # @flag deprecated def source.on_frame(~id:_, ~before=true, s, fn) = log.severe( label="lang.deprecated", "Use the on_frame source method!" ) source.methods(s).on_frame(synchronous=true, before=before, fn) end # Deprecated: use the on_position source method # @flag deprecated def source.on_offset( ~id:_, ~force:allow_partial=false, ~offset:position, fn, s ) = log.severe( label="lang.deprecated", "Use the on_position source method!" ) source.methods(s).on_position( synchronous=true, remaining=false, allow_partial=allow_partial, position=position, fn ) end # Deprecated: use the on_position source method # @flag deprecated def source.on_end(~id:_, ~delay:position, s, fn) = log.severe( label="lang.deprecated", "Use the on_position source method!" ) source.methods(s).on_position( synchronous=true, remaining=true, allow_partial=false, position=position, fn ) end # Deprecated: use the insert_metadata source method # @flag deprecated def insert_metadata(~id:_=null, s) = log.severe( label="lang.deprecated", "WARNING: `insert_metadata` operator is deprecated. Please use the \ `insert_metadata` source method!" ) source.methods(s) end # Deprecated: use `normalize_track_gain` # @flag deprecated def replaygain(~id=null, s) = log.severe( label="lang.deprecated", "Use the normalize_track_gain operator!" ) def add_legacy_meta(m) = if m["replaygain_track_gain"] != "" and m[settings.normalize_track_gain_metadata()] == "" then [(settings.normalize_track_gain_metadata(), m["replaygain_track_gain"])] else [] end end s = metadata.map(add_legacy_meta, s) amplify(id=id, override=settings.normalize_track_gain_metadata(), 1., s) end # Deprecated: use json.object # @flag deprecated def replaces json() = log.severe( label="lang.deprecated", "Use `json.object`" ) json.object() end liquidsoap-2.4.2/src/libs/extra/externals.liq000066400000000000000000000122261513273233300212550ustar00rootroot00000000000000# Enable the external ffmpeg decoder. # @category Liquidsoap # @param ~file_extensions File extensions to decode. Should not be empty # @param ~mimes Mime types to decode. Empty list means any type. # @param ~binary Path to the `ffmpeg` binary. def enable_external_ffmpeg_decoder(~binary="ffmpeg", ~mimes, ~file_extensions) = decoder.add( name="FFMPEG", description="Decode files using the ffmpeg decoder binary", mimes=mimes, file_extensions=file_extensions, fun (~rlog, ~maxtime:_, fname) -> begin # File is cleaned up as part of the request workflow. outfile = file.temp(cleanup=false, "ffmpeg", ".wav") try let {status = {code}} = process.run( "#{binary} -i #{process.quote(fname)} #{process.quote(outfile)}" ) code == 0 ? outfile : null catch err do file.remove(outfile) rlog( "Error while decoding #{fname} using ffmpeg: #{err}" ) null end end ) end # Enable the external openmpt123 decoder # @category Liquidsoap # @param ~file_extensions File extensions to decode. # @param ~mimes Mime types to decode. Empty list means any type. # @param ~options Extra options. # @param ~binary Path to the `ffmpeg` binary. def enable_external_openmpt123_decoder( ~binary="openmpt123", ~mimes=[ "audio/it", "audio/xm", "audio/s3m", "audio/x-mod", "audio/mod", "audio/module-xm", "audio/x-mod", "application/playerpro", "audio/x-s3m", "application/soundapp", "audio/med", "audio/x-xm" ], ~file_extensions=[ "xm", "mtm", "amf", "stm", "ult", "wow", "dmf", "it", "s3m", "far", "mod", "mt2", "okt", "med", "669" ], ~options="" ) = decoder.add( name="OPENMPT123", description="Decode files using the openmpt123 decoder binary", mimes=mimes, file_extensions=file_extensions, fun (~rlog, ~maxtime:_, infile) -> begin ret = process.read.lines( "#{binary} --info #{process.quote(infile)} 2>&1" ) def get_meta(l, s) = ret = string.extract(pattern="^(\\w+).+:\\s(.+)$", s) if list.length(ret) > 2 then label = ret[1] val = ret[2] label = "openmpt:#{string.case(lower=true, label)}" ["#{string.quote(label)}=#{string.quote(val)}", ...l] else l end end meta = list.fold(get_meta, [], ret) prefix = if meta == [] then "" else "annotate:#{string.concat(separator=',', meta)}:" end # File is cleaned up as part of the request workflow. outfile = file.temp(cleanup=false, "openmpt", ".wav") try let {status = {code}} = process.run( "#{binary} --assume-terminal --quiet --force #{options} --output #{ process.quote(outfile) } #{process.quote(infile)}" ) code == 0 ? "#{prefix}#{outfile}" : null catch err do file.remove(outfile) rlog( "Error while decoding #{infile} using ffmpeg: #{err}" ) null end end ) end # Standard function for displaying metadata. # Shows artist and title, using "Unknown" when a field is empty. # @param m Metadata packet to be displayed. # @category String def string_of_metadata(m) = artist = m["artist"] title = m["title"] artist = if "" == artist then "Unknown" else artist end title = if "" == title then "Unknown" else title end "#{artist} -- #{title}" end # Use X On Screen Display to display metadata info. # @flag extra # @param ~color Color of the text. # @param ~position Position of the text (top|middle|bottom). # @param ~font Font used (xfontsel is your friend...) # @param ~display Function used to display a metadata packet. # @category Source / Track processing def osd_metadata( ~color="green", ~position="top", ~font="-*-courier-*-r-*-*-*-240-*-*-*-*-*-*", ~display=string_of_metadata, s ) = osd = 'osd_cat -p #{position} --font #{process.quote(font)}' ^ ' --color #{color}' def feedback(m) = ignore( process.run( "echo #{process.quote(display(m))} | #{osd} &" ) ) end s.on_metadata(synchronous=false, feedback) end # Use notify to display metadata info. # @flag extra # @param ~urgency Urgency (low|normal|critical). # @param ~icon Icon filename or stock icon to display. # @param ~timeout Timeout in seconds. # @param ~display Function used to display a metadata packet. # @param ~title Title of the notification message. # @category Source / Track processing def notify_metadata( ~urgency="low", ~icon="stock_smiley-22", ~timeout=3., ~display=string_of_metadata, ~title="Liquidsoap: new track", s ) = time = int_of_float(timeout * 1000.) send = 'notify-send -i #{icon} -u #{urgency}' ^ ' -t #{time} #{process.quote(title)} ' s.on_metadata( synchronous=false, fun (m) -> ignore(process.run(send ^ process.quote(display(m)))) ) end liquidsoap-2.4.2/src/libs/extra/fades.liq000066400000000000000000000117541513273233300203370ustar00rootroot00000000000000# Plot the first crossfade transition. Used for visualizing and testing # crossfade transitions. # @category Source / Track processing # @flag extra def cross.plot(~png=null, ~dir=null, s) = dir = if null.defined(dir) then null.get(dir) else dir = file.temp_dir("plot") on_cleanup({file.rmdir(dir)}) dir end old_txt = path.concat(dir, "old.txt") new_txt = path.concat(dir, "new.txt") def gnuplot_cmd(filename) = 'set term png; set output "#{(filename : string)}"; plot "#{new_txt}" using \ 1:2 with lines title "new track", "#{old_txt}" using 1:2 with lines title \ "old track"' end def store_rms(~id, s) = s = rms(duration=settings.frame.duration(), s) t0 = ref(null) s.on_frame( synchronous=true, before=false, { let t0 = if null.defined(t0()) then null.get(t0()) else t0 := source.time(s) null.get(t0()) end let v = s.rms() let p = source.time(s) - t0 fname = id == "old" ? old_txt : new_txt file.write(append=true, data="#{p}\t#{v}\n", fname) } ) s end plotted = ref(false) def transition(old, new) = old = store_rms(id="old", fade.out(old.source)) new = store_rms(id="new", fade.in(new.source)) s = blank(duration=0.1) s.on_frame( synchronous=true, { if null.defined(png) and not plotted() then ignore( process.run( "gnuplot -e #{process.quote(gnuplot_cmd(null.get(png)))}" ) ) end plotted := true } ) sequence(single_track=false, [add(normalize=false, [new, old]), once(s)]) end cross(transition, s) end # Plot two sine tracks with given autocue params. # Used for visualizing and testing autocue crossfade transitions. # @category Source / Track processing # @flag extra def autocue.plot( ~png=null, ~dir=null, ~old_autocue, ~new_autocue, ~sync="auto", ~on_stop ) = dir = if null.defined(dir) then null.get(dir) else dir = file.temp_dir("plot") on_cleanup({file.rmdir(dir)}) dir end old_txt = path.concat(dir, "old.txt") new_txt = path.concat(dir, "new.txt") def gnuplot_cmd(filename) = 'set term png; set output "#{(filename : string)}"; plot "#{new_txt}" using \ 1:2 with lines title "new track", "#{old_txt}" using 1:2 with lines title \ "old track"' end def autocue_annotate(a, uri) = meta = autocue.metadata(implementation="autocue.plot", a) meta = list.map(fun ((label, value)) -> "#{label}=#{string.quote(value)}", meta) meta = string.concat(separator=",", meta) "annotate:#{meta}:#{uri}" end old_uri = process.uri( extname="wav", "ffmpeg -f lavfi -i \"sine=frequency=1000:duration=10\" -y $(output) < \ /dev/null" ) old_uri = autocue_annotate(old_autocue, old_uri) old_source = request.once(request.create(old_uri)) new_uri = process.uri( extname="wav", "ffmpeg -f lavfi -i \"sine=frequency=500:duration=10\" -y $(output) < \ /dev/null" ) new_uri = autocue_annotate(new_autocue, new_uri) new_source = request.once(request.create(new_uri)) s = sequence([old_source, new_source]) def store_rms(~fname, ~offset, s) = s = rms(duration=settings.frame.duration(), s) s.on_frame( synchronous=true, before=false, { let t = source.time(s) - offset let v = s.rms() file.write(append=true, data="#{t}\t#{v}\n", fname) } ) s end is_first_ending = ref(true) is_first_starting = ref(true) def transition(old, new) = list.iter( fun (x) -> log( level=4, "Before: #{x}" ), old.metadata ) list.iter( fun (x) -> log( level=4, "After : #{x}" ), new.metadata ) offset = source.duration(old.source) + source.duration(new.source) def ending_map(s) = if is_first_ending() then is_first_ending := false store_rms(fname=old_txt, offset=offset, s) else s end end def starting_map(s) = if is_first_starting() then is_first_starting := false store_rms(fname=new_txt, offset=offset, s) else s end end cross.simple( initial_fade_in_metadata=new.metadata, initial_fade_out_metadata=old.metadata, ending_map=ending_map, starting_map=starting_map, old.source, new.source ) end s = cross(transition, s) clock.assign_new(sync=sync, [s]) def on_stop() = if null.defined(png) then ignore( process.run( "gnuplot -e #{process.quote(gnuplot_cmd(null.get(png)))}" ) ) end on_stop() clock(s.clock).stop() end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) end liquidsoap-2.4.2/src/libs/extra/file.liq000066400000000000000000000035631513273233300201730ustar00rootroot00000000000000# Keep a record of played files. This is primarily useful to know when a song # was last played and avoid repetitions. # @flag extra # @param ~duration Duration (in seconds) after which songs are forgotten. By default, songs are not forgotten which means that the playlog will contain all the songs ever played. # @param ~hash Function to extract an identifier from the metadata. By default, the filename is used but we could return the artist to know when a song from a given artist was last played for instance. # @param ~persistency Set a file name where the values are stored and loaded in case the script is restarted. # @category Source / Track processing # @method add Record that file with given metadata has been played. # @method last How long ago a file was played (in seconds), `infinity` is returned if the song has never been played. def playlog( ~duration=infinity, ~persistency=null, ~hash=fun (m) -> m["filename"] ) = l = ref([]) # Load from persistency file if null.defined(persistency) then if file.exists(null.get(persistency)) then let json.parse (parsed : [(string * float)]?) = file.contents(null.get(persistency)) if null.defined(parsed) then l := null.get(parsed) end end end # Save into persistency file def save() = if null.defined(persistency) then data = json.stringify(l()) file.write(data=data, null.get(persistency)) end end # Remove too old elements def prune() = if duration != infinity then t = time() l := list.assoc.filter(fun (_, tf) -> t - tf <= duration, l()) end end # Add a new entry def add(m) = prune() f = hash(m) l := (f, time())::l() save() end # Last time this entry was played def last(m) = f = hash(m) time() - list.assoc(default=0. - infinity, f, l()) end {add=add, last=last} end liquidsoap-2.4.2/src/libs/extra/http.liq000066400000000000000000000111271513273233300202260ustar00rootroot00000000000000# Harbor middleware to add CORS headers # @category Internet # @flag extra # @param ~origin Configures the Access-Control-Allow-Origin CORS header # @param ~origin_callback Origin callback for advanced uses. If passed, overrides `origin` argument. Takes the request as input and returns the allowed origin. Return `null` to skip all CORS headers. # @param ~methods Configures the Access-Control-Allow-Methods CORS header. # @param ~allowed_headers Configures the Access-Control-Allow-Headers CORS header. If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header. # @param ~exposed_headers Configures the Access-Control-Expose-Headers CORS header. If not specified, no custom headers are exposed. # @param ~credentials Configures the Access-Control-Allow-Credentials CORS header. Set to true to pass the header, otherwise it is omitted. # @param ~max_age Configures the Access-Control-Max-Age CORS header. Set to an integer to pass the header, otherwise it is omitted. # @param ~preflight_continue Pass the CORS preflight response to the nexnhandler. # @param ~options_status_code Provides a status code to use for successful OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204. def harbor.http.middleware.cors( ~origin=null("*"), ~origin_callback=null, ~methods=["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"], ~allowed_headers=null, ~exposed_headers=[], ~credentials=false, ~max_age=null, ~preflight_continue=false, ~options_status_code=204 ) = fun (req, res, next) -> begin # This is for typing purposes res = if false then http.response() else res end if false then harbor.http.register( "/foo", fun (r, _) -> ignore(if false then r else req end) ) end vary = ref([]) def add_vary() = if vary() != [] then res.header("Vary", string.concat(separator=",", vary())) end end def vary(v) = vary := v::vary() end def add_origin(origin) = res.header("Access-Control-Allow-Origin", origin) if origin != "*" then vary("Origin") end end def add_methods() = if methods != [] then res.header( "Access-Control-Allow-Methods", string.concat(separator=",", methods) ) end end def add_credentials() = if credentials then res.header("Access-Control-Allow-Credentials", "true") end end def add_allowed_headers() = allowed_headers = if null.defined(allowed_headers) then string.concat(separator=",", null.get(allowed_headers)) elsif list.assoc.mem("access-control-request-headers", req.headers) then req.headers["access-control-request-headers"] else null end if null.defined(allowed_headers) then res.header("Access-Control-Allow-Headers", null.get(allowed_headers)) vary("Access-Control-Request-Headers") end end def add_exposed_headers() = if exposed_headers != [] then res.header( "Access-Control-Expose-Headers", string.concat(separator=",", exposed_headers) ) end end def add_max_age() = if null.defined(max_age) then res.header("Access-Control-Max-Age", "#{(null.get(max_age) : int)}") end end origin = if null.defined(origin_callback) then fn = null.get(origin_callback) fn(req) else getter.get(origin) end if not null.defined(origin) then next(req, res) else add_origin(null.get(origin)) if req.method == "OPTIONS" then add_credentials() add_methods() add_allowed_headers() add_max_age() add_exposed_headers() add_vary() if preflight_continue then next(req, res) else res.status_code(options_status_code) res.header("Content-length", "0") end else add_credentials() add_allowed_headers() add_vary() next(req, res) end end end end # This is for typing purposes if false then harbor.http.middleware.register(harbor.http.middleware.cors()) end liquidsoap-2.4.2/src/libs/extra/interactive.liq000066400000000000000000000504041513273233300215650ustar00rootroot00000000000000# Information about all variables variables = ref([]) # Float variables variables_float = ref([]) # Int variables variables_int = ref([]) # Bool variables variables_bool = ref([]) # String variables variables_string = ref([]) # Unit variables: those are not references but handler functions variables_unit = ref([]) let interactive = () let interactive.float = () let interactive.int = () let interactive.bool = () let interactive.string = () let interactive.unit = () let interactive.error = error.register("interactive.error") # @flag hidden def interactive.list(_) = l = variables() l = list.map( fun (xv) -> begin let (x, v) = xv "#{x} : #{v.type}" end, l ) string.concat(separator="\n", l) end server.register( usage="list", description="List available interactive variables.", namespace="var", "list", interactive.list ) # Description of an interactive variable. # @flag hidden def interactive.description(name) = list.assoc(name, variables()).description end # Type of an interactive variable. # @flag hidden def interactive.type(name) = list.assoc(name, variables()).type end # @flag hidden def interactive.float.ref(name) = list.assoc(name, variables_float()).ref end # @flag hidden def interactive.int.ref(name) = list.assoc(name, variables_int()).ref end # @flag hidden def interactive.bool.ref(name) = list.assoc(name, variables_bool()).ref end # @flag hidden def interactive.string.ref(name) = list.assoc(name, variables_string()).ref end # @flag hidden def interactive.unit.handler(name) = list.assoc(name, variables_unit()).handler end # @flag hidden def interactive.remove(name) = variables := list.assoc.remove.all(name, variables()) end # Remove an interactive variable. # @param name Name of the variable. # @category Interaction # @flag hidden def interactive.float.remove(name) = interactive.remove(name) variables_float := list.assoc.remove.all(name, variables_float()) end # Remove an interactive variable. # @param name Name of the variable. # @category Interaction # @flag hidden def interactive.int.remove(name) = interactive.remove(name) variables_int := list.assoc.remove.all(name, variables_int()) end # Remove an interactive variable. # @param name Name of the variable. # @category Interaction # @flag hidden def interactive.bool.remove(name) = interactive.remove(name) variables_bool := list.assoc.remove.all(name, variables_bool()) end # Remove an interactive variable. # @param name Name of the variable. # @category Interaction # @flag hidden def interactive.string.remove(name) = interactive.remove(name) variables_string := list.assoc.remove.all(name, variables_string()) end # Remove an interactive variable. # @param name Name of the variable. # @category Interaction # @flag hidden def interactive.unit.remove(name) = interactive.remove(name) variables_unit := list.assoc.remove.all(name, variables_unit()) end # Function called to ensure persistency of data. let interactive.persistency = ref(fun () -> ()) # @flag hidden def interactive.float.set(name, v) = interactive.float.ref(name) := v p = interactive.persistency() p() end # @flag hidden def interactive.int.set(name, v) = interactive.int.ref(name) := v p = interactive.persistency() p() end # @flag hidden def interactive.bool.set(name, v) = interactive.bool.ref(name) := v p = interactive.persistency() p() end # @flag hidden def interactive.string.set(name, v) = interactive.string.ref(name) := v p = interactive.persistency() p() end # @flag hidden def interactive.unit.set(name) = f = interactive.unit.handler(name) f() end %ifdef osc.on_float let stdlib_osc = osc %endif # Create an interactive variable. # @flag hidden # @param ~name Name of the variable. # @param ~description Description of the variable. # @param ~osc OSC address for the variable. # @param ~type Type of the variable. def interactive.create(~name, ~description="", ~osc="", ~type) = if list.assoc.mem(name, variables()) then error.raise( interactive.error, "variable already registered" ) end variables := (name, {type=type, description=description})::variables() variables := list.sort( fun (n, n') -> if fst(n) < fst(n') then -1 else 1 end, variables() ) %ifdef osc.on_float if osc != "" then if type == "float" then stdlib_osc.on_float(osc, fun (x) -> interactive.float.set(name, x)) elsif type == "int" then stdlib_osc.on_int(osc, fun (x) -> interactive.int.set(name, x)) elsif type == "bool" then stdlib_osc.on_bool(osc, fun (x) -> interactive.bool.set(name, x)) elsif type == "string" then stdlib_osc.on_string(osc, fun (x) -> interactive.string.set(name, x)) elsif type == "unit" then () else # TODO error.raise(error.not_found) end end %else ignore(osc) %endif end # @flag hidden def interactive.get(name) = try t = interactive.type(name) if t == "float" then r = interactive.float.ref(name) string.float(decimal_places=3, r()) elsif t == "int" then r = interactive.int.ref(name) string(r()) elsif t == "bool" then r = interactive.bool.ref(name) string(r()) elsif t == "string" then r = interactive.string.ref(name) r() elsif t == "unit" then "()" else error.raise(error.not_found) end catch _ do "Variable not found." end end server.register( namespace="var", description="Get the value of a variable.", "get", interactive.get ) # @flag hidden def interactive.set(arg) = try arg = r/=/.split(arg) name = string.trim(list.nth(arg, 0)) value = string.trim(list.nth(arg, 1)) t = interactive.type(name) if t == "float" then interactive.float.set(name, float_of_string(value)) elsif t == "int" then interactive.int.set(name, int_of_string(value)) elsif t == "bool" then interactive.bool.set(name, bool_of_string(value)) elsif t == "string" then interactive.string.set(name, value) elsif t == "unit" then interactive.unit.set(name) else error.raise(error.not_found) end "Variable #{name} set." catch _ do "Syntax error or variable not found." end end server.register( usage="set = ", description="Set the value of a variable.", namespace="var", "set", interactive.set ) # Save the value of all interactive variables in a file. # @category Interaction # @flag extra # @param fname Name of the file. def interactive.save(fname) = data = json.stringify( { float= list.map( fun (nv) -> begin let (name, v) = nv (name, v.ref()) end, variables_float() ), int= list.map( fun (nv) -> begin let (name, v) = nv (name, v.ref()) end, variables_int() ), bool= list.map( fun (nv) -> begin let (name, v) = nv (name, v.ref()) end, variables_bool() ), string= list.map( fun (nv) -> begin let (name, v) = nv (name, v.ref()) end, variables_string() ) } ) file.write(data=data, fname) end # Load the value of interactive variables from a file. # @category Interaction # @flag extra # @param fname Name of the file. def interactive.load(fname) = vars = file.contents(fname) let json.parse (vars : { float: [(string * float)], int: [(string * int)], bool: [(string * bool)], string: [(string * string)] } ) = vars list.iter( fun (nv) -> try interactive.float.set(fst(nv), snd(nv)) catch _ do log.important( label="interactive.load", "Variable #{fst(nv)} not found." ) end, vars.float ) list.iter( fun (nv) -> try interactive.int.set(fst(nv), snd(nv)) catch _ do log.important( label="interactive.load", "Variable #{fst(nv)} not found." ) end, vars.int ) list.iter( fun (nv) -> try interactive.bool.set(fst(nv), snd(nv)) catch _ do log.important( label="interactive.load", "Variable #{fst(nv)} not found." ) end, vars.bool ) list.iter( fun (nv) -> try interactive.string.set(fst(nv), snd(nv)) catch _ do log.important( label="interactive.load", "Variable #{fst(nv)} not found." ) end, vars.string ) end # Make the value of interactive variables persistent: they are loaded from the # given file and stored there whenever updated. This function should be called # after all interactive variables have been defined (variables not declared yet # will not be loaded). # @category Interaction # @flag extra # @param fname Name of the file. def interactive.persistent(fname) = if file.exists(fname) then interactive.load(fname) else interactive.save(fname) end interactive.persistency := {interactive.save(fname)} end # Expose interactive variables through harbor http server. Once this is called, # with default parameters, you can browse to # change the value of interactive variables using sliders. # @category Interaction # @flag extra # @param ~port Port of the server. # @param ~transport Http transport. Use `http.transport.ssl` or http.transport.secure_transport`, when available, to enable HTTPS output # @param ~uri URI of the server. def interactive.harbor( ~transport=http.transport.unix, ~port=8000, ~uri="/interactive" ) = def webpage(request, response) = form_data = request.data data = ref("") def add(s) = data := data() ^ s ^ "\n" end title = "Interactive values" add( "" ) add( "" ) add("#{title}") add( "" ) # TODO: we could send only the updated value instead of sending them all add( "" ) add("") add("

    #{title}

    ") def add_var((name, v)) = description = interactive.description(name) description = if description == "" then name else "#{description} (#{name})" end add( "" ) common = "id='#{name}' name='#{name}' class='interactive' onchange=\"send()\"" if v.type == "float" then v = list.assoc(name, variables_float()) value = http.string_of_float(v.ref()) if v.min == 0. - infinity or v.max == infinity then add( "" ) else min = http.string_of_float(v.min) max = http.string_of_float(v.max) step = http.string_of_float(v.step) value = http.string_of_float(v.ref()) unit = if v.unit == "" then "" else " " ^ v.unit end add( "
    #{value}#{unit}
    " ) end elsif v.type == "int" then v = list.assoc(name, variables_int()) value = string(v.ref()) add( "" ) elsif v.type == "bool" then v = list.assoc(name, variables_bool()) c = (v.ref()) ? "checked" : "" add( "" ) elsif v.type == "string" then v = list.assoc(name, variables_string()) add( "" ) elsif v.type == "unit" then add( "" ) else () end end add("
    ") list.iter(add_var, variables()) add("
    ") add("") response.html(data()) end harbor.http.register( transport=transport, port=port, method="GET", uri, webpage ) def setter(request, _) = data = url.split_args(request.body()) def update((name, v)) = try t = interactive.type(name) if t == "float" then interactive.float.set(name, float_of_string(v)) elsif t == "int" then interactive.int.set(name, int_of_string(v)) elsif t == "bool" then interactive.bool.set(name, bool_of_string(v)) elsif t == "string" then interactive.string.set(name, v) elsif t == "unit" then interactive.unit.set(name) end catch e do log.important( label="interactive.harbor", "Could not update variable #{name} (#{e.kind}: #{e.message})." ) end end list.iter(update, data) end harbor.http.register( transport=transport, port=port, method="POST", uri, setter ) log.important( label="interactive.harbor", "Website should be ready at ." ) end # Read a float from an interactive input. # @category Interaction # @flag extra # @param ~min Minimal value. # @param ~max Maximal value. # @param ~step Typical variation of the value. # @param ~description Description of the variable. # @param ~unit Unit for the variable. # @param ~osc OSC address. # @param name Name of the variable. # @param v Initial value. def replaces interactive.float( ~min=0. - infinity, ~max=infinity, ~step=0.1, ~description="", ~unit="", ~osc="", name, v ) = interactive.create(name=name, description=description, osc=osc, type="float") r = ref(v) variables_float := (name, {ref=r, unit=unit, min=min, max=max, step=step})::variables_float() r.{ set=fun (x) -> interactive.float.set(name, x), remove={interactive.float.remove(name)} } end # Read an integer from an interactive input. # @category Interaction # @flag extra # @param ~description Description of the variable. # @param ~osc OSC address. # @param name Name of the variable. # @param v Initial value. def replaces interactive.int(~description="", ~osc="", name, v) = interactive.create(name=name, description=description, osc=osc, type="int") r = ref(v) variables_int := (name, {ref=r})::variables_int() r.{ set=fun (x) -> interactive.int.set(name, x), remove={interactive.int.remove(name)} } end # Read a boolean from an interactive input. # @category Interaction # @flag extra # @param ~description Description of the variable. # @param ~osc OSC address. # @param name Name of the variable. # @param v Initial value. def replaces interactive.bool(~description="", ~osc="", name, v) = interactive.create(name=name, description=description, osc=osc, type="bool") r = ref(v) variables_bool := (name, {ref=r})::variables_bool() r.{ set=fun (x) -> interactive.bool.set(name, x), remove={interactive.bool.remove(name)} } end # Read a string from an interactive input. # @category Interaction # @flag extra # @param ~description Description of the variable. # @param ~osc OSC address. # @param name Name of the variable. # @param v Initial value. def replaces interactive.string(~description="", ~osc="", name, v) = interactive.create(name=name, description=description, osc=osc, type="string") r = ref(v) variables_string := (name, {ref=r})::variables_string() r.{ set=fun (x) -> interactive.string.set(name, x), remove={interactive.string.remove(name)} } end # Register a callback when a unit interactive input is set. # @category Interaction # @flag extra # @param ~description Description of the variable. # @param ~osc OSC address. # @param name Name of the variable. # @param f Function triggered when the value is set. def replaces interactive.unit(~description="", ~osc="", name, f) = interactive.create(name=name, description=description, osc=osc, type="unit") variables_unit := (name, {handler=f})::variables_unit() {set=f, remove={interactive.float.remove(name)}} end # Create a multiband compressor whose parameters are interactive variables. # @category Interaction # @flag extra # @param ~id Id of the source. Variable names are prefixed with this. # @param ~bands Number of bands. # @param s Source to compress. def compress.multiband.interactive(~id=null, ~bands=5, s) = id = string.id.default(default="compress.multiband.interactive", id) prefix = id ^ "_" wet = interactive.float(prefix ^ "wet", min=0., max=1., 1.) min_freq = 100. max_freq = 15000. def band(i) = frequency = exp( (ln(max_freq) - ln(min_freq)) * float_of_int(i) / float_of_int(bands - 1) + ln(min_freq) ) log.important( label=id, "Adding a band at #{frequency} Hz." ) frequency = interactive.float( "#{prefix}frequency#{i}", unit="Hz", min=0., max=20000., step=10., frequency ) attack = interactive.float( "#{prefix}attack#{i}", unit="ms", min=0., max=1000., step=10., 100. ) release = interactive.float( "#{prefix}release#{i}", unit="ms", min=0., max=1000., step=10., 200. ) threshold = interactive.float( "#{prefix}threshold#{i}", unit="dB", min=-20., max=0., step=0.1, -10. ) ratio = interactive.float("#{prefix}ratio#{i}", min=1., max=10., step=0.1, 4.) gain = interactive.float( "#{prefix}gain#{i}", unit="dB", min=0., max=30., step=0.1, 3. ) { frequency=frequency, attack=attack, release=release, threshold=threshold, ratio=ratio, gain=gain } end l = list.init(bands, band) compress.multiband(wet=wet, s, l) end liquidsoap-2.4.2/src/libs/extra/metadata.liq000066400000000000000000000040341513273233300210260ustar00rootroot00000000000000# Store and retrieve file covers using metadata. This returns a set of # getter/setter methods that can be used to store and retrieve cover art. # Typical usage is to set cover art in a `on_metadata` handler and retrieve # it in a `video.add_image` operator. See `video.add_cover` for an implementation # example. # @category Metadata # @flag extra # @param ~default Default cover file when no cover is available # @param ~mime_types Recognized mime types and their corresponding file extensions. def file.cover.manager( ~id=null, ~mime_types=[ ("image/gif", "gif"), ("image/jpg", "jpeg"), ("image/jpeg", "jpeg"), ("image/png", "png"), ("image/webp", "webp") ], ~default ) = id = string.id.default(id, default="cover") default = request.create(persistent=true, default) current_cover_request = ref(default) def extract_cover_from_metadata(_metadata) = filename = _metadata["filename"] log.info( label=id, "Extracting cover from #{string.quote(filename)}." ) cover = metadata.cover(_metadata) new_cover = if not null.defined(cover) then log.important( label=id, "File #{string.quote(filename)} has no cover." ) null else cover = null.get(cover) extension = mime_types[cover.mime] if extension == "" then log.important( label=id, "File #{string.quote(filename)} has unknown mime type #{ string.quote(cover.mime) }." ) null else cover_file = file.temp("#{id}_", ".#{extension}") file.write(cover_file, data=cover) log.important( label=id, "Cover for #{string.quote(filename)} saved to #{ string.quote(cover_file) }." ) request.create(temporary=true, cover_file) end end current_cover_request := (new_cover ?? default) end current_cover_request.{set=extract_cover_from_metadata} end liquidsoap-2.4.2/src/libs/extra/native.liq000066400000000000000000000103011513273233300205260ustar00rootroot00000000000000# Native reimplementation of track functions. let native = () # Create a source that plays only one track of the input source. # @category Source / Track processing # @flag extra def native.once(s) = # source.available(track_sensitive=true, s, predicate.first({true})) a = ref(true) s.on_track(synchronous=true, fun (_) -> a := false) source.available(s, a) end # At the beginning of each track, select the first ready child. # @category Source / Track processing # @flag extra # @param ~id Force the value of the source ID. # @param ~track_sensitive Re-select only on end of tracks. def native.fallback(~id=null, ~track_sensitive=true, sources) = fail = (source.fail() : source) def s() = list.find(default=fail, source.is_ready, getter.get(sources)) end infallible = getter.is_constant(sources) and not (list.exists(source.fallible, getter.get(sources))) source.dynamic( id=id, infallible=infallible, track_sensitive=track_sensitive, s ) end # Play only one track of every successive source, except for the last one which # is played as much as available. # @category Source / Track processing # @flag extra # @param ~id Force the value of the source ID. # @param sources List of sources to play tracks from. def native.sequence(~id=null, sources) = len = list.length(sources) n = ref(0) fail = source.fail() def rec s() = sn = list.nth(default=list.last(default=fail, sources), sources, n()) if source.is_ready(sn) or n() >= len - 1 then sn else ref.incr(n) s() end end infallible = not source.fallible(list.last(default=fail, sources)) s = source.dynamic(id=id, infallible=infallible, track_sensitive=true, s) first = ref(true) def ot(_) = # Drop the first track if first() then first := false else ref.incr(n) end end s.on_track(synchronous=true, ot) s end # Select the first source whose predicate is true in a list. If the second # argument is a getter, the source will be dynamically created. # @category Source / Track processing # @flag extra # @param ~id Force the value of the source ID. # @param ~track_sensitive Re-select only on end of tracks. # @param sources Sources with the predicate telling when they can be played. def native.switch(~id=null, ~track_sensitive=true, sources) = sources = list.map(fun (ps) -> source.available(snd(ps), fst(ps)), sources) native.fallback(id=id, track_sensitive=track_sensitive, sources) end # This allows doing `open native` let native.request = request # @docof request.dynamic def native.request.dynamic(%argsof(request.dynamic), f) = ignore(available) ignore(timeout) ignore(native) ignore(synchronous) def f() = try f() catch _ do null end end # Prepared requests queue = ref([]) def get_queue() = def get_request(s) = s.request end list.map(get_request, queue()) end def add(r) = s = request.once(r) if s.resolve() then queue := [...queue(), s] true else false end end def set_queue(l) = queue := [] list.iter(fun (r) -> ignore(add(r)), l) end current = ref(null) def get_current() = if null.defined(current()) then s = null.get(current()) s.request else null end end # Prefetch thread def fetch() = r = f() if null.defined(r) then r = null.get(r) s = request.once(r) if s.resolve() then log.info( "Added request on queue: #{request.uri(r)}." ) queue := [...queue(), s] true else log.important( "Failed to resolve request #{request.uri(r)}." ) false end else false end end def fill() = if list.length(queue()) < prefetch then ignore(fetch()) end end thread.run(every=retry_delay, fill) # Source def s() = if not list.is_empty(queue()) then let [s, ...rest] = queue() queue := rest current := s s else source.fail() end end source.dynamic(id=id, track_sensitive=true, s).{ fetch=fetch, queue=get_queue, add=add, set_queue=set_queue, current=get_current } end liquidsoap-2.4.2/src/libs/extra/openai.liq000066400000000000000000000064621513273233300205300ustar00rootroot00000000000000let error.openai = error.register("openai") openai = () # @flag hidden def parse_openai_error(ans, err) = try let json.parse (e : {error: {message: string, type: string}}) = ans e = e.error error.raise( error.openai, "#{e.type}: #{e.message}" ) catch _ do error.raise(err) end end # Query ChatGPT API. # @param ~base_url Base URL for the API query # @param ~key OpenAI API key. # @param ~model Language model. # @param ~timeout Timeout for network operations in seconds. # @param messages Messages initially exchanged. # @category Internet # @flag extra def openai.chat( ~key, ~base_url="https://api.openai.com", ~model="gpt-3.5-turbo", ~timeout=null(30.), ( messages: [ { role: string, content: string, name?: string, tool_calls?: [ {id: string, type: string, function: {name: string, arguments: string}} ], tool_call_id?: string } ] ) ) = payload = {model=model, messages=messages} ans = http.post( data=json.stringify(payload), timeout=timeout, headers=[ ("Content-Type", "application/json"), ( "Authorization", "Bearer #{(key : string)}" ) ], "#{base_url}/v1/chat/completions" ) if ans.status_code != 200 then error.raise( error.http, "#{ans.status_code}: #{ans.status_message}" ) end try let json.parse (ans : { choices: [ { finish_reason: string, index: int, message: {content: string, role: string} } ], created: int, model: string, object: string, usage: {completion_tokens: int, prompt_tokens: int, total_tokens: int} } ) = ans ans catch err : [error.json] do parse_openai_error(ans, err) end end # Generate speech using openai. Returns the encoded audio data. # @param ~base_url Base URL for the API query # @param ~key OpenAI API key. # @param ~model Language model. # @param ~timeout Timeout for network operations in seconds. # @param ~voice The voice to use when generating the audio. Supported voices are `"alloy"`, `"echo"`, `"fable"`, `"onyx"`, `"nova"`, and `"shimmer"` # @param ~response_format The format to audio in. Supported formats are: `"mp3"`, `"opus"`, `"aac"`, and `"flac"`. # @param ~speed The speed of the generated audio. Select a value from `0.25` to `4.0`. `1.0` is the default. # @params ~on_data Function executed when receiving the audio data. # @category Internet # @flag extra def openai.speech( ~key, ~base_url="https://api.openai.com", ~model="tts-1", ~timeout=null(30.), ~voice, ~response_format="mp3", ~speed=1., ~on_data, (input:string) ) = payload = { model=model, input=input, voice=(voice : string), response_format=response_format, speed=speed } ans = http.post.stream( data=json.stringify(payload), timeout=timeout, headers=[ ("Content-Type", "application/json"), ( "Authorization", "Bearer #{(key : string)}" ) ], on_body_data=on_data, "#{base_url}/v1/audio/speech" ) if ans.status_code != 200 then error.raise( error.http, "#{ans.status_code}: #{ans.status_message}" ) end end liquidsoap-2.4.2/src/libs/extra/server.liq000066400000000000000000000102541513273233300205550ustar00rootroot00000000000000# Register a command that outputs the RMS of the returned source. # @flag extra # @category Source / Visualization # @param ~id Force the value of the source ID. def server.rms(~id=null, s) = let s = rms(id=id, s) def rms(_) = rms = s.rms() "#{rms}" end s.register_command( description="Return the current RMS of the source.", usage="rms", "rms", rms ) s end # Register a server/telnet command to update a source's metadata. Returns a new # source, which will receive the updated metadata. The command has the following # format: insert key1="val1",key2="val2",... # @flag extra # @category Source / Track processing # @param ~id Force the value of the source ID. def server.insert_metadata(s) = def insert(s) = let (meta, _) = string.annotate.parse("#{s}:") if meta != [] then s.insert_metadata(meta) "Done" else "Syntax error or no metadata given. Use key1=\"val1\",key2=\"val2\",.." end end s.register_command( description="Insert a metadata chunk.", usage="insert key1=\"val1\",key2=\"val2\",..", "insert", insert ) s end # Start an interface for the "telnet" server over http. # @category Internet # @flag extra # @param ~port Port of the server. # @param ~transport Http transport. Use `http.transport.ssl` or http.transport.secure_transport`, when available, to enable HTTPS output # @param ~uri URI of the server. def server.harbor(~transport=http.transport.unix, ~port=8000, ~uri="/telnet") = def webpage(_, response) = response.html( " Liquidsoap telnet server

    Liquidsoap telnet server

    Type help if you are lost.

    " ) end harbor.http.register( transport=transport, port=port, method="GET", uri, webpage ) def setter(request, response) = log.info( "Executing command: #{request.data}" ) answer = server.execute(request.body()) answer = string.concat(separator="\n", answer) ^ "\n" response.data(answer) end harbor.http.register( transport=transport, port=port, method="POST", uri, setter ) log.important( label="server.harbor", "Website should be ready at ." ) end # Add a skip telnet command to a source when it does not have one by default. # @category Interaction # @flag extra # @param s The source to attach the command to. def add_skip_command(s) = # A command to skip def skip(_) = s.skip() "Done!" end s.on_wake_up( synchronous=true, { # Register the command: server.register( namespace="#{source.id(s)}", usage="skip", description="Skip the current song.", "skip", skip ) } ) end liquidsoap-2.4.2/src/libs/extra/source.liq000066400000000000000000000302241513273233300205460ustar00rootroot00000000000000# Apply a function to the first track of a source # @category Source / Track processing # @flag extra # @param ~id Force the value of the source ID. # @param fn The applied function. # @param s The input source. def map_first_track(~id=null("map_first_track"), fn, s) = fallback(id=id, track_sensitive=true, [fn((once(s) : source)), s]) end # Same operator as rotate but merges tracks from each sources. # For instance, `rotate.merge([intro,main,outro])` creates a source # that plays a sequence `[intro,main,outro]` as single track and loops back. # @category Source / Track processing # @flag extra # @param ~id Force the value of the source ID. # @param ~transitions Transition functions, padded with `fun (x,y) -> y` functions. # @param ~weights Weights of the children (padded with 1), defining for each child how many tracks are played from it per round, if that many are actually available. # @param sources Sequence of sources to be merged def rotate.merge( ~id=null("rotate.merge"), ~transitions=[], ~weights=[], sources ) = ready = ref(true) duration = frame.duration() def to_first(_, new) = ready := (not ready()) sequence(merge=true, [blank(duration=duration), (new : source)]) end transitions = if list.length(transitions) == 0 then [to_first] else list.mapi( ( fun (i, t) -> if i == 0 then (fun (old, new) -> to_first(old, t(old, new))) else t end ), transitions ) end s = rotate(transitions=transitions, weights=weights, sources) let {track_marks = _, ...tracks} = source.tracks(s) s = source(tracks) switch( id=id, replay_metadata=false, track_sensitive=false, [(ready, s), ({not ready()}, s)] ) end # Rotate between overlapping sources. Next track starts according # to 'liq_start_next' offset metadata. # @category Source / Track processing # @flag extra # @param ~id Force the value of the source ID. # @param ~start_next Metadata field indicating when the next track should start, relative to current track's time. # @param ~weights Relative weight of the sources in the sum. The empty list stands for the homogeneous distribution. # @param sources Sources to toggle from def overlap_sources( ~id=null("overlap_sources"), ~normalize=false, ~start_next="liq_start_next", ~weights=[], sources ) = position = ref(0) length = list.length(sources) def current_position() = pos = position() position := (pos + 1) mod length pos end ready_list = list.map(fun (_) -> ref(false), sources) grab_ready = fun (n) -> list.nth(default=ref(false), ready_list, n) def set_ready(pos, b) = is_ready = grab_ready(pos) is_ready := b end # Start next track on_offset def on_start_next(_, _) = set_ready(current_position(), true) end def on_offset(s) = let (s, offset) = metadata.getter.source.float(-1., start_next, s) s.on_position( synchronous=true, allow_partial=true, position=offset, on_start_next ) end list.iter(on_offset, sources) # Disable after each track def disable(pos, s) = def disable(_) = set_ready(pos, false) end s.on_track(synchronous=true, disable) end list.iteri(disable, sources) # Relay metadata from all sources send_to_main_source = ref(fun (_) -> ()) def relay_metadata(m) = fn = send_to_main_source() fn(m) end list.iter(fun (s) -> s.on_metadata(synchronous=true, relay_metadata), sources) def drop_metadata(s) = let {metadata = _, ...tracks} = source.tracks(s) source(tracks) end # Now drop all metadata sources = list.map(drop_metadata, sources) # Wrap sources into switches. def make_switch(pos, source) = is_ready = grab_ready(pos) switch(track_sensitive=true, [(is_ready, source)]) end sources = list.mapi(make_switch, sources) # Initiate the whole thing. set_ready(current_position(), true) # Create main source s = add(id=id, normalize=normalize, weights=weights, sources) # Set send_to_main_source send_to_main_source := fun (m) -> s.insert_metadata(m) s end # Append speech-synthesized tracks reading the metadata. # @category Metadata # @flag extra # @param ~pattern Pattern to use # @param s The source to use def source.say_metadata = def pattern(m) = artist = m["artist"] title = m["title"] artist_predicate = if artist != "" then "It was #{artist} playing " else "" end say_metadata = "#{artist_predicate}#{title}" say_metadata = r/:/g.replace(fun (_) -> '$(colon)', say_metadata) say_metadata = say_metadata == "" ? "Sorry, I do not know what this song title was" : say_metadata "say:#{say_metadata}" end fun (~id=null("source.say_metadata"), ~pattern=pattern, s) -> append(id=id, s, fun (m) -> once(single(pattern(m)))) end # Regularly insert track boundaries in a stream (useful for testing tracks). # @category Source / Track processing # @flag extra # @param ~every Duration of a track (in seconds). # @param ~metadata Metadata for tracks. # @param s The stream. def chop(~every=getter(3.), ~metadata=getter([]), s) = # Track time in the source's context: time = ref(0.) s = source.methods(s) is_first = ref(true) def f() = time := time() + settings.frame.duration() if is_first() or getter.get(every) <= time() then is_first := false time := 0. s.insert_metadata(new_track=true, getter.get(metadata)) end end s.on_frame(synchronous=true, f) s end # Regularly skip tracks from a source (useful for testing skipping). # @category Source / Track processing # @flag extra # @param ~every How often to skip tracks. # @param s The stream. # @flag extra def skipper(~every=getter(5.), s) = start_time = ref(0.) def f() = if getter.get(every) <= s.time() - start_time() then start_time := s.time() s.skip() end end s.on_frame(f) s end # Special track insensitive fallback that always skips current song before # switching. # @category Source / Track processing # @flag extra # @param s The main source. # @param ~fallback The fallback source. Defaults to `blank` if `null`. def fallback.skip(s, ~fallback:f=null) = f = f ?? (blank() : source) avail = ref(true) def check() = old = avail() avail := source.is_ready(s) if not old and avail() then source.skip(f) end end s = fallback(track_sensitive=false, [s, f]) # TODO: could we have something more efficient that checking on every frame s.on_frame(synchronous=true, check) s end # Generate a CUE file for the source. This function will generate a new track in # the file for each metadata of the source. This function tries to map metadata to # the appropriate CUE file standard values. You can use the `map_metadata` argument # to add your own pre-processing. The following metadata are recognized on tracks: # `"title"`, `"artist"`, `"album"`, `"isrc"`, and `"cue_year"`. # @category Source / Track processing # @flag extra # @param filename Path where the CUE file should be written. # @param ~last_tracks Only report the number of last tracks. # @param ~title Title of the stream. # @param ~file File where the stream is stored. # @param ~file_type Format in which the stream is stored. # @param ~comment Comment about the stream. # @param ~year Year for the stream. # @param ~map_metadata Function to apply to metadata before writing the CUE file (useful for pre-processing metadata). # @param ~temp_dir Temporary directory for atomic write. # @param ~deduplicate_using To avoid duplicate entries, duplicate metadata are \ # filtered. Set this to a list of labels to use for detecting \ # duplicated metadata. # @param ~delete Delete the CUE files when starting if it exists. def source.cue( ~title=null, ~performer=null, ~file:f=getter(null), ~file_type=null, ~comment=null, ~year=null, ~map_metadata=fun (m) -> (m : [(string * string)]), ~last_tracks=null, ~temp_dir=null, ~deduplicate_using=["title", "artist", "album", "isrc", "cue_year"], ~delete=true, filename, s ) = is_first = ref(true) current_filename = ref("") def write(~append, entries) = filename = current_filename() f = getter.get(f) file_type = file_type ?? file.extension(leading_dot=false, f ?? "") if append then log( label="source.cue", level=4, "Writing new entry to #{filename}" ) else log( label="source.cue", level=4, "Writing full CUE file at #{filename}" ) if delete and file.exists(filename) then file.remove(filename) end end write = file.write.stream(append=append, atomic=true, temp_dir=temp_dir, filename) # Append to the file. def w(data) = write(data ^ "\n") end # Write a tag. def tag(~indent=0, ~quote=true, name, (value:string?)) = quote = if quote then fun (v) -> string.quote(v) else fun (v) -> v end if null.defined(value) then s = "#{string.spaces(indent)}#{name} #{quote(null.get(value))}" w(s) end end if is_first() or not append then is_first := false tag("TITLE", title) tag("PERFORMER", performer) tag( "REM COMMENT", comment ) tag( quote=false, "REM DATE", null.map(string.of_int, year) ) if null.defined(f) then w( "FILE \"#{(null.get(f) : string)}\" #{string.uppercase(file_type)}" ) end end list.iter( fun (entry) -> begin let {position = p, time = t, metadata = m} = entry tag( indent=2, quote=false, "TRACK", "#{string.of_int(digits=2, p)} AUDIO" ) tag(indent=4, "TITLE", list.assoc.nullable("title", m)) tag(indent=4, "PERFORMER", list.assoc.nullable("artist", m)) tag( indent=4, "REM ALBUM", list.assoc.nullable("album", m) ) tag( indent=4, quote=false, "REM DATE", list.assoc.nullable("cue_year", m) ) tag(indent=4, quote=false, "ISRC", list.assoc.nullable("isrc", m)) frames = int_of_float((t - floor(t)) * 75.) t = int_of_float(t) minutes = t / 60 seconds = t mod 60 m = string.of_int(digits=2, minutes) s = string.of_int(digits=2, seconds) f = string.of_int(digits=2, frames) tag( indent=4, quote=false, "INDEX 01", "#{m}:#{s}:#{f}" ) end, entries ) write("") end entries = ref([]) current_position = ref(1) t0 = ref(source.time(s)) def handle_metadata(m) = filename = getter.get(filename) if current_filename() != filename then log( label="source.cue", level=4, "Opening new CUE sheet at #{filename}" ) t0 := source.time(s) current_position := 1 current_filename := filename is_first := true end m = map_metadata(m) entry = {position=current_position(), time=source.time(s) - t0(), metadata=m} ref.incr(current_position) if null.defined(last_tracks) then current_entries = null.case( last_tracks, entries, fun (last_tracks) -> list.rev(list.prefix(last_tracks - 1, list.rev(entries()))) ) entries := [...current_entries, entry] write(append=false, entries()) else write(append=true, [entry]) end end s = metadata.deduplicate(using=deduplicate_using, s) s.on_metadata(synchronous=true, handle_metadata) s.on_frame( before=false, synchronous=true, fun () -> begin # If filename has changed but no metadata handler was executed, # insert one if current_filename() != getter.get(filename) then s.insert_metadata(new_track=true, s.last_metadata() ?? []) end end ) s end liquidsoap-2.4.2/src/libs/extra/spinitron.liq000066400000000000000000000153321513273233300212760ustar00rootroot00000000000000let spinitron = {submit=()} # Submit a track to the spinitron track system # and return the raw response. # @category Interaction # @flag extra # @param ~api_key API key def spinitron.submit.raw( ~host="https://spinitron.com/api", ~api_key, ~live=false, ~start=null, ~duration=null, ~artist, ~release=null, ~label=null, ~genre=null, ~song, ~composer=null, ~isrc=null ) = params = [("song", song), ("artist", artist)] def fold_optional_string_params(params, param) = let (label, param) = param if null.defined(param) then [(label, null.get(param)), ...params] else params end end params = list.fold( fold_optional_string_params, params, [ ("live", null.map(fun (b) -> b ? "1" : "0" , (live : bool?))), ("start", start), ("duration", null.map(string, (duration : int?))), ("release", release), ("label", label), ("genre", genre), ("composer", composer), ("isrc", isrc) ] ) def encode_param(param) = let (label, param) = param "#{label}=#{url.encode(param)}" end params = string.concat(separator="&", list.map(encode_param, params)) http.post( data=params, headers=[ ("Accept", "application/json"), ("Content-Type", "application/x-www-form-urlencoded"), ( "Authorization", "Bearer #{(api_key : string)}" ) ], "#{host}/spins" ) end # Submit a track to the spinitron track system # and return the parsed response # @category Interaction # @flag extra # @param ~api_key API key def replaces spinitron.submit(%argsof(spinitron.submit.raw)) = resp = spinitron.submit.raw(%argsof(spinitron.submit.raw)) if resp.status_code == 201 then let json.parse (resp : { id: int, playlist_id: int, "start" as spin_start: string, "end" as spin_end: string?, duration: int?, timezone: string?, image: string?, classical: bool?, artist: string, "artist-custom" as artist_custom: string?, composer: string?, release: string?, "release-custom" as release_custom: string?, va: bool?, label: string?, "label-custom" as label_custom: string?, released: int?, medium: string?, genre: string?, song: string, note: string?, request: bool?, local: bool?, new: bool?, work: string?, conductor: string?, performers: string?, ensemble: string?, "catalog-number" as catalog_number: string?, isrc: string?, upc: string?, iswc: string?, "_links" as links: {self: {href: string}?, playlist: {href: string}?}? } ) = resp resp elsif resp.status_code == 422 then let json.parse (errors : [{field: string, message: string}]) = resp errors = list.map( fun (p) -> begin let {field, message} = p "#{field}: #{message}" end, errors ) errors = string.concat( separator=", ", errors ) error.raise( error.raise( error.http, "Invalid fields: #{errors}" ) ) else let json.parse ({name, message, code, status, type} : {name: string, message: string, code: int, status: int, type: string?} ) = resp type = type ?? "undefined" error.raise( error.raise( error.http, "#{name}: #{message} (code: #{code}, status: #{status}, type: #{type})" ) ) end end # Submit a spin using the given metadata to the spinitron track system # and return the parsed response. `artist` and `song` (or `title`) must # be present either as metadata or as optional argument. # @category Interaction # @flag extra # @param m Metadata to submit. Overrides optional arguments when present. # @param ~mapper Metadata mapper that can be used to map metadata fields to spinitron's expected. \ # Returned metadata are added to the submitted metadata. By default, `title` is \ # mapped to `song` and `album` to `release` if neither of those passed otherwise. # @param ~api_key API key def spinitron.submit.metadata( %argsof(spinitron.submit[!artist,!song]), ~mapper=( fun (m) -> [ ...(m["song"] != "" or m["title"] == "" ? [] : [("song", m["title"])] ), ...( m["release"] != "" or m["album"] == "" ? [] : [("release", m["album"])] ) ] ), ~artist=null, ~song=null, m ) = m = [...m, ...mapper(m)] def conv_opt_arg(convert, label, default) = list.assoc.mem(label, m) ? convert(m[label]) : default end opt_arg = fun (label, default) -> conv_opt_arg(fun (x) -> null(x), label, default) live = conv_opt_arg(bool_of_string, "live", live) start = opt_arg("start", start) duration = conv_opt_arg(int_of_string, "duration", duration) artist = opt_arg("artist", artist) release = opt_arg("release", release) label = opt_arg("label", label) genre = opt_arg("genre", genre) song = opt_arg("song", song) composer = opt_arg("composer", composer) isrc = opt_arg("isrc", isrc) if artist == null or song == null then error.raise( error.invalid, "Both \"artist\" and \"song\" (or \"title\" metadata) must be provided!" ) end artist = null.get(artist) song = null.get(song) res = spinitron.submit(%argsof(spinitron.submit)) print(res) res end # Specialized version of `s.on_metadata` that submits spins using # the source's metadata to the spinitron track system. `artist` and `song` # (or `title`) must be present either as metadata or as optional argument. # @category Interaction # @flag extra # @param m Metadata to submit. Overrides optional arguments when present. # @param ~api_key API key def spinitron.submit.on_metadata(%argsof(spinitron.submit.metadata), s) = def on_metadata(m) = if m["title"] == "" and m["song"] == "" then log.severe( label=source.id(s), "Field \"song\" or \"title\" missing, skipping metadata spinitron \ submission." ) elsif m["artist"] == "" then log.severe( label=source.id(s), "Field \"artist\" missing, skipping metadata spinitron submission." ) else try ignore(spinitron.submit.metadata(%argsof(spinitron.submit.metadata), m)) log.important( label=source.id(s), "Successfully submitted spin from metadata" ) catch err do log.severe( label=source.id(s), "Error while submitting spin from metadata: #{err}" ) end end end source.methods(s).on_metadata(synchronous=false, on_metadata) end liquidsoap-2.4.2/src/libs/extra/telnet.liq000066400000000000000000000143251513273233300205450ustar00rootroot00000000000000# @docof request.dynamic def replaces request.dynamic(%argsof(request.dynamic), fn) = s = request.dynamic(%argsof(request.dynamic), fn) s.on_wake_up( synchronous=true, memoize( { server.register( namespace=source.id(s), description="Flush the queue and skip the current track", "flush_and_skip", fun (_) -> try s.set_queue([]) s.skip() "Done." catch err do "Error while flushing and skipping source: #{err}" end ) } ) ) s end # @docof blank.strip def replaces blank.strip(%argsof(blank.strip), s) = s = blank.strip(%argsof(blank.strip), s) s.on_wake_up( synchronous=true, memoize( { server.register( namespace=source.id(s), description="Check if the source is stripping.", "is_stripping", fun (_) -> begin "#{s.is_blank()}" end ) } ) ) s end # @docof output.external def replaces output.external(%argsof(output.external), f, p, s) = s = output.external(%argsof(output.external), f, p, s) s.on_wake_up( synchronous=true, memoize( { server.register( namespace=s.id(), description="Re-open the output.", "reopen", fun (_) -> begin s.reopen() "Done." end ) } ) ) s end # @docof input.external.avi def replaces input.external.avi(%argsof(input.external.avi), s) = s = input.external.avi(%argsof(input.external.avi), s) s.on_wake_up( synchronous=true, memoize( { server.register( namespace=source.id(s), description="Show internal buffer length (in seconds).", "buffer_length", fun (_) -> begin buffered = s.buffered() audio = list.assoc(default=0., "audio", buffered) video = list.assoc(default=0., "video", buffered) total = min(audio, video) "audio buffer length: #{audio}\nvideo buffer length: #{ video }\ntotal buffer length: #{total}" end ) } ) ) s end # @docof input.harbor def replaces input.harbor(%argsof(input.harbor), s) = s = input.harbor(%argsof(input.harbor), s) s.on_wake_up( synchronous=true, memoize( { begin server.register( namespace=source.id(s), description="Stop current source client, if connected.", "stop", fun (_) -> begin s.stop() "Done" end ) server.register( namespace=source.id(s), description="Display current status.", "status", fun (_) -> begin s.status() end ) server.register( namespace=source.id(s), description="Get the buffer's length, in seconds.", "buffer_length", fun (_) -> begin "#{s.buffer_length()}" end ) end } ) ) s end %ifdef input.http # @docof input.http def replaces input.http(%argsof(input.http), s) = s = input.http(%argsof(input.http), s) s.on_wake_up( synchronous=true, memoize( { begin server.register( namespace=source.id(s), description="Start the source, if needed.", "start", fun (_) -> begin s.start() "Done!" end ) server.register( namespace=source.id(s), description="Stop the source if connected.", "stop", fun (_) -> begin s.stop() "Done!" end ) server.register( namespace=source.id(s), description="Get or set the stream's HTTP URL. Setting a new URL \ will not affect an ongoing connection.", usage="url [url]", "url", fun (url) -> begin if url == "" then s.url() else begin s.set_url({url}) "Done!" end end end ) server.register( namespace=source.id(s), description="Return the current status of the source, either \ \"stopped\" (the source isn't trying to relay the HTTP stream), \ \"polling\" (attempting to connect to the HTTP stream) or \ \"connected \" (connected to , buffering or playing back \ the stream).", "status", fun (_) -> begin s.status() end ) server.register( namespace=source.id(s), description="Get the buffer's length, in seconds.", "buffer_length", fun (_) -> "#{list.fold(fun (l, (_, l')) -> max(l, l'), 0., s.buffered())}" ) end } ) ) s end %endif # @docof lufs def replaces lufs(%argsof(lufs), s) = s = lufs(%argsof(lufs), s) s.on_wake_up( synchronous=true, memoize( { server.register( namespace=source.id(s), description="Current value for the LUFS (short-term value computed \ over the duration specified by the `window` parameter).", "lufs", fun (_) -> "#{s.lufs()} LUFS" ) server.register( namespace=source.id(s), description="Average LUFS value over the current track.", "lufs_integrated", fun (_) -> "#{s.lufs_integrated()} LUFS" ) server.register( namespace=source.id(s), description="Momentary LUFS (over a 400ms window).", "lufs_momentary", fun (_) -> "#{s.lufs_momentary()} LUFS" ) } ) ) s end server.register( description="Shutdown the running liquidsoap instance", "shutdown", fun (_) -> begin shutdown() "OK" end ) liquidsoap-2.4.2/src/libs/extra/video.liq000066400000000000000000000030421513273233300203520ustar00rootroot00000000000000# Extract cover from the source's metadata and add it as a static image. # @category Track / Video processing # @flag extra # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param ~default Default cover file when no cover is available # @param ~mime_types Recognized mime types and their corresponding file extensions. def video.add_cover( ~fallible=false, ~width=null, ~height=null, ~x=getter(0), ~y=getter(0), ~mime_types=[ ("image/gif", "gif"), ("image/jpg", "jpeg"), ("image/jpeg", "jpeg"), ("image/png", "png"), ("image/webp", "webp") ], ~default, s ) = cover_file = file.cover.manager(mime_types=mime_types, default=default) s.on_metadata(synchronous=true, cover_file.set) video.add_request( fallible=fallible, x=x, y=y, width=width, height=height, request=cover_file, s ) end let video.ffmpeg = () %ifdef input.ffmpeg # ffmpeg's test source video (useful for testing and debugging). # @category Source / Video processing # @flag extra def video.ffmpeg.testsrc(~id="video.testsrc") = video = "testsrc=size=#{video.frame.width()}x#{video.frame.height()}:rate=#{ video.frame.rate() }" audio = "sine=frequency=440:beep_factor=2:sample_rate=#{audio.samplerate()}" input.ffmpeg( id=id, format="lavfi", "#{video} [out0]; #{audio} [out1]" ) end %endif liquidsoap-2.4.2/src/libs/extra/visualization.liq000066400000000000000000000040261513273233300221500ustar00rootroot00000000000000# VU meter: display the audio volume (RMS in dB) on the standard output. # @category Source / Visualization # @flag extra # @param ~rms_min Minimal volume (dB). # @param ~rms_max Maximal volume (dB). # @param ~scroll Update the display in the same line. # @param ~window Duration in seconds of volume computation. def vumeter(~rms_min=-25., ~rms_max=-5., ~window=0.5, ~scroll=false, s) = screen_width = 80 bar_width = screen_width let s = rms(duration=window, s) def display() = v = dB_of_lin(s.rms()) x = (v - rms_min) / (rms_max - rms_min) x = if x < 0. then 0. else x end x = if x > 1. then 1. else x end n = int_of_float(x * float_of_int(bar_width)) bar = ref("") if not scroll then bar := "\r" end for _ = 0 to n - 1 do bar := bar() ^ "=" end for _ = 0 to bar_width - n - 1 do bar := bar() ^ "." end bar := bar() ^ " " ^ string(v) if scroll then bar := bar() ^ "\n" end print(newline=false, bar()) end thread.run(fast=true, every=window, display) s end # VU meter: display the audio volume (RMS in dB). This adds a video track to the # source. # @category Source / Visualization # @flag extra # @param ~rms_min Minimal volume (dB). # @param ~rms_max Maximal volume (dB). # @param ~window Duration in seconds of volume computation. # @param ~color Color of the display (0xRRGGBB). # @param ~persistence Persistence of the display (s). def video.vumeter( ~rms_min=-35., ~rms_max=0., ~window=0.1, ~color=0xff0000, ~persistence=0., s ) = s = source(s.{video=source.tracks(blank()).video}) s = rms(duration=window, s) height = video.frame.height() width = ref(0) def update() = v = dB_of_lin(s.rms()) x = (v - rms_min) / (rms_max - rms_min) x = if x < 0. then 0. else x end x = if x > 1. then 1. else x end width := int_of_float(x * float_of_int(video.frame.width())) end thread.run(fast=true, every=window, update) s = video.add_rectangle(width=width, height=height, color=color, s) video.persistence(duration=persistence, s) end liquidsoap-2.4.2/src/libs/fades.liq000066400000000000000000000547321513273233300172170ustar00rootroot00000000000000fade = () let settings.fade = settings.make.void( "Settings for the fade in/out operators" ) let settings.fade.in = settings.make.void( "Settings for fade.in operators" ) let settings.fade.in.duration = settings.make( description="Default fade.in duration", 3. ) let settings.fade.in.type = settings.make( description="Default fade.in type", "lin" ) let settings.fade.in.curve = settings.make( description="Default fade.in curve", 10. ) let settings.fade.out = settings.make.void( "Settings for fade.out operators" ) let settings.fade.out.duration = settings.make( description="Default fade.out duration", 3. ) let settings.fade.out.type = settings.make( description="Default fade.out type", "lin" ) let settings.fade.out.curve = settings.make( description="Default fade.out curve", 10. ) # Make a fade function based on a source's clock. # @category Source / Fade # @param ~curve Fade curve for `"log"` and `"exp"` shapes. If `null`, depends on the type of fade. \ # The higher the value, the shaper the curve. # @param ~type Fade shape. One of: `"sin"`, `"exp"`, `"log"`, `"lin"` # @param ~start Start value. # @param ~stop Stop value. # @param ~delay Initial delay before starting fade. # @param ~duration Duration in seconds. # @param ~on_done Function to execute when the fade is finished def mkfade( ~curve=null, ~type="lin", ~start=0., ~stop=1., ~delay=0., ~duration=3., ~on_done={()}, s ) = def log(x) = log(label="mkfade", x) end # Shape functions must map 0. -> 0. and 1. -> 1. pi = acos(-1.) def sin_shape(x) = (1. + sin((x - 0.5) * pi)) / 2. end exp_curve = curve ?? 3. m = exp(exp_curve - 1.) - exp(-1.) def exp_shape(x) = (exp((exp_curve * x) - 1.) - exp(-1.)) / m end ln_curve = curve ?? 10. m = ln(1. + ln_curve) def log_shape(x) = ln(1. + ln_curve * x) / m end def lin_shape(x) = x end shape = if type == "sin" then sin_shape elsif type == "exp" then exp_shape elsif type == "log" then log_shape elsif type == "lin" then lin_shape else log( "Invalid type #{type}, using \"lin\"" ) lin_shape end start_time = ref(-1.) increasing_fade = start <= stop def fade() = if start_time() < 0. then start_time := source.time(s) end t = source.time(s) - start_time() - delay if t <= 0. then if increasing_fade then 0. else 1. end elsif t >= duration then on_done() stop else if increasing_fade then start + shape(t / duration) * (stop - start) else stop + shape(1. - t / duration) * (start - stop) end end end fade end # Scale source during fading. # @category Source / Fade # @flag hidden def fade.scale(~id="fade.scale", x, s) = amplify(id=id, override=null, x, s) end # Fade the end of tracks. # @category Source / Fade # @param ~id Force the value of the source ID. # @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. Defaults to `settings.fade.out.curve` if `null`. # @param ~delay Initial delay before starting fade. Defaults to `settings.fade.out.delay` if `null`. # @param ~curve Fade curve. Defaults to `settings.fade.out.curve` if `null`. # @param ~override_duration Metadata field which, if present and containing a float, overrides the 'duration' parameter for the current track. # @param ~override_type Metadata field which, if present and correct, overrides the 'type' parameter for the current track. # @param ~override_curve Metadata field which, if presents and correct, overrides the `curve` parameter for the current track. Use `"default"` \ # to set to default value. # @param ~override_delay Metadata field which, if presents and correct, overrides the initial fade delay. # @param ~persist_overrides Keep duration and type overrides on track change. # @param ~track_sensitive Be track sensitive (if `false` we only fade ou once at the beginning of the track). # @param ~initial_metadata Initial metadata. # @param ~type Fader shape. One of: "lin"", "sin", "log" or "exp". Defaults to `settings.fade.out.type` if `null`. def fade.out( ~id="fade.out", ~duration=null, ~delay=0., ~curve=null, ~override_duration="liq_fade_out", ~override_type="liq_fade_out_type", ~override_curve="liq_fade_out_curve", ~override_delay="liq_fade_out_delay", ~persist_overrides=false, ~track_sensitive=false, ~initial_metadata=[], ~type=null, s ) = def log(~level=4, x) = log(label=source.id(s), level=level, x) end fn = ref(fun () -> 1.) original_type = type ?? settings.fade.out.type() type = ref(original_type) original_curve = (curve ?? settings.fade.out.curve() : float?) curve = ref(original_curve) original_duration = duration ?? settings.fade.out.duration() duration = ref(original_duration) original_delay = delay delay = ref(original_delay) start_time = ref(-1.) started = ref(false) last_metadata = ref(initial_metadata) def start_fade(d, _) = curve_log = if null.defined(curve()) then string(null.get(curve())) else "default" end start_time := source.time(s) let (delay, duration) = if d < delay() + duration() then if d < duration() then (0., d) else (max(0., d - duration()), duration()) end else (delay(), duration()) end log( "Fading out with type: #{type()}, curve: #{curve_log}, delay: #{delay}s, \ duration: #{duration}s and #{d}s remaining." ) fn := mkfade( start=1., stop=0., type=type(), curve=curve(), delay=delay, duration=duration, s ) started := true end def apply() = fn = fn() fn() end def stop_fade(_) = if started() then fn := fun () -> 1. started := false end end def update_fade(m) = if m[override_duration] != "" then old_duration = duration() duration := float_of_string(default=duration(), m[override_duration]) if duration() != old_duration then log( "New fade out duration: #{duration()}s." ) end end if m[override_delay] != "" then old_delay = delay() delay := float_of_string(default=0., m[override_delay]) if delay() != old_delay then log( "New fade out delay: #{delay()}s." ) end end if m[override_curve] != "" then old_curve = curve() if m[override_curve] == "default" then curve := null else try curve := float_of_string(m[override_curve]) catch _ do curve := null end end if curve() != old_curve then log( "New fade out curve: #{curve()}." ) end end if m[override_type] != "" then old_type = type() type := m[override_type] if type() != old_type then log( "New fade out type: #{type()}." ) end end end update_fade(last_metadata()) def reset_overrides(_) = if not persist_overrides then log( "Setting fade out to default duration: #{original_duration}s, delay: #{ original_delay }s, type: #{original_type}, curve: #{original_curve}" ) duration := original_duration delay := original_delay type := original_type curve := original_curve end end s = source.methods(s) s.on_track(synchronous=true, reset_overrides) s.on_metadata(synchronous=true, update_fade) if track_sensitive then s.on_track(synchronous=true, stop_fade) end d = source.dynamic(memoize({s})) if track_sensitive then d.on_position( synchronous=true, remaining=true, position=duration, start_fade ) else d.on_frame( synchronous=true, before=false, memoize({start_fade(source.remaining(s), [])}) ) end # Make sure we generate a frame before starting the fade # to ensure that we catch all the initial metadata. d.on_frame( synchronous=true, before=true, memoize({if s.is_ready() then s.generate_frame() end}) ) fade.scale(id=id, apply, d).{fade_duration={duration()}, fade_type={type()}} end # Fade when the metadata trigger is received and then skip. # @flag extra # @category Source / Fade # @flag extra # @param ~id Force the value of the source ID. # @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. # @param ~delay Initial delay before starting fade. # @param ~curve Fade curve. Default if `null`. # @param ~override_duration Metadata field which, if present and containing a float, overrides the 'duration' parameter for the current track. # @param ~override_type Metadata field which, if present and correct, overrides the 'type' parameter for the current track. # @param ~override_curve Metadata field which, if presents and correct, overrides the `curve` parameter for the current track. Use `"default"` \ # to set to default value. # @param ~override_skip Metadata field which, when present and set to "true", will trigger the fade # @param ~persist_overrides Keep duration and type overrides on track change. # @param ~initial_metadata Initial metadata. # @param ~type Fader shape (lin|sin|log|exp): linear, sinusoidal, logarithmic or exponential. def fade.skip( ~id="fade.skip", ~duration=5., ~delay=0., ~curve=null, ~override_duration="liq_fade_skip", ~override_type="liq_fade_skip_type", ~override_curve="liq_fade_skip_curve", ~persist_overrides=false, ~override_skip="liq_skip_meta", ~initial_metadata=[], ~type="lin", s ) = def log(x) = log(label=source.id(s), level=4, x) end fn = ref(fun () -> 1.) original_type = type type = ref(type) original_curve = curve curve = ref(curve) original_duration = duration duration = ref(duration) original_delay = delay delay = ref(original_delay) last_metadata = ref(initial_metadata) def apply() = fn = fn() fn() end def stop_fade(_) = fn := fun () -> 1. end def skip() = log( "Fade finished executing. Calling skip now" ) source.skip(s) end def update_fade(m) = if m[override_skip] == "true" then remaining = source.remaining(s) duration = if remaining < duration() then remaining else duration() end log( "Skip fade executed for: #{duration}s" ) fn := mkfade( start=1., stop=0., type=type(), curve=curve(), duration=duration, delay=delay(), on_done=skip, s ) end if m[override_duration] != "" then old_duration = duration() duration := float_of_string(default=duration(), m[override_duration]) if duration() != old_duration then log( "New fade skip duration: #{duration()}s." ) end end if m[override_curve] != "" then old_curve = curve() if m[override_curve] == "default" then curve := null else try curve := float_of_string(m[override_curve]) catch _ do curve := null end end if curve() != old_curve then log( "New fade skip curve: #{curve()}." ) end end if m[override_type] != "" then old_type = type() type := m[override_type] if type() != old_type then log( "New fade skip type: #{type()}." ) end end end update_fade(last_metadata()) def reset_overrides(_) = if not persist_overrides then log( "Setting fade skip to default duration: #{original_duration}s, delay: #{ original_delay }s, type: #{original_type}, curve: #{original_curve}" ) duration := original_duration type := original_type curve := original_curve end end s = source.methods(s) s.on_track(synchronous=true, reset_overrides) s.on_metadata(synchronous=true, update_fade) s.on_track(synchronous=true, stop_fade) fade.scale(id=id, apply, s).{fade_duration={duration()}, fade_type={type()}} end # Fade the beginning of tracks. # @category Source / Fade # @param ~id Force the value of the source ID. # @param ~duration Duration of the fading. This value can be set on a per-file basis using the metadata field passed as override. Defaults to `settings.fade.in.duration` if `null`. # @param ~delay Initial delay before starting fade. # @param ~curve Fade curve. Defaults to `settings.fade.in.curve` if `null`. # @param ~override_duration Metadata field which, if present and containing a float, overrides the 'duration' parameter for the current track. # @param ~override_type Metadata field which, if present and correct, overrides the 'type' parameter for the current track. # @param ~override_curve Metadata field which, if presents and correct, overrides the `curve` parameter for the current track. Use `"default"` \ # to set to default value. # @param ~override_delay Metadata field which, if presents and correct, overrides the initial fade delay. # @param ~persist_overrides Keep duration and type overrides on track change. # @param ~track_sensitive Be track sensitive (if `false` we only fade in once at the beginning of the track). # @param ~initial_metadata Initial metadata. # @param ~type Fader shape. One of: "lin"", "sin", "log" or "exp". Defaults to `settings.fade.in.type` if `null`. def fade.in( ~id="fade.in", ~duration=null, ~delay=0., ~curve=null, ~override_duration="liq_fade_in", ~override_type="liq_fade_in_type", ~override_curve="liq_fade_in_curve", ~override_delay="liq_fade_in_delay", ~persist_overrides=false, ~track_sensitive=false, ~initial_metadata=[], ~type=null, s ) = def log(~level=4, x) = log(label=source.id(s), level=level, x) end fn = ref(fun () -> 0.) original_duration = duration ?? settings.fade.in.duration() duration = ref(original_duration) original_delay = delay delay = ref(original_delay) original_type = type ?? settings.fade.in.type() type = ref(original_type) original_curve = curve ?? settings.fade.in.curve() curve = ref(curve) last_metadata = ref(initial_metadata) def apply() = fn = fn() fn() end def start_fade(_) = curve_log = if null.defined(curve()) then string(null.get(curve())) else "default" end duration = if source.remaining(s) < duration() then source.remaining(s) else duration() end log( "Fading in with type: #{type()}, curve: #{curve_log}, delay: #{delay()}s \ and duration: #{duration}s." ) # Note: delay here must match the add delay via blank to make the curve also match fn := mkfade( start=0., stop=1., type=type(), curve=curve(), delay=delay(), duration=duration, s ) end def update_fade(m) = if m[override_duration] != "" then old_duration = duration() duration := float_of_string(default=duration(), m[override_duration]) if duration() != old_duration then log( "New fade in duration: #{duration()}s." ) end end if m[override_delay] != "" then old_delay = delay() delay := float_of_string(default=0., m[override_delay]) if delay() != old_delay then log( "New fade in delay: #{delay()}s." ) end end if m[override_curve] != "" then old_curve = curve() if m[override_curve] == "default" then curve := null else try curve := float_of_string(m[override_curve]) catch _ do curve := null end end if curve() != old_curve then log( "New fade in curve: #{curve()}." ) end end if m[override_type] != "" then old_type = type() type := m[override_type] if type() != old_type then log( "New fade in type: #{type()}." ) end end end update_fade(last_metadata()) def reset_overrides(_) = if not persist_overrides then log( "Setting fade in to default duration: #{original_duration}s, delay: #{ original_delay }s, type: #{original_type}, curve: #{original_curve}" ) duration := original_duration delay := original_delay type := original_type curve := original_curve end end s = source.methods(s) s.on_track(synchronous=true, reset_overrides) s.on_metadata(synchronous=true, update_fade) # Initial duration for fade.in should be computed after fetching the initial # frame from s to make sure any initial metadata is taken into account prepare = ref(fun (_) -> ()) s = source.dynamic( memoize( fun () -> begin prepare = prepare() prepare(s) if s.is_active() then s.generate_frame() end delay = delay() if delay > 0. then effective_source = source.methods(source.effective(s)) if s.is_active() then log( level=2, "Active source #{effective_source.id()} is faded in after an \ initial delay of #{delay}s. This will result is initial data \ loss since the source will be animated while the delay \ expires." ) end sequence(merge=true, [blank(duration=delay), s]) else s end end ) ) s = fade.scale(id=id, apply, s) if track_sensitive then s.on_track(synchronous=true, start_fade) else s.on_frame(synchronous=true, before=false, memoize({start_fade([])})) end s.{fade_duration={duration()}, fade_delay={delay()}, fade_type={type()}} end # Simple transition for crossfade # @category Source / Fade # @param ~log Logging utility # @param ~fade_in Fade-in duration, if any. # @param ~fade_out Fade-out duration, if any. # @param ~initial_fade_in_metadata Initial fade-in metadata # @param ~initial_fade_out_metadata Initial fade-out metadata # @param ~ending_map Optional mapping for the ending track # @param ~starting_map Optional mapping for the starting track # @param a Ending track # @param b Starting track def cross.simple( ~log=(fun (s) -> log(label="cross.simple", level=3, s)), ~fade_in=3., ~fade_out=3., ~initial_fade_in_metadata=[], ~initial_fade_out_metadata=[], ~ending_map=fun (s) -> s, ~starting_map=fun (s) -> s, a, b ) = fade_out_start_next = if list.assoc.mem("liq_fade_out_start_next", initial_fade_out_metadata) then float_of_string(initial_fade_out_metadata["liq_fade_out_start_next"]) else 0. end fade_in_delay = if list.assoc.mem("liq_fade_in_delay", initial_fade_in_metadata) then float_of_string(initial_fade_in_metadata["liq_fade_in_delay"]) else fade_out_start_next end fade_in_delay = if source.duration(b) < source.duration(a) then delay = source.duration(a) - source.duration(b) log( "Adding #{delay} of fade_in_delay to match ending source" ) fade_in_delay + delay else fade_in_delay end def fade.out(s) = fade.out( type="sin", persist_overrides=true, duration=fade_out, initial_metadata=initial_fade_out_metadata, s ) end def fade.in(s) = fade.in( type="sin", persist_overrides=true, duration=fade_in, initial_metadata=[ ...list.assoc.remove("liq_fade_in_delay", initial_fade_in_metadata), ("liq_fade_in_delay", "#{fade_in_delay}") ], s ) end add = fun (a, b) -> add(normalize=false, [starting_map(b), ending_map(a)]) add(fade.out(a), fade.in(b)) end # @docof cross # @param ~deduplicate Crossfade transitions can generate duplicate metadata. When `true`, the operator \ # removes duplicate metadata from the returned source. def replaces cross(%argsof(cross), ~deduplicate=true, transition, s) = if not deduplicate then cross(%argsof(cross), transition, s) else s = cross(%argsof(cross[!id]), transition, s) dedup = metadata.deduplicate(s) dedup.{ buffered=s.buffered, duration=s.duration, start_duration=s.start_duration, end_duration=s.end_duration } end end # Crossfade between tracks, taking the respective volume levels into account in # the choice of the transition. # @category Source / Fade # @argsof cross[id,start_duration,end_duration,duration,override_start_duration,override_end_duration,override_duration,persist_override,width] # @param ~deduplicate Crossfade transitions can generate duplicate metadata. When `true`, the operator \ # removes duplicate metadata from the returned source. # @param s The input source. def crossfade( %argsof(cross[ id, start_duration, end_duration, duration, override_start_duration, override_end_duration, override_duration, persist_override, width] ), ~fade_in=3., ~fade_out=3., ~deduplicate=true, s ) = id = string.id.default(default="crossfade", id) def log(~level=3, x) = log(label=id, level=level, x) end def transition(a, b) = list.iter( fun (x) -> log( level=4, "Before: #{x}" ), a.metadata ) list.iter( fun (x) -> log( level=4, "After : #{x}" ), b.metadata ) cross.simple( log=log, fade_in=fade_in, fade_out=fade_out, initial_fade_in_metadata=b.metadata, initial_fade_out_metadata=a.metadata, a.source, b.source ) end cross( %argsof(cross[ id, start_duration, end_duration, duration, override_start_duration, override_end_duration, override_duration, persist_override, width] ), deduplicate=deduplicate, transition, s ) end liquidsoap-2.4.2/src/libs/ffmpeg.liq000066400000000000000000000354251513273233300173770ustar00rootroot00000000000000%ifdef track.ffmpeg.raw.encode.audio let ffmpeg.encode = () let ffmpeg.raw.encode = () # Encode a source's audio content # @category Source / Conversion def ffmpeg.encode.audio(~id=null("ffmpeg.encode.audio"), f, s) = audio = track.ffmpeg.encode.audio(f, source.tracks(s).audio) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio } ) end # Encode a source's audio content # @category Source / Conversion def ffmpeg.raw.encode.audio(~id=null("ffmpeg.raw.encode.audio"), f, s) = audio = track.ffmpeg.raw.encode.audio(f, source.tracks(s).audio) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio } ) end # Encode a source's video content # @category Source / Conversion def ffmpeg.encode.video(~id=null("ffmpeg.encode.video"), f, s) = video = track.ffmpeg.encode.video(f, source.tracks(s).video) source( id=id, { track_marks=track.track_marks(video), metadata=track.metadata(video), video=video } ) end # Encode a source's video content # @category Source / Conversion def ffmpeg.raw.encode.video(~id=null("ffmpeg.raw.encode.video"), f, s) = video = track.ffmpeg.raw.encode.video(f, source.tracks(s).video) source( id=id, { track_marks=track.track_marks(video), metadata=track.metadata(video), video=video } ) end # Encode a source's audio and video content # @category Source / Conversion def ffmpeg.encode.audio_video(~id=null("ffmpeg.encode.audio_video"), f, s) = let {audio, video} = source.tracks(s) audio = track.ffmpeg.encode.audio(f, audio) video = track.ffmpeg.encode.video(f, video) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio, video=video } ) end # Encode a source's audio and video content # @category Source / Conversion def ffmpeg.raw.encode.audio_video( ~id=null("ffmpeg.raw.encode.audio_video"), f, s ) = let {audio, video} = source.tracks(s) audio = track.ffmpeg.raw.encode.audio(f, audio) video = track.ffmpeg.raw.encode.video(f, video) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio, video=video } ) end %endif %ifdef track.ffmpeg.decode.audio let ffmpeg.decode = () let ffmpeg.raw.decode = () # Decode a source's audio content # @category Source / Conversion def ffmpeg.decode.audio(~id=null("ffmpeg.decode.audio"), s) = audio = track.ffmpeg.decode.audio(source.tracks(s).audio) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio } ) end # Decode a source's audio content # @category Source / Conversion def ffmpeg.raw.decode.audio(~id=null("ffmpeg.raw.decode.audio"), s) = audio = track.ffmpeg.raw.decode.audio(source.tracks(s).audio) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio } ) end # Decode a source's video content # @category Source / Conversion def ffmpeg.decode.video(~id=null("ffmpeg.decode.video"), s) = video = track.ffmpeg.decode.video(source.tracks(s).video) source( id=id, { track_marks=track.track_marks(video), metadata=track.metadata(video), video=video } ) end # Decode a source's video content # @category Source / Conversion def ffmpeg.raw.decode.video(~id=null("ffmpeg.raw.decode.video"), s) = video = track.ffmpeg.raw.decode.video(source.tracks(s).video) source( id=id, { track_marks=track.track_marks(video), metadata=track.metadata(video), video=video } ) end # Decode a source's audio and video content # @category Source / Conversion def ffmpeg.decode.audio_video(~id=null("ffmpeg.decode.audio_video"), s) = let {audio, video} = source.tracks(s) audio = track.ffmpeg.decode.audio(audio) video = track.ffmpeg.decode.video(video) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio, video=video } ) end # Decode a source's audio and video content # @category Source / Conversion def ffmpeg.raw.decode.audio_video( ~id=null("ffmpeg.raw.decode.audio_video"), s ) = let {audio, video} = source.tracks(s) audio = track.ffmpeg.raw.decode.audio(audio) video = track.ffmpeg.raw.decode.video(video) source( id=id, { track_marks=track.track_marks(audio), metadata=track.metadata(audio), audio=audio, video=video } ) end %endif %ifdef input.ffmpeg # Stream from a video4linux2 input device, such as a webcam. # @category Source / Input # @param ~id Force the value of the source ID. # @param ~max_buffer Maximum data buffer in seconds # @param ~device V4L2 device to use. def input.v4l2(~id=null, ~max_buffer=0.5, ~device="/dev/video0") = (input.ffmpeg(id=id, format="v4l2", max_buffer=max_buffer, device) : source(video=canvas) ) end # A test video source, which generates various patterns. # @category Source / Video processing # @param ~pattern Pattern drawn in the video: `"testsrc"`, `"testsrc2"`, `"smptebars"`, `"pal75bars"`, `"pal100bars"`, `"smptehdbars"`, `"yuvtestsrc"` or `"rgbtestsrc"` and more. Support any of the patterns supported by `ffmpeg`. # @param ~max_buffer Maximum data buffer in seconds # @param ~duration Duration of the source. def video.testsrc.ffmpeg( ~id=null, ~pattern="testsrc", ~max_buffer=0.5, ~duration=null ) = size = "size=#{settings.frame.video.width()}x#{settings.frame.video.height()}" rate = "rate=#{settings.frame.video.framerate()}" duration = if null.defined(duration) then ":duration=#{duration}" else "" end src = "#{pattern}=#{size}:#{rate}#{duration}" (input.ffmpeg(id=id, max_buffer=max_buffer, format="lavfi", src) : source(video=canvas) ) end # Read an RTMP stream. # @category Source / Input # @param ~max_buffer Maximum data buffer in seconds # @param ~listen Act as a RTMP server and wait for incoming connection # @param url URL to read RTMP from, in the form `rtmp://IP:PORT/ENDPOINT` def input.rtmp(~id=null, ~max_buffer=5., ~listen=true, url) = input.ffmpeg( id=id, max_buffer=max_buffer, format="live_flv", self_sync=true, int_args=[("listen", listen ? 1 : 0 )], url ) end %endif %ifdef ffmpeg.filter.drawtext let video.add_text.ffmpeg = () let video.add_text.ffmpeg.raw = () # Display a text. Use this operator inside ffmpeg filters with a ffmpeg video input # Returns a ffmpeg video output with `on_change` and `on_metadata` methods to be used # to update the output text. # @category Source / Video processing # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (0 means no scrolling and update \ # according to x and y in case they are variable). # @param ~graph a ffmpeg filter graph to attach this filter to. # @param ~x x offset. # @param ~y y offset. # @params Text to display. # @method on_change Method to call when parameters have changed to update the filter's rendered out, including when text changes. # @method on_metadata Method to call on new metadata. def video.add_text.ffmpeg.raw.filter( ~color=0xffffff, ~cycle=true, ~font=null, ~metadata=null, ~size=18, ~speed=70, ~x=getter(10), ~y=getter(10), ~graph, d=getter(""), s ) = color = "0x" ^ string.hex_of_int(pad=6, color) x = if speed != 0 then last_time = ref(time()) changed = getter.changes(x) effective_x = ref(getter.get(x)) getter( { begin cur_time = time() traveled_to = int(float(speed) * (cur_time - last_time())) last_time := cur_time if changed() then effective_x := getter.get(x) else effective_x := effective_x() - traveled_to end if effective_x() < 0 then effective_x := settings.frame.video.width() - effective_x() end effective_x() end } ) else x end filter = ffmpeg.filter.drawtext.create( fontfile=font, fontsize="#{size}", x="#{getter.get(x)}", y="#{getter.get(y)}", fontcolor=color, text=getter.get(d), graph ) def escape = def special_char(~encoding:_, s) = string.contains(substring=s, "(',%,\\,:,{,})") end def escape_char(~encoding:_, s) = "\\#{s}" end fun (s) -> string.escape(special_char=special_char, escape_char=escape_char, s) end def escaped_text() = escape(getter.get(d)) end filters = [ { args= getter( {"x=#{getter.get(x)}:y=#{getter.get(y)}:text=#{escaped_text()}"} ), filter=filter } ] filters = if cycle then x = getter({"min(#{getter.get(x)}-w,#{getter.get(x)}-text_w)"}) [ ...filters, { args= getter( {"x=#{getter.get(x)}:y=#{getter.get(y)}:text=#{escaped_text()}"} ), filter= ffmpeg.filter.drawtext.create( fontfile=font, fontsize="#{size}", x="#{getter.get(x)}", y="#{getter.get(y)}", fontcolor=color, text=getter.get(d), graph ) } ] else filters end changed = getter.changes(getter({(getter.get(x), getter.get(y), getter.get(d))})) def on_change() = ignore(getter.get(x)) ignore(getter.get(y)) ignore(getter.get(d)) if changed() then list.iter( ( fun (el) -> ignore(el.filter.process_command("reinit", getter.get(el.args))) ), filters ) end end def on_metadata(m) = if null.defined(metadata) then meta = (null.get(metadata) : string) d = escape(m[meta]) if d != "" then log( level=3, label="ffmpeg.filter.drawtext", "Setting new text #{d} from metadata #{meta}" ) list.iter( ( fun (el) -> ignore(el.filter.process_command("reinit", "text=#{d}")) ), filters ) end end end v = list.fold( ( fun (cur, el) -> begin el.filter.set_input(cur) el.filter.output end ), s, filters ) v.{on_change=on_change, on_metadata=on_metadata} end # Display a text. Use this operator inside ffmpeg filters with a input source # @category Source / Video processing # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (0 means no scrolling and update \ # according to x and y in case they are variable). # @param ~graph a ffmpeg filter graph to attach this filter to. # @param ~x x offset. # @param ~y y offset. # @params Text to display. def replaces video.add_text.ffmpeg.raw( ~color=0xffffff, ~cycle=true, ~font=null, ~metadata=null, ~size=18, ~speed=70, ~x=getter(10), ~y=getter(10), ~graph, d=getter(""), s ) = on_frame = ref(fun () -> ()) on_metadata = ref(fun (_) -> ()) s.on_metadata( synchronous=true, fun (m) -> begin fn = on_metadata() fn(m) end ) s.on_frame( synchronous=true, fun () -> begin fn = on_frame() fn() end ) s = ffmpeg.filter.video.input(graph, source.tracks(s).video) v = video.add_text.ffmpeg.raw.filter( color=color, cycle=cycle, font=font, metadata=metadata, size=size, speed=speed, x=x, y=y, graph=graph, d, s ) on_frame := v.on_change on_metadata := v.on_metadata ffmpeg.filter.null(graph, v) end def video.add_text.ffmpeg.internal( ~id=null, ~color=0xffffff, ~cycle=true, ~font=null, ~duration=null, ~metadata=null, ~size=18, ~speed=70, ~x=getter(10), ~y=getter(10), d, s ) = id = string.id.default(default="video.add_text.ffmpeg", id) s = ffmpeg.raw.encode.audio_video(%ffmpeg(%audio.raw, %video.raw), s) def mkfilter(graph) = v = video.add_text.ffmpeg.raw( color=color, cycle=cycle, font=font, metadata=metadata, size=size, speed=speed, x=x, y=y, graph=graph, d, s ) v = ffmpeg.filter.video.output(graph, v) a = ffmpeg.filter.audio.input(graph, source.tracks(s).audio) a = ffmpeg.filter.acopy(graph, a) a = ffmpeg.filter.audio.output(graph, a) source( id=id, { audio=a, video=v, metadata=track.metadata(a), track_marks=track.track_marks(a) } ) end s = ffmpeg.filter.create(mkfilter) s = ffmpeg.raw.decode.audio_video(s) null.defined(duration) ? max_duration(null.get(duration), s) : s end # Display a text. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (0 means no scrolling and update \ # according to x and y in case they are variable). # @param ~x x offset. # @param ~y y offset. # @params Text to display. def replaces video.add_text.ffmpeg( %argsof(video.add_text.ffmpeg.internal), d, s ) = video.add_text.ffmpeg.internal(%argsof(video.add_text.ffmpeg.internal), d, s) end %endif # video.add_text.available := [("ffmpeg", video.add_text.ffmpeg.internal), ...video.add_text.available()] # if settings.video.add_text() != "sdl" then # settings.video.add_text.set("ffmpeg") # end %ifdef ffmpeg.filter.video.output let ffmpeg.filter.audio_video = () # Return a source with audio and video from a filter's output. # @category Source / Output # @param id Force the value of the source ID. def ffmpeg.filter.audio_video.output(~id=null, graph, audio, video) = audio = ffmpeg.filter.audio.output(graph, audio.tracks().audio) video = ffmpeg.filter.video.output(graph, video.tracks().video) source(id=id, {audio=audio, video=video}) end %endif liquidsoap-2.4.2/src/libs/file.liq000066400000000000000000000221651513273233300170470ustar00rootroot00000000000000# @docof file.temp # @param ~cleanup Delete the file on shutdown def file.temp(~cleanup=false, %argsof(file.temp), prefix, suffix) = f = file.temp(%argsof(file.temp), prefix, suffix) if cleanup then on_cleanup({file.remove(f)}) end f end # @docof file.temp_dir # @param ~cleanup Delete the file on shutdown def file.temp_dir(~cleanup=false, prefix, suffix="") = dir = file.temp_dir(prefix, suffix) if cleanup then on_cleanup({file.rmdir(dir)}) end dir end # Read the content of a file. Returns a function of type `()->string`. File is # done reading when function returns the empty string `""`. # @category File # @method close Close the underlying file descriptor without waiting for the whole file to be read. def file.read(fname) = fd = file.open(write=false, fname) is_done = ref(false) def close() = if not is_done() then is_done := true fd.close() end end def read() = if is_done() then "" else s = fd.read() if s == "" then close() end s end end read.{close=close} end let file.write = () # Stream data to a file. Returns a callback to write to the file. Execute # with `null` or `""` to signify the end of the writing operation. # @category File # @param ~append Append data if file exists. # @param ~perms Default file rights if created. Default: `0o644`. # @param ~atomic Make the write atomic by writing to a temporary file and moving \ # the file to destination once writing has succeeded. # @param ~temp_dir Temporary directory for atomic write. # @param path Path to write to def file.write.stream( ~perms=0o644, ~append=false, ~atomic=false, ~temp_dir=null, p ) = let (fd, exec) = if atomic then let (temp_dir, ensure) = if null.defined(temp_dir) then (null.get(temp_dir), {()}) else temp_dir = file.temp_dir("temp", "dir") (temp_dir, {file.rmdir(temp_dir)}) end tmp = path.concat(temp_dir, "atomic.write") if append and file.exists(p) then file.copy(p, tmp) end def exec() = try file.move(atomic=true, tmp, p) catch _ : [error.file.cross_device] do log( label="file.write", "Atomic rename failed! Directory for temporary files appears to be \ on a different file system. Please set it to the same one using \ `temp_dir` argument to guarantee atomic file operations!" ) file.copy(force=true, tmp, p) finally ensure() end end fd = file.open(write=true, append=append, perms=perms, tmp) (fd, exec) else (file.open(write=true, append=append, perms=perms, p), {()}) end def write(s) = s = s ?? "" if s == "" then if not fd.closed() then fd.close() exec() end else fd.write(s) end end write end # Write data to a file. # @category File # @param ~data Data to write. If passing a callback `() -> string?`, the callback \ # must return `null` or `""` when it has finished sending all its data. # @param ~append Append data if file exists. # @param ~perms Default file rights if created. Default: `0o644`. # @param ~atomic Make the write atomic by writing to a temporary file and moving \ # the file to destination once writing has succeeded. # @param ~temp_dir Temporary directory for atomic write. # @param path Path to write to. def replaces file.write( ~data, ~perms=0o644, ~append=false, ~atomic=false, ~temp_dir=null, path ) = cb = file.write.stream( append=append, perms=perms, atomic=atomic, temp_dir=temp_dir, path ) try s = ref(getter.get(data) ?? "") cb(s()) if getter.is_constant(data) then cb("") else while s() != "" do s := getter.get(data) ?? "" cb(s()) end end catch err do cb("") error.raise(err) end end # Ensure that a file exists, creating it empty if it does not. # @category File # @param path Path of the file. def file.touch(~perms=0o644, path) = file.write(data="", perms=perms, append=true, path) end # Read the whole contents of a file. # @category File def file.contents(fname) = fn = file.read(fname) cur = ref("") next = ref(fn()) while next() != "" do cur := "#{cur()}#{next()}" next := fn() end cur() end # Get the list of lines of a file. # @category File def file.lines(fname) = r/\n/.split(file.contents(fname)) end # Iterate over the lines of a file. # @category File def file.lines.iterator(fname) = list.iterator(file.lines(fname)) end # Iterate over the contents of a file. # @category File def file.iterator(fname) = f = file.read(fname) fun () -> begin s = f() (s == "") ? null : s end end # Get a file's mime type by calling the `file` command line binary. # @category File def file.mime.cli(fname) = mime = list.hd( default="", process.read.lines( process.quote.command("file", args=["-b", "--mime-type", fname]) ) ) mime == "" ? null : mime end # Get a file's mime type. Uses libmagic if enabled, otherwise try # to get the value using the file binary. Returns `null` if no value # can be found. # @category File # @param file The file to test def replaces file.mime(fname) = fn = file.mime.cli %ifdef file.mime.libmagic ignore(fn) fn = file.mime.libmagic %endif fn(fname) end # Getter to the contents of a file. # @category File # @param fname Name of the file from which the contents should be taken. def file.getter(fname) = contents = ref("") def update() = contents := file.contents(fname) end update() ignore(file.watch(fname, update)) ref.getter(contents) end # Float getter from a file. # @category File # @param fname Name of the file from which the contents should be taken. # @param ~default Default value when the file contains invalid data. def file.getter.float(~default=0., fname) = x = file.getter(fname) def f(x) = float_of_string(default=default, string.trim(x)) end getter.map.memoize(f, x) end %ifndef file.metadata.flac let file.metadata.flac = fun (_) -> [] %endif let file.metadata.flac.cover = () # Decode a flac-encoded cover metadata string. # @category String def file.metadata.flac.cover.decode(s) = # See https://xiph.org/flac/format.html#metadata_block_picture i = ref(0) def read_int() = ret = string.binary.to_int( little_endian=false, string.sub(encoding="ascii", s, start=i(), length=4) ) i := i() + 4 ret end def read_string(len) = ret = string.sub(encoding="ascii", s, start=i(), length=len) i := i() + len (ret : string) end pic_type = read_int() mime_len = read_int() mime = mime_len == 0 ? "image/" : read_string(mime_len) desc_len = read_int() desc = read_string(desc_len) width = read_int() height = read_int() color_depth = read_int() number_of_colors = read_int() number_of_colors = number_of_colors > 0 ? null(number_of_colors) : null data_len = read_int() data = string.sub(encoding="ascii", s, start=i(), length=data_len) if data == "" then log.info( "Failed to read cover metadata" ) null else null( data.{ picture_type=pic_type, mime=mime, description=desc, width=width, height=height, color_depth=color_depth, number_of_colors=number_of_colors } ) end end # Encode cover metadata for embedding with flac files. # @category String def file.metadata.flac.cover.encode( ~picture_type, ~mime, ~description="", ~width, ~height, ~color_depth, ~number_of_colors=null, data ) = def encode_string(s) = len = 1 + (string.bytes.length(s) / 8) str_len = string.binary.of_int(little_endian=false, pad=4, len) if string.bytes.length(str_len) > 4 then error.raise( error.invalid, "Data length too long for APIC format!" ) end pad = string.make(char_code=0, len * 8 - string.bytes.length(s)) (str_len, "#{s}#{pad}") end pic_type = string.binary.of_int(little_endian=false, pad=4, picture_type) let (mime_len, mime) = encode_string(mime) let (desc_len, description) = encode_string(description) width = string.binary.of_int(little_endian=false, pad=4, width) height = string.binary.of_int(little_endian=false, pad=4, height) color_depth = string.binary.of_int(little_endian=false, pad=4, color_depth) number_of_colors = string.binary.of_int(little_endian=false, pad=4, number_of_colors ?? 0) let (data_len, data) = encode_string(data) "#{pic_type}#{mime_len}#{mime}#{desc_len}#{description}#{width}#{height}#{ color_depth }#{number_of_colors}#{data_len}#{data}" end # Download file using a regular http.get request. Returns `true` on success. # @category File # @param ~filename Downloaded filename. # @param ~timeout Timeout in seconds def file.download(~filename, ~timeout=5., url) = file_writer = file.write.stream(filename) response = http.get.stream(on_body_data=file_writer, timeout=timeout, url) response.status_code < 400 end liquidsoap-2.4.2/src/libs/getter.liq000066400000000000000000000025451513273233300174220ustar00rootroot00000000000000# Construct a function returning the value of a getter. # @category Getter def getter.function(x) = {getter.get(x)} end # Determine if a getter is a constant. # @category Getter def getter.is_constant(x) = getter.case(x, fun (_) -> true, fun (_) -> false) end # Convert an int getter to a float getter. # @category Getter def getter.float_of_int(x) = getter.map(float_of_int, x) end # Convert a float getter to a int getter. # @category Getter def getter.int_of_float(x) = getter.map(int_of_float, x) end # Execute a function when the value of the getter changes. # @category Getter def getter.on_change(f, x) = x = {getter.get(x)} old = ref(x()) fun () -> begin new = x() if old() != new then old := new f(new) end new end end # Detect whether the value of the getter changes. # @category Getter def getter.changes(x) = old = ref(getter.get(x)) fun () -> begin new = getter.get(x) if old() != new then old := new true else false end end end # Give the latest value among two getters. # @category Getter def getter.merge(x, y) = v = ref(getter.get(x)) x = getter.on_change(fun (x) -> v := x, x) y = getter.on_change(fun (y) -> v := y, y) fun () -> begin ignore(x()) ignore(y()) v() end end liquidsoap-2.4.2/src/libs/hls.liq000066400000000000000000000172301513273233300167130ustar00rootroot00000000000000let hls = {playlist=()} # Generate a main HLS playlist # @category String def hls.playlist.main(~extra_tags=[], ~prefix="", ~version=7, streams) = prefix = prefix == "" or r/\/$/.test(prefix) ? prefix : "#{prefix}/" streams = list.fold( fun (streams, s) -> begin let ({bandwidth, codecs, video_size?, ...s} : string.{ bandwidth: int, codecs: string, video_size?: (int * int) } ) = s resolution = if null.defined(video_size) then let (w, h) = null.get(video_size) ",RESOLUTION=#{w}x#{h}" else "" end [ ...streams, "#EXT-X-STREAM-INF:BANDWIDTH=#{bandwidth},CODECS=#{ string.quote(codecs) }#{resolution}", "#{prefix}#{s}.m3u8" ] end, [], streams ) string.concat( separator="\r\n", [ "#EXTM3U", "#EXT-X-VERSION:#{version}", ...extra_tags, ...streams, "" ] ) end # @docof output.file.hls def replaces output.file.hls( %argsof(output.file.hls[!main_playlist_writer]), ~main_playlist_writer=null( fun (~extra_tags, ~prefix, ~version, streams) -> null( hls.playlist.main( extra_tags=extra_tags, prefix=prefix, version=version, streams ) ) ), dir, streams, s ) = output.file.hls( %argsof(output.file.hls[!main_playlist_writer]), main_playlist_writer=main_playlist_writer, dir, streams, s ) end let input.hls = () # Play an HLS stream. # @category Source / Input # @param ~id Force the value of the source ID. # @param ~reload How often (in seconds) the playlist should be reloaded. # @param uri Playlist URI. # @flag experimental def input.hls.native(~id=null, ~reload=10., uri) = playlistr = ref([]) sequence = ref(0) playlist_uri = ref(uri) id = string.id.default(default="input.hls.native", id) def load_playlist() = pl = request.create(playlist_uri()) if request.resolve(pl) then pl = request.filename(pl) m = r/#EXT-X-MEDIA-SEQUENCE:(\d+)/.exec(file.contents(pl)) pl_sequence = m[1] log.info( label=id, "Sequence: " ^ pl_sequence ) pl_sequence = int_of_string(default=0, pl_sequence) files = playlist.parse(path=path.dirname(playlist_uri()) ^ "/", pl) def file_request(idx, el) = let (meta, file) = el def escape(s) = string.escape(encoding="ascii", s) end s = list.fold( fun (cur, el) -> "#{cur},#{fst(el)}=#{escape(snd(el))}", "", meta ) s = if s == "" then file else "annotate:#{s}:#{file}" end (pl_sequence + idx, s) end files = list.mapi(file_request, files) let (first_idx, _) = list.hd(default=(-1, ""), playlistr()) def add_file(playlist, file) = let (idx, _) = file if first_idx < idx and not list.assoc.mem(idx, playlist) then list.append(playlist, [file]) else playlist end end playlistr := list.fold(add_file, playlistr(), files) else log.severe( label=id, "Couldn't read playlist: request resolution failed." ) playlistr := [] end request.destroy(pl) end def rec next() = if list.length(playlistr()) > 0 then let (_, ret) = list.hd(default=(1, ""), playlistr()) playlistr := list.tl(playlistr()) sequence := sequence() + 1 request.create(ret) else null end end def find_stream() = pl = request.create(playlist_uri()) if request.resolve(pl) then plfile = request.filename(pl) m = r/#EXT-X-STREAM-INF[^\n]*\n([^\r\n]*)\r?\n/.exec(file.contents(plfile)) playlist_uri := list.assoc(default=playlist_uri(), 1, m) if not (string.contains(substring="/", playlist_uri())) then playlist_uri := path.dirname(request.uri(pl)) ^ "/" ^ playlist_uri() end log( label=id, "Playlist: " ^ playlist_uri() ) end end find_stream() s = request.dynamic(id=id, prefetch=10, next) let {track_marks = _, ...tracks} = source.tracks(s) s = source(tracks) thread.run(every=reload, load_playlist) s end let replaces input.hls = input.hls.native %ifdef input.ffmpeg let replaces input.hls = input.ffmpeg %endif # Play an HLS stream. # @category Source / Input # @param ~id Force the value of the source ID. # @param uri Playlist URI. def input.hls(~id=null, uri) = input.hls(id=id, uri) end let output.harbor.hls = () # @flag hidden def output.harbor.hls.base( %argsof(output.file.hls[!segment_name]), ~segment_name, ~tmpdir, ~port, ~path, serve, formats, s ) = tmpdir = tmpdir ?? file.temp_dir("hls", "") def content_type(fname) = ext = file.extension(fname) if ext == ".m3u8" then "application/x-mpegURL" else def f(cur, el) = format = snd(el) if ext == ".#{encoder.extension(format)}" then encoder.content_type(format) else cur end end list.fold(f, "", formats) end end serve(port=port, path=path, content_type=content_type, tmpdir) output.file.hls(%argsof(output.file.hls), tmpdir, formats, s) end # Output the source stream to an HTTP live stream served from the harbor HTTP server. # @category Source / Output # @argsof output.file.hls # @param ~headers Default response headers. # @param ~port Port for incoming harbor (http) connections. # @param ~path Base path for hls URIs. # @param ~transport Http transport. Use `http.transport.ssl` or `http.transport.secure_transport`, when available, to enable HTTPS output # @param ~tmpdir Directory for generated files. # @param formats List of specifications for each stream: (name, format). def replaces output.harbor.hls( %argsof(output.file.hls[!segment_name]), ~segment_name=( fun (metadata) -> "#{metadata.stream_name}_#{metadata.position}.#{metadata.extname}" ), ~headers=[("Access-Control-Allow-Origin", "*")], ~port=8000, ~path="/", ~tmpdir=null, ~transport=http.transport.unix, formats, s ) = def serve(~port, ~path, ~content_type, dir) = harbor.http.static( port=port, path=path, content_type=content_type, headers=headers, transport=transport, dir ) end output.harbor.hls.base( %argsof(output.file.hls), path=path, port=port, tmpdir=tmpdir, serve, formats, s ) end %ifdef harbor.https.static # Output the source stream to an HTTP live stream served from the harbor HTTPS server. # @category Source / Output # @param ~headers Default response headers. # @param ~port Port for incoming harbor (http) connections. # @param ~path Base path for hls URIs. # @param ~tmpdir Directory for generated files. # @param formats List of specifications for each stream: (name, format). def output.harbor.hls.https( %argsof(output.file.hls[!segment_name]), ~segment_name=( fun (metadata) -> "#{metadata.stream_name}_#{metadata.position}.#{metadata.extname}" ), ~headers=[("Access-Control-Allow-Origin", "*")], ~port=8000, ~path="/", ~tmpdir=null, formats, s ) = def serve(~port, ~path, ~content_type, dir) = harbor.https.static( port=port, path=path, content_type=content_type, headers=headers, dir ) end output.harbor.hls.base( %argsof(output.file.hls), path=path, port=port, tmpdir=tmpdir, serve, formats, s ) end %endif liquidsoap-2.4.2/src/libs/http.liq000066400000000000000000000611301513273233300171020ustar00rootroot00000000000000# Set of HTTP utils. # Prepare a list of `(string, string)` arguments for # sending as `"application/x-www-form-urlencoded"` content # @category Internet def http.www_form_urlencoded(params) = params = list.map( fun (v) -> begin let (key, value) = v "#{url.encode(key)}=#{url.encode(value)}" end, params ) string.concat(separator="&", params) end # Prepare a list of data to be sent as multipart form data. # @category Internet # @param ~boundary Specify boundary to use for multipart/form-data. # @param data data to insert def http.multipart_form_data(~boundary=null, data) = def default_boundary() = range = [...string.char.ascii.alphabet, ...string.char.ascii.number] l = list.init(12, fun (_) -> string.char.ascii.random(range)) string.concat(l) end boundary = null.default(boundary, default_boundary) def mk_content(contents, entry) = data = entry.contents attributes = [("name", entry.name), ...entry.attributes] attributes = list.map( fun (v) -> "#{string(fst(v))}=#{string.quote(snd(v))}", attributes ) attributes = string.concat( separator="; ", attributes ) headers = list.map( fun (v) -> "#{string(fst(v))}: #{string(snd(v))}", entry.headers ) headers = string.concat(separator="\r\n", headers) headers = headers == "" ? "" : "#{headers}\r\n" # This is for typing purposes (entry : unit) [ ...contents, getter("--#{boundary}\r\n"), getter( "Content-Disposition: form-data; #{attributes}\r\n" ), getter(headers), getter("\r\n"), data, getter("\r\n") ] end contents = [...list.fold(mk_content, [], data), getter("--#{boundary}--\r\n")] contents = string.getter.concat(contents) contents = if list.for_all(fun (entry) -> getter.is_constant(entry.contents), data) then getter(string.getter.flush(contents)) else contents end {contents=contents, boundary=boundary} end # Initiate a response handler with pre-filled values. # @category Internet # @method content_type Set `"Content-Type"` header # @method data Set response data. # @method headers Replace response headers. # @method header Set a single header on the response # @method json Set content-type to json and data to `json.stringify` of the argument # @method redirect Set `status_code` and `Location:` header for a HTTP redirect response # @method html Set content-type to html and data to argument value # @method http_version Set http protocol version # @method status_code Set response status code # @method status_message Set response status message def http.response( ~http_version="1.1", ~status_code=null, ~status_message=null, ~headers=[], ~content_type=null, ~data=getter("") ) = status_code = status_code ?? if http_version == "1.1" and headers["expect"] == "100-continue" and getter.get(data) == "" then 100 else 200 end http_version = ref(http_version) status_code = ref(status_code) status_message = ref(status_message) headers = ref(headers) content_type = ref(content_type) data = ref(data) status_sent = ref(false) headers_sent = ref(false) data_sent = ref(false) response_ended = ref(false) def mk_status() = status_sent := true http_version = http_version() status_code = status_code() status_code = if status_code == 100 and getter.get(data()) != "" then 200 else status_code end status_message = status_message() ?? http.codes[status_code] "HTTP/#{http_version} #{status_code} #{status_message}\r\n" end def mk_headers() = headers_sent := true headers = headers() content_type = content_type() data = data() headers = if getter.is_constant(data) then data = getter.get(data) len = string.bytes.length(data) if data != "" then ("Content-Length", "#{len}")::headers else headers end else ("Transfer-Encoding", "chunked")::headers end headers = if null.defined(content_type) and null.get(content_type) != "" then ("Content-type", null.get(content_type))::headers else headers end headers = list.map( fun (v) -> "#{fst(v)}: #{snd(v)}", headers ) headers = string.concat(separator="\r\n", headers) headers = if headers != "" then "#{headers}\r\n" else "" end "#{headers}\r\n" end def mk_data() = data_sent := true data = data() if getter.is_constant(data) then response_ended := true getter.get(data) else data = getter.get(data) len = string.bytes.length(data) response_ended := data == "" "#{string.hex_of_int(len)}\r\n#{data}\r\n" end end def response() = if response_ended() then "" elsif not status_sent() then mk_status() elsif not headers_sent() then mk_headers() else mk_data() end end def attr_method(sent, attr) = def set(v) = if sent() then error.raise( error.invalid, "HTTP response has already been sent for this value!" ) end attr := v end def get() = attr() end set.{current=get} end def header(k, v) = headers := (k, v)::headers() end code = status_code def redirect(~status_code=301, location) = if status_sent() then error.raise( error.invalid, "HTTP response has already been sent for this value!" ) end code := status_code header("Location", location) end def json(~compact=true, v) = if headers_sent() then error.raise( error.invalid, "HTTP response has already been sent for this value!" ) end content_type := "application/json; charset=utf-8" data := json.stringify(v, compact=compact) ^ "\n" end def html(d) = if headers_sent() then error.raise( error.invalid, "HTTP response has already been sent for this value!" ) end content_type := "text/html" data := d end def send_status(socket) = if not status_sent() then socket.write(mk_status()) end end def multipart_form(~boundary=null, contents) = if headers_sent() then error.raise( error.invalid, "HTTP response has already been sent for this value!" ) end form_data = http.multipart_form_data(boundary=boundary, contents) content_type := "multipart/form-data; boundary=#{form_data.boundary}" data := form_data.contents end response.{ http_version=attr_method(status_sent, http_version), status_code=attr_method(status_sent, status_code), status_message=attr_method(status_sent, status_message), headers=attr_method(headers_sent, headers), header=header, redirect=redirect, json=json, html=html, content_type=attr_method(headers_sent, content_type), multipart_form=multipart_form, data=attr_method(data_sent, data), send_status=send_status, status_sent={status_sent()} } end # @flag hidden def harbor.http.regexp_of_path(path) = def named_capture(s) = name = string.sub( encoding="ascii", s, start=1, length=string.bytes.length(s) - 1 ) "(?<#{name}>[^/]+)" end rex = r/:[\w_]+/g.replace(named_capture, path) regexp("^#{rex}$") end # @flag hidden def harbor.http.mk_body(get_data) = done = ref(false) data = ref("") def body(~timeout=10.) = if done() then data() else start_time = time() def rec read() = if done() then data() else if start_time + timeout < time() then error.raise(error.http, "Timeout!") end r = get_data(timeout=timeout) if r == "" then data() else data := "#{data()}#{r}" read() end end end read() end end body end # Register a HTTP handler on the harbor. This function offers a simple API, # suitable for quick implementation of HTTP handlers. See `harbor.http.register` # for a node/express like alternative API. # @category Internet # @argsof harbor.http.register def harbor.http.register.simple(%argsof(harbor.http.register), path, handler) = def handler(request) = def data(~timeout=10.) = request.data(timeout=timeout) end handler(request.{data=data, body=harbor.http.mk_body(data)}) end harbor.http.register( %argsof(harbor.http.register), harbor.http.regexp_of_path(path), handler ) end # Register a HTTP handler on the harbor with a generic regexp `path`. This function offers a simple API, # suitable for quick implementation of HTTP handlers. See `harbor.http.register` # for a node/express like alternative API. # @category Internet # @argsof harbor.http.register def harbor.http.register.simple.regexp( %argsof(harbor.http.register), path, handler ) = harbor.http.register(%argsof(harbor.http.register), path, handler) end # @flag hidden let harbor.http.middleware = ref(fun (req, res, next) -> next(req, res)) # Register a new harbor middleware # @category Internet def harbor.http.middleware.register(fn) = middleware = harbor.http.middleware() harbor.http.middleware := fun (req, res, next) -> begin middleware(req, res, fun (res, res) -> fn(req, res, next)) end end # @flag hidden def harbor.http.register.regexp(%argsof(harbor.http.register), path, handler) = def handler(request) = response = http.response(http_version=request.http_version) is_response_done = ref(false) def replaces response() = ret = response() is_response_done := ret == "" ret end def data(~timeout=10.) = if is_response_done() then error.raise( error.http, "Response ended!" ) end if response.status_code.current() == 100 and not response.status_sent() then response.send_status(request.socket) end request.data(timeout=timeout) end request = { body=harbor.http.mk_body(data), data=data, headers=request.headers, http_version=request.http_version, method=request.method, path=request.path, query=request.query } handler = fun (req, res) -> begin middleware = harbor.http.middleware() middleware(req, res, fun (req, res) -> handler(req, res)) end (handler(request, response) : unit) response end harbor.http.register(%argsof(harbor.http.register), path, handler) end def replaces harbor.http.middleware = () end # Register a HTTP handler on the harbor. The handler function # receives as argument the full requested information and returns the # answer sent to the client, including HTTP headers. This function # registers exact path matches, i.e. `"/users"`, `"/index.hml"` # as well as fragment matches, i.e. `"/user/:id"`, `"/users/:id/collabs/:cid"`, # etc. If you need more advanced matching, use `harbor.http.register.regexp` # to match regular expressions. Paths are resolved in the order they are declared # and can override default harbor paths such as metadata handlers. # The handler receives the request details as a record and a response # handler. Matched fragments are reported as part of the response `query` parameter. # The response handler can be used to fill up details about the http response, # which will be converted into a plain HTTP response string after the handler returns. # @category Internet # @argsof harbor.http.register def replaces harbor.http.register( %argsof(harbor.http.register), path, handler ) = harbor.http.register.regexp( %argsof(harbor.http.register), harbor.http.regexp_of_path(path), handler ) end let harbor.http.static = () # @flag hidden def harbor.http.static.base( serve, ~content_type, ~basepath, ~headers, ~browse, directory ) = directory = path.home.unrelate(directory) basepath = if r/^\//.test(basepath) then basepath else "/#{basepath}" end basepath = if r/\/$/.test(basepath) then basepath else "#{basepath}/" end def handler(request, response) = response.headers(headers) rpath = string.residual(prefix=basepath, request.path) if not null.defined(rpath) then response.status_code(404) else rpath = url.decode(null.get(rpath)) fname = path.concat(directory, rpath) log.debug( "Serving static file: #{fname}" ) if not file.exists(fname) then response.status_code(404) else if file.is_directory(fname) then if not browse then response.status_code(403) else page = ref("") base_url = if r/\/$/.test(request.path) then request.path else request.path ^ "/" end def add(s) = page := page() ^ s ^ "\n" end def add_file(f) = add( "
  • #{f}
  • " ) end add("
      ") list.iter(add_file, file.ls(sorted=true, fname)) add("
    ") response.content_type( "text/html; charset=UTF-8" ) response.data(string.getter.single(page())) end else mime = content_type(fname) if null.defined(mime) then response.content_type(null.get(mime)) end if request.method == "GET" then response.data(file.read(fname)) end end end end end basepath = "#{basepath}.*" def register(method) = serve(method=method, basepath, handler) end list.iter(register, ["OPTIONS", "HEAD", "GET"]) end # It seems that browsers want a trailing 0 for floats. # @flag hidden def http.string_of_float(x) = s = string(x) if r/\.$/.test(s) then "#{s}0" else s end end # @flag hidden def get_mime_process(file) = mime = list.hd( default="", process.read.lines( "file -b -I #{process.quote(file)}" ) ) if mime == "" then null else mime end end # @flag hidden content_type = get_mime_process %ifdef file.mime # @flag hidden content_type = file.mime %endif # Serve a static path. # @category Internet # @param ~port Port for incoming harbor (http) connections. # @param ~transport Http transport. Use `http.transport.ssl` or http.transport.secure_transport`, when available, to enable HTTPS output # @param ~path Base path. # @param ~headers Default response headers. # @param ~browse List files in directories. # @param ~content_type Callback to specify Content-Type on a per file basis. Default: file.mime if compiled or file CLI if present. # @param directory Local path to be served. def replaces harbor.http.static( ~transport=http.transport.unix, ~port=8000, ~path="/", ~browse=false, ~content_type=(content_type : (string)->string?), ~headers=[("Access-Control-Allow-Origin", "*")], directory ) = # Make the method argument non-optional, see #1018 serve = fun (~method, uri, handler) -> harbor.http.register( transport=transport, port=port, method=method, uri, handler ) harbor.http.static.base( serve, content_type=content_type, basepath=path, browse=browse, headers=headers, directory ) end # @flag hidden stdlib_file = file # @flag hidden upload_file_fn = fun ( ~name, ~content_type, ~headers, ~boundary, ~filename, ~file, ~contents, ~timeout, ~redirect, url, fn ) -> begin if not null.defined(filename) and not null.defined(file) then error.raise( error.http, "At least one of: `file` or `filename` must be defined!" ) end if null.defined(file) and null.defined(contents) then error.raise( error.http, "Only one of: `contents` or `file` must be defined!" ) end # Massage parameters filename = null.defined(filename) ? null.get(filename) : string(path.basename(null.get(file))) contents = null.defined(contents) ? null.get(contents) : getter(stdlib_file.read(null.get(file))) # Create query content_type = content_type ?? "application/octet-stream" data = http.multipart_form_data( boundary=boundary, [ { name=name, attributes=[("filename", filename)], headers=[("Content-Type", content_type)], contents=contents } ] ) headers = ( "Content-Type", "multipart/form-data; boundary=#{data.boundary}" )::headers fn( headers=headers, timeout=timeout, redirect=redirect, data=data.contents, url ) end # Send a file via POST request encoded in multipart/form-data. The contents can # either be directly specified (with the `contents` argument) or taken from a # file (with the `file` argument). # @category Internet # @param ~name Name of the field field # @param ~content_type Content-type (mime) for the file. # @param ~headers Additional headers. # @param ~boundary Specify boundary to use for multipart/form-data. # @param ~filename File name sent in the request. # @param ~file File whose contents is to be sent in the request. # @param ~contents Contents of the file sent in the request. # @param ~timeout Timeout in seconds. # @param ~redirect Follow reidrections. # @param url URL to post to. def http.post.file( ~name="file", ~content_type=null, ~headers=[], ~boundary=null, ~filename=null, ~file=null, ~contents=null, ~timeout=null, ~redirect=true, url ) = upload_file_fn( name=name, content_type=content_type, headers=headers, boundary=boundary, filename=filename, file=file, contents=contents, timeout=timeout, redirect=redirect, url, http.post ) end # Send a file via PUT request encoded in multipart/form-data. The contents can # either be directly specified (with the `contents` argument) or taken from a # file (with the `file` argument). # @category Internet # @param ~name Name of the field field # @param ~content_type Content-type (mime) for the file. # @param ~headers Additional headers. # @param ~boundary Specify boundary to use for multipart/form-data. # @param ~filename File name sent in the request. # @param ~file File whose contents is to be sent in the request. # @param ~contents Contents of the file sent in the request. # @param ~timeout Timeout in seconds. # @param ~redirect Follow reidrections. # @param url URL to put to. def http.put.file( ~name="file", ~content_type=null, ~headers=[], ~boundary=null, ~filename=null, ~file=null, ~contents=null, ~timeout=null, ~redirect=true, url ) = upload_file_fn( name=name, content_type=content_type, headers=headers, boundary=boundary, filename=filename, file=file, contents=contents, timeout=timeout, redirect=redirect, url, http.put ) end let harbor.http.request = () let settings.http.mime = settings.make.void( "MIME-related settings for HTTP requests" ) let settings.http.mime.extnames = settings.make( description="MIME to file extension mappings", [ ("application/mp4", ".mp4"), ("application/ogg", ".ogg"), ("application/pdf", ".pdf"), ("application/rss+xml", ".rss"), ("application/smil", ".smil"), ("application/smil+xml", ".smil"), ("application/x-cue", ".cue"), ("application/x-ogg", ".ogg"), ("application/xspf+xml", ".xspf"), ("audio/flac", ".flac"), ("audio/mp3", ".mp3"), ("audio/mp4", ".mp4"), ("audio/mpeg", ".mp3"), ("audio/mpegurl", ".m3u"), ("audio/ogg", ".ogg"), ("audio/vnd.wave", ".wav"), ("audio/wav", ".wav"), ("audio/wave", ".wav"), ("audio/x-flac", ".flac"), ("audio/x-mpegurl", ".m3u"), ("audio/x-ogg", ".ogg"), ("audio/x-scpls", ".pls"), ("audio/x-wav", ".wav"), ("image/bmp", ".bmp"), ("image/jpeg", ".jpg"), ("image/png", ".png"), ("text/plain", ".txt"), ("video/mp4", ".mp4"), ("video/ogg", ".ogg"), ("video/x-ms-asf", ".asf") ] ) %ifndef file.mime let file.mime = () %endif # Return the file extension associated with the given # content-type if it is known. # @category File def file.mime.extension(content_type) = extnames = settings.http.mime.extnames() extname = extnames[content_type] extname == "" ? null : extname end let http.headers = () # Extract the content-type header # @category Internet def http.headers.content_type(headers) = mime = try list.find( fun (v) -> begin let (header_name, _) = v string.case(lower=true, header_name) == "content-type" end, headers ) catch _ : [error.not_found] do null end mime = null.map(snd, mime) null.map( fun (mime) -> begin let [mime, ...args] = list.map(string.trim, string.split(separator=";", mime)) def parse_arg(arg) = let [name, ...value] = string.split(separator="=", arg) (name, string.unquote(string.concat(separator="=", value))) end {mime=mime, args=list.map(parse_arg, args)} end, mime ) end # Extract the content-disposition header # @category Internet def http.headers.content_disposition(headers) = content_disposition = try list.find( fun (v) -> begin let (header_name, _) = v string.case(lower=true, header_name) == "content-disposition" end, headers ) catch _ : [error.not_found] do null end def parse_arg(arg) = let [name, ...value] = string.split(separator="=", arg) (name, string.unquote(string.concat(separator="=", value))) end def parse_filename(args) = plain_filename = args["filename"] plain_filename = plain_filename == "" ? null : plain_filename encoded_filename = args["filename*"] encoded_filename = encoded_filename == "" ? null : encoded_filename encoded_filename = null.map( fun (encoded_filename) -> begin let [encoding, _, filename] = string.split(separator="'", encoded_filename) string.recode(in_enc=encoding, filename) end, encoded_filename ) filename = null.defined(encoded_filename) ? encoded_filename : plain_filename filename = null.map(fun (filename) -> url.decode(string.unquote(filename)), filename) ( filename, list.filter( fun (v) -> fst(v) != "filename" and fst(v) != "filename*", args ) ) end def parse_name(args) = name = args["name"] name = name == "" ? null : name name = null.map(fun (name) -> url.decode(string.unquote(name)), name) (name, list.filter(fun (v) -> fst(v) != "name", args)) end null.map( fun (v) -> begin let (_, header_value) = v let [type, ...args] = list.map(string.trim, string.split(separator=";", header_value)) args = list.map(parse_arg, args) let (filename, args) = parse_filename(args) let (name, args) = parse_name(args) ({type=type, filename=filename, name=name, args=args} : { type: string, filename?: string, name?: string, args: [(string * string?)] } ) end, content_disposition ) end # Try to get a filename from a request's headers. # @category Internet def http.headers.extname(headers) = content_disposition = http.headers.content_disposition(headers) content_type = http.headers.content_type(headers) extname = if null.defined(content_disposition?.filename) then extname = file.extension(null.get(content_disposition?.filename)) extname == "" ? null : extname else null end if null.defined(extname) then extname elsif null.defined(content_type) then file.mime.extension(null.get(content_type).mime) else null end end liquidsoap-2.4.2/src/libs/http_codes.liq000066400000000000000000000354751513273233300202740ustar00rootroot00000000000000# List of HTTP codes. Stolen from en.wikipedia.org. # List of HTTP response codes and statuses. # @category Interaction let http.codes = [ (100, "Continue"), #This means that the server has received the request headers, and that the client #should proceed to send the request body (in the case of a request for which a #body needs to be sent; for example, a POST request). If the request body is #large, sending it to a server when a request has already been rejected based #upon inappropriate headers is inefficient. To have a server check if the request #could be accepted based on the request's headers alone, a client must send #Expect: 100-continue as a header in its initial request and check if a 100 #Continue status code is received in response before continuing (or receive 417 #Expectation Failed and not continue). ( 101, "Switching Protocols" ), #This means the requester has asked the server to switch protocols and the server #is acknowledging that it will do so. (102, "Processing"), #As a WebDAV request may contain many sub-requests involving file operations, it #may take a long time to complete the request. This code indicates that the #server has received and is processing the request, but no response is available #yet. This prevents the client from timing out and assuming the request was #lost. ( 122, "Request-URI too long" ), #This is a non-standard IE7-only code which means the URI is longer than a #maximum of 2083 characters. (See code 414.), #2xx Success #This class of status codes indicates the action requested by the client was #received, understood, accepted and processed successfully. (200, "OK"), #Standard response for successful HTTP requests. The actual response will depend #on the request method used. In a GET request, the response will contain an #entity corresponding to the requested resource. In a POST request the response #will contain an entity describing or containing the result of the action. (201, "Created"), #The request has been fulfilled and resulted in a new resource being created. (202, "Accepted"), #The request has been accepted for processing, but the processing has not been #completed. The request might or might not eventually be acted upon, as it might #be disallowed when processing actually takes place. ( 203, "Non-Authoritative Information" ), #The server successfully processed the request, but is returning information that #may be from another source. ( 204, "No Content" ), #The server successfully processed the request, but is not returning any #content. ( 205, "Reset Content" ), #The server successfully processed the request, but is not returning any content. #Unlike a 204 response, this response requires that the requester reset the #document view. ( 206, "Partial Content" ), #The server is delivering only part of the resource due to a range header sent by #the client. The range header is used by tools like wget to enable resuming of #interrupted downloads, or split a download into multiple simultaneous #streams. (207, "Multi-Status"), #The message body that follows is an XML message and can contain a number of #separate response codes, depending on how many sub-requests were made. ( 226, "IM Used" ), #The server has fulfilled a GET request for the resource, and the response is a #representation of the result of one or more instance-manipulations applied to #the current instance. #3xx Redirection #The client must take additional action to complete the request. #This class of status code indicates that further action needs to be taken by the #user agent in order to fulfil the request. The action required may be carried #out by the user agent without interaction with the user if and only if the #method used in the second request is GET or HEAD. A user agent should not #automatically redirect a request more than five times, since such redirections #usually indicate an infinite loop. ( 300, "Multiple Choices" ), #Indicates multiple options for the resource that the client may follow. It, for #instance, could be used to present different format options for video, list #files with different extensions, or word sense disambiguation. ( 301, "Moved Permanently" ), #This and all future requests should be directed to the given URI. (302, "Found"), #This is an example of industrial practice contradicting the standard. #HTTP/1.0 specification (RFC 1945) required the client to perform a temporary #redirect (the original describing phrase was "Moved Temporarily"), but #popular browsers implemented 302 with the functionality of a 303 See Other. #Therefore, HTTP/1.1 added status codes 303 and 307 to distinguish between the #two behaviours. However, the majority of Web applications and frameworks #still[as of?] use the 302 status code as if it were the 303.[citation needed] ( 303, "See Other" ), #The response to the request can be found under another URI using a GET method. #When received in response to a POST (or PUT/DELETE), it should be assumed that #the server has received the data and the redirect should be issued with a #separate GET message. ( 304, "Not Modified" ), #Indicates the resource has not been modified since last requested. Typically, #the HTTP client provides a header like the If-Modified-Since header to provide a #time against which to compare. Using this saves bandwidth and reprocessing on #both the server and client, as only the header data must be sent and received in #comparison to the entirety of the page being re-processed by the server, then #sent again using more bandwidth of the server and client. ( 305, "Use Proxy" ), #Many HTTP clients (such as Mozilla and Internet Explorer) do not correctly #handle responses with this status code, primarily for security reasons. ( 306, "Switch Proxy" ), #No longer used. ( 307, "Temporary Redirect" ), #In this occasion, the request should be repeated with another URI, but future #requests can still use the original URI. In contrast to 303, the request #method should not be changed when reissuing the original request. For instance, #a POST request must be repeated using another POST request. #4xx Client Error #The 4xx class of status code is intended for cases in which the client seems to #have erred. Except when responding to a HEAD request, the server should include #an entity containing an explanation of the error situation, and whether it is a #temporary or permanent condition. These status codes are applicable to any #request method. User agents should display any included entity to the user. #These are typically the most common error codes encountered while online. ( 400, "Bad Request" ), #The request cannot be fulfilled due to bad syntax. (401, "Unauthorized"), #Similar to 403 Forbidden, but specifically for use when authentication is #possible but has failed or not yet been provided. The response must include a #WWW-Authenticate header field containing a challenge applicable to the requested #resource. See Basic access authentication and Digest access authentication. ( 402, "Payment Required" ), #Reserved for future use. The original intention was that this code might be #used as part of some form of digital cash or micropayment scheme, but that has #not happened, and this code is not usually used. As an example of its use, #however, Apple's MobileMe service generates a 402 error (httpStatusCode:402" in #the Mac OS X Console log) if the MobileMe account is delinquent. (403, "Forbidden"), #The request was a legal request, but the server is refusing to respond to it. #Unlike a 401 Unauthorized response, authenticating will make no difference. ( 404, "Not Found" ), #The requested resource could not be found but may be available again in the #future. Subsequent requests by the client are permissible. ( 405, "Method Not Allowed" ), #A request was made of a resource using a request method not supported by that #resource; for example, using GET on a form which requires data to be #presented via POST, or using PUT on a read-only resource. ( 406, "Not Acceptable" ), #The requested resource is only capable of generating content not acceptable #according to the Accept headers sent in the request. ( 407, "Proxy Authentication Required" ), ( 408, "Request Timeout" ), #The server timed out waiting for the request. According to W3 HTTP #specifications: "The client did not produce a request within the time that the #server was prepared to wait. The client MAY repeat the request without #modifications at any later time." (409, "Conflict"), #Indicates that the request could not be processed because of conflict in the #request, such as an edit conflict. (410, "Gone"), #Indicates that the resource requested is no longer available and will not be #available again. This should be used when a resource has been intentionally #removed and the resource should be purged. Upon receiving a 410 status code, the #client should not request the resource again in the future. Clients such as #search engines should remove the resource from their indices. Most use cases do #not require clients and search engines to purge the resource, and a "404 Not #Found" may be used instead. ( 411, "Length Required" ), #The request did not specify the length of its content, which is required by the #requested resource. ( 412, "Precondition Failed" ), #The server does not meet one of the preconditions that the requester put on the #request. ( 413, "Request Entity Too Large" ), #The request is larger than the server is willing or able to process. ( 414, "Request-URI Too Long" ), #The URI provided was too long for the server to process. ( 415, "Unsupported Media Type" ), #The request entity has a media type which the server or resource does not #support. For example, the client uploads an image as image/svg+xml, but the #server requires that images use a different format. ( 416, "Requested Range Not Satisfiable" ), #The client has asked for a portion of the file, but the server cannot supply #that portion. For example, if the client asked for a part of the file that #lies beyond the end of the file. ( 417, "Expectation Failed" ), #The server cannot meet the requirements of the Expect request-header field. ( 418, "I'm a teapot" ), #This code was defined in 1998 as one of the traditional IETF April Fools' jokes, #in RFC 2324, Hyper Text Coffee Pot Control Protocol, and is not expected to be #implemented by actual HTTP servers. ( 422, "Unprocessable Entity" ), #The request was well-formed but was unable to be followed due to semantic #errors. (423, "Locked"), #The resource that is being accessed is locked. ( 424, "Failed Dependency" ), #The request failed due to failure of a previous request (e.g. a PROPPATCH). ( 425, "Unordered Collection" ), #Defined in drafts of "WebDAV Advanced Collections Protocol", but not present #in "Web Distributed Authoring and Versioning (WebDAV) Ordered Collections #Protocol". ( 426, "Upgrade Required" ), #The client should switch to a different protocol such as TLS/1.0. ( 444, "No Response" ), #A Nginx HTTP server extension. The server returns no information to the client #and closes the connection (useful as a deterrent for malware). ( 449, "Retry With" ), #A Microsoft extension. The request should be retried after performing the #appropriate action. ( 450, "Blocked by Windows Parental Controls" ), #A Microsoft extension. This error is given when Windows Parental Controls are #turned on and are blocking access to the given webpage. ( 499, "Client Closed Request" ), #An Nginx HTTP server extension. This code is introduced to log the case when the #connection is closed by client while HTTP server is processing its request, #making server unable to send the HTTP header back. #5xx Server Error #The server failed to fulfill an apparently valid request. #Response status codes beginning with the digit "5" indicate cases in which the #server is aware that it has encountered an error or is otherwise incapable of #performing the request. Except when responding to a HEAD request, the server #should include an entity containing an explanation of the error situation, and #indicate whether it is a temporary or permanent condition. Likewise, user agents #should display any included entity to the user. These response codes are #applicable to any request method. ( 500, "Internal Server Error" ), #A generic error message, given when no more specific message is suitable. ( 501, "Not Implemented" ), #The server either does not recognise the request method, or it lacks the ability #to fulfill the request. ( 502, "Bad Gateway" ), #The server was acting as a gateway or proxy and received an invalid response #from the upstream server. ( 503, "Service Unavailable" ), #The server is currently unavailable (because it is overloaded or down for #maintenance). Generally, this is a temporary state. ( 504, "Gateway Timeout" ), #The server was acting as a gateway or proxy and did not receive a timely #response from the upstream server. ( 505, "HTTP Version Not Supported" ), #The server does not support the HTTP protocol version used in the request. ( 506, "Variant Also Negotiates" ), #Transparent content negotiation for the request results in a circular #reference. ( 507, "Insufficient Storage" ), ( 509, "Bandwidth Limit Exceeded" ), #This status code, while used by many servers, is not specified in any RFCs. ( 510, "Not Extended" ) #Further extensions to the request are required for the server to fulfill it. ] liquidsoap-2.4.2/src/libs/icecast.liq000066400000000000000000000026111513273233300175350ustar00rootroot00000000000000%ifdef output.icecast # Encode and output the stream to a shoutcast server. # @category Source / Output # @argsof output.icecast[!method,!mount,!description,!protocol] # @param ~icy_reset Reset shoutcast source buffer upon connecting (necessary for NSV). # @param ~dj Set DJ name. # @param e Encoding format. Should be mp3 or AAC(+). # @param s The source to output def output.shoutcast( %argsof(output.icecast[!method,!mount,!description,!protocol]), ~icy_reset=true, ~dj=getter(""), ~aim="", ~icq="", ~irc="", e, s ) = icy_reset = if icy_reset then "1" else "0" end headers = [ ("icy-aim", aim), ("icy-irc", irc), ("icy-icq", icq), ("icy-reset", icy_reset), ...headers ] def map(m) = dj = getter.get(dj) if dj != "" then ("dj", dj)::m else m end end s = metadata.map(insert_missing=false, map, s) output.icecast( %argsof(output.icecast[!method,!mount,!headers,!description,!protocol]), mount="", headers=headers, protocol="icy", e, s ) end # Encode and output the stream to an icecast server. # @category Source / Output # @argsof output.icecast[!protocol, !icy_id] # @param e Encoding format. # @param s The source to output def output.icecast(%argsof(output.icecast[!protocol,!icy_id]), e, s) = output.icecast( %argsof(output.icecast[!protocol,!icy_id]), protocol="http", e, s ) end %endif liquidsoap-2.4.2/src/libs/io.liq000066400000000000000000000076361513273233300165450ustar00rootroot00000000000000let replaces output = output.dummy %ifdef output.ao let replaces output = output.ao %endif %ifdef output.alsa let replaces output = output.alsa %endif %ifdef output.oss let replaces output = output.oss %endif %ifdef output.portaudio let replaces output = output.portaudio %endif %ifdef output.pulseaudio let replaces output = output.pulseaudio %endif # Output a stream using the default operator. The input source does not need to # be infallible, blank will just be played during failures. # @category Source / Output # @param ~id Force the value of the source ID. # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If `true`, an infallible (normal) output will start as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. # @param s Source to play. def replaces output(~id=null, ~fallible=true, ~start=true, s) = output(id=id, fallible=fallible, start=start, s) end let output.video = output.dummy %ifdef output.sdl def output.video(%argsof(output.sdl), s) = if output.sdl.has_video() then (output.sdl(%argsof(output.sdl), s) : unit) else # Avoid using SDL when there is no video output. (output.dummy(fallible=fallible, s) : unit) end end %endif %ifdef output.graphics let output.video = output.graphics %endif # Output a video stream using the default operator. The input source does not # need to be infallible, blank will just be played during failures. # @category Source / Output # @param ~id Force the value of the source ID. # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If `true`, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. # @param s Source to play. def output.video(~id=null, ~fallible=true, ~start=true, s) = output.video(id=id, fallible=fallible, start=start, s) end # Output a stream with audio and video using the default operator. The input # source does not need to be infallible, blank will just be played during # failures. # @category Source / Output # @param ~id Force the value of the source ID. # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If `true`, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. # @param s Source to play. def output.audio_video(~id=null, ~fallible=true, ~start=true, s) = let {audio, video} = source.tracks(s) output(id=id, fallible=fallible, start=start, source({audio=audio})) output.video(id=id, fallible=fallible, start=start, source({video=video})) end def replaces input(~id=null, ~start=true, ~fallible=false) = ignore(start) ignore(fallible) blank(id=id) end %ifdef input.alsa let replaces input = input.alsa %endif %ifdef input.oss let replaces input = input.oss %endif %ifdef input.portaudio let replaces input = input.portaudio %endif %ifdef input.pulseaudio let replaces input = input.pulseaudio %endif # Input an audio stream using the default operator. # @category Source / Input # @param ~id Force the value of the source ID. # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If `true`, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. def replaces input(~id=null, ~start=true, ~fallible=false) = input(id=id, start=start, fallible=fallible) end liquidsoap-2.4.2/src/libs/liquidsoap.liq000066400000000000000000000013321513273233300202730ustar00rootroot00000000000000let liquidsoap.chroot = () # Export all the files required to install liquidsoap in a root folder. Useful # for packaging and docker images. # @category Liquidsoap def liquidsoap.chroot.make(chroot) = def chroot(p) = path.concat(chroot, p) end def mkdir(p) = process.run( "mkdir -p #{process.quote(p)}" ) end def cp(source) = mkdir(chroot(path.dirname(source))) process.run( "cp -rf #{process.quote(source)} #{process.quote(chroot(source))}" ) end cp(configure.libdir) cp(configure.bindir) if file.exists(settings.default_font()) then cp(settings.default_font()) end mkdir(chroot(configure.logdir)) mkdir(chroot(configure.rundir)) cp(liquidsoap.executable) () end liquidsoap-2.4.2/src/libs/list.liq000066400000000000000000000240321513273233300170760ustar00rootroot00000000000000# Add an element at the top of a list. # @category List def list.cons(x, l) = list.add(x, l) end # "Delayed" version of `list.case` where the value on empty list is only # evaluated if necessary. # @category List def list.dcase(l, d, f) = f = list.case(l, d, fun (x, l) -> {f(x, l)}) f() end # Return the head (first element) of a list, or `default` if the list is empty. # @category List # @param ~default Default value if key does not exist. def list.hd(~default=null, l) = list.dcase( l, { default ?? error.raise( error.not_found, "no default value for list.hd" ) }, fun (x, _) -> x ) end # Return the list without its first element. # @category List def list.tl(l) = list.case(l, [], fun (_, l) -> l) end # Create a list with given length, filled with given element. # @category List # @param n Number of elements in the list. # @param x Element to fill the list with. def list.make(n, x) = list.init(n, fun (_) -> x) end # Determining whether a list is empty or not. # @category List def list.is_empty(l) = list.case(l, true, fun (_, _) -> false) end # Return the last element of a list. # @category List def list.last(~default=null, l) = list.nth(default=default, l, list.length(l) - 1) end # Call a function on every element of a list. # @category List def list.iter(f, l) = list.iteri(fun (_, v) -> f(v), l) end # Check whether an element belongs to a list. # @category List def list.mem(x, l) = err = error.register("mem") try list.iter(fun (v) -> if v == x then error.raise(err, "found") end, l) false catch _ : [err] do true end end # Map a function on every element of a list, starting from the right. This # function is tail-recursive. # @category List def list.map.right(f, l) = list.ind(l, [], fun (x, _, l) -> list.cons(f(x), l)) end # Map a function on every element of a list, along with its index. # @category List def list.mapi(f, l) = n = ref(0) def f(x) = i = n() n := i + 1 f(i, x) end list.map(f, l) end # Add indices to every element of a list, so that it can be accessed with the # notation `l[n]`. # @category List def list.indexed(l) = list.mapi(fun (i, x) -> (i, x), l) end # Fold a function on every element of a list: `list.fold(f,x1,[e1,..,en]) is f(...f(f(x1,e1),e2)...,en)`. # @category List # @param f Function `f` for which `f(x,e)` which will be called on every element `e` with the current value of `x`, returning the new value of `x`. # @param x Initial value x1, to be updated by successive calls of `f(x,e)`. def list.fold(f, x, l) = ret = ref(x) list.iter(fun (v) -> ret := f(ret(), v), l) ret() end # Fold a function on every element of a list. Similar to `list.fold` but # iterates from the right of the list. It is slightly more efficient than # `list.fold`. # @category List # @param f Function `f` for which `f(x,e)` which will be called on every element `e` with the current value of `x`, returning the new value of `x`. # @param x Initial value x1, to be updated by successive calls of `f(x,e)`. def list.fold.right(f, x, l) = list.ind(l, x, fun (e, _, r) -> f(e, r)) end # Concatenate all the elements of a list of lists. # @category List def list.flatten(l) = list.fold(fun (l, s) -> list.append(l, s), [], l) end # Filter a list according to a predicate. The order in which elements are # handled is not specified (and is currently implemented from the right). # @category List # @param ~remove Function called on an element when it is removed. # @param p Predicate indicating whether an element should be kept or not. # @param l List to filter. def list.filter(~remove=fun (_) -> (), p, l) = # list.case(l, [], fun (x, l) -> if p(x) then list.cons(x, list.filter(p, l)) else list.filter(p, l) end) list.ind( l, [], fun (x, _, l) -> if p(x) then list.cons(x, l) else (remove(x) : unit) l end ) end # Map a function on a list (like `list.map`) excepting that the value is removed # if the function returns `null`. # @category List # @param f Function called on every element of the list. # @param l The list. def list.filter_map(f, l) = def f(x, _, l) = y = f(x) if null.defined(y) then list.cons(null.get(y), l) else l end end list.ind(l, [], f) end # Associate a value to a key in an association list. This functions raises # `error.not_found` if no default value is specified. # @category List # @param ~default Value returned if the key is not found. def list.assoc(~default=null, key, l) = ret = ref(💣()) err = error.register("assoc") try list.iter( fun (v) -> if fst(v) == key then ret := snd(v) error.raise(err, "found") end, l ) null.case( default, fun () -> error.raise( error.not_found, "no default value for list.assoc" ), fun (v) -> v ) catch _ : [err] do ret() end end # `list.assoc.mem(key,l)` returns `true` if `l` contains a pair (key,value). # @category List # @param a Key to look for. # @param l List of pairs (key,value). def list.assoc.mem(a, l) = err = error.register("find") try list.iter(fun (el) -> if fst(el) == a then error.raise(err, "found") end, l) false catch _ : [err] do true end end # Associate a value to a key in an association list. This functions is similar # to `list.assoc` excepting that it returns `null` if no value exists for the # key. # @category List def list.assoc.nullable(key, l) = try list.assoc(key, l) catch _ do null end end # Keep only the elements of an association list satisfying a given predicate. # @category List def list.assoc.filter(p, l) = def p(kv) = p(fst(kv), snd(kv)) end list.filter(p, l) end # Map a function of every element of the associative list, removing the entry if # the function returns `null`. # @category List def list.assoc.filter_map(f, l) = def f(kv) = f(fst(kv), snd(kv)) end list.filter_map(f, l) end # Remove the first pair from an associative list. # @category List # @param key Key of pair to be removed. # @param l List of pairs (key,value). def list.assoc.remove(key, l) = ret = ref(💣()) err = error.register("assoc.remove") try list.iter( fun (v) -> if fst(v) == key then ret := snd(v) error.raise(err, "found") end, l ) l catch _ : [err] do list.remove((key, ret()), l) end end # Remove all pairs with given key from an associative list. # @category List # @param key Key of pairs to be removed. # @param l List of pairs (key,value). def list.assoc.remove.all(key, l) = list.assoc.filter(fun (k, _) -> k != key, l) end # Check that a predicate is satisfied for every element in a list. # @category List # @param p Predicate. # @param l List def list.for_all(p, l) = err = error.register("for_all") try list.iter(fun (v) -> if not p(v) then error.raise(err, "found") end, l) true catch _ : [err] do false end end # Check that a predicate is satisfied for some element in a list. # @category List # @param p Predicate. # @param l List def list.exists(p, l) = err = error.register("exists") try list.iter(fun (v) -> if p(v) then error.raise(err, "found") end, l) false catch _ : [err] do true end end # First element satisfying a predicate. Raises `error.not_found` if not element # is found and no default value was specified. # @category List # @param ~default Returned value when the predicate is not found. # @param p Predicate. # @param l List def list.find(~default=null, p, l) = ret = ref(💣()) err = error.register("find") try list.iter( fun (v) -> if p(v) then ret := v error.raise(err, "found") end, l ) null.case( default, fun () -> error.raise( error.not_found, "no default value for list.find" ), fun (v) -> v ) catch _ : [err] do ret() end end # First index where a predicate is satisfied. # @category List # @param p Predicate. # @param l List def list.index(p, l) = list.ind(l, 0, fun (x, _, r) -> if p(x) then 0 else r + 1 end) end # Create an iterator over the elements of a list. # @category List def list.iterator(l) = l = ref(l) def f() = list.case( l(), null, fun (x, t) -> begin l := t x end ) end f end # Returns a copy of the given list with a new element inserted at a given # position. Raises `error.not_found` if the list has less than `index` elements. # @category List # @param index Index to insert at, starting at `0`. # @param new_element Element to insert # @param l List to insert into. def list.insert(index, new_element, l) = if list.length(l) < index then error.raise( error.not_found, "List should have at least #{index} elemments" ) end if index == 0 then new_element::l else def f(cur, el) = let (pos, l) = cur l = if pos + 1 == index then new_element::el::l else el::l end (pos + 1, l) end let (_, l) = list.fold(f, (0, []), l) list.rev(l) end end # Compute the beginning of a list. # @category List # @param n Number of elements in the returned list. # @param l List whose prefix should be taken. def list.prefix(n, l) = len = list.length(l) n = n < len ? n : len list.init(n, fun (n) -> list.nth(l, n)) end # Pick a random element in a list. # @category List # @param ~default Value returned if the list is empty. # @param l List in which the element should be picked. def list.pick(~default=null, l) = if list.is_empty(l) then default ?? error.raise( error.not_found, "empty list in list.pick" ) else list.nth(l, random.int(min=0, max=list.length(l))) end end # Sort a list according to the "natural" order. # @category List # @param l List to sort def list.sort.natural(l) = def compare(x, y) = if x < y then -1 elsif x > y then 1 else 0 end end list.sort(compare, l) end liquidsoap-2.4.2/src/libs/log.liq000066400000000000000000000016021513273233300167020ustar00rootroot00000000000000# Log a critical message # @category Liquidsoap def log.critical(~label="lang", msg) = log(label=label, level=1, msg) end # Log a severe message # @category Liquidsoap def log.severe(~label="lang", msg) = log(label=label, level=2, msg) end # Log an important message # @category Liquidsoap def log.important(~label="lang", msg) = log(label=label, level=3, msg) end # Log a normal message # @category Liquidsoap def log.info(~label="lang", msg) = log(label=label, level=4, msg) end # Log a debug message # @category Liquidsoap def log.debug(~label="lang", msg) = log(label=label, level=5, msg) end # Get and set the log level. # @category Liquidsoap def log.level = settings.log.level end # Get and set the file logging # @category Liquidsoap def log.file = settings.log.file end # Get and set logging to stdout # @category Liquidsoap def log.stdout = settings.log.stdout end liquidsoap-2.4.2/src/libs/lufs.liq000066400000000000000000000102001513273233300170640ustar00rootroot00000000000000let settings.lufs = () let settings.lufs.track_gain_target = settings.make( description="Target LUFS All available autocue implementations", -16. ) let settings.lufs.integrated_metadata = settings.make( description="Metadata used to store integrated LUFS", "liq_integrated_lufs" ) let settings.lufs.decoding_ratio = settings.make( description="Decoding ratio used when decoding integrated LUFS from files", 50. ) let file.lufs = () # Compute the LUFS of a file (in dB). # @category File # @param ~id Force the value of the source ID. # @param ~ratio Decoding ratio. A value of `50` means try to decode the file `50x` faster than real time, if possible. Use this setting to lower CPU peaks when computing lufs tags. Defaults to `settings.lufs.decoding_ratio` when `null` # @param file_name File name. # @flag hidden def file.lufs.compute(~ratio=null, file_name) = ratio = ratio ?? settings.lufs.decoding_ratio() _request = request.create(resolve_metadata=false, file_name) if request.resolve(_request) then get_lufs = ref(fun () -> null) def process(s) = s = lufs(s) get_lufs := {s.lufs_integrated()} s end request.process(ratio=ratio, process=process, _request) fn = get_lufs() fn() else null end end # Extract the LUFS from the metadata (in dB). # @category Metadata # @param _metadata Metadata from which the LUFS should be extracted. def metadata.lufs(_metadata) = k = settings.lufs.integrated_metadata() if list.assoc.mem(k, _metadata) then lufs_metadata = _metadata[k] match = r/([+-]?\d*\.?\d*)/.exec(lufs_metadata) try float_of_string(list.assoc(1, match)) catch _ do null end else null end end # Get the LUFS for a file (in dB). # @category File # @param ~id Force the value of the source ID. # @param ~compute Compute LUFS if metadata tag is empty. # @param ~ratio Decoding ratio. A value of `50` means try to decode the file `50x` faster than real time, if possible. Use this setting to lower CPU peaks when computing lufs tags. Defaults to `settings.lufs.decoding_ratio` when `null`. # @param file_name File name. def replaces file.lufs(~id=null, ~compute=true, ~ratio=null, file_name) = id = string.id.default(default="file.lufs", id) file_name_quoted = string.quote(file_name) ratio = ratio ?? settings.lufs.decoding_ratio() _metadata = file.metadata(exclude=decoder.metadata.reentrant(), file_name) gain = metadata.lufs(_metadata) if gain != null then log.info( label=id, "Detected track lufs #{gain} dB for #{file_name_quoted}." ) gain elsif compute then log.info( label=id, "Computing integrated LUFS for #{file_name_quoted}." ) start_time = time() gain = file.lufs.compute(ratio=ratio, file_name) elapsed_time = time() - start_time if gain != null then log.info( label=id, "Computed integrated LUFS of #{gain} dB for #{file_name_quoted} (time: #{ elapsed_time } s)." ) end gain else null end end # Enable LUFS metadata resolver. This resolver will process any file # decoded by Liquidsoap and add a `lufs_track_gain` metadata when this # value could be computed. For a finer-grained replay gain processing, use the # `lufs_track_gain:` protocol. # @param ~compute Compute lufs if metadata tag is empty. # @param ~ratio Decoding ratio. A value of `50.` means try to decode the file `50x` faster than real time, if possible. Use this setting to lower CPU peaks when computing lufs tags. Defaults to `settings.lufs.decoding_ratio` when `null` # @category Liquidsoap def enable_lufs_track_gain_metadata(~compute=true, ~ratio=null) = ratio = ratio ?? settings.lufs.decoding_ratio() def lufs_metadata(~metadata:_, file_name) = gain = file.lufs(compute=compute, ratio=ratio, file_name) if gain != null then [ ( settings.normalize_track_gain_metadata(), "#{settings.lufs.track_gain_target() - null.get(gain)} dB" ) ] else [] end end decoder.metadata.add(reentrant=true, "lufs_track_gain", lufs_metadata) end liquidsoap-2.4.2/src/libs/math.liq000066400000000000000000000006241513273233300170550ustar00rootroot00000000000000# Compute the minimum of two values. # @category Math def min(a, b) = if a <= b then a else b end end # Compute the maximum of two values. # @category Math def max(a, b) = if a >= b then a else b end end # Convert linear scale into decibels. # @category Math def dB_of_lin(x) = 20. * log10(x) end # Convert decibels into linear scale. # @category Math def lin_of_dB(x) = pow(10., x / 20.) end liquidsoap-2.4.2/src/libs/medialib.liq000066400000000000000000000416711513273233300177010ustar00rootroot00000000000000# A library to store the metadata of files in given folders and query them. This # is useful to generate playlists based on metadata. # @category File # @param ~persistency Store the database in given file, which is reuse to populate the database on next run. # @param ~refresh Scan directories for new files every given number of seconds (by default the database is never updated). # @param ~standardize Function mapped on metadata when indexing. It can be used to change the field names to standard ones, pretreat data, etc. # @param ~initial_progress Show progress of library being indexed at startup. # @param ~directories Directories to look for files in. # @param dir Directory to look for files in. # @method find Find files according to conditions on metadata. # @method refresh Update metadatas and look for new files. # @method add_directory Add a new directory which should be scanned. # @method clear Remove all known metadata. def medialib( ~id=null, ~persistency=null, ~refresh=null, ~standardize=fun (m) -> m, ~initial_progress=true, ~directories=[], dir=null ) = id = string.id.default(default="medialib", id) refresh_time = refresh directories = ref(directories) if null.defined(dir) then directories := null.get(dir)::directories() end db = ref([]) def dt(t) = string.float(decimal_places=2, time() - t) end # Read metadata from file. def metadata(f) = m = file.metadata.native(f) m = standardize(m) # Sanitize m = metadata.cover.remove(m) m = list.assoc.filter(fun (k, _) -> not list.mem(k, ["priv", "rva2"]), m) # Add more metadata m = ("basename", path.basename(f))::m m = ( "last scan", string.float(time()) )::m m end # Whether an entry needs to be updated. def needs_update(f, m) = file.mtime(f) > string.to_float( m[ "last scan" ] ) end # Add a file to the database. def add(f) = # If file doesn't exist remove it if not (file.exists(f)) then db := list.assoc.remove(f, db()) else # New file or not recent enough metadata if not list.assoc.mem(f, db()) or needs_update(f, list.assoc(f, db())) then db := (f, metadata(f))::list.assoc.remove(f, db()) end end end # Update database by renewing metadata and removing removed files. def update(~progress=fun (_, _) -> ()) = len = list.length(db()) n = ref(0) nu = ref(0) def u(fm) = let (f, m) = fm ref.incr(n) progress(n(), len) if not (file.exists(f)) then null elsif needs_update(f, m) then ref.incr(nu) (f, metadata(f)) else (f, m) end end db := list.filter_map(u, db()) log.debug( label=id, "Updated #{nu()} files." ) end # Make sure that new files from directories are registered. def scan(~progress=fun (_, _) -> ()) = l = list.map( fun (d) -> file.ls(absolute=true, recursive=true, d), directories() ) l = list.flatten(l) n = ref(0) len = list.length(l) def add(f) = ref.incr(n) progress(n(), len) add(f) end list.iter(add, l) end # Increment when the format of the db changes db_version = 1 # Load from the persistent file. def load() = db := [] if null.defined(persistency) then f = null.get(persistency) if file.exists(f) then try let json.parse ((v, parsed) : (int * [(string * [(string * string)])]?) ) = file.contents(f) if v == db_version and null.defined(parsed) then db := null.get(parsed) end catch e do log.important( label=id, "Failed to parse persistent file #{f}: #{e.kind}: #{e.message}" ) end end end end # Store the file in a persistent file. def store() = if null.defined(persistency) then f = null.get(persistency) data = json.stringify(compact=true, (db_version, db())) file.write(data=data, f) log.info( label=id, "Wrote persistent file #{f}" ) end end # Refresh the library. def refresh() = log.info( label=id, "Refreshing the library..." ) t = time() update() scan() store() log.info( label=id, "Refreshed the library in #{dt(t)}s." ) end # Find all files matching given criteria. def find( ~case_sensitive=true, ~artist=null, ~artist_contains=null, ~artist_matches=null, ~album=null, ~genre=null, ~title=null, ~title_contains=null, ~filename=null, ~filename_contains=null, ~filename_matches=null, ~year=null, ~year_ge=null, ~year_lt=null, ~bpm=null, ~bpm_ge=null, ~bpm_lt=null, ~predicate=(fun (_) -> true) ) = def p(m) = def eq(s, t) = if case_sensitive then s == t else string.case(s) == string.case(t) end end def contains(s, t) = if case_sensitive then string.contains(substring=s, t) else string.contains(substring=string.case(s), string.case(t)) end end def eqf(k, v) = null.defined(v) ? eq(m[k], null.get(v)) : true end def ctf(k, v) = null.defined(v) ? contains(null.get(v), m[k]) : true end def mtf(k, v) = null.defined(v) ? string.match(pattern=null.get(v), m[k]) : true end eqf("artist", artist) and ctf("artist", artist_contains) and mtf("artist", artist_matches) and eqf("album", album) and eqf("genre", genre) and eqf("title", title) and ctf("title", title_contains) and eqf("filename", filename) and ctf("basename", filename_contains) and mtf("basename", filename_matches) and if null.defined(year) or null.defined(year_ge) or null.defined(year_lt) then if string.is_int(m["year"]) then y = string.to_int(m["year"]) (null.defined(year) ? y == null.get(year) : true ) and (null.defined(year_ge) ? y >= null.get(year_ge) : true ) and (null.defined(year_lt) ? y < null.get(year_lt) : true ) else false end else true end and if null.defined(bpm) or null.defined(bpm_ge) or null.defined(bpm_lt) then if string.is_int(m["bpm"]) then b = string.to_int(m["bpm"]) (null.defined(bpm) ? b == null.get(bpm) : true ) and (null.defined(bpm_ge) ? b >= null.get(bpm_ge) : true ) and (null.defined(bpm_lt) ? b < null.get(bpm_lt) : true ) else false end else true end and predicate(m) end l = list.filter(fun (fm) -> p(snd(fm)), db()) l = list.map(fst, l) l end t = time() load() log.important( label=id, "Loaded library from #{persistency} in #{dt(t)}s: #{list.length(db())} \ entries" ) t = time() progress = if initial_progress then fun (n, l) -> print( newline=false, "#{id}: updating #{n * 100 / l}%...\r" ) else fun (_, _) -> () end update(progress=progress) log.important( label=id, "Updated library in #{dt(t)}s: #{list.length(db())} entries" ) t = time() progress = if initial_progress then fun (n, l) -> print( newline=false, "#{id}: scanning #{n * 100 / l}%...\r" ) else fun (_, _) -> () end scan(progress=progress) log.important( label=id, "Scanned new files in #{dt(t)}s: #{list.length(db())} entries" ) store() log.important( label=id, "Stored library" ) if null.defined(refresh_time) then thread.run( delay=null.get(refresh_time), every=null.get(refresh_time), refresh ) end def clear() = db := [] end def add_directory(d) = directories := d::directories() scan() end {find=find, refresh=refresh, add_directory=add_directory, clear=clear} end %ifdef sqlite # A library to store the metadata of files in given folders and query # them. This is useful to generate playlists based on metadata. This version # use an SQL implementation which should be much faster and less memory # consuming than the basic one. # @category File # @param ~persistency Store the database in given file, which is reuse to populate the database on next run. # @param ~refresh Scan directories for new files every given number of seconds (by default the database is never updated). # @param ~standardize Function mapped on metadata when indexing. It can be used to change the field names to standard ones, pretreat data, etc. # @param ~initial_progress Show progress of library being indexed at startup. # @param ~directories Directories to look for files in. # @param dir Directory to look for files in. # @method find Find files according to conditions on metadata. # @method refresh Update metadatas and look for new files. # @method add_directory Add a new directory which should be scanned. # @method clear Remove all known metadata. def medialib.sqlite( ~id=null, ~database, ~refresh=null, ~standardize=fun (m) -> m, ~initial_progress=true, ~directories=[], dir=null ) = id = string.id.default(default="medialib.sqlite", id) refresh_time = refresh directories = ref(directories) if null.defined(dir) then directories := null.get(dir)::directories() end fields_string = ["artist", "title", "album", "genre", "basename"] fields_int = ["year", "bpm"] fields_float = ["last_scan"] db = sqlite(database) begin fields_string = list.map(fun (l) -> (l, "STRING"), fields_string) fields_int = list.map(fun (l) -> (l, "INT"), fields_int) fields_float = list.map(fun (l) -> (l, "FLOAT"), fields_float) db.table.create( "metadata", preserve=true, [ ( "file", "STRING PRIMARY KEY" ), ...fields_string, ...fields_int, ...fields_float ] ) end def dt(t) = string.float(decimal_places=2, time() - t) end # Read metadata from file. def metadata(f) = m = file.metadata.native(f) m = standardize(m) # Sanitize m = metadata.cover.remove(m) m = list.assoc.filter(fun (k, _) -> not list.mem(k, ["priv", "rva2"]), m) # Add more metadata m = ("basename", path.basename(f))::m m = ("last_scan", string.float(time()))::m m end # Whether an entry needs to be updated. def needs_update(f, last_scan) = file.mtime(f) > last_scan end # Remove file from the database def remove(f) = db.delete(table="metadata", where="file=#{sqlite.escape(f)}") end # Add a file to the database. def add(f) = # If file doesn't exist remove it if not (file.exists(f)) then remove(f) else count = db.count(table="metadata", where="file=#{sqlite.escape(f)}") def last_scan() = let sqlite.query ([{last_scan}] : [{last_scan: float}]) = db.select( "last_scan", table="metadata", where="file=#{sqlite.escape(f)}" ) last_scan end # New file or not recent enough metadata if count == 0 or needs_update(f, last_scan()) then m = metadata(f) def field(~map, k) = def map(x) = # Harden try map(x) catch _ do null end end if list.assoc.mem(k, m) then map(list.assoc(k, m)) else null end end id = fun (x) -> x m = { file=f, artist=field(map=id, "artist"), title=field(map=id, "title"), album=field(map=id, "album"), genre=field(map=id, "genre"), basename=field(map=id, "basename"), last_scan=field(map=float_of_string, "last_scan"), year=field(map=int_of_string, "year"), bpm=field(map=int_of_string, "bpm") } db.insert(table="metadata", replace=true, m) end end end # Number of entries in the database def count() = db.count(table="metadata") end # Update database by renewing metadata and removing removed files. def update(~progress=fun (_, _) -> ()) = len = count() n = ref(0) nu = ref(0) def u(row) = ref.incr(n) progress(n(), len) let sqlite.row ({file} : {file: string}) = row add(file) end db.select.iter(u, "file", table="metadata") log.debug( label=id, "Updated #{nu()} files." ) end # Make sure that new files from directories are registered. def scan(~progress=fun (_, _) -> ()) = l = list.map( fun (d) -> file.ls(absolute=true, recursive=true, d), directories() ) l = list.flatten(l) n = ref(0) len = list.length(l) def add(f) = ref.incr(n) progress(n(), len) add(f) end list.iter(add, l) end # Refresh the library. def refresh() = log.info( label=id, "Refreshing the library..." ) t = time() update() scan() log.info( label=id, "Refreshed the library in #{dt(t)}s." ) end # Find all files matching given criteria. def find( ~case_sensitive=true, ~artist=null, ~artist_contains=null, ~artist_matches=null, ~album=null, ~genre=null, ~title=null, ~title_contains=null, ~filename=null, ~filename_contains=null, ~filename_matches=null, ~year=null, ~year_ge=null, ~year_lt=null, ~bpm=null, ~bpm_ge=null, ~bpm_lt=null, ~condition=null ) = predicates = ref([]) def pred(p) = predicates := p::predicates() end if null.defined(condition) then pred(null.get(condition)) end def cmp(op, k, v) = if null.defined(v) then v = null.get(v) p = if case_sensitive then ( "#{k} #{op} #{sqlite.escape(v)}" ) else ( "UPPER(#{k}) #{op} UPPER(#{sqlite.escape(v)})" ) end pred(p) end end def eqf(k, v) = cmp("=", k, v) end def ctf(k, v) = if null.defined(v) then v = null.get(v) cmp("LIKE", k, "%" ^ v ^ "%") end end def mtf(k, v) = cmp("MATCHES", k, v) end eqf("artist", artist) ctf("artist", artist_contains) mtf("artist", artist_matches) eqf("album", album) eqf("genre", genre) eqf("title", title) ctf("title", title_contains) eqf("filename", filename) ctf("basename", filename_contains) mtf("basename", filename_matches) if null.defined(year) then year = null.get(year) pred("year=#{year}") end if null.defined(year_ge) then year_ge = null.get(year_ge) pred( "year >= #{year_ge}" ) end if null.defined(year_lt) then year_lt = null.get(year_lt) pred( "year < #{year_lt}" ) end if null.defined(bpm) then bpm = null.get(bpm) pred("bpm=#{bpm}") end if null.defined(bpm_ge) then bpm_ge = null.get(bpm_ge) pred( "bpm >= #{bpm_ge}" ) end if null.defined(bpm_lt) then bpm_lt = null.get(bpm_lt) pred( "bpm < #{bpm_lt}" ) end predicates = string.concat( separator=" AND ", predicates() ) let sqlite.query (l : [{file: string}]) = db.select("file", table="metadata", where=predicates) list.map((fun (l) -> l.file), l) end t = time() progress = if initial_progress then fun (n, l) -> print( newline=false, "#{id}: updating #{n * 100 / l}%...\r" ) else fun (_, _) -> () end update(progress=progress) log.important( label=id, "Updated library in #{dt(t)}s: #{count()} entries" ) t = time() progress = if initial_progress then fun (n, l) -> print( newline=false, "#{id}: scanning #{n * 100 / l}%...\r" ) else fun (_, _) -> () end scan(progress=progress) log.important( label=id, "Scanned new files in #{dt(t)}s: #{count()} entries" ) if null.defined(refresh_time) then thread.run( delay=null.get(refresh_time), every=null.get(refresh_time), refresh ) end def clear() = db.delete(table="metadata") end def add_directory(d) = directories := d::directories() scan() end {find=find, refresh=refresh, add_directory=add_directory, clear=clear} end %endif liquidsoap-2.4.2/src/libs/metadata.liq000066400000000000000000000152371513273233300177120ustar00rootroot00000000000000let metadata.getter = () # Create a getter from a metadata. # @category Metadata # @flag hidden # @param init Initial value. # @param map Function to apply to the metadata value to obtain the new value. # @param metadata Metadata on which the value should be updated. # @param s Source containing the metadata. def metadata.getter.base(init, map, metadata, s) = x = ref(init) def f(m) = v = m[metadata] if v != "" then x := map(v) end end s.on_metadata(synchronous=true, f) ref.getter(x) end let metadata.getter.source = () # Variant of `metadata.getter.base` which also returns the source. Using this # variant is a bit more complex, but safer this it does not involve a global # state, which might unexpectedly change the metadata if a source is used at # various places. # @flag hidden def metadata.getter.source.base(init, map, metadata, s) = x = ref(init) def f(m) = v = m[metadata] if v != "" then x := map(v) end end s.on_metadata(synchronous=true, f) (s, ref.getter(x)) end # Create a getter from a metadata: this is a string, whose value can be changed # with a metadata. # @category Metadata # @param init Initial value. # @param m Metadata on which the value should be updated. # @param s Source containing the metadata. def replaces metadata.getter(init, m, s) = metadata.getter.base(init, fun (v) -> v, m, s) end # Create a float getter from a metadata: this is a float, whose value can be # changed with a metadata. # @category Metadata # @param init Initial value. # @param m Metadata on which the value should be updated. # @param s Source containing the metadata. def metadata.getter.float(init, m, s) = metadata.getter.base(init, float_of_string, m, s) end # Create a float getter from a metadata: this is a float, whose value can be # changed with a metadata. This function also returns the source. # @category Metadata # @param init Initial value. # @param m Metadata on which the value should be updated. # @param s Source containing the metadata. def metadata.getter.source.float(init, m, s) = metadata.getter.source.base(init, float_of_string, m, s) end # Extract filename from metadata. # @category Metadata def metadata.filename(m) = m["filename"] end # Extract title from metadata. # @category Metadata def metadata.title(m) = m["title"] end # Extract artist from metadata. # @category Metadata def metadata.artist(m) = m["artist"] end # Extract comment from metadata. # @category Metadata def metadata.comment(m) = m["comment"] end # Extract cover from metadata. This function implements cover extraction # for the following formats: coverart (ogg), apic (flac, mp3) and pic (mp3). # @category Metadata # @param m Metadata from which the cover should be extracted. # @param ~coverart_mime Mime type to use for `"coverart"` metadata. Support disabled if `null`. # @method mime MIME type for the cover. def metadata.cover(~coverart_mime=null, m) = fname = metadata.filename(m) if list.assoc.mem("coverart", m) and null.defined(coverart_mime) then cover = list.assoc(default="", "coverart", m) string.base64.decode(cover).{mime=null.get(coverart_mime)} elsif list.assoc.mem("metadata_block_picture", m) then # See https://xiph.org/flac/format.html#metadata_block_picture cover = list.assoc(default="", "metadata_block_picture", m) cover = file.metadata.flac.cover.decode(cover) if not null.defined(cover) then log.info( "Failed to read cover metadata for #{fname}." ) null else null.get(cover) end else # Assume we have an mp3 file m = if list.assoc.mem("apic", m) or list.assoc.mem("pic", m) then m else # Try the builtin tag reader because APIC tags are not read by default, log.debug( label="metadata.cover", "APIC or PIC not found for #{fname}, trying builtin tag reader." ) file.metadata.id3v2(fname) end pic = list.assoc(default="", "pic", m) apic = list.assoc(default="", "apic", m) if apic != "" then log.debug( label="metadata.cover", "Found APIC for #{fname}." ) # TODO: we could use file type in order to select cover if there are many string.apic.parse(apic) elsif pic != "" then log.debug( label="metadata.cover", "Found APIC for #{fname}." ) # TODO: we could use file type in order to select cover if there are many pic = string.pic.parse(pic) mime = if pic.format == "JPG" then "image/jpeg" elsif pic.format == "PNG" then "image/png" else "application/octet-stream" end pic.{mime=mime} else log.info( "No cover found for #{fname}." ) null end end end # Obtain cover-art for a file. `null` is returned in case there is no # such information. # @category Metadata # @param file File from which the cover should be obtained def file.cover(fname) = metadata.cover(file.metadata(fname)) end # Remove cover metadata. # @category Metadata def metadata.cover.remove(m) = list.assoc.filter( fun (k, (_:string)) -> not list.mem(k, settings.encoder.metadata.cover()), m ) end # Cleanup metadata for export. This is used to remove Liquidsoap's internal # metadata entries before sending them. List of exported metadata is set using # `settings.encoder.metadata.export.set`. # @category Metadata def metadata.export(m) = exported_keys = settings.encoder.metadata.export() list.assoc.filter((fun (k, (_:string)) -> list.mem(k, exported_keys)), m) end let metadata.json = () # Export metadata as JSON object. Cover art, if found, is extracted using # `metadata.cover` and exported with key `"cover"` and exported using # `string.data_uri.encode`. # @category Metadata # @param ~coverart_mime Mime type to use for `"coverart"` metadata. Support disasbled if `null`. # @param ~compact Output compact text. # @param ~json5 Use json5 extended spec. def metadata.json.stringify( ~coverart_mime=null, ~base64=true, ~compact=false, ~json5=false, m ) = c = metadata.cover(coverart_mime=coverart_mime, m) m = metadata.cover.remove(m) m = metadata.export(m) m = if null.defined(c) then c = null.get(c) [("cover", string.data_uri.encode(base64=base64, mime=c.mime, c)), ...m] else m end data = json.object() list.iter(fun (v) -> data.add(fst(v), snd(v)), m) json.stringify(json5=json5, compact=compact, data) end # Parse metadata from JSON object. # @category Metadata def metadata.json.parse(json_string) = let json.parse (metadata_list : [(string*string)] as json.object) = json_string metadata_list end liquidsoap-2.4.2/src/libs/null.liq000066400000000000000000000032751513273233300171030ustar00rootroot00000000000000# Determine whether a nullable value is not null. # @category Programming def _null.defined(x) = null.case(x, {false}, fun (_) -> true) end # Get the value of a nullable. Raises `error.not_found` if the value is `null` # and no default value was specified. # @category Programming # @param ~default Returned value when the value is `null`. def _null.get(~default=null, x) = null.case( x, { default ?? error.raise( error.not_found, "no default value for null.get" ) }, fun (x) -> x ) end # Convert a nullable value to a list containing zero or one element depending on # whether the value is null or not. # @category Programming def _null.to_list(x) = null.case(x, {[]}, fun (x) -> [x]) end # Apply a function on a nullable value if it is not null, and return null # otherwise. # @category Programming def _null.map(f, x) = null.case(x, {null}, fun (x) -> f(x)) end # Find the first element of a list for which the image of the function is not # `null`. Raises `error.not_found` if not element is found and no default value # was specified. # @category Programming # @param ~default Returned value when no element is found. # @param f Function. # @param l List. def _null.find(~default=null, f, l) = def rec aux(l) = f = list.case( l, { default ?? error.raise( error.not_found, "no default value for list.find_defined" ) }, fun (x, l) -> { begin y = f(x) if null.defined(y) then y else aux(l) end end } ) f() end aux(l) end liquidsoap-2.4.2/src/libs/playlist.liq000066400000000000000000001031231513273233300177630ustar00rootroot00000000000000let settings.playlist.mime_types = settings.make.void( "Mime-types used for guessing playlist formats." ) let playlist.parse.cue = () # Parse a cue file # @category Liquidsoap # @param ~pwd Path to use for relative path resolution def playlist.parse.cue.full(~pwd=null, content) = content = string.split(separator="[\r\n]+", content) def parse_file(s) = matches = r/^FILE (.+)$/.exec(string.trim(s)) try match = list.assoc(1, matches) match_chars = string.chars(match) def rec get_last(~char, cur, chars) = let [...chars, last] = chars if last == char then filename = string.concat(separator="", chars) file_type = string.trim(string.concat(separator="", cur)) file_type = if file_type == "" then null else string.case(lower=true, file_type) end (filename, file_type) else get_last(char=char, [last, ...cur], chars) end end let (filename, file_type) = if list.hd(match_chars) == '"' then let [_, ...chars] = match_chars let (filename, file_type) = get_last(char='"', [], chars) (string.unquote('"#{filename}"'), file_type) else get_last( char=" ", [], match_chars ) end filename = playlist.parse.get_file(pwd=pwd, filename) {filename=filename, file_type=file_type} catch _ do null end end def parse_track_attribute(s) = matches = r/^TRACK ([^\s]+)\s([^\s]+)?$/.exec(string.trim(s)) try position = int_of_string(list.assoc(1, matches)) type = list.assoc(2, matches) {position=position, track_type=string.case(lower=true, type)} catch _ : [error.not_found] do null end end def parse_rem(s) = matches = r/^REM ([^\s]+) (.+)$/.exec(string.trim(s)) try name = string.case(lower=true, list.assoc(1, matches)) value = string.unquote(list.assoc(2, matches)) (name, value) catch _ : [error.not_found] do null end end def parse_timecode(s) = matches = r/^([\d]+):([\d]+):([\d]+)/.exec(string.trim(s)) try minutes = int_of_string(list.assoc(1, matches)) seconds = int_of_string(list.assoc(2, matches)) frames = int_of_string(list.assoc(3, matches)) {minutes=minutes, seconds=seconds, frames=frames} catch _ : [error.not_found] do null end end def parse_index(s) = matches = r/^INDEX ([\d]+) ([\d:]+)/.exec(string.trim(s)) try index = int_of_string(list.assoc(1, matches)) timecode = list.assoc(2, matches) (index, null.get(parse_timecode(timecode))) catch _ do null end end def parse_optional(~label, s) = matches = regexp( "^#{string.case(lower=false, label)} (.+)$" ).exec(string.trim(s)) null.map(string.unquote, list.assoc.nullable(1, matches)) end def end_parse_track(content) = content == [] or null.defined(parse_track_attribute(list.hd(content))) end def rec parse_track(~file, ~track, content) = track = (track : { position: int, track_type?: string, performer?: string, title?: string, album?: string, isrc?: string, postgap?: {minutes: int, seconds: int, frames: int}, pregap?: {minutes: int, seconds: int, frames: int}, indexes: [ ( int * { filename?: string, file_type?: string, minutes: int, seconds: int, frames: int } ) ] } ) if end_parse_track(content) then (file, track, content) else let [s, ...content] = content index = parse_index(s) new_file = parse_file(s) let (file, track) = if null.defined(index) then let (idx, timecode) = null.get(index) ( file, { ...track, indexes=[...track.indexes, (idx, {...file, ...timecode})] } ) elsif null.defined(new_file) then (new_file, track) else track_attributes = [ ("title", fun (title) -> {...track, title=title}), ("performer", fun (performer) -> {...track, performer=performer}), ( "pregap", fun (pregap) -> {...track, pregap=null.get(parse_timecode(pregap))} ), ( "postgap", fun (postgap) -> {...track, postgap=null.get(parse_timecode(postgap))} ), ( "rem", fun (rem) -> begin parsed = parse_rem( "REM #{rem}" ) if null.defined(parsed) then let (label, value) = null.get(parsed) if label == "album" then {...track, album=value} else log.important( label="playlist.parse.cue.full", "Unknown track attribute REM #{rem}, please file a bug \ report!" ) track end end end ), ("isrc", fun (isrc) -> {...track, isrc=isrc}) ] attribute_names = list.map(fst, track_attributes) def rec check_attribute(attribute_names) = if attribute_names == [] then log.important( label="playlist.parse.cue.full", "Could not parse track attribute: #{s}" ) track else let [name, ...attribute_names] = attribute_names let fn = list.assoc(name, track_attributes) parsed = parse_optional(label=name, s) if null.defined(parsed) then fn(null.get(parsed)) else check_attribute(attribute_names) end end end (file, check_attribute(attribute_names)) end parse_track(file=file, track=track, content) end end def rec parse_content(~file, ~sheet, content) = sheet = (sheet : { catalog?: string, performer?: string, title?: string, rem: [(string * string)], tracks: [ { position: int, track_type?: string, performer?: string, title?: string, album?: string, isrc?: string, postgap?: {minutes: int, seconds: int, frames: int}, pregap?: {minutes: int, seconds: int, frames: int}, indexes: [ ( int * { filename?: string, file_type?: string, minutes: int, seconds: int, frames: int } ) ] } ] } ) if content == [] then sheet else let [s, ...content] = content new_file = parse_file(s) new_rem = parse_rem(s) track = parse_track_attribute(s) if null.defined(new_file) then parse_content(file=new_file, sheet=sheet, content) elsif null.defined(new_rem) then parse_content( file=file, sheet={...sheet, rem=[null.get(new_rem), ...sheet.rem]}, content ) elsif null.defined(track) then let (file, track, content) = parse_track( file=file, track={...null.get(track), indexes=[]}, content ) parse_content( file=file, sheet={...sheet, tracks=[...sheet.tracks, track]}, content ) else sheet_attributes = [ ("catalog", fun (catalog) -> {...sheet, catalog=catalog}), ("performer", fun (performer) -> {...sheet, performer=performer}), ("title", fun (title) -> {...sheet, title=title}) ] attribute_names = list.map(fst, sheet_attributes) def rec check_attribute(attribute_names) = if attribute_names == [] then log.important( label="playlist.parse.cue.full", "Could not parse attribute: #{string.quote(s)}" ) sheet else let [name, ...attribute_names] = attribute_names let fn = list.assoc(name, sheet_attributes) let parsed = parse_optional(label=name, s) if null.defined(parsed) then fn(null.get(parsed)) else check_attribute(attribute_names) end end end parse_content( file=file, sheet=check_attribute(attribute_names), content ) end end end parse_content(file=null, sheet={tracks=[], rem=[]}, content) end let settings.playlist.cue = settings.make.void( "Settings for parsing cue files" ) let settings.playlist.cue.pregap_metadata = settings.make( description="Metadata used to pass pre-gap cue metadata", "liq_pregap" ) let settings.playlist.cue.index_zero_metadata = settings.make( description="Metadata used to pass index 0 cue metadata", "liq_index_zero" ) let settings.playlist.cue.index_zero_filename_metadata = settings.make( description="Metadata used to pass index 0 filename metadata", "liq_index_zero_filename" ) let settings.playlist.cue.postgap_metadata = settings.make( description="Metadata used to pass pre-gap cue metadata", "liq_postgap" ) # Parse a cue file and return a value suitable for playlist parser registration. # @category Liquidsoap # @param ~pwd Path to use for relative path resolution def replaces playlist.parse.cue(~pwd=null, content) = # Simple test: playlist should have at least one file.. if r/FILE/.test(content) then let {catalog?, title?, performer?, rem, tracks} = playlist.parse.cue.full(pwd=pwd, content) let playlist_album = title let album_performer = performer rem = list.map( fun (el) -> begin let (name, value) = el if name == "date" then ("year", value) else (name, value) end end, rem ) def seconds_of_timecode(timecode) = let {minutes, seconds, frames} = timecode float(minutes) * 60. + float(seconds) + float(frames) / 75. end def string_of_timecode(timecode) = string(seconds_of_timecode(timecode)) end def add_track(tracks, track) = let { performer?, title?, album?, isrc?, pregap?, postgap?, position, indexes } = track index_zero = list.assoc.nullable(0, indexes) index_zero_filename = null.map(fun (m) -> m?.filename, index_zero) index = list.assoc.nullable(1, indexes) filename = null.map(fun (m) -> m?.filename, index) cue_in_metadata = settings.playlist.cue_in_metadata() cue_out_metadata = settings.playlist.cue_out_metadata() pregap_metadata = settings.playlist.cue.pregap_metadata() index_zero_metadata = settings.playlist.cue.index_zero_metadata() index_zero_filename_metadata = settings.playlist.cue.index_zero_filename_metadata() postgap_metadata = settings.playlist.cue.postgap_metadata() if not null.defined(filename) or (not null.defined(index)) then log.important( label="playlist.cue.parse", "Track without filename or index: #{track}" ) tracks else let timecode = null.get(index) let filename = null.get(filename) timecode = seconds_of_timecode(timecode) cue_in = timecode == 0. ? [] : [(cue_in_metadata, string(timecode))] cue_out = try let (old_meta, old_filename) = list.hd(tracks) if old_filename == filename then index_zero = list.assoc(default="", index_zero_metadata, old_meta) cue_out = if index_zero != "" then index_zero else list.assoc(cue_in_metadata, old_meta) end [(cue_out_metadata, cue_out)] else [] end catch _ do [] end meta = list.fold( fun (meta, el) -> begin let (name, value) = el if null.defined(value) then [(name, null.get(value)), ...meta] else meta end end, [], [ (pregap_metadata, null.map(string_of_timecode, pregap)), (postgap_metadata, null.map(string_of_timecode, postgap)), (index_zero_metadata, null.map(string_of_timecode, index_zero)), (index_zero_filename_metadata, index_zero_filename), ("tracknumber", string(position)), ("catalog", catalog), ...( null.defined(performer) ? [("artist", performer), ("albumartist", album_performer)] : [("artist", album_performer)] ), ("title", title), ("isrc", isrc), ("album", album ?? playlist_album) ] ) [([...cue_in, ...cue_out, ...meta, ...rem], filename), ...tracks] end end list.fold(add_track, [], list.rev(tracks)) else [] end end let settings.playlist.mime_types.basic = settings.make( description="Mime-types used for guessing text-based playlists.", [ { name= "scpls format", mimes=["audio/x-scpls"], strict=true, parser=playlist.parse.scpls }, { name= "cue sheet", mimes=["application/x-cue"], strict=true, parser=playlist.parse.cue }, { name= "m3u Format", mimes=["audio/x-mpegurl", "audio/mpegurl", "application/x-mpegURL"], strict=false, parser=playlist.parse.m3u } ] ) %ifdef playlist.parse.xml let settings.playlist.mime_types.xml = settings.make( description="Mime-types used for guessing xml-based playlists.", [ { name= "xmlplaylist format", mimes= [ "video/x-ms-asf", "audio/x-ms-asx", "text/xml", "application/xml", "application/smil", "application/smil+xml", "application/xspf+xml", "application/rss+xml" ], strict=true, parser=playlist.parse.xml } ] ) %endif # @flag hidden let register_playlist_parsers = begin registered = ref(false) fun () -> begin if not registered() then parsers = settings.playlist.mime_types.basic() %ifdef playlist.parse.xml parsers = [...parsers, ...settings.playlist.mime_types.xml()] %endif list.iter( fun ({name, mimes, strict, parser}) -> playlist.parse.register( name=name, mimes=mimes, strict=strict, parser ), parsers ) end registered := true end end on_start(register_playlist_parsers) # @docof playlist.parse def replaces playlist.parse(%argsof(playlist.parse), uri) = register_playlist_parsers() playlist.parse(%argsof(playlist.parse), uri) end # Default id assignment for playlists (the identifier is generated from the # filename). # @category Liquidsoap # @flag hidden # @param ~default Default name pattern when no useful name can be extracted from `uri` # @param uri Playlist uri def playlist.id(~default, uri) = basename = path.basename(uri) basename = if basename == "." then let l = r/\//g.split(uri) if l == [] then path.dirname(uri) else list.hd(list.rev(l)) end else basename end if basename == "." then string.id.default(default=default, null) else basename end end # Retrieve the list of files contained in a playlist. # @category File # @param ~mime_type Default MIME type for the playlist. `null` means automatic detection. # @param ~timeout Timeout for resolving the playlist # @param uri Path to the playlist def playlist.files(~id=null, ~mime_type=null, ~timeout=null, uri) = id = id ?? playlist.id(default="playlist.files", uri) if file.is_directory(uri) then log.info( label=id, "Playlist is a directory." ) files = file.ls(absolute=true, recursive=true, sorted=true, uri) files = list.filter(fun (f) -> not (file.is_directory(f)), files) files else pl = request.create(resolve_metadata=false, uri) result = if request.resolve(timeout=timeout, pl) then pl = request.filename(pl) files = playlist.parse(mime=mime_type, pl) def file_request(el) = let (meta, file) = el s = string.concat( separator=",", list.map(fun (el) -> "#{fst(el)}=#{string.quote(snd(el))}", meta) ) if s == "" then file else "annotate:#{s}:#{file}" end end list.map.right(file_request, files) else log.important( label=id, "Couldn't read playlist: request resolution failed." ) request.destroy(pl) error.raise( error.invalid, "Could not resolve uri: #{uri}" ) end request.destroy(pl) result end end %ifdef native let stdlib_native = native %endif # Play a list of files. # @category Source / Input # @param ~id Force the value of the source ID. # @param ~check_next Function used to filter next tracks. A candidate track is \ # only validated if the function returns true on it. The function is called \ # before resolution, hence metadata will only be available for requests \ # corresponding to local files. This is typically used to avoid repetitions, \ # but be careful: if the function rejects all attempts, the playlist will \ # enter into a consuming loop and stop playing anything. # @param ~prefetch How many requests should be queued in advance. # @param ~loop Loop on the playlist. # @param ~mode Play the files in the playlist either in the order ("normal" mode), \ # or shuffle the playlist each time it is loaded, and play it in this order for a \ # whole round ("randomize" mode), or pick a random file in the playlist each time \ # ("random" mode). # @param ~native Use native implementation, when available. # @param ~on_loop Function executed when the playlist is about to loop. # @param ~on_done Function executed when the playlist is finished. # @param ~max_fail When this number of requests fail to resolve, the whole playlists is considered as failed and `on_fail` is called. # @param ~on_fail Function executed when too many requests failed and returning the contents of a fixed playlist. # @param ~timeout Timeout (in sec.) to resolve the request. Defaults to `settings.request.timeout` when `null`. # @param ~cue_in_metadata Metadata for cue in points. Disabled if `null`. # @param ~cue_out_metadata Metadata for cue out points. Disabled if `null`. # @param playlist Playlist. # @method reload Reload the playlist with given list of songs. # @method remaining_files Songs remaining to be played. def playlist.list( ~id=null, ~check_next=null, ~prefetch=null, ~loop=true, ~mode="normal", ~native=false, ~on_loop={()}, ~on_done={()}, ~max_fail=10, ~on_fail=null, ~timeout=null, ~cue_in_metadata=null("liq_cue_in"), ~cue_out_metadata=null("liq_cue_out"), playlist ) = ignore(native) id = string.id.default(default="playlist.list", id) mode = if not list.mem(mode, ["normal", "random", "randomize"]) then log.severe( label=id, "Invalid mode: #{mode}" ) "randomize" else mode end check_next = check_next ?? fun (_) -> true should_stop = ref(false) on_shutdown({should_stop.set(true)}) on_fail = null.map( fun (on_fail) -> {if not should_stop() then on_fail() else [] end}, on_fail ) # Original playlist when loaded playlist_orig = ref(playlist) # Randomize the playlist if necessary def randomize(p) = if mode == "randomize" then list.shuffle(p) else p end end # Current remaining playlist playlist = ref(randomize(playlist)) # A reference to know if the source has been stopped has_stopped = ref(false) # Delay the creation of next after the source because we need it to resolve # requests at the right content type. next_fun = ref(fun () -> null) def next() = f = next_fun() f() end # Instantiate the source default = fun () -> request.dynamic( id=id, prefetch=prefetch, timeout=timeout, retry_delay=1., available={not has_stopped()}, next ) s = %ifdef native if native then stdlib_native.request.dynamic(id=id, next) else default() end %else default() %endif # Prevent concurrent reload and next() is_reloading = ref(false) pending_next = ref(false) # The reload function def reload(~empty_queue=true, p) = is_reloading := true log.debug( label=id, "Reloading playlist." ) playlist_orig := p playlist := randomize(playlist_orig()) has_stopped := false if empty_queue then q = s.queue() s.set_queue([]) list.iter(request.destroy, q) pending_next := true end if pending_next() then s.fetch() end pending_next := false is_reloading := false end # When we have more than max_fail failures in a row, we wait for 1 second # before trying again in order to avoid infinite loops. failed_count = ref(0) failed_time = ref(0.) # The (real) next function def rec next() = if is_reloading() then pending_next := true elsif loop and list.is_empty(playlist()) then on_loop() # The above function might have reloaded the playlist if list.is_empty(playlist()) then playlist := randomize(playlist_orig()) end end file = if list.length(playlist()) > 0 then if mode == "random" then n = random.int(min=0, max=list.length(playlist())) list.nth(default="", playlist(), n) else ret = list.hd(default="", playlist()) playlist := list.tl(playlist()) ret end else # Playlist finished if not has_stopped() then has_stopped := true log.info( label=id, "Playlist stopped." ) on_done() end "" end if file == "" or (failed_count() >= max_fail and time() < failed_time() + 1.) then # Playlist failed too many times recently, don't try next for now. null else log.debug( label=id, "Next song will be \"#{file}\"." ) r = request.create( cue_in_metadata=cue_in_metadata, cue_out_metadata=cue_out_metadata, file ) if check_next(r) then if not request.resolve(r) then log.info( label=id, "Could not resolve request: #{request.uri(r)}." ) request.destroy(r) ref.incr(failed_count) # Playlist failed, call handler. if failed_count() < max_fail then log.info( label=id, "Playlist failed." ) if null.defined(on_fail) then f = null.get(on_fail) reload(f()) end end failed_time := time() (next() : request?) else failed_count := 0 r end else log.info( label=id, "Request #{request.uri(r)} rejected by check_next." ) request.destroy(r) next() end end end next_fun := next # List of songs remaining to be played def remaining_files() = playlist() end # Return s.{reload=reload, remaining_files=remaining_files} end # Read a playlist or a directory and play all files. # @category Source / Input # @param ~id Force the value of the source ID. # @param ~check_next Function used to filter next tracks. A candidate track is \ # only validated if the function returns true on it. The function is called \ # before resolution, hence metadata will only be available for requests \ # corresponding to local files. This is typically used to avoid repetitions, \ # but be careful: if the function rejects all attempts, the playlist will \ # enter into a consuming loop and stop playing anything. # @param ~prefetch How many requests should be queued in advance. # @param ~loop Loop on the playlist. # @param ~mime_type Default MIME type for the playlist. `null` means automatic \ # detection. # @param ~mode Play the files in the playlist either in the order ("normal" mode), \ # or shuffle the playlist each time it is loaded, and play it in this order for a \ # whole round ("randomize" mode), or pick a random file in the playlist each time \ # ("random" mode). # @param ~native Use native implementation. # @param ~max_fail When this number of requests fail to resolve, the whole playlists is considered as failed and `on_fail` is called. # @param ~on_done Function executed when the playlist is finished. # @param ~on_fail Function executed when too many requests failed and returning the contents of a fixed playlist. # @param ~on_reload Callback called after playlist has reloaded. # @param ~prefix Add a constant prefix to all requests. Useful for passing extra \ # information using annotate, or for resolution through a particular protocol, \ # such as replaygain. # @param ~reload Amount of time (in seconds or rounds), when applicable, before \ # which the playlist is reloaded; 0 means never. # @param ~reload_mode Unit of the reload parameter, either "never" (never reload \ # the playlist), "rounds", "seconds" or "watch" (reload the file whenever it is \ # changed). # @param ~register_server_commands Register corresponding server commands # @param ~timeout Timeout (in sec.) to resolve the request. Defaults to `settings.request.timeout` when `null`. # @param ~cue_in_metadata Metadata for cue in points. Disabled if `null`. # @param ~cue_out_metadata Metadata for cue out points. Disabled if `null`. # @param uri Playlist URI. # @method reload Reload the playlist. # @method length Length of the of the playlist (the number of songs it contains). # @method remaining_files Songs remaining to be played. def replaces playlist( ~id=null, ~check_next=null, ~prefetch=null, ~loop=true, ~max_fail=10, ~mime_type=null, ~mode="randomize", ~native=false, ~on_done={()}, ~on_fail=null, ~on_reload=(fun (_) -> ()), ~prefix="", ~reload=0, ~reload_mode="seconds", ~timeout=null, ~cue_in_metadata=null("liq_cue_in"), ~cue_out_metadata=null("liq_cue_out"), ~register_server_commands=true, uri ) = id = id ?? playlist.id(default="playlist", uri) reload_mode = if not list.mem(reload_mode, ["never", "rounds", "seconds", "watch"]) then log.severe( label=id, "Invalid reload mode: #{mode}" ) "seconds" else reload_mode end round = ref(0) # URI of the current playlist playlist_uri = ref(uri) # List of files in the current playlist files = ref([]) # The reload function reloader_ref = ref(fun (~empty_queue=true) -> ignore(empty_queue)) failed_loads = ref(0) # The load function def load_playlist() = playlist_uri = path.home.unrelate(playlist_uri()) log.info( label=id, "Reloading playlist." ) files = try playlist.files( id=id, mime_type=mime_type, timeout=timeout, playlist_uri ) catch err do log.info( label=id, "Playlist load failed: #{err}" ) ref.incr(failed_loads) if failed_loads() < max_fail then [] else log.info( label=id, "Maximum failures reached!" ) on_fail = on_fail ?? fun () -> [] on_fail() end end list.map.right(fun (file) -> prefix ^ file, files) end # Reload when the playlist is done def on_loop() = reloader = reloader_ref() if reload_mode == "rounds" and reload > 0 then round := round() + 1 if round() >= reload then round := 0 reloader() end end end watcher_reload_ref = ref(fun (_) -> ()) def on_reload(uri) = watcher_reload = watcher_reload_ref() watcher_reload(uri) on_reload(uri) end s = playlist.list( id=id, check_next=check_next, prefetch=prefetch, loop=loop, max_fail=max_fail, mode=mode, native=native, on_done=on_done, on_loop=on_loop, on_fail=on_fail, timeout=timeout, cue_in_metadata=cue_in_metadata, cue_out_metadata=cue_out_metadata, files() ) s.on_wake_up( synchronous=true, { log( label=s.id(), "Initial load with URI #{playlist_uri()}." ) files := load_playlist() s.reload(empty_queue=true, files()) } ) # The reload function def s.reload(~empty_queue=true, ~uri=null) = if failed_loads() < max_fail then if null.defined(uri) then playlist_uri := null.get(uri) end log( label=s.id(), "Reloading playlist with URI #{playlist_uri()}." ) files := load_playlist() s.reload(empty_queue=empty_queue, files()) on_reload(playlist_uri()) end end reloader_ref := s.reload def s.length() = list.length(files()) end # Set up reloading for seconds and watch if reload_mode == "seconds" and reload > 0 then n = float_of_int(reload) thread.run(delay=n, every=n, s.reload) elsif reload_mode == "watch" then watcher = if file.exists(playlist_uri()) then ref(null(file.watch(playlist_uri(), s.reload))) else ref(null) end watched_uri = ref(playlist_uri()) def watcher_reload(uri) = if uri != watched_uri() then w = watcher() if null.defined(w) then null.get(w).unwatch() end watched_uri := uri watcher := if file.exists(uri) then file.watch(uri, s.reload) else null end end end watcher_reload_ref := watcher_reload def watcher_shutdown() = w = watcher() if null.defined(w) then null.get(w).unwatch() end end s.on_shutdown(synchronous=true, watcher_shutdown) end if register_server_commands then # Set up telnet commands s.register_command( description="Skip current song in the playlist.", usage="skip", "skip", fun (_) -> begin s.skip() "OK" end ) s.register_command( description="Return up to 10 next URIs to be played.", usage="next", "next", fun (n) -> begin n = max(10, int_of_string(default=10, n)) requests = list.fold( (fun (cur, el) -> list.length(cur) < n ? [...cur, el] : cur ), [], s.queue() ) string.concat( separator="\n", list.map( ( fun (r) -> begin m = request.metadata(r) get = fun (lbl) -> list.assoc(default="?", lbl, m) status = get("status") uri = get("initial_uri") "[#{status}] #{uri}" end ), requests ) ) end ) s.register_command( description="Reload the playlist, unless already being loaded.", usage="reload", "reload", fun (_) -> begin s.reload() "OK" end ) def uri_cmd(uri') = if uri' == "" then playlist_uri() else if reload_mode == "watch" then log.important( label=id, "Warning: the watched file is not updated for now when changing the \ uri!" ) end # TODO playlist_uri := uri' s.reload(uri=uri') "OK" end end s.register_command( description="Print playlist URI if called without an argument, otherwise \ set a new one and load it.", usage="uri []", "uri", uri_cmd ) end s end liquidsoap-2.4.2/src/libs/predicate.liq000066400000000000000000000034321513273233300200640ustar00rootroot00000000000000predicate = () # Detect when a predicate becomes true. # @category Programming # @param ~init Detect at beginning. # @param p Predicate. def predicate.activates(~init=false, p) = last = ref(not init) fun () -> begin cur = p() ans = (not last()) and cur last := cur ans end end # Become true once every time a predicate is true. # @category Programming # @param p Predicate. def predicate.once(p) = predicate.activates(init=true, p) end # Limit the number of times a predicate is true is a row. # @category Programming # @param n Number of times the predicate is allowed to be true. # @param p Predicate. def predicate.at_most(n, p) = k = ref(0) fun () -> begin if p() then ref.incr(k) k() <= n else k := 0 false end end end # Detect when a predicate changes. # @category Programming # @param p Predicate. def predicate.changes(p) = last = ref(p()) fun () -> begin cur = p() ans = (cur != last()) last := cur ans end end # First occurrence of a predicate. # @category Programming # @param p Predicate. def predicate.first(p) = done = ref(false) fun () -> begin if done() then false else if p() then done := true true else false end end end end # Predicate which is true when a signal is sent. The returned predicate has a # method `signal` to send the signal. # @category Programming # @method signal Send a signal. def predicate.signal() = state = ref(false) def signal() = state := true end def p() = if state() then state := false true else false end end p.{signal=signal} end liquidsoap-2.4.2/src/libs/process.liq000066400000000000000000000044771513273233300176140ustar00rootroot00000000000000# Perform a shell call and return its output. # @category System # @param ~timeout Cancel process after `timeout` has elapsed. Ignored if negative. # @param ~env Process environment # @param ~inherit_env Inherit calling process's environment when `env` parameter is empty. # @param ~log_errors Log details if the command does not return 0. # @param command Command to run def process.read( ~timeout=(-1.), ~env=[], ~inherit_env=true, ~log_errors=true, command ) = p = process.run(timeout=timeout, env=env, inherit_env=inherit_env, command) if log_errors and (p.status != "exit" or p.status.code != 0) then log.important( "Failed to execute `#{command}`: #{p.status} (#{p.status.code}) #{ p.stdout } #{p.stderr}" ) end p.stdout end # Perform a shell call and return the list of its output lines. # @category System # @param ~timeout Cancel process after `timeout` has elapsed. Ignored if negative. # @param ~env Process environment # @param ~inherit_env Inherit calling process's environment when `env` parameter is empty. # @param ~log_errors Log details if the command does not return 0. # @param command Command to run def process.read.lines( ~timeout=(-1.), ~env=[], ~inherit_env=true, ~log_errors=true, command ) = s = process.read( timeout=timeout, env=env, inherit_env=inherit_env, log_errors=log_errors, command ) r/\r?\n/.split(s) end # Return true if process exited with 0 code. # @category System # @param ~timeout Cancel process after `timeout` has elapsed. Ignored if negative. # @param ~env Process environment # @param ~inherit_env Inherit calling process's environment when `env` parameter is empty. # @param command Command to test def process.test(~timeout=(-1.), ~env=[], ~inherit_env=true, command) = p = process.run(timeout=timeout, env=env, inherit_env=inherit_env, command) p.status == "exit" and p.status.code == 0 end # Read some value from standard input (console). # @category System # @param ~hide Hide typed characters (for passwords). def read(~hide=false) = if hide then process.run( "stty -echo" ) end s = list.hd( default="", process.read.lines( "read BLA && echo $BLA" ) ) if hide then process.run( "stty echo" ) end print("") s end liquidsoap-2.4.2/src/libs/profiler.liq000066400000000000000000000001621513273233300177430ustar00rootroot00000000000000# Print profiling information. # @category Liquidsoap def profiler.print() = print(profiler.stats.string()) end liquidsoap-2.4.2/src/libs/protocols.liq000066400000000000000000000622201513273233300201500ustar00rootroot00000000000000# @flag hidden def settings.make.protocol(name) = settings.make.void( "Settings for the #{name} protocol" ) end let settings.protocol = settings.make.void( "Settings for registered protocols" ) # Register the lufs_track_gain protocol. # @flag hidden def protocol.lufs_track_gain(~rlog:_, ~maxtime:_, arg) = gain = file.lufs(arg) if null.defined(gain) then "annotate:#{settings.normalize_track_gain_metadata()}=\"#{ settings.lufs.track_gain_target() - null.get(gain) } dB\":#{arg}" else arg end end protocol.add( "lufs_track_gain", protocol.lufs_track_gain, syntax="lufs_track_gain:uri", doc="Compute LUFS track gain correction and add it as metadata" ) # Register the replaygain protocol. # @flag hidden def protocol.replaygain(~rlog:_, ~maxtime:_, arg) = gain = file.replaygain(arg) tag = settings.normalize_track_gain_metadata() if null.defined(gain) then "annotate:#{tag}=\"#{null.get(gain)} dB\":#{arg}" else arg end end protocol.add( "replaygain", protocol.replaygain, syntax="replaygain:uri", doc="Compute ReplayGain value. Adds returned value as \ `\"replaygain_track_gain\"` metadata" ) let settings.protocol.process = settings.make.protocol("process") let settings.protocol.process.env = settings.make( description="List of environment variables passed down to the executed \ process.", [] ) let settings.protocol.process.inherit_env = settings.make( description="Inherit calling process's environment when `env` parameter is \ empty.", true ) let protocol.process = () # Parse process protocol arguments # @flag hidden def protocol.process.parse(~default_timeout, arg) = let [args, ...uri] = r/:/.split(arg) uri = string.concat(separator=":", uri) args = r/,/.split(args) let args = if string.contains(prefix="timeout=", list.hd(args)) then let [timeout, extname, ...cmd] = args timeout = string.residual(prefix="timeout=", timeout) timeout = null.map(string.to_float, timeout) ?? default_timeout timeout = min(default_timeout, timeout) {timeout=timeout, extname=extname, cmd=cmd} else let [extname, ...cmd] = args {timeout=default_timeout, extname=extname, cmd=cmd} end args.{uri=uri, cmd=string.concat(separator=",", args.cmd)} end # Register the process protocol. Syntax: # process:[timeout=],,:uri where `timeout` argument is optional and # cannot exceed the underlying time and is interpolated with: # [("input",),("output",),("colon",":")] # See say: protocol for an example. # @flag hidden def replaces protocol.process(~rlog:_, ~maxtime, arg) = log.info( "Processing #{arg}" ) let {uri, timeout, extname, cmd} = protocol.process.parse(default_timeout=maxtime - time(), arg) output = file.temp("liq-process", ".#{extname}") def resolve(input) = cmd = cmd % [ ("input", process.quote(input)), ("output", process.quote(output)), ("colon", ":") ] log.info( "Executing #{cmd}" ) env_vars = settings.protocol.process.env() env = environment() def get_env(k) = (k, env[k]) end env = list.map(get_env, env_vars) inherit_env = settings.protocol.process.inherit_env() p = process.run(timeout=timeout, env=env, inherit_env=inherit_env, cmd) if p.status == "exit" and p.status.code == 0 then output else log.important( "Failed to execute #{cmd}: #{p.status} (#{p.status.code})" ) log.info( "Standard output:\n#{p.stdout}" ) log.info( "Error output:\n#{p.stderr}" ) log.info( "Removing #{output}." ) file.remove(output) null end end if uri == "" then resolve("") else r = request.create(uri) delay = maxtime - time() if request.resolve(timeout=delay, r) then res = resolve(request.filename(r)) request.destroy(r) res else log( level=3, "Failed to resolve #{uri}" ) null end end end protocol.add( temporary=true, "process", protocol.process, doc="Resolve a request using an arbitrary process. `` is interpolated \ with: `[(\"input\",),(\"output\",),(\"colon\",\":\")]`. `uri` \ is an optional child request, `` is the name of a fresh temporary \ file and has extension `.`. `` is an optional input file name \ as returned while resolving `uri`.", syntax="process:,[:uri]" ) # Create a process: uri, replacing `:` with `$(colon)`. # @category Liquidsoap # @param cmd Command line to execute # @param ~extname Output file extension (with no leading '.') # @param ~uri Input uri def process.uri(~timeout=null, ~extname, ~uri="", cmd) = timeout = null.case(timeout, {""}, fun (t) -> "timeout=" ^ string(t) ^ ",") cmd = r/:/g.replace(fun (_) -> "$(colon)", cmd) uri = if uri != "" then ":#{uri}" else "" end "process:#{timeout}#{extname},#{cmd}#{uri}" end # Resolve http(s) URLs using curl # @flag hidden def protocol.http(proto, ~rlog, ~maxtime, arg) = uri = "#{proto}:#{arg}" def log(~level, s) = rlog(s) log(label="procol.external", level=level, s) end timeout = maxtime - time() ret = http.head(timeout=timeout, uri) code = ret.status_code ?? 999 extname = 200 <= code and code < 300 ? http.headers.extname(ret.headers) : null extname = if null.defined(extname) then null.get(extname) else begin content_type = http.headers.content_type(ret.headers) extra_log = if null.defined(content_type) and null.get(content_type).mime != "" then begin content_type = null.get(content_type).mime " Response has unknown mime-type: #{string.quote(content_type)} \ you may want to add it to `settings.http.mime.extnames` and \ report to us if it is a common one." end else "" end log( level=3, "Failed to find a file extension for #{string.quote(uri)}.#{ extra_log }" ) ".osb" end end output = file.temp("liq-process", extname) file_writer = file.write.stream(output) timeout = maxtime - time() try response = http.get.stream(on_body_data=file_writer, timeout=timeout, uri) if response.status_code < 400 then output else log( level=3, "Error while fetching http data: #{response.status_code} - #{ response.status_message }" ) null end catch err do file_writer(null) log( level=3, "Error while fetching http data: #{err}" ) null end end # Register download protocol. # @flag hidden def protocol.add.http(proto) = def protocol.http(~rlog, ~maxtime, arg) = protocol.http(proto, rlog=rlog, maxtime=maxtime, arg) end protocol.add( temporary=true, syntax="#{proto}://...", doc="Download http URLs using curl", proto, protocol.http ) end list.iter(protocol.add.http, ["http", "https"]) let settings.protocol.youtube_dl = settings.make.protocol("youtube-dl") let settings.protocol.youtube_dl.path = settings.make( description="Path of the youtube-dl (or yt-dlp) binary.", "yt-dlp" ) let settings.protocol.youtube_dl.timeout = settings.make( description="Timeout (in seconds) for youtube-dl executions.", 300. ) # Register the youtube-dl protocol, using youtube-dl. # Syntax: youtube-dl: # @flag hidden def protocol.youtube_dl(~rlog, ~maxtime, arg) = binary = settings.protocol.youtube_dl.path() timeout = settings.protocol.youtube_dl.timeout() def log(~level, s) = rlog(s) log(label="protocol.youtube-dl", level=level, s) end delay = maxtime - time() cmd = "#{binary} --get-title --get-filename -- #{process.quote(arg)}" log( level=4, "Executing #{cmd}" ) x = process.read.lines(timeout=delay, cmd) x = if list.length(x) >= 2 then x else ["", ".osb"] end title = list.hd(default="", x) ext = file.extension(leading_dot=false, list.nth(default="", x, 1)) cmd = "#{binary} -q -f best --no-continue --no-playlist -o $(output) -- #{ process.quote(arg) }" cmd = process.uri(timeout=timeout, extname=ext, cmd) if title != "" then "annotate:title=#{string.quote(title)}:#{cmd}" else cmd end end protocol.add( "youtube-dl", protocol.youtube_dl, doc="Resolve a request using youtube-dl.", syntax="youtube-dl:uri" ) # Register the youtube-pl protocol. # Syntax: youtube-pl: # @flag hidden def protocol.youtube_pl(~rlog:_, ~maxtime, arg) = binary = settings.protocol.youtube_dl.path() delay = maxtime - time() cmd = "#{binary} -i -s --get-id --flat-playlist -- #{process.quote(arg)}" log( level=4, "Executing #{cmd}" ) l = process.read.lines(timeout=delay, cmd) l = list.map(fun (s) -> "youtube-dl:https://www.youtube.com/watch?v=" ^ s, l) l = string.concat(separator="\n", l) ^ "\n" tmp = file.temp("youtube-pl", "") file.write(data=l, tmp) tmp end protocol.add( "youtube-pl", protocol.youtube_pl, doc="Resolve a request as a youtube playlist using youtube-dl. You typically \ want to use this as `playlist(\"youtube-pl:...\")`.", temporary=true, syntax="youtube-pl:uri" ) # Register tmp # @flag hidden def protocol.tmp(~rlog, ~maxtime, arg) = r = request.create(arg) delay = maxtime - time() if request.resolve(timeout=delay, r) then request.filename(r) else rlog( "Failed to resolve #{arg}" ) log( level=3, "Failed to resolve #{arg}" ) null end end protocol.add( "tmp", protocol.tmp, doc="Mark the given uri as temporary. Useful when chaining protocols", temporary=true, syntax="tmp:uri" ) # Register fallible # @flag hidden def protocol.fallible(~rlog:_, ~maxtime:_, arg) = arg end protocol.add( "fallible", protocol.fallible, doc="Mark the given uri as being fallible. This can be used to prevent a \ request or source from being resolved once and for all and considered \ infallible for the duration of the script, typically when debugging.", static=fun (_) -> false, syntax="fallible:uri" ) let settings.protocol.ffmpeg = settings.make.protocol("FFmpeg") let settings.protocol.ffmpeg.path = settings.make( description="Path to the ffmpeg binary", "ffmpeg" ) let settings.protocol.ffmpeg.metadata = settings.make( description="Should the protocol extract metadata", true ) let settings.protocol.ffmpeg.replaygain = settings.make( description="Should the protocol adjust ReplayGain", false ) # Register ffmpeg # @flag hidden def protocol.ffmpeg(~rlog, ~maxtime, arg) = ffmpeg = settings.protocol.ffmpeg.path() metadata = settings.protocol.ffmpeg.metadata() replaygain = settings.protocol.ffmpeg.replaygain() def log(~level, s) = rlog(s) log(label="protocol.ffmpeg", level=level, s) end def annotate(m) = def f(x) = let (key, value) = x "#{key}=#{string.quote(value)}" end m = string.concat(separator=",", list.map(f, m)) if string.bytes.length(m) > 0 then "annotate:#{m}:" else "" end end def parse_metadata(file) = cmd = "#{ffmpeg} -i #{process.quote(file)} -f ffmetadata - 2>/dev/null | grep -v \ '^;'" delay = maxtime - time() log( level=4, "Executing #{cmd}" ) lines = process.read.lines(timeout=delay, cmd) def f(cur, line) = m = r/=/.split(line) if list.length(m) >= 2 then key = list.hd(default="", m) value = string.concat(separator="=", list.tl(m)) (key, value)::cur else cur end end list.fold(f, [], lines) end def replaygain_filter(fname) = if replaygain then gain = file.replaygain(fname) if null.defined(gain) then "-af \"volume=#{null.get(gain)} dB\"" else "" end else "" end end def cue_points(m) = cue_in = float_of_string(default=0., list.assoc(default="0.", "liq_cue_in", m)) cue_out = float_of_string(default=0., list.assoc(default="", "liq_cue_out", m)) args = if cue_in > 0. then "-ss #{cue_in}" else "" end if cue_out > cue_in then "#{args} -t #{cue_out - cue_in}" else args end end def fades(r) = m = request.metadata(r) fade_type = list.assoc(default="", "liq_fade_type", m) fade_in = list.assoc(default="", "liq_fade_in", m) cue_in = list.assoc(default="", "liq_cue_in", m) fade_out = list.assoc(default="", "liq_fade_out", m) cue_out = list.assoc(default="", "liq_cue_out", m) curve = if fade_type == "lin" then ":curve=tri" elsif fade_type == "sin" then ":curve=qsin" elsif fade_type == "log" then ":curve=log" elsif fade_type == "exp" then ":curve=exp" else "" end args = if fade_in != "" then fade_in = float_of_string(default=0., fade_in) start_time = if cue_in != "" then float_of_string(default=0., cue_in) else 0. end if fade_in > 0. then ["afade=in:st=#{start_time}:d=#{fade_in}#{curve}"] else [] end else [] end args = if fade_out != "" then fade_out = float_of_string(default=0., fade_out) end_time = if cue_out != "" then float_of_string(default=0., cue_out) else null.get(request.duration(request.filename(r))) end if fade_out > 0. then list.append( args, ["afade=out:st=#{end_time - fade_out}:d=#{fade_out}#{curve}"] ) else args end else args end if list.length(args) > 0 then args = string.concat(separator=",", args) "-af #{args}" else "" end end r = request.create(arg) delay = maxtime - time() if request.resolve(timeout=delay, r) then filename = request.filename(r) m = request.metadata(r) m = if metadata then list.append(m, parse_metadata(filename)) else m end annotate = annotate(m) request.destroy(r) # Now parse the audio wav = file.temp("liq-process", ".wav") cue_points = cue_points(request.metadata(r)) fades = fades(r) replaygain_filter = replaygain_filter(filename) cmd = "#{ffmpeg} -y -i $(input) #{cue_points} #{fades} #{replaygain_filter} #{ process.quote(wav) }" uri = process.uri(extname="wav", uri=filename, cmd) wav_r = request.create(uri) delay = maxtime - time() if request.resolve(timeout=delay, wav_r) then request.destroy(wav_r) "#{annotate}tmp:#{wav}" else log( level=3, "Failed to resolve #{uri}" ) null end else log( level=3, "Failed to resolve #{arg}" ) null end end protocol.add( "ffmpeg", protocol.ffmpeg, doc="Decode any file to wave using ffmpeg", syntax="ffmpeg:uri" ) # Register stereo protocol which converts a file to stereo (currently decodes as # wav). # @flag hidden def protocol.stereo(~rlog:_, ~maxtime:_, arg) = file = file.temp("liq-stereo", ".wav") r = request.create(arg) if not request.resolve(r) then log.info( "Stereo: failed to resolve request #{arg}" ) null else request.dump(%wav, file, request.create(arg)) file end end protocol.add( static=fun (_) -> true, temporary=true, "stereo", protocol.stereo, doc="Convert a file to stereo (currently decodes to wav).", syntax="stereo:" ) # Copy # @flag hidden def protocol.copy(~rlog:_, ~maxtime:_, arg) = extname = file.extension(arg) tmpfile = file.temp("tmp", extname) file.copy(force=true, arg, tmpfile) tmpfile end protocol.add( static=fun (_) -> true, "copy", protocol.copy, doc="Copy file to a temporary destination", syntax="copy:/path/to/file.extname" ) # Text2wave let settings.protocol.text2wave = settings.make.protocol("text2wave") let settings.protocol.text2wave.path = settings.make( description="Path to the text2wave binary", "text2wave" ) # Register the text2wave: protocol using text2wave # @flag hidden def protocol.text2wave(~rlog:_, ~maxtime:_, arg) = binary = settings.protocol.text2wave.path() process.uri( extname="wav", "echo #{process.quote(arg)} | #{binary} -scale 1.9 > $(output)" ) end protocol.add( static=fun (_) -> true, "text2wave", protocol.text2wave, doc="Generate speech synthesis using text2wave. Result may be mono.", syntax="text2wave:Text to read" ) # Pico2wave let settings.protocol.pico2wave = settings.make.protocol("pico2wave") let settings.protocol.pico2wave.path = settings.make( description="Path to the pico2wave binary", "pico2wave" ) let settings.protocol.pico2wave.lang = settings.make( description="pico2wave language. One of: `\"en-US\"`, `\"en-GB\"`, \ `\"es-ES\"`, `\"de-DE\"`, `\"fr-FR\"` or `\"it-IT\"`.", "en-US" ) # @flag hidden def protocol.pico2wave(~rlog:_, ~maxtime:_, arg) = binary = settings.protocol.pico2wave.path() lang = settings.protocol.pico2wave.lang() process.uri( extname="wav", "#{binary} -l #{lang} -w $(output) #{process.quote(arg)}" ) end protocol.add( static=fun (_) -> true, "pico2wave", protocol.pico2wave, doc="Generate speech synthesis using pico2wave. Result may be mono.", syntax="pico2wave:Text to read" ) # GTTS let settings.protocol.gtts = settings.make.protocol("gtts") let settings.protocol.gtts.path = settings.make( description="Path to the gtts binary", "gtts-cli" ) let settings.protocol.gtts.lang = settings.make( description="Language to speak in.", "en" ) let settings.protocol.gtts.options = settings.make( description="Command line options.", "" ) # Register the gtts: protocol using gtts # @flag hidden def protocol.gtts(~rlog:_, ~maxtime:_, arg) = binary = settings.protocol.gtts.path() lang = settings.protocol.gtts.lang() options = settings.protocol.gtts.options() process.uri( extname="mp3", "#{binary} --lang #{lang} #{options} -o $(output) #{process.quote(arg)}" ) end protocol.add( static=fun (_) -> true, "gtts", protocol.gtts, doc="Generate speech synthesis using Google translate's text-to-speech API. \ This requires the `gtts-cli` binary. Result may be mono.", syntax="gtts:Text to read" ) # MacOS say let settings.protocol.macos_say = settings.make.protocol("macos_say") let settings.protocol.macos_say.path = settings.make( description="Path to the say binary", "say" ) let settings.protocol.macos_say.options = settings.make( description="Command line options.", "" ) # Register the macos_say: protocol using the say command available on macos # @flag hidden def protocol.macos_say(~rlog:_, ~maxtime:_, arg) = binary = settings.protocol.macos_say.path() options = settings.protocol.macos_say.options() process.uri( extname="aiff", "#{binary} #{options} -o $(output) #{process.quote(arg)}" ) end protocol.add( static=fun (_) -> true, "macos_say", protocol.macos_say, doc="Generate speech synthesis using the `say` command available on macos.", syntax="macos_say:Text to read" ) # Say let settings.protocol.say = settings.make.protocol("say") let settings.protocol.say.implementation = settings.make( description="Implementation to use. One of: \"pico2wave\", \"gtts\", \ \"text2wave\" or \"macos_say\".", liquidsoap.build_config.system == "macosx" ? "macos_say" : "pico2wave" ) # Register the legacy say: protocol # @flag hidden def protocol.say(~rlog:_, ~maxtime:_, arg) = "#{settings.protocol.say.implementation()}:#{arg}" end protocol.add( static=fun (_) -> true, "say", protocol.say, doc="Generate speech synthesis using text2wave. Result is always stereo.", syntax="say:Text to read" ) let settings.protocol.aws = settings.make.protocol("AWS") let settings.protocol.aws.profile = settings.make( description="Use a specific profile from your credential file.", null ) let settings.protocol.aws.endpoint = settings.make( description="Alternative endpoint URL (useful for other S3 \ implementations).", null ) let settings.protocol.aws.region = settings.make( description="AWS Region", null ) let settings.protocol.aws.path = settings.make( description="Path to aws CLI binary", "aws" ) let settings.protocol.aws.polly = settings.make.protocol("polly") let settings.protocol.aws.polly.format = settings.make( description="Output format", "mp3" ) let settings.protocol.aws.polly.voice = settings.make( description="Voice ID", "Joanna" ) let settings.protocol.aws.polly.extra_args = settings.make( description="Extra command line arguments", ([] : [string]) ) # Build a aws base call # @flag hidden def aws_base() = aws = settings.protocol.aws.path() region = settings.protocol.aws.region() aws = if null.defined(region) then "#{aws} --region #{null.get(region)}" else aws end endpoint = settings.protocol.aws.endpoint() aws = if null.defined(endpoint) then "#{aws} --endpoint-url #{process.quote(null.get(endpoint))}" else aws end profile = settings.protocol.aws.profile() if null.defined(profile) then "#{aws} --profile #{process.quote(null.get(profile))}" else aws end end # Register the s3:// protocol # @flag hidden def s3_protocol(~rlog:_, ~maxtime:_, arg) = extname = file.extension(leading_dot=false, dir_sep="/", arg) arg = process.quote("s3:#{arg}") process.uri( extname=extname, "#{aws_base()} s3 cp #{arg} $(output)" ) end protocol.add( "s3", s3_protocol, doc="Fetch files from s3 using the AWS CLI", syntax="s3://uri" ) # Register the polly: protocol using AWS Polly # speech synthesis services. Syntax: polly: # @flag hidden def polly_protocol(~rlog:_, ~maxtime:_, text) = aws = aws_base() format = settings.protocol.aws.polly.format() extname = if format == "mp3" then "mp3" elsif format == "ogg_vorbis" then "ogg" else "wav" end aws = "#{aws} polly synthesize-speech --output-format #{format}" voice_id = settings.protocol.aws.polly.voice() extra_args = string.concat( separator=" ", settings.protocol.aws.polly.extra_args() ) cmd = "#{aws} --text #{process.quote(text)} --voice-id #{ process.quote(voice_id) } #{extra_args} $(output)" process.uri(extname=extname, cmd) end protocol.add( static=fun (_) -> true, "polly", polly_protocol, doc="Generate speech synthesis using AWS polly service. Result might be mono, \ needs aws binary in the path.", syntax="polly:Text to read" ) # Protocol to synthesize audio. # @flag hidden def synth_protocol(~rlog:_, ~maxtime:_, text) = log.debug( label="synth", "Synthesizing request: #{text}" ) args = r/,/.split(text) args = list.map(r/=/.split, args) if list.exists(fun (l) -> list.length(l) != 2, args) then null else args = list.map( fun (l) -> (list.hd(default="", l), list.hd(default="", list.tl(l))), args ) shape = ref("sine") duration = ref(10.) frequency = ref(440.) def set(p) = let (k, v) = p if k == "d" or k == "duration" then duration := float_of_string(v) elsif k == "f" or k == "freq" or k == "frequency" then frequency := float_of_string(v) elsif k == "s" or k == "shape" then shape := v end end list.iter(set, args) def synth(s) = file = file.temp("liq-synth", ".wav") log.info( label="synth", "Synthesizing #{shape()} in #{file}." ) clock.assign_new(sync="passive", [s]) stopped = ref(false) o = output.file(fallible=true, %wav, file, once(s)) o.on_stop(synchronous=true, {stopped.set(true)}) c = clock(s.clock) c.start() while not stopped() do c.tick() end c.stop() file end if shape() == "sine" then synth(sine(duration=duration(), frequency())) elsif shape() == "saw" then synth(saw(duration=duration(), frequency())) elsif shape() == "square" then synth(square(duration=duration(), frequency())) elsif shape() == "blank" then synth(blank(duration=duration())) else null end end end protocol.add( static=fun (_) -> true, temporary=true, "synth", synth_protocol, doc="Synthesize audio. Parameters are optional.", syntax="synth:shape=sine,frequency=440.,duration=10." ) # File protocol # @flag hidden def file_protocol(~rlog:_, ~maxtime:_, arg) = if not r/^file:/.test(arg) then null else url.decode(r/^file:(?:\/\/)?/.replace(fun (_) -> "", arg)) end end protocol.add( static=fun (_) -> true, temporary=false, "file", file_protocol, doc="File protocol. Only local files are supported", syntax="file:///path/to/file" ) liquidsoap-2.4.2/src/libs/ref.liq000066400000000000000000000013271513273233300167010ustar00rootroot00000000000000# Create a reference from a pair of get / set functions. # @category Programming # @param get Function to retrieve the value of the reference. # @param set Function to change the value of the reference. def ref.make(get, set) = (get.{set=set} : ref) end # Create a getter from a reference (sometimes useful to remove the `set` # method). # @category Programming def ref.getter((r:ref)) = {r()} end # Map functions to a reference. # @category Programming # @param g Function to apply to the getter. # @param s Function to apply to the setter. def ref.map(g, s, (r:ref)) = ref.make({g(r())}, fun (x) -> r.set(s(x))) end # Increment a reference to an integer. # @category Programming def ref.incr(r) = r := r() + 1 end liquidsoap-2.4.2/src/libs/replaygain.liq000066400000000000000000000072731513273233300202660ustar00rootroot00000000000000let file.replaygain = () # Compute the ReplayGain for a file (in dB). # @category File # @param ~id Force the value of the source ID. # @param ~ratio Decoding ratio. A value of `50` means try to decode the file `50x` faster than real time, if possible. # Use this setting to lower CPU peaks when computing replaygain tags. # @param file_name File name. # @flag hidden def file.replaygain.compute(~ratio=50., file_name) = _request = request.create(resolve_metadata=false, file_name) if request.resolve(_request) then get_gain = ref(fun () -> null) def process(s) = s = source.replaygain.compute(s) get_gain := {s.gain()} s end request.process(ratio=ratio, process=process, _request) fn = get_gain() fn() else null end end # Extract the ReplayGain from the metadata (in dB). # @category Metadata # @param _metadata Metadata from which the ReplayGain should be extracted. def metadata.replaygain(_metadata) = if list.assoc.mem("r128_track_gain", _metadata) then try float_of_string(_metadata["r128_track_gain"]) / 256. catch _ do null end elsif list.assoc.mem("replaygain_track_gain", _metadata) then replaygain_metadata = _metadata["replaygain_track_gain"] match = r/([+-]?\d*\.?\d*)/.exec(replaygain_metadata) try float_of_string(list.assoc(1, match)) catch _ do null end else null end end # Get the ReplayGain for a file (in dB). # @category File # @param ~id Force the value of the source ID. # @param ~compute Compute ReplayGain if metadata tag is empty. # @param ~ratio Decoding ratio. A value of `50` means try to decode the file `50x` faster than real time, if possible. # Use this setting to lower CPU peaks when computing replaygain tags. # @param file_name File name. def replaces file.replaygain(~id=null, ~compute=true, ~ratio=50., file_name) = id = string.id.default(default="file.replaygain", id) file_name_quoted = string.quote(file_name) _metadata = file.metadata(exclude=decoder.metadata.reentrant(), file_name) gain = metadata.replaygain(_metadata) if gain != null then log.info( label=id, "Detected track replaygain #{gain} dB for #{file_name_quoted}." ) gain elsif compute then log.info( label=id, "Computing replay gain for #{file_name_quoted}." ) start_time = time() gain = file.replaygain.compute(ratio=ratio, file_name) elapsed_time = time() - start_time if gain != null then log.info( label=id, "Computed replay gain #{gain} dB for #{file_name_quoted} (time: #{ elapsed_time } s)." ) end gain else null end end # Enable ReplayGain metadata resolver. This resolver will process any file # decoded by Liquidsoap and add a `replaygain_track_gain` metadata when this # value could be computed. For a finer-grained replay gain processing, use the # `replaygain:` protocol. # @param ~compute Compute replaygain if metadata tag is empty. # @param ~ratio Decoding ratio. A value of `50.` means try to decode the file `50x` faster than real time, if possible. Use this setting to lower CPU peaks when computing replaygain tags. # @category Liquidsoap def enable_replaygain_metadata(~compute=true, ~ratio=50.) = def replaygain_metadata(~metadata:_, file_name) = gain = file.replaygain(compute=compute, ratio=ratio, file_name) if gain != null then [ ( settings.normalize_track_gain_metadata(), "#{null.get(gain)} dB" ) ] else [] end end decoder.metadata.add( reentrant=true, "replaygain_track_gain", replaygain_metadata ) end liquidsoap-2.4.2/src/libs/request.liq000066400000000000000000000240031513273233300176110ustar00rootroot00000000000000%ifdef native let stdlib_native = native %endif # @docof request.dynamic # @param ~native Use native implementation, when available. def replaces request.dynamic(%argsof(request.dynamic), ~native=false, f) = ignore(native) default = request.dynamic(%argsof(request.dynamic), f) %ifdef native if native then stdlib_native.request.dynamic(%argsof(request.dynamic), f) else default end %else default %endif end # Play a queue of requests (the first added request gets played first). # @category Source / Track processing # @param ~id Force the value of the source ID. # @param ~interactive Should the queue be controllable via telnet? # @param ~prefetch How many requests should be queued in advance. # @param ~native Use native implementation, when available. # @param ~queue Initial queue of requests. # @param ~timeout Timeout (in sec.) to resolve the request. # @method add This method is internal and should not be used. Consider using `push` instead. # @method push Push a request on the request queue. # @method length Length of the queue. def request.queue( ~id=null, ~interactive=true, ~prefetch=null, ~native=false, ~queue=[], ~timeout=null ) = ignore(native) id = string.id.default(default="request.queue", id) initial_queue = ref(queue) queue = ref([]) fetch = ref(fun () -> ()) started = ref(false) def next() = if queue() != [] then let [r, ...q] = queue() queue := q (r : request) else null end end def push(r) = if started() then log.info( label=id, "Pushing #{r} on the queue." ) queue := [...queue(), r] fn = fetch() fn() else log.info( label=id, "Pushing #{r} on the initial queue." ) initial_queue := [...initial_queue(), r] end end def push_uri(uri) = r = request.create(uri) push(r) end default = fun () -> request.dynamic( id=id, prefetch=prefetch, timeout=timeout, available={not list.is_empty(queue())}, next ) s = %ifdef native if native then stdlib_native.request.dynamic(id=id, timeout=timeout, next) else default() end %else default() %endif def add(r) = log.severe( label=s.id(), "Please use #{s.id()}.push instead of #{s.id()}.add()" ) s.add(r) end def set_queue(q) = if started() then queue := q else initial_queue := q end s.set_queue([]) end def get_queue() = [...s.queue(), ...initial_queue(), ...queue()] end s = s.{ add=add, push=push.{uri=push_uri}, length={list.length(queue()) + list.length(s.queue())}, set_queue=set_queue, queue=get_queue } s.on_wake_up( synchronous=true, fun () -> begin started := true s.set_queue(initial_queue()) initial_queue := [] end ) fetch := s.fetch if interactive then def push(uri) = r = request.create(uri) push(r) "#{request.id(r)}" end s.register_command( description="Flush the queue and skip the current track", "flush_and_skip", fun (_) -> try s.set_queue([]) s.skip() "Done." catch err do "Error while flushing and skipping source: #{err}" end ) s.register_command( description="Push a new request in the queue.", usage="push ", "push", push ) def show_queue(_) = queue = s.queue() string.concat( separator=" ", list.map(fun (r) -> string(request.id(r)), queue) ) end s.register_command( description="Display current queue content.", usage="queue", "queue", show_queue ) def skip(_) = s.skip() "Done." end s.register_command( description="Skip current song.", usage="skip", "skip", skip ) end s end # Play a request once and become unavailable. # @category Source / Input # @param ~timeout Timeout in seconds for resolving the request. # @param r Request to play. def request.once(~id=null("request.once"), ~timeout=null, r) = id = string.id.default(default="request.once", id) done = ref(false) fail = fallback([]) def next() = if done() then null else done := true if request.resolve(r, timeout=timeout) then request.queue(queue=[r]) else log.critical( label=id, "Failed to prepare track: request not ready." ) request.destroy(r) fail end end end s = source.dynamic(self_sync=false, track_sensitive=true, next) (s : source_methods).{ resolve=fun () -> request.resolve(r, timeout=timeout), request=r } end # Loop on a request. It never fails if the request is static, meaning # that it can be fetched once. Typically, http, ftp, say requests are # static, and time is not. # @category Source / Input # @param ~prefetch How many requests should be queued in advance. # @param ~timeout Timeout (in sec.) to resolve the request. # @param ~fallible Enforce fallibility of the request. # @param r Request def request.single( ~id=null("request.single"), ~prefetch=null, ~timeout=null, ~fallible=null, r ) = id = string.id.default(default="single", id) fallible = fallible ?? not getter.is_constant(r) infallible = if not fallible then if not getter.is_constant(r) then error.raise( error.invalid, "Infallible sources cannot change their underlying file." ) end initial_request = getter.get(r) uri = request.uri(initial_request) request.is_static(uri) else false end if not fallible and not infallible then log.severe( label=id, "Source was marked a infallible but its request is not a static file. The \ source is considered fallible for backward compatibility but this will \ fail in future versions!" ) end static_request = ref(null) def on_wake_up(s) = if infallible then initial_request = getter.get(r) uri = request.uri(initial_request) log.important( label=id, "#{uri} is static, resolving once for all..." ) if not request.resolve(initial_request, timeout=timeout, content_type=s) then request.destroy(initial_request) error.raise( error.invalid, "Could not resolve uri: #{uri}" ) else static_request := initial_request end end end def on_shutdown() = if null.defined(static_request()) then request.destroy(null.get(static_request())) end static_request := null end def next() = static_request() ?? getter.get(r) end def mk_source(id) = request.dynamic(id=id, prefetch=prefetch, synchronous=infallible, next) end # We want to mark infallible source as such. `source.dynamic` is a nice # way to do it as it will raise a user-friendly error in case the underlying # source does not respect the conditions for being infallible. s = if infallible then s = mk_source("#{id}.actual") source.dynamic( id=id, infallible=infallible, self_sync=false, track_sensitive=true, {s} ) else mk_source(id) end s.on_wake_up(synchronous=true, fun () -> on_wake_up(s)) s.on_shutdown(synchronous=true, on_shutdown) (s : source_methods) end # Loop on a URI. It never fails if the request is static, meaning # that it can be fetched once. Typically, http, ftp, say requests are # static, and time is not. # @category Source / Input # @param ~prefetch How many requests should be queued in advance. # @param ~timeout Timeout (in sec.) to resolve the request. # @param ~fallible Enforce fallibility of the request. # @param ~cue_in_metadata Metadata for cue in points. Disabled if `null`. # @param ~cue_out_metadata Metadata for cue out points. Disabled if `null`. # @param uri URI where to find the file def single( %argsof(request.single[!id,!fallible]), ~id=null("single"), ~fallible=false, ~cue_in_metadata=null("liq_cue_in"), ~cue_out_metadata=null("liq_cue_out"), uri ) = r = if fallible then getter( { request.create( cue_in_metadata=cue_in_metadata, cue_out_metadata=cue_out_metadata, uri ) } ) else request.create( persistent=true, cue_in_metadata=cue_in_metadata, cue_out_metadata=cue_out_metadata, uri ) end request.single(%argsof(request.single), r) end # Create a source on which plays immediately requests given with the `play` # method. # @category Source / Track processing # @param ~simultaneous Allow multiple requests to play simultaneously. If `false` a new request replaces the previous one. # @method play Play a request. # @method length Number of currently playing requests. def request.player(~simultaneous=true) = if simultaneous then l = ref([]) s = ref(add(normalize=false, l())) # Perform some garbage collection in order to avoid that the list grows too # much. def collect(~reload_source) = len = list.length(l()) l := list.filter(source.is_ready, l()) if reload_source and list.length(l()) != len then s := add(normalize=false, l()) end end def play(r) = collect(reload_source=false) l := [request.once(r), ...l()] s := add(normalize=false, l()) end source.dynamic(s).{ play=play, length= { collect(reload_source=true) list.length(l()) } } else next_source = ref(null) def next() = s = next_source() next_source := null s end s = source.dynamic(next) def play(r) = r = request.once(r) r = s.prepare(r) next_source := r end s.{play=play, length={1}} end end liquidsoap-2.4.2/src/libs/resolvers.liq000066400000000000000000000011751513273233300201520ustar00rootroot00000000000000# @flag hidden def youtube_playlist_parser(~pwd="", url) = ignore(pwd) binary = null.get(settings.protocol.youtube_dl.path()) def parse_line(line) = let json.parse (parsed : {url: string}?) = line parsed = parsed ?? {url="foo"} url = parsed.url ([], "youtube-dl:#{url}") end if r/^youtube-pl/.test(url) then uri = list.nth(default="", r/:/.split(url), 1) list.map( parse_line, process.read.lines( "#{binary} -j --flat-playlist #{uri}" ) ) else [] end end playlist.parse.register( name="youtube-dl", mimes=[], strict=true, youtube_playlist_parser ) liquidsoap-2.4.2/src/libs/runtime.liq000066400000000000000000000035501513273233300176100ustar00rootroot00000000000000# @docof runtime.memory def replaces runtime.memory() = x = { ...runtime.memory(), process_managed_memory= runtime.gc.quick_stat().heap_words * runtime.sys.word_size / 8 } let x.pretty = { process_managed_memory= runtime.memory.prettify_bytes(x.process_managed_memory), total_virtual_memory= runtime.memory.prettify_bytes(x.total_virtual_memory), total_physical_memory= runtime.memory.prettify_bytes(x.total_physical_memory), total_used_virtual_memory= runtime.memory.prettify_bytes(x.total_used_virtual_memory), total_used_physical_memory= runtime.memory.prettify_bytes(x.total_used_physical_memory), process_virtual_memory= runtime.memory.prettify_bytes(x.process_virtual_memory), process_physical_memory= runtime.memory.prettify_bytes(x.process_physical_memory), process_private_memory= runtime.memory.prettify_bytes(x.process_private_memory), process_swapped_memory= runtime.memory.prettify_bytes(x.process_swapped_memory) } x end let runtime.cpu = () %ifdef process.time # Create a function returning CPU usage (in `float` percent so # `0.2` means `20%`) since the last time it was called. # @category Liquidsoap def runtime.cpu.usage_getter() = t = ref(time()) let {user, system} = process.time() u = ref(user) s = ref(system) def f() = t' = time() delta = t' - t() let {user, system} = process.time() u' = user - u() s' = system - s() t := t' u := user s := system {user=u' / delta, system=s' / delta, total=(u' + s') / delta} end f end %endif # Set the current time zone. This is # equivalent to setting the `TZ` environment # variable. # @category Time def time.zone.set(tz) = environment.set("TZ", tz) end runtime.gc.set(runtime.gc.get().{space_overhead=80}) liquidsoap-2.4.2/src/libs/server.liq000066400000000000000000000025761513273233300174420ustar00rootroot00000000000000# Enable telnet server. # @category Interaction # @param ~port Port on which we should listen. def server.telnet(~port=1234) = settings.server.telnet.port := port settings.server.telnet := true end server.register( namespace="runtime.gc", description="Run a full memory collection", "full_major", fun (_) -> begin runtime.gc.full_major() "Done!" end ) server.register( namespace="runtime.gc", description="Compact OCaml memory", "compact", fun (_) -> begin runtime.gc.compact() "Done!" end ) server.register( namespace="runtime", description="Return a description of the memory used by the process", "memory", fun (_) -> begin let { process_managed_memory, process_physical_memory, process_private_memory, process_swapped_memory } = runtime.memory().pretty "Physical memory: #{process_physical_memory}\nPrivate memory: #{ process_private_memory }\nManaged memory: #{process_managed_memory}\nSwapped memory: #{ process_swapped_memory }" end ) server.register( namespace="clocks", description="Dump a description of the current clocks and sources", "dump", fun (_) -> clock.dump() ) server.register( namespace="clocks", description="Dump the source graph for all active clocks", "dump_sources", fun (_) -> clock.dump_all_sources() ) liquidsoap-2.4.2/src/libs/settings.liq000066400000000000000000000021501513273233300177600ustar00rootroot00000000000000# Instantiate a new setting. # @category Settings # @flag hidden def settings.make(~comments="", ~(description:string), v) = current_value = ref(v) current_value.{description=description, comments=comments} end # Instantiate a new empty setting. # @category Settings # @flag hidden def settings.make.void(~comments="", (description:string)) = {description=description, comments=comments} end let frame = () # Duration of a frame. # @category Settings def frame.duration = settings.frame.duration end let settings.init.compact_before_start = settings.make( description="Run the OCaml memory compaction algorithm before starting your \ script. This is useful when script caching is not possible but initial \ memory consumption is a concern. This will result in a large chunk of \ memory being freed right before starting the script. This also increases \ the script's initial startup time.", true ) # Top-level init module for convenience. # @category Settings # @flag hidden init = settings.init on_start( {if settings.init.compact_before_start() then runtime.gc.compact() end} ) liquidsoap-2.4.2/src/libs/socket.liq000066400000000000000000000015241513273233300174140ustar00rootroot00000000000000# Open a named UNIX socket and connect as a client. # @param ~non_blocking Open in non-blocking mode. # @category File def socket.unix.client(~non_blocking=false, path) = s = socket.unix(domain=socket.domain.unix) s.non_blocking(non_blocking) s.connect(socket.address.unix(path)) (s : socket).{read=s.read, write=s.write, type=s.type, close=s.close} end # Open a named socket and wait for a client to connect # @param ~non_blocking Open in non-blocking mode. # @category File def socket.unix.listen(~non_blocking=false, path) = s = socket.unix(domain=socket.domain.unix) s.non_blocking(non_blocking) s.bind(socket.address.unix(path)) s.listen(1) let (s', _) = s.accept() close = fun () -> begin s'.close() s.close() end (s' : socket).{read=s'.read, write=s'.write, type=s'.type, close=close} end liquidsoap-2.4.2/src/libs/source.liq000066400000000000000000000162471513273233300174340ustar00rootroot00000000000000# Create an audio source from the given track, with # metadata and track marks from that same track. # @category Source / Track processing def source.audio(~id=null("source.audio"), audio) = source( id=id, { audio=audio, metadata=track.metadata(audio), track_marks=track.track_marks(audio) } ) end # Create a video source from the given track, with # metadata and track marks from that same track. # @category Source / Track processing def source.video(~id=null("source.video"), video) = source( id=id, { video=video, metadata=track.metadata(video), track_marks=track.track_marks(video) } ) end # Remove duplicate metadata in a track. # @param ~using Labels to use to compare the metadata. Defaults to all of them \ # when `null`. # @category Metadata def track.metadata.deduplicate( ~id=null("track.metadata.deduplicate"), ~using=null, t ) = last_meta = ref([]) def f(m) = m = if null.defined(using) then using = null.get(using) list.filter(fun (x) -> list.mem(fst(x), using), m) else m end if m == last_meta() then [] else last_meta := m m end end track.metadata.map( id=id, insert_missing=false, update=false, strip=true, f, t ) end # Remove duplicate metadata in a source. # @category Metadata # @param ~using Labels to use to compare the metadata. Defaults to all of them \ # when `null`. # @param ~id Source id # @param s source def metadata.deduplicate(~id=null("metadata.deduplicate"), ~using=null, s) = tracks = source.tracks(s) source( id=id, tracks.{metadata=track.metadata.deduplicate(using=using, tracks.metadata)} ) end # Rewrite metadata on the fly using a function. # @category Source / Track processing # @argsof track.metadata.map def metadata.map( ~id=null("metadata.map"), %argsof(track.metadata.map[!id]), f, (s:source) ) = tracks = source.tracks(s) source( id=id, tracks.{ metadata= track.metadata.map(%argsof(track.metadata.map), f, tracks.metadata) } ) end # Turn a source into an infaillible source by adding blank when the source is # not available. # @param s the source to turn infaillible # @category Source / Track processing def mksafe(~id="mksafe", s) = fallback(id=id, track_sensitive=false, [(s : source), blank(id="safe_blank")]) end # Creates a source that plays only one track of the input source. # @category Source / Track processing # @param s The input source. def once(~id=null("once"), s) = sequence(id=id, [(s : source), source.fail()]) end # Skip track when detecting a blank. # @category Source / Track processing # @param ~id Force the value of the source ID. # @param ~threshold Power in decibels under which the stream is considered silent. # @param ~max_blank Maximum silence length allowed, in seconds. # @param ~min_noise Minimum duration of noise required to end silence, in seconds. # @param ~track_sensitive Reset blank counter at each track. def blank.skip( ~id=null("blank.skip"), ~threshold=-40., ~max_blank=20., ~min_noise=0., ~track_sensitive=true, s ) = s' = blank.detect( id=id, threshold=threshold, max_blank=max_blank, min_noise=min_noise, track_sensitive=track_sensitive, s ) s'.on_blank(synchronous=true, source.methods(s).skip) s' end # Run a function regularly. This is similar to `thread.run` but based on a # source internal time instead of the computer's time. # @category Source / Track processing # @param s Source whose time is taken as reference. # @param ~delay Time to wait before the first run (in seconds). # @param ~every How often to run the function (in seconds). The function is run once if `null`. # @param f Function to run. def source.run(s, ~delay=0., ~every=null, f) = next = ref(delay) def check() = if source.time(s) >= next() then null.case( every, {next := infinity}, fun (every) -> next := next() + every ) f() end end source.methods(s).on_frame(synchronous=true, check) s end # Append an extra track to every track. Set the metadata ‘liq_append’ to # `false` to inhibit appending on one track. # @category Source / Track processing # @param f Given the metadata, build the source producing the track to append. The function can return `null` to prevent appending a new track. # @param ~insert_missing Treat track beginnings without metadata as having empty one. # @param ~merge Merge the track with its appended track. # @flag extra def append(~id=null("append"), ~insert_missing=true, ~merge=false, s, f) = last_meta = ref(null) pending = ref(null) def next() = p = pending() pending := null last_meta := null if null.defined(p) then null.get(p) else (s : source) end end d = source.dynamic( track_sensitive=true, merge={merge and null.defined(pending)}, next ) def f(m) = if d.current_source() == (s : source) then if m["liq_append"] == "false" then last_meta := null pending := null elsif last_meta() != m then last_meta := m s = f(m) s = d.prepare(s) pending := s end end end if insert_missing then d.on_track(synchronous=true, f) end d.on_metadata(synchronous=true, f) s = fallback(id=id, track_sensitive=false, [d, s]) s.{ pending= # Return the pending source fun () -> pending(), set_pending= # Set the pending source fun (s) -> pending.set(s), cancel_pending= # Cancel any pending appended source. fun () -> pending.set(null), skip= # Skip the current track. Pending appended source are cancelled by default. Pass `cancel_pending=false` to keep it. fun (~cancel_pending=true) -> begin if cancel_pending then pending.set(null) end s.skip() end } end # Prepend an extra track before every track. Set the metadata `liq_prepend` to # `false` to inhibit prepending on one track. # @category Source / Track processing # @param f Given the metadata, build the source producing the track to append. The function can return `null` to prevent any track prepend. # @param ~merge Merge the track with its appended track. # @flag extra def prepend(~id=null("prepend"), ~merge=false, s, f) = last_meta = ref(null) def on_meta(m) = if m["liq_prepend"] == "false" then last_meta := null elsif last_meta() != m then last_meta := m end end s.on_track( synchronous=true, fun (m) -> m == [] ? last_meta := null : on_meta(m) ) s.on_metadata(synchronous=true, on_meta) def next() = if null.defined(last_meta) then m = last_meta() last_meta := null if null.defined(m) then m = null.get(m) p = (f(m) : source?) if null.defined(p) then sequence(merge=merge, [null.get(p), s]) else s end else s end else s end end d = source.dynamic(track_sensitive=true, next) fallback(id=id, track_sensitive=true, [d, s]) end liquidsoap-2.4.2/src/libs/sqlite.liq000066400000000000000000000057551513273233300174370ustar00rootroot00000000000000%ifdef sqlite # @docof sqlite def replaces sqlite(file) = db = sqlite(file) # Insert a list of key / value pairs, where all the values are # strings. def db.insert.list(~table, entries) = let (columns, values) = list.fold( fun (result, entry) -> begin let (columns, values) = result let (column, value) = entry ([...columns, column], [...values, sqlite.escape(value)]) end, ([], []), entries ) columns = string.concat( separator=", ", columns ) values = string.concat( separator=", ", values ) sql = "INSERT INTO #{sqlite.escape(table)} (#{columns}) VALUES (#{values})" db.exec(sql) end def db.select(~table, what="*", ~where="", ~limit=null) = where = if where == "" then "" else " WHERE #{where}" end limit = if null.defined(limit) then " LIMIT #{null.get(limit)}" else "" end db.query( "SELECT #{what} FROM #{table}#{where}#{limit}" ) end def db.select.iter(f, ~table, what="*", ~where="") = where = if where == "" then "" else " WHERE #{where}" end db.iter( f, "SELECT #{what} FROM #{table}#{where}" ) end def db.delete(~table, ~where="") = where = if where == "" then "" else " WHERE #{where}" end db.exec( "DELETE FROM #{table}#{where}" ) end # Count the number of items matching a condition. def db.count(~table, ~where="") = where = if where == "" then "" else " WHERE " ^ where end let sqlite.query ([{count}] : [{count: int}]) = db.query( "SELECT COUNT(*) AS count FROM #{table}#{where}" ) count end # Operations on tables. let db.table = () def db.table.drop(table, ~existing=true) = existing = if existing then " IF EXISTS" else "" end db.exec( "DROP TABLE#{existing} #{table}" ) end # Create a table with given fields. # @param ~preserve Preserve previous table if one already exists under the same name. def db.table.create(table, ~preserve=false, fields) = preserve = if preserve then " IF NOT EXISTS" else "" end fields = string.concat( separator=", ", list.map( fun (lt) -> "#{fst(lt)} #{snd(lt)}", fields ) ) db.exec( "CREATE TABLE#{preserve} #{table} (#{fields})" ) end # Check whether a table exists. def db.table.exists(name) = let sqlite.query ([{name}] : [{name: string}]) = db.query( "SELECT name FROM sqlite_master WHERE type='table' AND name=#{ sqlite.escape(name) }" ) name end db end %endif liquidsoap-2.4.2/src/libs/stdlib.liq000066400000000000000000000030661513273233300174100ustar00rootroot00000000000000%include "error.liq" %include "null.liq" %include "ref.liq" %include "list.liq" %include "math.liq" %include "getter.liq" %include "predicate.liq" %include "settings.liq" %include "log.liq" %include "thread.liq" %include "process.liq" %include "string.liq" %include "socket.liq" %include "cron.liq" %include "file.liq" %include "switches.liq" %include "utils.liq" %include "clock.liq" %include "metadata.liq" %include "playlist.liq" %include "source.liq" %include "tracks.liq" %include "request.liq" %include "audio.liq" %include "fades.liq" %include "autocue.liq" %include "replaygain.liq" %include "lufs.liq" %include "http_codes.liq" %include "http.liq" %include "protocols.liq" %include "resolvers.liq" %include "icecast.liq" %include "hls.liq" %include "video.liq" %include "ffmpeg.liq" %include "profiler.liq" %include "io.liq" %include "sqlite.liq" %include "medialib.liq" %include "runtime.liq" %include "server.liq" %include "sqlite.liq" %include "testing.liq" %include "liquidsoap.liq" %include_extra "extra/file.liq" %include_extra "extra/native.liq" %include_extra "extra/audio.liq" %include_extra "extra/source.liq" %include_extra "extra/http.liq" %include_extra "extra/externals.liq" %include_extra "extra/audioscrobbler.liq" %include_extra "extra/server.liq" %include_extra "extra/telnet.liq" %include_extra "extra/interactive.liq" %include_extra "extra/visualization.liq" %include_extra "extra/openai.liq" %include_extra "extra/spinitron.liq" %include_extra "extra/metadata.liq" %include_extra "extra/fades.liq" %include_extra "extra/video.liq" set_settings_ref(settings) liquidsoap-2.4.2/src/libs/string.liq000066400000000000000000000270701513273233300174360ustar00rootroot00000000000000# Match a string with an expression. Perl compatible regular expressions are # recognized. Hence, special characters should be escaped. Alternatively, one # can use the the `r/_/.test(_)` syntax for regular expressions. # @category String def string.match(~pattern, s) = regexp(pattern).test(s) end # Extract substrings from a string. Perl compatible regular expressions are # recognized. Hence, special characters should be escaped. Returns a list of # (index,value). If the list does not have a pair associated to some index, it # means that the corresponding pattern was not found. Alter natively, one can # use the `r/_/.exec(_)` syntax for regular expressions. # @category String def string.extract(~pattern, s) = regexp(pattern).exec(s) end # Replace all substrings matched by a pattern by another string returned by a # function. Alternatively, one can use the `r/_/g.replace(_)` syntax for regular # expressions. # @category String # @param ~pattern Pattern (regular expression) of substrings which should be replaced. # @param f Function getting a matched substring an returning the string to replace it with. # @param s String whose substrings should be replaced. def string.replace(~pattern, f, s) = regexp(flags=["g"], pattern).replace(f, s) end # Split a string at "separator". Perl compatible regular expressions are # recognized. Hence, special characters should be escaped. Alternatively, one # can use the `r/_/.split(_)` syntax for regular expressions. # @category String def string.split(~separator, s) = regexp(separator).split(s) end # Return an array of the string's bytes. # @category String def string.bytes(s) = string.split(separator="", s) end # Return the length of the string in bytes. # @category String def string.bytes.length(s) = string.length(encoding="ascii", s) end # Split a string in two at first "separator". # @category String def string.split.first(~encoding=null, ~separator, s) = n = string.length(encoding=encoding, s) l = string.length(encoding=encoding, separator) i = string.index(substring=separator, s) if i < 0 then error.raise( error.not_found, "String does not contain the separator." ) end ( string.sub(encoding=encoding, s, start=0, length=i), string.sub(encoding=encoding, s, start=i + l, length=n - (i + l)) ) end # Test whether a string contains a given prefix, substring or suffix. # @category String # @param ~encoding Encoding used to split characters. Should be one of: `"utf8"` or `"ascii"` # @param ~prefix Prefix to look for. # @param ~substring Substring to look for. # @param ~suffix Suffix to look for. # @param s The string to look into. def string.contains(~encoding=null, ~prefix="", ~substring="", ~suffix="", s) = ans = ref(prefix == "" and substring == "" and suffix == "") if prefix != "" then ans := ans() or string.sub( encoding=encoding, s, start=0, length=string.length(encoding=encoding, prefix) ) == prefix end if suffix != "" then suflen = string.length(encoding=encoding, suffix) ans := ans() or string.sub( encoding=encoding, s, start=string.length(encoding=encoding, s) - suflen, length=suflen ) == suffix end if substring != "" then sublen = string.length(encoding=encoding, substring) for i = 0 to string.length(encoding=encoding, s) - sublen do ans := ans() or (string.sub(encoding=encoding, s, start=i, length=sublen) == substring) end end ans() end # What remains of a string after a given prefix. # @category String # @param ~encoding Encoding used to split characters. Should be one of: `"utf8"` or `"ascii"` # @param ~prefix Requested prefix. def string.residual(~encoding=null, ~prefix, s) = n = string.length(encoding=encoding, prefix) if string.contains(encoding=encoding, prefix=prefix, s) then string.sub( encoding=encoding, s, start=n, length=string.length(encoding=encoding, s) - n ) else null end end # Test whether a string is a valid integer. # @category String def string.is_int(s) = try ignore(int_of_string(s)) true catch _ do false end end # Convert a string to a int. # @category String def string.to_int(~default=0, s) = int_of_string(default=default, s) end # Convert an int to string. # @category String # @param ~digits Minimal number of digits (pad with 0s on the left if necessary). def string.of_int(~digits=0, n) = s = string(n) if string.length(s) >= digits then s else string.make(char_code=48, digits - string.bytes.length(s)) ^ s end end # Convert a string to a float. # @category String def string.to_float(~default=0., s) = float_of_string(default=default, s) end let string.binary = () # Value of a positive (unsigned) integer encoded using native memory # representation. # @category String # @param ~little_endian Whether the memory representation is little endian. # @param s String containing the binary representation. def string.binary.to_int(~little_endian=true, s) = ans = ref(0) n = string.bytes.length(s) for i = 0 to n - 1 do ans := lsl(ans(), 8) + string.nth(s, if little_endian then n - 1 - i else i end) end ans() end # Encode a positive (unsigned) integer using native memory representation. # @category String # @param ~pad Minimum length in digits (pad on the left with zeros in order to reach it) # @param ~little_endian Whether the memory representation is little endian. # @param s String containing the binary representation. def string.binary.of_int(~pad=0, ~little_endian=true, d) = def rec f(d, s) = if d > 0 then c = string.hex_of_int(pad=2, (d mod 256)) if little_endian then f(lsr(d, 8), "#{s}\\x#{c}") else f(lsr(d, 8), "\\x#{c}#{s}") end else s end end ret = d == 0 ? "\\x00" : f(d, "") ret = string.unescape(ret) len = string.bytes.length(ret) if len < pad then ans = string.make(char_code=0, pad - len) if little_endian then "#{ret}#{ans}" else "#{ans}#{ret}" end else ret end end # Add a null character at the end of a string. # @category String # @param s String. def string.null_terminated(s) = s ^ "\000" end # Generate an identifier if no identifier was provided. # @category String # @param ~default Name from which identifier is generated if not present. # @param id Proposed identifier. def string.id.default(~default, id) = null.default(id, {string.id(default)}) end # Return a quoted copy of the given string. # By default, the string is assumed to be `"utf8"` encoded and is escaped # following JSON and javascript specification. # @category String # @param ~encoding One of: `"ascii"` or `"utf8"`. If `null`, `utf8` is tried first and `ascii` is used as a fallback if this fails. def string.quote(~encoding=null, s) = def special_char(~encoding, s) = if s == "'" then false else string.escape.special_char(encoding=encoding, s) end end s = string.escape(special_char=special_char, encoding=encoding, s) "\"#{s}\"" end # Return an unquoted copy of the given string. # Quotes are removed by trying to parse the string # following the JSON string escaping convention. # @category String def string.unquote(s) = try let json.parse (s : string) = s s catch _ do s end end let string.data_uri = () # Encode a string using the data uri format, # i.e. `"data:[;base64],"`. # @category String # @param ~base64 Encode data using the base64 format # @param ~mime Data mime type def string.data_uri.encode(~base64=true, ~(mime:string), s) = s = base64 ? ";base64,#{string.base64.encode(s)}" : ",#{s}" "data:#{mime}#{s}" end # Decode a string using the data uri format, # i.e. `"data:[;base64],"`. # @category String def string.data_uri.decode(s) = captured = r/^data:(?[\/\w]+)(?;base64)?,(?.+)$/.exec(s).groups if list.assoc.mem("mime", captured) and list.assoc.mem("data", captured) then mime = list.assoc("mime", captured) data = list.assoc("data", captured) data = if list.assoc.mem("base64", captured) then string.base64.decode(data) else data end data.{mime=mime} else null end end let string.getter = () # Create a string getter which will return once the given string and then the # empty string. # @category String def string.getter.single(s) = first = ref(true) fun () -> begin if first() then first := false s else "" end end end # Flush all values from a string getter and return # the concatenated result. If the getter is constant, # return the constant string. Otherwise, call the getter # repeatedly until it returns an empty string and return # the concatenated result # @category String def string.getter.flush(~separator="", gen) = if getter.is_constant(gen) then getter.get(gen) else def rec f(data) = chunk = getter.get(gen) if chunk == "" then string.concat(separator=separator, data) else f([...data, chunk]) end end f([]) end end # Combine a list of string getters `[g1, ...]` # and return a single getter `g` such that: # `string.getter.flush(separator=s, g) = string.concat(separator=s, list.filter(fun (s) -> s != "", [string.getter.flush(g1), ...]))` # @category String def string.getter.concat(l) = len = list.length(l) pos = ref(0) def rec f() = if pos() == len then "" else gen = list.nth(l, pos()) ret = getter.get(gen) if ret == "" or getter.is_constant(gen) then ref.incr(pos) end ret == "" ? f() : ret end end getter(f) end let string.char.ascii = () # All ASCII characters code # @category String let string.char.ascii = list.init(128, fun (c) -> c) # All ASCII control character codes # @category String let string.char.ascii.control = list.init(32, fun (c) -> c) # All ASCII printable character codes # @category String let string.char.ascii.printable = list.init(96, fun (c) -> c + 32) # All ASCII alphabet character codes # @category String let string.char.ascii.alphabet = [ # A-Z ...list.init(24, fun (c) -> c + 65), # a-z ...list.init(24, fun (c) -> c + 97) ] # All ASCII number character codes # @category String let string.char.ascii.number = list.init(10, fun (c) -> c + 48) # Return a random ASCII character # @category String def string.char.ascii.random(range=[...string.char.ascii]) = string.char(list.nth(range, random.int(min=0, max=list.length(range) - 1))) end # Escape HTML entities. # @category String # @argsof string.escape[encoding] def string.escape.html(%argsof(string.escape[encoding]), s) = escaped = [ ("&", "&"), ("<", "<"), (">", ">"), ('"', """), ("'", "'") ] def special_char(~encoding:_, c) = list.assoc.mem(c, escaped) end def escape_char(~encoding:_, c) = escaped[c] end string.escape( %argsof(string.escape[encoding]), special_char=special_char, escape_char=escape_char, s ) end # Generate a given number of spaces (this can be useful for indenting). # @category String # @param n Number of spaces. def string.spaces(n) = string.make(char_code=32, n) end # Convert a string to uppercase. # @category String def string.uppercase(s) = string.case(lower=false, s) end # Convert a string to lowercase. # @category String def string.lowercase(s) = string.case(lower=true, s) end liquidsoap-2.4.2/src/libs/switches.liq000066400000000000000000000147521513273233300177640ustar00rootroot00000000000000# At the beginning of each track, select the first ready child. # @category Source / Track processing # @param ~id Force the value of the source ID. # @param ~override Metadata field which, if present and containing a float, overrides the `transition_length` parameter. # @param ~replay_metadata Replay the last metadata of a child when switching to it in the middle of a track. # @param ~track_sensitive Re-select only on end of tracks. # @param ~transition_length Maximum transition duration. # @param ~transitions Transition functions, padded with `fun (x,y) -> y` functions. def fallback( ~id=null, ~override="liq_transition_length", ~replay_metadata=true, ~track_sensitive=true, ~transition_length=5., ~transitions=[], sources ) = def add_condition(s) = ({true}, s) end switch( id=id, override=override, replay_metadata=replay_metadata, transitions=transitions, track_sensitive=track_sensitive, transition_length=transition_length, list.map(add_condition, sources) ) end # Rotate between sources. # @category Source / Track processing # @param ~id Force the value of the source ID. # @param ~override Metadata field which, if present and containing a float, overrides the `transition_length` parameter. # @param ~replay_metadata Replay the last metadata of a child when switching to it in the middle of a track. # @param ~transition_length Maximum transition duration. # @param ~transitions Transition functions, padded with `fun (x,y) -> y` functions. # @param ~weights Weights of the children (padded with 1), defining for each child how many tracks are played from it per round, if that many are actually available. def rotate( ~id=null, ~override="liq_transition_length", ~replay_metadata=true, ~transition_length=5., ~transitions=[], ~weights=[], sources ) = weights = list.map(getter.function, weights) default_weight = {1} failed = (source.fail() : source) # Currently selected index picked_index = ref(-1) # Number of tracks played per selected source # source IDs can change between calls.. tracks_played = list.map(fun (s) -> ((fun () -> source.id(s)), ref(0)), sources) tracks_played = fun () -> list.map( fun (x) -> begin label_fn = fst(x) (label_fn(), snd(x)) end, tracks_played ) # Find index of next source to play, i.e. first ready source after currently # selected one. def pick() = list.iter((fun (el) -> snd(el) := 0), tracks_played()) if list.exists(source.is_ready, sources) then def rec f(index) = s = list.nth(default=failed, sources, index) if source.is_ready(s) then picked_index := index else f((index + 1) mod list.length(sources)) end end f((picked_index() + 1) mod list.length(sources)) else picked_index := -1 end end # Add condition to i-th source. def add_condition(index, s) = def cond() = if picked_index() == -1 then pick() end if picked_index() != -1 then picked_weight = list.nth(default=default_weight, weights, picked_index()) picked_source = list.nth(sources, picked_index()) fn = list.assoc(source.id(picked_source), tracks_played()) if picked_weight() <= fn() then pick() end end picked_index() == index end (cond, s) end s = switch( override=override, replay_metadata=replay_metadata, track_sensitive=true, transition_length=transition_length, transitions=transitions, list.mapi(add_condition, sources) ) def f(_) = if null.defined(s.selected()) then selected_id = source.id(null.get(s.selected())) if list.assoc.mem(selected_id, tracks_played()) then played = list.assoc(selected_id, tracks_played()) ref.incr(played) end end end s.on_track(synchronous=true, f) def replaces s = fallback(id=id, track_sensitive=true, s::sources) end s end # At the beginning of every track, select a random ready child. # @category Source / Track processing # @param ~id Force the value of the source ID. # @param ~override Metadata field which, if present and containing a float, overrides the `transition_length` parameter. # @param ~replay_metadata Replay the last metadata of a child when switching to it in the middle of a track. # @param ~transition_length Maximum transition duration. # @param ~transitions Transition functions, padded with `fun (x,y) -> y` functions. # @param ~weights Weights of the children (padded with 1), defining for each child the probability that it is selected. def replaces random( ~id=null, ~override="liq_transition_length", ~replay_metadata=true, ~transition_length=5., ~transitions=[], ~weights=[], sources ) = weights = list.map(getter.function, weights) default_weight = fun () -> 1 next_index = ref(-1) def pick() = def available_weighted_sources(cur, s) = let (index, current_weight, indexes) = cur weight = list.nth(default=default_weight, weights, index) let (current_weight, indexes) = if source.is_ready(s) then weight = weight() indexes = (current_weight, current_weight + weight, index)::indexes (current_weight + weight, indexes) else (current_weight, indexes) end (index + 1, current_weight, indexes) end let (_, total_weight, weighted_indexes) = list.fold(available_weighted_sources, (0, 0, []), sources) picked_weight = if total_weight > 0 then random.int(min=0, max=total_weight) else 0 end def pick_source(picked_index, el) = let (lower_bound, upper_bound, index) = el if lower_bound <= picked_weight and picked_weight < upper_bound then index else picked_index end end next_index := list.fold(pick_source, -1, weighted_indexes) end def add_condition(index, s) = def cond() = if next_index() == -1 then pick() end next_index() == index end (cond, s) end s = switch( override=override, replay_metadata=replay_metadata, track_sensitive=true, transition_length=transition_length, transitions=transitions, list.mapi(add_condition, sources) ) def f(_) = next_index := -1 end s.on_track(synchronous=true, f) def replaces s = fallback(id=id, track_sensitive=true, s::sources) end s end liquidsoap-2.4.2/src/libs/testing.liq000066400000000000000000000022211513273233300175740ustar00rootroot00000000000000# Sleep regularly, thus inducing delays in the sound production. This is mainly # useful for emulating network delays or sources which are slow to produce data, # and thus test bufferization and robustness of scripts. # @category Source / Testing # @flag experimental # @param ~every How often we should sleep (in seconds, 0 means every frame). # @param ~delay Delay introduced (in seconds). # @param ~delay_random Maximum amount of time randomly added to the delay (in seconds). # @param ~on_delay Function called when a delay is introduced, with the delay as argument. # @param s Source in which the delays should be introduced. # @method frozen The stream production is frozen while set to `true`. def sleeper( ~every=1., ~delay=1.1, ~delay_random=0., ~on_delay=fun (_) -> (), s ) = last = ref(0.) frozen = ref(false) def f() = while frozen() do thread.pause(0.1) end now = source.time(s) if now >= last() + every then last := now delay = delay + random.float(max=delay_random) on_delay(delay) thread.pause(delay) end end source.methods(s).on_frame(synchronous=true, f) s.{frozen=frozen} end liquidsoap-2.4.2/src/libs/thread.liq000066400000000000000000000120161513273233300173710ustar00rootroot00000000000000# Run a function in a separate thread. # @category Programming # @param ~fast Whether the thread is supposed to return quickly or not. Typically, blocking tasks (e.g. fetching data over the internet) should not be considered to be fast. When set to `false` its priority will be lowered below that of request resolutions and fast timeouts. This is only effective if you set a dedicated queue for fast tasks, see the "scheduler" settings for more details. # @param ~delay Delay (in seconds) after which the thread should be launched. # @param ~every How often (in seconds) the thread should be run. If negative or `null`, run once. # @param ~on_error Error callback executed when an error occurred while running the given function. When passed, \ # all raised errors are silenced unless re-raised by the callback. # @param f Function to execute. def replaces thread.run(~fast=true, ~delay=0., ~on_error=null, ~every=null, f) = every = every ?? getter(-1.) def f() = ignore(f() == ()) getter.get(every) end on_error = null.map( fun (fn) -> fun (err) -> begin (fn(err) : unit) (-1.) end, on_error ) thread.run.recurrent(fast=fast, delay=delay, on_error=on_error, f) end # Unless told not to, we want to batch thread.when calls when the getter is a fixed # value. We batch them by getter value. All callbacks are expected to be fast so we # do a single global callback for all batched calls. After tasks_count_warning is # reached that they might need to refactor their code. let thread.when = {tasks_count=ref(0), tasks_count_warning=70, batches=ref([])} # @flag hidden def thread.when.add_to_batch(~every, check) = current_batches = thread.when.batches() if list.assoc.mem(every, current_batches) then tasks = list.assoc(every, current_batches) tasks := [check, ...tasks()] else tasks = ref([check]) errors = ref([]) def exec_task(fn) = try fn() catch err do errors := [err, ...errors()] end end def check_tasks() = current_tasks = tasks() list.iter(exec_task, current_tasks) current_errors = errors() errors := [] if current_errors != [] then errors_list = string.concat( separator=", ", list.map(string, current_errors) ) log.critical( label="thread.when", "Error(s) while executing batched `thread.when`: #{errors_list}" ) end every end thread.run.recurrent(fast=true, delay=0., on_error=null, check_tasks) thread.when.batches := [(every, tasks), ...current_batches] end end # Execute a callback when a predicate is `true`. The predicate # is checked `every` seconds and the callback is # called when the predicate returns `true` after having been # `false`, following the same semantics as `predicate.activates`. # @category Programming # @param ~fast Whether the callback is supposed to return quickly or not. # @param ~init Detect at beginning. # @param ~every How often (in sec.) to check for the predicate. # @param ~once Execute the function only once. # @param ~changed Execute the function only if the predicate was false when last checked. # @param ~batched When `fast` is `true`, `every` is a fixed value, `on_error` is `null` and `once` is `false`, \ # we batch callbacks to make it more efficient. Set this argument to `false` to disable that. # @param ~on_error Error callback executed when an error occurred while running the given function. When passed, \ # all raised errors are silenced unless re-raised by the callback. # @param p Predicate indicating when to execute the function, typically a time interval such as `{10h-10h30}`. # @param f Function to execute when the predicate is true. def thread.when( ~fast=true, ~init=true, ~every=getter(0.5), ~once=false, ~changed=true, ~on_error=null, ~batched=true, p, f ) = ref.incr(thread.when.tasks_count) if thread.when.tasks_count() == thread.when.tasks_count_warning + 1 then log.critical( label="thread.when", "There are over #{thread.when.tasks_count_warning} `thread.when` tasks \ running! You might want to consider refactoring your script to use a \ single callback!" ) end p = once or not changed ? p : (predicate.activates(init=init, p)) def check() = if p() then f() once ? (-1.) : (getter.get(every)) else getter.get(every) end end if ( batched and getter.is_constant(every) and not null.defined(on_error) and fast and not once ) then thread.when.add_to_batch(every=getter.get(every), fun () -> ignore(check())) else thread.run.recurrent(fast=fast, delay=0., on_error=on_error, check) end end # @flag hidden def default_error_handler(~backtrace, err) = log( label="runtime", level=1, "Uncaught error #{err}!\n#{backtrace}" ) end thread.on_error(null, default_error_handler) liquidsoap-2.4.2/src/libs/tracks.liq000066400000000000000000000056221513273233300174160ustar00rootroot00000000000000let source.mux = () let source.mux.track = () # Replace the audio track of a source. # @category Source / Track processing def source.mux.track.audio(~id=null, ~audio, s) = source(id=id, source.tracks(s).{audio=audio}) end # Replace the video track of a source. # @category Source / Track processing def source.mux.track.video(~id=null, ~video, s) = source(id=id, source.tracks(s).{video=video}) end # Replace the audio track of a source by the one of another source. # @category Source / Track processing # @param ~audio Source whose audio track is to be taken. def source.mux.audio(~id=null, ~(audio:source), s) = source.mux.track.audio(id=id, audio=source.tracks(audio).audio, s) end # Replace the video track of a source by the one of another source. # @category Source / Track processing # @param ~audio Source whose video track is to be taken. def source.mux.video(~id=null, ~(video:source), s) = source.mux.track.video(id=id, video=source.tracks(video).video, s) end # Replace the midi track of a source by the one of another source. # @category Source / Track processing # @param ~midi Source whose midi track is to be taken. def source.mux.midi(~id=null, ~(midi:source), s) = source(id=id, source.tracks(s).{midi=source.tracks(midi).midi}) end let source.drop = () # Remove the audio track of a source. # @category Source / Track processing def source.drop.audio(~id=null, s) = let {audio = _, ...tracks} = source.tracks(s) source(id=id, tracks) end # Remove the video track of a source. # @category Source / Track processing def source.drop.video(~id=null, s) = let {video = _, ...tracks} = source.tracks(s) source(id=id, tracks) end # Remove the midi track of a source. # @category Source / Track processing def source.drop.midi(~id=null, s) = let {midi = _, ...tracks} = source.tracks(s) source(id=id, tracks) end # Remove the metadata track of a source. # @category Source / Track processing def source.drop.metadata(~id=null, s) = let {metadata = _, ...tracks} = source.tracks(s) source(id=id, tracks) end # Remove the track marks of a source. # @category Source / Track processing def source.drop.track_marks(~id=null, s) = let {track_marks = _, ...tracks} = source.tracks(s) source(id=id, tracks) end # Remove the metadata and track marks of a source. # @category Source / Track processing def source.drop.metadata_track_marks(~id=null, s) = let {metadata = _, track_marks = _, ...tracks} = source.tracks(s) source(id=id, tracks) end let settings.amplify = settings.make.void( "Settings for the amplify operator" ) let settings.amplify.override = settings.make( description="Default metadata used to override amplification.", "liq_amplify" ) # @docof track.audio.amplify def track.audio.amplify( %argsof(track.audio.amplify[!override]), ~override=getter({(settings.amplify.override() : string?)}), v, t ) = track.audio.amplify(%argsof(track.audio.amplify), v, t) end liquidsoap-2.4.2/src/libs/utils.liq000066400000000000000000000034211513273233300172620ustar00rootroot00000000000000%ifdef environment # Get the value of an environment variable. # Returns `default` if the variable is not set. # @category System def environment.get(~default="", s) = env = environment() if not list.assoc.mem(s, env) then default else list.assoc(default=default, s, env) end end %endif # Split the arguments of an url of the form `arg=bar&arg2=bar2` into # `[("arg","bar"),("arg2","bar2")]`. The returned strings are decoded (see # `url.decode`). # @category String # @param args Argument string to split. def url.split_args(args) = def f(args, x) = if x == "" then args else ret = r/=/.split(x) arg = url.decode(list.nth(default="", ret, 0)) val = url.decode(list.nth(default="", ret, 1)) [...args, (arg, val)] end end l = r/&/.split(args) list.fold(f, [], l) end # Split an url of the form `foo?arg=bar&arg2=bar2` into # `("foo",[("arg","bar"),("arg2","bar2")])`. The returned strings are decoded # (see `url.decode`). # @category String # @param uri Url to split. def url.split(uri) = ret = r/(?[^\?]*)\?(?.*)/.exec(uri).groups args = ret["args"] if args != "" then (url.decode(ret["uri"]), url.split_args(args)) else (url.decode(uri), []) end end # Memoize the result of a function, making sure it is only executed once. # @category Programming def memoize(fn) = cached_result = ref([]) fun () -> begin if cached_result() != [] then list.hd(cached_result()) else result = fn() cached_result := [result] result end end end let duration = () # Convert a duration in seconds to hour/minutes/seconds # @category Time def duration.split(d) = {hours=d / 3600, minutes=(d / 60) mod 60, seconds=(d mod 60)} end liquidsoap-2.4.2/src/libs/video.liq000066400000000000000000000557561513273233300172520ustar00rootroot00000000000000# Width for all video frames. # @category Source / Video processing def video.frame.width = settings.frame.video.width end # Height for all video frames. # @category Source / Video processing def video.frame.height = settings.frame.video.height end # Framerate for all video frames. # @category Source / Video processing def video.frame.rate = settings.frame.video.framerate end # Generate a source from an image request. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param req Image request def request.image( ~id=null, ~fallible=false, ~width=null, ~height=null, ~x=getter(0), ~y=getter(0), req ) = last_req = ref(null) def next() = req = (getter.get(req) : request) if req != last_req() then last_req := req image = request.single(id=id, fallible=fallible, req) image = video.crop(image) video.resize(id=id, x=x, y=y, width=width, height=height, image) else null end end source.dynamic(id=id, track_sensitive=false, next) end # Generate a source from an image file. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param file Path to the image. # @method set Change the request. def image(%argsof(request.image), file) = r = getter.map.memoize(fun (file) -> request.create(file), file) request.image(%argsof(request.image), r) end # @flag hidden let orig_request = request # Add a static request on the given video track. # @category Track / Video processing # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param ~request Request to add to the video track def track.video.add_request( ~id=null("track.video.add_request"), ~fallible=false, ~width=null, ~height=null, ~x=getter(0), ~y=getter(0), ~request, v ) = image = orig_request.image( id=id, fallible=fallible, x=x, y=y, width=width, height=height, request ) let {video = image} = source.tracks(image) track.video.add([v, image]) end # Add a static image on the given video track. # @category Track / Video processing # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param ~file Path to the image file. def track.video.add_image( ~id=null("track.video.add_image"), ~fallible=false, ~width=null, ~height=null, ~x=getter(0), ~y=getter(0), ~file, v ) = image = image(id=id, fallible=fallible, x=x, y=y, width=width, height=height, file) let {video = image} = source.tracks(image) track.video.add([v, image]) end # Add a static request on the source video channel. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param ~request Request to add to the video channel def video.add_request( ~id=null("video.add_request"), ~fallible=false, ~width=null, ~height=null, ~x=getter(0), ~y=getter(0), ~request, (s:source) ) = let {video, ...tracks} = source.tracks(s) video = track.video.add_request( fallible=fallible, width=width, height=height, x=x, y=y, request=request, video ) source(id=id, tracks.{video=video}) end # Add a static image on the source video channel. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~fallible Whether we are allowed to fail (in case the file is non-existent or invalid). # @param ~width Scale to width # @param ~height Scale to height # @param ~x x position. # @param ~y y position. # @param ~file Path to the image file. def video.add_image( ~id=null("video.add_image"), ~fallible=false, ~width=null, ~height=null, ~x=getter(0), ~y=getter(0), ~file, (s:source) ) = let {video, ...tracks} = source.tracks(s) video = track.video.add_image( fallible=fallible, width=width, height=height, x=x, y=y, file=file, video ) source(id=id, tracks.{video=video}) end # Generate a video source containing cover-art for current track of input audio # source. # @category Source / Video processing # @param s Audio source whose metadata contain cover-art. def video.cover(s) = last_filename = ref(null) last_metadata = source.methods(s).last_metadata b = (blank() : source) def next() = m = last_metadata() ?? [] filename = m["filename"] if filename != last_filename() then last_filename := filename cover = if file.exists(filename) then file.cover(filename) else "".{mime=""} end if null.defined(cover) and null.get(cover) != "" then cover = null.get(cover) extname = (null.defined(cover.mime) ? file.mime.extension(null.get(cover.mime)) : null ) ?? ".osb" f = file.temp("cover", extname) log.debug( "Found cover for #{filename}." ) file.write(data=cover, f) request.once(request.create(temporary=false, f)) else log.debug( "No cover for #{filename}." ) b end else null end end source.dynamic(track_sensitive=false, next) end let output.youtube = () let output.youtube.live = () # Stream to youtube using RTMP. # @category Source / Output # @param ~id Force the value of the source ID. # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If true, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. # @param ~url RTMP URL to stream to # @param ~encoder Encoder to use (most likely a `%ffmpeg` encoder) # @param ~key Your secret youtube key def output.youtube.live.rtmp( ~id=null, ~fallible=false, ~start=true, ~url="rtmp://a.rtmp.youtube.com/live2", ~(key:string), ~encoder, s ) = output.url( id=id, fallible=fallible, start=start, url="#{url}/#{key}", encoder, s ) end # Stream to youtube using HLS. # @category Source / Output # @param ~id Force the value of the source ID. # @param ~fallible Allow the child source to fail, in which case the output will be (temporarily) stopped. # @param ~start Automatically start outputting whenever possible. If true, an infallible (normal) output will start outputting as soon as it is created, and a fallible output will (re)start as soon as its source becomes available for streaming. # @param ~segment_duration Segment duration (in seconds). # @param ~segments Number of segments per playlist. # @param ~segments_overhead Number of segments to keep after they have been featured in the live playlist. # @param ~url HLS URL to stream to # @param ~encoder Encoder to use (most likely a `%ffmpeg` encoder) # @param ~key Your secret youtube key def output.youtube.live.hls( ~id=null, ~fallible=false, ~segment_duration=2.0, ~segments=4, ~segments_overhead=4, ~start=true, ~url="https://a.upload.youtube.com/http_upload_hls", ~(key:string), ~encoder, s ) = id = string.id.default(default="output.youtube.live.hls", id) def file_url(fname) = "#{url}?cid=#{key}©=0&file=#{fname}" end def file_upload(fname) = fun () -> try ignore(http.post(data=file.read(fname), file_url(path.basename(fname)))) catch err do log( label=id, level=3, "Error while uploading: #{err}" ) end end def on_file_change({state, path = fname}) = if (state == "created" or state == "updated") and path.basename(fname) != "main.m3u8" then thread.run(file_upload(fname)) end end tmpdir = file.temp_dir("hls", "") on_shutdown({file.rmdir(tmpdir)}) o = output.file.hls( id=id, start=start, fallible=fallible, playlist="main.m3u8", segment_duration=segment_duration, segments=segments, segments_overhead=segments_overhead, tmpdir, [("live", encoder)], s ) o.on_file_change(synchronous=true, on_file_change) o end # @flag hidden def add_text_builder(f) = def at( ~id=null, ~duration=null, ~color=getter(0xffffff), ~cycle=true, ~font=null, ~metadata=null, ~size=getter(18), ~speed=0, ~x=getter(10), ~y=getter(10), ~on_cycle={()}, text, s ) = available = s.is_ready # Handle modifying the text with metadata. tref = ref(getter.get(text)) text = null.defined(metadata) ? tref : text def on_metadata(m) = if null.defined(metadata) then m = m[null.get(metadata)] if m != "" then tref := m end end end if null.defined(metadata) then s.on_metadata(synchronous=true, on_metadata) end # Our text source. t = f(id=id, duration=duration, color=color, font=font, size=size, text) t = video.info(video.crop(t)) # Handle scrolling if necessary. x = if speed == 0 then x else fps = video.frame.rate() x = ref(getter.get(x)) def x() = if cycle and x() < 0 - t.width() then on_cycle() x := video.frame.width() end x := x() - getter.get(speed) / fps x() end x end t = video.translate(x=x, y=y, t) # Ensure that we fail when s fails. t = source.available(t, available) # Add the text to the original source. let {video = v, ...tracks} = source.tracks(s) let {video = t} = source.tracks(t) let v = track.video.add([v, t]) source(tracks.{video=v}) end at end let video.add_text = () let video.text.available = ref([]) # Add a text to a stream (native implementation). # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (`0` means no scrolling and update according to `x` and `y` in case they are variable). # @param ~x x offset. # @param ~y y offset. # @params d Text to display. def video.add_text.native = add_text_builder(video.text.native) end video.text.available := [("native", video.text.native), ...video.text.available()] %ifdef video.text.gd video.text.available := [("gd", video.text.gd), ...video.text.available()] # Add a text to a stream (GD implementation). # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (`0` means no scrolling and update according to `x` and `y` in case they are variable). # @param ~x x offset. # @param ~y y offset. # @params d Text to display. def video.add_text.gd = add_text_builder(video.text.gd) end %endif %ifdef video.text.sdl video.text.available := [("sdl", video.text.sdl), ...video.text.available()] # Add a text to a stream (SDL implementation). # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (`0` means no scrolling and update according to `x` and `y` in case they are variable). # @param ~x x offset. # @param ~y y offset. # @params d Text to display. def video.add_text.sdl = add_text_builder(video.text.sdl) end %endif %ifdef video.text.camlimages video.text.available := [("camlimages", video.text.camlimages), ...video.text.available()] # Add a text to a stream (camlimages implementation). # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (`0` means no scrolling and update according to `x` and `y` in case they are variable). # @param ~x x offset. # @param ~y y offset. # @params d Text to display. def video.add_text.camlimages = add_text_builder(video.text.camlimages) end %endif let settings.video.text = settings.make( description="`video.text` implementation.", fst(list.hd(video.text.available())) ) thread.run( ( fun () -> begin text = settings.video.text() if list.assoc.mem(text, video.text.available()) then log.important( label="video.text", "Using #{text} implementation" ) else log.severe( label="video.text", "Cannot find #{text} implementation for `video.text`, using default #{ fst(list.hd(video.text.available())) }" ) end end ) ) # Display a text using the first available operator in: camlimages, SDL, FFmpeg, gd or native. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~duration Duration in seconds (`null` means infinite). # @param ~font Path to ttf font file. # @param ~size Font size. # @param text Text to display. def replaces video.text( ~id=null, ~color=getter(0xffffff), ~duration=null, ~font=null, ~size=getter(18), text ) = f = list.assoc( default=snd(list.hd(video.text.available())), settings.video.text(), video.text.available() ) f(id=id, color=color, duration=duration, font=font, size=size, text) end # Add a text to a stream. Uses the first available operator in: camlimages, SDL, # FFmpeg, gd or native. # @category Source / Video processing # @param ~id Force the value of the source ID. # @param ~color Text color (in 0xRRGGBB format). # @param ~cycle Cycle text when it reaches left boundary. # @param ~font Path to ttf font file. # @param ~metadata Change text on a particular metadata (empty string means disabled). # @param ~size Font size. # @param ~speed Horizontal speed in pixels per second (`0` means no scrolling and update according to `x` and `y` in case they are variable). # @param ~x x offset. # @param ~y y offset. # @param ~on_cycle Function called when text is cycling. # @params d Text to display. def replaces video.add_text( ~id=null, ~duration=null, ~color=0xffffff, ~cycle=true, ~font=null, ~metadata=null, ~size=18, ~speed=0, ~x=getter(10), ~y=getter(10), ~on_cycle={()}, d, s ) = add_text = add_text_builder(video.text) add_text( id=id, duration=duration, cycle=cycle, font=font, metadata=metadata, size=size, color=color, speed=speed, x=x, y=y, on_cycle=on_cycle, d, s ) end # Add subtitle from metadata. # @category Source / Video processing # @param ~override Metadata where subtitle to display are located. # @param ~offset Offset in pixels. # @param s Source. def video.add_subtitle( ~override="subtitle", ~size=18, ~color=0xffffff, ~offset=20, s ) = subtitle = ref("") t = video.text(size=size, color=color, subtitle) t = video.bounding_box(t) x = {(video.frame.width() - t.width()) / 2} y = {video.frame.height() - t.height() - offset} t = video.translate(x=x, y=y, t) def meta(m) = if list.assoc.mem(override, m) then subtitle := list.assoc(override, m) end end s.on_metadata(synchronous=true, meta) let {video = v, ...tracks} = source.tracks(s) let {video = t} = source.tracks(t) let v = track.video.add([v, t]) source(tracks.{video=v}) end # Display a slideshow (typically of pictures). # @category Source / Video processing # @param ~cyclic Go to the first picture after the last. # @param ~advance Skip to the next file after this amount of time in seconds (negative means never). # @param l List of files to display. # @method append Append a list of files to the slideshow. # @method clear Clear the list of files in the slideshow. # @method next Go to next file. # @method prev Go to previous file. # @method current Currently displayed file. def video.slideshow( ~id=null, ~cyclic=getter(true), ~advance=getter(-1.), l=[] ) = id = string.id.default(default="video.slideshow", id) l = ref(l) n = ref(-1) next_source = ref(null) def next() = s = next_source() next_source := null s end s = source.dynamic(next) def current() = list.nth(l(), n()) end # Set current file to the nth. def set(n') = if 0 <= n' and n' < list.length(l()) and n' != n() then n := n' new_source = request.once(request.create(current())) new_source = s.prepare(new_source) next_source := new_source end end def next() = log.debug( label=id, "Going to next file" ) n' = n() + 1 n' = if n' >= list.length(l()) then if getter.get(cyclic) then 0 else list.length(l()) - 1 end else n' end set(n') end def prev() = log.debug( label=id, "Going to previous file" ) n' = n() - 1 n' = if n' < 0 then if getter.get(cyclic) then list.length(l()) - 1 else 0 end else n' end set(n') end def clear() = l := [] n := 0 end def append(l') = l := list.append(l(), l') end set(0) if getter.get(advance) >= 0. then thread.run(delay=getter.get(advance), every=advance, next) end s.{append=append, clear=clear, next=next, prev=prev, current=current} end # Generate a video filled with given color. # @category Source / Video processing # @param color Color (in 0xRRGGBB format). def video.color(color) = video.fill(color=color, blank()) end # Tile sources # @category Source / Video processing # @argsof track.video.tile # @argsof track.audio.add[!id] def video.tile( ~id=null("video.tile"), %argsof(track.audio.add[!id]), %argsof(track.video.tile[!id]), ~weights=[], sources ) = tracks = list.map(fun (s) -> source.tracks(s), sources) video_tracks = list.map(fun (t) -> t.video, tracks) new_tracks = {video=track.video.tile(%argsof(track.video.tile[!id]), video_tracks)} new_tracks = if list.length(tracks) != 0 and null.defined(list.hd(tracks)?.audio) then def mk_audio_track(pos, track) = weight = try list.nth(weights, pos) catch _ do getter(1.) end null.get(track?.audio).{weight=weight} end audio_tracks = list.mapi(mk_audio_track, tracks) new_tracks.{ audio=track.audio.add(%argsof(track.audio.add[!id]), audio_tracks) } else new_tracks end source(id=id, new_tracks) end # Plot a floating point value. # @category Source / Video processing # @param ~color Color of the drawn point (in 0xRRGGBB format). # @param ~lines Draw lines connecting plotted points. # @param ~min Minimal value of the parameter. # @param ~max Maximal value of the parameter. # @param ~speed Speed in pixels per second. # @param y Value to plot. def video.plot(~lines=true, ~min=0., ~max=1., ~speed=100., ~color=0xffffff, y) = width = video.frame.width() height = video.frame.height() s = video.board(width=width * 3, height=height) height = float(s.height()) tx = ref(width) ty = ref(0) s' = video.translate(x=tx, y=ty, s) # offset of the currently drawn point. x = ref(0) dx = int_of_float(speed * frame.duration()) def update() = tx := tx() - dx y = int_of_float(((max - y()) / (max - min)) * height) if lines then s.line_to(color=color, x(), y) else s.pixel(x(), y) := color end x := x() + dx if x() > width * 2 then s.clear_and_copy(x=0 - width) tx := tx() + width x := x() - width end end s'.on_frame(synchronous=true, update) s' end let video.canvas = () # Create a virtual canvas that can be used to return video position and sizes # that are independent of the frame's dimensions. # @category Source / Video processing # @param ~virtual_width Virtual height, in pixels, of the canvas # @param ~actual_size Actual size, in pixels, of the canvas # @param ~font_size Font size, in virtual pixels. # @method ~px Map a virtual size in pixel to the actual size. # @method ~rem Map a fraction of the virtual font size into an actual font size # @method ~vh Return a position in percent (as a value between `0.` and `1.`) \ # of the canvas height # @method ~vw Return a position in percent (as a value between `0.` and `1.`) \ # of the canvas width def video.canvas.make(~virtual_width, ~actual_size, ~font_size) = virtual_width = float(virtual_width) actual_height = float(actual_size.height) actual_width = float(actual_size.width) ratio = actual_width / virtual_width font_ratio = float(font_size) * ratio def px((v:int)) = int_of_float(float(v) * ratio) end def vh(v) = int_of_float(v * actual_height) end def vw(v) = int_of_float(v * (float(actual_width))) end def rem(v) = int_of_float(v * font_ratio) end {px=px, rem=rem, vw=vw, vh=vh, ...actual_size} end # Standard video canvas based off a `10k` virtual canvas. # @category Source / Video processing def video.canvas.virtual_10k = def make(width, height) = video.canvas.make( virtual_width=10000, actual_size={width=width, height=height}, font_size=160 ) end { actual_360p=make(640, 360), actual_480p=make(640, 480), actual_720p=make(1280, 720), actual_1080p=make(1920, 1080), actual_1440p=make(2560, 1440), actual_4k=make(3840, 2160), actual_8k=make(7680, 4320) } end liquidsoap-2.4.2/src/modules/000077500000000000000000000000001513273233300161325ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/cron/000077500000000000000000000000001513273233300170735ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/cron/base.ml000066400000000000000000000007151513273233300203420ustar00rootroot00000000000000type entry = [ `Int of int | `List of int list | `Range of int * int | `Step of int | `Any ] let string_of_entry = function | `Int i -> string_of_int i | `List l -> String.concat "," (List.map string_of_int l) | `Range (v, v') -> Printf.sprintf "%d-%d" v v' | `Step v -> Printf.sprintf "*/%d" v | `Any -> "*" type t = { week_day : entry; month : entry; month_day : entry; hour : entry; minute : entry; } exception Parse_error of string liquidsoap-2.4.2/src/modules/cron/cron.ml000066400000000000000000000023431513273233300203700ustar00rootroot00000000000000include Base let valid_entry (v, v') = match v' with | `Int v' -> v = v' | `List l -> List.mem v l | `Step s -> v mod s == 0 | `Range (min, max) -> min <= v && v <= max | `Any -> true let test ?time entry = let time = match time with None -> Unix.time () | Some t -> t in let { Unix.tm_min = sys_minute; tm_hour = sys_hour; tm_mday = sys_month_day; tm_mon = sys_month; tm_wday = sys_week_day; } = Unix.localtime time in let { minute; hour; month_day; month; week_day } = entry in List.for_all valid_entry [ (sys_minute, minute); (sys_hour, hour); (sys_month_day, month_day); (sys_month + 1, month); (sys_week_day, week_day); ] let parse = let processor = MenhirLib.Convert.Simplified.traditional2revised Parser.cron in fun s -> let lexbuf = Sedlexing.Utf8.from_string s in try processor (fun () -> let token = Lexer.token lexbuf in let p, p' = Sedlexing.lexing_bytes_positions lexbuf in (token, p, p')) with | Parse_error _ as exn -> let bt = Printexc.get_raw_backtrace () in Printexc.raise_with_backtrace exn bt | _ -> raise (Parse_error "Invalid CRON string") liquidsoap-2.4.2/src/modules/cron/cron.mli000066400000000000000000000005151513273233300205400ustar00rootroot00000000000000type entry = [ `Int of int | `List of int list | `Range of int * int | `Step of int | `Any ] type t = { week_day : entry; month : entry; month_day : entry; hour : entry; minute : entry; } exception Parse_error of string val parse : string -> t val test : ?time:float -> t -> bool val string_of_entry : entry -> string liquidsoap-2.4.2/src/modules/cron/dune000066400000000000000000000003531513273233300177520ustar00rootroot00000000000000(env (release (ocamlopt_flags (:standard -O2))) (dev (flags (:standard -w -9)))) (menhir (modules parser)) (library (name cron) (libraries menhirLib unix) (preprocess (pps sedlex.ppx)) (synopsis "Cron utilities")) liquidsoap-2.4.2/src/modules/cron/lexer.ml000066400000000000000000000022361513273233300205470ustar00rootroot00000000000000open Parser let decimal_digit = [%sedlex.regexp? '0' .. '9'] let one_nine = [%sedlex.regexp? '1' .. '9'] let non_null_integer = [%sedlex.regexp? one_nine, Star decimal_digit] let integer = [%sedlex.regexp? '0' | non_null_integer] let skipped = [%sedlex.regexp? Plus ' '] let rec token lexbuf = match%sedlex lexbuf with | skipped -> token lexbuf | "-" -> DASH | "/" -> SLASH | "*" -> STAR | "," -> COMMA | "sun" -> WEEK_DAY 0 | "mon" -> WEEK_DAY 1 | "tue" -> WEEK_DAY 2 | "wed" -> WEEK_DAY 3 | "thu" -> WEEK_DAY 4 | "fri" -> WEEK_DAY 5 | "sat" -> WEEK_DAY 6 | "jan" -> MONTH 1 | "feb" -> MONTH 2 | "mar" -> MONTH 3 | "apr" -> MONTH 4 | "may" -> MONTH 5 | "jun" -> MONTH 6 | "jul" -> MONTH 7 | "aug" -> MONTH 8 | "sep" -> MONTH 9 | "oct" -> MONTH 10 | "nov" -> MONTH 11 | "dec" -> MONTH 12 | "@yearly" -> YEARLY | "@annually" -> YEARLY | "@monthly" -> MONTHLY | "@weekly" -> WEEKLY | "@daily" -> DAILY | "@hourly" -> HOURLY | eof -> EOF | integer -> INT (int_of_string (Sedlexing.Utf8.lexeme lexbuf)) | _ -> raise (Base.Parse_error "Syntax error") liquidsoap-2.4.2/src/modules/cron/parser.mly000066400000000000000000000066251513273233300211230ustar00rootroot00000000000000 %start cron %type cron %token STAR %token DASH %token SLASH %token COMMA %token EOF %token YEARLY %token MONTHLY %token WEEKLY %token DAILY %token HOURLY %token INT %token WEEK_DAY %token MONTH %% week_day_entry: | INT { if $1 < 0 || 7 < $1 then raise (Base.Parse_error "Week day should be in the 0-7 range!"); $1 mod 7 } | WEEK_DAY { $1 } week_day_list: | week_day_entry COMMA week_day_list { $1 :: $3 } | week_day_entry { [$1] } week_day: | STAR { `Any } | week_day_entry { `Int $1 } | week_day_entry COMMA week_day_list { `List ($1::$3) } | week_day_entry DASH week_day_entry { `Range ($1, $3) } | STAR SLASH week_day_entry { `Step $3 } month_entry: | INT { if $1 < 1 || 12 < $1 then raise (Base.Parse_error "Month should be in the 1-12 range!"); $1 } | MONTH { $1 } month_list: | month_entry COMMA month_list { $1 :: $3 } | month_entry { [$1] } month: | STAR { `Any } | month_entry { `Int $1 } | month_entry COMMA month_list { `List ($1::$3) } | month_entry DASH month_entry { `Range ($1, $3) } | STAR SLASH month_entry { `Step $3 } month_day_entry: | INT { if $1 < 1 || 31 < $1 then raise (Base.Parse_error "Month day should be in the 1-31 range!"); $1 } month_day_list: | month_day_entry COMMA month_day_list { $1 :: $3 } | month_day_entry { [$1] } month_day: | STAR { `Any } | month_day_entry { `Int $1 } | month_day_entry COMMA month_day_list { `List ($1::$3) } | month_day_entry DASH month_day_entry { `Range ($1, $3) } | STAR SLASH month_day_entry { `Step $3 } hour_entry: | INT { if $1 < 0 || 23 < $1 then raise (Base.Parse_error "Hour should be in the 0-23 range!"); $1 } hour_list: | hour_entry COMMA hour_list { $1 :: $3 } | hour_entry { [$1] } hour: | STAR { `Any } | hour_entry { `Int $1 } | hour_entry COMMA hour_list { `List ($1::$3) } | hour_entry DASH hour_entry { `Range ($1, $3) } | STAR SLASH hour_entry { `Step $3 } minute_entry: | INT { if $1 < 0 || 59 < $1 then raise (Base.Parse_error "Minute should be in the 0-59 range!"); $1 } minute_list: | minute_entry COMMA minute_list { $1 :: $3 } | minute_entry { [$1] } minute: | STAR { `Any } | minute_entry { `Int $1 } | minute_entry COMMA minute_list { `List ($1::$3) } | minute_entry DASH minute_entry { `Range ($1, $3) } | STAR SLASH minute_entry { `Step $3 } cron: | YEARLY { { Base.minute = `Int 0; hour = `Int 0; month_day = `Int 1; month = `Int 1; week_day = `Any } } | MONTHLY { { Base.minute = `Int 0; hour = `Int 0; month_day = `Int 1; month = `Any; week_day = `Any } } | WEEKLY { { Base.minute = `Int 0; hour = `Int 0; month_day = `Any; month = `Any; week_day = `Int 0 } } | DAILY { { Base.minute = `Int 0; hour = `Int 0; month_day = `Any; month = `Any; week_day = `Any } } | HOURLY { { Base.minute = `Int 0; hour = `Any; month_day = `Any; month = `Any; week_day = `Any } } | minute hour month_day month week_day EOF { { Base.minute = $1; hour = $2; month_day = $3; month = $4; week_day = $5 } } liquidsoap-2.4.2/src/modules/cry/000077500000000000000000000000001513273233300167275ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/cry/cry.ml000066400000000000000000000605151513273233300200650ustar00rootroot00000000000000(* * Copyright 2003-2016 Savonet team * * This file is part of Ocaml-cry. * * Ocaml-cry 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. * * Ocaml-cry 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 Ocaml-cry; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *) (** OCaml low level implementation of the shout source protocol. *) external poll : Unix.file_descr array -> Unix.file_descr array -> Unix.file_descr array -> float -> Unix.file_descr array * Unix.file_descr array * Unix.file_descr array = "caml_cry_poll" let poll r w e timeout = let r = Array.of_list r in let w = Array.of_list w in let e = Array.of_list e in let r, w, e = poll r w e timeout in (Array.to_list r, Array.to_list w, Array.to_list e) let select = match Sys.os_type with "Unix" -> poll | _ -> Unix.select type error = | Create of exn | Connect of exn | Close of exn | Write of exn | Read of exn | Busy | Ssl_unavailable | Not_connected | Invalid_usage | Unknown_host of string | Bad_answer of string option | Http_answer of int * string * string exception Error of error exception Timeout type event = [ `Read | `Write | `Both ] type socket = < typ : string ; transport : transport ; file_descr : Unix.file_descr ; wait_for : ?log:(string -> unit) -> event -> float -> unit ; write : Bytes.t -> int -> int -> int ; read : Bytes.t -> int -> int -> int ; close : unit > and transport = < name : string ; protocol : string ; default_port : int ; connect : ?bind_address:string -> ?timeout:float -> ?prefer:[ `System_default | `Ipv4 | `Ipv6 ] -> string -> int -> socket > (* Wait for [`Read socker], [`Write socket] or [`Both socket] for at most * [timeout] seconds on the given [socket]. Raises [Timeout] if timeout * is reached. *) let wait_for ?(log = fun _ -> ()) event timeout = let start_time = Unix.gettimeofday () in let max_time = start_time +. timeout in let r, w = match event with | `Read socket -> ([socket], []) | `Write socket -> ([], [socket]) | `Both socket -> ([socket], [socket]) in let rec wait t = let r, w, _ = try select r w [] t with Unix.Unix_error (Unix.EINTR, _, _) -> ([], [], []) in if r = [] && w = [] then ( let current_time = Unix.gettimeofday () in if current_time >= max_time then ( log "Timeout reached!"; raise Timeout) else wait (min 1. (max_time -. current_time))) in wait (min 1. timeout) let unix_socket transport fd : socket = object method typ = "unix" method file_descr = fd method transport = transport method wait_for ?log (event : event) d = let event = match event with | `Read -> `Read fd | `Write -> `Write fd | `Both -> `Both fd in wait_for ?log event d method write = Unix.write fd method read = Unix.read fd method close = Unix.close fd end let sockaddr_of_address address = match Unix.getaddrinfo address "0" [AI_NUMERICHOST] with | [] -> raise Not_found | addr :: _ -> addr.ai_addr let addrinfo_order = function | _, Unix.ADDR_UNIX _ -> 2 | `Ipv4, Unix.ADDR_INET (s, _) -> if Unix.is_inet6_addr s then 1 else 0 | `Ipv6, Unix.ADDR_INET (s, _) -> if Unix.is_inet6_addr s then 0 else 1 let resolve_host ~prefer host port = match ( prefer, Unix.getaddrinfo host (string_of_int port) [AI_SOCKTYPE SOCK_STREAM] ) with | _, [] -> raise Not_found | `System_default, l -> l | ((`Ipv4, l) as v) | ((`Ipv6, l) as v) -> List.sort (fun { Unix.ai_addr = s; _ } { Unix.ai_addr = s'; _ } -> Stdlib.compare (addrinfo_order (fst v, s)) (addrinfo_order (fst v, s'))) l let connect_sockaddr ?bind_address ?timeout sockaddr = let domain = Unix.domain_of_sockaddr sockaddr in let socket = Unix.socket ~cloexec:true domain Unix.SOCK_STREAM 0 in (try match bind_address with | None -> () | Some s -> Unix.bind socket (sockaddr_of_address s) with exn -> let bt = Printexc.get_raw_backtrace () in begin try Unix.close socket with _ -> () end; Printexc.raise_with_backtrace exn bt); let do_timeout = timeout <> None in let check_timeout () = let timeout = Option.get timeout in (* Block in a select call for [timeout] seconds. *) let _, w, _ = select [] [socket] [] timeout in if w = [] then raise Timeout; match Unix.getsockopt_error socket with | Some err -> raise (Unix.Unix_error (err, "connect", "")) | None -> Unix.clear_nonblock socket; socket in let finish () = try if do_timeout then Unix.set_nonblock socket; Unix.connect socket sockaddr; if do_timeout then Unix.clear_nonblock socket; socket with | Unix.Unix_error (Unix.EINPROGRESS, _, _) -> check_timeout () | Unix.Unix_error (Unix.EWOULDBLOCK, _, _) when Sys.os_type = "Win32" -> check_timeout () in try finish () with e -> let bt = Printexc.get_raw_backtrace () in begin try Unix.close socket with _ -> () end; Printexc.raise_with_backtrace e bt let unix_connect ?bind_address ?timeout ?(prefer = `System_default) host port = let rec connect_any ?bind_address ?timeout (addrs : Unix.addr_info list) = match addrs with | [] -> raise Not_found | [addr] -> (* Let a possible error bubble up *) connect_sockaddr ?bind_address ?timeout addr.ai_addr | addr :: tail -> ( try connect_sockaddr ?bind_address ?timeout addr.ai_addr with _ -> connect_any ?bind_address ?timeout tail) in connect_any ?bind_address ?timeout (resolve_host ~prefer host port) let unix_transport : transport = object (self) method name = "unix" method protocol = "http" method default_port = 80 method connect ?bind_address ?timeout ?prefer host port = let socket = unix_connect ?bind_address ?timeout ?prefer host port in unix_socket self socket end let unix_socket = unix_socket unix_transport let rec string_of_error = function | Error (Create e) -> pp "could not initiate a new handler" e | Error (Connect e) -> pp "could not connect to host" e | Error (Write e) -> pp "could not write data to host" e | Error (Read e) -> pp "could not read data from host" e | Error (Close e) -> pp "could not close connection" e | Error Busy -> "busy" | Error Ssl_unavailable -> "SSL transport is not available" | Error Not_connected -> "not connected" | Error Invalid_usage -> "invalid usage" (* | Unix.unix_error (code,name,param) -> Printf.sprintf "%s in %s(%s)" (Unix.error_message code) name param *) | Error (Unknown_host h) -> Printf.sprintf "Unknown host: %s" h | Timeout -> "connection timeout" | Error (Bad_answer s) -> Printf.sprintf "bad answer%s" (match s with Some s -> Printf.sprintf ": %s" s | None -> "") | Error (Http_answer (c, x, v)) -> Printf.sprintf "%i, %s (HTTP/%s)" c x v | e -> Printexc.to_string e and pp s e = Printf.sprintf "%s: %s" s (string_of_error e) type verb = Put | Post | Source let string_of_verb = function | Put -> "PUT" | Post -> "POST" | Source -> "SOURCE" type protocol = Icy | Http of verb let string_of_protocol = function | Icy -> "icy" | Http v -> Printf.sprintf "http(%s)" (string_of_verb v) type content_type = string let ogg_application = "application/ogg" let ogg_audio = "audio/ogg" let ogg_video = "video/ogg" let mpeg = "audio/mpeg" let content_type_of_string s = s let string_of_content_type x = x type mount = Icy_id of int | Icecast_mount of string type connection = { mount : mount; user : string; password : string; host : string; port : int; chunked : bool; content_type : content_type; protocol : protocol; headers : (string, string) Hashtbl.t; } let string_of_mount = function | Icy_id id -> Printf.sprintf "\"sid\": %d" id | Icecast_mount mount -> Printf.sprintf "\"mount\": %S" mount let string_of_connection c = Printf.sprintf "{ %s,\n\ \"user\": %S,\n\ \"password\": %S,\n\ \"host\": %S,\n\ \"port\": %d,\n\ \"chunked\": %b,\n\ \"content_type\": %S,\n\ \"protocol\": %S,\n\ \"headers\": { %s } }" (string_of_mount c.mount) c.user c.password c.host c.port c.chunked (string_of_content_type c.content_type) (string_of_protocol c.protocol) (Hashtbl.fold (fun x y z -> Printf.sprintf "%S: %S,\n%s" x y z) c.headers "") type audio_info = (string, string) Hashtbl.t type metadata = (string, string) Hashtbl.t (* Metadata socket is only present if icy_cap is true *) type connection_data = { connection : connection; socket : socket } type status_priv = PrivConnected of connection_data | PrivDisconnected type status = Connected of connection_data | Disconnected type t = { timeout : float; connection_timeout : float option; bind_address : string option; transport : transport; mutable icy_cap : bool; mutable status : status_priv; mutable chunked : bool; } let get_connection_data x = match x.status with | PrivConnected d -> d | PrivDisconnected -> raise (Error Not_connected) let create ?bind_address ?connection_timeout ?(timeout = 30.) ?(transport = unix_transport) () = { timeout; connection_timeout; transport; bind_address; icy_cap = false; status = PrivDisconnected; chunked = false; } let write_data ~timeout ?(offset = 0) ?length (socket : socket) request = let request = Bytes.unsafe_of_string request in let len = match length with Some l -> l | None -> Bytes.length request in let rec write ofs = socket#wait_for `Write timeout; let rem = len - ofs in if rem > 0 then ( let ret = socket#write request ofs rem in if ret = 0 then raise (Failure "connection closed."); if ret < rem then write (ofs + ret)) in try write offset with e -> raise (Error (Write e)) let close x = try let c = get_connection_data x in Fun.protect ~finally:(fun () -> c.socket#close) (fun () -> if x.chunked then write_data ~timeout:x.timeout c.socket "0\r\n\r\n"; x.chunked <- false; x.icy_cap <- false; x.status <- PrivDisconnected) with | Error _ as e -> raise e | e -> raise (Error (Close e)) let get_status c = match c.status with | PrivConnected d -> Connected d | PrivDisconnected -> Disconnected let get_icy_cap c = c.icy_cap let audio_info ?samplerate ?channels ?quality ?bitrate () = let infos = Hashtbl.create 10 in let f m x y = match y with Some v -> Hashtbl.replace infos x (m v) | None -> () in f string_of_int "channels" channels; f string_of_int "samplerate" samplerate; f string_of_int "bitrate" bitrate; f string_of_float "quality" quality; infos let normalize_mount mount = match mount with | Icecast_mount mount -> Icecast_mount begin match mount with | "" -> "/" | mount -> if mount.[0] = '/' then mount else Printf.sprintf "/%s" mount end | _ -> mount let connection ?user_agent ?name ?genre ?url ?public ?audio_info ?description ?(host = "localhost") ?(port = 8000) ?(chunked = false) ?(password = "hackme") ?(protocol = Http Source) ?(user = "source") ~mount ~content_type () = let headers = Hashtbl.create 10 in let public = match public with | Some x -> if x then Some "1" else Some "0" | None -> None in let mount = normalize_mount mount in let f (x, y) = match y with Some v -> Hashtbl.replace headers x v | None -> () in let x = match protocol with | Http _ -> let audio_info = match audio_info with | Some x -> let f x y z = let z = if z <> "" then z ^ ";" else z in Printf.sprintf "%s%s=%s" z x y in Some (Hashtbl.fold f x "") | None -> None in [ ("User-Agent", user_agent); ("ice-name", name); ("ice-genre", genre); ("ice-url", url); ("ice-public", public); ("ice-audio-info", audio_info); ("ice-description", description); ] | Icy -> [ ("icy-name", name); ("icy-url", url); ("icy-pub", public); ("icy-genre", genre); ( "icy-br", match audio_info with | None -> None | Some x -> ( try Some (Hashtbl.find x "bitrate") with Not_found -> None) ); ] in List.iter f x; { host; port; user; chunked; password; mount; content_type; protocol; headers; } (** Base 64 encoding. *) let encode64 s = let digit = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" in let extra = String.length s mod 3 in let s = match extra with 1 -> s ^ "\000\000" | 2 -> s ^ "\000" | _ -> s in let n = String.length s in let dst = Bytes.create (4 * (n / 3)) in for i = 0 to (n / 3) - 1 do let ( := ) j v = Bytes.set dst ((i * 4) + j) digit.[v] in let c j = int_of_char s.[(i * 3) + j] in let c0 = c 0 and c1 = c 1 and c2 = c 2 in 0 := c0 lsr 2; 1 := (c0 lsl 4) land 63 lor (c1 lsr 4); 2 := (c1 lsl 2) land 63 lor (c2 lsr 6); 3 := c2 land 63 done; if extra = 1 then begin Bytes.set dst ((4 * (n / 3)) - 2) '='; Bytes.set dst ((4 * (n / 3)) - 1) '=' end else if extra = 2 then Bytes.set dst ((4 * (n / 3)) - 1) '='; Bytes.to_string dst (* URL encoding/decoging according to RFC 1738, RFC 1630. * Borrowed from ocamlnet. *) (** Converts k to a 2-digit hexadecimal string. *) let to_hex2 = let hex_digits = [| '0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'; 'A'; 'B'; 'C'; 'D'; 'E'; 'F'; |] in fun k -> let s = Bytes.create 2 in Bytes.set s 0 hex_digits.((k lsr 4) land 15); Bytes.set s 1 hex_digits.(k land 15); Bytes.to_string s let url_encode s = let rec do_url_encode s s' = (* True if x is an acceptable char *) let range x = (* 0-9 *) (x >= 0x30 && x <= 0x39) (* A-Z *) || (x >= 0x41 && x <= 0x5A) || (* a-z *) (x >= 0x61 && x <= 0x7a) in match String.length s with | 0 -> s' | l -> let x = Char.code s.[0] in let s' = if not (range x) then Printf.sprintf "%s%%%s" s' (to_hex2 x) else Printf.sprintf "%s%c" s' s.[0] in do_url_encode (String.sub s 1 (l - 1)) s' in do_url_encode s "" let http_header = Printf.sprintf "%s %s HTTP/1.%d\r\n%s\r\n\r\n" let get_auth user password = Printf.sprintf "Basic %s" (encode64 (user ^ ":" ^ password)) let buf = Bytes.create 1024 (* TODO: review all reading code as this * seems a bit ad-hoc/naive.. *) (** Read and split data. * There should always be at least * one element in the resulting list. * If not, something bad happened. *) let read_data ~timeout (socket : socket) = socket#wait_for `Read timeout; try let ret = socket#read buf 0 1024 in (* split data *) let buf = Bytes.sub buf 0 ret in let rec f pos l = try let x = Bytes.index_from buf pos '\n' in f (x + 1) (Bytes.sub buf pos (x - pos - 1) :: l) with Invalid_argument _ | Not_found -> if pos < ret then Bytes.sub buf pos (ret - pos) :: l else l in let ret = f 0 [] in if List.length ret = 0 then [Bytes.of_string ""] else List.rev (f 0 []) with e -> raise (Error (Read e)) let header_string headers source = let unique_headers = Hashtbl.create 10 in Hashtbl.iter (Hashtbl.replace unique_headers) headers; (* Icy headers are of the form: %s:%s *) let sep = if source.protocol = Icy then "" else " " in (* "content-type" seems to be in lower case * for shoutcast. Also, it seems that * it is good to pass it last, see: * http://forums.winamp.com/showthread.php?threadid=285035 *) let content_label = if source.protocol = Icy then "content-type" else "Content-Type" in let f x y z = Printf.sprintf "%s:%s%s" x sep y :: z in let headers = Hashtbl.fold f unique_headers (f content_label source.content_type []) in String.concat "\r\n" headers let parse_http_answer s = let f v c s = (v, c, s) in try Scanf.sscanf s "HTTP/%s %i %[^\r^\n]" f with | Scanf.Scan_failure s -> raise (Error (Bad_answer (Some s))) | _ -> raise (Error (Bad_answer None)) let http_path_of_mount = function | Icecast_mount mount -> mount | _ -> raise (Error Invalid_usage) let connect_http c (socket : socket) source verb = let auth = get_auth source.user source.password in try let http_version = if source.chunked then 1 else 0 in let headers = Hashtbl.copy source.headers in Hashtbl.replace headers "Authorization" auth; Hashtbl.replace headers "Host" (Printf.sprintf "%s:%d" source.host source.port); if source.chunked then Hashtbl.replace headers "Transfer-Encoding" "chunked"; if verb = Put then Hashtbl.add headers "Expect" "100-continue"; let headers = header_string headers source in let request = http_header (string_of_verb verb) (http_path_of_mount source.mount) http_version headers in write_data ~timeout:c.timeout socket request; (* Read input from socket. *) let ret = read_data ~timeout:c.timeout socket in let v, code, s = parse_http_answer (Bytes.to_string (List.hd ret)) in let err = Error (Http_answer (code, s, v)) in begin match verb with | Put when code <> 100 -> raise err | v when v <> Put && (code < 200 || code >= 300) -> raise err | _ -> () end; c.chunked <- source.chunked; c.icy_cap <- true; c.status <- PrivConnected { connection = source; socket } with e -> let bt = Printexc.get_raw_backtrace () in begin try close c with _ -> () end; Printexc.raise_with_backtrace e bt let icy_id_of_mount = function | Icy_id id -> id | _ -> raise (Error Invalid_usage) let connect_icy c socket source = let password = let user = match source.user with "" -> "" | user -> Printf.sprintf "%s:" user in let id = match icy_id_of_mount source.mount with | 1 -> "" | id -> Printf.sprintf ":#%d" id in Printf.sprintf "%s%s%s" user source.password id in let request = Printf.sprintf "%s\r\n" password in try write_data ~timeout:c.timeout socket request; (* Read input from socket. *) let ret = read_data ~timeout:c.timeout socket in let f s = if s.[0] <> 'O' && s.[1] <> 'K' then raise (Error (Bad_answer (Some s))) in begin try Scanf.sscanf (Bytes.to_string (List.hd ret)) "%[^\r^\n]" f with | Scanf.Scan_failure s -> raise (Error (Bad_answer (Some s))) | Error _ as e -> raise e | _ -> raise (Error (Bad_answer None)) end; (* Read another line *) let ret = match ret with | _ :: y :: _ -> y | _ -> List.hd (read_data ~timeout:c.timeout socket) in let f _ = c.icy_cap <- true in begin try Scanf.sscanf (Bytes.to_string ret) "icy-caps:%[1]" f with Scanf.Scan_failure _ -> () end; (* Now Write headers *) let headers = header_string source.headers source in let request = Printf.sprintf "%s\r\n\r\n" headers in write_data ~timeout:c.timeout socket request; c.status <- PrivConnected { connection = source; socket } with e -> let bt = Printexc.get_raw_backtrace () in begin try close c with _ -> () end; Printexc.raise_with_backtrace e bt let connect c source = if c.status <> PrivDisconnected then raise (Error Busy); let port = if source.protocol = Icy then source.port + 1 else source.port in let socket = c.transport#connect ?bind_address:c.bind_address ?timeout:c.connection_timeout source.host port in try (* We do not know icy capabilities so far.. *) c.icy_cap <- false; match source.protocol with | Http verb -> connect_http c socket source verb | Icy -> connect_icy c socket source with e -> let bt = Printexc.get_raw_backtrace () in begin try socket#close with _ -> () end; Printexc.raise_with_backtrace e bt let http_meta_request mount charset meta headers = let unique_headers = Hashtbl.create 10 in Hashtbl.iter (Hashtbl.replace unique_headers) headers; let header = Hashtbl.fold (Printf.sprintf "%s: %s\r\n%s") unique_headers "" in Printf.sprintf "GET /admin/metadata?mode=updinfo&mount=%s%s%s HTTP/1.0\r\n%s\r\n" mount charset meta header let icy_meta_request = Printf.sprintf "GET /admin.cgi?mode=updinfo&pass=%s%s HTTP/1.0\r\n%s\r\n" let manual_update_metadata ~host ~port ~protocol ~user ~password ~mount ?(connection_timeout = 5.) ?(timeout = 30.) ?headers ?bind_address ?charset ?(transport = unix_transport) m = let mount = normalize_mount mount in let headers = match headers with Some x -> Hashtbl.copy x | None -> Hashtbl.create 0 in let socket = transport#connect ?bind_address ~timeout:connection_timeout host port in let close () = try socket#close with e -> raise (Error (Close e)) in try let charset = match charset with Some c -> Printf.sprintf "&charset=%s" c | None -> "" in let f x y z = if y <> "" then Printf.sprintf "%s&%s=%s" z (url_encode x) (url_encode y) else z in let meta = Hashtbl.fold f m "" in let request = match protocol with | Http _ -> let mount = match mount with | Icecast_mount mount -> mount | _ -> raise (Error Invalid_usage) in Hashtbl.replace headers "Authorization" (get_auth user password); Hashtbl.replace headers "Host" (Printf.sprintf "%s:%d" host port); http_meta_request mount charset meta headers | Icy -> let sid = match mount with | Icy_id id -> id | _ -> raise (Error Invalid_usage) in let meta = match sid with | 1 -> meta | sid -> Printf.sprintf "%s&sid=%d" meta sid in let user_agent = try Hashtbl.find headers "User-Agent" with Not_found -> "ocaml-cry" in let user_agent = Printf.sprintf "User-Agent: %s (Mozilla compatible)\r\n" user_agent in icy_meta_request password meta user_agent in write_data ~timeout socket request; (* Read input from socket. *) let ret = read_data ~timeout socket in let v, code, s = parse_http_answer (Bytes.to_string (List.hd ret)) in if code <> 200 then raise (Error (Http_answer (code, s, v))); close () with e -> let bt = Printexc.get_raw_backtrace () in begin try close () with _ -> () end; Printexc.raise_with_backtrace e bt let update_metadata ?charset c m = if not c.icy_cap then raise (Error Invalid_usage); let data = get_connection_data c in let source = data.connection in let user = source.user in let port = source.port in let password = source.password in let headers = Some source.headers in let protocol = source.protocol in let mount = source.mount in let host = source.host in let timeout = c.timeout in let transport = c.transport in let connection_timeout = c.connection_timeout in manual_update_metadata ~host ~port ~protocol ~user ~password ~timeout ~mount ~transport ?headers ?connection_timeout ?charset m let send ?(offset = 0) ?length c x = let length = match length with Some l -> l | None -> String.length x in let d = get_connection_data c in if c.chunked then begin if length > 0 then ( let x = Printf.sprintf "%X\r\n%s\r\n" length (String.sub x offset length) in write_data ~timeout:c.timeout d.socket x) end else write_data ~offset ~length ~timeout:c.timeout d.socket x liquidsoap-2.4.2/src/modules/cry/cry.mli000066400000000000000000000202221513273233300202250ustar00rootroot00000000000000(* * Copyright 2003-2016 Savonet team * * This file is part of Ocaml-cry. * * Ocaml-cry 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. * * Ocaml-cry 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 Ocaml-cry; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *) (** Native implementation of the shout source protocols * for icecast and shoutcast. *) (** {2 Description} * * [Cry] implements the protocols used to connect and send source data to * icecast2 and shoutcast servers. * * It is a low-level implementation that minimally manages source * connections. In particular, it does not handle synchronisation, unlike * the main implementation libshout. Hence, the task of sending audio data * to the streaming server at real time rate is left to the application. *) (** {2 Types and errors} *) type event = [ `Read | `Write | `Both ] type socket = < typ : string ; transport : transport ; file_descr : Unix.file_descr ; wait_for : ?log:(string -> unit) -> event -> float -> unit ; write : Bytes.t -> int -> int -> int ; read : Bytes.t -> int -> int -> int ; close : unit > and transport = < name : string ; protocol : string ; default_port : int ; connect : ?bind_address:string -> ?timeout:float -> ?prefer:[ `System_default | `Ipv4 | `Ipv6 ] -> string -> int -> socket > (** Possible errors. *) type error = | Create of exn | Connect of exn | Close of exn | Write of exn | Read of exn | Busy | Ssl_unavailable | Not_connected | Invalid_usage | Unknown_host of string | Bad_answer of string option | Http_answer of int * string * string exception Error of error exception Timeout (** Base unix connect *) val unix_connect : ?bind_address:string -> ?timeout:float -> ?prefer:[ `System_default | `Ipv4 | `Ipv6 ] -> string -> int -> Unix.file_descr (** Unix transport and socket. *) val unix_transport : transport val unix_socket : Unix.file_descr -> socket (** Get a string explaining an error. *) val string_of_error : exn -> string (** Possible verbs for HTTP streaming. Default: [Source] *) type verb = Put | Post | Source (** Possible protocols. [Icy] is the (undocumented) shoutcast source protocol. * [Http] is the icecast2 source protocol. * * Ogg streaming is only possible with [Http]. Any headerless format, * (e.g. mpeg containers), should work with both protocols, * provided you set the correct content-type (mime) for the source. *) type protocol = Icy | Http of verb (** Return a string representation of a protocol. *) val string_of_protocol : protocol -> string (** Special type for content-type (mime) data. *) type content_type (** General mime type for ogg data. *) val ogg_application : content_type (** Mime type for audio data encapsulated using ogg. *) val ogg_audio : content_type (** Mime type for video data encapsulated using ogg. *) val ogg_video : content_type (** Mime type for mpeg audio data (mp3). *) val mpeg : content_type (** Create a mime type from a string (e.g. "audio/aacp") *) val content_type_of_string : string -> content_type (** Get the string representation of a mime type. *) val string_of_content_type : content_type -> string (** Type for a mount point. [Icy_id] are for Shoutcast v2 * sid. For Shoutcast v1, use [Icy_id 1]. *) type mount = Icy_id of int | Icecast_mount of string (** Type for a source connection. * * [headers] is a hash table containing the headers. * See [connection] function for more details. *) type connection = { mount : mount; user : string; password : string; host : string; port : int; chunked : bool; content_type : content_type; protocol : protocol; headers : (string, string) Hashtbl.t; } (** Returns a JSON string representation of a connection. *) val string_of_connection : connection -> string (** Type for audio information. Used for connection headers. * See [audio_info] function for more details. *) type audio_info = (string, string) Hashtbl.t (** Type for metadata values. *) type metadata = (string, string) Hashtbl.t (* Type for connection data *) type connection_data = { connection : connection; socket : socket } (** Type for the status of a handler. *) type status = Connected of connection_data | Disconnected (** Type for the main handler. *) type t (** {2 API} *) (** Create a new handler. * * [bind] is not used by default (system default). * [timeout] is [30.] by default. *) val create : ?bind_address:string -> ?connection_timeout:float -> ?timeout:float -> ?transport:transport -> unit -> t (** Get a handler's status *) val get_status : t -> status (** Get a handler's ICY capabilities. * For the [Http] protocol, this is always true. * For the [Icy] protocol, this is detected upon connecting. *) val get_icy_cap : t -> bool (** Get data associated with a connection. Use it only if you know * what you are doing. * * Raises: [Error Not_connected] if not connected. *) val get_connection_data : t -> connection_data (** Create a new [audio_info] value, * filed with given values. *) val audio_info : ?samplerate:int -> ?channels:int -> ?quality:float -> ?bitrate:int -> unit -> audio_info (** Create a new [connection] value * with default values. * * [mount] is mandatory when using the [Http] protocol. * [icy_id] is mandatory to support multiple shoutcast sources * on shoutcast v2. * * [host] is ["localhost"] by default. * [password] is ["hackme"] by default. * [user] is ["source"] by default. Change [user] only if you know * what your are doing. * [protocol] is [Http] by default. * [port] is [8000] by default. * [chunked] is [false] by default and only works with HTTP/1.1-capable * servers. * * The list of preset headers for [Http] connections is: * ["User-Agent"], ["ice-name"], ["ice-genre"], * ["ice-url"], ["ice-public"], ["ice-audio-info"], * ["ice-description"]. * * The list of preset headers for [Icy] connections is: * ["User-Agent"], ["icy-name"], ["icy-url"], ["icy-pub"], * ["icy-genre"], ["icy-br"]. * * Additionally, you can also add: * ["icy-irc"], ["icy-icq"] and ["icy-aim"] but these are not added * by this function. *) val connection : ?user_agent:string -> ?name:string -> ?genre:string -> ?url:string -> ?public:bool -> ?audio_info:audio_info -> ?description:string -> ?host:string -> ?port:int -> ?chunked:bool -> ?password:string -> ?protocol:protocol -> ?user:string -> mount:mount -> content_type:content_type -> unit -> connection (** Connect a handler. *) val connect : t -> connection -> unit (** Update metadata on a handler. Useful only for non-ogg data format, * and if [icy_cap] is [true] for [Icy] connections. * * For [Icy] protocol, the relevant metadata are only ["song"] * and ["url"]. * * Raises: [Error Not_connected] * if not connected. *) val update_metadata : ?charset:string -> t -> metadata -> unit (** Manually update metadata on any source without necessarily * being connected to it for streaming. * * Optional [timeout] is [30.] by default. * * Use it only if you know what you are doing ! *) val manual_update_metadata : host:string -> port:int -> protocol:protocol -> user:string -> password:string -> mount:mount -> ?connection_timeout:float -> ?timeout:float -> ?headers:(string, string) Hashtbl.t -> ?bind_address:string -> ?charset:string -> ?transport:transport -> metadata -> unit (** Send data to a source connection. * * Raises: [Error Not_connected] * if not connected. *) val send : ?offset:int -> ?length:int -> t -> string -> unit (** Close a source connection. * * Raises: [Error Not_connected] * if not connected. *) val close : t -> unit liquidsoap-2.4.2/src/modules/cry/cry_stubs.c000066400000000000000000000055071513273233300211170ustar00rootroot00000000000000#include #include #include #include #include #include #include #include /* On native Windows platforms, many macros are not defined. */ #if (defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__ #ifndef EWOULDBLOCK #define EWOULDBLOCK EAGAIN #endif #endif #ifdef WIN32 #define Fd_val(fd) win_CRT_fd_of_filedescr(fd) #define Val_fd(fd) caml_failwith("Val_fd") #else #define Fd_val(fd) Int_val(fd) #define Val_fd(fd) Val_int(fd) #endif #ifndef WIN32 #include CAMLprim value caml_cry_poll(value _read, value _write, value _err, value _timeout) { CAMLparam3(_read, _write, _err); CAMLlocal4(_pread, _pwrite, _perr, _ret); struct pollfd *fds; nfds_t nfds = 0; nfds_t nread = 0; nfds_t nwrite = 0; nfds_t nerr = 0; int timeout; size_t last = 0; int n, ret; if (Double_val(_timeout) == -1) timeout = -1; else timeout = Double_val(_timeout) * 1000; nfds += Wosize_val(_read); nfds += Wosize_val(_write); nfds += Wosize_val(_err); fds = calloc(nfds, sizeof(struct pollfd)); if (fds == NULL) caml_raise_out_of_memory(); for (n = 0; n < Wosize_val(_read); n++) { fds[last + n].fd = Fd_val(Field(_read, n)); fds[last + n].events = POLLIN; } last += Wosize_val(_read); for (n = 0; n < Wosize_val(_write); n++) { fds[last + n].fd = Fd_val(Field(_write, n)); fds[last + n].events = POLLOUT; } last += Wosize_val(_write); for (n = 0; n < Wosize_val(_err); n++) { fds[last + n].fd = Fd_val(Field(_err, n)); fds[last + n].events = POLLERR; } caml_release_runtime_system(); ret = poll(fds, nfds, timeout); caml_acquire_runtime_system(); if (ret == -1) { free(fds); uerror("poll", Nothing); } for (n = 0; n < nfds; n++) { if (fds[n].revents & POLLIN) nread++; if (fds[n].revents & POLLOUT) nwrite++; if (fds[n].revents & POLLERR) nerr++; } _pread = caml_alloc_tuple(nread); nread = 0; _pwrite = caml_alloc_tuple(nwrite); nwrite = 0; _perr = caml_alloc_tuple(nerr); nerr = 0; for (n = 0; n < nfds; n++) { if (fds[n].revents & POLLIN) { Store_field(_pread, nread, Val_fd(fds[n].fd)); nread++; } if (fds[n].revents & POLLOUT) { Store_field(_pwrite, nwrite, Val_fd(fds[n].fd)); nwrite++; } if (fds[n].revents & POLLERR) { Store_field(_perr, nerr, Val_fd(fds[n].fd)); nerr++; } } free(fds); _ret = caml_alloc_tuple(3); Store_field(_ret, 0, _pread); Store_field(_ret, 1, _pwrite); Store_field(_ret, 2, _perr); CAMLreturn(_ret); } #else CAMLprim value caml_cry_poll(value _read, value _write, value _err, value _timeout) { caml_failwith("caml_poll"); } #endif liquidsoap-2.4.2/src/modules/cry/dune000066400000000000000000000002641513273233300176070ustar00rootroot00000000000000(library (name cry) (libraries bytes unix) (foreign_stubs (language c) (names cry_stubs)) (synopsis "OCaml client for the various icecast & shoutcast source protocols")) liquidsoap-2.4.2/src/modules/dtools/000077500000000000000000000000001513273233300174365ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/dtools/README.md000066400000000000000000000030331513273233300207140ustar00rootroot00000000000000# OCaml-dtools - OCaml daemon tools library Author: Gimenez Stéphane Email: savonet-users@lists.sourceforge.net Homepage: http://savonet.sourceforge.net/ Copyright (c) 2003-2020 the Savonet Team. # Dependencies To build this library you need to have OCaml 3.07. or later (available at http://caml.inria.fr) and ocamlfind also called findlib (available at http://www.ocaml-programming.de/packages/) # Installation This module is provided as part of the [opam](http://opam.ocaml.org/packages/dtools/). Recommended installation method is via `opam`: ``` opam install dtools ``` This installs the latest released version of this module. To compile the code from this repository, type: ``` dune build ``` To install the code from this repository using `opam`, type: ``` opam install . ``` To install the code from this repository using `dune`: ``` dune install ``` # License 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. liquidsoap-2.4.2/src/modules/dtools/dtools.ml000066400000000000000000000016711513273233300213010ustar00rootroot00000000000000(**************************************************************************) (* ocaml-dtools *) (* Copyright (C) 2003-2010 The Savonet Team *) (**************************************************************************) (* 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 *) (* any later version. *) (**************************************************************************) (* Contact: savonet-devl@lists.sourceforge.net *) (**************************************************************************) (* $Id$ *) (** ocaml-dtools. @author Stephane Gimenez *) include Dtools_impl include Dtools_syslog liquidsoap-2.4.2/src/modules/dtools/dtools.mli000066400000000000000000000211601513273233300214450ustar00rootroot00000000000000(**************************************************************************) (* ocaml-dtools *) (* Copyright (C) 2003-2010 The Savonet Team *) (**************************************************************************) (* 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 *) (* any later version. *) (**************************************************************************) (* Contact: savonet-devl@lists.sourceforge.net *) (**************************************************************************) (* $Id$ *) (** ocaml-dtools. @author Stephane Gimenez *) (** Configuration management module. *) module Conf : sig (** Type for links between keys *) type link = string (** Type for paths between keys *) type path = link list type ut = < kind : string option ; descr : string ; comments : string list ; plug : link -> ut -> unit ; subs : link list ; path : path -> ut ; routes : ut -> path list ; ut : ut > (** Type for untyped keys (or keys with unknown type) - [kind]: a string describing the type of this key - [descr]: a key description/title - [comments]: some comments on the key purposes - [plug]: a way to plug subkeys - [subs]: the list of link names to subkeys - [path]: a way to access subkeys - [routes]: a way to find paths to an other key *) type 'a t = < kind : string option ; alias : ?comments:string list -> ?descr:string -> (ut -> unit) -> 'a t ; descr : string ; comments : string list ; plug : link -> ut -> unit ; subs : link list ; path : path -> ut ; routes : ut -> path list ; ut : ut ; set_d : 'a option -> unit ; get_d : 'a option ; set : 'a -> unit ; get : 'a ; validate : ('a -> bool) -> unit ; on_change : ('a -> unit) -> unit > (** Type for 'a keys - [ut]: cast to un untyped key - [set_d]: set the default value associated to the key - [get_d]: get the default value associated to the key - [set]: set the key value according to a user demmand - [get]: retrieve the resulting key value *) (** A set of connections to others keys *) type links = (link * ut) list (** Raised on access to an undefined key (without default value) *) exception Undefined of ut (** Raised when an invalid link has been specified *) exception Invalid of string (** Raised when a specified link does not exist *) exception Unbound of ut * string (** Raised when a specified link already exist *) exception Bound of ut * string (** Raised on access to a key with a mismatching type *) exception Mismatch of ut (** Raised on cyclic plug *) exception Cyclic of ut * ut (** Raised on invalid value set *) exception Invalid_Value of ut (** Raised when bad configuration assignations are encountered *) exception Wrong_Conf of string * string (** Raised when bad configuration assignations are encountered inside configuration files *) exception File_Wrong_Conf of string * int * string (** Receipt to build a 'a key *) type 'a builder = ?d:'a -> ?p:(ut -> unit) -> ?l:links -> ?comments:string list -> string -> 'a t val unit : unit builder val int : int builder val float : float builder val bool : bool builder val string : string builder (** Some key builders *) val list : string list builder (** A structural key builder *) val void : ?p:(ut -> unit) -> ?l:links -> ?comments:string list -> string -> ut val as_unit : ut -> unit t val as_int : ut -> int t val as_float : ut -> float t val as_bool : ut -> bool t val as_string : ut -> string t (** Casts to specifically typed keys. Raises [Mismatch] on mismatching cast. *) val as_list : ut -> string list t (** Convert a dot separated string to a path *) val path_of_string : string -> path (** Convert a path to a dot separated string *) val string_of_path : path -> string (** Generate a description table of a (sub)key *) val descr : ?prefix:path -> ut -> string (** Dump the configuration table for a (sub)key *) val dump : ?prefix:path -> ut -> string (** Add a value to the configuration keys, according to the given correctly formatted string: "type key :value" Raises [Wrong_Conf] in badly formatted cases. *) val conf_set : ut -> string -> unit (** Read configuration values from the file associated with the given filename. Raises [File_Wrong_Conf] with filename line and and error message in case of a bad configuration file. *) val conf_file : ut -> string -> unit (** A set of command line options to be used with the Arg module. *) val args : ut -> (string list * Arg.spec * string) list end (** Initialisation management module. Allow to define procedures that must be executed at start up, and procedures that are to be executed at exit to have a clean quit. *) module Init : sig type t (** Root start atom *) val start : t (** Root stop atom *) val stop : t (** Define a init atom associated with the given [(unit -> unit)] procedure, which eventually depends on others atoms (these atoms will be executed before the one currently defined) and triggers other atoms (these atoms will be executed after the one currently defined). [after] and [before] allow to register the currently defined atom in the depend and triggers lists of other atoms. *) val make : ?name:string -> ?depends:t list -> ?triggers:t list -> ?after:t list -> ?before:t list -> (unit -> unit) -> t (** Same as [make] plus a shortcut for "after Init.start". *) val at_start : ?name:string -> ?depends:t list -> ?triggers:t list -> ?after:t list -> ?before:t list -> (unit -> unit) -> t (** Same as [make] plus a shortcut for "before Init.stop". *) val at_stop : ?name:string -> ?depends:t list -> ?triggers:t list -> ?after:t list -> ?before:t list -> (unit -> unit) -> t (** Launch the execution of a given init atom. *) val exec : t -> unit exception Root_prohibited of [ `User | `Group | `Both ] (** This function must be used to launch the main procedure of the program. It first execute the registered start atoms, then call the main procedure, then execute the registered stop atoms. Exceptions raised by the main procedure are caught, in order to close properly even in such cases. Exceptions are raised again after cleaning. When invoqued with [~prohibit_root:true], it checks for root access rights (euid, egid) and exit in this case. *) val init : ?prohibit_root:bool -> (unit -> unit) -> unit exception StartError of exn exception StopError of exn val conf : Conf.ut val conf_daemon : bool Conf.t val conf_daemon_pidfile : bool Conf.t val conf_daemon_pidfile_path : string Conf.t val conf_daemon_pidfile_perms : int Conf.t val conf_trace : bool Conf.t val conf_catch_exn : bool Conf.t (** A set of command line options to be used with the Arg module. *) val args : (string list * Arg.spec * string) list end module Log : sig type entry = { time : float; label : string option; level : int option; log : string; } (** Type for loggers. *) type t = < active : int -> bool ; level : int ; set_level : int -> unit ; path : Conf.path ; f : 'a. int -> ('a, unit, string, unit) format4 -> 'a ; g : 'a. ?colorize:(entry -> entry) -> int -> ('a, unit, string, unit) format4 -> 'a > type custom_log = { timestamp : bool; exec : string -> unit } (** Add a custom logging functions. *) val add_custom_log : string -> custom_log -> unit (** Remove a custom logging functions. *) val rm_custom_log : string -> unit (** Make a logger labeled according to the given path. *) val make : Conf.path -> t (** An atom that starts the logging. *) val start : Init.t (** An atom that stops the logging. *) val stop : Init.t val conf : Conf.ut val conf_level : int Conf.t val conf_unix_timestamps : bool Conf.t val conf_stdout : bool Conf.t val conf_file : bool Conf.t val conf_file_path : string Conf.t val conf_file_append : bool Conf.t val conf_file_perms : int Conf.t (** A set of command line options to be used with the Arg module. *) val args : (string list * Arg.spec * string) list end liquidsoap-2.4.2/src/modules/dtools/dtools_impl.ml000066400000000000000000000644051513273233300223260ustar00rootroot00000000000000(**************************************************************************) (* ocaml-dtools *) (* Copyright (C) 2003-2010 The Savonet Team *) (**************************************************************************) (* 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 *) (* any later version. *) (**************************************************************************) (* Contact: savonet-devl@lists.sourceforge.net *) (**************************************************************************) (* $Id$ *) (** ocaml-dtools @author Stephane Gimenez *) module Conf = struct type link = string type path = link list and ut = < kind : string option ; descr : string ; comments : string list ; plug : string -> ut -> unit ; subs : string list ; path : string list -> ut ; routes : ut -> path list ; ut : ut > type 'a t = < kind : string option ; alias : ?comments:string list -> ?descr:string -> (ut -> unit) -> 'a t ; descr : string ; comments : string list ; plug : string -> ut -> unit ; subs : string list ; path : string list -> ut ; routes : ut -> path list ; ut : ut ; set_d : 'a option -> unit ; get_d : 'a option ; set : 'a -> unit ; get : 'a ; validate : ('a -> bool) -> unit ; on_change : ('a -> unit) -> unit > type links = (string * ut) list type 'a builder = ?d:'a -> ?p:(ut -> unit) -> ?l:links -> ?comments:string list -> string -> 'a t exception Undefined of ut exception Invalid of string exception Unbound of ut * string exception Bound of ut * string exception Mismatch of ut exception Wrong_Conf of string * string exception File_Wrong_Conf of string * int * string exception Cyclic of ut * ut exception Invalid_Value of ut let path_sep_regexp = Str.regexp "\\." let list_sep_regexp = Str.regexp ":" let line_regexp = Str.regexp "^[ \t]*\\([a-zA-Z]+\\)[ \t]+\\([a-zA-Z0-9._-]+\\)[ \t]*:\\(.*\\)$" let comment_regexp = Str.regexp "^[ ]*\\(#.*\\)?$" let check s = if Str.string_match path_sep_regexp s 0 then raise (Invalid s) let make kind ?(d : 'a option) ?(p : ut -> unit = fun _ -> ()) ?(l : links = []) ?(comments : string list = []) descr : 'a t = object (self) val kind : string option = kind val mutable descr : string = descr val mutable comments : string list = comments val mutable links : links = [] val value_d : 'a option ref = ref d val value : 'a option ref = ref None val mutable validators : ('a -> bool) list = [] val mutable listeners : ('a -> unit) list = [] initializer p self#ut; List.iter (fun (s, t) -> self#plug s t) l method subs = List.sort compare (List.map fst links) method private sub (s : string) : ut = check s; try List.assoc s links with Not_found -> raise (Unbound (self#ut, s)) method path (l : string list) : ut = match l with [] -> self#ut | s :: q -> (self#sub s)#path q method routes (st : ut) = (* todo: cache already accessed nodes *) let rec aux l t = match t = st with | true -> [List.rev l] | false -> List.concat (List.map (fun s -> aux (s :: l) (t#path [s])) t#subs) in aux [] self#ut method kind = kind method descr = descr method private set_descr new_descr = descr <- new_descr method comments = comments method private set_comments new_comments = comments <- new_comments method plug s t = if t#routes self#ut <> [] then raise (Cyclic (self#ut, t)); if List.mem_assoc s links then raise (Bound (self#ut, s)); links <- (s, t) :: links (* Nice hack. heh! *) method alias ?comments ?descr p = let maybe f x = match x with Some x -> f x | None -> () in let old_comments = self#comments in let old_descr = self#descr in maybe self#set_comments comments; maybe self#set_descr descr; let key = Oo.copy self in p key#ut; self#set_comments old_comments; self#set_descr old_descr; key method ut = (self :> ut) method get_d : 'a option = !value_d method set_d (v : 'a option) : unit = value_d := v method get : 'a = match !value with | None -> ( match !value_d with | None -> raise (Undefined self#ut) | Some v -> v) | Some v -> v method set (v : 'a) : unit = List.iter (fun fn -> if not (fn v) then raise (Invalid_Value self#ut)) validators; value := Some v; List.iter (fun fn -> fn v) listeners method validate (fn : 'a -> bool) : unit = validators <- fn :: validators method on_change (fn : 'a -> unit) : unit = listeners <- fn :: listeners end let void ?p ?l ?comments descr = (make None ?p ?l ~d:None ?comments descr)#ut let unit ?d = make (Some "unit") ?d let int ?d = make (Some "int") ?d let float ?d = make (Some "float") ?d let bool ?d = make (Some "bool") ?d let string ?d = make (Some "string") ?d let list ?d = make (Some "list") ?d (* Harmful function, do not use *) let force_type c (t : ut) : 'a t = match t#kind with | Some x when x = c -> (Obj.magic t : 'a t) | _ -> raise (Mismatch t) let as_unit t : unit t = force_type "unit" t let as_int t : int t = force_type "int" t let as_float t : float t = force_type "float" t let as_bool t : bool t = force_type "bool" t let as_string t : string t = force_type "string" t let as_list t : string list t = force_type "list" t let path_of_string p = Str.split path_sep_regexp p let string_of_path p = String.concat "." p let get_string (t : ut) = try match t#kind with | None -> None | Some "unit" -> Some "" | Some "int" -> Some (string_of_int (as_int t)#get) | Some "float" -> Some (string_of_float (as_float t)#get) | Some "bool" -> Some (string_of_bool (as_bool t)#get) | Some "string" -> Some (as_string t)#get | Some "list" -> Some (String.concat ":" (as_list t)#get) | _ -> assert false with Undefined _ -> None let get_d_string (t : ut) = let mapopt f = function None -> None | Some x -> Some (f x) in try match t#kind with | None -> None | Some "unit" -> mapopt (fun () -> "") (as_unit t)#get_d | Some "int" -> mapopt string_of_int (as_int t)#get_d | Some "float" -> mapopt string_of_float (as_float t)#get_d | Some "bool" -> mapopt string_of_bool (as_bool t)#get_d | Some "string" -> (as_string t)#get_d | Some "list" -> mapopt (String.concat ":") (as_list t)#get_d | _ -> assert false with Undefined _ -> None let descr ?(prefix = []) (t : ut) = let rec aux prefix t = let p s = if prefix = "" then s else prefix ^ "." ^ s in let subs = List.map (function s -> aux (p s) (t#path [s])) t#subs in Printf.sprintf "## %s\n" t#descr ^ begin match get_d_string t with | None -> "" | Some d -> Printf.sprintf "# default :%s\n" d end ^ begin match (t#kind, get_string t) with | Some k, None -> Printf.sprintf "#%s\t%-30s\n" k prefix | Some k, Some p -> Printf.sprintf "%s\t%-30s :%s\n" k prefix p | _ -> "" end ^ begin match t#comments with | [] -> "" | l -> "# comments:\n" ^ String.concat "" (List.map (fun s -> Printf.sprintf "# %s\n" s) l) end ^ "\n" ^ String.concat "" subs in aux (string_of_path prefix) (t#path prefix) let dump ?(prefix = []) (t : ut) = let rec aux prefix t = let p s = if prefix = "" then s else prefix ^ "." ^ s in let subs = List.map (function s -> aux (p s) (t#path [s])) t#subs in begin match t#kind with | Some k -> ( match (get_d_string t, get_string t) with | None, None -> Printf.sprintf "#%s\t%-30s\n" k prefix | Some p, None -> Printf.sprintf "#%s\t%-30s :%s\n" k prefix p | Some p, Some p' when p' = p -> Printf.sprintf "#%s\t%-30s :%s\n" k prefix p | _, Some p -> Printf.sprintf "%s\t%-30s :%s\n" k prefix p) | _ -> "" end ^ String.concat "" subs in aux (string_of_path prefix) (t#path prefix) let conf_set (t : ut) s = if Str.string_match line_regexp s 0 then ( let val0 = Str.matched_group 1 s in let val1 = Str.matched_group 2 s in let val2 = Str.matched_group 3 s in let st = t#path (path_of_string val1) in match val0 with | "unit" -> ( match val2 = "" with | false -> raise (Wrong_Conf (s, "unit expected")) | true -> (as_unit st)#set ()) | "int" -> let i = try int_of_string val2 with Invalid_argument _ -> raise (Wrong_Conf (s, "integer expected")) in (as_int st)#set i | "float" -> let f = try float_of_string val2 with Invalid_argument _ -> raise (Wrong_Conf (s, "float expected")) in (as_float st)#set f | "bool" -> let b = try bool_of_string val2 with Invalid_argument _ -> raise (Wrong_Conf (s, "boolean expected")) in (as_bool st)#set b | "string" -> let s = val2 in (as_string st)#set s | "list" -> let l = Str.split list_sep_regexp val2 in (as_list st)#set l | _ -> raise (Wrong_Conf (s, "unknown type"))) else raise (Wrong_Conf (s, "syntax error")) let conf_file t s = let nb = ref 0 in let f = open_in s in try while true do nb := !nb + 1; let l = input_line f in if Str.string_match comment_regexp l 0 then () else begin try conf_set t l with Wrong_Conf (_, y) -> raise (File_Wrong_Conf (s, !nb, y)) end done with End_of_file -> () let args t = [ ( ["--conf-file"; "-f"], Arg.String (conf_file t), "read the given configuration file" ); ( ["--conf-set"; "-s"], Arg.String (conf_set t), "apply the given configuration assignation" ); ( ["--conf-descr-key"], Arg.String (fun p -> Printf.printf "%s" (descr ~prefix:(path_of_string p) t); exit 0), "describe a configuration key" ); ( ["--conf-descr"], Arg.Unit (fun () -> Printf.printf "%s" (descr t); exit 0), "display a described table of the configuration keys" ); ( ["--conf-dump"], Arg.Unit (fun () -> Printf.printf "%s" (dump t); exit 0), "dump the configuration state" ); ] end module Init = struct let conf = Conf.void "initialization configuration" (* Unix.fork is not implemented in Win32. *) let daemon_conf = if Sys.os_type <> "Win32" then conf else Conf.void "dummy conf" let conf_daemon = Conf.bool ~p:(daemon_conf#plug "daemon") ~d:false "run in daemon mode" let conf_daemon_pidfile = Conf.bool ~p:(conf_daemon#plug "pidfile") ~d:false "support for pidfile generation" let conf_daemon_pidfile_path = Conf.string ~p:(conf_daemon_pidfile#plug "path") "path to pidfile" let conf_daemon_pidfile_perms = Conf.int ~d:0o640 ~p:(conf_daemon_pidfile#plug "perms") "Unix file permissions for pidfile. Default: `0o640`." let conf_daemon_drop_user = Conf.bool ~p:(conf_daemon#plug "change_user") ~d:false "Changes the effective user (drops privileges)." let conf_daemon_user = Conf.string ~p:(conf_daemon_drop_user#plug "user") ~d:"daemon" "User used to run the daemon." let conf_daemon_group = Conf.string ~p:(conf_daemon_drop_user#plug "group") ~d:"daemon" "Group used to run the daemon." let conf_trace = Conf.bool ~p:(conf#plug "trace") ~d:false "dump an initialization trace" let conf_catch_exn = Conf.bool ~p:(conf#plug "catch_exn") ~d:true "catch exceptions, use false to backtrace exceptions" type t = { name : string; mutable launched : bool; mutable depends : t list; mutable triggers : t list; mutable mutex : Mutex.t; f : unit -> unit; } let make ?(name = "") ?(depends = []) ?(triggers = []) ?(after = []) ?(before = []) f = let na = { name; launched = false; depends; triggers; mutex = Mutex.create (); f } in List.iter (fun a -> a.triggers <- na :: a.triggers) after; List.iter (fun a -> a.depends <- na :: a.depends) before; na let start = make ~name:"init-start" flush_all let stop = make ~name:"init-stop" flush_all let at_start ?name ?depends ?triggers ?after ?before f = let a = make ?name ?depends ?triggers ?after ?before f in start.triggers <- a :: start.triggers; a let at_stop ?name ?depends ?triggers ?after ?before f = let a = make ?name ?depends ?triggers ?after ?before f in stop.depends <- a :: stop.depends; a let rec exec a = let log = if conf_trace#get then fun s -> let id = Thread.id (Thread.self ()) in Printf.printf "init(%i):%-35s@%s\n%!" id a.name s else fun _ -> () in log "called"; Mutex.lock a.mutex; try if not a.launched then begin a.launched <- true; log "start"; log "start-depends"; List.iter exec a.depends; log "stop-depends"; log "start-atom"; a.f (); log "stop-atom"; log "start-triggers"; List.iter exec a.triggers; log "stop-triggers"; log "stop" end; Mutex.unlock a.mutex; log "return" with e -> Mutex.unlock a.mutex; raise e let rec wait_signal () = try ignore (Thread.wait_signal [Sys.sigterm; Sys.sigint]) with | Unix.Unix_error (Unix.EINTR, _, _) -> () | Sys_error s when s = "Thread.wait_signal: Interrupted system call" -> wait_signal () exception StartError of exn exception StopError of exn (* Dummy functions in the case where * Printexc does not have the required * functions. *) let get_backtrace () = "ocaml-dtools not compiled with ocaml >= 3.11, cannot print stack backtrace" (* For the compiler.. *) let () = ignore (get_backtrace ()) open Printexc let main f () = begin try exec start with e -> raise (StartError e) end; let quit pid = if Sys.os_type <> "Win32" then Unix.kill pid Sys.sigterm in let thread pid = try f (); quit pid with e -> let se = Printexc.to_string e in Printf.eprintf "init: exception encountered during main phase:\n %s\n%!" se; Printf.eprintf "exception: %s\n%s%!" se (get_backtrace ()); if conf_catch_exn#get then quit pid else raise e in let th = Thread.create thread (Unix.getpid ()) in if Sys.os_type <> "Win32" then wait_signal () else Thread.join th; try exec stop with e -> raise (StopError e) let catch f clean = try f (); clean () with | StartError e -> Printf.eprintf "init: exception encountered during start phase:\n %s\n%!" (Printexc.to_string e); clean (); exit (-1) | StopError e -> Printf.eprintf "init: exception encountered during stop phase:\n %s\n%!" (Printexc.to_string e); clean (); exit (-1) (** A function to reopen a file descriptor * Thanks to Xavier Leroy! * Ref: http://caml.inria.fr/pub/ml-archives/caml-list/2000/01/ * a7e3bbdfaab33603320d75dbdcd40c37.en.html *) let reopen_out outchan filename = flush outchan; let fd1 = Unix.descr_of_out_channel outchan in let fd2 = Unix.openfile filename [Unix.O_WRONLY] 0o666 in Unix.dup2 fd2 fd1; Unix.close fd2 (** The same for inchan *) let reopen_in inchan filename = let fd1 = Unix.descr_of_in_channel inchan in let fd2 = Unix.openfile filename [Unix.O_RDONLY] 0o666 in Unix.dup2 fd2 fd1; Unix.close fd2 let daemonize () = if Unix.fork () <> 0 then exit 0; (* Detach from the console *) if Unix.setsid () < 0 then exit 1; (* Refork.. *) if Unix.fork () <> 0 then exit 0; (* Change umask to 0 *) ignore (Unix.umask 0); (* chdir to / *) Unix.chdir "/"; if conf_daemon_pidfile#get then begin (* Write PID to file *) let filename = conf_daemon_pidfile_path#get in let f = open_out_gen [Open_wronly; Open_creat; Open_trunc] conf_daemon_pidfile_perms#get filename in let pid = Unix.getpid () in output_string f (string_of_int pid); output_char f '\n'; close_out f end; (* Reopen usual file descriptor *) reopen_in stdin "/dev/null"; reopen_out stdout "/dev/null"; reopen_out stderr "/dev/null" let cleanup_daemon () = if conf_daemon_pidfile#get then ( try let filename = conf_daemon_pidfile_path#get in Sys.remove filename with _ -> ()) exception Root_prohibited of [ `User | `Group | `Both ] let exit_when_root () = (* Change user.. *) if conf_daemon_drop_user#get then begin let grd = Unix.getgrnam conf_daemon_group#get in let gid = grd.Unix.gr_gid in if Unix.getegid () <> gid then Unix.setgid gid; let pwd = Unix.getpwnam conf_daemon_user#get in let uid = pwd.Unix.pw_uid in if Unix.geteuid () <> uid then Unix.setuid uid end; match (Unix.geteuid (), Unix.getegid ()) with | 0, 0 -> raise (Root_prohibited `Both) | 0, _ -> raise (Root_prohibited `User) | _, 0 -> raise (Root_prohibited `Group) | _ -> () let init ?(prohibit_root = false) f = if prohibit_root then exit_when_root (); if conf_daemon#get && Sys.os_type <> "Win32" then daemonize (); let signal_h _ = () in Sys.set_signal Sys.sigterm (Sys.Signal_handle signal_h); Sys.set_signal Sys.sigint (Sys.Signal_handle signal_h); (* We block signals that would kill us, * we'll wait for them and shutdown cleanly. * On Windows this is impossible so the only way for the application * to shutdown is to terminate the main function [f]. *) if Sys.os_type <> "Win32" then ignore (Unix.sigprocmask Unix.SIG_BLOCK [Sys.sigterm; Sys.sigint]); let cleanup = if conf_daemon#get && Sys.os_type <> "Win32" then cleanup_daemon else fun () -> () in catch (main f) cleanup let args = if Sys.os_type <> "Win32" then [ ( ["-d"; "--daemon"], Arg.Unit (fun () -> conf_daemon#set true), "Run in daemon mode." ); ] else [] end module Log = struct type entry = { time : float; label : string option; level : int option; log : string; } type pending_entry = { colorize : entry -> entry; entry : entry } type t = < active : int -> bool ; level : int ; set_level : int -> unit ; path : Conf.path ; f : 'a. int -> ('a, unit, string, unit) format4 -> 'a ; g : 'a. ?colorize:(entry -> entry) -> int -> ('a, unit, string, unit) format4 -> 'a > type custom_log = { timestamp : bool; exec : string -> unit } let log_ch = ref None (* Custom logging methods. *) let custom_log : (string, custom_log) Hashtbl.t = Hashtbl.create 0 let add_custom_log name f = Hashtbl.replace custom_log name f let rm_custom_log name = Hashtbl.remove custom_log name let conf = Conf.void "log configuration" let conf_level = Conf.int ~p:(conf#plug "level") ~d:3 "general log level" let conf_unix_timestamps = Conf.bool ~p:(conf#plug "unix_timestamps") ~d:false "display unix timestamps (subsecond accuracy, timezone independent)" let conf_file = Conf.bool ~p:(conf#plug "file") ~d:true "log to file" let conf_file_path = Conf.string ~p:(conf_file#plug "path") "path to log file" let conf_file_append = Conf.bool ~p:(conf_file#plug "append") ~d:true "append log to the file" let conf_file_perms = Conf.int ~p:(conf_file#plug "perms") ~d:0o600 "log file permissions" let conf_stdout = Conf.bool ~p:(conf#plug "stdout") ~d:false "log to stdout" let timestamp time = match conf_unix_timestamps#get with | true -> Printf.sprintf "%f" time | false -> let date = Unix.localtime time in Printf.sprintf "%d/%02d/%02d %02d:%02d:%02d" (date.Unix.tm_year + 1900) (date.Unix.tm_mon + 1) date.Unix.tm_mday date.Unix.tm_hour date.Unix.tm_min date.Unix.tm_sec let message ?(show_timestamp = true) { time; label; level; log } = let label = match (label, level) with | None, None -> "" | Some l, None -> Printf.sprintf "[%s] " l | None, Some d -> Printf.sprintf "[%d] " d | Some l, Some d -> Printf.sprintf "[%s:%d] " l d in let str = label ^ log in let timestamp = if show_timestamp then timestamp time ^ " " else "" in Printf.sprintf "%s%s" timestamp str let print { colorize; entry } = let to_stdout = conf_stdout#get in let to_file = !log_ch <> None in begin match to_stdout || to_file with | true -> let do_stdout () = Printf.printf "%s\n%!" (message (colorize entry)) in let do_file () = match !log_ch with | None -> () | Some ch -> Printf.fprintf ch "%s\n%!" (message entry) in if to_stdout then do_stdout (); if to_file then do_file () | false -> () end; let f _ x = x.exec (message ~show_timestamp:x.timestamp entry) in Hashtbl.iter f custom_log (* Avoid interlacing logs *) let log_mutex = Mutex.create () let log_condition = Condition.create () let log_queue = ref (Queue.create ()) let log_stop = ref false let log_thread = ref None let mutexify f x = Mutex.lock log_mutex; try let ret = f x in Mutex.unlock log_mutex; ret with e -> Mutex.unlock log_mutex; raise e let rotate_queue () = let new_q = Queue.create () in mutexify (fun () -> let q = !log_queue in log_queue := new_q; q) () let flush_queue () = let rec flush q = Queue.iter print q; let q = rotate_queue () in if not (Queue.is_empty q) then flush q in flush (rotate_queue ()) let log_thread_fn () = let rec f () = flush_queue (); let log_stop = mutexify (fun () -> if !log_stop then true else begin Condition.wait log_condition log_mutex; !log_stop end) () in if not log_stop then f () in f () let proceed = mutexify (fun entry -> Queue.push entry !log_queue; Condition.signal log_condition) let make path : t = let path_str = Conf.string_of_path path in let conf_level = ref (fun () -> conf_level#get) in object (self : t) method path = path method active level = level <= !conf_level () method level = !conf_level () method set_level level = conf_level := fun () -> level method g ?(colorize = fun x -> x) level = match self#active level with | true -> let time = Unix.gettimeofday () in Printf.ksprintf (fun s -> List.iter (fun log -> proceed { colorize; entry = { time; label = Some path_str; level = Some level; log; }; }) (String.split_on_char '\n' s)) | false -> Printf.ksprintf (fun _ -> ()) method f level = self#g ?colorize:None level end let init () = let time = Unix.gettimeofday () in let reopen_log = if conf_file#get then begin let opts = [Open_wronly; Open_creat; Open_nonblock] @ if conf_file_append#get then [Open_append] else [Open_trunc] in let log_file_path = conf_file_path#get in let log_file_perms = conf_file_perms#get in log_ch := Some (open_out_gen opts log_file_perms log_file_path); fun _ -> begin match !log_ch with | None -> () | Some ch -> log_ch := None; close_out ch end; log_ch := Some (open_out_gen opts log_file_perms log_file_path) end else fun _ -> () in (* Re-open log file on SIGUSR1 -- for logrotate *) if Sys.os_type <> "Win32" then Sys.set_signal Sys.sigusr1 (Sys.Signal_handle reopen_log); print { colorize = (fun x -> x); entry = { time; level = None; label = None; log = ">>> LOG START" }; }; log_thread := Some (Thread.create log_thread_fn ()) let start = Init.make ~name:"init-log-start" ~before:[Init.start] init let close () = let time = Unix.gettimeofday () in mutexify (fun () -> log_stop := true) (); proceed { colorize = (fun x -> x); entry = { time; level = None; label = None; log = ">>> LOG END" }; }; begin match !log_thread with | None -> () | Some th -> log_thread := None; Condition.signal log_condition; Thread.join th end; match !log_ch with | None -> () | Some ch -> log_ch := None; close_out ch let stop = Init.make ~name:"init-log-stop" ~after:[Init.stop] close let args = [ ( ["--log-stdout"], Arg.Unit (fun () -> conf_stdout#set true), "log also to stdout" ); ( ["--log-file"; "-l"], Arg.String (fun s -> conf_file#set true; conf_file_path#set s), "log file" ); ] end liquidsoap-2.4.2/src/modules/dtools/dtools_syslog.impl.ml000066400000000000000000000046511513273233300236420ustar00rootroot00000000000000(**************************************************************************) (* ocaml-dtools *) (* Copyright (C) 2003-2010 The Savonet Team *) (**************************************************************************) (* 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 *) (* any later version. *) (**************************************************************************) (* Contact: savonet-devl@lists.sourceforge.net *) (**************************************************************************) (* $Id$ *) (* Syslog logging. *) open Dtools_impl let log = Log.make ["syslog"] let conf_syslog = Conf.bool ~p:(Log.conf#plug "syslog") ~d:false "Enable syslog logging." let conf_program = Conf.string ~p:(conf_syslog#plug "program") ~d:(Filename.basename Sys.executable_name) "Name of the program." let conf_facility = Conf.string ~p:(conf_syslog#plug "facility") ~d:"DAEMON" "Logging facility." let conf_level = Conf.string ~p:(onf_syslog#plug "level") ~d:"info" "Logging level. One of: `\"emergency\"`, `\"alert\"`, `\"critical\"`, \ `\"error\"`, `\"warning\"`, `\"notice\"`, `\"info\"` or `\"debug\"`." let level_of_string = function | "emergency" -> `LOG_EMERG | "alert" -> `LOG_ALERT | "critical" -> `LOG_CRIT | "error" -> `LOG_ERR | "warning" -> `LOG_WARNING | "notice" -> `LOG_NOTICE | "info" -> `LOG_INFO | "debug" -> `LOG_DEBUG | s -> log#critical "Invalid log level: %s" s; `LOG_INFO let logging = ref None let () = let start () = if conf_syslog#get then ( let facility = Syslog.facility_of_string conf_facility#get in let program = Printf.sprintf "%s[%d]" conf_program#get (Unix.getpid ()) in let log = Syslog.openlog ~facility program in logging := Some log; let log_level = level_of_string conf_level#get in let exec s = Syslog.syslog log log_level s in Log.add_custom_log program { Log.timestamp = false; exec }) in let stop () = match !logging with Some x -> Syslog.closelog x | _ -> () in ignore (Init.at_start ~before:[Log.start] start); ignore (Init.at_stop ~after:[Log.stop] stop) liquidsoap-2.4.2/src/modules/dtools/dtools_syslog.noop.ml000066400000000000000000000000131513273233300236400ustar00rootroot00000000000000(* noop *) liquidsoap-2.4.2/src/modules/dtools/dune000066400000000000000000000003601513273233300203130ustar00rootroot00000000000000(library (name dtools) (libraries str threads unix (select dtools_syslog.ml from (syslog -> dtools_syslog.impl.ml) (-> dtools_syslog.noop.ml))) (synopsis "Library providing various helper functions to make daemons")) liquidsoap-2.4.2/src/modules/duppy/000077500000000000000000000000001513273233300172735ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/duppy/.merlin000066400000000000000000000000441513273233300205600ustar00rootroot00000000000000B src/** S src/** B +threads PKG re liquidsoap-2.4.2/src/modules/duppy/README.md000066400000000000000000000013531513273233300205540ustar00rootroot00000000000000# ocaml-duppy ocaml-duppy is an advanced scheduler for Ocaml programmers. Please read the COPYING file before using this software. ## Documentation The API is documented here: https://www.liquidsoap.info/ocaml-duppy/ ## Prerequisites: - ocaml - findlib - ocaml-re - dune ## Compilation: ```sh $ dune build ``` This should build both the native and the byte-code version of the extension library. ## Installation: Via `opam`: ```sh $ opam install duppy ``` Via `dune` (for developers): ```sh $ dune install ``` This should install the library file (using ocamlfind) in the appropriate place. ## Author: This author of this software may be contacted by electronic mail at the following address: savonet-users@lists.sourceforge.net. liquidsoap-2.4.2/src/modules/duppy/dune000066400000000000000000000002221513273233300201450ustar00rootroot00000000000000(library (name duppy) (libraries unix threads re) (foreign_stubs (language c) (names duppy_stubs)) (synopsis "OCaml advanced scheduler")) liquidsoap-2.4.2/src/modules/duppy/duppy.ml000066400000000000000000000774351513273233300210060ustar00rootroot00000000000000(***************************************************************************** Duppy, a task scheduler for OCaml. Copyright 2003-2010 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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 *****************************************************************************) module Pcre = Re.Pcre type fd = Unix.file_descr external poll : Unix.file_descr array -> Unix.file_descr array -> Unix.file_descr array -> float -> Unix.file_descr array * Unix.file_descr array * Unix.file_descr array = "caml_poll" let poll r w e timeout = let r = Array.of_list r in let w = Array.of_list w in let e = Array.of_list e in let r, w, e = poll r w e timeout in (Array.to_list r, Array.to_list w, Array.to_list e) let select, select_fname = match Sys.os_type with | "Unix" -> (poll, "poll") | _ -> (Unix.select, "select") (** [remove f l] is like [List.find f l] but also returns the result of removing * the found element from the original list. *) let remove f l = let rec aux acc = function | [] -> raise Not_found | x :: l -> if f x then (x, List.rev_append acc l) else aux (x :: acc) l in aux [] l (** Events and tasks from the implementation point-of-view: * we have to hide the 'a parameter. *) type e = { r : fd list; w : fd list; x : fd list; t : float } type 'a t = { prio : 'a; enrich : e -> e; is_ready : e -> (unit -> 'a t list) option; } type 'a scheduler = { on_error : exn -> Printexc.raw_backtrace -> unit; out_pipe : Unix.file_descr; in_pipe : Unix.file_descr; compare : 'a -> 'a -> int; select_m : Mutex.t; mutable tasks : 'a t list; tasks_m : Mutex.t; mutable ready : ('a * (unit -> 'a t list)) list; ready_m : Mutex.t; mutable queues : Condition.t list; queues_m : Mutex.t; mutable stop : bool; stop_m : Mutex.t; queue_stopped_c : Condition.t; } let clear_tasks s = Mutex.lock s.tasks_m; s.tasks <- []; Mutex.unlock s.tasks_m let create ?(on_error = Printexc.raise_with_backtrace) ?(compare = compare) () = let out_pipe, in_pipe = Unix.pipe () in if not Sys.win32 then Unix.set_nonblock in_pipe; { on_error; out_pipe; in_pipe; compare; select_m = Mutex.create (); tasks = []; tasks_m = Mutex.create (); ready = []; ready_m = Mutex.create (); queues = []; queues_m = Mutex.create (); stop = false; stop_m = Mutex.create (); queue_stopped_c = Condition.create (); } let wake_up s = try ignore (Unix.write s.in_pipe (Bytes.of_string "x") 0 1) with | Unix.Unix_error (Unix.EAGAIN, _, _) | Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> () module Task = struct (** Events and tasks from the user's point-of-view. *) type event = [ `Delay of float | `Write of fd | `Read of fd | `Exception of fd ] type ('a, 'b) task = { priority : 'a; events : 'b list; handler : 'b list -> ('a, 'b) task list; } let time () = Unix.gettimeofday () let rec t_of_task (task : ('a, [< event ]) task) = let t0 = time () in { prio = task.priority; enrich = (fun e -> List.fold_left (fun e -> function | `Delay s -> { e with t = min e.t (t0 +. s) } | `Read s -> { e with r = s :: e.r } | `Write s -> { e with w = s :: e.w } | `Exception s -> { e with x = s :: e.x }) e task.events); is_ready = (fun e -> let l = List.filter (fun evt -> match (evt :> event) with | `Delay s when time () > t0 +. s -> true | `Read s when List.mem s e.r -> true | `Write s when List.mem s e.w -> true | `Exception s when List.mem s e.x -> true | _ -> false) task.events in if l = [] then None else Some (fun () -> List.map t_of_task (task.handler l))); } let add_t s items = let f item = match item.is_ready { r = []; w = []; x = []; t = 0. } with | Some f -> Mutex.lock s.ready_m; s.ready <- (item.prio, f) :: s.ready; Mutex.unlock s.ready_m | None -> Mutex.lock s.tasks_m; s.tasks <- item :: s.tasks; Mutex.unlock s.tasks_m in List.iter f items; wake_up s let add s t = add_t s [t_of_task t] end open Task let stop s = clear_tasks s; Mutex.lock s.stop_m; s.stop <- true; Mutex.unlock s.stop_m; Mutex.lock s.queues_m; while List.length s.queues > 0 do wake_up s; Mutex.lock s.ready_m; List.iter Condition.signal s.queues; Mutex.unlock s.ready_m; Condition.wait s.queue_stopped_c s.queues_m done; Mutex.unlock s.queues_m let tmp = Bytes.create 1024 (** There should be only one call of #process at a time. * Process waits for tasks to become ready, and moves ready tasks * to the ready queue. *) let process s log = (* Compute the union of all events. *) let e = List.fold_left (fun e t -> t.enrich e) { r = [s.out_pipe]; w = []; x = []; t = infinity } s.tasks in (* Poll for an event. *) let r, w, x = let rec f () = try let timeout = if e.t = infinity then -1. else max 0. (e.t -. time ()) in log (Printf.sprintf "Enter %s at %f, timeout %f (%d/%d/%d)." select_fname (time ()) timeout (List.length e.r) (List.length e.w) (List.length e.x)); let r, w, x = select e.r e.w e.x timeout in log (Printf.sprintf "Left %s at %f (%d/%d/%d)." select_fname (time ()) (List.length r) (List.length w) (List.length x)); (r, w, x) with | Unix.Unix_error (Unix.EINTR, _, _) -> (* [EINTR] means that select was interrupted by * a signal before any of the selected events * occurred and before the timeout interval expired. * We catch it and restart.. *) log (Printf.sprintf "Select interrupted at %f." (time ())); f () | e -> (* Uncaught exception: * 1) Discards all tasks currently in the loop (we do not know which * socket caused an error). * 2) Re-Raise e *) clear_tasks s; raise e in f () in (* Empty the wake_up pipe if needed. *) let () = if List.mem s.out_pipe r then (* For safety, we may absorb more than * one write. This avoids bad situation * when exceesive wake_up may fill up the * pipe's write buffer, causing a wake_up * to become blocking.. *) ignore (Unix.read s.out_pipe tmp 0 1024) in (* Move ready tasks to the ready list. *) let e = { r; w; x; t = 0. } in Mutex.lock s.tasks_m; (* Split [tasks] into [r]eady and still [w]aiting. *) let r, w = List.fold_left (fun (r, w) t -> match t.is_ready e with | Some f -> ((t.prio, f) :: r, w) | None -> (r, t :: w)) ([], []) s.tasks in s.tasks <- w; Mutex.unlock s.tasks_m; Mutex.lock s.ready_m; s.ready <- List.stable_sort (fun (p, _) (p', _) -> s.compare p p') (s.ready @ r); Mutex.unlock s.ready_m (** Code for a queue to process ready tasks. * Returns true a task was found (and hence processed). * * s.ready_m *must* be locked before calling * this function, and is freed *only* * if some task was processed. *) let exec s (priorities : 'a -> bool) = (* This assertion does not work on * win32 because a thread can double-lock * the same mutex.. *) if Sys.os_type <> "Win32" then assert (not (Mutex.try_lock s.ready_m)); match remove (fun (p, _) -> priorities p) s.ready with | (_, task), remaining -> s.ready <- remaining; Mutex.unlock s.ready_m; let tasks = match task () with | exception exn -> let bt = Printexc.get_raw_backtrace () in s.on_error exn bt; [] | v -> v in add_t s tasks; true | exception Not_found -> false exception Queue_stopped exception Queue_processed (** Main loop for queues. *) let queue ?log ?(priorities = fun _ -> true) s name = let log = match log with Some e -> e | None -> Printf.printf "queue %s: %s\n" name in let c = let c = Condition.create () in Mutex.lock s.queues_m; s.queues <- c :: s.queues; Mutex.unlock s.queues_m; log (Printf.sprintf "Queue #%d starting..." (List.length s.queues)); c in (* Try to process ready tasks, otherwise try to become the master, * or be a slave and wait for the master to get some more ready tasks. *) let run () = Mutex.lock s.stop_m; let stop = s.stop in Mutex.unlock s.stop_m; if stop then raise Queue_stopped; (* Lock the ready tasks until the queue has a task to proceed, * *or* is really ready to restart on its condition, see the * Condition.wait call below for the atomic unlock and wait. *) Mutex.lock s.ready_m; log (Printf.sprintf "There are %d ready tasks." (List.length s.ready)); if exec s priorities then raise Queue_processed; let wake () = let is_ready = Mutex.lock s.ready_m; let is_ready = s.ready <> [] in Mutex.unlock s.ready_m; is_ready in (* Wake up other queues if there are remaining tasks *) if is_ready then begin Mutex.lock s.queues_m; List.iter (fun x -> if x <> c then Condition.signal x) s.queues; Mutex.unlock s.queues_m end in if Mutex.try_lock s.select_m then begin (* Processing finished for me * I can unlock ready_m now.. *) Mutex.unlock s.ready_m; process s log; Mutex.unlock s.select_m; wake () end else begin (* We use s.ready_m mutex here. * Hence, we avoid race conditions * with any other queue being processing * a task that would create a new task: * without this mutex, the new task may not be * notified to this queue if it is going to sleep * in concurrency.. * It also avoid race conditions when restarting * queues since s.ready_m is locked until all * queues have been signaled. *) Condition.wait c s.ready_m; Mutex.unlock s.ready_m end in let rec f () = begin try run () with Queue_processed -> () end; (f [@tailcall]) () in let on_done () = Mutex.lock s.queues_m; s.queues <- List.filter (fun q -> q <> c) s.queues; Condition.signal s.queue_stopped_c; Mutex.unlock s.queues_m in (try f () with | Queue_stopped -> () | exn -> let bt = Printexc.get_raw_backtrace () in (try on_done () with _ -> ()); Printexc.raise_with_backtrace exn bt); on_done () module Async = struct (* m is used to make sure that * calls to [wake_up] and [stop] * are thread-safe. *) type t = { stop : bool ref; mutable fd : fd option; m : Mutex.t } exception Stopped let add ~priority (scheduler : 'a scheduler) f = (* A pipe to wake up the task *) let out_pipe, in_pipe = Unix.pipe () in if not Sys.win32 then Unix.set_nonblock in_pipe; let stop = ref false in let tmp = Bytes.create 1024 in let rec task l = if List.exists (( = ) (`Read out_pipe)) l then (* Consume data from the pipe *) ignore (Unix.read out_pipe tmp 0 1024); if !stop then begin begin try (* This interface is purely asynchronous * so we close both sides of the pipe here. *) Unix.close in_pipe; Unix.close out_pipe with _ -> () end; [] end else begin let delay = f () in let event = if delay >= 0. then [`Delay delay] else [] in [{ priority; events = `Read out_pipe :: event; handler = task }] end in let task = { priority; events = [`Read out_pipe]; handler = task } in add scheduler task; { stop; fd = Some in_pipe; m = Mutex.create () } let wake_up t = Mutex.lock t.m; try begin match t.fd with | Some t -> ( try ignore (Unix.write t (Bytes.of_string " ") 0 1) with | Unix.Unix_error (Unix.EAGAIN, _, _) | Unix.Unix_error (Unix.EWOULDBLOCK, _, _) -> ()) | None -> raise Stopped end; Mutex.unlock t.m with e -> Mutex.unlock t.m; raise e let stop t = Mutex.lock t.m; try begin match t.fd with | Some c -> t.stop := true; ignore (Unix.write c (Bytes.of_string " ") 0 1) | None -> raise Stopped end; t.fd <- None; Mutex.unlock t.m with e -> Mutex.unlock t.m; raise e end module type Transport_t = sig type t type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t val sock : t -> Unix.file_descr val read : t -> Bytes.t -> int -> int -> int val write : t -> Bytes.t -> int -> int -> int val ba_write : t -> bigarray -> int -> int -> int end module Unix_transport : Transport_t with type t = Unix.file_descr = struct type t = Unix.file_descr type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t let sock s = s let read = Unix.read let write = Unix.write external ba_write : t -> bigarray -> int -> int -> int = "ocaml_duppy_write_ba" end module type Io_t = sig type socket type marker = Length of int | Split of string type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t type failure = | Io_error | Unix of (Unix.error * string * string * Printexc.raw_backtrace) | Unknown of exn * Printexc.raw_backtrace | Timeout val read : ?recursive:bool -> ?init:string -> ?on_error:(string * failure -> unit) -> ?timeout:float -> priority:'a -> 'a scheduler -> socket -> marker -> (string * string option -> unit) -> unit val write : ?exec:(unit -> unit) -> ?on_error:(failure -> unit) -> ?bigarray:bigarray -> ?offset:int -> ?length:int -> ?string:Bytes.t -> ?timeout:float -> priority:'a -> 'a scheduler -> socket -> unit end module MakeIo (Transport : Transport_t) : Io_t with type socket = Transport.t = struct type socket = Transport.t type marker = Length of int | Split of string type failure = | Io_error | Unix of (Unix.error * string * string * Printexc.raw_backtrace) | Unknown of exn * Printexc.raw_backtrace | Timeout exception Io exception Timeout_exc type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t let read ?(recursive = false) ?(init = "") ?(on_error = fun _ -> ()) ?timeout ~priority (scheduler : 'a scheduler) socket marker exec = let length = 1024 in let b = Buffer.create length in let buf = Bytes.make length ' ' in Buffer.add_string b init; let unix_socket = Transport.sock socket in let events, check_timeout = match timeout with | None -> ([`Read unix_socket], fun _ -> false) | Some f -> ([`Read unix_socket; `Delay f], List.mem (`Delay f)) in let rec f l = if check_timeout l then raise Timeout_exc; if List.mem (`Read unix_socket) l then begin let input = Transport.read socket buf 0 length in if input <= 0 then raise Io; Buffer.add_subbytes b buf 0 input end; let ret = match marker with | Split r -> let rex = Pcre.regexp r in let acc = Buffer.contents b in let ret = Pcre.full_split ~max:2 ~rex acc in let rec p l = match l with | Pcre.Text x :: Pcre.Delim _ :: l -> let f b x = match x with | Pcre.Text s | Pcre.Delim s -> Buffer.add_string b s | _ -> () in if recursive then begin Buffer.reset b; List.iter (f b) l; Some (x, None) end else begin let b = Buffer.create 10 in List.iter (f b) l; Some (x, Some (Buffer.contents b)) end | _ :: l' -> p l' | [] -> None in p ret | Length n when n <= Buffer.length b -> let s = Buffer.sub b 0 n in let rem = Buffer.sub b n (Buffer.length b - n) in if recursive then begin Buffer.reset b; Buffer.add_string b rem; Some (s, None) end else Some (s, Some rem) | _ -> None in (* Catch all exceptions.. *) let f x = try f x with | Io -> on_error (Buffer.contents b, Io_error); [] | Timeout_exc -> on_error (Buffer.contents b, Timeout); [] | Unix.Unix_error (x, y, z) -> let bt = Printexc.get_raw_backtrace () in on_error (Buffer.contents b, Unix (x, y, z, bt)); [] | e -> let bt = Printexc.get_raw_backtrace () in on_error (Buffer.contents b, Unknown (e, bt)); [] in match ret with | Some x -> ( match x with | s, Some _ when recursive -> exec (s, None); [{ priority; events; handler = f }] | _ -> exec x; []) | None -> [{ priority; events; handler = f }] in (* Catch all exceptions.. *) let f x = try f x with | Io -> on_error (Buffer.contents b, Io_error); [] | Timeout_exc -> on_error (Buffer.contents b, Timeout); [] | Unix.Unix_error (x, y, z) -> let bt = Printexc.get_raw_backtrace () in on_error (Buffer.contents b, Unix (x, y, z, bt)); [] | e -> let bt = Printexc.get_raw_backtrace () in on_error (Buffer.contents b, Unknown (e, bt)); [] in (* First one is without read, * in case init contains the wanted match. * Unless the user sets timeout to 0., this * should not interfere with user-defined timeout.. *) let task = { priority; events = [`Delay 0.; `Read unix_socket]; handler = f } in add scheduler task let write ?(exec = fun () -> ()) ?(on_error = fun _ -> ()) ?bigarray ?(offset = 0) ?length ?string ?timeout ~priority (scheduler : 'a scheduler) socket = let length, write = match (string, bigarray) with | Some s, _ -> let length = match length with Some length -> length | None -> Bytes.length s in (length, Transport.write socket s) | None, Some b -> let length = match length with | Some length -> length | None -> Bigarray.Array1.dim b in (length, Transport.ba_write socket b) | _ -> (0, fun _ _ -> 0) in let unix_socket = Transport.sock (socket : Transport.t) in let exec () = if Sys.os_type = "Win32" then Unix.clear_nonblock unix_socket; exec () in let events, check_timeout = match timeout with | None -> ([`Write unix_socket], fun _ -> false) | Some f -> ([`Write unix_socket; `Delay f], List.mem (`Delay f)) in let rec f pos l = try if check_timeout l then raise Timeout_exc; assert (List.exists (( = ) (`Write unix_socket)) l); let len = length - pos in let n = write pos len in if n <= 0 then ( on_error Io_error; []) else if n < len then [{ priority; events = [`Write unix_socket]; handler = f (pos + n) }] else ( exec (); []) with | Unix.Unix_error (Unix.EWOULDBLOCK, _, _) when Sys.os_type = "Win32" -> [{ priority; events = [`Write unix_socket]; handler = f pos }] | Timeout_exc -> on_error Timeout; [] | Unix.Unix_error (x, y, z) -> let bt = Printexc.get_raw_backtrace () in on_error (Unix (x, y, z, bt)); [] | e -> let bt = Printexc.get_raw_backtrace () in on_error (Unknown (e, bt)); [] in let task = { priority; events; handler = f offset } in if length > 0 then (* Win32 is particularly bad with writing on sockets. It is nearly impossible * to write proper non-blocking code. send will block on blocking sockets if * there isn't enough data available instead of returning a partial buffer * and WSAEventSelect will not return if the socket still has available space. * Thus, setting the socket to non-blocking and writing as much as we can. *) if Sys.os_type = "Win32" then begin Unix.set_nonblock unix_socket; List.iter (add scheduler) (f offset [`Write unix_socket]) end else add scheduler task else exec () end module Io : Io_t with type socket = Unix.file_descr = MakeIo (Unix_transport) (** A monad for implicit continuations or responses *) module Monad = struct type ('a, 'b) handler = { return : 'a -> unit; raise : 'b -> unit } type ('a, 'b) t = ('a, 'b) handler -> unit let return x h = h.return x let raise x h = h.raise x let bind f g h = let ret x = let process = g x in process h in f { return = ret; raise = h.raise } let ( >>= ) = bind let run ~return:ret ~raise f = f { return = ret; raise } let catch f g h = let raise x = let process = g x in process h in f { return = h.return; raise } let ( =<< ) x y = catch y x let rec fold_left f a = function | [] -> a | b :: l -> fold_left f (bind a (fun a -> f a b)) l let fold_left f a l = fold_left f (return a) l let iter f l = fold_left (fun () b -> f b) () l module Mutex_o = Mutex module Mutex = struct module type Mutex_control = sig type priority val scheduler : priority scheduler val priority : priority end module type Mutex_t = sig (** Type for a mutex. *) type mutex module Control : Mutex_control (** [create ()] creates a mutex. Implementation-wise, * a duppy task is created that will be used to select a * waiting computation, lock the mutex on it and resume it. * Thus, [priority] and [s] represents, resp., the priority * and scheduler used when running calling process' computation. *) val create : unit -> mutex (** A computation that locks a mutex * and returns [unit] afterwards. Computation * will be blocked until the mutex is successfully locked. *) val lock : mutex -> (unit, 'a) t (** A computation that tries to lock a mutex. * Returns immediately [true] if the mutex was successfully locked * or [false] otherwise. *) val try_lock : mutex -> (bool, 'a) t (** A computation that unlocks a mutex. * Should return immediately. *) val unlock : mutex -> (unit, 'a) t end module Factory (Control : Mutex_control) = struct (* A mutex is either locked or not * and has a list of tasks waiting to get * it. *) type mutex = { mutable locked : bool; mutable tasks : (unit -> unit) list; } module Control = Control let tmp = Bytes.create 1024 let x, y = Unix.pipe () let stop = ref false let wake_up () = ignore (Unix.write y (Bytes.of_string " ") 0 1) let ctl_m = Mutex_o.create () let finalise _ = stop := true; wake_up () let mutexes = Queue.create () let () = Gc.finalise finalise mutexes let register () = let m = { locked = false; tasks = [] } in Queue.push m mutexes; m let cleanup m = Mutex_o.lock ctl_m; let q = Queue.create () in Queue.iter (fun m' -> if m <> m' then Queue.push m q) mutexes; Queue.clear mutexes; Queue.transfer q mutexes; Mutex_o.unlock ctl_m let task f = { Task.priority = Control.priority; events = [`Delay 0.]; handler = (fun _ -> f (); []); } (* This should only be called when [ctl_m] is locked. *) let process_mutex tasks m = if not m.locked then ( (* I don't think shuffling tasks * matters here.. *) match m.tasks with | x :: l -> m.tasks <- l; m.locked <- true; task x :: tasks | _ -> tasks) else tasks let rec handler _ = Mutex_o.lock ctl_m; if not !stop then begin let tasks = Queue.fold process_mutex [] mutexes in Mutex_o.unlock ctl_m; ignore (Unix.read x tmp 0 1024); { Task.priority = Control.priority; events = [`Read x]; handler } :: tasks end else begin Mutex_o.unlock ctl_m; try Unix.close x; Unix.close y; [] with _ -> [] end let () = Task.add Control.scheduler { Task.priority = Control.priority; events = [`Read x]; handler } let create () = Mutex_o.lock ctl_m; let ret = register () in Mutex_o.unlock ctl_m; Gc.finalise cleanup ret; ret let lock m h' = Mutex_o.lock ctl_m; if not m.locked then begin m.locked <- true; Mutex_o.unlock ctl_m; h'.return () end else begin m.tasks <- h'.return :: m.tasks; Mutex_o.unlock ctl_m end let try_lock m h' = Mutex_o.lock ctl_m; if not m.locked then begin m.locked <- true; Mutex_o.unlock ctl_m; h'.return true end else begin Mutex_o.unlock ctl_m; h'.return false end let unlock m h' = Mutex_o.lock ctl_m; (* Here we allow inter-thread * and double unlock.. Double unlock * is not necessarily a problem and * inter-thread unlock well.. what is * a thread here ?? :-) *) m.locked <- false; let wake = m.tasks <> [] in Mutex_o.unlock ctl_m; if wake then wake_up (); h'.return () end end module Condition = struct module Factory (Mutex : Mutex.Mutex_t) = struct type condition = { condition_m : Mutex_o.t; waiting : (unit -> unit) Queue.t; } module Control = Mutex.Control let create () = { condition_m = Mutex_o.create (); waiting = Queue.create () } (* Mutex.unlock m needs to happen _after_ * the task has been registered. *) let wait c m h = let proc () = Mutex.lock m h in Mutex_o.lock c.condition_m; Queue.push proc c.waiting; Mutex_o.unlock c.condition_m; (* Mutex.unlock does not raise exceptions (for now..) *) let h' = { return = (fun () -> ()); raise = (fun _ -> assert false) } in Mutex.unlock m h' let wake_up h = let handler _ = h (); [] in Task.add Control.scheduler { Task.priority = Control.priority; events = [`Delay 0.]; handler } let signal c h = Mutex_o.lock c.condition_m; let h' = Queue.pop c.waiting in Mutex_o.unlock c.condition_m; wake_up h'; h.return () let broadcast c h = let q = Queue.create () in Mutex_o.lock c.condition_m; Queue.transfer c.waiting q; Mutex_o.unlock c.condition_m; Queue.iter wake_up q; h.return () end end module type Monad_io_t = sig type socket module Io : Io_t with type socket = socket type ('a, 'b) handler = { scheduler : 'a scheduler; socket : Io.socket; mutable data : string; on_error : Io.failure -> 'b; } val exec : ?delay:float -> priority:'a -> ('a, 'b) handler -> ('c, 'b) t -> ('c, 'b) t val delay : priority:'a -> ('a, 'b) handler -> float -> (unit, 'b) t val read : ?timeout:float -> priority:'a -> marker:Io.marker -> ('a, 'b) handler -> (string, 'b) t val read_all : ?timeout:float -> priority:'a -> 'a scheduler -> Io.socket -> (string, string * Io.failure) t val write : ?timeout:float -> priority:'a -> ('a, 'b) handler -> ?offset:int -> ?length:int -> Bytes.t -> (unit, 'b) t val write_bigarray : ?timeout:float -> priority:'a -> ('a, 'b) handler -> Io.bigarray -> (unit, 'b) t end module MakeIo (Io : Io_t) = struct type socket = Io.socket module Io = Io type ('a, 'b) handler = { scheduler : 'a scheduler; socket : Io.socket; mutable data : string; on_error : Io.failure -> 'b; } let exec ?(delay = 0.) ~priority h f h' = let handler _ = begin try f h' with e -> let bt = Printexc.get_raw_backtrace () in h'.raise (h.on_error (Io.Unknown (e, bt))) end; [] in Task.add h.scheduler { Task.priority; events = [`Delay delay]; handler } let delay ~priority h delay = exec ~delay ~priority h (return ()) let read ?timeout ~priority ~marker h h' = let process x = let s = match x with | s, None -> h.data <- ""; s | s, Some s' -> h.data <- s'; s in h'.return s in let init = h.data in h.data <- ""; let on_error (s, x) = h.data <- s; h'.raise (h.on_error x) in Io.read ?timeout ~priority ~init ~recursive:false ~on_error h.scheduler h.socket marker process let read_all ?timeout ~priority s sock = let handler = { scheduler = s; socket = sock; data = ""; on_error = (fun e -> e) } in let buf = Buffer.create 1024 in let rec f () = let data = read ?timeout ~priority ~marker:(Io.Length 1024) handler in let process data = Buffer.add_string buf data; f () in data >>= process in let catch_ret e = Buffer.add_string buf handler.data; match e with | Io.Io_error -> return (Buffer.contents buf) | e -> raise (Buffer.contents buf, e) in catch (f ()) catch_ret let write ?timeout ~priority h ?offset ?length s h' = let on_error x = h'.raise (h.on_error x) in let exec () = h'.return () in Io.write ?timeout ~priority ~on_error ~exec ?offset ?length ~string:s h.scheduler h.socket let write_bigarray ?timeout ~priority h ba h' = let on_error x = h'.raise (h.on_error x) in let exec () = h'.return () in Io.write ?timeout ~priority ~on_error ~exec ~bigarray:ba h.scheduler h.socket end module Io = MakeIo (Io) end liquidsoap-2.4.2/src/modules/duppy/duppy.mli000066400000000000000000000501031513273233300211360ustar00rootroot00000000000000(***************************************************************************** Duppy, a task scheduler for OCaml. Copyright 2003-2010 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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 *****************************************************************************) (** Advanced scheduler and monad for server-oriented programming. *) (** * {R {i {v * The bars could not hold me; * Force could not control me now. * They try to keep me down, yeah! * But Jah put I around. * (...) * Let me tell you this - * I'm a duppy conqueror ! * v} } } * {R {b Lee "Scratch" Perry & Bob Marley - Duppy conqueror }} * * {2 Duppy task scheduler for OCaml.} * * {!Duppy} is a task scheduler for ocaml. It implements a wrapper * around [Unix.select]. * * Using {!Duppy.Task}, the programmer can easily submit tasks that need to wait * on a socket even, or for a given timeout (possibly zero). * * With {!Duppy.Async}, one can use a scheduler to submit asynchronous tasks. * * {!Duppy.Io} implements recursive easy reading and writing to a [Unix.file_descr] * * Finally, {!Duppy.Monad} and {!Duppy.Monad.Io} provide a monadic interface to * program server code that with an implicit return/reply execution flow. * * The scheduler can use several queues running concurrently, each queue * processing ready tasks. Of course, a queue should run in its own thread.*) (** A scheduler is a device for processing tasks. Several queues might run in * different threads, processing one scheduler's tasks. * * ['a] is the type of objects used for priorities. *) type 'a scheduler (** Initiate a new scheduler * @param compare the comparison function used to sort tasks according to priorities. * Works as in [List.sort] *) val create : ?on_error:(exn -> Printexc.raw_backtrace -> unit) -> ?compare:('a -> 'a -> int) -> unit -> 'a scheduler (** Internal polling function. Uses `Unix.select` on windows and `poll` otherwise. *) val poll : Unix.file_descr list -> Unix.file_descr list -> Unix.file_descr list -> float -> Unix.file_descr list * Unix.file_descr list * Unix.file_descr list (** [queue ~log ~priorities s name] * starts a queue, on the scheduler [s] only processing priorities [p] * for which [priorities p] returns [true]. * * Several queues can be run concurrently against [s]. * @param log Logging function. Default: [Printf.printf "queue %s: %s\n" name] * @param priorities Predicate specifying which priority to process. Default: [fun _ -> _ -> true] * * An exception is raised from this call when duppy's event loops has * crashed. This exception should be considered a MAJOR FAILURE. All current * non-ready tasks registered for the calling scheduler are dropped. You may * restart Duppy's queues after it is raised but it should only be used to terminate * the process diligently!! *) val queue : ?log:(string -> unit) -> ?priorities:('a -> bool) -> 'a scheduler -> string -> unit (** Stop all queues running on that scheduler and wait for them to return. *) val stop : 'a scheduler -> unit (** Core task registration. * * A task will be a set of events to watch, and a corresponding function to * execute when one of the events is triggered. * * The executed function may then return a list of new tasks to schedule. *) module Task : sig (** A task is a list of events awaited, * and a function to process events that have occurred. * * The ['a] parameter is the type of priorities, ['b] will be a subset of possible * events. *) type ('a, 'b) task = { priority : 'a; events : 'b list; handler : 'b list -> ('a, 'b) task list; } (** Type for possible events. * * Please not that currently, under win32, all socket used in ocaml-duppy * are expected to be in blocking mode only! *) type event = [ `Delay of float | `Write of Unix.file_descr | `Read of Unix.file_descr | `Exception of Unix.file_descr ] (** Schedule a task. *) val add : 'a scheduler -> ('a, [< event ]) task -> unit end (** Asynchronous task module * * This module implements an asynchronous API to {!Duppy.scheduler} * It allows to create a task that will run and then go to sleep. *) module Async : sig type t (** Exception raised when trying to wake_up a task * that has been previously stopped *) exception Stopped (** [add ~priority s f] creates an asynchronous task in [s] with * priority [priority]. * * The task executes the function [f]. * If the task returns a positive float, the function will be executed * again after this delay. Otherwise it goes to sleep, and * you can use [wake_up] to resume the task and execute [f] again. * Only a single call to [f] is done at each time. * Multiple [wake_up] while previous task has not * finished will result in sequentialized calls to [f]. *) val add : priority:'a -> 'a scheduler -> (unit -> float) -> t (** Wake up an asynchronous task. * Raises [Stopped] if the task has been stopped. *) val wake_up : t -> unit (** Stop and remove the asynchronous task. Doesn't quit a running task. * Raises [Stopped] if the task has been stopped. *) val stop : t -> unit end (** Module type for Io functor. *) module type Transport_t = sig type t type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t val sock : t -> Unix.file_descr val read : t -> Bytes.t -> int -> int -> int val write : t -> Bytes.t -> int -> int -> int val ba_write : t -> bigarray -> int -> int -> int end (** Easy parsing of [Unix.file_descr]. * * With {!Duppy.Io.read}, you can pass a file descriptor to the scheduler, * along with a marker, and have it run the associated function when the * marker is found. * * With {!Duppy.Io.write}, the schdeduler will try to write recursively to the file descriptor * the given string. *) module type Io_t = sig type socket (** Type for markers. * * [Split s] recognizes all regexp allowed by the * [Pcre] module. *) type marker = Length of int | Split of string (** Type of [Bigarray] used here. *) type bigarray = (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t (** Different types of failure. * * [Io_error] is raised when reading or writing * returned 0. This usually means that the socket * was closed. *) type failure = | Io_error | Unix of (Unix.error * string * string * Printexc.raw_backtrace) | Unknown of exn * Printexc.raw_backtrace | Timeout (** Wrapper to perform a read on a socket and trigger a function when * a marker has been detected, or enough data has been read. * It reads recursively on a socket, splitting into strings separated * by the marker (if any) and calls the given function on the list of strings. * * Can be used recursively or not, depending on the way you process strings. * Because of Unix's semantic, it is not possible to stop reading * at first marker, so there can be a remaining string. If not used * recursively, the second optional argument may contain a remaining * string. You should then initiate the next read with this value. * * The [on_error] function is used when reading failed on the socket. * Depending on your usage, it can be a hard failure, or simply a lost client. * The string passed to [on_error] contains data read before error * occurred. * @param recursive recursively read and process, default: [true] * @param init initial string for reading, default: [""] * @param on_error function used when read failed, default: [fun _ -> ()] * @param timeout Terminate with [Timeout] failure if nothing has been read * after the given amount of time in seconds. More precisely, * the exception is raised when no character have been read * and the socket was not close while waiting. Default: wait * forever. *) val read : ?recursive:bool -> ?init:string -> ?on_error:(string * failure -> unit) -> ?timeout:float -> priority:'a -> 'a scheduler -> socket -> marker -> (string * string option -> unit) -> unit (** Similar to [read] but less complex. * [write ?exec ?on_error ?string ?bigarray ~priority scheduler socket] * write data from [string], or from [bigarray] if no string is given, * to [socket], and executes [exec] or [on_error] if errors occurred. * * Caveat: on Win32, all file descriptors are expected to be in blocking * mode before being passed to this call due to limitations in the emulation * of the unix/posix API. See code comments for more details. * * @param exec function to execute after writing, default: [fun () -> ()] * @param on_error function to execute when an error occurred, default: [fun _ -> ()] * @param string write data from this string * @param bigarray write data from this bigarray, if no [string] is given * @param timeout Terminate with [Timeout] failure if nothing has been written * after the given amount of time in seconds. More precisely, * the exception is raised when no character have been written * and the socket was not close while waiting. Default: wait * forever. *) val write : ?exec:(unit -> unit) -> ?on_error:(failure -> unit) -> ?bigarray:bigarray -> ?offset:int -> ?length:int -> ?string:Bytes.t -> ?timeout:float -> priority:'a -> 'a scheduler -> socket -> unit end module MakeIo (Transport : Transport_t) : Io_t with type socket = Transport.t module Io : Io_t with type socket = Unix.file_descr (** Monadic interface to {!Duppy.Io}. * * This module can be used to write code * that runs in various Duppy's tasks and * raise values in a completely transparent way. * * You can see examples of its use * in the [examples/] directory of the * source code and in the files * [src/tools/{harbor.camlp4,server.camlp4}] * in liquidsoap's code. * * When a server communicates * with a client, it performs several * computations and, eventually, terminates. * A computation can either return a new * value or terminate. For instance: * * - Client connects. * - Server tries to authenticate the client. * - If authentication is ok, proceed with the next step. * - Otherwise terminate. * * The purpose of the monad is to embed * computations which can either return * a new value or raise a value that is used * to terminate. *) module Monad : sig (** Type representing a computation * which returns a value of type ['a] * or raises a value of type ['b] *) type ('a, 'b) t (** [return x] create a computation that * returns value [x]. *) val return : 'a -> ('a, 'b) t (** [raise x] create a computation that raises * value [x]. *) val raise : 'b -> ('a, 'b) t (** Compose two computations. * [bind f g] is equivalent to: * [let x = f in g x] where [x] * has f's return type. *) val bind : ('a, 'b) t -> ('a -> ('c, 'b) t) -> ('c, 'b) t (** [>>=] is an alternative notation * for [bind] *) val ( >>= ) : ('a, 'b) t -> ('a -> ('c, 'b) t) -> ('c, 'b) t (** [run f ~return ~raise ()] executes [f] and process * returned values with [return] or raised values * with [raise]. *) val run : return:('a -> unit) -> raise:('b -> unit) -> ('a, 'b) t -> unit (** [catch f g] redirects values [x] raised during * [f]'s execution to [g]. The name suggests the * usual [try .. with ..] exception catching. *) val catch : ('a, 'b) t -> ('b -> ('a, 'c) t) -> ('a, 'c) t (** [=<<] is an alternative notation for catch. *) val ( =<< ) : ('b -> ('a, 'c) t) -> ('a, 'b) t -> ('a, 'c) t (** [fold_left f a [b1; b2; ..]] returns computation * [ (f a b1) >>= (fun a -> f a b2) >>= ...] *) val fold_left : ('a -> 'b -> ('a, 'c) t) -> 'a -> 'b list -> ('a, 'c) t (** [iter f [x1; x2; ..]] returns computation * [f x1 >>= (fun () -> f x2) >>= ...] *) val iter : ('a -> (unit, 'b) t) -> 'a list -> (unit, 'b) t (** This module implements monadic * mutex computations. They can be used * to write blocking code that is compatible * with duppy's tasks, i.e. [Mutex.lock m] blocks * the calling computation and not the calling thread. *) module Mutex : sig (** Information used to initialize a Mutex module. * [priority] and [scheduler] are used to initialize a task * which treat mutexes as well as conditions from the below * [Condition] module. *) module type Mutex_control = sig type priority val scheduler : priority scheduler val priority : priority end module type Mutex_t = sig (** Type for a mutex. *) type mutex module Control : Mutex_control (** [create ()] creates a mutex. *) val create : unit -> mutex (** A computation that locks a mutex * and returns [unit] afterwards. Computation * will be blocked until the mutex is successfully locked. *) val lock : mutex -> (unit, 'a) t (** A computation that tries to lock a mutex. * Returns immediately [true] if the mutex was successfully locked * or [false] otherwise. *) val try_lock : mutex -> (bool, 'a) t (** A computation that unlocks a mutex. * Should return immediately. *) val unlock : mutex -> (unit, 'a) t end module Factory (_ : Mutex_control) : Mutex_t end (** This module implements monadic * condition computations. They can be used * to write waiting code that is compatible * with duppy's tasks, i.e. [Condition.wait c m] blocks * the calling computation and not the calling thread * until [Condition.signal c] or [Condition.broadcast c] has * been called. *) module Condition : sig module Factory (Mutex : Mutex.Mutex_t) : sig (** Type of a condition, used in [wait] and [broadcast] *) type condition (** Create a condition. Implementation-wise, * a duppy task is created that will be used to select a * waiting computation, and resume it. * Thus, [priority] and [s] represents, resp., the priority * and scheduler used when running calling process' computation. *) val create : unit -> condition (** [wait h m] is a computation that: * {ul * {- Unlock mutex [m]} * {- Wait until [Condition.signal c] or [Condition.broadcast c] has been called} * {- Locks mutex [m]} * {- Returns [unit]}} *) val wait : condition -> Mutex.mutex -> (unit, 'a) t (** [broadcast c] is a computation that * resumes all computations waiting on [c]. It should * return immediately. *) val broadcast : condition -> (unit, 'a) t (** [signal c] is a computation that resumes one * computation waiting on [c]. It should return * immediately. *) val signal : condition -> (unit, 'a) t end end (** This module implements monadic computations * using [Duppy.Io]. It can be used to create * computations that read or write from a socket, * and also to redirect a computation in a different * queue with a new priority. *) module type Monad_io_t = sig type socket module Io : Io_t with type socket = socket (** {2 Type} *) (** A handler for this module * is a record that contains the * required elements. In particular, * [on_error] is a function that transforms * an error raised by [Duppy.Io] to a reply * used to terminate the computation. * [data] is an internal data buffer. It should * be initialized with [""]. It contains the * remaining data that was received when * using [read]. If an error occurred, * [data] contain data read before the * error. *) type ('a, 'b) handler = { scheduler : 'a scheduler; socket : Io.socket; mutable data : string; on_error : Io.failure -> 'b; } (** {2 Execution flow} *) (** [exec ?delay ~priority h f] redirects computation * [f] into a new queue with priority [priority] and * delay [delay] ([0.] by default). * It can be used to redirect a computation that * has to run under a different priority. For instance, * a computation that reads from a socket is generally * not blocking because the function is executed * only when some data is available for reading. * However, if the data that is read needs to be processed * by a computation that can be blocking, then one may * use [exec] to redirect this computation into an * appropriate queue. *) val exec : ?delay:float -> priority:'a -> ('a, 'b) handler -> ('c, 'b) t -> ('c, 'b) t (** [delay ~priority h d] creates a computation that returns * [unit] after delay [d] in seconds. *) val delay : priority:'a -> ('a, 'b) handler -> float -> (unit, 'b) t (** {2 Read/write} *) (** [read ?timeout ~priority ~marker h] creates a * computation that reads from [h.socket] * and returns the first string split * according to [marker]. This function * can be used to create a computation that * reads data from a socket. [timeout] parameter * forces the computation to return an error if * nothing has been read for more than [timeout] * seconds. Default: wait forever. *) val read : ?timeout:float -> priority:'a -> marker:Io.marker -> ('a, 'b) handler -> (string, 'b) t (** [read_all ?timeout ~priority s sock] creates a * computation that reads all data from [sock] * and returns it. Raised value contains data * read before an error occurred. *) val read_all : ?timeout:float -> priority:'a -> 'a scheduler -> Io.socket -> (string, string * Io.failure) t (** [write ?timeout ~priority h s] creates a computation * that writes string [s] to [h.socket]. This * function can be used to create a computation * that sends data to a socket. [timeout] parameter * forces the computation to return an error if * nothing has been written for more than [timeout] * seconds. Default: wait forever. *) val write : ?timeout:float -> priority:'a -> ('a, 'b) handler -> ?offset:int -> ?length:int -> Bytes.t -> (unit, 'b) t (** [write_bigarray ?timeout ~priority h ba] creates a computation * that writes data from [ba] to [h.socket]. This function * can to create a computation that writes data to a socket. *) val write_bigarray : ?timeout:float -> priority:'a -> ('a, 'b) handler -> Io.bigarray -> (unit, 'b) t end module MakeIo (Io : Io_t) : Monad_io_t with type socket = Io.socket and module Io = Io module Io : Monad_io_t with type socket = Unix.file_descr and module Io = Io end (** {2 Some culture..} * {e Duppy is a Caribbean patois word of West African origin meaning ghost or spirit. * Much of Caribbean folklore revolves around duppies. * Duppies are generally regarded as malevolent spirits. * They are said to come out and haunt people at night mostly, * and people from the islands claim to have seen them. * The 'Rolling Calf', 'Three footed horse' or 'Old Higue' are examples of the more malicious spirits. } * {R {{:http://en.wikipedia.org/wiki/Duppy} http://en.wikipedia.org/wiki/Duppy}}*) liquidsoap-2.4.2/src/modules/duppy/duppy_stubs.c000066400000000000000000000102371513273233300220230ustar00rootroot00000000000000/* * Copyright 2010 Savonet team * * This file is part of Ocaml-duppy. * * Ocaml-duppy 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. * * Ocaml-duppy 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 Ocaml-duppy; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include /* On native Windows platforms, many macros are not defined. */ # if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ #ifndef EWOULDBLOCK #define EWOULDBLOCK EAGAIN #endif #endif #ifdef WIN32 #define Fd_val(fd) win_CRT_fd_of_filedescr(fd) #define Val_fd(fd) caml_failwith("Val_fd") #else #define Fd_val(fd) Int_val(fd) #define Val_fd(fd) Val_int(fd) #endif #ifndef WIN32 #include CAMLprim value caml_poll(value _read, value _write, value _err, value _timeout) { CAMLparam4(_read, _write, _err, _timeout); CAMLlocal4(_pread, _pwrite, _perr, _ret); struct pollfd *fds; nfds_t nfds = 0; nfds_t nread = 0; nfds_t nwrite = 0; nfds_t nerr = 0; int timeout; size_t last = 0; int n, ret; if (Double_val(_timeout) == -1) timeout = -1; else timeout = ceil(Double_val(_timeout) * 1000); nfds += Wosize_val(_read); nfds += Wosize_val(_write); nfds += Wosize_val(_err); fds = calloc(nfds,sizeof(struct pollfd)); if (fds == NULL) caml_raise_out_of_memory(); for (n = 0; n < Wosize_val(_read); n++) { fds[last+n].fd = Fd_val(Field(_read,n)); fds[last+n].events = POLLIN; } last += Wosize_val(_read); for (n = 0; n < Wosize_val(_write); n++) { fds[last+n].fd = Fd_val(Field(_write,n)); fds[last+n].events = POLLOUT; } last += Wosize_val(_write); for (n = 0; n < Wosize_val(_err); n++) { fds[last+n].fd = Fd_val(Field(_err,n)); fds[last+n].events = POLLERR; } caml_release_runtime_system(); ret = poll(fds, nfds, timeout); caml_acquire_runtime_system(); if (ret == -1) { free(fds); uerror("poll", Nothing); } for (n = 0; n < nfds; n++) { if (fds[n].revents & POLLIN) nread++; if (fds[n].revents & POLLOUT) nwrite++; if (fds[n].revents & POLLERR) nerr++; } _pread = caml_alloc_tuple(nread); nread = 0; _pwrite = caml_alloc_tuple(nwrite); nwrite = 0; _perr = caml_alloc_tuple(nerr); nerr = 0; for (n = 0; n < nfds; n++) { if (fds[n].revents & POLLIN) { Store_field(_pread, nread, Val_fd(fds[n].fd)); nread++; } if (fds[n].revents & POLLOUT) { Store_field(_pwrite, nwrite, Val_fd(fds[n].fd)); nwrite++; } if (fds[n].revents & POLLERR) { Store_field(_perr, nerr, Val_fd(fds[n].fd)); nerr++; } } free(fds); _ret = caml_alloc_tuple(3); Store_field(_ret, 0, _pread); Store_field(_ret, 1, _pwrite); Store_field(_ret, 2, _perr); CAMLreturn(_ret); } #else CAMLprim value caml_poll(value _read, value _write, value _err, value _timeout) { caml_failwith("caml_poll"); } #endif CAMLprim value ocaml_duppy_write_ba(value _fd, value ba, value _ofs, value _len) { CAMLparam4(_fd, ba, _ofs, _len); int fd = Fd_val(_fd); long ofs = Long_val(_ofs); long len = Long_val(_len); void *buf = Caml_ba_data_val(ba); int ret; int written = 0; while (len > 0) { caml_enter_blocking_section(); ret = write(fd, buf+ofs, len); caml_leave_blocking_section(); if (ret == -1) { if ((errno == EAGAIN || errno == EWOULDBLOCK) && written > 0) break; uerror("write", Nothing); } written += ret; ofs += ret; len -= ret; } CAMLreturn(Val_long(written)); } liquidsoap-2.4.2/src/modules/duppy/examples/000077500000000000000000000000001513273233300211115ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/duppy/examples/dune000066400000000000000000000001771513273233300217740ustar00rootroot00000000000000(executable (name http) (modules http) (libraries duppy)) (executable (name telnet) (modules telnet) (libraries duppy)) liquidsoap-2.4.2/src/modules/duppy/examples/http.ml000066400000000000000000000471471513273233300224370ustar00rootroot00000000000000module Pcre = Re.Pcre let non_blocking_queues = ref 3 let maybe_blocking_queues = ref 1 let files_path = ref "" let port = ref 8080 let usage = "usage: http [options] /path/to/files" let () = let pnum = ref 0 in let arg s = incr pnum; if !pnum > 1 then ( Printf.eprintf "Error: too many arguments\n"; exit 1) else files_path := s in Arg.parse [ ( "--non_blocking_queues", Arg.Int (fun i -> non_blocking_queues := i), Printf.sprintf "Number of non-blocking queues. (default: %d)" !non_blocking_queues ); ( "--maybe_blocking_queues", Arg.Int (fun i -> maybe_blocking_queues := i), Printf.sprintf "Number of maybe-blocking queues. (default: %d)" !maybe_blocking_queues ); ( "--port", Arg.Int (fun i -> port := i), Printf.sprintf "Port used to bind the server. (default: %d)" !port ); ] arg usage; if !files_path = "" then ( Printf.printf "%s\n" usage; exit 1) else () type priority = Maybe_blocking | Non_blocking let scheduler = Duppy.create () type http_method = Post | Get type http_protocol = Http_11 | Http_10 let string_of_protocol = function | Http_11 -> "HTTP/1.1" | Http_10 -> "HTTP/1.0" let protocol_of_string = function | "HTTP/1.1" -> Http_11 | "HTTP/1.0" -> Http_10 | _ -> assert false let string_of_method = function Post -> "POST" | Get -> "GET" let method_of_string = function | "POST" -> Post | "GET" -> Get | _ -> assert false type data = None | String of string | File of Unix.file_descr type request = { request_protocol : http_protocol; request_method : http_method; request_uri : string; request_headers : (string * string) list; request_data : data; } type reply = { reply_protocol : http_protocol; reply_status : int * string; reply_headers : (string * string) list; reply_data : data; } exception Assoc of string let assoc_uppercase x y = try List.iter (fun (l, v) -> if String.uppercase_ascii l = x then raise (Assoc v) else ()) y; raise Not_found with Assoc s -> s let server = "dhttpd" let html_template = Printf.sprintf "\r\n\ \r\n\ %s" let server_error status protocol = let _, explanation = status in let data = String (html_template (Printf.sprintf "%s\r\n%s !" explanation explanation)) in { reply_protocol = protocol; reply_status = status; reply_headers = [("Content-Type", "text/html; charset=UTF-8"); ("Server", server)]; reply_data = data; } let error_404 = server_error (404, "File Not Found") let error_500 = server_error (500, "Bad Request") Http_10 let error_403 = server_error (403, "Forbidden") let http_302 protocol uri = { reply_protocol = protocol; reply_status = (302, "Found"); reply_headers = [("Location", uri)]; reply_data = String ""; } type socket_status = Keep | Close let send_reply h reply = let write s = Duppy.Monad.Io.write ?timeout:None ~priority:Non_blocking h (Bytes.unsafe_of_string s) in let code, status = reply.reply_status in let http_header = Printf.sprintf "%s %d %s\r\n%s\r\n\r\n" (string_of_protocol reply.reply_protocol) code status (String.concat "\r\n" (List.map (fun (x, y) -> Printf.sprintf "%s: %s" x y) reply.reply_headers)) in Duppy.Monad.bind (write http_header) (fun () -> match reply.reply_data with | String s -> write s | File fd -> let stats = Unix.fstat fd in let ba = Unix.map_file fd Bigarray.char Bigarray.c_layout false [| stats.Unix.st_size |] in let ba = Bigarray.array1_of_genarray ba in let close () = try Unix.close fd with _ -> () in let on_error e = close (); h.Duppy.Monad.Io.on_error e in let h = { h with Duppy.Monad.Io.on_error } in Duppy.Monad.bind (Duppy.Monad.Io.write_bigarray ?timeout:None ~priority:Non_blocking h ba) (fun () -> Duppy.Monad.return (close ())) | None -> Duppy.Monad.return ()) let parse_headers headers = let split_header l h = try let rex = Pcre.regexp "([^:\\r\\n]+):\\s*([^\\r\\n]+)" in let sub = Pcre.exec ~rex h in Duppy.Monad.return ((Pcre.get_substring sub 1, Pcre.get_substring sub 2) :: l) with Not_found -> Duppy.Monad.raise error_500 in Duppy.Monad.fold_left split_header [] headers let index_uri path index protocol uri = let uri = try let ret = Pcre.extract ~rex:(Pcre.regexp "([^\\?]*)\\?") uri in ret.(1) with Not_found -> uri in try if Sys.is_directory (Printf.sprintf "%s%s" path uri) then if uri.[String.length uri - 1] <> '/' then Duppy.Monad.raise (http_302 protocol (Printf.sprintf "%s/" uri)) else ( let index = Printf.sprintf "%s/%s" uri index in if Sys.file_exists (Printf.sprintf "%s/%s" path index) then Duppy.Monad.return index else Duppy.Monad.return uri) else Duppy.Monad.return uri with _ -> Duppy.Monad.return uri let file_request path _ request = let uri = try let ret = Pcre.extract ~rex:(Pcre.regexp "([^\\?]*)\\?.*") request.request_uri in ret.(1) with Not_found -> request.request_uri in let __pa_duppy_0 = index_uri path "index.html" request.request_protocol uri in Duppy.Monad.bind __pa_duppy_0 (fun uri -> let fname = Printf.sprintf "%s%s" path uri in if Sys.file_exists fname then ( try let fd = Unix.openfile fname [Unix.O_RDONLY] 0o640 in let stats = Unix.fstat fd in let headers = [ ("Server", server); ("Content-Length", string_of_int stats.Unix.st_size); ] in let headers = if Pcre.pmatch ~rex:(Pcre.regexp "\\.html$") fname then ("Content-Type", "text/html") :: headers else if Pcre.pmatch ~rex:(Pcre.regexp "\\.css$") fname then ("Content-Type", "text/css") :: headers else headers in Duppy.Monad.raise { reply_protocol = request.request_protocol; reply_status = (200, "OK"); reply_headers = headers; reply_data = File fd; } with _ -> Duppy.Monad.raise (error_403 request.request_protocol)) else Duppy.Monad.raise (error_404 request.request_protocol)) let file_handler = ((fun _ -> Duppy.Monad.return true), file_request !files_path) let cgi_handler process path h request = let uri, args, suffix = try let ret = Pcre.extract ~rex:(Pcre.regexp "([^\\?]*)\\?(.*)") request.request_uri in try let ans = Pcre.extract ~rex:(Pcre.regexp "^([^/]*)/([^&=]*)$") ret.(2) in (ret.(1), ans.(1), ans.(2)) with Not_found -> (ret.(1), ret.(2), "") with Not_found -> (request.request_uri, "", "") in let __pa_duppy_0 = index_uri path "index.php" request.request_protocol uri in Duppy.Monad.bind __pa_duppy_0 (fun script -> let script = Printf.sprintf "%s%s" path script in let env = Printf.sprintf "export SERVER_SOFTWARE=Duppy-httpd/1.0; export \ SERVER_NAME=localhost; export GATEWAY_INTERFACE=CGI/1.1; export \ SERVER_PROTOCOL=%s; export SERVER_PORT=%d; export \ REQUEST_METHOD=%s; export REQUEST_URI=%s; export \ REDIRECT_STATUS=200; export SCRIPT_FILENAME=%s" (string_of_protocol request.request_protocol) !port (string_of_method request.request_method) (Filename.quote uri) (Filename.quote script) in let env = Printf.sprintf "%s; export QUERY_STRING=%s" env (Filename.quote args) in let env = let tr_suffix = Printf.sprintf "%s%s" path suffix in (* Trick ! *) let tr_suffix = Printf.sprintf "%s/%s" (Filename.dirname tr_suffix) (Filename.basename tr_suffix) in Printf.sprintf "%s; export PATH_TRANSLATED=%s; export PATH_INFO=%s" env (Filename.quote tr_suffix) (Filename.quote suffix) in let sanitize s = Pcre.substitute ~rex:(Pcre.regexp "-") ~subst:(fun _ -> "_") (String.uppercase_ascii s) in let headers = List.map (fun (x, y) -> (sanitize x, y)) request.request_headers in let append env key = if List.mem_assoc key headers then Printf.sprintf "%s; export %s=%s" env key (Filename.quote (List.assoc key headers)) else env in let env = append env "CONTENT_TYPE" in let env = append env "CONTENT_LENGTH" in let __pa_duppy_0 = if List.mem_assoc "AUTHORIZATION" headers then ( let ret = Pcre.extract ~rex:(Pcre.regexp "(^[^\\s]*\\s.*)$") (List.assoc "AUTHORIZATION" headers) in if Array.length ret > 0 then Duppy.Monad.return (Printf.sprintf "%s; extract AUTH_TYPE=%s" env ret.(1)) else Duppy.Monad.raise error_500) else Duppy.Monad.return env in Duppy.Monad.bind __pa_duppy_0 (fun env -> let f env (x, y) = Printf.sprintf "%s; export HTTP_%s=%s" env x (Filename.quote y) in let env = List.fold_left f env headers in let data = match request.request_data with | None -> "" | String s -> s | _ -> assert false in (* not implemented *) let process = Printf.sprintf "%s; %s 2>/dev/null" env process in let in_c, out_c = Unix.open_process process in let out_s = Unix.descr_of_out_channel out_c in let h = { h with Duppy.Monad.Io.socket = out_s; data = "" } in let __pa_duppy_0 = Duppy.Monad.Io.write ?timeout:None ~priority:Non_blocking h (Bytes.unsafe_of_string data) in Duppy.Monad.bind __pa_duppy_0 (fun () -> let in_s = Unix.descr_of_in_channel in_c in let h = { h with Duppy.Monad.Io.socket = in_s; data = "" } in let __pa_duppy_0 = Duppy.Monad.Io.read ?timeout:None ~priority:Non_blocking ~marker:(Duppy.Io.Split "[\r]?\n[\r]?\n") h in Duppy.Monad.bind __pa_duppy_0 (fun headers -> let __pa_duppy_0 = Duppy.Monad.catch (Duppy.Monad.Io.read_all ?timeout:None ~priority:Non_blocking h.Duppy.Monad.Io.scheduler in_s) (fun (s, _) -> Duppy.Monad.return s) in Duppy.Monad.bind __pa_duppy_0 (fun data -> let data = Printf.sprintf "%s%s" h.Duppy.Monad.Io.data data in ignore (Unix.close_process (in_c, out_c)); let __pa_duppy_0 = let headers = Pcre.split ~rex:(Pcre.regexp "\r\n") headers in parse_headers headers in Duppy.Monad.bind __pa_duppy_0 (fun headers -> let __pa_duppy_0 = if List.mem_assoc "Status" headers then ( try let ans = Pcre.extract ~rex:(Pcre.regexp "([\\d]+)\\s(.*)") (List.assoc "Status" headers) in Duppy.Monad.return ( (int_of_string ans.(1), ans.(2)), List.filter (fun (x, _) -> x <> "Status") headers ) with _ -> Duppy.Monad.raise error_500) else Duppy.Monad.return ((200, "OK"), headers) in Duppy.Monad.bind __pa_duppy_0 (fun (status, headers) -> let headers = ( "Content-length", string_of_int (String.length data) ) :: headers in Duppy.Monad.raise { reply_protocol = request.request_protocol; reply_status = status; reply_headers = headers; reply_data = String data; }))))))) let php_handler = ( (fun request -> let __pa_duppy_0 = index_uri !files_path "index.php" request.request_protocol request.request_uri in Duppy.Monad.bind __pa_duppy_0 (fun uri -> Duppy.Monad.return (Pcre.pmatch ~rex:(Pcre.regexp "\\.php$") uri))), cgi_handler "php-cgi" !files_path ) let handlers = [php_handler; file_handler] let handle_request h request = let f (check, handler) = let __pa_duppy_0 = check request in Duppy.Monad.bind __pa_duppy_0 (fun check -> if check then handler h request else Duppy.Monad.return ()) in Duppy.Monad.catch (Duppy.Monad.bind (Duppy.Monad.iter f handlers) (fun () -> Duppy.Monad.return (error_404 request.request_protocol))) (fun reply -> Duppy.Monad.return reply) let parse_request h r = try let headers = Pcre.split ~rex:(Pcre.regexp "\r\n") r in let __pa_duppy_0 = match headers with | e :: l -> let __pa_duppy_0 = parse_headers l in Duppy.Monad.bind __pa_duppy_0 (fun headers -> Duppy.Monad.return (e, headers)) | _ -> Duppy.Monad.raise error_500 in Duppy.Monad.bind __pa_duppy_0 (fun (request, headers) -> let rex = Pcre.regexp "([\\w]+)\\s([^\\s]+)\\s(HTTP/1.[01])" in let __pa_duppy_0 = try let sub = Pcre.exec ~rex request in let http_method, uri, protocol = ( Pcre.get_substring sub 1, Pcre.get_substring sub 2, Pcre.get_substring sub 3 ) in Duppy.Monad.return (method_of_string http_method, uri, protocol_of_string protocol) with _ -> Duppy.Monad.raise error_500 in Duppy.Monad.bind __pa_duppy_0 (fun (http_method, uri, protocol) -> let __pa_duppy_0 = match http_method with | Get -> Duppy.Monad.return None | Post -> let __pa_duppy_0 = try let length = assoc_uppercase "CONTENT-LENGTH" headers in Duppy.Monad.return (int_of_string length) with | Not_found -> Duppy.Monad.return 0 | _ -> Duppy.Monad.raise error_500 in Duppy.Monad.bind __pa_duppy_0 (fun len -> match len with | 0 -> Duppy.Monad.return None | d -> let __pa_duppy_0 = Duppy.Monad.Io.read ?timeout:None ~priority:Non_blocking ~marker:(Duppy.Io.Length d) h in Duppy.Monad.bind __pa_duppy_0 (fun data -> Duppy.Monad.return (String data))) in Duppy.Monad.bind __pa_duppy_0 (fun data -> Duppy.Monad.return { request_method = http_method; request_protocol = protocol; request_uri = uri; request_headers = headers; request_data = data; }))) with _ -> Duppy.Monad.raise error_500 let handle_client socket = (* Read and process lines *) let on_error _ = error_500 in let h = { Duppy.Monad.Io.scheduler; socket; data = ""; on_error } in let rec exec () = let __pa_duppy_0 = Duppy.Monad.catch (let __pa_duppy_0 = Duppy.Monad.Io.read ?timeout:None ~priority:Non_blocking ~marker:(Duppy.Io.Split "\r\n\r\n") h in Duppy.Monad.bind __pa_duppy_0 (fun data -> let __pa_duppy_0 = parse_request h data in Duppy.Monad.bind __pa_duppy_0 (fun request -> let __pa_duppy_0 = handle_request h request in Duppy.Monad.bind __pa_duppy_0 (fun reply -> let close_header headers = try assoc_uppercase "CONNECTION" headers = "close" with Not_found -> false in let keep = if request.request_protocol = Http_10 || close_header request.request_headers || close_header reply.reply_headers then Close else Keep in Duppy.Monad.return (keep, reply))))) (fun reply -> Duppy.Monad.return (Close, reply)) in Duppy.Monad.bind __pa_duppy_0 (fun (keep, reply) -> Duppy.Monad.bind (send_reply h reply) (fun () -> if keep = Keep then exec () else Duppy.Monad.return ())) in let finish _ = try Unix.close socket with _ -> () in Duppy.Monad.run ~return:finish ~raise:finish (exec ()) let new_queue ~priority ~name () = let priorities p = p = priority in let queue () = Duppy.queue scheduler ~log:(fun _ -> ()) ~priorities name in Thread.create queue () let bind_addr_inet = Unix.inet_addr_of_string "0.0.0.0" let bind_addr = Unix.ADDR_INET (bind_addr_inet, !port) let max_conn = 100 let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 let () = (* See http://caml.inria.fr/mantis/print_bug_page.php?bug_id=4640 * for this: we want Unix EPIPE error and not SIGPIPE, which * crashes the program.. *) Sys.set_signal Sys.sigpipe Sys.Signal_ignore; ignore (Unix.sigprocmask Unix.SIG_BLOCK [Sys.sigpipe]); Unix.setsockopt sock Unix.SO_REUSEADDR true; let rec incoming _ = (try let s, _ = Unix.accept sock in handle_client s with e -> Printf.printf "Failed to accept new client: %S\n" (Printexc.to_string e)); [ { Duppy.Task.priority = Non_blocking; events = [`Read sock]; handler = incoming; }; ] in (try Unix.bind sock bind_addr with Unix.Unix_error (Unix.EADDRINUSE, "bind", "") -> failwith (Printf.sprintf "port %d already taken" !port)); Unix.listen sock max_conn; Duppy.Task.add scheduler { Duppy.Task.priority = Non_blocking; events = [`Read sock]; handler = incoming; }; for i = 1 to !non_blocking_queues do ignore (new_queue ~priority:Non_blocking ~name:(Printf.sprintf "Non blocking queue #%d" i) ()) done; for i = 1 to !maybe_blocking_queues do ignore (new_queue ~priority:Maybe_blocking ~name:(Printf.sprintf "Maybe blocking queue #%d" i) ()) done; Duppy.queue scheduler ~log:(fun _ -> ()) "root" liquidsoap-2.4.2/src/modules/duppy/examples/index.html000066400000000000000000000000041513273233300231000ustar00rootroot00000000000000bla liquidsoap-2.4.2/src/modules/duppy/examples/telnet.ml000066400000000000000000000116741513273233300227470ustar00rootroot00000000000000type priority = Non_blocking | Maybe_blocking let io_priority = Non_blocking (* Create scheduler *) let scheduler = Duppy.create () (* Create two queues, * one for non blocking events * and another for blocking * events *) let new_queue ~priority ~name () = let log = Printf.printf "%s: %s\n%!" name in let priorities p = p = priority in let queue () = Duppy.queue scheduler ~log ~priorities name in Thread.create queue () let th = ignore (new_queue ~priority:Non_blocking ~name:"Non blocking queue" ()); ignore (new_queue ~priority:Maybe_blocking ~name:"Maybe blocking queue #1" ()); new_queue ~priority:Maybe_blocking ~name:"Maybe blocking queue #2" () let exec_command s () = let chan = Unix.open_process_in s in let rec aux () = match try Some (input_line chan) with End_of_file -> None with | None -> [] | Some s -> s :: aux () in let l = aux () in ignore (Unix.close_process_in chan); Duppy.Monad.return (String.concat "\r\n" l) let commands = Hashtbl.create 10 let () = Hashtbl.add commands "hello" (false, fun () -> Duppy.Monad.return "world"); Hashtbl.add commands "foo" (false, fun () -> Duppy.Monad.return "bar"); Hashtbl.add commands "uptime" (true, exec_command "uptime"); Hashtbl.add commands "date" (true, exec_command "date"); Hashtbl.add commands "whoami" (true, exec_command "whoami"); Hashtbl.add commands "sleep" (true, exec_command "sleep 15"); Hashtbl.add commands "exit" (true, fun () -> Duppy.Monad.raise ()) (* Add commands here *) let help = Buffer.create 10 let () = Buffer.add_string help "List of commands:"; Hashtbl.iter (fun x _ -> Buffer.add_string help (Printf.sprintf "\r\n%s" x)) commands; Hashtbl.add commands "help" (false, fun () -> Duppy.Monad.return (Buffer.contents help)) let handle_client socket = let on_error e = match e with | Duppy.Io.Io_error -> Printf.printf "Client disconnected" | Duppy.Io.Unix (c, p, m, _) -> Printf.printf "%s" (Printexc.to_string (Unix.Unix_error (c, p, m))) | Duppy.Io.Unknown (e, _) -> Printf.printf "%s" (Printexc.to_string e) | Duppy.Io.Timeout -> Printf.printf "Timeout" in let h = { Duppy.Monad.Io.scheduler; socket; data = ""; on_error } in (* Read and process lines *) let rec exec () = let __pa_duppy_0 = Duppy.Monad.Io.read ?timeout:None ~priority:io_priority ~marker:(Duppy.Io.Split "[\r\n]+") h in Duppy.Monad.bind __pa_duppy_0 (fun req -> let __pa_duppy_0 = try let blocking, command = Hashtbl.find commands req in if not blocking then command () else Duppy.Monad.Io.exec ~priority:Maybe_blocking h (command ()) with Not_found -> Duppy.Monad.return "ERROR: unknown command, type \"help\" to get a list of commands." in Duppy.Monad.bind __pa_duppy_0 (fun ans -> Duppy.Monad.bind (Duppy.Monad.bind (Duppy.Monad.Io.write ?timeout:None ~priority:io_priority h (Bytes.unsafe_of_string "BEGIN\r\n")) (fun () -> Duppy.Monad.bind (Duppy.Monad.Io.write ?timeout:None ~priority:io_priority h (Bytes.unsafe_of_string ans)) (fun () -> Duppy.Monad.Io.write ?timeout:None ~priority:io_priority h (Bytes.unsafe_of_string "\r\nEND\r\n")))) (fun () -> exec ()))) in let close () = try Unix.close socket with _ -> () in let return () = let on_error e = on_error e; close () in Duppy.Io.write ~priority:io_priority ~on_error ~exec:close scheduler ~string:(Bytes.unsafe_of_string "Bye!\r\n") socket in Duppy.Monad.run ~return ~raise:close (exec ()) open Unix let port = 4123 let bind_addr_inet = inet_addr_of_string "0.0.0.0" let bind_addr = ADDR_INET (bind_addr_inet, port) let max_conn = 10 let sock = socket PF_INET SOCK_STREAM 0 let () = setsockopt sock SO_REUSEADDR true; let rec incoming _ = (try let s, caller = accept sock in let ip = let a = match caller with ADDR_INET (a, _) -> a | _ -> assert false in try (gethostbyaddr a).h_name with Not_found -> string_of_inet_addr a in Printf.printf "New client: %s\n" ip; handle_client s with e -> Printf.printf "Failed to accept new client: %S\n" (Printexc.to_string e)); [ { Duppy.Task.priority = io_priority; Duppy.Task.events = [`Read sock]; Duppy.Task.handler = incoming; }; ] in (try bind sock bind_addr with Unix.Unix_error (Unix.EADDRINUSE, "bind", "") -> failwith (Printf.sprintf "port %d already taken" port)); listen sock max_conn; Duppy.Task.add scheduler { Duppy.Task.priority = io_priority; Duppy.Task.events = [`Read sock]; Duppy.Task.handler = incoming; }; Thread.join th liquidsoap-2.4.2/src/modules/ndi/000077500000000000000000000000001513273233300167045ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/ndi/dune000066400000000000000000000001051513273233300175560ustar00rootroot00000000000000(library (optional) (name ndi) (libraries ctypes ctypes.foreign)) liquidsoap-2.4.2/src/modules/ndi/ndi.ml000066400000000000000000000305321513273233300200130ustar00rootroot00000000000000open Ctypes open Foreign module type Config = sig val filename : string end exception Library_not_found exception Library_initialized of string type source = { source_name : string; source_url : string } let strlen = foreign "strlen" (ptr char @-> returning int) (* let malloc = foreign "malloc" (size_t @-> returning (ptr void)) let malloc typ = let ptr = malloc (Unsigned.Size_t.of_int (sizeof typ)) in if is_null ptr then failwith "out of memory!"; from_voidp typ ptr let memcpy = foreign "memcpy" (ptr void @-> ptr void @-> size_t @-> returning void) let memcpy : 'a. 'a ptr -> 'a ptr -> unit = fun dst src -> memcpy (to_voidp dst) (to_voidp src) (Unsigned.Size_t.of_int (sizeof (reference_type dst))) *) let opt_str_ptr = function | None -> from_voidp char null | Some s -> let s = CArray.of_string s in CArray.start s let opt_int64 = function None -> Int64.max_int | Some i -> i let source_struct : [ `Source ] structure typ = structure "NDIlib_source_t" let source_p_ndi_name = field source_struct "p_ndi_name" (ptr char) let source_p_url_address = field source_struct "p_url_address" (ptr char) let () = seal source_struct let source_name source = let name = getf !@source source_p_ndi_name in if is_null name then "" else string_from_ptr name ~length:(strlen name) let source_url source = let url = getf !@source source_p_url_address in if is_null url then "" else string_from_ptr url ~length:(strlen url) let find_create_struct : [ `Find_create ] structure typ = structure "NDIlib_find_create_t" let find_create_show_local_sources = field find_create_struct "show_local_sources" bool let find_create_p_groups = field find_create_struct "p_groups" (ptr char) let find_create_p_extra_ips = field find_create_struct "p_extra_ips" (ptr char) let () = seal find_create_struct let find_instance = typedef void "NDIlib_find_instance_t" let send_create_struct : [ `Send_create ] structure typ = structure "NDIlib_send_create_t" let send_create_p_ndi_name = field send_create_struct "p_ndi_name" (ptr char) let send_create_p_groups = field send_create_struct "p_groups" (ptr char) let send_create_clock_video = field send_create_struct "clock_video" bool let send_create_clock_audio = field send_create_struct "clock_audio" bool let () = seal send_create_struct let send_instance = typedef void "NDIlib_send_instance_t" module Frame = struct (* type frame_type = [ `None | `Video | `Audio | `Metadata | `Error | `Status_change ] let frame_type = function | `None -> 0 | `Video -> 1 | `Audio -> 2 | `Metadata -> 3 | `Error -> 4 | `Status_change -> 100 *) let four_cc a b c d = Char.code a lor (Char.code b lsl 8) lor (Char.code c lsl 16) lor (Char.code d lsl 24) module Video = struct type i420 = { data : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t; stride : int; } type data = [ `I420 of i420 ] type format = [ `Progressive ] type t = { xres : int; yres : int; frame_rate_N : int; frame_rate_D : int; picture_aspect_ratio : float option; format : format; timecode : int64 option; data : data; metadata : string option; timestamp : int64 option; } let four_cc_code = function `I420 -> four_cc 'I' '4' '2' '0' let format_code = function `Progressive -> 1 let video_frame_struct : [ `Video_frame_v2 ] structure typ = structure "NDIlib_video_frame_v2_t" let xres_field = field video_frame_struct "xres" int let yres_field = field video_frame_struct "yres" int let four_cc_field = field video_frame_struct "FourCC" int let frame_rate_N_field = field video_frame_struct "frame_rate_N" int let frame_rate_D_field = field video_frame_struct "frame_rate_D" int let picture_aspect_ratio_field = field video_frame_struct "picture_aspect_ratio" float let format_field = field video_frame_struct "format" int let timecode_field = field video_frame_struct "timecode" int64_t let data_field = field video_frame_struct "data" (ptr uint8_t) let line_stride_in_bytes_field = field video_frame_struct "line_stride_in_bytes" int let metadata_field = field video_frame_struct "metadata" (ptr char) let timestamp_field = field video_frame_struct "timestamp" int64_t let () = seal video_frame_struct let frame { xres; yres; frame_rate_N; frame_rate_D; picture_aspect_ratio; format; timecode; data; metadata; timestamp; } = let frame = make video_frame_struct in setf frame xres_field xres; setf frame yres_field yres; setf frame frame_rate_N_field frame_rate_N; setf frame frame_rate_D_field frame_rate_D; setf frame picture_aspect_ratio_field (Option.value ~default:0. picture_aspect_ratio); setf frame format_field (format_code format); setf frame timecode_field (opt_int64 timecode); setf frame metadata_field (opt_str_ptr metadata); setf frame timestamp_field (opt_int64 timestamp); let four_cc, data, stride = match data with | `I420 { data; stride } -> let four_cc = four_cc_code `I420 in let data = coerce (ptr int) (ptr uint8_t) (bigarray_start array1 data) in (four_cc, data, stride) in setf frame four_cc_field four_cc; setf frame data_field data; setf frame line_stride_in_bytes_field stride; frame end module Audio = struct type fltp = { data : (float, Bigarray.float32_elt, Bigarray.c_layout) Bigarray.Array1.t; stride : int; } type data = [ `Fltp of fltp ] let four_cc_code = function `Fltp -> four_cc 'F' 'L' 'T' 'p' type t = { sample_rate : int; channels : int; samples : int; timecode : int64 option; data : data; metadata : string option; timestamp : int64 option; } let audio_frame_struct : [ `Audio_frame_v3 ] structure typ = structure "NDIlib_audio_frame_v3_t" let sample_rate_field = field audio_frame_struct "sample_rate" int let no_channels_field = field audio_frame_struct "no_channels" int let no_samples_field = field audio_frame_struct "no_samples" int let timecode_field = field audio_frame_struct "timecode" int64_t let four_cc_field = field audio_frame_struct "FourCC" int let data_field = field audio_frame_struct "data" (ptr uint8_t) let channel_stride_in_bytes_field = field audio_frame_struct "channel_stride_in_bytes" int let p_metadata_field = field audio_frame_struct "p_metadata" (ptr char) let timestamp_field = field audio_frame_struct "timestamp" int64_t let () = seal audio_frame_struct let frame { sample_rate; channels; samples; timecode; data; metadata; timestamp } = let frame = make audio_frame_struct in setf frame sample_rate_field sample_rate; setf frame no_channels_field channels; setf frame no_samples_field samples; setf frame timecode_field (opt_int64 timecode); let four_cc, data, stride = match data with | `Fltp { data; stride } -> let four_cc = four_cc_code `Fltp in let data = coerce (ptr float) (ptr uint8_t) (bigarray_start array1 data) in (four_cc, data, stride) in setf frame four_cc_field four_cc; setf frame data_field data; setf frame channel_stride_in_bytes_field stride; setf frame p_metadata_field (opt_str_ptr metadata); setf frame timestamp_field (opt_int64 timestamp); frame end end module C (Conf : Config) = struct let lib = try Dl.dlopen ~filename:Conf.filename ~flags:[Dl.RTLD_NOW] with _ -> raise Library_not_found let foreign = foreign ~from:lib let initialize = foreign "NDIlib_initialize" (void @-> returning void) let destroy = foreign "NDIlib_destroy" (void @-> returning void) let version = foreign "NDIlib_version" (void @-> returning string) let find_create_v2 = foreign "NDIlib_find_create_v2" (ptr find_create_struct @-> returning (ptr_opt find_instance)) let find_destroy = foreign "NDIlib_find_destroy" (ptr find_instance @-> returning void) let find_wait_for_sources = foreign "NDIlib_find_wait_for_sources" (ptr find_instance @-> uint32_t @-> returning bool) let get_current_sources = foreign "NDIlib_find_get_current_sources" (ptr find_instance @-> ptr uint32_t @-> returning (ptr source_struct)) let find ?(show_local_sources = true) ?groups ?extra_ips ?(timeout = 500) () = let find_create = make find_create_struct in setf find_create find_create_show_local_sources show_local_sources; setf find_create find_create_p_groups (opt_str_ptr groups); setf find_create find_create_p_extra_ips (opt_str_ptr extra_ips); match find_create_v2 (addr find_create) with | None -> failwith "Error while creating find_create instance!" | Some f -> Gc.finalise find_destroy f; while not (find_wait_for_sources f (Unsigned.UInt32.of_int timeout)) do () done; let nb_sources = allocate uint32_t (Unsigned.UInt32.of_int 0) in let source = get_current_sources f nb_sources in let nb_sources = Unsigned.UInt32.to_int !@nb_sources in let rec get pos sources = if pos < nb_sources then ( let s = source +@ pos in get (pos + 1) ({ source_name = source_name s; source_url = source_url s } :: sources)) else sources in get 0 [] let send_create = foreign "NDIlib_send_create" (ptr send_create_struct @-> returning (ptr send_instance)) let send_destroy = foreign "NDIlib_send_destroy" (ptr send_instance @-> returning void) let send_create ?groups ?(clock_video = false) ?(clock_audio = false) ?name () = let send_create_settings = make send_create_struct in setf send_create_settings send_create_p_ndi_name (opt_str_ptr name); setf send_create_settings send_create_p_groups (opt_str_ptr groups); setf send_create_settings send_create_clock_video clock_video; setf send_create_settings send_create_clock_audio clock_audio; let send_instance = send_create (addr send_create_settings) in send_instance let send_audio_v3 = foreign "NDIlib_send_send_audio_v3" (ptr void @-> ptr Frame.Audio.audio_frame_struct @-> returning void) let send_video_v2 = foreign "NDIlib_send_send_video_v2" (ptr void @-> ptr Frame.Video.video_frame_struct @-> returning void) end module type C = module type of C (struct let filename = "foo" end) type t = { _module : (module C) } let initialized = Atomic.make None let init ~filename () = (match Atomic.get initialized with | Some f when f <> filename -> raise (Library_initialized f) | _ -> Atomic.set initialized (Some filename)); try let module C = C (struct let filename = filename end) in C.initialize (); let _module = (module C : C) in let finalise _ = C.destroy () in let handler = { _module = (module C : C) } in Gc.finalise finalise handler; handler with _ -> raise Library_not_found let version { _module } = let module C = (val _module : C) in C.version () let find ?show_local_sources ?groups ?extra_ips ?timeout { _module } = let module C = (val _module : C) in let extra_ips = Option.map (fun ips -> String.concat "," ips) extra_ips in C.find ?show_local_sources ?groups ?extra_ips ?timeout () module Send = struct type sender = { sender_module : (module C); sender : unit ptr } let init ?clock_audio ?clock_video ?groups ?name { _module } = let module C = (val _module : C) in { sender_module = _module; sender = C.send_create ?clock_audio ?clock_video ?groups ?name (); } let send_audio { sender_module; sender } frame = let module C = (val sender_module : C) in let frame = Frame.Audio.frame frame in C.send_audio_v3 sender (addr frame) let send_video { sender_module; sender } frame = let module C = (val sender_module : C) in let frame = Frame.Video.frame frame in C.send_video_v2 sender (addr frame) let destroy { sender_module; sender } = let module C = (val sender_module : C) in C.send_destroy sender end liquidsoap-2.4.2/src/modules/ndi/ndi.mli000066400000000000000000000032611513273233300201630ustar00rootroot00000000000000(** Binding to the proprietary NDI library. Please refer to the library's documentation for details regarding this binding's functions. *) type t type source = { source_name : string; source_url : string } exception Library_not_found exception Library_initialized of string val init : filename:string -> unit -> t val version : t -> string val find : ?show_local_sources:bool -> ?groups:string -> ?extra_ips:string list -> ?timeout:int -> t -> source list module Frame : sig module Audio : sig type fltp = { data : (float, Bigarray.float32_elt, Bigarray.c_layout) Bigarray.Array1.t; stride : int; } type data = [ `Fltp of fltp ] type t = { sample_rate : int; channels : int; samples : int; timecode : int64 option; data : data; metadata : string option; timestamp : int64 option; } end module Video : sig type i420 = { data : (int, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t; stride : int; } type data = [ `I420 of i420 ] type format = [ `Progressive ] type t = { xres : int; yres : int; frame_rate_N : int; frame_rate_D : int; picture_aspect_ratio : float option; format : format; timecode : int64 option; data : data; metadata : string option; timestamp : int64 option; } end end module Send : sig type sender val init : ?clock_audio:bool -> ?clock_video:bool -> ?groups:string -> ?name:string -> t -> sender val send_audio : sender -> Frame.Audio.t -> unit val send_video : sender -> Frame.Video.t -> unit val destroy : sender -> unit end liquidsoap-2.4.2/src/modules/stereotool/000077500000000000000000000000001513273233300203315ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/stereotool/dune000066400000000000000000000001141513273233300212030ustar00rootroot00000000000000(library (optional) (name stereotool) (libraries ctypes ctypes.foreign)) liquidsoap-2.4.2/src/modules/stereotool/stereotool.ml000066400000000000000000000106741513273233300230720ustar00rootroot00000000000000open Ctypes open Foreign type handler = unit ptr let handler : handler typ = ptr void module type Config = sig val filename : string end exception Library_not_found exception Library_initialized of string let strnlen = foreign "strnlen" (ocaml_bytes @-> int @-> returning int) module C (Conf : Config) = struct let lib = try Dl.dlopen ~filename:Conf.filename ~flags:[Dl.RTLD_NOW] with _ -> raise Library_not_found let foreign = foreign ~from:lib let software_version = foreign "stereoTool_GetSoftwareVersion" (void @-> returning int) let api_version = foreign "stereoTool_GetApiVersion" (void @-> returning int) let create = foreign "stereoTool_Create" (string_opt @-> returning handler) let delete = foreign "stereoTool_Delete" (handler @-> returning void) let valid_license = foreign "stereoTool_CheckLicenseValid" (handler @-> returning bool) let unlincensed_used_features = foreign "stereoTool_GetUnlicensedUsedFeatures" (handler @-> ocaml_bytes @-> int @-> returning bool) let load_preset = foreign "stereoTool_LoadPreset" (handler @-> string @-> int @-> returning bool) let save_preset = foreign "stereoTool_SavePreset" (handler @-> string @-> int @-> returning bool) let reset = foreign "stereoTool_Reset" (handler @-> returning void) let latency = foreign "stereoTool_GetLatency2" (handler @-> int @-> bool @-> returning int) let process = foreign "stereoTool_Process" (handler @-> ptr float @-> int @-> int @-> int @-> returning void) end module type C = module type of C (struct let filename = "foo" end) type t = { handler : handler; _module : (module C) } type load_type = [ `Totalinit | `All_settings | `Audiofm | `Audio | `Processing | `Repair | `Repair_no_pnr | `Sublevel_pnr ] let int_of_load_type = function | `Totalinit -> 10387 | `All_settings -> 10386 | `Audiofm -> 10385 | `Audio -> 10384 | `Processing -> 10709 | `Repair -> 10708 | `Repair_no_pnr -> 11069 | `Sublevel_pnr -> 10699 let initialized = Atomic.make None let init ?license_key ~filename () = (match Atomic.get initialized with | Some f when f <> filename -> raise (Library_initialized f) | _ -> Atomic.set initialized (Some filename)); try let module C = C (struct let filename = filename end) in let handler = C.create license_key in Gc.finalise C.delete handler; { handler; _module = (module C : C) } with _ -> raise Library_not_found let api_version { _module; _ } = let module C = (val _module : C) in C.api_version () let software_version { _module; _ } = let module C = (val _module : C) in C.software_version () let valid_license { handler; _module } = let module C = (val _module : C) in C.valid_license handler let unlincensed_used_features = let buflen = 1024 in let buf = Bytes.create buflen in fun { handler; _module } -> let module C = (val _module : C) in let ptr = ocaml_bytes_start buf in if C.unlincensed_used_features handler ptr buflen then ( let n = strnlen ptr buflen in Some (Bytes.to_string (Bytes.sub buf 0 n))) else None let load_preset ?(load_type = `Totalinit) ~filename { handler; _module } = let module C = (val _module : C) in C.load_preset handler filename (int_of_load_type load_type) let latency ~samplerate ~feed_silence { handler; _module } = let module C = (val _module : C) in C.latency handler samplerate feed_silence let process_interleaved ~samplerate ~channels { handler; _module } samples ofs len = let module C = (val _module : C) in let buf = CArray.make float len in for i = 0 to len - 1 do CArray.unsafe_set buf i samples.(ofs + i) done; C.process handler (CArray.start buf) len channels samplerate; for i = 0 to len - 1 do samples.(ofs + i) <- CArray.unsafe_get buf i done let process ~samplerate { handler; _module } samples ofs len = let channels = Array.length samples in if 0 < channels && 0 < len then ( let module C = (val _module : C) in let buf = CArray.make float (channels * len) in for c = 0 to channels - 1 do let chan = samples.(c) in for i = 0 to len - 1 do CArray.unsafe_set buf ((i * channels) + c) chan.(ofs + i) done done; C.process handler (CArray.start buf) len channels samplerate; for c = 0 to channels - 1 do let chan = samples.(c) in for i = 0 to len - 1 do chan.(ofs + i) <- CArray.unsafe_get buf ((i * channels) + c) done done) liquidsoap-2.4.2/src/modules/stereotool/stereotool.mli000066400000000000000000000016011513273233300232310ustar00rootroot00000000000000(** Binding to the proprietary StereoTool processing library. Please refer to the library's documentation for details regarding this binding's functions. *) type t type load_type = [ `Totalinit | `All_settings | `Audiofm | `Audio | `Processing | `Repair | `Repair_no_pnr | `Sublevel_pnr ] exception Library_not_found exception Library_initialized of string val init : ?license_key:string -> filename:string -> unit -> t val software_version : t -> int val api_version : t -> int val valid_license : t -> bool val unlincensed_used_features : t -> string option val load_preset : ?load_type:load_type -> filename:string -> t -> bool val latency : samplerate:int -> feed_silence:bool -> t -> int val process_interleaved : samplerate:int -> channels:int -> t -> float array -> int -> int -> unit val process : samplerate:int -> t -> float array array -> int -> int -> unit liquidsoap-2.4.2/src/modules/xmlplaylist/000077500000000000000000000000001513273233300205145ustar00rootroot00000000000000liquidsoap-2.4.2/src/modules/xmlplaylist/dune000066400000000000000000000001531513273233300213710ustar00rootroot00000000000000(library (name xmlplaylist) (optional) (libraries xmlm) (synopsis "Parser for various xml playlists")) liquidsoap-2.4.2/src/modules/xmlplaylist/xmlplaylist.ml000066400000000000000000000142131513273233300234310ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable audio stream generator. Copyright 2003-2007 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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 *****************************************************************************) (* Generic xml parsing module * Uses xmlm library *) type error = XmlError of string | Empty | UnknownType | Internal type format = Podcast | Xspf | Smil | Asx exception Error of error let string_of_error = function | XmlError s -> Printf.sprintf "xml error: %s" s | Empty -> "no interesting data in Xml" | UnknownType -> "unknown Xml type" | Internal -> "xmlplaylist internal error" let () = let f = function Error e -> Some (string_of_error e) | _ -> None in Printexc.register_printer f let raise e = raise (Error e) (** Wrapper to parse data from Xmlm and return * a xml tree as previously returned by xml-light *) type xml = | Element of (string * (string * string) list * xml list) | PCData of string let parse_string s = let source = `String (0, s) in let input = Xmlm.make_input source in (* Map a tag representation in xmlm to * (name, attributes list) where attribute = string*string. *) let make_tag (x, l) = (* Forget about the uri attribute *) let l = List.map (fun ((_, y), z) -> (y, z)) l in (snd x, l) in let rec get_elems l = if Xmlm.eoi input then l else ( match Xmlm.input input with | `El_start tag -> let elem = get_elems [] in let name, attributes = make_tag tag in get_elems (Element (name, attributes, List.rev elem) :: l) | `El_end -> l | `Data s -> get_elems (PCData s :: l) | `Dtd _ -> get_elems l) in try let elems = get_elems [] in Element ("", [], List.rev elems) with Xmlm.Error (_, e) -> raise (XmlError (Xmlm.error_message e)) let rec lowercase_tags xml = match xml with | Element (s, l, x) -> Element ( String.lowercase_ascii s, List.map (fun (a, b) -> (String.lowercase_ascii a, b)) l, List.map lowercase_tags x ) | _ -> xml let rec get_format x = (* Assume any rss is a podcast due to broken * implementation.. *) (* let rec match_rss l = match l with | (s,s') :: l' when s = "xmlns:itunes" -> Podcast | _ :: l' -> match_rss l' | [] -> raise UnknownType in *) match x with | Element (s, _, _) :: _ when s = "playlist" -> Xspf | Element (s, _, _) :: _ when s = "rss" -> Podcast (* match_rss l *) | Element (s, _, _) :: _ when s = "smil" -> Smil | Element (s, _, _) :: _ when s = "asx" -> Asx | Element (_, _, x) :: l' -> get_format (l' @ x) | _ :: l' -> get_format l' | [] -> raise UnknownType let podcast_uri l _ = try List.assoc "url" l with _ -> raise Empty let xspf_uri _ x = match x with [PCData v] -> v | _ -> raise Empty let asx_uri l _ = try List.assoc "href" l with _ -> raise Empty (* Should return useful markup values for parsing: * author,location,track,extract loc function *) let xml_spec f = match f with | Podcast -> ("itunes:author", "enclosure", "item", podcast_uri) | Xspf -> ("creator", "location", "track", xspf_uri) | Asx -> ("author", "ref", "entry", asx_uri) | _ -> raise Internal let xml_tracks t xml = let author, location, track, extract = xml_spec t in let rec get_tracks l r = match l with | Element (s, _, x) :: l' when s = track -> get_tracks l' (x :: r) | Element (_, _, x) :: l' -> get_tracks (l' @ x) r | _ :: l' -> get_tracks l' r | [] -> r in let tracks = get_tracks [xml] [] in let rec parse_uri l = match l with | Element (s, l, x) :: _ when s = location -> extract l x | Element (_, _, _) :: l' -> parse_uri l' | _ :: l' -> parse_uri l' | [] -> raise Empty in let rec parse_metadatas m l = match l with | Element (s, _, [PCData x]) :: l' when s = author -> ("artist", x) :: parse_metadatas m l' | Element (s, _, [PCData x]) :: l' -> (s, x) :: parse_metadatas m l' | _ :: l' -> parse_metadatas m l' | [] -> m in let rec parse_tracks t r = match t with | track :: l -> ( try parse_tracks l ((parse_metadatas [] track, parse_uri track) :: r) with Error Empty -> parse_tracks l r) | [] -> r in parse_tracks tracks [] let smil_tracks xml = let rec get_tracks r l = match l with | Element ("audio", l', _) :: l'' -> get_tracks (l' :: r) l'' | Element (_, _, x) :: l' -> get_tracks r (l' @ x) | _ :: l' -> get_tracks r l' | [] -> r in let tracks = get_tracks [] [xml] in let smil_uri l = try List.assoc "src" l with _ -> raise Internal in let rec smil_meta m l = match l with | (s, s') :: l' when s = "author" -> ("artist", s') :: smil_meta m l' | (s, s') :: l' -> (s, s') :: smil_meta m l' | [] -> m in let rec parse_tracks t r = match t with | track :: l -> parse_tracks l (try (smil_meta [] track, smil_uri track) :: r with Error Empty -> r) | [] -> r in parse_tracks tracks [] let tracks ?format xml = let xml = lowercase_tags (parse_string xml) in let t = match format with Some t -> t | None -> get_format [xml] in match t with | Podcast | Xspf | Asx -> xml_tracks t xml | Smil -> smil_tracks xml let detect_format xml = let xml = lowercase_tags (parse_string xml) in get_format [xml] liquidsoap-2.4.2/src/modules/xmlplaylist/xmlplaylist.mli000066400000000000000000000040571513273233300236070ustar00rootroot00000000000000(***************************************************************************** Liquidsoap, a programmable audio stream generator. Copyright 2003-2007 Savonet team 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, fully stated in the COPYING file at the root of the liquidsoap distribution. 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 *****************************************************************************) (** Generic xml playlist parsing module This module implements basic playlist parsing for various xml formats. Currently supported formats are: [podcast (rss), xspf, smil asx] *) (** {2 Types and exceptions} *) type error = XmlError of string | Empty | UnknownType | Internal exception Error of error type format = Podcast | Xspf | Smil | Asx (** {2 Functions} *) (** Get meaning of Error e *) val string_of_error : error -> string (** [tracks data] performs whole process and outputs a list of metadatas,uri from given xml data string All metadatas are what is provided by the playlist The only variable name that is changed is the author because each formats has its own field for that. The module will use "artist". Order of tracks is preserved. [format] is an optional argument, used to force format detection. Otherwise. [detect_format] is used. *) val tracks : ?format:format -> string -> ((string * string) list * string) list (** Try to detect the format automatically. *) val detect_format : string -> format liquidsoap-2.4.2/tests/000077500000000000000000000000001513273233300150355ustar00rootroot00000000000000liquidsoap-2.4.2/tests/core/000077500000000000000000000000001513273233300157655ustar00rootroot00000000000000liquidsoap-2.4.2/tests/core/clock_utils_test.ml000066400000000000000000000301041513273233300216670ustar00rootroot00000000000000let check name result expected = if result <> expected then begin Printf.printf "Test %s failed!\n" name; Printf.printf "Expected:\n%s\n" expected; Printf.printf "Got:\n%s\n" result; assert false end let src id activations = Clock_utils.{ id; activations } let () = (* Test simple clock with no sub-clocks *) let entry = Clock_utils. { name = "main_clock"; outputs = [src "output1" []; src "output2" []]; active = [src "src1" []]; passive = [src "src2" []; src "src3" []]; sub_clocks = []; } in let result = Clock_utils.format_clock entry in let expected = {|main_clock: ├── outputs = [output1 [], output2 []] ├── active = [src1 []] └── passive = [src2 [], src3 []]|} in check "simple clock" result expected; (* Test clock with sub-clock *) let entry = Clock_utils. { name = "parent"; outputs = [src "out1" []]; active = []; passive = [src "p1" []]; sub_clocks = [ { name = "child"; outputs = [src "out2" []]; active = [src "a1" []]; passive = []; sub_clocks = []; }; ]; } in let result = Clock_utils.format_clock entry in let expected = {|parent: ├── outputs = [out1 []] ├── passive = [p1 []] └─ child: │ outputs = [out2 []] │ active = [a1 []]|} in check "clock with sub-clock" result expected; (* Test line wrapping *) let entry = Clock_utils. { name = "audio.producer"; outputs = [src "audio.consumer" []]; active = [src "foo" []]; passive = [ src "metadata_map.3" []; src "mksafe" []; src "track_metadata_deduplicate" []; src "safe_blank" []; src "safe_blank.1" []; src "cross" []; src "metadata_deduplicate" []; src "metadata_map.2" []; src "mksafe.1" []; ]; sub_clocks = []; } in let result = Clock_utils.format_clock ~max_width:80 entry in let expected = {|audio.producer: ├── outputs = [audio.consumer []] ├── active = [foo []] └── passive = [cross [], metadata_deduplicate [], metadata_map.2 [], metadata_map.3 [], mksafe [], mksafe.1 [], safe_blank [], safe_blank.1 [], track_metadata_deduplicate []]|} in check "line wrapping" result expected; (* Test empty lists are omitted *) let entry = Clock_utils. { name = "minimal"; outputs = []; active = []; passive = [src "only_passive" []]; sub_clocks = []; } in let result = Clock_utils.format_clock entry in let expected = {|minimal: └── passive = [only_passive []]|} in check "empty lists omitted" result expected; (* Test with activations *) let entry = Clock_utils. { name = "with_activations"; outputs = [src "out1" ["switch"; "fallback"]]; active = [src "src1" ["main"]]; passive = [src "src2" []]; sub_clocks = []; } in let result = Clock_utils.format_clock entry in let expected = {|with_activations: ├── outputs = [out1 [switch, fallback]] ├── active = [src1 [main]] └── passive = [src2 []]|} in check "with activations" result expected; (* Test format_dump with single clock *) let entry = Clock_utils. { clock_name = "main"; ticks = 42; time = 1.05; self_sync = true; outputs = [src "out1" []; src "out2" []]; active = [src "active1" []]; passive = [src "passive1" []; src "passive2" []]; sub_clocks = []; } in let result = Clock_utils.format_dump [entry] in let expected = {|· main (ticks: 42, time: 1.05s, self_sync: true) ├── outputs: out1 [], out2 [] ├── active sources: active1 [] └── passive sources: passive1 [], passive2 []|} in check "dump single clock" result expected; (* Test format_dump with nested clocks *) let entry = Clock_utils. { clock_name = "parent"; ticks = 100; time = 2.50; self_sync = false; outputs = [src "output" []]; active = []; passive = [src "src1" []]; sub_clocks = [ { clock_name = "child"; ticks = 50; time = 1.25; self_sync = true; outputs = [src "child_out" []]; active = [src "child_active" []]; passive = []; sub_clocks = []; }; ]; } in let result = Clock_utils.format_dump [entry] in let expected = {|· parent (ticks: 100, time: 2.50s, self_sync: false) ├── outputs: output [] ├── active sources: ├── passive sources: src1 [] └── child (ticks: 50, time: 1.25s, self_sync: true) ├── outputs: child_out [] ├── active sources: child_active [] └── passive sources:|} in check "dump nested clocks" result expected; (* Test format_dump with multiple top-level clocks *) let entries = Clock_utils. [ { clock_name = "clock1"; ticks = 10; time = 0.25; self_sync = false; outputs = [src "o1" []]; active = [src "a1" []]; passive = []; sub_clocks = []; }; { clock_name = "clock2"; ticks = 20; time = 0.50; self_sync = true; outputs = []; active = []; passive = [src "p1" []]; sub_clocks = []; }; ] in let result = Clock_utils.format_dump entries in let expected = {|· clock1 (ticks: 10, time: 0.25s, self_sync: false) ├── outputs: o1 [] ├── active sources: a1 [] └── passive sources: · clock2 (ticks: 20, time: 0.50s, self_sync: true) ├── outputs: ├── active sources: └── passive sources: p1 []|} in check "dump multiple clocks" result expected; (* Test format_dump with line wrapping *) let entry = Clock_utils. { clock_name = "main"; ticks = 100; time = 2.50; self_sync = false; outputs = [src "output1" []; src "output2" []; src "output3" []]; active = [ src "active_source_1" []; src "active_source_2" []; src "active_source_3" []; ]; passive = [ src "passive1" []; src "passive2" []; src "passive3" []; src "passive4" []; src "passive5" []; ]; sub_clocks = []; } in let result = Clock_utils.format_dump ~max_width:70 [entry] in let expected = {|· main (ticks: 100, time: 2.50s, self_sync: false) ├── outputs: output1 [], output2 [], output3 [] ├── active sources: active_source_1 [], active_source_2 [], │ active_source_3 [] └── passive sources: passive1 [], passive2 [], passive3 [], passive4 [], passive5 []|} in check "dump line wrapping" result expected; (* Test format_dump with activations *) let entry = Clock_utils. { clock_name = "main"; ticks = 10; time = 0.25; self_sync = false; outputs = [src "out1" ["switch"; "fallback"]]; active = [src "src1" ["main"]]; passive = [src "src2" []]; sub_clocks = []; } in let result = Clock_utils.format_dump [entry] in let expected = {|· main (ticks: 10, time: 0.25s, self_sync: false) ├── outputs: out1 [switch, fallback] ├── active sources: src1 [main] └── passive sources: src2 []|} in check "dump with activations" result expected; (* Test format_source_graph with simple tree Activations point upward: playlist is activated by audio.producer, etc. *) let gsrc name kind activations = Clock_utils. { source_name = name; source_kind = kind; source_activations = activations; } in let sources = [ gsrc "output.icecast" `Output []; gsrc "encoder" `Passive ["output.icecast"]; gsrc "audio.producer" `Passive ["encoder"]; gsrc "playlist" `Passive ["audio.producer"]; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · output.icecast [output] └── encoder [passive] └── audio.producer [passive] └── playlist [passive]|} in check "source graph simple tree" result expected; (* Test format_source_graph with shared source shared_encoder is activated by both outputs *) let sources = [ gsrc "output.icecast" `Output []; gsrc "output.file" `Output []; gsrc "shared_encoder" `Passive ["output.icecast"; "output.file"]; gsrc "audio" `Passive ["shared_encoder"]; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · output.icecast [output] └── shared_encoder [passive] └── audio [passive] · output.file [output] └── shared_encoder [passive] (*)|} in check "source graph shared source" result expected; (* Test format_source_graph with multiple activations All playlists are activated by the switch *) let sources = [ gsrc "output" `Output []; gsrc "switch" `Active ["output"]; gsrc "playlist1" `Passive ["switch"]; gsrc "playlist2" `Passive ["switch"]; gsrc "fallback" `Passive ["switch"]; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · output [output] └── switch [active] ├── playlist1 [passive] ├── playlist2 [passive] └── fallback [passive]|} in check "source graph multiple activations" result expected; (* Test format_source_graph with singletons *) let sources = [ gsrc "output" `Output []; gsrc "source" `Passive ["output"]; gsrc "unused" `Passive []; gsrc "orphan" `Active []; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · output [output] └── source [passive] Singletons: · unused [passive] · orphan [active]|} in check "source graph with singletons" result expected; (* Test format_source_graph with external activation on output *) let sources = [ gsrc "output" `Output ["external_clock"]; gsrc "encoder" `Passive ["output"]; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · external_clock [external activation] └── output [output] └── encoder [passive]|} in check "source graph with external output activation" result expected; (* Test format_source_graph with external activation on singleton *) let sources = [ gsrc "output" `Output []; gsrc "encoder" `Passive ["output"]; gsrc "cross_clock_src" `Passive ["ffmpeg_graph"]; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · output [output] └── encoder [passive] Singletons: · ffmpeg_graph [external activation] └── cross_clock_src [passive]|} in check "source graph with external singleton activation" result expected; (* Test format_source_graph with mixed external and standalone *) let sources = [ gsrc "out1" `Output ["ext1"]; gsrc "out2" `Output []; gsrc "src1" `Passive ["out1"]; gsrc "src2" `Passive ["out2"]; gsrc "singleton1" `Passive ["ext2"]; gsrc "singleton2" `Passive []; ] in let result = Clock_utils.format_source_graph sources in let expected = {|Outputs: · ext1 [external activation] └── out1 [output] └── src1 [passive] · out2 [output] └── src2 [passive] Singletons: · ext2 [external activation] └── singleton1 [passive] · singleton2 [passive]|} in check "source graph mixed external and standalone" result expected; Printf.printf "All Clock_utils tests passed!\n%!" liquidsoap-2.4.2/tests/core/content_test.ml000066400000000000000000000042771513273233300210420ustar00rootroot00000000000000open Content let () = Frame_settings.conf_duration#set 0.04; Frame_settings.lazy_config_eval := true let () = let marks ?(offset = 0) len = List.init len (fun x -> x + offset) in let c = Content.make ~length:1000 Content.Track_marks.format in let c' = Content.make ~length:10 Content.Track_marks.format in Track_marks.set_data c (marks 1000); (* Track marks outside of the declared length should be ignored. *) Track_marks.set_data c' (marks 10); assert (Track_marks.get_data c' = marks 10); assert (Track_marks.get_data (Content.sub c 5 10) = List.init 10 (fun x -> x)) (* Test metadata uniqueness. *) let () = let ctype = Frame_type.content_type (Lang.frame_t Lang.unit_t (Frame.Fields.make ~audio:(Format_type.audio ()) ())) in let frame = Frame.create ~length:(Lazy.force Frame.size) ctype in let m = Frame.Metadata.from_list [("foo", "bla")] in let frame = Frame.add_metadata frame 123 m in let m = Frame.Metadata.from_list [("gni", "gno")] in let frame = Frame.add_metadata frame 123 m in assert (Frame.get_all_metadata frame = [(123, m)]) let compare_image (p, img) (p', img') = p = p' && img == img' (* Test content boundaries. We create 3 content chunk and make sure that the consolidated content contains the first one's data. *) let () = let length = Lazy.force Frame.size in let chunk_len = length / 3 in assert (Frame.video_of_main length = 1); let fst = Content.make ~length Content.(default_format Video.kind) in let fst = Content.sub fst 0 chunk_len in let snd = Content.make ~length Content.(default_format Video.kind) in let snd = Content.sub snd chunk_len chunk_len in let thrd = Content.make ~length Content.(default_format Video.kind) in let thrd = Content.sub thrd (2 * chunk_len) (length - (2 * chunk_len)) in let data = Content.append fst snd in let data = Content.append data thrd in assert (Content.length data = length); let data = Content.Video.get_data data in assert (List.length data.Content.Video.data = 1); let fst = Content.Video.get_data fst in assert (List.length fst.Content.Video.data = 1); assert ( compare_image (List.hd fst.Content.Video.data) (List.hd data.Content.Video.data)) liquidsoap-2.4.2/tests/core/decoder_test.ml000066400000000000000000000036061513273233300207700ustar00rootroot00000000000000let () = Frame_settings.lazy_config_eval := true; Frame_settings.conf_video_default#set true let () = let mono = Content.( Audio.lift_params { Content.Audio.channel_layout = Lazy.from_val `Mono }) in let stereo = Content.( Audio.lift_params { Content.Audio.channel_layout = Lazy.from_val `Stereo }) in let five_point_one = Content.( Audio.lift_params { Content.Audio.channel_layout = Lazy.from_val `Five_point_one }) in let canvas = Content.default_format Content_video.kind in let midi = Content.(Midi.lift_params { Content.Midi.channels = 1 }) in assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:stereo ()) (Frame.Fields.make ~audio:stereo ())); assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:mono ()) (Frame.Fields.make ~audio:stereo ())); assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:five_point_one ()) (Frame.Fields.make ~audio:stereo ())); assert ( not (Decoder.can_decode_type (Frame.Fields.make ~audio:mono ()) (Frame.Fields.make ~audio:stereo ~video:canvas ()))); assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:mono ~video:canvas ()) (Frame.Fields.make ~audio:stereo ~video:canvas ())); assert ( not (Decoder.can_decode_type (Frame.Fields.make ~audio:mono ()) (Frame.Fields.make ~audio:stereo ~midi ()))); assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:stereo ~video:canvas ~midi ()) (Frame.Fields.make ~audio:stereo ~midi ())); assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:stereo ~video:canvas ~midi ()) (Frame.Fields.make ~audio:stereo ~video:canvas ~midi ())); assert ( Decoder.can_decode_type (Frame.Fields.make ~audio:stereo ~video:canvas ~midi ()) (Frame.Fields.make ~video:canvas ~midi ())) liquidsoap-2.4.2/tests/core/dune000066400000000000000000000031221513273233300166410ustar00rootroot00000000000000(env (release (ocamlopt_flags (:standard -w -9 -alert --deprecated -O2))) (_ (flags (:standard -w -9 -alert --deprecated)))) ; Regenerate using dune build @gendune --auto-promote (include dune.inc) (executable (name gen_dune) (libraries liquidsoap_build_tools) (modules gen_dune)) (rule (alias gendune) (deps (source_tree .)) (target dune.inc.gen) (action (with-stdout-to dune.inc.gen (run ./gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) (rule (alias citest) (target test.wav) (action (run ffmpeg -f lavfi -i "sine=frequency=440:duration=5" -ac 2 %{target}))) (rule (alias citest) (target test.mp3) (action (run ffmpeg -f lavfi -i "sine=frequency=440:duration=5" -ac 2 %{target}))) (rule (alias citest) (target json) (action (progn (run rm -rf json) (run git clone --depth 1 https://github.com/nst/JSONTestSuite json) (system "cd json && mv test_parsing/*.json .") (system "cd json && find . -maxdepth 1 -type d | grep -v '^\\.$' | xargs rm -rf")))) (rule (alias citest) (target json5) (action (progn (run rm -rf json5) (run git clone --depth 1 https://github.com/json5/json5-tests.git json5) (system "cd json5 && find . -type f | egrep '\\.json|\\.js|\\.txt' | xargs -I % mv % .") (system "cd json5 && find . -type d | grep -v '^\\.$' | xargs rm -rf")))) (rule (alias citest) (target big-list-of-naughty-strings) (action (progn (run rm -rf big-list-of-naughty-strings) (run git clone --depth 1 https://github.com/minimaxir/big-list-of-naughty-strings.git)))) liquidsoap-2.4.2/tests/core/dune.inc000066400000000000000000000163121513273233300174160ustar00rootroot00000000000000 (executable (name clock_utils_test) (modules clock_utils_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias clock_utils_test) (package liquidsoap) (deps (:clock_utils_test clock_utils_test.exe)) (action (run %{clock_utils_test} ))) (executable (name content_test) (modules content_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias content_test) (package liquidsoap) (deps (:content_test content_test.exe)) (action (run %{content_test} ))) (executable (name decoder_test) (modules decoder_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias decoder_test) (package liquidsoap) (deps (:decoder_test decoder_test.exe)) (action (run %{decoder_test} ))) (executable (name ffmpeg_quality) (modules ffmpeg_quality) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias ffmpeg_quality) (package liquidsoap) (deps (:ffmpeg_quality ffmpeg_quality.exe)) (action (run %{ffmpeg_quality} ))) (executable (name flush_test) (modules flush_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias flush_test) (package liquidsoap) (deps (:flush_test flush_test.exe)) (action (run %{flush_test} ))) (executable (name frame_test) (modules frame_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias frame_test) (package liquidsoap) (deps (:frame_test frame_test.exe)) (action (run %{frame_test} ))) (executable (name generator_test) (modules generator_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias generator_test) (package liquidsoap) (deps (:generator_test generator_test.exe)) (action (run %{generator_test} ))) (executable (name http_test) (modules http_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias http_test) (package liquidsoap) (deps (:http_test http_test.exe)) (action (run %{http_test} ))) (executable (name is_url) (modules is_url) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias is_url) (package liquidsoap) (deps (:is_url is_url.exe)) (action (run %{is_url} ))) (executable (name json_test) (modules json_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias json_test) (package liquidsoap) (deps (:json ./json) (:json5 ./json5) (:big-list-of-naughty-strings ./big-list-of-naughty-strings) (:json_test json_test.exe)) (action (run %{json_test} ))) (executable (name meth) (modules meth) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias meth) (package liquidsoap) (deps (:meth meth.exe)) (action (run %{meth} ))) (executable (name more_types) (modules more_types) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias more_types) (package liquidsoap) (deps (:more_types more_types.exe)) (action (run %{more_types} ))) (executable (name output_encoded_test) (modules output_encoded_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias output_encoded_test) (package liquidsoap) (deps (:output_encoded_test output_encoded_test.exe)) (action (run %{output_encoded_test} ))) (executable (name output_test) (modules output_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias output_test) (package liquidsoap) (deps (:output_test output_test.exe)) (action (run %{output_test} ))) (executable (name parsesrt) (modules parsesrt) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias parsesrt) (package liquidsoap) (deps (:test_srt ./test.srt) (:parsesrt parsesrt.exe)) (action (run %{parsesrt} %{test_srt}))) (executable (name playlist_basic_test) (modules playlist_basic_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias playlist_basic_test) (package liquidsoap) (deps (:playlist_basic_test playlist_basic_test.exe)) (action (run %{playlist_basic_test} ))) (executable (name pool_test) (modules pool_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias pool_test) (package liquidsoap) (deps (:pool_test pool_test.exe)) (action (run %{pool_test} ))) (executable (name start_stop_test) (modules start_stop_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias start_stop_test) (package liquidsoap) (deps (:start_stop_test start_stop_test.exe)) (action (run %{start_stop_test} ))) (executable (name stream_decoder_test) (modules stream_decoder_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias stream_decoder_test) (package liquidsoap) (deps (:test_wav ./test.wav) (:test_mp3 ./test.mp3) (:stream_decoder_test stream_decoder_test.exe)) (action (progn (run %{stream_decoder_test} %{test_wav} bla.wav) (run %{stream_decoder_test} %{test_mp3} bla.wav)))) (executable (name string_test) (modules string_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias string_test) (package liquidsoap) (deps (:string_test string_test.exe)) (action (run %{string_test} ))) (executable (name strings_test) (modules strings_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias strings_test) (package liquidsoap) (deps (:strings_test strings_test.exe)) (action (run %{strings_test} ))) (executable (name timezone) (modules timezone) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias timezone) (package liquidsoap) (deps (:timezone timezone.exe)) (action (run %{timezone} ))) (executable (name types) (modules types) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias types) (package liquidsoap) (deps (:types types.exe)) (action (run %{types} ))) (executable (name unifier_test) (modules unifier_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias unifier_test) (package liquidsoap) (deps (:unifier_test unifier_test.exe)) (action (run %{unifier_test} ))) (executable (name utils_test) (modules utils_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias utils_test) (package liquidsoap) (deps (:utils_test utils_test.exe)) (action (run %{utils_test} ))) (executable (name version_test) (modules version_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias version_test) (package liquidsoap) (deps (:version_test version_test.exe)) (action (run %{version_test} ))) (executable (name weak_queue_test) (modules weak_queue_test) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias weak_queue_test) (package liquidsoap) (deps (:weak_queue_test weak_queue_test.exe)) (action (run %{weak_queue_test} ))) (alias (name citest) (deps (alias clock_utils_test) (alias content_test) (alias decoder_test) (alias ffmpeg_quality) (alias flush_test) (alias frame_test) (alias generator_test) (alias http_test) (alias is_url) (alias json_test) (alias meth) (alias more_types) (alias output_encoded_test) (alias output_test) (alias parsesrt) (alias playlist_basic_test) (alias pool_test) (alias start_stop_test) (alias stream_decoder_test) (alias string_test) (alias strings_test) (alias timezone) (alias types) (alias unifier_test) (alias utils_test) (alias version_test) (alias weak_queue_test))) liquidsoap-2.4.2/tests/core/ffmpeg_quality.ml000066400000000000000000000046521513273233300213420ustar00rootroot00000000000000open Ffmpeg_format let () = Frame_settings.lazy_config_eval := true let () = let vorbis_enc = { format = Some "ogg"; interleaved = `Default; output = `Stream; metadata = Frame.Metadata.empty; streams = [ ( Frame.Fields.audio, `Encode { mode = `Internal; codec = Some "libvorbis"; options = `Audio { pcm_kind = Content.Audio.kind; channels = 2; samplerate = lazy 44100; sample_format = None; }; opts = Hashtbl.create 0; } ); ]; opts = Hashtbl.create 0; } in let mk_encoder = Encoder.get_factory (Encoder.Ffmpeg vorbis_enc) in let enc = mk_encoder ~pos:None "ffmpeg" Frame.Metadata.Export.empty in assert (Encoder.(enc.hls.bitrate ()) = None) let () = let mixed_enc = { format = Some "ogg"; interleaved = `Default; output = `Stream; metadata = Frame.Metadata.empty; streams = [ ( Frame.Fields.audio, `Encode { mode = `Internal; codec = Some "libvorbis"; options = `Audio { pcm_kind = Content.Audio.kind; channels = 2; samplerate = lazy 44100; sample_format = None; }; opts = Hashtbl.create 0; } ); ( Frame.Fields.audio, `Encode { mode = `Internal; codec = Some "libmp3lame"; options = `Audio { pcm_kind = Content.Audio.kind; channels = 2; samplerate = lazy 44100; sample_format = None; }; opts = (let m = Hashtbl.create 0 in Hashtbl.replace m "b" (`String "128k"); m); } ); ]; opts = Hashtbl.create 0; } in let mk_encoder = Encoder.get_factory (Encoder.Ffmpeg mixed_enc) in let enc = mk_encoder ~pos:None "ffmpeg" Frame.Metadata.Export.empty in assert (Encoder.(enc.hls.bitrate ()) = Some 128000) liquidsoap-2.4.2/tests/core/flush_test.ml000066400000000000000000000014001513273233300204720ustar00rootroot00000000000000(* This can be used to manually benchmark memory usage. Otherwise, it simply exists. *) let () = exit 0 let _ = Stdlib.Lazy.force Builtins_settings.settings_module; Lang.eval ~cache:true ~typecheck:false ~stdlib:`Disabled {| %include "../../src/libs/stdlib.liq" enable_autocue_metadata() |} let () = Frame_settings.lazy_config_eval := true; Dtools.Log.conf_level#set 4; Dtools.Log.conf_stdout#set true; Dtools.Log.conf_file#set false; Dtools.Init.exec Dtools.Log.start; Tutils.start (); for _ = 0 to 10 do let r = Request.create ~cue_in_metadata:None ~cue_out_metadata:None "/tmp/bla.mp3" in ignore (Request.resolve r); Request.destroy r; Gc.compact () done; Dtools.Init.exec Dtools.Log.stop; Tutils.shutdown 0 liquidsoap-2.4.2/tests/core/frame_test.ml000066400000000000000000000004311513273233300204460ustar00rootroot00000000000000let () = let pcm_t = Lang.frame_t (Lang.univ_t ()) (Frame.Fields.make ~audio:(Format_type.audio ()) ()) in Typing.(pcm_t <: Lang.univ_t ()) let () = let f = Frame.create ~length:10 (Frame.Fields.from_list []) in assert (Frame.position (Frame.slice f 20) = 10) liquidsoap-2.4.2/tests/core/gen_dune.ml000066400000000000000000000033501513273233300201040ustar00rootroot00000000000000(* test name, (deps, args) *) let test_params = [ ( "json_test", ( "(:json ./json) (:json5 ./json5) (:big-list-of-naughty-strings \ ./big-list-of-naughty-strings)", [""] ) ); ( "stream_decoder_test", ( "(:test_wav ./test.wav) (:test_mp3 ./test.mp3)", ["%{test_wav} bla.wav"; "%{test_mp3} bla.wav"] ) ); ("parsesrt", ("(:test_srt ./test.srt)", ["%{test_srt}"])); ] let test_names = ref [] let test_name s = let test_name = Filename.remove_extension s in test_names := Printf.sprintf "(alias %s)" test_name :: !test_names; test_name let () = let location = Sys.getcwd () in let tests = List.sort Stdlib.compare (List.filter_map (fun f -> if f <> "gen_dune.ml" && Filename.extension f = ".ml" then Some (Filename.remove_extension f) else None) (Build_tools.read_files ~location "")) in List.iter (fun test -> let deps, args = match List.assoc_opt test test_params with | None -> ("", [""]) | Some (deps, args) -> (deps, args) in Printf.printf {| (executable (name %s) (modules %s) (libraries liquidsoap_core liquidsoap_optionals)) (rule (alias %s) (package liquidsoap) (deps %s (:%s %s.exe)) (action %s%s%s)) |} (test_name test) test test deps test test (if List.length args > 1 then "(progn " else "") (String.concat " " (List.map (fun arg -> Printf.sprintf "(run %%{%s} %s)" test arg) args)) (if List.length args > 1 then ")" else "")) tests let () = Printf.printf {|(alias (name citest) (deps %s)) |} (String.concat "\n " (List.sort_uniq Stdlib.compare !test_names)) liquidsoap-2.4.2/tests/core/generator_test.ml000066400000000000000000000066341513273233300213550ustar00rootroot00000000000000let () = Frame_settings.lazy_config_eval := true let audio = Content.Audio.format_of_channels 2 let video = Content.default_format Content.Video.kind let () = let buffer = Generator.create ~max_length:1000 (Frame.Fields.make ~audio ~video ()) in Generator.put buffer Frame.Fields.audio (Content.make ~length:500 audio); assert (Generator.length buffer = 0); assert (Generator.buffered_length buffer = 500); Generator.put buffer Frame.Fields.video (Content.make ~length:250 video); assert (Generator.length buffer = 250); assert (Generator.buffered_length buffer = 500); let m = Frame.Metadata.from_list [("foo", "bla")] in Generator.add_metadata buffer m; Generator.add_track_mark buffer; Generator.put buffer Frame.Fields.video (Content.make ~length:1 video); let c = Generator.slice buffer (Generator.length buffer) in assert ( Content.Metadata.get_data (Frame.Fields.find Frame.Fields.metadata c) = [(250, m)]); assert ( Content.Track_marks.get_data (Frame.Fields.find Frame.Fields.track_marks c) = [250]); Generator.put buffer Frame.Fields.video (Content.make ~length:50 video); assert (Generator.length buffer = 50); (* Last position for length [n] is [n-1], same as with arrays.. *) Generator.add_metadata ~pos:23 buffer m; Generator.add_track_mark ~pos:23 buffer; let c = Generator.slice buffer 23 in assert ( Content.Metadata.get_data (Frame.Fields.find Frame.Fields.metadata c) = []); assert ( Content.Track_marks.get_data (Frame.Fields.find Frame.Fields.track_marks c) = []); assert (Generator.length buffer = 27); let c = Generator.slice buffer 1 in assert ( Content.Metadata.get_data (Frame.Fields.find Frame.Fields.metadata c) = [(0, m)]); assert ( Content.Track_marks.get_data (Frame.Fields.find Frame.Fields.track_marks c) = [0]) let () = let buffer = Generator.create ~max_length:1000 (Frame.Fields.make ~audio ()) in Generator.put buffer Frame.Fields.audio (Content.make ~length:500 audio); assert (Generator.length buffer = 500); assert (Generator.buffered_length buffer = 500); let c = Generator.peek buffer in let m = Frame.Fields.find Frame.Fields.metadata c in assert (Content.length m = max_int); let slice = Content.sub m 34 234 in assert (Content.length slice = 234); let rem = Content.truncate m 23 in assert (Content.length rem = max_int); let m = Frame.Fields.find Frame.Fields.track_marks c in assert (Content.length m = max_int); let slice = Content.sub m 34 234 in assert (Content.length slice = 234); let rem = Content.truncate m 23 in assert (Content.length rem = max_int); Generator.truncate buffer 50; assert (Generator.length buffer = 450); assert (Generator.buffered_length buffer = 450); let c = Generator.peek buffer in let m = Frame.Fields.find Frame.Fields.metadata c in assert (Content.length m = max_int); let m = Frame.Fields.find Frame.Fields.track_marks c in assert (Content.length m = max_int); let c = Generator.slice buffer 100 in Frame.Fields.iter (fun _ c -> assert (Content.length c = 100)) c; assert (Generator.length buffer = 350); assert (Generator.buffered_length buffer = 350); (try Generator.put buffer Frame.Fields.video (Content.make ~length:250 video); assert false with Not_found -> ()); try Generator.put buffer Frame.Fields.audio (Content.make ~length:250 video); assert false with Content.Invalid -> () liquidsoap-2.4.2/tests/core/http_test.ml000066400000000000000000000002131513273233300203310ustar00rootroot00000000000000let () = let { Liq_http.user; password } = Liq_http.parse_auth "foo:bar:gni" in assert (user = "foo"); assert (password = "bar:gni") liquidsoap-2.4.2/tests/core/is_url.ml000066400000000000000000000002351513273233300176140ustar00rootroot00000000000000let () = assert (Liq_http.is_url "http://www.test.com/"); assert (Liq_http.is_url "https://www.test.com/"); assert (not (Liq_http.is_url "/tmp/test")) liquidsoap-2.4.2/tests/core/json_test.ml000066400000000000000000000101001513273233300203170ustar00rootroot00000000000000let () = let s = "AverageLevel\000\196\016\000\000" in assert ( Liquidsoap_lang.Json.to_string (`String s) = "\"AverageLevel\\u0000\\uFFFD\\u0000\\u0000\"") let json = [| "{\n\ \"firstName\": \"John\",\n\ \"lastName\": \"Smith\",\n\ \"isAlive\": true,\n\ \"age\": 25,\n\ \"address\": {\n\ \"streetAddress\": \"21 2nd Street\",\n\ \"city\": \"New York\",\n\ \"state\": \"NY\",\n\ \"postalCode\": \"10021-3100\"\n\ },\n\ \"phoneNumbers\": [\n\ {\n\ \"type\": \"home\",\n\ \"number\": \"212 555-1234\"\n\ },\n\ {\n\ \"type\": \"office\",\n\ \"number\": \"646 555-4567\"\n\ }\n\ ],\n\ \"children\": [],\n\ \"spouse\": null\n\ }\n"; "{ \"face\": \"😂\" }"; "{ \"face\": \"\\uD83D\\uDE02\" }"; |] let () = for i = 0 to Array.length json - 1 do ignore (Json.from_string json.(i)) done let read_file filename = let ch = open_in filename in let s = really_input_string ch (in_channel_length ch) in close_in ch; s exception Failed type expectation = [ `Success | `Failure | `Unspecified ] let string_of_expectation = function | `Success -> "success" | `Failure -> "failure" | `Unspecified -> "unspecified" let () = let pwd = Filename.dirname Sys.argv.(0) in let samples = Filename.concat pwd "json" in let files = List.sort Stdlib.compare (Array.to_list (Sys.readdir samples)) in let tests = List.filter_map (fun f -> match Filename.extension f with | ".json" -> let expectation = match f.[0] with | 'y' -> `Success | 'n' -> `Failure | 'i' -> `Unspecified | _ -> assert false in Some (expectation, f) | _ -> None) files in let run_test ((expectation : expectation), file) = Printf.printf "Testing file %s with %s expectation... %!" file (string_of_expectation expectation); try let v = Json.from_string (read_file (Filename.concat samples file)) in List.iter (fun (compact, json5) -> ignore (Json.to_string ~compact ~json5 v)) [(true, true); (true, false); (false, true); (false, false)]; match expectation with | `Success -> Printf.printf "OK!\n%!" | `Failure -> raise Failed | `Unspecified -> Printf.printf "SUCCESS!\n%!" with | Failed -> Printf.printf "FAILED!\n%!"; exit 1 | _ when expectation = `Failure -> Printf.printf "OK!\n%!" | _ when expectation = `Unspecified -> Printf.printf "FAILED!\n%!" in List.iter run_test tests let () = let pwd = Filename.dirname Sys.argv.(0) in let samples = Filename.concat pwd "json5" in let files = List.sort Stdlib.compare (Array.to_list (Sys.readdir samples)) in let tests = List.filter_map (fun f -> match Filename.extension f with | ".json" | ".json5" -> Some (`Success, f) | ".txt" | ".js" -> Some (`Failure, f) | _ -> None) files in let run_test ((expectation : expectation), file) = Printf.printf "Testing file %s with %s expectation... %!" file (string_of_expectation expectation); try let v = Json.from_string ~json5:true (read_file (Filename.concat samples file)) in List.iter (fun compact -> ignore (Json.to_string ~compact ~json5:true v)) [true; false]; if expectation <> `Success then raise Failed; Printf.printf "OK!\n%!" with | Failed -> Printf.printf "FAILED!\n%!"; exit 1 | _ when expectation = `Failure -> Printf.printf "OK!\n%!" in List.iter run_test tests let () = let pwd = Filename.dirname Sys.argv.(0) in let basedir = Filename.concat pwd "big-list-of-naughty-strings" in let strings_file = Filename.concat basedir "blns.json" in Printf.printf "Testing big-list-of-naughty-strings...\n%!"; let s = read_file strings_file in let v = Json.from_string s in ignore (Json.to_string ~compact:true v); ignore (Json.to_string ~compact:false v) liquidsoap-2.4.2/tests/core/meth.ml000066400000000000000000000026511513273233300172600ustar00rootroot00000000000000(* The the code found in add_builtin. *) let () = (* x.l1.l2.l3 = v means x = (x where l1 = (x.l1 where l2 = (x.l1.l2 where l3 = v))) *) let x = "x" in let v = "v" in let rec aux prefix = function | l :: ll -> Printf.sprintf "%s%s%s where (%s = %s)" x (if prefix = [] then "" else ".") (String.concat "." (List.rev prefix)) l (aux (l :: prefix) ll) | [] -> v in let ll = ["l1"; "l2"; "l3"] in Printf.printf "%s = %s\n\n%!" x (aux [] ll) (* Test adding submethods. *) let () = let print t = Printf.printf "%s\n%!" (Type.to_string t) in let t = Type.make Type.Int in print t; let t = Type.meth "f" ([], Type.make Type.Float) t in print t; let t = Type.meth "ff" ([], Type.make Type.Float) t in print t; let t = Type.meths ["f"; "s"] ([], Type.make Type.String) t in print t; let t = Type.meths ["f"; "s"; "f'"] ([], Type.make Type.Float) t in print t (* Test subtyping. *) let () = (* Make sure unifying variables sees top-level methods: We do: t = ('a).{ f : int } <: t' = int.{ ff : int, f : float } and make sure that this fails. *) let t = Type.var () in let t = Type.meth "f" ([], Type.make Type.Int) t in let t' = Type.make Type.Int in let t' = Type.meth "f" ([], Type.make Type.Float) t' in let t' = Type.meth "ff" ([], Type.make Type.Int) t' in assert ( try Typing.(t <: t'); false with _ -> true) liquidsoap-2.4.2/tests/core/more_types.ml000066400000000000000000000045761513273233300205210ustar00rootroot00000000000000open Liquidsoap_lang let () = (* def f(x) = if x?.foo ?? false then x.{foo = false} else x end end *) let _if = Term.make (`Var "if") in let x = Term.make (`Var "x") in let _false () = Term.make (`Bool false) in let cond = Term.make (`Invoke { Term.invoked = x; invoke_default = Some (_false ()); meth = "foo" }) in let _then = Term.make (`Fun { Term.name = None; arguments = []; body = Term.make ~t:x.Term.t ~methods:(Term.Methods.add "foo" (_false ()) Term.Methods.empty) x.Term.term; free_vars = None; }) in let _else = Term.make (`Fun { Term.name = None; arguments = []; body = x; free_vars = None }) in let f = Term.make (`Fun { Term.free_vars = None; name = None; arguments = [ { Term.label = "x"; as_variable = None; typ = Type.var (); default = None; pos = None; }; ]; body = Term.make (`App (_if, [("", cond); ("then", _then); ("else", _else)])); }) in Typechecking.check ~throw:(fun ~bt exn -> Printexc.raise_with_backtrace exn bt) ~check_top_level_override:false f; match (Type.deref f.Term.t).Type.descr with | Type.Arrow ([(false, "x", _)], t) -> ( let meths, _ = Type.split_meths t in match meths with | [{ Type.meth = "foo"; optional = true; _ }] -> () | _ -> assert false) | _ -> assert false exception Failed let () = (* term = (1 : int.{opt?: string}).foo *) let typ = Type.meth ~optional:true "opt" ([], Lang.string_t) Lang.int_t in let term = { Term.t = typ; term = `Int 1; methods = Term.Methods.empty; flags = Flags.empty; } in let invoke = { Term.t = Lang.univ_t (); term = `Invoke { Term.invoked = term; invoke_default = None; meth = "opt" }; methods = Term.Methods.empty; flags = Flags.empty; } in try Typechecking.check ~throw:(fun ~bt exn -> Printexc.raise_with_backtrace exn bt) ~check_top_level_override:false invoke; raise Failed with | Failed -> raise Failed | _ -> () liquidsoap-2.4.2/tests/core/output_encoded_test.ml000066400000000000000000000023011513273233300223730ustar00rootroot00000000000000let encode_metadata_called = ref false let encode_called = ref false let send_called = ref false (* Make sure send is always called before insert metadata. *) class encoded_test = object (self) inherit [unit] Output.encoded ~output_kind:"foo" ~name:"encoded_test" ~infallible:false ~register_telnet:false ~autostart:false ~export_cover_metadata:false (Lang.source (new Noise.noise None)) method self_sync = (`Static, None) method encode_metadata _ = assert !send_called; encode_metadata_called := true method encode _ = encode_called := true; () method send _ = assert !encode_called; send_called := true method test_send_frame frame = self#send_frame frame method start = () method stop = () end let () = Frame_settings.lazy_config_eval := true; let encoded_test = new encoded_test in encoded_test#content_type_computation_allowed; let frame = Frame.create ~length:(Lazy.force Frame.size) encoded_test#content_type in let m = Frame.Metadata.from_list [("foo", "bla")] in let frame = Frame.add_metadata frame 0 m in encoded_test#test_send_frame frame; assert !encode_metadata_called liquidsoap-2.4.2/tests/core/output_test.ml000066400000000000000000000026251513273233300207230ustar00rootroot00000000000000class dummy ~clock ~autostart source = object (self) inherit Output.dummy ~clock ~autostart ~infallible:false ~register_telnet:false (Lang.source (source :> Source.source)) method test_wake_up = self#wake_up (self :> Clock.source) val mutable test_can_generate_frame = false method test_set_can_generate_frame = test_can_generate_frame <- true method! can_generate_frame = test_can_generate_frame method test_output = self#output end class test_source = object (self) inherit Debug_sources.fail "test_source" val mutable test_can_generate_frame = false method test_set_can_generate_frame = test_can_generate_frame <- true method! can_generate_frame = test_can_generate_frame method! generate_frame = self#empty_frame end let () = Frame_settings.lazy_config_eval := true; let started = ref false in let test_source = new test_source in let clock = Clock.create ~sync:`Passive () in Clock.start ~force:true clock; let o = new dummy ~clock ~autostart:true test_source in o#on_start (fun () -> started := true); o#content_type_computation_allowed; assert (not o#can_generate_frame); let a = o#test_wake_up in assert (not !started); Clock.tick clock; o#test_set_can_generate_frame; assert o#is_ready; test_source#test_set_can_generate_frame; assert test_source#is_ready; o#test_output; assert !started; o#sleep a; () liquidsoap-2.4.2/tests/core/parsesrt.ml000066400000000000000000000006441513273233300201660ustar00rootroot00000000000000let () = let f = "test.srt" in if not (Sys.file_exists f) then ( Printf.printf "Test %s not found, exiting.\n" f; exit 0); let h = Srt_parser.parse_file f in Printf.printf "Found %d subtitles.\n\n%!" (List.length h); List.iter (fun (((h1, m1, s1, n1), (h2, m2, s2, n2)), sub) -> Printf.printf "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n%s\n\n%!" h1 m1 s1 n1 h2 m2 s2 n2 sub) h liquidsoap-2.4.2/tests/core/playlist_basic_test.ml000066400000000000000000000045351513273233300223670ustar00rootroot00000000000000let () = assert ( Playlist_basic.parse_extinf "#EXTINF:,- songname" = [("song", "songname")]); assert ( Playlist_basic.parse_extinf "#EXTINF:,-songname" = [("song", "songname")]); assert ( Playlist_basic.parse_extinf "#EXTINF:,artist - title" = [("artist", "artist"); ("title", "title")]); assert ( Playlist_basic.parse_extinf "#EXTINF:,artist-title" = [("artist", "artist"); ("title", "title")]); assert ( Playlist_basic.parse_extinf "#EXTINF:123,- songname" = [("extinf_duration", "123"); ("song", "songname")]); assert ( Playlist_basic.parse_extinf "#EXTINF:123,-songname" = [("extinf_duration", "123"); ("song", "songname")]); assert ( Playlist_basic.parse_extinf "#EXTINF:123 ,artist - title" = [("extinf_duration", "123"); ("artist", "artist"); ("title", "title")]); assert ( Playlist_basic.parse_extinf "#EXTINF:123,artist-title" = [("extinf_duration", "123"); ("artist", "artist"); ("title", "title")]); assert ( Playlist_basic.parse_extinf "#EXTINF:123 foo=\"bla\",gni=\"gnu\",- songname" = [ ("extinf_duration", "123"); ("foo", "bla"); ("gni", "gnu"); ("song", "songname"); ]); assert ( Playlist_basic.parse_extinf "#EXTINF:123 foo=\"bla,-songname" = [("extinf_duration", "123")]); assert ( Playlist_basic.parse_extinf "#EXTINF:123 foo=\"bla\",gni=\"gnu\",artist - title" = [ ("extinf_duration", "123"); ("foo", "bla"); ("gni", "gnu"); ("artist", "artist"); ("title", "title"); ]); assert ( Playlist_basic.parse_extinf "#EXTINF:123 foo=\"bla\",gni=\"gnu\",artist-title" = [ ("extinf_duration", "123"); ("foo", "bla"); ("gni", "gnu"); ("artist", "artist"); ("title", "title"); ]); assert ( Playlist_basic.parse_extinf "#EXTINF:157,Blood, Sweat & Tears - Spinning wheel" = [ ("extinf_duration", "157"); ("artist", "Blood, Sweat & Tears"); ("title", "Spinning wheel"); ]); assert ( Playlist_basic.parse_extinf "#EXTINF:157 foo=\"bla\",gni=\"gnu\",Blood, Sweat & Tears - Spinning \ wheel" = [ ("extinf_duration", "157"); ("foo", "bla"); ("gni", "gnu"); ("artist", "Blood, Sweat & Tears"); ("title", "Spinning wheel"); ]) liquidsoap-2.4.2/tests/core/pool_test.ml000066400000000000000000000022231513273233300203260ustar00rootroot00000000000000type req = { id : int; destroyed : bool } module Pool = Pool.Make (struct type t = req let id { id; _ } = id let destroyed id = { id; destroyed = true } let is_destroyed { destroyed; _ } = destroyed end) let m = Mutex.create () let _done = Condition.create () let started = Condition.create () let fill () = (* Create a bunch of requests. *) let l = List.init 100 (fun _ -> Pool.add (fun id -> { id; destroyed = false })) in List.iter (fun { id; _ } -> ignore (Printf.sprintf "id: %d\n%!" id)) l; (* Delete 15th one. *) Pool.remove 15; Gc.full_major (); List.iter (fun { id; _ } -> ignore (Printf.sprintf "id: %d\n%!" id)) l; assert (Pool.size () = 99); let r = Pool.add (fun id -> { id; destroyed = false }) in assert (Pool.size () = 100); assert (r.id = 100); assert (List.length l = 100); Condition.signal _done let check () = Mutex.lock m; Condition.signal started; Condition.wait _done m; Gc.full_major (); assert (Pool.size () = 0); Mutex.unlock m let () = Mutex.lock m; let th = Thread.create check () in Condition.wait started m; Mutex.unlock m; ignore (Thread.create fill ()); Thread.join th liquidsoap-2.4.2/tests/core/start_stop_test.ml000066400000000000000000000011311513273233300215540ustar00rootroot00000000000000class start_stop_test _state start stop = object inherit Start_stop.base method! state = _state method start = start () method stop = stop () method fallible = true end exception Success let () = let s = new start_stop_test `Idle (fun () -> assert false) (fun () -> assert false) in s#reset; let s = new start_stop_test `Stopped (fun () -> assert false) (fun () -> assert false) in s#reset; let s = new start_stop_test `Started (fun () -> raise Success) (fun () -> ()) in try s#reset; assert false with Success -> () liquidsoap-2.4.2/tests/core/stream_decoder_test.ml000066400000000000000000000034211513273233300223360ustar00rootroot00000000000000(* Test a stream decoder. ffmpeg/audio only for now. *) let () = if Array.length Sys.argv < 3 then ( Printf.printf "Usage: stream_decoder_test \n%!"; exit 1); Frame_settings.lazy_config_eval := true; Dtools.Log.conf_stdout#set true; Dtools.Log.conf_file#set false; Dtools.Log.conf_level#set 5; Dtools.Init.exec Dtools.Log.start; let in_file = Sys.argv.(1) in let in_fd = open_in_bin in_file in let read = input in_fd in let out_file = Sys.argv.(2) in let out_fd = open_out_bin out_file in let write = Strings.iter (output_substring out_fd) in let format = "application/ffmpeg" in let ctype = Frame_type.content_type (Lang.frame_t Lang.unit_t (Frame.Fields.make ~audio:(Format_type.audio ()) ())) in let create_decoder = Option.get (Decoder.get_stream_decoder ~ctype format) in let decoder = create_decoder { Decoder.read; tell = None; length = None; lseek = None } in let log = Log.make ["generator"] in let generator = Generator.create ~log ctype in let buffer = Decoder.mk_buffer ~ctype generator in let mp3_format = Lang_mp3.mp3_base_defaults () in let create_encoder = Encoder.get_factory (Encoder.MP3 mp3_format) in let encoder = create_encoder ~pos:None "test stream" Frame.Metadata.Export.empty in write (encoder.Encoder.header ()); let size = Lazy.force Frame.size in try while true do try while Generator.length generator < Lazy.force Frame.size do decoder.Decoder.decode buffer done; let frame = Generator.slice generator size in write (encoder.Encoder.encode frame) with Avutil.Error `Invalid_data -> () done with Ffmpeg_decoder.End_of_file -> write (encoder.Encoder.stop ()); close_out out_fd; close_in in_fd liquidsoap-2.4.2/tests/core/string_test.ml000066400000000000000000000002611513273233300206630ustar00rootroot00000000000000let () = assert ("aa\\\"bb" = Lang_string.escape_utf8_string "aa\"bb") (* Bug #4287 *) let () = ignore (Lang_string.Version.of_string "rolling-release-v2.3.x+git@2addd93da") liquidsoap-2.4.2/tests/core/strings_test.ml000066400000000000000000000012641513273233300210520ustar00rootroot00000000000000let () = let buf = Strings.of_list ["a"; "bc"; ""; "de"] in assert (Strings.length buf = 5); assert (Strings.to_string buf = "abcde"); let b = Bytes.create 2 in assert (Strings.to_string (Strings.drop buf 1) = "bcde"); assert (Strings.to_string (Strings.drop buf 2) = "cde"); assert (Strings.to_string (Strings.drop buf 5) = ""); Strings.blit (Strings.sub buf 1 2) b 0; assert (Bytes.unsafe_to_string b = "bc"); Strings.blit (Strings.sub buf 2 2) b 0; assert (Bytes.unsafe_to_string b = "cd"); assert (Strings.to_string (Strings.sub buf 1 2) = "bc"); assert (Strings.to_string (Strings.sub buf 2 2) = "cd"); assert (Strings.to_string (Strings.keep buf 3) = "cde") liquidsoap-2.4.2/tests/core/test.srt000066400000000000000000000004611513273233300174770ustar00rootroot000000000000001 00:02:16,612 --> 00:02:19,376 Senator, we're making our final approach into Coruscant. 2 00:02:19,482 --> 00:02:21,609 Very good, Lieutenant. 3 00:03:13,336 --> 00:03:15,167 We made it. 4 00:03:18,608 --> 00:03:20,371 I guess I was wrong. 5 00:03:20,476 --> 00:03:22,671 There was no danger at all. liquidsoap-2.4.2/tests/core/timezone.ml000066400000000000000000000004751513273233300201570ustar00rootroot00000000000000let () = let tz = -Utils.timezone () in Printf.printf "timezone: %d (sec)\n%!" tz; Printf.printf "timezone: %+03d%02d\n%!" (tz / 3600) (abs (tz / 60) mod 60); Printf.printf "timezone: %s\n%!" (Utils.strftime "%z"); let std, dst = Utils.timezone_by_name () in Printf.printf "tzname = {%s, %s}\n%!" std dst liquidsoap-2.4.2/tests/core/types.ml000066400000000000000000000233321513273233300174660ustar00rootroot00000000000000open Type exception Failed let () = Frame_settings.lazy_config_eval := true let should_work t t' r = let t = make t in let t' = make t' in let r = make r in Printf.printf "Finding min for %s and %s\n%!" (to_string t) (to_string t'); let m = Typing.sup ~pos:None t t' in Printf.printf "Got: %s, expect %s\n%!" (to_string m) (to_string r); Typing.(m <: r); Typing.(t <: m); Typing.(t' <: m) let should_fail t t' = try ignore (Typing.sup ~pos:None (make t) (make t')); assert false with _ -> () let () = should_work (var ()).descr Bool Bool; should_work Bool (var ()).descr Bool; should_fail Bool Int; should_fail (List { t = make Bool; json_repr = `Tuple }) (List { t = make Int; json_repr = `Tuple }); let mk_meth meth ty t = Meth ( { meth; optional = false; scheme = ([], make ty); doc = { meth_descr = ""; category = `Method }; json_name = None; }, make t ) in let m = mk_meth "aa" Int Bool in should_work m Bool Bool; let n = mk_meth "b" Bool m in should_work m n m; let n = mk_meth "aa" Int Int in should_fail m n; let n = mk_meth "aa" Bool Bool in should_fail m n; () let () = (* 'a.{foo:int} *) let a = Lang.method_t (Lang.univ_t ()) [("foo", ([], Lang.int_t), "foo")] in (* {foo:int} *) let b = Lang.method_t Lang.unit_t [("foo", ([], Lang.int_t), "foo")] in Typing.(a <: b); match (snd (Type.split_meths a)).Type.descr with | Tuple [] -> () | _ -> assert false let () = (* 'a *) let ty = Lang.univ_t () in (* 'b.{foo:int} *) let t1 = Lang.method_t (Lang.univ_t ()) [("foo", ([], Lang.int_t), "foo")] in (* 'c.{gni:string} *) let t2 = Lang.method_t (Lang.univ_t ()) [("gni", ([], Lang.string_t), "gni")] in (* (ty:t1) *) Typing.(ty <: t1); Typing.(t1 <: ty); (* (ty:t2) *) Typing.(ty <: t2); Typing.(t2 <: ty) let () = (* 'a where 'a is an orderable type *) let a = Type.var ~constraints:[Type.ord_constr] () in (* ['b] *) let b = Lang.list_t (Lang.univ_t ()) in Typing.(a <: b); assert ( Type.Constraints.mem Type.ord_constr (match (demeth b).Type.descr with | List { Type.t = { Type.descr = Var { contents = Free v }; _ }; _ } -> v.Type.constraints | _ -> assert false)) let () = (* 'a *) let a = Lang.univ_t () in (* 'b.{foo:int} *) let b = Lang.method_t (Lang.univ_t ()) [("foo", ([], Lang.int_t), "foo")] in (* 'c where 'c is an orderable type *) let c = Type.var ~constraints:[Type.ord_constr] () in (* 'a <: 'b.{foo:int} *) Typing.(a <: b); (* 'b.{foo:int} <: 'c *) Typing.(b <: c); assert ( Type.Constraints.mem Type.ord_constr (match (demeth b).Type.descr with | Var { contents = Free v } -> v.Type.constraints | _ -> assert false)); assert ( Type.Constraints.mem Type.ord_constr (match (demeth a).Type.descr with | Var { contents = Free v } -> v.Type.constraints | _ -> assert false)) let () = (* 'a *) let a = Lang.univ_t () in (* 'a? *) let nullable_a = Lang.nullable_t a in (* 'b where 'b is a num type *) let b = Type.var ~constraints:[Type.num_constr] () in (* 'a <: 'b *) Typing.(a <: b); try (* 'a? <: string *) Typing.(nullable_a <: Lang.string_t); assert false with Liquidsoap_lang.Repr.Type_error _ -> () let () = (* 'a *) let a = Lang.univ_t () in (* 'a.{foo:int} *) let a_meth = Lang.method_t a [("foo", ([], Lang.int_t), "foo")] in (* 'b where 'b is a num type *) let b = Type.var ~constraints:[Type.num_constr] () in (* 'a <: 'b *) Typing.(a <: b); try (* 'a.{foo:int} <: string *) Typing.(a_meth <: Lang.string_t); assert false with Liquidsoap_lang.Repr.Type_error _ -> () let () = (* {gni:int} *) let a_meth = Type.meth "gni" ([], Lang.int_t) Lang.unit_t in (* 'a *) let b = Lang.univ_t () in (* 'a.{foo?:int} *) let b_meth = Type.meth ~optional:true "foo" ([], Lang.int_t) b in (* {gni:int} <: 'a.{foo?:int} *) Typing.(a_meth <: b_meth); (* b_meth becomes {gni:int, foo?:int} *) let meths, u = Type.split_meths b_meth in assert (u.Type.descr = Type.Tuple []); assert (List.length meths = 2); let foo = List.find (fun Type.{ meth; _ } -> meth = "foo") meths in assert (foo.Type.optional = true); let gni = List.find (fun Type.{ meth; _ } -> meth = "gni") meths in assert (gni.Type.optional = false) let () = let open Liquidsoap_lang in (* {gni?:int} *) let a_meth = Type.meth ~optional:true "gni" ([], Lang.int_t) Lang.unit_t in (* {gni:'a} *) let b_meth = Type.meth "gni" ([], Lang.univ_t ()) Lang.unit_t in (* {gni?:int} <: {gni:'a} *) try Typing.(a_meth <: b_meth); assert false with Repr.Type_error (_, a, _, _, _) -> ( let meths, _ = Type.split_meths a in match meths with | [{ Type.meth = "gni"; optional = true; scheme = [], t; _ }] -> Typing.(t <: Lang.int_t) | _ -> assert false) let () = (* { audio: pcm('a), video?: canvas } *) let f = Type.meth "audio" ([], Format_type.audio ()) (Type.meth ~optional:true "video" ([], Format_type.video ()) Lang.unit_t) in let c = Frame_type.content_type f in assert (Frame.Fields.cardinal c = 1); assert (Content.Audio.is_format (Frame.Fields.find Frame.Fields.audio c)) let () = (* {gni:int} *) let a_meth = Type.meth "gni" ([], Lang.int_t) Lang.unit_t in (* {foo:float}.{foo?:int} *) let b_meth = Type.meth ~optional:false "foo" ([], Lang.float_t) Lang.unit_t in let b_meth = Type.meth ~optional:true "foo" ([], Lang.int_t) b_meth in (* This should work: {gni:int} <: {foo: float}.{foo?:int} *) Typing.(a_meth <: b_meth) let () = (* 'a.{gni:int} *) let a_meth = Type.meth "gni" ([], Lang.int_t) (Lang.univ_t ()) in (* 'a.{foo?:int} *) let b_meth = Type.meth ~optional:true "foo" ([], Lang.int_t) (Lang.univ_t ()) in Typing.(a_meth <: b_meth); let meths, _ = Type.split_meths a_meth in let foo = List.find (fun Type.{ meth; _ } -> meth = "foo") meths in assert (foo.Type.optional = true) let () = (* source(audio=pcm(stereo)) *) let a = Lang.source_t (Lang.record_t [("audio", Format_type.audio_stereo ())]) in (* source() *) let b = Lang.source_t Lang.unit_t in (* { audio?: pcm(stereo)}, covariant *) let record_t = Lang.optional_record_t [("audio", Format_type.audio_stereo ())] in let covariant_t = Lang.univ_t () in Typing.bind ~variance:`Covariant covariant_t record_t; (match covariant_t.Type.descr with | Type.Var { contents = Type.Link (`Covariant, _) } -> () | _ -> assert false); (* source(?audio=pcm(stereo)) *) let optional = Lang.source_t covariant_t in Typing.(a <: optional); Typing.(b <: optional); match optional.Type.descr with | Type.Constr { constructor = "source"; params = [(`Invariant, t)] } -> ( let meths, t = Type.split_meths t in Typing.(t <: Lang.unit_t); match meths with | [{ Type.meth = "audio"; optional = true; scheme = [], t; _ }] -> Typing.(t <: Format_type.audio_stereo ()) | _ -> assert false) | _ -> assert false exception Test_failed let () = (* fun (format('a), source('a)) -> unit *) let a = Lang.univ_t () in let fn_t = Lang.fun_t [(false, "", Lang.format_t a); (false, "", Lang.source_t a)] Lang.unit_t in let fn = Term.make (`Var "fn") in (* format(audio: pcm(stereo)) *) let x_t = Lang.format_t (Lang.frame_t Lang.unit_t (Frame.Fields.make ~audio:(Format_type.audio_stereo ()) ())) in let x_var = Term.make (`Var "x") in (* source(audio: pcm(mono)) *) let y_t = Lang.source_t (Lang.frame_t Lang.unit_t (Frame.Fields.make ~audio:(Format_type.audio_mono ()) ())) in let y_var = Term.make (`Var "y") in let app = Term.make (`App (fn, [("", x_var); ("", y_var)])) in let throw ~bt exn = Printexc.raise_with_backtrace exn bt in let env = [("fn", ([], fn_t)); ("x", ([], x_t)); ("y", ([], y_t))] in try Liquidsoap_lang.Typechecking.check ~check_top_level_override:false ~throw ~env app; raise Test_failed with | Test_failed -> raise Test_failed | _ -> () let () = (* source('a) where 'a is an internal media type. *) let a = Lang.source_t (Lang.internal_tracks_t ()) in (* source(video: ffmpeg.video.raw, 'b) *) let b = Lang.source_t (Frame_type.make (Lang.univ_t ()) Frame.Fields.( add video (Type.make (Format_type.descr (`Kind Ffmpeg_raw_content.Video.kind))) empty)) in let () = try Typing.(a <: b); raise Failed with | Failed -> raise Failed | _ -> () in (* source('a) where 'a is an internal media type. *) let a = Lang.source_t (Lang.internal_tracks_t ()) in (* source(video: ffmpeg.video.raw, 'b) *) let b = Lang.source_t (Frame_type.make (Lang.univ_t ()) Frame.Fields.( add video (Type.make (Format_type.descr (`Kind Ffmpeg_raw_content.Video.kind))) empty)) in try Typing.(b <: a); raise Failed with | Failed -> raise Failed | _ -> () let () = (* { audio: 'a, metadata: metadata, track_marks : track_marks } where 'a is an internal media type. *) let a = Lang.record_t [ ("audio", Lang.internal_tracks_t ()); ("metadata", Lang.metadata_track_t); ("track_marks", Lang.track_marks_t); ] in (* 'b.{metadata? : metadata, track_marks? : track_marks } where 'b is an internal media type. *) let b = Lang.optional_method_t (Lang.internal_tracks_t ()) [ ("metadata", ([], Lang.metadata_track_t), ""); ("track_marks", ([], Lang.track_marks_t), ""); ] in Typing.(a <: b) liquidsoap-2.4.2/tests/core/unifier_test.ml000066400000000000000000000014001513273233300210120ustar00rootroot00000000000000let () = let x = Unifier.make 1 in let y = Unifier.make 2 in let z = Unifier.make 3 in Unifier.(x <-- y); assert (Unifier.deref x = 2); Unifier.set x 5; assert (Unifier.deref y = 5); Unifier.set y 4; assert (Unifier.deref x = 4); Unifier.(x <-- z); assert (Unifier.deref x = 3); assert (Unifier.deref y = 3); Unifier.set x 2; assert (Unifier.deref y = 2); assert (Unifier.deref z = 2); Unifier.set y 4; assert (Unifier.deref x = 4); assert (Unifier.deref z = 4); Unifier.set z 1; assert (Unifier.deref x = 1); assert (Unifier.deref y = 1); Unifier.(y <-- x); Unifier.set y 4; assert (Unifier.deref x = 4); assert (Unifier.deref z = 4); Unifier.set x 2; assert (Unifier.deref y = 2); assert (Unifier.deref z = 2) liquidsoap-2.4.2/tests/core/utils_test.ml000066400000000000000000000003121513273233300205120ustar00rootroot00000000000000let () = assert ( Lang_string.escape_utf8_string "aa\240\159\152\133bb" = "aa\240\159\152\133bb"); assert ( Lang_string.quote_string "aa\240\159\152\133bb" = "\"aa\240\159\152\133bb\"") liquidsoap-2.4.2/tests/core/version_test.ml000066400000000000000000000026361513273233300210520ustar00rootroot00000000000000let () = assert (Lang_string.Version.of_string "2.0.0~beta1" = ([2; 0; 0], "~beta1")); assert ( Lang_string.Version.of_string "2.0.0+git@7e211ffd" = ([2; 0; 0], "+git@7e211ffd")); assert ( Lang_string.Version.of_string "2.314234~beta1" = ([2; 314234], "~beta1")); assert (Lang_string.Version.of_string "2.314234" = ([2; 314234], "")); assert (Lang_string.Version.of_string "2.0.0" = ([2; 0; 0], "")); assert ( Lang_string.Version.of_string "2.3.1.4.2.3.4" = ([2; 3; 1; 4; 2; 3; 4], "")); assert (Lang_string.Version.compare ([2; 0; 0], "") ([2; 0; 0], "~beta1") = 1); assert ( Lang_string.Version.compare ([2; 0; 0], "+foo") ([2; 0; 0], "~beta1") = 1); assert ( Lang_string.Version.compare ([2; 0; 0], "~beta1") ([2; 0; 0], "+foo") = -1); assert (Lang_string.Version.compare ([2; 0; 0], "~beta1") ([2; 0; 0], "") = -1); assert (Lang_string.Version.compare ([2; 1; 0], "~beta1") ([2; 0; 0], "") = 1); assert ( Lang_string.Version.compare ([2; 1; 0], "~beta1") ([2; 12; 0], "") = -1); assert (Lang_string.Version.compare ([2; 12; 0], "") ([2; 12; 0], "") = 0); assert ( Lang_string.Version.compare ([2; 0; 0], "+foo") ([2; 0; 0], "+bla") = 1); assert ( Lang_string.Version.compare ([2; 0; 0], "~bla") ([2; 0; 0], "~foo") = -1); assert (Lang_string.Version.compare ([2; 0], "") ([2; 0; 0], "") = -1); assert (Lang_string.Version.compare ([2; 0; 0], "") ([2; 0], "") = 1) liquidsoap-2.4.2/tests/core/weak_queue_test.ml000066400000000000000000000044271513273233300215200ustar00rootroot00000000000000(* Keep strong references to prevent GC from collecting weak references *) let kept = ref [] let keep v = kept := Obj.repr v :: !kept; v let () = (* Test create and basic operations *) let q = Queues.WeakQueue.create () in assert (Queues.WeakQueue.length q = 0); assert (Queues.WeakQueue.elements q = []); (* Test push and elements *) Queues.WeakQueue.push q (keep "a"); Queues.WeakQueue.push q (keep "b"); Queues.WeakQueue.push q (keep "c"); assert (Queues.WeakQueue.length q = 3); assert (Queues.WeakQueue.elements q = ["a"; "b"; "c"]); (* Test exists *) assert (Queues.WeakQueue.exists q (fun x -> x = "b")); assert (not (Queues.WeakQueue.exists q (fun x -> x = "d"))); (* Test iter *) let acc = ref [] in Queues.WeakQueue.iter q (fun x -> acc := x :: !acc); assert (!acc = ["c"; "b"; "a"]); (* Test fold *) let result = Queues.WeakQueue.fold q (fun x acc -> x :: acc) [] in assert (result = ["c"; "b"; "a"]); (* Test filter *) let q2 = Queues.WeakQueue.create () in Queues.WeakQueue.push q2 (keep "1"); Queues.WeakQueue.push q2 (keep "2"); Queues.WeakQueue.push q2 (keep "3"); Queues.WeakQueue.push q2 (keep "4"); Queues.WeakQueue.filter q2 (fun x -> x = "2" || x = "4"); assert (Queues.WeakQueue.elements q2 = ["2"; "4"]); (* Test filter_out *) let q3 = Queues.WeakQueue.create () in Queues.WeakQueue.push q3 (keep "10"); Queues.WeakQueue.push q3 (keep "20"); Queues.WeakQueue.push q3 (keep "30"); Queues.WeakQueue.push q3 (keep "40"); Queues.WeakQueue.filter_out q3 (fun x -> x = "20" || x = "40"); assert (Queues.WeakQueue.elements q3 = ["10"; "30"]); (* Test flush_elements *) let q4 = Queues.WeakQueue.create () in Queues.WeakQueue.push q4 (keep "x"); Queues.WeakQueue.push q4 (keep "y"); let flushed = Queues.WeakQueue.flush_elements q4 in assert (flushed = ["x"; "y"]); assert (Queues.WeakQueue.length q4 = 0); assert (Queues.WeakQueue.elements q4 = []); (* Test flush_iter *) let q5 = Queues.WeakQueue.create () in Queues.WeakQueue.push q5 (keep "100"); Queues.WeakQueue.push q5 (keep "200"); let acc = ref [] in Queues.WeakQueue.flush_iter q5 (fun x -> acc := x :: !acc); assert (!acc = ["200"; "100"]); assert (Queues.WeakQueue.length q5 = 0); Printf.printf "All WeakQueue tests passed!\n%!" liquidsoap-2.4.2/tests/dune000066400000000000000000000002731513273233300157150ustar00rootroot00000000000000(executable (name gen_dune) (libraries liquidsoap_build_tools) (modules gen_dune)) (executable (name run_test) (modules run_test) (libraries liquidsoap-lang.console unix threads)) liquidsoap-2.4.2/tests/failures/000077500000000000000000000000001513273233300166475ustar00rootroot00000000000000liquidsoap-2.4.2/tests/failures/ctype.liq000077500000000000000000000003361513273233300205070ustar00rootroot00000000000000# Testing dynamic kind unification errors. s = mean(sine()) def mkfilter(graph) = x = ffmpeg.filter.audio.input(channels=2, graph, s) ffmpeg.filter.audio.output(graph, x) end s = ffmpeg.filter.create(mkfilter) out(s) liquidsoap-2.4.2/tests/failures/error.liq000077500000000000000000000001301513273233300205040ustar00rootroot00000000000000# Test uncaught errors. def f() = error.raise( "testing exceptions" ) end f() liquidsoap-2.4.2/tests/failures/record-invalid-member1.liq000077500000000000000000000004741513273233300236160ustar00rootroot00000000000000# Testing invalid record members. The default error was # At line 4, char 4: # Error 5: this value has type # unit (inferred at line 3, char 4-6) # but it should be a subtype of the type of the value at line 4, char 4-10 # {{ _ | f = _ }} # It should be replaced with something more readable. r = () _ = r.f(3) liquidsoap-2.4.2/tests/failures/record-invalid-member2.liq000077500000000000000000000001731513273233300236130ustar00rootroot00000000000000# We had a problem that we had no location was reported for builtin records, # when a member is not found. _ = list.bla(3) liquidsoap-2.4.2/tests/failures/ref.liq000077500000000000000000000002521513273233300201340ustar00rootroot00000000000000#!../../liquidsoap # Ensure that value restriction prevents the usual problems. def id(x) = x end def incr(n) = n + 1 end r = ref(id) r := incr f = !r _ = f("a") liquidsoap-2.4.2/tests/fixme/000077500000000000000000000000001513273233300161455ustar00rootroot00000000000000liquidsoap-2.4.2/tests/fixme/GH1170.liq000077500000000000000000000013351513273233300174700ustar00rootroot00000000000000#!../src/liquidsoap ../libs/stdlib.liq ../libs/deprecations.liq %include "test.liq" # GH1170 was a tricky bug: we were checking on [is_ready] # for the crossfaded source only based on the transition source # during transitions. However, if the transition source fails, # we should still be able to switch back to the main source if # it is ready.. s = noise() s = chop(duration=20., s) track_count = ref 0 def f(_) = track_count := !track_count + 1 # Let's do 3 tracks here for security. if !track_count > 2 then test.pass() end end s = on_track(f, s) s = crossfade(s) clock.assign_new(sync="none", [s]) def on_stop() = if !track_count < 3 then test.fail() end end output.dummy(on_stop=on_stop, fallible=true, s) liquidsoap-2.4.2/tests/fixme/LS527.liq000066400000000000000000000011421513273233300174260ustar00rootroot00000000000000# In LS-527, an exception was raised while initializing # the sources. Because the clock was delegated to one of those # sources and only the failed source was properly terminated, # liquidsoap refused to shutdown.. # By the way, this one is a more complicated instance of LS503.. %include "test.liq" skipped = true %ifdef input.alsa # Reopen stderr to /dev/null to # disable printing expected exception reopen.stderr("/dev/null") skipped = false on_shutdown(test.pass) s = input.alsa() output.icecast(%wav, host="nonexistent", mount="foo", on_stop=shutdown, s) %endif if skipped then test.skip() end liquidsoap-2.4.2/tests/fixme/cross2.liq000077500000000000000000000007651513273233300201020ustar00rootroot00000000000000#!../../liquidsoap ../test.liq # See #2080. s = amplify(0.5, sequence([sine(duration=5.), sine(500.)])) jingle = playlist("files/jingles") # The subtlety here is that once(jingle) returns a source with methods (such as # id). The inference thus thinks that a.source has methods. And the function f # thus cannot be passed to cross because it is not general enough. def f(a, b) = sequence([a.source, once(jingle), b.source]) end s = cross(f, s) output.dummy(s) thread.run(delay=8., test.pass) liquidsoap-2.4.2/tests/fixme/cross3.liq000077500000000000000000000010321513273233300200670ustar00rootroot00000000000000#!/usr/bin/env -S dune exec ../../src/bin/liquidsoap.exe -- --no-stdlib -i # See #2080. Simplified version. Here, "float" stands for "source". # Our main source. s = 3. def once((s:float)) = s.{id="bla"} end # Here, because of once, we infer that f should take as argument a source with # many methods (id, etc.). However, we use it with cross below, which takes a # function operating on undecorated sources. def f(a) = ignore([once(2.), a]) end def cross((f:(float)->unit), s) = ignore(f) s end s = cross(f, s) ignore(s) liquidsoap-2.4.2/tests/fixme/sequence-1.liq000077500000000000000000000030441513273233300206260ustar00rootroot00000000000000#!../../src/liquidsoap ../../libs/stdlib.liq ../../libs/deprecations.liq # Testing that head_ready is used in sequence#is_ready. # # We setup a sequence to start playing a source s, then stop playing the # sequence, make s unavailable and start playing the sequence again. # At this point the sequence should end its track and cleanup s *before* # becoming unavailable. # # Pitfalls when cooking up this test: # - Does not work with a track insensitive switch to "instantly kill" the # source, because the switch makes sure to stay ready until the end of # track. # - Also does not work simply with a sequence underneath a fallible output, # because such an output will stop as soon as its source is not ready. %include "test.liq" # Create a fallible source. The simplest way is to use once() -- which is built # from sequence(), but that is irrelevant. s = once(sine(duration=1.)) # The imported sequence here is seq2. We put it in a fallback which will first # play seq2, then switch to seq1 which will consume s and fail. The proper # behavior at this point is to go back to seq2, which will notice that s has # become unavailable and end its track and cleanup s. (By then s will be free, # because seq1 will have also move away from it.) test = fallback( track_sensitive=false, [ delay(initial=true, 0.5, sequence(id="seq1", [s, fail()])), sequence(id="seq2", [s, fail()]) ] ) def check() = if source.is_up(s) then test.fail() else test.pass() end end output.dummy(fallback([test, on_track(fun (_) -> check(), sine())])) liquidsoap-2.4.2/tests/fixme/sequence-2.liq000066400000000000000000000021131513273233300206200ustar00rootroot00000000000000# Testing that head_ready is used in sequence#is_ready. # # We setup a sequence to start playing a source s, then stop playing the # sequence, make s unavailable and start playing the sequence again. # At this point the sequence should end its track and cleanup s *before* # becoming unavailable. # # Pitfalls when cooking up this test: # - Does not work with a track insensitive switch to "instantly kill" the # source, because the switch makes sure to stay ready until the end of # track. # - Also does not work simply with a sequence underneath a fallible output, # because such an output will stop as soon as its source is not ready. %include "test.liq" # Create a fallible source. We use the experimental operator source.dynamic() # which is bit fragile for a test. flag = ref(true) thread.run(delay=1., {flag := false}) on = sine() off = fail() s = source.dynamic({if !flag then [on] else [off] end}) test = sequence([s, fail()]) def check() = if source.is_up(s) then test.fail() else test.pass() end end output.dummy(fallback([test, on_track(fun (_) -> check(), sine())])) liquidsoap-2.4.2/tests/gen_dune.ml000066400000000000000000000010161513273233300171510ustar00rootroot00000000000000let () = let location = Sys.getcwd () in let tests = List.filter (fun f -> Filename.extension f = ".liq") (Build_tools.read_files ~location "") in List.iter (fun test -> Printf.printf {| (rule (alias citest) (package liquidsoap) (deps %s ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %%{run_test} %s liquidsoap %%{test_liq} %s))) |} test test test) tests liquidsoap-2.4.2/tests/harbor/000077500000000000000000000000001513273233300163125ustar00rootroot00000000000000liquidsoap-2.4.2/tests/harbor/dune000066400000000000000000000004341513273233300171710ustar00rootroot00000000000000; Regenerate using dune build @gendune --auto-promote (include dune.inc) (rule (alias gendune) (deps (source_tree .)) (target dune.inc.gen) (action (with-stdout-to dune.inc.gen (run ../gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) liquidsoap-2.4.2/tests/harbor/dune.inc000066400000000000000000000025541513273233300177460ustar00rootroot00000000000000 (rule (alias citest) (package liquidsoap) (deps http.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} http.liq liquidsoap %{test_liq} http.liq))) (rule (alias citest) (package liquidsoap) (deps http2.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} http2.liq liquidsoap %{test_liq} http2.liq))) (rule (alias citest) (package liquidsoap) (deps http3.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} http3.liq liquidsoap %{test_liq} http3.liq))) (rule (alias citest) (package liquidsoap) (deps post.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} post.liq liquidsoap %{test_liq} post.liq))) (rule (alias citest) (package liquidsoap) (deps put.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} put.liq liquidsoap %{test_liq} put.liq))) liquidsoap-2.4.2/tests/harbor/http.liq000066400000000000000000000200461513273233300200020ustar00rootroot00000000000000def f() = # Default response def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "GET") test.equal(req.body(timeout=5.0), "") test.equal(req.query, []) test.equal(req.path, "/default") end harbor.http.register("/default", port=3456, handler) resp = http.get("http://localhost:3456/default") test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Endpoint are executed in the order they are declared def handler(_, _) = test.fail() end harbor.http.register.regexp(r/default/g, port=3456, handler) harbor.http.register("/default", port=3456, handler) resp = http.get("http://localhost:3456/default") test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # String response with matches def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "GET") test.equal(req.body(timeout=5.0), "") test.equal(req.query, [("bla", "blo"), ("gni", "gno")]) test.equal(req.path, "/path/gno/blo") end harbor.http.register("/path/:gni/:bla", port=3456, handler) resp = http.get("http://localhost:3456/path/gno/blo") test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Full query def handler(req, res) = test.equal(req.http_version, "1.0") test.equal(req.method, "POST") test.equal( req.query, [("bla", "in"), ("foo", "with"), ("gnu", "gno"), ("gni", "gno")] ) test.equal( req.headers, [ ("host", "localhost:3456"), ("user-agent", http.user_agent), ("accept", "*/*"), ("req", "header"), ("content-length", "9"), ("content-type", "application/x-www-form-urlencoded") ] ) test.equal(req.path, "/some/path/with/full/in/it") test.equal(req.body(timeout=5.0), "foobarlol") res.status_code(201) res.status_message("YYR") res.http_version("1.0") data_count = ref(3) def data() = if data_count() >= 0 then data_count := data_count() - 1 "gnigno" else "" end end res.data(data) res.headers([("some", "value")]) res.content_type("liquidsoap/test") end harbor.http.register.regexp( r/(?[^\/]+)\/full\/(?[^\/]+)/g, method="POST", port=3456, handler ) resp = http.post( http_version="1.0", data="foobarlol", headers=[("req", "header")], "http://localhost:3456/some/path/with/full/in/it?gni=gno&gnu=gno" ) test.equal(resp.status_message, "YYR") test.equal(resp.status_code, 201) test.equal(resp.http_version, "1.0") test.equal( resp.headers, [ ("some", "value"), ("transfer-encoding", "chunked"), ("content-type", "liquidsoap/test") ] ) test.equal("#{resp}", "gnignognignognignognigno") # Large non-chunked query def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "POST") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:3456"), ("user-agent", http.user_agent), ("accept", "*/*"), ("content-length", "10000"), ("content-type", "application/x-www-form-urlencoded") ] ) test.equal(req.path, "/large_non_chunked") test.equal(string.length(encoding="ascii", req.body(timeout=5.0)), 10_000) end harbor.http.register("/large_non_chunked", method="POST", port=3456, handler) resp = http.post( data=string.make(10_000), "http://localhost:3456/large_non_chunked" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Chunked query def handler(req, res) = test.equal(req.http_version, "1.1") test.equal(req.method, "POST") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:3456"), ("user-agent", http.user_agent), ("accept", "*/*"), ("transfer-encoding", "chunked"), ("content-type", "application/x-www-form-urlencoded"), ("expect", "100-continue") ] ) test.equal(req.path, "/chunked") test.equal(req.body(timeout=5.0), "foobarlol") end harbor.http.register("/chunked", method="POST", port=3456, handler) data = begin is_done = ref(false) fun () -> if is_done() then "" else is_done := true "foobarlol" end end resp = http.post(data=data, "http://localhost:3456/chunked") test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Large chunked query def handler(req, res) = test.equal(req.http_version, "1.1") test.equal(req.method, "POST") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:3456"), ("user-agent", http.user_agent), ("accept", "*/*"), ("transfer-encoding", "chunked"), ("content-type", "application/x-www-form-urlencoded"), ("expect", "100-continue") ] ) test.equal(req.path, "/large_chunked") def rec f(count) = ret = req.data() if ret != "" then f(count + string.length(encoding="ascii", ret)) else count end end test.equal(f(0), 10_000_000) end harbor.http.register("/large_chunked", method="POST", port=3456, handler) data = begin tmp = string.make(10_000) count = ref(10_000_000 / 10_000) fun () -> if 0 < count() then count := count() - 1 tmp else "" end end # Custom response def handler(req) = test.equal(req.http_version, "1.1") test.equal(req.method, "GET") test.equal(req.data(), "") test.equal(req.query, []) test.equal(req.path, "/custom") req.socket.write( "HTTP/1.0 201 YYR\r\nFoo: bar\r\n\r\n" ) req.socket.close() null end harbor.http.register.simple("/custom", port=3456, handler) resp = http.get("http://localhost:3456/custom") test.equal(resp.status_message, "YYR") test.equal(resp.status_code, 201) test.equal(resp.http_version, "1.0") test.equal(resp.headers, [("foo", "bar")]) test.equal("#{resp}", "") # Cors headers harbor.http.middleware.register(harbor.http.middleware.cors(origin="foo.com")) resp = http.get("http://localhost:3456/default") test.equal(resp.status_message, "OK") test.equal(resp.http_version, "1.1") test.equal(resp.status_code, 200) test.equal( resp.headers, [("access-control-allow-origin", "foo.com"), ("vary", "Origin")] ) test.equal("#{resp}", "") # transport conflict transport = http.transport.ssl(certificate="foo", key="bla") try harbor.http.register( "/default", transport=transport, port=3456, fun (_, _) -> () ) test.fail() catch err : [error.http] do test.equal( err.message, "Port is already opened with a different transport" ) end # Response ended read = ref(fun (~timeout:_) -> error.raise(error.assertion)) def handler(req, _) = read := req.data end harbor.http.register("/response_ended", port=3456, handler) resp = http.get("http://localhost:3456/response_ended") test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) try fn = read() fn(timeout=10.) test.fail() catch err : [error.http] do test.equal( err.message, "Response ended!" ) end # Register output on the same port o = output.harbor(port=4455, mount="bla.wav", %wav, sine()) o.on_start(synchronous=true, test.pass) end test.check(f) liquidsoap-2.4.2/tests/harbor/http2.liq000066400000000000000000000045571513273233300200750ustar00rootroot00000000000000def f() = content_disposition = null.get( http.headers.content_disposition([('Content-Disposition', 'inline')]) ) test.equal(content_disposition.type, "inline") test.equal(content_disposition?.filename, null) test.equal(content_disposition.args, []) content_disposition = null.get( http.headers.content_disposition( [ ( 'Content-Disposition', 'attachment; filename="filename.jpg"' ) ] ) ) test.equal(content_disposition.type, "attachment") test.equal(content_disposition?.filename, "filename.jpg") test.equal(content_disposition.args, []) content_disposition = null.get( http.headers.content_disposition( [ ( 'Content-Disposition', "attachment; filename*=UTF-8''Na%C3%AFve%20file.txt" ) ] ) ) test.equal(content_disposition.type, "attachment") test.equal( content_disposition?.filename, "Naïve file.txt" ) test.equal(content_disposition.args, []) content_disposition = null.get( http.headers.content_disposition( [ ( 'Content-Disposition', "attachment; filename=Na%C3%AFve%20file.txt" ) ] ) ) test.equal(content_disposition.type, "attachment") test.equal( content_disposition?.filename, "Naïve file.txt" ) test.equal(content_disposition.args, []) content_disposition = null.get( http.headers.content_disposition( [ ( 'Content-Disposition', "attachment; filename=Naïve file.txt" ) ] ) ) test.equal(content_disposition.type, "attachment") test.equal( content_disposition?.filename, "Naïve file.txt" ) test.equal(content_disposition.args, []) content_disposition = null.get( http.headers.content_disposition( [ ( 'coNtent-dispOsition', "attachment; filename*=UTF-8''Na%C3%AFve%20file.txt; \ name=\"gni%20gno\"; foo=bla" ) ] ) ) test.equal(content_disposition.type, "attachment") test.equal( content_disposition?.filename, "Naïve file.txt" ) test.equal( content_disposition?.name, "gni gno" ) test.equal(content_disposition.args, [("foo", "bla")]) test.pass() end test.check(f) liquidsoap-2.4.2/tests/harbor/http3.liq000066400000000000000000000021271513273233300200650ustar00rootroot00000000000000def f() = content_type = http.headers.content_type( [ ( "Content-Type", "text/html; charset=utf-8" ) ] ) test.equal(content_type, {mime="text/html", args=[("charset", "utf-8")]}) content_type = http.headers.content_type( [ ( "coNtent-tYpe", "multipart/form-data; boundary=something" ) ] ) test.equal( content_type, {mime="multipart/form-data", args=[("boundary", "something")]} ) extname = http.headers.extname( [ ( "Content-Disposition", 'attachment; filename="filename.jpg"' ) ] ) test.equal(extname, ".jpg") extname = http.headers.extname( [ ("Content-type", "audio/mpeg"), ( "Content-Disposition", 'attachment; filename="filename.jpg"' ) ] ) test.equal(extname, ".jpg") settings.http.mime.extnames := [("foo", ".bla")] extname = http.headers.extname([("conTent-tyPe", "foo")]) test.equal(extname, ".bla") test.pass() end test.check(f) liquidsoap-2.4.2/tests/harbor/post.liq000066400000000000000000000125471513273233300200170ustar00rootroot00000000000000def f() = data = http.multipart_form_data( boundary="foobar", [ { name= "the name", contents=getter("foobarlol"), headers=[("some", "headers")], attributes= [ ( "filename", "the filename" ) ] }, {name="namez", contents=getter("gnigno"), headers=[], attributes=[]} ] ) test.equal( string.getter.flush(data.contents), "--foobar\r\nContent-Disposition: form-data; name=\"the name\"; \ filename=\"the filename\"\r\nsome: \ headers\r\n\r\nfoobarlol\r\n--foobar\r\nContent-Disposition: form-data; \ name=\"namez\"\r\n\r\ngnigno\r\n--foobar--\r\n" ) test.equal(data.boundary, "foobar") data = http.multipart_form_data( boundary="foobar", [ { name= "the name", contents=getter("foobarlol"), headers=[("some", "headers")], attributes= [ ( "filename", "the filename" ) ] }, {name="namez", contents=getter("gnigno"), headers=[], attributes=[]} ] ) test.equal( string.getter.flush(data.contents), "--#{data.boundary}\r\nContent-Disposition: form-data; name=\"the name\"; \ filename=\"the filename\"\r\nsome: headers\r\n\r\nfoobarlol\r\n--#{ data.boundary }\r\nContent-Disposition: form-data; name=\"namez\"\r\n\r\ngnigno\r\n--#{ data.boundary }--\r\n" ) # Test file upload fname = "post.liq" range = [...string.char.ascii.alphabet, ...string.char.ascii.number] l = list.init(12, fun (_) -> string.char.ascii.random(range)) boundary = string.concat(l) def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "POST") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:6543"), ("user-agent", http.user_agent), ("accept", "*/*"), ("transfer-encoding", "chunked"), ( "content-type", "multipart/form-data; boundary=#{boundary}" ), ("expect", "100-continue") ] ) test.equal(req.path, "/file_upload") test.equal( req.body(timeout=5.0), "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; \ filename=\"#{fname}\"\r\nContent-Type: text/plain\r\n\r\n#{ file.contents(fname) }\r\n--#{boundary}--\r\n" ) end harbor.http.register("/file_upload", method="POST", port=6543, handler) resp = http.post.file( file=fname, content_type="text/plain", boundary=boundary, "http://localhost:6543/file_upload" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Test in-memory file upload range = [...string.char.ascii.alphabet, ...string.char.ascii.number] l = list.init(12, fun (_) -> string.char.ascii.random(range)) boundary = string.concat(l) def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "POST") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:6543"), ("user-agent", http.user_agent), ("accept", "*/*"), ( "content-type", "multipart/form-data; boundary=#{boundary}" ), ("content-length", "138") ] ) test.equal(req.path, "/in_memory_file_upload") test.equal( req.body(timeout=5.0), "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; \ filename=\"foo.txt\"\r\nContent-Type: text/plain\r\n\r\nfoobarlol\r\n--#{ boundary }--\r\n" ) end harbor.http.register( "/in_memory_file_upload", method="POST", port=6543, handler ) resp = http.post.file( filename="foo.txt", contents="foobarlol", content_type="text/plain", boundary=boundary, "http://localhost:6543/in_memory_file_upload" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Test JSON post j = json.object() j.add("foo", random.int()) j.add("contents", file.contents("post.liq")) data = json.stringify(j) def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "POST") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:6543"), ("user-agent", http.user_agent), ("content-type", "application/json"), ("accept", "application/json"), ("content-length", "#{string.length(data)}") ] ) test.equal(req.path, "/json_post") test.equal(req.body(timeout=5.0), data) end harbor.http.register("/json_post", method="POST", port=6543, handler) resp = http.post( headers=[ ("Content-Type", "application/json"), ("Accept", "application/json") ], data=data, "http://localhost:6543/json_post" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") test.pass() end test.check(f) liquidsoap-2.4.2/tests/harbor/put.liq000066400000000000000000000126551513273233300176420ustar00rootroot00000000000000def f() = data = http.multipart_form_data( boundary="foobar", [ { name= "the name", contents=getter("foobarlol"), headers=[("some", "headers")], attributes= [ ( "filename", "the filename" ) ] }, {name="namez", contents=getter("gnigno"), headers=[], attributes=[]} ] ) test.equal( string.getter.flush(data.contents), "--foobar\r\nContent-Disposition: form-data; name=\"the name\"; \ filename=\"the filename\"\r\nsome: \ headers\r\n\r\nfoobarlol\r\n--foobar\r\nContent-Disposition: form-data; \ name=\"namez\"\r\n\r\ngnigno\r\n--foobar--\r\n" ) test.equal(data.boundary, "foobar") data = http.multipart_form_data( boundary="foobar", [ { name= "the name", contents=getter("foobarlol"), headers=[("some", "headers")], attributes= [ ( "filename", "the filename" ) ] }, {name="namez", contents=getter("gnigno"), headers=[], attributes=[]} ] ) test.equal( string.getter.flush(data.contents), "--#{data.boundary}\r\nContent-Disposition: form-data; name=\"the name\"; \ filename=\"the filename\"\r\nsome: headers\r\n\r\nfoobarlol\r\n--#{ data.boundary }\r\nContent-Disposition: form-data; name=\"namez\"\r\n\r\ngnigno\r\n--#{ data.boundary }--\r\n" ) # Test file upload fname = "put.liq" range = [...string.char.ascii.alphabet, ...string.char.ascii.number] l = list.init(12, fun (_) -> string.char.ascii.random(range)) boundary = string.concat(l) def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "PUT") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:5427"), ("user-agent", http.user_agent), ("accept", "*/*"), ("transfer-encoding", "chunked"), ( "content-type", "multipart/form-data; boundary=#{boundary}" ), ("expect", "100-continue") ] ) test.equal(req.path, "/large_non_chunked") test.equal( req.body(timeout=5.0), "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; \ filename=\"#{fname}\"\r\nContent-Type: text/plain\r\n\r\n#{ file.contents(fname) }\r\n--#{boundary}--\r\n" ) end harbor.http.register("/large_non_chunked", method="PUT", port=5427, handler) resp = http.put.file( file=fname, content_type="text/plain", boundary=boundary, "http://localhost:5427/large_non_chunked" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Test in-memory file upload range = [...string.char.ascii.alphabet, ...string.char.ascii.number] l = list.init(12, fun (_) -> string.char.ascii.random(range)) boundary = string.concat(l) def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "PUT") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:5427"), ("user-agent", http.user_agent), ("accept", "*/*"), ("transfer-encoding", "chunked"), ( "content-type", "multipart/form-data; boundary=#{boundary}" ), ("expect", "100-continue") ] ) test.equal(req.path, "/in_memory_file_upload") test.equal( req.body(timeout=5.0), "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; \ filename=\"foo.txt\"\r\nContent-Type: text/plain\r\n\r\nfoobarlol\r\n--#{ boundary }--\r\n" ) end harbor.http.register( "/in_memory_file_upload", method="PUT", port=5427, handler ) resp = http.put.file( filename="foo.txt", contents="foobarlol", content_type="text/plain", boundary=boundary, "http://localhost:5427/in_memory_file_upload" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") # Test JSON put j = json.object() j.add("foo", random.int()) j.add("contents", file.contents("put.liq")) data = json.stringify(j) def handler(req, _) = test.equal(req.http_version, "1.1") test.equal(req.method, "PUT") test.equal(req.query, []) test.equal( req.headers, [ ("host", "localhost:5427"), ("user-agent", http.user_agent), ("transfer-encoding", "chunked"), ("content-type", "application/json"), ("accept", "application/json"), ("expect", "100-continue") ] ) test.equal(req.path, "/json_put") test.equal(req.body(timeout=5.0), data) end harbor.http.register("/json_put", method="PUT", port=5427, handler) resp = http.put( headers=[ ("Content-Type", "application/json"), ("Accept", "application/json") ], data=data, "http://localhost:5427/json_put" ) test.equal(resp.status_message, "OK") test.equal(resp.status_code, 200) test.equal(resp.http_version, "1.1") test.equal(resp.headers, []) test.equal("#{resp}", "") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/000077500000000000000000000000001513273233300166205ustar00rootroot00000000000000liquidsoap-2.4.2/tests/language/.gitignore000066400000000000000000000000211513273233300206010ustar00rootroot00000000000000interactive.json liquidsoap-2.4.2/tests/language/argsof.liq000066400000000000000000000024521513273233300206130ustar00rootroot00000000000000# Test argsof directive. def f() = # file.extension: (?dir_sep : string, ?leading_dot : bool, string) -> string # Add all labeled arguments def g(%argsof(file.extension)) = "#{dir_sep}-#{leading_dot}" end if g() != "/-true" then test.fail() end if g(dir_sep="gni") != "gni-true" then test.fail() end if g(leading_dot=false) != "/-false" then test.fail() end if g(dir_sep="gni", leading_dot=false) != "gni-false" then test.fail() end # Add all but leading dot. Add unlabeled argument to test as well def g(%argsof(file.extension[!leading_dot]), x) = "#{dir_sep}-#{x}" end if g("bla") != "/-bla" then test.fail() end if g(dir_sep="gni", "bla") != "gni-bla" then test.fail() end # Add only leading_dot def g(%argsof(file.extension[leading_dot]), x) = "#{leading_dot}-#{x}" end if g(leading_dot=false, "bla") != "false-bla" then test.fail() end # Test argsof call def h(~leading_dot=false, ~dir_sep="no_dir_sep") = "#{leading_dot}-#{dir_sep}" end leading_dot = true dir_sep = "/" if h(%argsof(file.extension)) != "true-/" then test.fail() end if h(%argsof(file.extension[!leading_dot])) != "false-/" then test.fail() end if h(%argsof(file.extension[leading_dot])) != "true-no_dir_sep" then test.fail() end test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/bool.liq000066400000000000000000000005121513273233300202600ustar00rootroot00000000000000def f() = # Test strictness and evaluation order l = ref(false) r = ref(false) if begin l := true true end or begin r := true true end then () end test.equal(l() and (not r()), true) ignore(true == false ? 5 : 6 ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/comments.liq000066400000000000000000000021761513273233300211620ustar00rootroot00000000000000#<------# This is a multiline comment with > some # of these characters in it. #------># #<--------------- # Yet another # form of multiline comment ----------------># # A one line comment #< can be followed by multiple lines comment ># #< multiline comment can start and end in the middle of a line ># #<-----# Anything following them is executed #----># x = 1 #<-----#y = x + 2. Anything after the mark is comment #----># #<--------------- # Nested comments #< Are accepted ># #----------------># s = "foo #<----- this is a comment inside a string, it is not a code comment! #----> bar " ignore(s) r = r/foo #<----- this is a comment inside a regexp, it is not a code comment! #----> bar/ ignore(r) def f() = test.equal(x, 1) try let eval _ = "#<----# This comment is not terminated" test.fail() catch _ do () end let eval _ = "# single line comment can be followed by EOF" test.equal("a#b", string.base64.decode("YSNi")) test.equal(string(r/a#b/), "r/a#b/") test.equal("a##c", string.base64.decode("YSM8Yj4jYw==")) test.equal(string(r/a##c/), "r/a##c/") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/conversions.liq000066400000000000000000000007261513273233300217040ustar00rootroot00000000000000def fn() = test.equal(int_of_string("0xff"), 255) test.raises(fun () -> int_of_string("ff")) test.equal(int_of_string(default=1, "ff"), 1) test.equal(float_of_string("0xff"), 255.) test.raises(fun () -> float_of_string("ff")) test.equal(float_of_string(default=1., "ff"), 1.) test.equal(bool_of_string("false"), false) test.raises(fun () -> bool_of_string("ff")) test.equal(bool_of_string(default=false, "ff"), false) test.pass() end test.check(fn) liquidsoap-2.4.2/tests/language/cron.liq000066400000000000000000000014141513273233300202700ustar00rootroot00000000000000def f() = let json.parse (test_cases : [ { crontab_entry: string, time: {day: int, hour: int, min: int, month: int, year: int}, result: bool } ] ) = file.contents("crontab_test_cases.json") list.iter( fun ({crontab_entry, time = t, result}) -> begin let {test = test_fn} = cron.parse(crontab_entry) secs = time.make(t.{dst=null, sec=0}) if not (test_fn(time=secs) == result) then test.fail( "Cron pattern #{crontab_entry} should be #{result} at #{t}" ) else log.important( "Cron pattern #{crontab_entry} is #{result} at #{t}" ) end end, test_cases ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/crontab_test_cases.json000066400000000000000000000214301513273233300233600ustar00rootroot00000000000000[ { "crontab_entry": "* * * * *", "time": { "day": 15, "hour": 12, "min": 30, "month": 6, "year": 2024 }, "result": true }, { "crontab_entry": "30 14 * * *", "time": { "day": 10, "hour": 14, "min": 30, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 1 1 *", "time": { "day": 1, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 1 1 *", "time": { "day": 2, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": false }, { "crontab_entry": "15 10 * * 1", "time": { "day": 8, "hour": 10, "min": 15, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "15 10 * * 1", "time": { "day": 9, "hour": 10, "min": 15, "month": 7, "year": 2024 }, "result": false }, { "crontab_entry": "*/15 * * * *", "time": { "day": 20, "hour": 8, "min": 45, "month": 3, "year": 2024 }, "result": true }, { "crontab_entry": "*/15 * * * *", "time": { "day": 20, "hour": 8, "min": 44, "month": 3, "year": 2024 }, "result": false }, { "crontab_entry": "0 9-17 * * *", "time": { "day": 22, "hour": 13, "min": 0, "month": 10, "year": 2024 }, "result": true }, { "crontab_entry": "0 9-17 * * *", "time": { "day": 22, "hour": 18, "min": 0, "month": 10, "year": 2024 }, "result": false }, { "crontab_entry": "0 0 1 */2 *", "time": { "day": 1, "hour": 0, "min": 0, "month": 4, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 1 */2 *", "time": { "day": 1, "hour": 0, "min": 0, "month": 5, "year": 2024 }, "result": false }, { "crontab_entry": "0 0 * 12 *", "time": { "day": 15, "hour": 0, "min": 0, "month": 12, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 * 12 *", "time": { "day": 15, "hour": 0, "min": 0, "month": 11, "year": 2024 }, "result": false }, { "crontab_entry": "0 12 * * 6", "time": { "day": 6, "hour": 12, "min": 0, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "0 12 * * 6", "time": { "day": 7, "hour": 12, "min": 0, "month": 7, "year": 2024 }, "result": false }, { "crontab_entry": "5 4 * * 1-5", "time": { "day": 17, "hour": 4, "min": 5, "month": 6, "year": 2024 }, "result": true }, { "crontab_entry": "5 4 * * 3-5", "time": { "day": 18, "hour": 4, "min": 5, "month": 6, "year": 2024 }, "result": false }, { "crontab_entry": "0 6,18 * * *", "time": { "day": 10, "hour": 6, "min": 0, "month": 8, "year": 2024 }, "result": true }, { "crontab_entry": "0 6,18 * * *", "time": { "day": 10, "hour": 17, "min": 0, "month": 8, "year": 2024 }, "result": false }, { "crontab_entry": "@hourly", "time": { "day": 5, "hour": 14, "min": 0, "month": 9, "year": 2024 }, "result": true }, { "crontab_entry": "@hourly", "time": { "day": 5, "hour": 14, "min": 1, "month": 9, "year": 2024 }, "result": false }, { "crontab_entry": "@daily", "time": { "day": 8, "hour": 0, "min": 0, "month": 3, "year": 2024 }, "result": true }, { "crontab_entry": "@daily", "time": { "day": 8, "hour": 0, "min": 1, "month": 3, "year": 2024 }, "result": false }, { "crontab_entry": "@weekly", "time": { "day": 7, "hour": 0, "min": 0, "month": 4, "year": 2024 }, "result": true }, { "crontab_entry": "@weekly", "time": { "day": 8, "hour": 0, "min": 0, "month": 4, "year": 2024 }, "result": false }, { "crontab_entry": "@monthly", "time": { "day": 1, "hour": 0, "min": 0, "month": 5, "year": 2024 }, "result": true }, { "crontab_entry": "@monthly", "time": { "day": 2, "hour": 0, "min": 0, "month": 5, "year": 2024 }, "result": false }, { "crontab_entry": "@yearly", "time": { "day": 1, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": true }, { "crontab_entry": "@yearly", "time": { "day": 2, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": false }, { "crontab_entry": "@annually", "time": { "day": 1, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": true }, { "crontab_entry": "@annually", "time": { "day": 2, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": false }, { "crontab_entry": "0 0 1,15 * *", "time": { "day": 1, "hour": 0, "min": 0, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 1,15 * *", "time": { "day": 15, "hour": 0, "min": 0, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 1,15 * *", "time": { "day": 2, "hour": 0, "min": 0, "month": 7, "year": 2024 }, "result": false }, { "crontab_entry": "0 8,20 * * *", "time": { "day": 5, "hour": 8, "min": 0, "month": 5, "year": 2024 }, "result": true }, { "crontab_entry": "0 8,20 * * *", "time": { "day": 5, "hour": 20, "min": 0, "month": 5, "year": 2024 }, "result": true }, { "crontab_entry": "0 8,20 * * *", "time": { "day": 5, "hour": 19, "min": 0, "month": 5, "year": 2024 }, "result": false }, { "crontab_entry": "30 7 * 1,6,12 *", "time": { "day": 10, "hour": 7, "min": 30, "month": 6, "year": 2024 }, "result": true }, { "crontab_entry": "30 7 * 1,6,12 *", "time": { "day": 10, "hour": 7, "min": 30, "month": 3, "year": 2024 }, "result": false }, { "crontab_entry": "0 0 * * 1,3,5", "time": { "day": 10, "hour": 0, "min": 0, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 * * 2,4", "time": { "day": 11, "hour": 0, "min": 0, "month": 7, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 * * 2,4", "time": { "day": 12, "hour": 0, "min": 0, "month": 7, "year": 2024 }, "result": false }, { "crontab_entry": "0 6 10,20 3,6 *", "time": { "day": 10, "hour": 6, "min": 0, "month": 3, "year": 2024 }, "result": true }, { "crontab_entry": "0 6 10,20 3,6 *", "time": { "day": 20, "hour": 6, "min": 0, "month": 6, "year": 2024 }, "result": true }, { "crontab_entry": "0 6 10,20 3,6 *", "time": { "day": 15, "hour": 6, "min": 0, "month": 6, "year": 2024 }, "result": false }, { "crontab_entry": "45 1 1,15,30 * *", "time": { "day": 15, "hour": 1, "min": 45, "month": 8, "year": 2024 }, "result": true }, { "crontab_entry": "45 1 1,15,30 * *", "time": { "day": 30, "hour": 1, "min": 45, "month": 8, "year": 2024 }, "result": true }, { "crontab_entry": "45 1 1,15,30 * *", "time": { "day": 10, "hour": 1, "min": 45, "month": 8, "year": 2024 }, "result": false }, { "crontab_entry": "0 0 1,15 1,6,12 *", "time": { "day": 15, "hour": 0, "min": 0, "month": 1, "year": 2024 }, "result": true }, { "crontab_entry": "0 0 1,15 1,6,12 *", "time": { "day": 15, "hour": 0, "min": 0, "month": 2, "year": 2024 }, "result": false }, { "crontab_entry": "0 0 1,15 1,6,12 *", "time": { "day": 1, "hour": 0, "min": 0, "month": 6, "year": 2024 }, "result": true } ] liquidsoap-2.4.2/tests/language/cue_test.liq000066400000000000000000002743471513273233300211630ustar00rootroot00000000000000def f() = cue_file = 'REM GENRE Electronica REM DATE 2020 PERFORMER "Electronic Mix" TITLE "Musique a la carte" FILE "Bronski beat - Smalltown boy (12 extended).mp3" MP3 TRACK 01 AUDIO TITLE "Smalltown boy (12 extended)" PERFORMER "Bronski Beat" INDEX 01 00:00:00 FILE "Rihanna - Dont Stop The Music (Ed Marquis Remix).mp3" MP3 TRACK 02 AUDIO TITLE "Dont Stop The Music (Ed Marquis Remix)" PERFORMER "Rihanna" INDEX 01 00:00:00' test.equal( playlist.parse.cue.full(cue_file), { tracks= [ { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="mp3", filename= "Bronski beat - Smalltown boy (12 extended).mp3" } ) ], title= "Smalltown boy (12 extended)", position=1, track_type="audio", performer= "Bronski Beat" }, { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="mp3", filename= "Rihanna - Dont Stop The Music (Ed Marquis Remix).mp3" } ) ], title= "Dont Stop The Music (Ed Marquis Remix)", position=2, track_type="audio", performer="Rihanna" } ], title= "Musique a la carte", rem=[("date", "2020"), ("genre", "Electronica")], performer= "Electronic Mix" } ) test.equal( playlist.parse.cue(cue_file), [ ( [ ( "album", "Musique a la carte" ), ( "title", "Smalltown boy (12 extended)" ), ( "albumartist", "Electronic Mix" ), ( "artist", "Bronski Beat" ), ("tracknumber", "1"), ("year", "2020"), ("genre", "Electronica") ], "Bronski beat - Smalltown boy (12 extended).mp3" ), ( [ ( "album", "Musique a la carte" ), ( "title", "Dont Stop The Music (Ed Marquis Remix)" ), ( "albumartist", "Electronic Mix" ), ("artist", "Rihanna"), ("tracknumber", "2"), ("year", "2020"), ("genre", "Electronica") ], "Rihanna - Dont Stop The Music (Ed Marquis Remix).mp3" ) ] ) cue_file = 'PERFORMER "Block Party" TITLE "Silent Alarm" FILE "Block Party - Silent Alarm.flac" WAVE TRACK 01 AUDIO TITLE "Like Eating Glass" PERFORMER "Block Party" INDEX 00 00:00:00 INDEX 01 03:22:70 TRACK 02 AUDIO TITLE "Helicopter" PERFORMER "Block Party" INDEX 00 07:42:69 INDEX 01 07:44:69' test.equal( playlist.parse.cue.full(cue_file), { tracks= [ { indexes= [ ( 0, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "Block Party - Silent Alarm.flac" } ), ( 1, { frames=70, seconds=22, minutes=3, file_type="wave", filename= "Block Party - Silent Alarm.flac" } ) ], performer= "Block Party", track_type="audio", position=1, title= "Like Eating Glass" }, { indexes= [ ( 0, { frames=69, seconds=42, minutes=7, file_type="wave", filename= "Block Party - Silent Alarm.flac" } ), ( 1, { frames=69, seconds=44, minutes=7, file_type="wave", filename= "Block Party - Silent Alarm.flac" } ) ], performer= "Block Party", track_type="audio", position=2, title="Helicopter" } ], title= "Silent Alarm", rem=[], performer= "Block Party" } ) test.equal( playlist.parse.cue(cue_file), [ ( [ ("liq_cue_in", "202.933333333"), ("liq_cue_out", "462.92"), ( "album", "Silent Alarm" ), ( "title", "Like Eating Glass" ), ( "albumartist", "Block Party" ), ( "artist", "Block Party" ), ("tracknumber", "1"), ( "liq_index_zero_filename", "Block Party - Silent Alarm.flac" ), ("liq_index_zero", "0.0") ], "Block Party - Silent Alarm.flac" ), ( [ ("liq_cue_in", "464.92"), ( "album", "Silent Alarm" ), ("title", "Helicopter"), ( "albumartist", "Block Party" ), ( "artist", "Block Party" ), ("tracknumber", "2"), ( "liq_index_zero_filename", "Block Party - Silent Alarm.flac" ), ("liq_index_zero", "462.92") ], "Block Party - Silent Alarm.flac" ) ] ) cue_file = 'REM GENRE Ska REM DATE 1991 REM DISCID D00DA810 REM COMMENT "ExactAudioCopy v0.95b4" PERFORMER "The Specials" TITLE "Singles" FILE "The Specials - Singles.wav" WAVE TRACK 01 AUDIO TITLE "Gangsters" PERFORMER "The Specials" INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "Rudi, A Message To You" PERFORMER "The Specials" INDEX 00 02:47:74 INDEX 01 02:48:27 TRACK 03 AUDIO TITLE "Nite Klub" PERFORMER "The Specials" INDEX 00 05:41:50 INDEX 01 05:42:27 TRACK 04 AUDIO TITLE "Too Much Too Young" PERFORMER "The Specials" INDEX 00 08:53:47 INDEX 01 08:54:37 TRACK 05 AUDIO TITLE "Guns Of Navarone" PERFORMER "The Specials" INDEX 00 10:59:20 INDEX 01 11:00:17 TRACK 06 AUDIO TITLE "Rat Race" PERFORMER "The Specials" INDEX 00 13:20:55 INDEX 01 13:20:67 TRACK 07 AUDIO TITLE "Stereotype" PERFORMER "The Specials" INDEX 00 16:29:67 INDEX 01 16:30:30 TRACK 08 AUDIO TITLE "International Jet Set" PERFORMER "The Specials" INDEX 00 20:19:27 INDEX 01 20:20:20 TRACK 09 AUDIO TITLE "Do Nothing" PERFORMER "The Specials" INDEX 00 24:30:70 INDEX 01 24:32:27 TRACK 10 AUDIO TITLE "Ghost Town" PERFORMER "The Specials" INDEX 00 28:23:30 INDEX 01 28:23:42 TRACK 11 AUDIO TITLE "Why?" PERFORMER "The Specials" INDEX 00 34:21:37 INDEX 01 34:21:47 TRACK 12 AUDIO TITLE "Friday Night, Saturday Morning" PERFORMER "The Specials" INDEX 00 38:16:50 INDEX 01 38:16:55 TRACK 13 AUDIO TITLE "War Crimes" PERFORMER "The Specials" INDEX 00 41:50:07 INDEX 01 41:51:00 TRACK 14 AUDIO TITLE "Racist Friend" PERFORMER "The Specials" INDEX 00 45:50:55 INDEX 01 45:51:72 TRACK 15 AUDIO TITLE "Nelson Mandela" PERFORMER "The Specials" INDEX 00 49:35:55 INDEX 01 49:38:22 TRACK 16 AUDIO TITLE "(What I Like Most About You Is Your) Girlfriend" PERFORMER "The Specials" INDEX 00 54:11:00 INDEX 01 54:12:40' test.equal( playlist.parse.cue.full(cue_file), { tracks= [ { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "The Specials - Singles.wav" } ) ], title="Gangsters", position=1, track_type="audio", performer= "The Specials" }, { indexes= [ ( 0, { frames=74, seconds=47, minutes=2, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=27, seconds=48, minutes=2, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=2, title= "Rudi, A Message To You" }, { indexes= [ ( 0, { frames=50, seconds=41, minutes=5, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=27, seconds=42, minutes=5, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=3, title= "Nite Klub" }, { indexes= [ ( 0, { frames=47, seconds=53, minutes=8, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=37, seconds=54, minutes=8, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=4, title= "Too Much Too Young" }, { indexes= [ ( 0, { frames=20, seconds=59, minutes=10, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=17, seconds=0, minutes=11, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=5, title= "Guns Of Navarone" }, { indexes= [ ( 0, { frames=55, seconds=20, minutes=13, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=67, seconds=20, minutes=13, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=6, title= "Rat Race" }, { indexes= [ ( 0, { frames=67, seconds=29, minutes=16, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=30, seconds=30, minutes=16, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=7, title="Stereotype" }, { indexes= [ ( 0, { frames=27, seconds=19, minutes=20, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=20, seconds=20, minutes=20, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=8, title= "International Jet Set" }, { indexes= [ ( 0, { frames=70, seconds=30, minutes=24, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=27, seconds=32, minutes=24, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=9, title= "Do Nothing" }, { indexes= [ ( 0, { frames=30, seconds=23, minutes=28, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=42, seconds=23, minutes=28, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=10, title= "Ghost Town" }, { indexes= [ ( 0, { frames=37, seconds=21, minutes=34, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=47, seconds=21, minutes=34, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=11, title="Why?" }, { indexes= [ ( 0, { frames=50, seconds=16, minutes=38, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=55, seconds=16, minutes=38, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=12, title= "Friday Night, Saturday Morning" }, { indexes= [ ( 0, { frames=7, seconds=50, minutes=41, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=0, seconds=51, minutes=41, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=13, title= "War Crimes" }, { indexes= [ ( 0, { frames=55, seconds=50, minutes=45, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=72, seconds=51, minutes=45, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=14, title= "Racist Friend" }, { indexes= [ ( 0, { frames=55, seconds=35, minutes=49, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=22, seconds=38, minutes=49, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=15, title= "Nelson Mandela" }, { indexes= [ ( 0, { frames=0, seconds=11, minutes=54, file_type="wave", filename= "The Specials - Singles.wav" } ), ( 1, { frames=40, seconds=12, minutes=54, file_type="wave", filename= "The Specials - Singles.wav" } ) ], performer= "The Specials", track_type="audio", position=16, title= "(What I Like Most About You Is Your) Girlfriend" } ], title="Singles", rem= [ ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("date", "1991"), ("genre", "Ska") ], performer= "The Specials" } ) test.equal( playlist.parse.cue(cue_file), [ ( [ ("liq_cue_out", "167.986666667"), ("album", "Singles"), ("title", "Gangsters"), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "1"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "168.36"), ("liq_cue_out", "341.666666667"), ("album", "Singles"), ( "title", "Rudi, A Message To You" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "2"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "167.986666667"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "342.36"), ("liq_cue_out", "533.626666667"), ("album", "Singles"), ( "title", "Nite Klub" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "3"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "341.666666667"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "534.493333333"), ("liq_cue_out", "659.266666667"), ("album", "Singles"), ( "title", "Too Much Too Young" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "4"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "533.626666667"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "660.226666667"), ("liq_cue_out", "800.733333333"), ("album", "Singles"), ( "title", "Guns Of Navarone" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "5"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "659.266666667"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "800.893333333"), ("liq_cue_out", "989.893333333"), ("album", "Singles"), ( "title", "Rat Race" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "6"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "800.733333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "990.4"), ("liq_cue_out", "1219.36"), ("album", "Singles"), ("title", "Stereotype"), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "7"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "989.893333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "1220.26666667"), ("liq_cue_out", "1470.93333333"), ("album", "Singles"), ( "title", "International Jet Set" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "8"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "1219.36"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "1472.36"), ("liq_cue_out", "1703.4"), ("album", "Singles"), ( "title", "Do Nothing" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "9"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "1470.93333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "1703.56"), ("liq_cue_out", "2061.49333333"), ("album", "Singles"), ( "title", "Ghost Town" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "10"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "1703.4"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "2061.62666667"), ("liq_cue_out", "2296.66666667"), ("album", "Singles"), ("title", "Why?"), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "11"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "2061.49333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "2296.73333333"), ("liq_cue_out", "2510.09333333"), ("album", "Singles"), ( "title", "Friday Night, Saturday Morning" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "12"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "2296.66666667"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "2511.0"), ("liq_cue_out", "2750.73333333"), ("album", "Singles"), ( "title", "War Crimes" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "13"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "2510.09333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "2751.96"), ("liq_cue_out", "2975.73333333"), ("album", "Singles"), ( "title", "Racist Friend" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "14"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "2750.73333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "2978.29333333"), ("liq_cue_out", "3251.0"), ("album", "Singles"), ( "title", "Nelson Mandela" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "15"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "2975.73333333"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ), ( [ ("liq_cue_in", "3252.53333333"), ("album", "Singles"), ( "title", "(What I Like Most About You Is Your) Girlfriend" ), ( "albumartist", "The Specials" ), ( "artist", "The Specials" ), ("tracknumber", "16"), ( "liq_index_zero_filename", "The Specials - Singles.wav" ), ("liq_index_zero", "3251.0"), ( "comment", "ExactAudioCopy v0.95b4" ), ("discid", "D00DA810"), ("year", "1991"), ("genre", "Ska") ], "The Specials - Singles.wav" ) ] ) cue_file = 'FILE "The Specials - Singles - 01 - Gangsters.wav" WAVE TRACK 01 AUDIO TITLE "Gangsters" PERFORMER "The Specials" INDEX 01 00:00:00 FILE "The Specials - Singles - 02 - Rudi, A Message To You.wav" WAVE TRACK 02 AUDIO TITLE "Rudi, A Message To You" PERFORMER "The Specials" PREGAP 00:00:28 INDEX 01 00:00:00' test.equal( playlist.parse.cue.full(cue_file), { tracks= [ { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "The Specials - Singles - 01 - Gangsters.wav" } ) ], title="Gangsters", position=1, track_type="audio", performer= "The Specials" }, { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "The Specials - Singles - 02 - Rudi, A Message To You.wav" } ) ], performer= "The Specials", track_type="audio", position=2, title= "Rudi, A Message To You", pregap={minutes=0, seconds=0, frames=28} } ], rem=[] } ) test.equal( playlist.parse.cue(cue_file), [ ( [ ("title", "Gangsters"), ( "artist", "The Specials" ), ("tracknumber", "1") ], "The Specials - Singles - 01 - Gangsters.wav" ), ( [ ( "title", "Rudi, A Message To You" ), ( "artist", "The Specials" ), ("tracknumber", "2"), ("liq_pregap", "0.373333333333") ], "The Specials - Singles - 02 - Rudi, A Message To You.wav" ) ] ) cue_file = 'REM GENRE Rap/Metal REM DATE 1996 REM DISCID A30AEF0B REM COMMENT "ExactAudioCopy v1.0b3" PERFORMER "Rage Against The Machine" TITLE "Evil Empire" FILE "01 People Of The Sun.flac" WAVE TRACK 01 AUDIO TITLE "People Of The Sun" PERFORMER "Rage Against The Machine" ISRC USSM19502587 INDEX 01 00:00:00 FILE "02 Bulls On Parade.flac" WAVE TRACK 02 AUDIO TITLE "Bulls On Parade" PERFORMER "Rage Against The Machine" ISRC USSM19502563 INDEX 01 00:00:00 TRACK 03 AUDIO TITLE "Vietnow" PERFORMER "Rage Against The Machine" ISRC USSM19502588 INDEX 00 03:49:52 FILE "03 Vietnow.flac" WAVE INDEX 01 00:00:00 TRACK 04 AUDIO TITLE "Revolver" PERFORMER "Rage Against The Machine" ISRC USSM19502589 INDEX 00 04:37:42 FILE "04 Revolver.flac" WAVE INDEX 01 00:00:00 TRACK 05 AUDIO TITLE "Snakecharmer" PERFORMER "Rage Against The Machine" ISRC USSM19502590 INDEX 00 05:29:18 FILE "05 Snakecharmer.flac" WAVE INDEX 01 00:00:00 TRACK 06 AUDIO TITLE "Tire Me" PERFORMER "Rage Against The Machine" ISRC USSM19502591 INDEX 00 03:54:43 FILE "06 Tire Me.flac" WAVE INDEX 01 00:00:00 TRACK 07 AUDIO TITLE "Down Rodeo" PERFORMER "Rage Against The Machine" ISRC USSM19502592 INDEX 00 02:57:73 FILE "07 Down Rodeo.flac" WAVE INDEX 01 00:00:00 FILE "08 Without A Face.flac" WAVE TRACK 08 AUDIO TITLE "Without A Face" PERFORMER "Rage Against The Machine" ISRC USSM19502594 INDEX 01 00:00:00 FILE "09 Wind Below.flac" WAVE TRACK 09 AUDIO TITLE "Wind Below" PERFORMER "Rage Against The Machine" ISRC USSM19502593 INDEX 01 00:00:00 FILE "10 Roll Right.flac" WAVE TRACK 10 AUDIO TITLE "Roll Right" PERFORMER "Rage Against The Machine" ISRC USSM19502595 INDEX 01 00:00:00 TRACK 11 AUDIO TITLE "Year Of Tha Boomerang" PERFORMER "Rage Against The Machine" ISRC USSM19502596 INDEX 00 04:20:38 FILE "11 Year Of Tha Boomerang.flac" WAVE INDEX 01 00:00:00' test.equal( playlist.parse.cue.full(cue_file), { tracks= [ { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "01 People Of The Sun.flac" } ) ], performer= "Rage Against The Machine", track_type="audio", position=1, title= "People Of The Sun", isrc="USSM19502587" }, { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "02 Bulls On Parade.flac" } ) ], performer= "Rage Against The Machine", track_type="audio", position=2, title= "Bulls On Parade", isrc="USSM19502563" }, { indexes= [ ( 0, { frames=52, seconds=49, minutes=3, file_type="wave", filename= "02 Bulls On Parade.flac" } ), ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "03 Vietnow.flac" } ) ], isrc="USSM19502588", title="Vietnow", position=3, track_type="audio", performer= "Rage Against The Machine" }, { indexes= [ ( 0, { frames=42, seconds=37, minutes=4, file_type="wave", filename= "03 Vietnow.flac" } ), ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "04 Revolver.flac" } ) ], isrc="USSM19502589", title="Revolver", position=4, track_type="audio", performer= "Rage Against The Machine" }, { indexes= [ ( 0, { frames=18, seconds=29, minutes=5, file_type="wave", filename= "04 Revolver.flac" } ), ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "05 Snakecharmer.flac" } ) ], isrc="USSM19502590", title="Snakecharmer", position=5, track_type="audio", performer= "Rage Against The Machine" }, { indexes= [ ( 0, { frames=43, seconds=54, minutes=3, file_type="wave", filename= "05 Snakecharmer.flac" } ), ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "06 Tire Me.flac" } ) ], isrc="USSM19502591", title= "Tire Me", position=6, track_type="audio", performer= "Rage Against The Machine" }, { indexes= [ ( 0, { frames=73, seconds=57, minutes=2, file_type="wave", filename= "06 Tire Me.flac" } ), ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "07 Down Rodeo.flac" } ) ], isrc="USSM19502592", title= "Down Rodeo", position=7, track_type="audio", performer= "Rage Against The Machine" }, { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "08 Without A Face.flac" } ) ], performer= "Rage Against The Machine", track_type="audio", position=8, title= "Without A Face", isrc="USSM19502594" }, { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "09 Wind Below.flac" } ) ], performer= "Rage Against The Machine", track_type="audio", position=9, title= "Wind Below", isrc="USSM19502593" }, { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "10 Roll Right.flac" } ) ], performer= "Rage Against The Machine", track_type="audio", position=10, title= "Roll Right", isrc="USSM19502595" }, { indexes= [ ( 0, { frames=38, seconds=20, minutes=4, file_type="wave", filename= "10 Roll Right.flac" } ), ( 1, { frames=0, seconds=0, minutes=0, file_type="wave", filename= "11 Year Of Tha Boomerang.flac" } ) ], isrc="USSM19502596", title= "Year Of Tha Boomerang", position=11, track_type="audio", performer= "Rage Against The Machine" } ], performer= "Rage Against The Machine", rem= [ ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("date", "1996"), ("genre", "Rap/Metal") ], title= "Evil Empire" } ) test.equal( playlist.parse.cue(cue_file), [ ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502587"), ( "title", "People Of The Sun" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "1"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "01 People Of The Sun.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502563"), ( "title", "Bulls On Parade" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "2"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "02 Bulls On Parade.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502588"), ("title", "Vietnow"), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "3"), ( "liq_index_zero_filename", "02 Bulls On Parade.flac" ), ("liq_index_zero", "229.693333333"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "03 Vietnow.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502589"), ("title", "Revolver"), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "4"), ( "liq_index_zero_filename", "03 Vietnow.flac" ), ("liq_index_zero", "277.56"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "04 Revolver.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502590"), ("title", "Snakecharmer"), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "5"), ( "liq_index_zero_filename", "04 Revolver.flac" ), ("liq_index_zero", "329.24"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "05 Snakecharmer.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502591"), ( "title", "Tire Me" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "6"), ( "liq_index_zero_filename", "05 Snakecharmer.flac" ), ("liq_index_zero", "234.573333333"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "06 Tire Me.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502592"), ( "title", "Down Rodeo" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "7"), ( "liq_index_zero_filename", "06 Tire Me.flac" ), ("liq_index_zero", "177.973333333"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "07 Down Rodeo.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502594"), ( "title", "Without A Face" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "8"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "08 Without A Face.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502593"), ( "title", "Wind Below" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "9"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "09 Wind Below.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502595"), ( "title", "Roll Right" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "10"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "10 Roll Right.flac" ), ( [ ( "album", "Evil Empire" ), ("isrc", "USSM19502596"), ( "title", "Year Of Tha Boomerang" ), ( "albumartist", "Rage Against The Machine" ), ( "artist", "Rage Against The Machine" ), ("tracknumber", "11"), ( "liq_index_zero_filename", "10 Roll Right.flac" ), ("liq_index_zero", "260.506666667"), ( "comment", "ExactAudioCopy v1.0b3" ), ("discid", "A30AEF0B"), ("year", "1996"), ("genre", "Rap/Metal") ], "11 Year Of Tha Boomerang.flac" ) ] ) cue_file = 'FILE "/home/radio/firepit/StandardTime.mp3" MP3 TRACK 01 AUDIO TITLE "Moonlight Serenade" PERFORMER "Big Bands In Hi-Fi" REM ALBUM "Volume 1 : Let\'s Dance - (Disc2" INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "The Laughs are on me from Frank Sinatra Balids" PERFORMER "frank sinatra" INDEX 01 03:42:00 TRACK 03 AUDIO TITLE "It Was A Very Good Year" PERFORMER "Frank Sinatra" REM ALBUM "Anthology - Frank Sinatra: Collection" INDEX 01 07:09:00 TRACK 04 AUDIO TITLE "It Was A Very Good Year" PERFORMER "" REM ALBUM "The Capitol Years (Disc 2)" INDEX 01 12:07:00 TRACK 05 AUDIO TITLE "I\'m Old Fashioned (Kern-Mercer)" PERFORMER "Marian McPartland" REM ALBUM "Personal Choice" INDEX 01 15:42:00 TRACK 06 AUDIO TITLE "You Took Advantage Of Me" PERFORMER "Rosemary Clooney" REM ALBUM "Rosie Solves The Swingin\' Riddle! (Vinyl)" INDEX 01 19:16:00 TRACK 07 AUDIO TITLE "Pennies from Heaven" PERFORMER "Rosemary Clooney" REM ALBUM "From Bing to Billie" INDEX 01 22:12:00 TRACK 08 AUDIO TITLE "Getting Some Fun Out Of Life" PERFORMER "Billie Holiday" REM ALBUM "Live - Billie Holiday: 1936-1949 - Just Jazz with Ed Beach" INDEX 01 26:42:00 TRACK 09 AUDIO TITLE "It\'s Been A Long, Long Time" PERFORMER "Bing Crosby" REM ALBUM "The Definitive Collection" INDEX 01 29:47:00 TRACK 10 AUDIO TITLE "Gone Fishin\' (Duet With Bing Crosby)" PERFORMER "Louis Armstrong" REM ALBUM "The Definitive Collection" INDEX 01 32:43:00 TRACK 11 AUDIO TITLE "Moonglow" PERFORMER "Diana Krall" REM ALBUM "Turn Up The Quiet" INDEX 01 35:53:00 TRACK 12 AUDIO TITLE "Hello, Young Lovers" PERFORMER "Perry Como" INDEX 01 41:39:00 TRACK 13 AUDIO TITLE "I\'d be but a slave to you perry como" PERFORMER "Perry Como" INDEX 01 44:16:00 TRACK 14 AUDIO TITLE "It Isn\'t Fair" PERFORMER "Dinah Washington" REM ALBUM "The Dinah Washington Story" INDEX 01 47:36:00 TRACK 15 AUDIO TITLE "Mona Lisa" PERFORMER "Tommy Emmanuel" REM ALBUM "Endless Road" INDEX 01 51:07:00 TRACK 16 AUDIO TITLE "Don\'t Explain" PERFORMER "June Christy" REM ALBUM "Anthology - VA: Lady Sings The Blues" INDEX 01 55:04:00 TRACK 17 AUDIO TITLE "Milord" PERFORMER "Bobby Darin" REM ALBUM "The Bobby Darin Collection (Disc 2) - The Pop Years - Part One" INDEX 01 57:48:00 TRACK 18 AUDIO TITLE "A Fine Romance" PERFORMER "Fred Astaire" REM ALBUM "Steppin\'Out: Astaire Sings" INDEX 01 59:49:00 TRACK 19 AUDIO TITLE "Dreamer" PERFORMER "Stacey Kent" REM ALBUM "Dreamer In Concert" INDEX 01 63:58:00 TRACK 20 AUDIO TITLE "Jardin D\'hiver" PERFORMER "Stacey Kent" REM ALBUM "Dreamer In Concert" INDEX 01 69:04:00 TRACK 21 AUDIO TITLE "Maybe This Time [From Cabaret]" PERFORMER "Tony Bennett" REM ALBUM "Forty Years: The Artistry of Tony Bennett Disc 4" INDEX 01 73:30:00 TRACK 22 AUDIO TITLE "If I Ruled The World 3:00" PERFORMER "Tony Bennett" REM ALBUM "Very Best of" INDEX 01 77:33:00 TRACK 23 AUDIO TITLE "Be Careful It\'s My Heart" PERFORMER "Tommy Dorsey" REM ALBUM "Sentimental Memories With Vocals By Frank Sinatra" INDEX 01 80:49:00 TRACK 24 AUDIO TITLE "You Made Me Love You (I Didn\'t Want To Do It)" PERFORMER "Day, Doris" REM ALBUM "The Essential Love Songs" INDEX 01 83:55:00 TRACK 25 AUDIO TITLE "Li\'l Darlin\'" PERFORMER "Frank Capp" REM ALBUM "In a Hefti Bag" INDEX 01 86:41:00 TRACK 26 AUDIO TITLE "Standard Time" PERFORMER "" INDEX 01 91:37:00 TRACK 27 AUDIO TITLE "Soundtrack Of Your Life" PERFORMER "" INDEX 01 91:41:00 TRACK 28 AUDIO TITLE "Misty" PERFORMER "Johnny Mathis" REM ALBUM "The Essential Johnny Mathis Disc 1" INDEX 01 91:46:00 TRACK 29 AUDIO TITLE "Soundtrack Of Your Life" PERFORMER "" INDEX 01 95:19:00 TRACK 30 AUDIO TITLE "Standard Time" PERFORMER "" INDEX 01 95:25:00 TRACK 31 AUDIO TITLE "Don\'t worry bout me" PERFORMER "Tierny Sutton" INDEX 01 95:39:00 TRACK 32 AUDIO TITLE "What I Did For Love" PERFORMER "Steve Lawrence, Eydie Gorme" REM ALBUM "The Hits of Steve Lawrence & Eydie Gorme" INDEX 01 100:23:00 TRACK 33 AUDIO TITLE "Woodchopper\'s Ball" PERFORMER "Woody Herman" REM ALBUM "Woodchopper\'s Ball" INDEX 01 104:47:00 TRACK 34 AUDIO TITLE "There\'s No You" PERFORMER "Jo Stafford" REM ALBUM "Fools Rush In" INDEX 01 108:12:00 TRACK 35 AUDIO TITLE "It Looks Like Love" PERFORMER "Dean Martin" REM ALBUM "Essential Love Songs [+digital booklet]" INDEX 01 111:33:00 TRACK 36 AUDIO TITLE "12-sammy_davis_jr-too_close_for_comfort" PERFORMER "" REM ALBUM "The Reprise Years" INDEX 01 113:56:00 TRACK 37 AUDIO TITLE "La Vie En Rose" PERFORMER "Pete Fountain" REM ALBUM "A Touch Of Class" INDEX 01 117:19:00' test.equal( playlist.parse.cue.full(cue_file), { tracks= [ { indexes= [ ( 1, { frames=0, seconds=0, minutes=0, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Big Bands In Hi-Fi", track_type="audio", position=1, title= "Moonlight Serenade", album= "Volume 1 : Let's Dance - (Disc2" }, { indexes= [ ( 1, { frames=0, seconds=42, minutes=3, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "The Laughs are on me from Frank Sinatra Balids", position=2, track_type="audio", performer= "frank sinatra" }, { indexes= [ ( 1, { frames=0, seconds=9, minutes=7, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Frank Sinatra", track_type="audio", position=3, title= "It Was A Very Good Year", album= "Anthology - Frank Sinatra: Collection" }, { indexes= [ ( 1, { frames=0, seconds=7, minutes=12, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer="", track_type="audio", position=4, title= "It Was A Very Good Year", album= "The Capitol Years (Disc 2)" }, { indexes= [ ( 1, { frames=0, seconds=42, minutes=15, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Marian McPartland", track_type="audio", position=5, title= "I'm Old Fashioned (Kern-Mercer)", album= "Personal Choice" }, { indexes= [ ( 1, { frames=0, seconds=16, minutes=19, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Rosemary Clooney", track_type="audio", position=6, title= "You Took Advantage Of Me", album= "Rosie Solves The Swingin' Riddle! (Vinyl)" }, { indexes= [ ( 1, { frames=0, seconds=12, minutes=22, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Rosemary Clooney", track_type="audio", position=7, title= "Pennies from Heaven", album= "From Bing to Billie" }, { indexes= [ ( 1, { frames=0, seconds=42, minutes=26, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Billie Holiday", track_type="audio", position=8, title= "Getting Some Fun Out Of Life", album= "Live - Billie Holiday: 1936-1949 - Just Jazz with Ed Beach" }, { indexes= [ ( 1, { frames=0, seconds=47, minutes=29, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Bing Crosby", track_type="audio", position=9, title= "It's Been A Long, Long Time", album= "The Definitive Collection" }, { indexes= [ ( 1, { frames=0, seconds=43, minutes=32, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Louis Armstrong", track_type="audio", position=10, title= "Gone Fishin' (Duet With Bing Crosby)", album= "The Definitive Collection" }, { indexes= [ ( 1, { frames=0, seconds=53, minutes=35, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Diana Krall", track_type="audio", position=11, title="Moonglow", album= "Turn Up The Quiet" }, { indexes= [ ( 1, { frames=0, seconds=39, minutes=41, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "Hello, Young Lovers", position=12, track_type="audio", performer= "Perry Como" }, { indexes= [ ( 1, { frames=0, seconds=16, minutes=44, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "I'd be but a slave to you perry como", position=13, track_type="audio", performer= "Perry Como" }, { indexes= [ ( 1, { frames=0, seconds=36, minutes=47, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Dinah Washington", track_type="audio", position=14, title= "It Isn't Fair", album= "The Dinah Washington Story" }, { indexes= [ ( 1, { frames=0, seconds=7, minutes=51, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Tommy Emmanuel", track_type="audio", position=15, title= "Mona Lisa", album= "Endless Road" }, { indexes= [ ( 1, { frames=0, seconds=4, minutes=55, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "June Christy", track_type="audio", position=16, title= "Don't Explain", album= "Anthology - VA: Lady Sings The Blues" }, { indexes= [ ( 1, { frames=0, seconds=48, minutes=57, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Bobby Darin", track_type="audio", position=17, title="Milord", album= "The Bobby Darin Collection (Disc 2) - The Pop Years - Part One" }, { indexes= [ ( 1, { frames=0, seconds=49, minutes=59, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Fred Astaire", track_type="audio", position=18, title= "A Fine Romance", album= "Steppin'Out: Astaire Sings" }, { indexes= [ ( 1, { frames=0, seconds=58, minutes=63, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Stacey Kent", track_type="audio", position=19, title="Dreamer", album= "Dreamer In Concert" }, { indexes= [ ( 1, { frames=0, seconds=4, minutes=69, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Stacey Kent", track_type="audio", position=20, title= "Jardin D'hiver", album= "Dreamer In Concert" }, { indexes= [ ( 1, { frames=0, seconds=30, minutes=73, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Tony Bennett", track_type="audio", position=21, title= "Maybe This Time [From Cabaret]", album= "Forty Years: The Artistry of Tony Bennett Disc 4" }, { indexes= [ ( 1, { frames=0, seconds=33, minutes=77, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Tony Bennett", track_type="audio", position=22, title= "If I Ruled The World 3:00", album= "Very Best of" }, { indexes= [ ( 1, { frames=0, seconds=49, minutes=80, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Tommy Dorsey", track_type="audio", position=23, title= "Be Careful It's My Heart", album= "Sentimental Memories With Vocals By Frank Sinatra" }, { indexes= [ ( 1, { frames=0, seconds=55, minutes=83, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Day, Doris", track_type="audio", position=24, title= "You Made Me Love You (I Didn't Want To Do It)", album= "The Essential Love Songs" }, { indexes= [ ( 1, { frames=0, seconds=41, minutes=86, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Frank Capp", track_type="audio", position=25, title= "Li'l Darlin'", album= "In a Hefti Bag" }, { indexes= [ ( 1, { frames=0, seconds=37, minutes=91, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "Standard Time", position=26, track_type="audio", performer="" }, { indexes= [ ( 1, { frames=0, seconds=41, minutes=91, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "Soundtrack Of Your Life", position=27, track_type="audio", performer="" }, { indexes= [ ( 1, { frames=0, seconds=46, minutes=91, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Johnny Mathis", track_type="audio", position=28, title="Misty", album= "The Essential Johnny Mathis Disc 1" }, { indexes= [ ( 1, { frames=0, seconds=19, minutes=95, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "Soundtrack Of Your Life", position=29, track_type="audio", performer="" }, { indexes= [ ( 1, { frames=0, seconds=25, minutes=95, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "Standard Time", position=30, track_type="audio", performer="" }, { indexes= [ ( 1, { frames=0, seconds=39, minutes=95, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], title= "Don't worry bout me", position=31, track_type="audio", performer= "Tierny Sutton" }, { indexes= [ ( 1, { frames=0, seconds=23, minutes=100, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Steve Lawrence, Eydie Gorme", track_type="audio", position=32, title= "What I Did For Love", album= "The Hits of Steve Lawrence & Eydie Gorme" }, { indexes= [ ( 1, { frames=0, seconds=47, minutes=104, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Woody Herman", track_type="audio", position=33, title= "Woodchopper's Ball", album= "Woodchopper's Ball" }, { indexes= [ ( 1, { frames=0, seconds=12, minutes=108, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Jo Stafford", track_type="audio", position=34, title= "There's No You", album= "Fools Rush In" }, { indexes= [ ( 1, { frames=0, seconds=33, minutes=111, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Dean Martin", track_type="audio", position=35, title= "It Looks Like Love", album= "Essential Love Songs [+digital booklet]" }, { indexes= [ ( 1, { frames=0, seconds=56, minutes=113, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer="", track_type="audio", position=36, title="12-sammy_davis_jr-too_close_for_comfort", album= "The Reprise Years" }, { indexes= [ ( 1, { frames=0, seconds=19, minutes=117, file_type="mp3", filename="/home/radio/firepit/StandardTime.mp3" } ) ], performer= "Pete Fountain", track_type="audio", position=37, title= "La Vie En Rose", album= "A Touch Of Class" } ], rem=[] } ) test.equal( playlist.parse.cue(cue_file), [ ( [ ("liq_cue_out", "222.0"), ( "album", "Volume 1 : Let's Dance - (Disc2" ), ( "title", "Moonlight Serenade" ), ( "artist", "Big Bands In Hi-Fi" ), ("tracknumber", "1") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "222.0"), ("liq_cue_out", "429.0"), ( "title", "The Laughs are on me from Frank Sinatra Balids" ), ( "artist", "frank sinatra" ), ("tracknumber", "2") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "429.0"), ("liq_cue_out", "727.0"), ( "album", "Anthology - Frank Sinatra: Collection" ), ( "title", "It Was A Very Good Year" ), ( "artist", "Frank Sinatra" ), ("tracknumber", "3") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "727.0"), ("liq_cue_out", "942.0"), ( "album", "The Capitol Years (Disc 2)" ), ( "title", "It Was A Very Good Year" ), ("artist", ""), ("tracknumber", "4") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "942.0"), ("liq_cue_out", "1156.0"), ( "album", "Personal Choice" ), ( "title", "I'm Old Fashioned (Kern-Mercer)" ), ( "artist", "Marian McPartland" ), ("tracknumber", "5") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "1156.0"), ("liq_cue_out", "1332.0"), ( "album", "Rosie Solves The Swingin' Riddle! (Vinyl)" ), ( "title", "You Took Advantage Of Me" ), ( "artist", "Rosemary Clooney" ), ("tracknumber", "6") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "1332.0"), ("liq_cue_out", "1602.0"), ( "album", "From Bing to Billie" ), ( "title", "Pennies from Heaven" ), ( "artist", "Rosemary Clooney" ), ("tracknumber", "7") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "1602.0"), ("liq_cue_out", "1787.0"), ( "album", "Live - Billie Holiday: 1936-1949 - Just Jazz with Ed Beach" ), ( "title", "Getting Some Fun Out Of Life" ), ( "artist", "Billie Holiday" ), ("tracknumber", "8") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "1787.0"), ("liq_cue_out", "1963.0"), ( "album", "The Definitive Collection" ), ( "title", "It's Been A Long, Long Time" ), ( "artist", "Bing Crosby" ), ("tracknumber", "9") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "1963.0"), ("liq_cue_out", "2153.0"), ( "album", "The Definitive Collection" ), ( "title", "Gone Fishin' (Duet With Bing Crosby)" ), ( "artist", "Louis Armstrong" ), ("tracknumber", "10") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "2153.0"), ("liq_cue_out", "2499.0"), ( "album", "Turn Up The Quiet" ), ("title", "Moonglow"), ( "artist", "Diana Krall" ), ("tracknumber", "11") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "2499.0"), ("liq_cue_out", "2656.0"), ( "title", "Hello, Young Lovers" ), ( "artist", "Perry Como" ), ("tracknumber", "12") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "2656.0"), ("liq_cue_out", "2856.0"), ( "title", "I'd be but a slave to you perry como" ), ( "artist", "Perry Como" ), ("tracknumber", "13") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "2856.0"), ("liq_cue_out", "3067.0"), ( "album", "The Dinah Washington Story" ), ( "title", "It Isn't Fair" ), ( "artist", "Dinah Washington" ), ("tracknumber", "14") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "3067.0"), ("liq_cue_out", "3304.0"), ( "album", "Endless Road" ), ( "title", "Mona Lisa" ), ( "artist", "Tommy Emmanuel" ), ("tracknumber", "15") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "3304.0"), ("liq_cue_out", "3468.0"), ( "album", "Anthology - VA: Lady Sings The Blues" ), ( "title", "Don't Explain" ), ( "artist", "June Christy" ), ("tracknumber", "16") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "3468.0"), ("liq_cue_out", "3589.0"), ( "album", "The Bobby Darin Collection (Disc 2) - The Pop Years - Part One" ), ("title", "Milord"), ( "artist", "Bobby Darin" ), ("tracknumber", "17") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "3589.0"), ("liq_cue_out", "3838.0"), ( "album", "Steppin'Out: Astaire Sings" ), ( "title", "A Fine Romance" ), ( "artist", "Fred Astaire" ), ("tracknumber", "18") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "3838.0"), ("liq_cue_out", "4144.0"), ( "album", "Dreamer In Concert" ), ("title", "Dreamer"), ( "artist", "Stacey Kent" ), ("tracknumber", "19") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "4144.0"), ("liq_cue_out", "4410.0"), ( "album", "Dreamer In Concert" ), ( "title", "Jardin D'hiver" ), ( "artist", "Stacey Kent" ), ("tracknumber", "20") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "4410.0"), ("liq_cue_out", "4653.0"), ( "album", "Forty Years: The Artistry of Tony Bennett Disc 4" ), ( "title", "Maybe This Time [From Cabaret]" ), ( "artist", "Tony Bennett" ), ("tracknumber", "21") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "4653.0"), ("liq_cue_out", "4849.0"), ( "album", "Very Best of" ), ( "title", "If I Ruled The World 3:00" ), ( "artist", "Tony Bennett" ), ("tracknumber", "22") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "4849.0"), ("liq_cue_out", "5035.0"), ( "album", "Sentimental Memories With Vocals By Frank Sinatra" ), ( "title", "Be Careful It's My Heart" ), ( "artist", "Tommy Dorsey" ), ("tracknumber", "23") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5035.0"), ("liq_cue_out", "5201.0"), ( "album", "The Essential Love Songs" ), ( "title", "You Made Me Love You (I Didn't Want To Do It)" ), ( "artist", "Day, Doris" ), ("tracknumber", "24") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5201.0"), ("liq_cue_out", "5497.0"), ( "album", "In a Hefti Bag" ), ( "title", "Li'l Darlin'" ), ( "artist", "Frank Capp" ), ("tracknumber", "25") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5497.0"), ("liq_cue_out", "5501.0"), ( "title", "Standard Time" ), ("artist", ""), ("tracknumber", "26") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5501.0"), ("liq_cue_out", "5506.0"), ( "title", "Soundtrack Of Your Life" ), ("artist", ""), ("tracknumber", "27") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5506.0"), ("liq_cue_out", "5719.0"), ( "album", "The Essential Johnny Mathis Disc 1" ), ("title", "Misty"), ( "artist", "Johnny Mathis" ), ("tracknumber", "28") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5719.0"), ("liq_cue_out", "5725.0"), ( "title", "Soundtrack Of Your Life" ), ("artist", ""), ("tracknumber", "29") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5725.0"), ("liq_cue_out", "5739.0"), ( "title", "Standard Time" ), ("artist", ""), ("tracknumber", "30") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "5739.0"), ("liq_cue_out", "6023.0"), ( "title", "Don't worry bout me" ), ( "artist", "Tierny Sutton" ), ("tracknumber", "31") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "6023.0"), ("liq_cue_out", "6287.0"), ( "album", "The Hits of Steve Lawrence & Eydie Gorme" ), ( "title", "What I Did For Love" ), ( "artist", "Steve Lawrence, Eydie Gorme" ), ("tracknumber", "32") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "6287.0"), ("liq_cue_out", "6492.0"), ( "album", "Woodchopper's Ball" ), ( "title", "Woodchopper's Ball" ), ( "artist", "Woody Herman" ), ("tracknumber", "33") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "6492.0"), ("liq_cue_out", "6693.0"), ( "album", "Fools Rush In" ), ( "title", "There's No You" ), ( "artist", "Jo Stafford" ), ("tracknumber", "34") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "6693.0"), ("liq_cue_out", "6836.0"), ( "album", "Essential Love Songs [+digital booklet]" ), ( "title", "It Looks Like Love" ), ( "artist", "Dean Martin" ), ("tracknumber", "35") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "6836.0"), ("liq_cue_out", "7039.0"), ( "album", "The Reprise Years" ), ("title", "12-sammy_davis_jr-too_close_for_comfort"), ("artist", ""), ("tracknumber", "36") ], "/home/radio/firepit/StandardTime.mp3" ), ( [ ("liq_cue_in", "7039.0"), ( "album", "A Touch Of Class" ), ( "title", "La Vie En Rose" ), ( "artist", "Pete Fountain" ), ("tracknumber", "37") ], "/home/radio/firepit/StandardTime.mp3" ) ] ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/doc.liq000066400000000000000000000001771513273233300201010ustar00rootroot00000000000000a = () # My function # @param ~x The parameter. def a.b(~x) = x end def f() = ignore(a) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/dune000066400000000000000000000022351513273233300175000ustar00rootroot00000000000000; Regenerate using dune build @gendune --auto-promote (include dune.inc) (executable (name gen_dune) (libraries liquidsoap_build_tools) (modules gen_dune)) (rule (alias gendune) (deps (source_tree .)) (target dune.inc.gen) (action (with-stdout-to dune.inc.gen (run ./gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) (rule (deps write_cover.liq ../../src/bin/liquidsoap.exe (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/libs)) (target cover.png) (action (run ../../src/bin/liquidsoap.exe ./write_cover.liq -- %{target}))) (rule (alias citest) (deps cover.png) (target cover.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=5" -ac 2 -i ./cover.png -map 0 -map 1 -vcodec copy %{target}))) (rule (alias citest) (deps cover.png cover.mp3 test_cover.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} test_cover.liq liquidsoap %{test_liq} test_cover.liq))) liquidsoap-2.4.2/tests/language/dune.inc000066400000000000000000000350401513273233300202500ustar00rootroot00000000000000 (rule (alias citest) (package liquidsoap) (deps argsof.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} argsof.liq liquidsoap %{test_liq} argsof.liq))) (rule (alias citest) (package liquidsoap) (deps bool.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} bool.liq liquidsoap %{test_liq} bool.liq))) (rule (alias citest) (package liquidsoap) (deps comments.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} comments.liq liquidsoap %{test_liq} comments.liq))) (rule (alias citest) (package liquidsoap) (deps conversions.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} conversions.liq liquidsoap %{test_liq} conversions.liq))) (rule (alias citest) (package liquidsoap) (deps cron.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cron.liq liquidsoap %{test_liq} cron.liq))) (rule (alias citest) (package liquidsoap) (deps cue_test.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cue_test.liq liquidsoap %{test_liq} cue_test.liq))) (rule (alias citest) (package liquidsoap) (deps doc.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} doc.liq liquidsoap %{test_liq} doc.liq))) (rule (alias citest) (package liquidsoap) (deps encoders.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} encoders.liq liquidsoap %{test_liq} encoders.liq))) (rule (alias citest) (package liquidsoap) (deps error.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} error.liq liquidsoap %{test_liq} error.liq))) (rule (alias citest) (package liquidsoap) (deps eval.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} eval.liq liquidsoap %{test_liq} eval.liq))) (rule (alias citest) (package liquidsoap) (deps file.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} file.liq liquidsoap %{test_liq} file.liq))) (rule (alias citest) (package liquidsoap) (deps file.watch.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} file.watch.liq liquidsoap %{test_liq} file.watch.liq))) (rule (alias citest) (package liquidsoap) (deps file.watch2.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} file.watch2.liq liquidsoap %{test_liq} file.watch2.liq))) (rule (alias citest) (package liquidsoap) (deps file_protocol.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} file_protocol.liq liquidsoap %{test_liq} file_protocol.liq))) (rule (alias citest) (package liquidsoap) (deps functions.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} functions.liq liquidsoap %{test_liq} functions.liq))) (rule (alias citest) (package liquidsoap) (deps getter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} getter.liq liquidsoap %{test_liq} getter.liq))) (rule (alias citest) (package liquidsoap) (deps interactive.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} interactive.liq liquidsoap %{test_liq} interactive.liq))) (rule (alias citest) (package liquidsoap) (deps json.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} json.liq liquidsoap %{test_liq} json.liq))) (rule (alias citest) (package liquidsoap) (deps list.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} list.liq liquidsoap %{test_liq} list.liq))) (rule (alias citest) (package liquidsoap) (deps loop.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} loop.liq liquidsoap %{test_liq} loop.liq))) (rule (alias citest) (package liquidsoap) (deps math.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} math.liq liquidsoap %{test_liq} math.liq))) (rule (alias citest) (package liquidsoap) (deps mem_usage.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} mem_usage.liq liquidsoap %{test_liq} mem_usage.liq))) (rule (alias citest) (package liquidsoap) (deps metadata.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} metadata.liq liquidsoap %{test_liq} metadata.liq))) (rule (alias citest) (package liquidsoap) (deps metrics.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} metrics.liq liquidsoap %{test_liq} metrics.liq))) (rule (alias citest) (package liquidsoap) (deps null.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} null.liq liquidsoap %{test_liq} null.liq))) (rule (alias citest) (package liquidsoap) (deps number.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} number.liq liquidsoap %{test_liq} number.liq))) (rule (alias citest) (package liquidsoap) (deps osc.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} osc.liq liquidsoap %{test_liq} osc.liq))) (rule (alias citest) (package liquidsoap) (deps pattern.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} pattern.liq liquidsoap %{test_liq} pattern.liq))) (rule (alias citest) (package liquidsoap) (deps pp.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} pp.liq liquidsoap %{test_liq} pp.liq))) (rule (alias citest) (package liquidsoap) (deps predicate.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} predicate.liq liquidsoap %{test_liq} predicate.liq))) (rule (alias citest) (package liquidsoap) (deps process.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} process.liq liquidsoap %{test_liq} process.liq))) (rule (alias citest) (package liquidsoap) (deps rec.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} rec.liq liquidsoap %{test_liq} rec.liq))) (rule (alias citest) (package liquidsoap) (deps record.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} record.liq liquidsoap %{test_liq} record.liq))) (rule (alias citest) (package liquidsoap) (deps ref.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ref.liq liquidsoap %{test_liq} ref.liq))) (rule (alias citest) (package liquidsoap) (deps regexp.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} regexp.liq liquidsoap %{test_liq} regexp.liq))) (rule (alias citest) (package liquidsoap) (deps replaygain.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} replaygain.liq liquidsoap %{test_liq} replaygain.liq))) (rule (alias citest) (package liquidsoap) (deps socket.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} socket.liq liquidsoap %{test_liq} socket.liq))) (rule (alias citest) (package liquidsoap) (deps sqlite.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} sqlite.liq liquidsoap %{test_liq} sqlite.liq))) (rule (alias citest) (package liquidsoap) (deps stdlib.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} stdlib.liq liquidsoap %{test_liq} stdlib.liq))) (rule (alias citest) (package liquidsoap) (deps string.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} string.liq liquidsoap %{test_liq} string.liq))) (rule (alias citest) (package liquidsoap) (deps thread.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} thread.liq liquidsoap %{test_liq} thread.liq))) (rule (alias citest) (package liquidsoap) (deps time.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} time.liq liquidsoap %{test_liq} time.liq))) (rule (alias citest) (package liquidsoap) (deps type_errors.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} type_errors.liq liquidsoap %{test_liq} type_errors.liq))) (rule (alias citest) (package liquidsoap) (deps typing.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} typing.liq liquidsoap %{test_liq} typing.liq))) (rule (alias citest) (package liquidsoap) (deps url.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} url.liq liquidsoap %{test_liq} url.liq))) (rule (alias citest) (package liquidsoap) (deps various.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} various.liq liquidsoap %{test_liq} various.liq))) (rule (alias citest) (package liquidsoap) (deps xml_test.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} xml_test.liq liquidsoap %{test_liq} xml_test.liq))) (rule (alias citest) (package liquidsoap) (deps yaml.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} yaml.liq liquidsoap %{test_liq} yaml.liq))) liquidsoap-2.4.2/tests/language/encoders.liq000066400000000000000000000031251513273233300211320ustar00rootroot00000000000000def f() = def five_point_one(s) = if false then output.file(%ffmpeg(%audio(channel_layout = 5.1)), "", s) end end five_point_one((sine() : source(audio=pcm(5.1)))) try enc = %wav(samplesize = 123456) catch err do if err.kind != "encoder" or err.message != "invalid sample size" then test.fail() end end try let eval _ = "l = [%wav(channels=1), %wav(channels=2)]" test.fail() catch _ do () end try let eval _ = "l = [%mp3(stereo), %wav(channels=1)]" test.fail() catch _ do () end try let eval _ = "l = [(blank():source(audio=pcm(stereo))), \ (blank():source(audio=pcm(mono)))]" test.fail() catch _ do () end try let eval _ = "l = [(blank():source(audio=pcm(mono))), \ (blank():source(audio=pcm(stereo)))]" test.fail() catch _ do () end try let eval _ = "n = false ignore(%mp3(stereo=n))" test.fail() catch _ do () end try let eval _ = "n = false ignore(%mp3(mono=n))" test.fail() catch _ do () end try let eval _ = "n = 2 ignore(%mp3(channels=n))" test.fail() catch _ do () end try let eval _ = "n = 2 ignore(%ffmpeg(%audio(channels=n)))" test.fail() catch _ do () end try let eval _ = "n = 2 ignore(%ffmpeg(%audio(ac=n)))" test.fail() catch _ do () end ignore(%ffmpeg(metadata = [("bla", "blo"), ("title", "gni")])) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/error.liq000066400000000000000000000064041513273233300204640ustar00rootroot00000000000000def f() = # Works as expected with no errors ret = try 4 catch _ do 5 end test.equal(ret, 4) e = error.register("foo") # Works as expected with no errors ret = try 4 catch _ : [e] do 5 end test.equal(ret, 4) # Can report kind ret = try error.raise(e) "bla" catch err do err.kind end test.equal(ret, "foo") # Can report empty message ret = try error.raise(e) "bla" catch err do err.message end test.equal(ret, "") # Can report set message ret = try error.raise(e, "msg") "bla" catch err do err.message ?? "blo" end test.equal(ret, "msg") # Can report stack trace trace = try error.raise(e, "msg") [] catch err do err.trace end pos = string.concat( separator=", ", list.map(fun (pos) -> pos.to_string(), trace) ) test.equal(r/error.liq, line 58 char 4 - line 63 char 7/.test(pos), true) test.equal(r/thread.liq, line 13, char 11-14/.test(pos), true) e' = error.register("bla") test.equal(false, (e == e')) # Ignores errors when not in list ret = try try error.raise(e, "msg") "bla" catch _ : [e'] do "blo" end catch _ : [e] do "gni" end test.equal(ret, "gni") # Ignore errors when list is empty ret = try try error.raise(e, "msg") "bla" catch _ : [] do "blo" end catch _ : [e] do "gni" end test.equal(ret, "gni") # Catches error when in list ret = try try error.raise(e, "msg") "bla" catch _ : [e, e'] do "blo" end catch _ : [e] do "gni" end test.equal(ret, "blo") on_error = ref(error.register("dummy")) error.on_error(fun (e) -> on_error := error.methods(e)) try error.raise( e, "On done callback" ) catch _ : [e] do () end if (on_error()).kind != e.kind then test.fail() end if (on_error()).message != "On done callback" then test.fail() end def on_done() = test.pass() end # Works as expected with finally ret = ref(false) try () finally ret := true end test.equal(ret(), true) ret = ref(false) try () catch _ do () finally ret := true end test.equal(ret(), true) ret = ref(false) try error.raise(error.not_found) catch _ do () finally ret := true end test.equal(ret(), true) ret = ref(false) try error.raise(error.not_found) catch _ : [error.not_found] do () finally ret := true end test.equal(ret(), true) ret = ref(false) try try error.raise(error.not_found) catch _ : [error.not_found] do error.raise(error.failure) finally ret := true end catch _ : [error.failure] do () end test.equal(ret(), true) # Catches error def on_error(~backtrace, e) = print( "caught error #{e} from thread and backtrace:\n#{backtrace}" ) if error.methods(e).kind == "foo" then on_done() else test.fail() end end thread.on_error(e, on_error) thread.run( fun () -> error.raise( e, "Asynchronous error" ) ) end test.check(f) liquidsoap-2.4.2/tests/language/eval.liq000066400000000000000000000032111513273233300202530ustar00rootroot00000000000000#!../../liquidsoap ../test.liq count = ref(1) fail = ref(false) def echo(s) = print(s) if s != string(count()) then fail := true end count := count() + 1 () end def t(lbl, f) = if f() then echo(lbl) else echo( "fail #{lbl}" ) end end def f() = t("1", {1 == 1}) t("2", {1 + 1 == 2}) t("3", {(-1) + 2 == 1}) t("4", {(-1) + 2 <= 3 * 2}) t("5", {true}) t("6", {true and true}) t("7", {1 == 1 and 1 == 1}) t("8", {(1 == 1) and (1 == 1)}) t("9", {true and (-1) + 2 <= 3 * 2}) l = [("bla", ""), ("bli", "x"), ("blo", "xx"), ("blu", "xxx"), ("dix", "10")] echo(l["dix"]) t("11", {2 == list.length(r//.split(l["blo"]))}) echo("1#{1 + 1}") echo(string(int_of_float(float_of_string(default=13., "blah")))) f = fun (x) -> x # Checking that the following is not recursive: f = fun (x) -> f(x) echo(string(f(14))) t("15", {list.remove(2, [2]) == []}) t("16", {"bla" == (true ? "bla" : "foo" )}) t("17", {"foo" == (false ? "bla" : "foo" )}) # Generic eval let eval x = "{foo = 123, gni = \"aabbcc\"}" t("18", {x.foo == 123}) t("19", {x.gni == "aabbcc"}) # Eval with sources! let eval x = "output.dummy(id='bla', blank())" t("20", {x.id() == "bla"}) # Eval with patterns let eval {foo, gni, gni = [x, y]} = "{foo = 123, gni = [1,2]}" t("21", {foo == 123}) t("22", {gni == [1, 2]}) t("23", {x == 1}) t("24", {y == 2}) # Eval with type cast let eval ([x, y] : [int]) = "[123,456]" t("25", {(x, y) == (123, 456)}) # @ infix notation def f(x) = 2 * x end t("26", {2 @ f == 4}) if fail() then test.fail() else test.pass() end end test.check(f) liquidsoap-2.4.2/tests/language/file.liq000077500000000000000000000023051513273233300202510ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = try ignore(file.read("mqlskjdfdjnsi")) test.fail() catch _ : [error.file] do () end tmpdir = file.temp_dir("testfile") on_cleanup({file.rmdir(tmpdir)}) src_file = path.concat(tmpdir, "test-src-file") file.write(data="blablo", src_file) dst_file = path.concat(tmpdir, "test-dst-file") file.copy(src_file, dst_file) test.equal(file.exists(dst_file), true) tmpdir2 = file.temp_dir("testfile") on_cleanup({file.rmdir(tmpdir2)}) file.copy(recursive=true, tmpdir, tmpdir2) targetdir = path.concat(tmpdir2, path.basename(tmpdir)) test.equal(file.exists(path.concat(targetdir, "test-src-file")), true) non_existent = path.concat(tmpdir, "non-existent") dst_file2 = path.concat(tmpdir2, "another_dst") try file.copy(non_existent, dst_file2) test.fail() catch _ : [error.file] do () end dst_file3 = path.concat(tmpdir, "test-dst-file3") file.move(src_file, dst_file3) test.equal(file.exists(dst_file3), true) file.touch("test-dst-file4") test.equal(file.exists("test-dst-file4"), true) file.mkdir(parents=true, "/tmp/a/b/c/d") test.equal(file.exists("/tmp/a/b/c/d"), true) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/file.watch.liq000066400000000000000000000005561513273233300213610ustar00rootroot00000000000000uname_s = string.trim( process.run( "uname -s" ).stdout ) if uname_s == "Darwin" then test.skip() end success = ref(false) def f() = fname = "/tmp/fw" file.write(data="abc", fname) file.watch(fname, {success := true}) file.write(data="xxx", fname) thread.delay(1.) if success() then test.pass() else test.fail() end end test.check(f) liquidsoap-2.4.2/tests/language/file.watch2.liq000066400000000000000000000006031513273233300214340ustar00rootroot00000000000000success = ref(false) fname = file.temp("liq", "test") def cleanup() = file.remove(fname) end on_cleanup(cleanup) file.write(data="abc", fname) file.watch(fname, {success := true}) def write() = file.write(data="xxx", fname) end thread.run(delay=1., write) def check() = if success() then test.pass() else test.fail() end end thread.run(delay=3., check) output.dummy(blank()) liquidsoap-2.4.2/tests/language/file_protocol.liq000066400000000000000000000005371513273233300221740ustar00rootroot00000000000000def rlog(_) = () end tests = [ ("file:///path/to/file", "/path/to/file"), ("file:/path/to/file", "/path/to/file"), ( "file:/path/to/my%20file", "/path/to/my file" ) ] def f() = list.iter( fun ((t, t')) -> test.equal(file_protocol(rlog=rlog, maxtime=1., t), t'), tests ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/functions.liq000066400000000000000000000055331513273233300213450ustar00rootroot00000000000000def f() = def f(~(foo:int), ~(bar:string)="123", (x:source), (y:int)=123) = () end # Arguments with same name def f(x, x) = x end test.equal(f(2, 3), 3) # Free variables with same name x = 4 x = 5 def f() = x end test.equal(f(), 5) def recf() = () end recf() def replacesf() = () end replacesf() let replacesg = () x = replacesg let evalf = () x = evalf # Function application generalization def f() = fun (_) -> 1 end fn = f() ignore(fn(1)) ignore(fn("bla")) def f() = fun (x) -> {foo=x} end fn = f() test.equal(fn(1), {foo=1}) test.equal(fn("aabb"), {foo="aabb"}) def f() = fun (x) -> {foo=x.gni} end fn = f() test.equal(fn({gni=1}), {foo=1}) test.equal(fn({gni="aabb"}), {foo="aabb"}) # Ignored anonymous argument def f(_) = 123 end # f : ('a) -> int = fun (_) -> 123 # Typed anonymous argument def f((foo:int)) = foo end # f : (int) -> int = # Anonymous argument with default value def f(foo=123) = foo end # f : (?int) -> int = # Typed ignored anonymous argument def f((_:int)) = 123 end # f : (int) -> int = fun (_) -> 123 # Typed anonymous argument with default value def f((foo:int)=123) = foo end # f : (?int) -> int = # Ignored anonymous argument with default value def f(_=123) = 456 end # f : (?int) -> int = fun (_=123) -> 456 # Typed ignored anonymous argument with default value def f((_:int)=123) = 456 end # f : (?int) -> int = fun (_=123) -> 456 # Typed named argument def f(~(foo:int)) = foo end # f : (foo : int) -> int = # Named argument with rename def f(~foo:bla) = bla end # f : (foo : 'a) -> 'a = # Ignored named argument def f(~foo:_) = 123 end # f : (foo : 'a) -> int = fun (~foo=_) -> 123 # Named argument with default value def f(~foo=123) = foo end # f : (?foo : int) -> int = # Typed named argument with rename def f(~foo:(bla:int)) = bla end # f : (foo : int) -> int = # Typed named argument with default value def f(~(foo:int)=123) = foo end # f : (?foo : int) -> int = # Typed named argument with rename and default value def f(~foo:(bla:int)=123) = bla end # f : (?foo : int) -> int = # Typed ignored named argument with default value def f(~foo:(_:int)=123) = 456 end # f : (?foo : int) -> int = fun (~foo=123) -> 456 # Ignored argument with default value def f(~foo:_=123) = 456 end # f : (?foo : int) -> int = fun (~foo=123) -> 456 x = 123 def f({foo}, [a, b], ~x:{gni}) = test.equal(foo, 123) test.equal(a, "aaa") test.equal(b, "bbb") test.equal(x, 123) test.equal(gni, 3.14) end f({foo=123}, ["aaa", "bbb"], x={gni=3.14}) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/gen_dune.ml000066400000000000000000000012171513273233300207370ustar00rootroot00000000000000let excluded_files = ["write_cover.liq"; "test_cover.liq"] let () = let location = Sys.getcwd () in let tests = List.filter (fun f -> Filename.extension f = ".liq" && not (List.mem f excluded_files)) (Build_tools.read_files ~location "") in List.iter (fun test -> Printf.printf {| (rule (alias citest) (package liquidsoap) (deps %s ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) crontab_test_cases.json (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %%{run_test} %s liquidsoap %%{test_liq} %s))) |} test test test) tests liquidsoap-2.4.2/tests/language/getter.liq000066400000000000000000000030661513273233300206260ustar00rootroot00000000000000#!../../liquidsoap ../test.liq # Test getters, see #1181 def f(~x) = ignore(getter.get(x) + 2) end def g(~x=getter(2)) = x = getter.function(x) ignore(x() + 2) end def tests() = f(x=3) f(x={4}) g() g(x=3) g(x={4}) r = ref(5) f(x=r) g(x=r) r.set(r() + 2) test.equal(getter.get(getter(3)), 3) test.equal(getter.get(getter({3})), 3) test.equal(getter.get(getter.map(fun (x) -> 2 * x, getter(3))), 6) test.equal(getter.get(getter.map(fun (x) -> 2 * x, getter({3}))), 6) test.equal(getter.is_constant(getter(3)), true) test.equal(getter.is_constant(getter({3})), false) def gen = pos = ref(0) fun () -> begin if pos() == 3 then "" else ref.incr(pos) "foobar" end end end test.equal( string.getter.flush(separator=",", getter(gen)), "foobar,foobar,foobar" ) test.equal(string.getter.flush(getter("foobar")), "foobar") def gen = pos = ref(0) fun () -> begin if pos() == 3 then "" else ref.incr(pos) "foobar" end end end test.equal( string.getter.flush( separator="|", string.getter.concat([getter(""), getter(gen), getter("gno")]) ), "foobar|foobar|foobar|gno" ) # Check that memoize does memoize! count = ref(0) def f(_) = ref.incr(count) if count() > 1 then test.fail() end end g = getter("aabb") g = getter.map.memoize(f, g) getter.get(g) getter.get(g) test.pass() end test.check(tests) liquidsoap-2.4.2/tests/language/interactive.liq000066400000000000000000000012421513273233300216430ustar00rootroot00000000000000success = ref(true) file = "interactive.json" def f() = x = interactive.float("x", 2.) interactive.save(file) x.set(15.) test.equal(x(), 15.) interactive.load(file) test.equal(x(), 2.) try _ = interactive.float("bla", 0.) _ = interactive.float("bla", 0.) log.critical( "Did not detect double registration." ) success := false catch e do log.important( "Double registration detected: #{e}" ) end try x = interactive.float("xxx", 3.) x.remove() x.set(2.) success := false catch e do log.important( "Inexistent variable use detected: #{e}" ) end test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/json.liq000066400000000000000000000155501513273233300203060ustar00rootroot00000000000000# We test some ground values for json import/export. def test_parse_error(name, f, msg) = error_caught = ref(false) try print(f()) catch err : [error.json] do if err.kind != "json" then print( "parse error test #{name} failed: wrong error kind, got: #{err.kind}, \ expected: json" ) test.fail() end if err.message != msg then print( "parse error test #{name} failed: wrong error message, got: #{ err.message }, expected: #{msg}" ) test.fail() end error_caught := true end if not error_caught() then print( "parse error test #{name} failed: no error caught" ) test.fail() end end def f() = test.equal(json.stringify(()), '[]') test.equal(json.stringify("aa'bb"), "\"aa'bb\"") test.equal(json.stringify("a"), '"a"') test.equal(json.stringify("©"), '"©"') test.equal(json.stringify('"'), '"\\""') test.equal(json.stringify('\\'), '"\\\\"') test.equal(json.stringify(json5=true, infinity), 'Infinity') test.equal(json.stringify(json5=true, (0. - infinity)), '-Infinity') test.equal(json.stringify(json5=true, nan), 'NaN') let b = json.object() b.add("b", 1) s = json.stringify({a=null({a=1}), b=null(b)}) test.equal( s, "{ \"a\": { \"a\": 1 }, \"b\": { \"b\": 1 } }" ) data = "123" let json.parse (x : int) = data test.equal(x, 123) data = '{ "foo": 34.24, "gni gno": true, "nested": { "tuple": [123, 3.14, false], "list": [44.0, 55, 66.12], "nullable_list": [12.33, 23, "aabb"], "object_as_list": { "foo": 123, "gni": 456.0, "gno": 3.14 }, "arbitrary object key ✨": true }, "extra": "ignored" }' let json.parse (x : { foo: float, "gni gno" as gni_gno: bool, nested: { tuple: (_ * float), list: [float], nullable_list: [int?], object_as_list: [(string*float)] as json.object, "arbitrary object key ✨" as arbitrary_object_key: bool, not_present: bool? } } ) = data test.equal( x, { foo=34.24, gni_gno=true, nested= { tuple=(null, 3.14), list=[44., 55., 66.12], nullable_list=[null, 23, null], object_as_list=[("foo", 123.), ("gni", 456.0), ("gno", 3.14)], arbitrary_object_key=true, not_present=null } } ) # Pattern extraction with json parsing let json.parse { foo, nested = {tuple = (t1, t2, t3), nullable_list = [l1, ...tl]} } = data test.equal(foo, 34.24) test.equal(t1, 123) test.equal(t2, 3.14) test.equal(t3, false) test.equal(l1, null) test.equal(tl, [23, null]) let json.parse x = data ignore(x.foo + 1.0) let (x, y, _) = x.nested.tuple ignore(x + 1) ignore(y + 3.14) def failed_array() = data = "[]" let json.parse (val : {x: int}) = data print(val.x) end test_parse_error( "failed array parsing", failed_array, "Parsing error: json value cannot be parsed as type {x : int}" ) def failed_runtime() = let json.parse x = data ignore(x.foo + 1.0) let (x, _, _) = x.nested.tuple ignore(x ^ "foo") end test_parse_error( "failed runtime", failed_runtime, "Parsing error: json value cannot be parsed as type {nested: {tuple: \ (string,_,_), _}, _}" ) def nested_tuple() = let json.parse (x : { nested: { tuple: (int * float * int * bool), list: [float], nullable_list: [int?], object_as_list: [(string*float)] as json.object, "arbitrary object key ✨" as arbitrary_object_key: bool, not_present: bool? } } ) = data ignore(data) end test_parse_error( "nested tuple", nested_tuple, "Parsing error: json value cannot be parsed as type {nested: {tuple: \ (_,_,int,_), _}, _}" ) def nested_list() = let json.parse (x : { nested: { tuple: (int * float * bool), list: [int], nullable_list: [int?], object_as_list: [(string*float)] as json.object, "arbitrary object key ✨" as arbitrary_object_key: bool, not_present: bool? } } ) = data ignore(data) end test_parse_error( "nested list", nested_list, "Parsing error: json value cannot be parsed as type {nested: {list: [int], \ _}, _}" ) def nested_object() = let json.parse (x : { nested: { tuple: (int * float * bool), list: [float], nullable_list: [int], object_as_list: [(string*float)] as json.object, "arbitrary object key ✨" as arbitrary_object_key: bool, not_present: bool? } } ) = data ignore(data) end test_parse_error( "nested object", nested_object, 'Parsing error: json value cannot be parsed as type {nested: {nullable_list: \ [int], _}, _}' ) data = '{"aabbcc": 34, "ddeerr": 54 }' let json.parse (x : [(string*int)] as json.object) = data test.equal(list.assoc("aabbcc", x), 34) test.equal(list.assoc("ddeerr", x), 54) data = '{ "foo": 123 }' let json.parse (x : {foo: string}?) = data test.equal(x, null) data = '[ "gni", 123 ]' let json.parse (x : [int]?) = data test.equal(x, null) let json.parse (x : (string * int * bool)?) = data test.equal(x, null) data = '[ "gni", 123, "gno" ]' let json.parse (x : (string * int)) = data test.equal(x, ("gni", 123)) data = '{ "foo": { "gni": { "bla": 123 } } }' let json.parse x = data test.equal(x?.foo.gni?.bla, null(123)) data = '{ "foo": {} }' let json.parse x = data test.equal(x?.foo.gni?.bla, null) data = '{}' let json.parse x = data test.equal(x?.foo?.gni?.bla, null) # Test escaping of invalid utf8 strings. s = "S\x00e\x00k\x00e\x00r\x00n\x00u\x00p\x00 \ \x00K\x00i\x00n\x00g\x00e\x00r\x00p\x00\xE2\x00" test.equal( json.stringify(s), "\"S\\u0000e\\u0000k\\u0000e\\u0000r\\u0000n\\u0000u\\u0000p\\u0000 \ \\u0000K\\u0000i\\u0000n\\u0000g\\u0000e\\u0000r\\u0000p\\u0000\\uFFFD\"" ) j = json.object() j.add("foo", 1) j.add("bla", "bar") j.add("baz", 3.14) j.add("key_with_methods", "value".{method=123}) j.add("record", {a=1, b="ert"}) j.remove("foo") j = json.stringify(j) test.equal( j, '{ "record": { "a": 1, "b": "ert" }, "key_with_methods": "value", "bla": "bar", "baz": 3.14 }' ) e = ref(null) def f(data) = try d = json.stringify(data) ignore(d) catch err do e := err end end f(infinity) if not null.defined(e()) then test.fail() end test.equal(json.stringify(json.value(123)), "123") test.equal(json.stringify(json.value(true)), "true") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/list.liq000077500000000000000000000101571513273233300203110ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = test.equal(1::2::[3, 4], [1, 2, 3, 4]) test.equal(list.hd(default=0, []), 0) test.equal(list.hd([5, 6]), 5) test.equal( try list.hd([]) catch _ do 9 end, 9 ) test.equal(list.tl([]), []) test.equal(list.tl([4, 5, 6]), [5, 6]) test.equal(list.nth(default=1, [4], 5), 1) test.equal(list.nth([4, 5, 6, 7], 2), 6) test.equal(list.last(default=1, []), 1) test.equal(list.last([12, 5, 8]), 8) test.equal(list.length([]), 0) test.equal(list.length([1, 2, 3]), 3) test.equal(list.length(["a", "b"]), 2) test.equal(list.mem(1, [4, 5, 6]), false) test.equal(list.mem(5, [4, 5, 6]), true) test.equal(list.assoc.mem(1, [(4, "bla"), (5, "foo"), (6, "gni")]), false) test.equal(list.assoc.mem(5, [(4, "bla"), (5, "foo"), (6, "gni")]), true) test.equal(list.map(fun (x) -> 2 * x, [1, 2, 3]), [2, 4, 6]) test.equal(list.map(fun (x) -> 2. * x, [1., 2., 3.]), [2., 4., 6.]) test.equal(list.map.right(fun (x) -> 2 * x, [1, 2, 3]), [2, 4, 6]) test.equal(list.mapi(fun (i, x) -> i + x, [1, 1, 2]), [1, 2, 4]) test.equal(list.filter(fun (x) -> x mod 2 == 0, [1, 2, 3, 4, 5]), [2, 4]) test.equal(list.remove(1, [3, 1, 2, 1, 1]), [3, 2, 1, 1]) test.equal(list.remove(1, [3, 2]), [3, 2]) test.equal(list.append([1, 2], [3, 4, 5]), [1, 2, 3, 4, 5]) test.equal(list.rev([1, 2, 3]), [3, 2, 1]) test.equal(list.assoc(default=0, "", [("a", 1), ("b", 2)]), 0) test.equal(list.assoc("b", [("a", 1), ("b", 2)]), 2) test.equal(list.assoc.nullable("b", [("a", 1), ("b", 2)]), 2) test.equal(list.assoc.nullable("x", [("a", 1), ("b", 2)]), null) test.equal( list.assoc.remove("a", [("b", 2), ("a", 1), ("a", 3)]), [("b", 2), ("a", 3)] ) test.equal( list.assoc.remove.all("c", [("c", 1), ("b", 2), ("c", 2)]), [("b", 2)] ) test.equal( list.assoc.remove.all("c", [("a", 1), ("b", 2)]), [("a", 1), ("b", 2)] ) test.equal(list.for_all(fun (n) -> n > 0, []), true) test.equal(list.for_all(fun (n) -> n > 0, [1, 2, 3]), true) test.equal(list.for_all(fun (n) -> n > 0, [1, 0, 3]), false) test.equal(list.exists(fun (n) -> n > 0, [0, 2, 0]), true) test.equal(list.exists(fun (n) -> n > 0, [0, 0, 0]), false) test.equal(list.fold(fun (x, y) -> x + y, 1, [2, 3, 4]), 10) test.equal(list.fold.right(fun (x, y) -> x + y, 1, [2, 3, 4]), 10) test.equal(list.prefix(2, [1, 2, 3]), [1, 2]) test.equal(list.prefix(3, [1]), [1]) test.equal(list.init(4, fun (x) -> 2 * x), [0, 2, 4, 6]) test.equal(list.index(fun (x) -> x == 0, [1, 2, 0, 3]), 2) test.equal(list.index(fun (x) -> x == 0, [1, 2, 3]), 3) test.equal(list.insert(0, 1, []), [1]) test.equal(list.insert(0, 1, [2, 3, 4]), [1, 2, 3, 4]) test.equal(list.insert(1, 2, [1, 3, 4]), [1, 2, 3, 4]) test.equal(list.insert(4, 5, [1, 2, 3, 4]), [1, 2, 3, 4, 5]) test.equal(list.flatten([[1, 2], [3, 4]]), [1, 2, 3, 4]) def even(x) = (x / 2) * 2 == x end test.equal(list.find(default=42, even, [1, 3, 5]), 42) test.equal(list.find(even, [1, 3, 4, 5, 6]), 4) test.equal(list.sort.natural(["b", "c", "a"]), ["a", "b", "c"]) l = list.indexed(["a", "b", "c"]) test.equal(l[1], "b") l = [(1, "a"), (2, "b"), (3, "c")] test.equal(l[2], "b") test.equal(l[27], "") test.equal([1, ...[2, 3, 4], ...[5, 6], 7], [1, 2, 3, 4, 5, 6, 7]) let [x, y] = [1, 2, 3] test.equal(x, 1) test.equal(y, 2) let [..._, x, y] = [1, 2, 3] test.equal(x, 2) test.equal(y, 3) let [x, _, ...z] = [1, 2, 3] test.equal(x, 1) test.equal(z, [3]) # Ensure that common methods are kept and others are forgotten l = ["a".{b=true, k=0}, "b".{b=true, l=1}] test.equal(list.for_all(fun (x) -> x.b, l), true) _ = ["a".{b=true}, "c"] # There used to be a bug where [x] would loose all its methods. x = 1 let x.foo = "bla" _ = list.add(x, [2.{foo="gni"}]) try ignore(list.insert(23, 2, [1, 2, 3])) test.fail() catch err : [error.not_found] do test.equal(err.kind, "not_found") test.equal( err.message, "List should have at least 23 elemments" ) end # Test huge lists, see #2162. # l = list.init(100000, fun(i) -> i) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/loop.liq000066400000000000000000000006241513273233300203020ustar00rootroot00000000000000def f() = n = ref(1) while n() < 10 do n := n() * 2 end test.equal(n(), 16) n = ref(0) for i = 0 to 10 do n := n() + i end test.equal(n(), 55) n = ref(0) for i = list.iterator([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) do n := n() + i end test.equal(n(), 55) s = ref("") for i = list.iterator(["a", "b", "c"]) do s := s() ^ i end test.equal(s(), "abc") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/math.liq000066400000000000000000000010511513273233300202550ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def test_db_lin() = x = 5. test.almost_equal(dB_of_lin(lin_of_dB(x)), x) test.almost_equal(lin_of_dB(dB_of_lin(x)), x) y = -x test.equal(y, -5.) test.equal(y == -5., true) y = 1 + -2 test.equal(y, -1) def f() = 1 + -2 end test.equal(f(), -1) test.equal(nan == nan, false) test.equal(nan != nan, true) test.equal(1e4, 10000.0) test.equal(1e+4, 10000.0) test.equal(1e-4, 0.0001) test.equal(1.1e-4, 0.00011) test.equal(1.e-4, 0.0001) test.pass() end test.check(test_db_lin) liquidsoap-2.4.2/tests/language/mem_usage.liq000066400000000000000000000031641513273233300212750ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = mu = runtime.memory() print( "Memory usage:\n#{mu}" ) test.metric( category="memory", name="Memory usage: process private memory", value=float(mu.process_private_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: process swapped memory", value=float(mu.process_swapped_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: process physical memory", value=float(mu.process_physical_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: process virtual memory", value=float(mu.process_virtual_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: total physical memory", value=float(mu.total_physical_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: total virtual memory", value=float(mu.total_virtual_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: total used physical memory", value=float(mu.total_used_physical_memory) / (1024. * 1024.), unit="MiB", min=0. ) test.metric( category="memory", name="Memory usage: total used virtual memory", value=float(mu.total_used_virtual_memory) / (1024. * 1024.), unit="MiB", min=0. ) # See #2427 thread.run({ignore(runtime.memory())}) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/metadata.liq000066400000000000000000003173501513273233300211200ustar00rootroot00000000000000def f() = test.equal( metadata.cover.remove( [("a", "A"), ("apic", "XXX"), ("pic", "YYY"), ("b", "B"), ("cover", "YYY") ] ), [("a", "A"), ("b", "B")] ) apic_cover = string.base64.decode( "AGltYWdlL3BuZwADQWxidW0gY292ZXIAiVBORw0KGgoAAAANSUhEUgAAAIAAAACAEAIAAAAczCrfAAAACXBIWXMAAAAAAAAAAQCEeRdzAAACoklEQVR4nO3E4UYYAACF0StJkiRJkiRJkiRJkiRJkiTJli1btmzZsmXLli3atGnTpk2bNm3atEmSJEmSJEmSJEmSJEmSJEnS9h73Oz+O9N/VFbPvurxg9l0XZ8y+6/yE2XedHTH7rtMDZt91ssfsu453mH3X0Raz7zrcYPZdB2vMvmt/hdl37S0x+67dBWbftTPH7Lu2Z5h919YUs+/anGD2XRtjzL5rfYTZd60NMfuu1QFm37Xyh9l3Lf9i9l1LP5h91+I3Zt+18IXZd81/YvZdcx+YfdfsO2bfNfOG2XdNv2L2XVMtzL5rspnZd000Mfuu8UZm3zXWwOy7RuuZfddIHbPvGq5l9l1DNcy+a7Ca2XcNVDH7rv4KZt/1t4zZd/WVMPuu30XMvqu3gNl3/cxj9l09Ocy+63sWs+/qzmD2XV/TmH1XVwqz7/qcxOy7OhOYfdfHOGbf1RHD7LveRzH7rvYIZt/1NozZd7WFMPuu10HMvqs1gNl3tVwy+64X58y+q/mU2Xc9O2b2XU2HzL7ryT6z72rcZfZdj7aZfVfDJrPverDO7LvqV5l9171lZt9Vt8jsu+7MM/uu2llm33Vrmtl31Uwy+64b48y+q3qU2XddG2b2XVWDzL6rsp/Zd1X0Mfuu8l5m31XWw+y7SruZfVdJF7PvKu5k9l1FHcy+q7Cd2XcVtDH7rvxWZt+V95LZd+U+Z/ZdOU+ZfVf2Y2bflfWQ2Xdl3mf2XRl3mX1X+m1m35V2k9l3pV5n9l0plcy+K7mc2XcllTL7rsRiZt+VUMjsu+LzmX1XXC6z74rNZvZdMZnMvis6ndl3RaUy+67IZGbfFZHI7LvC45l9V1gss+8KjWb2XSGRzL4rOJzZdwWFMvuuwGBm3xUQyOw7YOsfJwPDsnrI0ewAAAAASUVORK5CYII=" ) parse_apic_data = string.apic.parse(apic_cover) expected_apic_string_data = string.base64.decode( "iVBORw0KGgoAAAANSUhEUgAAAIAAAACAEAIAAAAczCrfAAAACXBIWXMAAAAAAAAAAQCEeRdzAAACoklEQVR4nO3E4UYYAACF0StJkiRJkiRJkiRJkiRJkiTJli1btmzZsmXLli3atGnTpk2bNm3atEmSJEmSJEmSJEmSJEmSJEnS9h73Oz+O9N/VFbPvurxg9l0XZ8y+6/yE2XedHTH7rtMDZt91ssfsu453mH3X0Raz7zrcYPZdB2vMvmt/hdl37S0x+67dBWbftTPH7Lu2Z5h919YUs+/anGD2XRtjzL5rfYTZd60NMfuu1QFm37Xyh9l3Lf9i9l1LP5h91+I3Zt+18IXZd81/YvZdcx+YfdfsO2bfNfOG2XdNv2L2XVMtzL5rspnZd000Mfuu8UZm3zXWwOy7RuuZfddIHbPvGq5l9l1DNcy+a7Ca2XcNVDH7rv4KZt/1t4zZd/WVMPuu30XMvqu3gNl3/cxj9l09Ocy+63sWs+/qzmD2XV/TmH1XVwqz7/qcxOy7OhOYfdfHOGbf1RHD7LveRzH7rvYIZt/1NozZd7WFMPuu10HMvqs1gNl3tVwy+64X58y+q/mU2Xc9O2b2XU2HzL7ryT6z72rcZfZdj7aZfVfDJrPverDO7LvqV5l9171lZt9Vt8jsu+7MM/uu2llm33Vrmtl31Uwy+64b48y+q3qU2XddG2b2XVWDzL6rsp/Zd1X0Mfuu8l5m31XWw+y7SruZfVdJF7PvKu5k9l1FHcy+q7Cd2XcVtDH7rvxWZt+V95LZd+U+Z/ZdOU+ZfVf2Y2bflfWQ2Xdl3mf2XRl3mX1X+m1m35V2k9l3pV5n9l0plcy+K7mc2XcllTL7rsRiZt+VUMjsu+LzmX1XXC6z74rNZvZdMZnMvis6ndl3RaUy+67IZGbfFZHI7LvC45l9V1gss+8KjWb2XSGRzL4rOJzZdwWFMvuuwGBm3xUQyOw7YOsfJwPDsnrI0ewAAAAASUVORK5CYII=" ) test.equal( parse_apic_data, expected_apic_string_data.{ description= "Album cover", picture_type=3, mime="image/png" } ) pic_cover = string.base64.decode( "AEpQRwAA/9j/4AAQSkZJRgABAQEASABIAAD/7RBSUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEAAQBIAAAAAQABOEJJTQQNAAAAAAAEAAAAeDhCSU0D8wAAAAAACAAAAAAAAAAAOEJJTQQKAAAAAAABAAA4QklNJxAAAAAAAAoAAQAAAAAAAAACOEJJTQP1AAAAAABIAC9mZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAABAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAAAAA4QklNBBQAAAAAAAQAAAABOEJJTQQMAAAAAA7BAAAAAQAAAHAAAABwAAABUAAAkwAAAA6lABgAAf/Y/+AAEEpGSUYAAQIBAEgASAAA/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAcABwAwEiAAIRAQMRAf/dAAQAB//EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8A3U8pDhMVnOgrsok9vxSJnjWeFQyes9Ox6L77LSasZza7nVtLoe/eGtbH0/dVY121I/ypbOcYiyW6XH5+CbyB/iqmX1vpWC6tt5te59Lckiqp1np0u+jkZGz+ar9yMes9La/KrFhLsKgZd0NkGktbZ6tB/wAN7LK/89RmUztEgHwa8uZkfljXmmaSOxP4J95g+0qkz6x9NsyqcRjMk25Oz0R6LocLNpZbun+a9/vs/MTWfWTpDHZAD7Lm4mlttdZdVO5lJZXcdrLXNfY36CH63x+xZ7+Xv+DdDhxMR4hO2Tqg9T6hhdMrrszNwF1npMDG7zujcXES3axrfpuVXM6107CzbMO43Vvp2eraKnOpZ6sel6lzJ9PdP7iMZTNenivt4L48xLqLdIREp1Tr6ni2ZtmBL/tFT3V2ew7A5g3ODrPoq2D/AKhSA22YzEhYUf8AcmEjzT902shJL//Q3jp3TE+CTu/inYJPwWbIgAkt6cxCJkei9TQHBz+G6n5ariMak3145Lg+vqGPlZljZ034v2/0nf2fWZ/bXdls6D4JNqqERWwAAtbDWiA76TW+36L/AM9qijlIskXbnyyykST1/B5DF6th4Yzrs6zY3qfSsc4pMkWllJxbMdj2/wCF9f8AwapOwcrHoyrgDXkdOw8SnJrPPoZNDqMhrx/wO6iz+wvQBUyGDY0NrO6tu0Q0/vMEfo3f1FUvzMmp2aGYYeKBV6T9jj6weaxdu2Nd6n2f1f8AX9L6UkcuvpjvV2f8FAn4OLTW+zqvRqWPix/RHsr1iN1Za13KzrMrGb9ST0dx9HPwSz7ThvBa5u3IbvuLXe1zXerX7l1d2ddUMl/2drfsz211n0rXkVk7fX3Y9T/Vqe33ejg+p6O/9YQLepZbjebengOqpx3j1a32Em01faMd3pVWuu+w+r72U+/9F/Nf6JCR002MTv1B/wDXiQfDt1cP6yZOP1i+unp7XdQqZh5N5sx9Qx1jhj1Xv37P0dLqLN356p5eS3MsuzbrCMK2jpj+p11lo3Uv9r3GxzXvYyrLbX9H/SLssW19tprfiDGHoVW6Ngh9km3He9rGVbqHf6Lf/wAJs/m0R1LGaBjdp00a0CP3XCEhkEQBw7ba9/UoSrSnnenWN/5xdWpdnAOsyrSOnBoIfDGfrQs+k30tu301sBxadjtQdGlGdVWHF4rY15/ODWh2v8sDcoPZuEfio5T1BHQALoZDE2NFJGFBpOoI1H5FKVKDYtvQkJRsP//R3ZJRWtgeJQ2cidFF+Re221rcdzhWx7mOE+9zAxzGfu/pnOc1iyMtk121ZOanZEe2pbI4Umj84/JUqMvLttqruxDS2zfNm6Q0M+jvlrNrrnfzf/qtXg5kfTbp/KH96YYkHVqrkho1WhjdJvsbuyHGlp4YIL/7U+2tLpOK2x32p/ua0xUORI5s/lbfzFr9lbwcuCBKfXaP8WSENLLTHS8Ac1Fx8XOcf+/KFvR8J7YrD6XDhzHE/wDRs3tV08pdlZ9rGRXBH7AycI7BwMnCvwz74sqJhtg4k9nN/McgESPiuisY17Sx4DmuEEHghc/ktZi3mp7w1vLC9wbLT/W/dVTPg4PVH5e37rHOFahruEHbyOxUHCFO66gMJFlZcAS0b2amJ26u/OWdb1G4Y3qCqt1u/b6e/wDN2b/U1O7+e/Rf6P8A8+KARJ2WhsuhrweJ5T6yR4cqrVlm0WtuFdRrIA/SAh+nvdS6Rvrb++rDLK3tBY9ryBDw1wdB8H7T7HJ+OwSPq2uXlR4e7//S3RVXYQLGNsA1Ac0OH3PBUm4uL/3HpHl6bP8AyCg6s2AsD317oO+s7XCDOjtVBuPk+t9O7aXQXC4aNBJDvTdX+79JZE74j6qVzBPuy+n5NgYuKXf0ek/9aZ/5BSOLhhpP2ajT/gq//IKlXj5z7B6j7qmFu3ey8F8SXTZX6fp7/f8ATZ/omLRrZtrDNxfpG55lxnxKaSR+lf1Yfq9B06plOFTXW0Ma1ohrQABOujWqz2VXptotw6nDsNp+I9pVo8LVFUKbLE8p+yY8p0VMCsbrdNT31PfWywiWje1roB92m8O/dWyVj9XsDr6qhyAXnyH0R/1Siz/zcvJEvlLmHFxv+49Pl+jZ/wCQVB+RjtyX4rMFtltfp+7ZW1pFsbdjnN/N3e9aZBg7dD28J81mijNcXvsbBcdwa28hky3irb7fbv8A8J/I/wAIqET3P4sIZ0DGvx672UsY21geGljJAcJ2u2tRGMY1vta1skzAAJjjdtCVAubWRcAC0w07txLR+e8/vJx/Nj4kpR+dnwfOP5dH/9PbsZa7b6dppM6uDWv5HH6Tcn9DL/7munxFNP8A5BS1/uU2ukfwWRl0N6a+AZOaiRIS/eH4hF9nzB/2vfP/ABNGn/gSn9lzf/LG3/tnG/8ASCKOFJpjTmO6YJHw/wAWLVttdIyn4T/Qyb3X13Oltz2sYQ4/4NzKW117XfmO2rfBkLlyGuaWvaHNcIcD3B5Raep9RwQGtrPUMYcM3NZeyPzQ+zbXkf2/Ts/4RXMHMCuGelbHoywmKovQ6ypdlin609PZ/SKM2h37pxLrf/BMNmTV/wBNKz6xtcz9Uwcm5x+ibWDGbr3d9qcy7/MoVn3IAWZCvNfY7ullZFWNS6654ZWwSXFc1kjJybnZH2q7H9QCKmNpcA0fR/pFF1m/9/3o1tmRkWCzLeHuaZZWwEVsP7zd3vsf/wAJZ/YZWokg6lU8+fi9Mfl7kfMxznegarsbJA1z8gk/yMb+GKoHFyYj7ffP9TH/APeZWSZO7t2USTxyB+VV+I+H2RWtR2PktE/bbj5FlH8MdGbVZUA19zrTAPvaxu2fzG+jXV/4IpwHvAd9AaujwCYnc4uP5xlPxaknt4Nnlo2b7ftf/9TeKdhh09j+VKE0DjnyWZKPEKb2SAnExP080xPdVuodQxen013ZW/ZbY2lvpt3ne4Ocz2A7vzPzP0iI15Gh47FRzMXDy6BXmNa+ljt53uLGgw5kuc11fs2WOb7/AGKACpVIHxpzZQMZVIbMcjqmLi4+RkX7/TxL/s1sNk+oCxv6Ns+9n6bdv/0fqJZXVcTF+2OtFnpdO2DKva0GtrrNkVMcXB1lldd1d921myur8/1P0aV+F065rq8msWNzbTca3PeW2Wit26ytofs/o7Xv/R/o/wDCprMbBtGSWsZe4tY3Jra8mTUWuq+01tftbY302fpLGeq+utOHDpof5fykjRK/qFdXT3dRcHtobT9oLSALAwt9TVjnNb6mx351ir4/W8HIfW1hewW4pzvUsDWNFLXPrc50v9R38y936FltPp+nZ636WtQq22Y+PhWY2zEyqxVU31g4OZ6fqsqofWfUs201/T9X+b/wqEasPHDm3YgxqxU6iwnIgfZn2uaXvra/e9lll9j/AFrP0jLrv51ECOtg301jsrRO3rWI6vDsDLQ3PANQLQHNm2nD25Dd/wCjey7JZv2+p7PVUX9YpbS66yjIDGZP2LaGNLjf9AM2std7H2/oGWf6VErwekPdjCttZNQOVisa92jXuZb67KvU/SY/rCq1nqM9Bl2z89F+x0hrmCr2Ov8AtThLv54OF3rc/S9VrX7P5tA8HY7/AIK0SvkEjQwY04UHQ1slOXRqe3KX0Tudq781p7fynf8AfU0Ak0F0IGRACxBa0s/Pdq/yH5rP/JKJUtdSTJPJKiRqrEYgCg6MICEQB/Iv/9XoImU0JwQNUiXduFnOgsQOFWz8V+VhXYgdAuAG4gnbDm2fm7XfmKySYTR96BANdxsVs8cZipD69XGd04VOYy7qZqa00Orbse0N9IPrd6DW3enW+z3f+7leUiWYePZn221dQbU/LvZksY2p27a11hZWy4Pbv95s/wCD/wBLU9axa08jUayour3Gd72nj2uLe86ppE+9/wCKGtPlpj5Txfg5GJg492HlYdfUjkXX1sDLHiya9SC+sWWtc1trLaqLa6bat/8Ah96fOpxszMfeM7bZZR9mNfok/o65us42u92RV9o/8Dr9RabccCN2ReQJhrn7gd0j/o7vYndiUvADrr3bRAIcB3nX3H3NQ9d7H/msfs5P3S472dMfZQftwZ6FeNW5oqsBecVzbqrHvn81p/Qsb9D1/wBLZd+j9MnTumOcKsmvMflBljXseRY2sNYbRZUK7bdnufZ/OWs/R+mtVlNTGgAFxBkOsJcf++tU3Oe4jcZ/gjUiKuh9JMkeXkdzw/ipoDNQdz/3uw/qg/8Afk3mnSToxERo2YY4wFR+3qtMlMQlxwkiuf/ZADhCSU0EBgAAAAAABwABAAAAAQEA/+IMWElDQ19QUk9GSUxFAAEBAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf///9sAQwADAgICAgIDAgICAwMDAwQGBAQEBAQIBgYFBgkICgoJCAkJCgwPDAoLDgsJCQ0RDQ4PEBAREAoMEhMSEBMPEBAQ/9sAQwEDAwMEAwQIBAQIEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ/8AAEQgBaAFoAwEiAAIRAQMRAf/EAB0AAAIDAQEBAQEAAAAAAAAAAAADBAUHBggCAQn/xABlEAABAgQDAwYHCQoJBQ4GAwADAAQCBQYTBxIjFCIzAQgyQkNTERVSYmNycxYhJDGCg5KishclNEFRYZOjwtIYJlRxkaGz4vBEgdHT8gknKDU2NzhIZHSxwcPjRUdVVljzZXaW/8QAGwEBAAIDAQEAAAAAAAAAAAAAAAMFAgQGAQf/xAA6EQABAgQDBAgEBQQDAQAAAAAAAgMBBBITBRExFCEiMiNBQlFSYZGhBjNxgRUkNFNiQ3Kx4YKSwfD/2gAMAwEAAhEDEQA/AN0GT0Pr3EXBi4faIuXPXSyXLnBXzQ+ijO18xMHcSxj7VA+zQEjT+Wlj4n7aEXLf/toBZLdy6NFxBB/TRwh2+IgC2MqOyRxB2ksndWd/qIBlwf76j+/c9omcMmrw0u5d0+HGProA1PBbJZ+bQRBB6ntFHuDEPVQDM/5ku4PUS7n5vlpZO97xAMGTUIXqIGS1c8viJfCHwUEuF0xhMS53aZnmcBlwhCDKghNPTDxNNA2bgtwRbI/PUcjMYiWtsD3kcfnrBcwhvnVA1lzbLfOqBMGQn6RDcn99LGRmMZBbYYnzUHTTNsb/AJFr7fLeIg/FZbxEgZBl9JGPqIG4/wBtR/GDcvYo2xn3P65Z7ez4h+LS/i9iw01HedmUpvXgtJY3jPtLw/oJemUlwcyN6hBZP/BS7Wy52iRGJS7naJA/qdRLHb1BII3J2RgkS7by3d2Mx7fdiUkItk6HkL5VZjLg/wB9MGS5c8hQ2/E8DnTTB8O5e7ReExI0xD0umPu0D4nGCl6aYP8As0ADtiJ3cY0D1CaQUXCFJ55FIB9dAL0+00/PUjTHbFZ9IljIO5aRqcVAHE/tEE0u+HGgnDRbuIA4n76M+ndTB2+z/fUcmoTwoAcaiW49KpBO68vpqG41LfZxoAcEtdsmW9NRyDu8Lppmp6bTQEgfDJ9e4hL7vjE9ohATLdrUGl3CFueWNA7aB6hLvD9mgAZNTVMmD1dVLJbuo2jsr1zzEAwhEaZdJLIQfEH2aXcIRAMud32aYThJemL5xLuaf2EAy5qfF8hLHqXLf00shB9ol7RqfsIAHbIQl03yEW9m0uojT7NDgg7ZCIAJ/aKOT4MO65+Wl7YQpNm4cfkIIPadVybc8hYPOob5lZGs/MMyyK1qyGbYMrfTDcuItk7Q1vtMg0D0h/BtODr21IbtyF1LKqnMU/aT6lDN4/8Asp+8SPc7Kz6lxLIRwUmobc8hWHi8f78CkDlbftNNVy3HneZRTP4k85zKKcbcgvm0bOQhPZroNjt+jSyNx9zuDXlGZpbQU+xx/lRsbj5a6C36C4l7PuJbQL5R7GThI2MgvZkV4NuO5a0UWxjJ5ikti+UZB/7Cj27S6DZx9klxt+9Ua2ySD5R6mpaMmN5g8ZcIxhwE6dsqmEbpZGZBcNG1LbJoPRgLJNG5fwn9xFsYhuCj6ZNSCAnTzpbhmTh2VHGMguGt1ueW15lhL4m632s/qXGzkHpXkOLltV45gS5a6EH1FYDeXbZRG9eDqKxYm2nfIvpfGGnOfcLHcIpA+09Il2xj1OHc7tM1LS3C1g7VoFz03DTLmmo9xuMf7aYO4gGalvSS+0J6TqJhPRoJpIAuaml0EWycQpku33vaIITU7xAFsZPnOml2+61EanaphCaluygI6ZbS7ZB8NFy784gGDHbuXEIbkHbt9T2SEBI7JMSx3Lndpg+JaJ2fQQBGljti4akXFHJbt6SAWQZCD0+mmXB+zSx3O+twJZLlvvEBI4n7CWTS7FHC0u86CCf2iAj3NO53iXtHZ/XTCcS2lkt8QprYx9O5uQDUwqpAhCalpLHct6qyurOcBJ2zcjmiZb46atHOzuXl3JAOP0Y+kX1+iryrMQHkkwjJiQ2CHPsQDwBJ0M8WXkWlPOPIpQynfHcUGJYxa4Gd8e87ghLvDCpDeXkLqLx3MOexWjJ43bCk9OjPHp5LRozb3veHweSvZkveDLJ283emCCDYoHbmPoQDzQZo1TTeHTTFKne0ci+487xrVnmSG7MYlIGMez/Ob68j4qc9CYEmDiSYUhaDag09vOKCONxHF1oM0EUEMMK5+j+epWkpcD92wQzpqQmsEYoIDNg5Opagh3vWW+3gcxRX7HmzLPbmzjRbGVcWTESXvsP3ld0s8C7gHLju21zszQweHKSBYPh/zvKom9aSeU1szlAJc7cwNHLkDWOCMebd8Pvx8vWWu1IPLQqlOmpGlhaz1YQdvV4iLg+07NcvipUkwonDueVTJDB2qWt9ogub8HTh/EvPeHfOYriu5HXD1yaXNPc9ISP20YGkGmaGODk37nmr1iQefTWnTQIaUtNR6oITtLPo0wfDXhcfO8xU2faXMykeT0cvgSx88TFi4Ns2nEuyE6dtoGP+fqdVbn4JMZVbifZl6nui4PhpeoPhKnJVkrklDt6xqyZCA1BLgO3Lkm5BvQQ9D1l5frjnoVBMphs2GVNtAS7+WTPfMT1IPDlWuxIPTHInQwQwtzlPXDchCj7tBHBPkLzHhvzgMTK/oOvJkU0jHMablzV22MBrub2a573hih6ECuOb3jRXGJFUPJRUDxocDRlG405fAHfzweSpF4a62hUVKhw6kkGF8XkehLgyjtfXRbtD9n10W+9WT84zFSoMLqXl7mkjNPHcye24IHAoDQWYYN/pedkWlLsbStKO8jb6RZqHaqOQY7nB4i814B84zEyv8RB0lW0ylEbU7c9mxL4G1w0MEUXJ4I/kdBa5jfWk8oDC+cVbTZmg5owsWb4r0G9HD1FtP4etp5LCst5JQttdB2Dxnd4Xy4FDtkGTS014rb88DnCfhLnxGeC5uQN5VBn/APDkWkYZ88QdQTwckr+Thabfcsv2gsmz7+XVg9/6qmfwGYa40ZR+hs0LPSA3gxE4O+Tpqwbkb27V65GTqLN8aK8mGG9Ps5s2C0JGd7Yjv7kCy9nzjJ49ISWy3ZBzG3csnaZ7mb4uSDLy/wBKwlW5miuOhZSU29L6adx6ct6f20sZCCIuDwfryrKtp94+rYMuG9G9jG2jYCyQEDkg5d/fiWgXB6d3tFYcFdNR00pNomeX0FjcXCd55iYQZCkJ6RLJ3iZcIL0aGyBPRIjR2mr0PIS+GS6gDUuEH84mEuE1UD1dVLJq3EAW9S4P6ajkHqXRGUzT+QPqKO4Hp6emgGaYkJdu4PjIQFgMdwaXb1NTTTOv5ijjH/sICRGjJ+dR9S5wVIuDtoBZB/Q+ul+iTLlwl0htwnQSyEtE4PD66AW4toJc/wD1pZNXtvUQMd0lvu+uvIxpCowaRnEP1hFy+LDjYcN6gIMN+2y34CdAkES7AbcfCF8tZvzoCbNg3OGwjW9vIBpHH0NyKPf5Pf8AKhVbCa2l9KEqyhnA43EcZvrsM6d/eeQ3jhv7n5w50WjUdgeQZckG9qQQQZc29oLZKoqQbnmbydzeMTaHo2EEG5BHpuY+XL9HkWX0XIyTfAvESbNg2/ET2VO4A2uoO6Mn9qo5J4N7hHJ6FIYI9kqJ3N9mILhwRBgt+9D51xdA4yhyn+MSsoufaJzbim9mGMlm3tbeMgTWs/Rj8HreWvWmMFWDknNrk5Ja8MSOZN5cwDGQWS5uanvfNrF8aJGSn29DsRWWm10yAkelHnzxGji/Fm3tRdZips8y5s+He23jgG5txxjyQcPPydb1FA/+Zi2pXiDiK6PqV/NPw3k9U1pMKknctC/BTwxkbQEFBHAR0ToR5PB1ci7jnoUXJy0mzrvYwjmjR7AwjMDcjcBJ0II+Xk8HRi5EvmRkG5ldYWtrJ8Na77gUGfoR/k6S7jnaWy4JvLvZzFjZ6m/nWs9MOwxBKKo6wga61/mKDE+bPVDiZYb4mUc5s7FLZUd+z1c/YxQxwe/8heb54zcbQ3bMTGAfsdLPH0FvnNnGO5iAMZtz3GPvU6q4/C+i/uiTSaU22CYj0kmfO2ern+Ehghi5B/Lh3fnFdMKQ265H6G/Bdtaj0hNK0HijzP5hP3Ly498XbBMrfT2oJoIY/pdJZnzK2EvLiBVDQrMJBnkMA44Ca1yC9+PMuLw7xE2HDvECgHN4AJtLtvYXC/5UGOHPBB68H9mtE5l5CfdAqgnX8TDz6UEHbLSea2eXehDvzgQLRbaUTOehTdLyRnSfiilZQ0jIR3+CNIGtzhbvLbgVhzO6Xped0fUDmZU3KDxjmsA4Nrl4TR5Mn5YuToxJnPgb3W9Hlc6kY9uyfqFecye2Wh6gc2bEY5qPc+Qtdbi44cnXXUjg5+XMr54mJDx7XDfDtib7108MZ3La7uEdE8v1IV2nNj5vdNuaTb1/X8haO45lqM5a4FnbNww5skfLB2ufz1h+Ojcn3YK0KUJr45ifW9D1PxL3xhuNn9z+l/Ft6x4mY2fUswrOdc2OUbS1uz6yR7o2U0Gb44U/TdJYT1ZMqbpuXMHUyZAYRmYNIAxkgiNDkg3Vh/MrGT7ok0KUxjwElRNbvN+D4/fXoTnUD/3j55b7wG/88sD5m5LWJE0Y9ccqJ+xyqOVjVIuXDFv9Oo9iEHp6XZ9deP8AnqTgZK0peSF14AS7a44Bl7w3g3/oL2IO4Xsc8C8B42TBnXePkwEIJj/fEEobbIXqD0un1outlWthCOlr7oGErzfQo6LnBKAxIkZXLO2eUzEG0wXeHvw+HciXrjnIM2b7B+pGzkNyAlgf66Bea+dhTfinFyebMEPwsYH8Gz9Pegy/sLfMQKg91vNP91P8rlTG9bLv54XMAo/f9eBWU+m4tmY84E7nMlfeZXzM27htWFQEKY2nKhjgudPJegXB85iRyuU4uThtJJaFhB8FcGCPcDeighi6HJw8/hzcqh0HUGJlPy+oKpw8nBpTYbD8ZW2gTR2YjQw8kXJyE5IujFH0oVeYH4XvMbK4eTeqKk/ACQO38ZN969gJHF0LnJy7u5vZ1uRRYcXMLVuyyyJlcEVKNM5ym0FwDod69DcPclt722x+Hl+tyJfNfpek6toN5NplIQnP4xtwXL2nudFWnPcbkJhvJ2wrI4yTkYw+j3F5npuaYsSmVuPcLOKtADafhgZTe1I97pjHurTlmtokqaqc4kaFcB74ldNyel2fi2QS0LQFy5kHn6f+dWjN4T8Gc9DyFx+C7iePsN5O5qTa/GNs4zbXnjNumj+OOJdI8H2i5hdcu8ri0iesPraXWhRcWx29L6akqmZzAnc3LneK0uDt3Rdp3itpWbRMHXyM+iZ4I7lAMZEvtP20DGmWyfI9GpyxC3pj8hHU7FF30KBkuE7uPqRoAt2uGlkIPtDJlvUSyWuEX6CALnodxCLhNPRue03EICwJbSyDGL/20wlu3aUe2O5qdBAHtUEuW+8R2feQJfd6P6RAMJ3nEUe3d7b5CZ6UpkDITTt6kZO7TQRjlvDZ7pPtpg24+EO8NSB90PUudNSBjHbVFOz1xdCNDjMaxa50LOnWA9MfnrC+eA4ILD+Vsf5XNe9ycOCLlW8aaw/nKUHXFfjp9tRsh28DTayOY7oYLccVrwcWOFMNo2hJzrEeNJx/N7p8c7wfxElGsSOZEjHnIWOOPPZii5P8QrznL5eOYzxmxcs7kEycjbx3O0zR/ET+le0ObfQ9WUTQ7yU1lLdkdEekyQXYI4LOTc6PLEsjw/5veJktxglc2qCmw+55pOo3cbnam2pBDmigJ4IeXrK9YnkIcerVDyJ23+NRI55Ev8UTSj9m1IPF0bCC4LcHbjhyZ1VuGbid8zfbWxjX6amtzi9SHdjh/WrVOcxhfXldOKbc0LLTOzy3ar0d0MFvNk+O6pHNvwzqilsP6gonEim9kBMnMdlmQoTXAxQZY+Xl5R8sSxRMttyyV1b4RPbvRp8omL80PESV0tiJMKOqB4Fh49ZDsxn3INpH0IPD6nKtM56FcSuW0PK6O8ZBJNJlMYHEYenGMI4Ovycnh6X4lm+KnMnqiWvCPcPP4wy7iQMyFgC6H5m9ulXP0PzO8VKgnG01IzDTbUn4S8digjc9X3oBra/KOOpnK/sbHRLXdqOw5u9PuC4b4oVbxIDyU8sDtHQJHCGMkfxfIVHzQyf79EvIINuAkufDgj9aCH8S9WN8O2dJYRvMPKFlu4OVOmjaAmSCNwYkEW+Tq78SyPm/4D4mUBiIzndWyFo0YkbnbmMCYBNbjig/Ny5olCieQ+l5dWuhFtCF1mL84zDv3AYuTATYJhy6ZffZhGPJBAOA0e+Hl8PkxrQOZfbJXlSaJs/iof8AbfJW0c4zBdxijTcvc0+EJ53KXGjATJBcDFxOTPEuf5t+DdeYZVRNJlVstC0au5dYDGN3AbfveHq8vKvFzaJiSVWrflkL9yXyq3nL88wYy+5cbnofC/2Fccy8ZCUXUjYprluYgz/Kg8O/4Fec5jCOvMSHFP8AuSk4Zk1YDPtNx0EO/FHB3nKrTmv4Z1hhnTc4ltYy0LA796M8Ft3AaDJk81Q3Efh1FW/uI1LRs/NvMD53mG5JJXg6tHLbkuqEdyMw8mmYMHgjgj9bpLSOa3jBKy0W3w8q2cNGj2S/BGBidBwGH8/R3OivQFaYf03iHTbylqkZ3GLvr9dvH1CD8PXXkOsOZniRJJgQlCbJPWsZLl7a9mdDy+XBFyww/RWTDjM5LpZeVlGGhIwtEwihassjbOcY4ZzfBerGMtmTR29aDaO3IQFgjjGG9B7/ACweHyVgfM/cS+W4gTgb140YNRyUmSNwWAO/nF8Xh5V2mGeBeKFP4Z4mSSZUfYmlQsmrdgEjoOcmWOK74d/7cSy95zU8dLnwagzDjHwYxzBn1fy6y3ZRlmyuXrhlnqStoRQpFR7InFaU+xp+eVJLZ9Ln8EiZHOaw7gNAOOGDNyckeXlX816bqiaSipG9Yy2ZC8YgJ4zCZwLPBnh+Ll5ff3l6UpPAfHCm8M60pIdN/fSe7C3DfdtoA7LDHFEXwZTReYuw5t+B9YYXVBOJ/WMtCA520DBnG3dwGtwZ8x4t3l3eHAo2Nnw5C+KCt4bcRLVcWZ5XrDECqMTZoOpKxeNDzEAxtM7RpA2gGGGOLl5Oj1t9bpRc4HN+ZvUjEhjEjYOTt8/mROQE5OX662DnKYb1Bith23ltLa83lsxA7bQOCwQQEg3oSQZ+Xl5Fm9F4L4uU3hnWlCkptoA85G02aDxqE0BIxm1d/PF2flKRc2zMtJXuTlGG4X0OpT1bzj+adI2dUe7ym5kYNibSGBpHbFuDuR5fx+R0lzeC9SOMKcWG8tnYdggI5JJZrBdgyZ8+Xkj+SRbRzb8F68wzqycPakZtG7WZS4YLw3YTb8McH5OXN3i5fHzm115XWIkwq2iZbLhtXdh3HH4wCGO9DBlj3Iv5l5tbTji2lq4YwFxFakVanUc8hncoOT3QmyDmvZ9MejF76h8zt4zlOHc0bFmTQEHjntHUEFzRh5Px5ekrTGig8UK6wrpeW+5sLioQObk1bAdByboYoc+cscKwt5zY8ZCD/wCR5iHGOMcEYJgz6HxeHk1lgyllyVtKchDfEIocRRUe3BuG774SIwTweWMueD+pR3jcZbiz/m90PUFCYbjkFSM9ke+MTuIw3YI9yKCDe3eWLyFolu4TzFzj7aG1qTrka3yllO4b2lIZzDsi3tTT9opDhv3uoqt437L66123LS60m/Lv5cfXAvLn0PLX6oTN4Mun9OBTO0ucTyF0Uu+iYRUddIT20o84agO52mp5aZb7roItkIj0QlmWIzs9JLSye2QMen3kaAYTtEJZLfFKhATCE/V92o5BkL6g1IJp/FvoHqDtoCP2iLgyJbjSuWu0QMfDQDNPs0xvcFw+ITTyKOMnttPp21YDb8MpVX4lN2kWkaxOdx3EtnRYRrEmDHw0zZ26B/o0EGqI4cjkuD1OuhuO5cTLZFIGPtSoQgO4PSRqamj8tA+J3iZqIAtuLikcFL4fpEwbe76izPSPc1NMO+pDNnqXSpgxj+WpHEXrZ4LGPsxKRs5BcUPEVHXAxlouoBE04CSp3vjLk7GJeX+bfMJhUlaUn7nzTwcxlrd2erTP3ei4bRRxWOQA+WPyd1WspK3GVLq0Npti4hS+49eW9TS1Lmoi3p6i8385Ag/uqUex8W1FNoDy496WyEscDlxBDHHFueDyV1DioJhQnNveTum5DN5E9Hcstptnjct4yGy5/fgh/wA26pNh4Erq5hY4Eq7zaCEtD1UcXVEsLoPAuRuabpuv3NeVEOfP7EzNMhzCDISOL3+UPg5foKZjgSaVTiJQ+FLapHkllFQ7W7mUbQuQzi38Q4E2VNVFX13C2iuiETaCEt8UJtRL+EcMQeH3a85uJG4wOxooum6SqSbnklYabyVP3d7o+9ngXP8AOcmlLyjExv7rTVFspKVjG2gkrqMMY3sRo4QR+Rl8pEyNbyUIVujDPPIksZK4VanqS44uaoUwm0CHcKE3zgsiyuk5fWDHm/kZV+8MeaeIXxI4xl1hhigj5RclzkzdRef5O4nlAYL0XjrT9bTwcxmUx2B/LXDq8ycBiMWHk0vMhgRmRiurj0jlDzCGK+0e1B6g9Ph+QjZ+zIHiLM+cBJ60qTDfYaFC7dutoGd4zYFyGcMsmoJV/NvnFHzam54Wj2dRSyOCa25lLZ06vGlzmGCGG38UMX095QbPHZ7tXWYW+Co0hwztaog7g0sez/5Tp9nnVxc5CW7f0Etwzuj9otQiKclwXE6HD00EGMno/ITNRsQmjcRpkGgFkt27TntNPzFHJ2f11IcXNotWUv5m4hKQ3AyankJfaaWn5CkOBjJxVHIPukMxez3dRV7xuS2S1qKwH3fX66WQeoQfXWBI24U47giEL1Bq0bkblH3lwdyBR3jcgtVR29wZNm6ikl3NmWWUpNrll3UlwRul6Y/XSyEIMY7feDz+p+NBCEJprokruorO4YcQ8hK09ZI4v/uJdv0P9SCEJbIVL1PXgXhIMJbtoSycQZfpoQFg4HpjtJY/YplziJfGQCycQd1Gp7RM7O71x9QiG9snYo45aQpfcRTEwiWQpxfUDe32Vnz41YDuE+bQ3GPTuqQNuPii6BFyrji3V1rPmU3MKmXlOq6xY+0ufQTO1R1/9WmDGMmqsDRC3w0ZPzpqVp3PRoBntUshBiuC66YQhLZEDb3SXVmBjcYykUwYxjS9QXCTPSoAto/Vo1Eamn3alBHnErHO5PMJI5MYcEybEaRxj6eQkGX3lwcjwHkdPuKXfSSpJu0dUvfAFzagzuGpDXeUJfN3yLRCE1EwZCXFsNvrb4EEtazg8SMH5PiJUEvqR9Uk8lLqWt4wNo5YWCCPe+Pf6SuJHRcvlFD+4l8Z3ULIg4xmjflzmcXI/D76vHDxu2H8JNxOgrOW09V1RDyySnHdkn+UH0Q/WW22madRSjOOXcSt3nEUdxiEr5tdJyiaS+ZFqSonDJg5G7Zy0jqDZhmhjzQfiXQYsYSU/irL5eObzKYy2Yy1zfYTJgXI6bx/zrbGeCNVkt7dOJc39nnNH+yrPkwJ5OXp1gb5tpB++t9OG4itdynfDzJrD6+I8wYb4ByOiak9207rCeVhUIx22zyblzxt4Pi3Ol1VcVpg/I67qRxUE7mTskBJCeQmZjFBkyEjiJeudKGOCPeXoNxgC4tfA613+rnYf6I1WP8ABiuGQyFlruXTLyILsYY/rcmVFyOJwXXTv8sjJbDvMYvSdD+5LD8mHhKqmMya7Mdo2eHFBAZuEkGW3yfH0PxLi6b5qlByl5JyzKfVRUMup4lyVSqZus7JvH8fvCW2TSn6oklzx/TcxaQD7YgoIw/pId1VY3g3Ixk4ns1VrempZakLzTVruyI8nm/uV9eUGzxDlbdk5qSbyU7R7tYXMsd2Y88P2kYb4byvDOVvGLGcTGZOpk9jfvH78uczg0Xvb/Krwbggh3SqwHbITg7nXUN9dFqrcR1rooGXBj4XZ9NFzTSyexR2mqH141ERi3DcZbflqnJ8BcfbVwTifYUdwMbknB3xoCPc1NPtFHuDHc7RLcDI2cDt6cHXg89SCcPVChKQ7l0ndpZPbW1IJ3vXGo7jsx8S5/jwoCHpiIpGmUaHHE0ku5/toZkdx2lvUjVW8bj1Bl+QrghNP2ihuG/ak9osDYbCXuLlu4Hh9NSMmpdVeMlol1WFz/HeK1kX+wdHg0xbXaX16ATh95B10u2O3/rEzl7NBCD7RWB04ahbn20JfokICQMY+F1EXBiIi3+sSyejQEi4pDdvd9TrqPbuKwG3J7RVWKP8qIfWJzHxHN8CWEfWIxu31O7Rw+IpFwlzZlHJxB3TKnOMBTB3LdtL2e2QfaJmoVZkIDJqIuI7PSS7mosAAx7Tq8RWA9JR247amrMCuzTUrtFIQC7ltLuEQ4uIGNw5cDYy1ntb0+mGAa2W21vLobJW27oshLY7jnTtrqKXw4qCqPhLy9KZdxIDEFrE9SBd3Q+GcukgxzKbWn80j6/UH5o/3loIxrr8NwD+pMehYsSVvnOapvDylKW+EspbfdfyxxrG/r6PyV0NzvExfC6ptptpFCE5FjCGQohEIXwsz0+0y4loXlAGXNNcNU+EtJz6ON42D4pek7ZpuQE9cfRXar4UT8s3MoodTmLZ52qih6gpIlx8G+y/ljfofL8lV9zhlGFelCDu6RVldcYbkuEm1NhCP+Us+886BcfiOAW6nJf0K5+U60HBjcdmQyZ2mkobcl0el2fTuKQ3IuYpindErgS+CpGmInmJepw/oICO8b3NTrjVOMhBEtk+gugIMdy0quYD0yF4caAj3LpLdlLJct+f1ExuQZUztNPpoSkcgyW+CoZB9rZ+QplzUUe3b9GhmLHp3NFLIO5poITUt953aZcHb9mhKU5Bk1FYD1GY9H0car3necNTJWTUIIvyF7LuWlm825bpWnqF6Yxj4w7aZ2nmdRMeXBE+3GgdsnFXRNaHcy7l1tK+8Oz2lCCIXhMMGPUu/UTLY7d0nU6iWMlxECBXCTG47vEVgPu1DZ3Cdtb8hWG0DYjI5cmsQAHcjjJ0BwQ/jXNPruvKifM8Sf2iZUrzFkHp3foI4vF6az9njJ43p+VzaU0fNn551MXTBmwGUMBiWYIoo4+XlLywww7vIu8ZOHDlu3cuZaZhGQY87YmTOOOLqR+BeLYW3zfQ0HCZbGVMJ3azeX4ySdyzbuXNKzdoB+N94qMcobLwzKCMhw8nKOOK3HlHH01D/hASOW02Sfz+lZvKYySqCfNmxChj22XkMIW0DKPliHuxF4fSUuwTHhI7Zpmz6mqgY9TSDw1yY8SJW+xI+522ZmIckqgme2XYLOSKDNBudLeXaMx2lrONra5wskfB+JZTNNL98o01DA/Lg9NBLaLemlkIMZLhVKZAQmmMQw3zk6AR9Mkf5FrmG9D+JB+MplqTQ49+Pu4PIgXP4X0ndJ7pHwd8g7baAnZwf3lrjeC2u8wPCdnQl5zWOnkXcoxbRWMHcT18dmvvP+ddObkd58JZExfCHpHQhCAF9peoJMgQBGl3ExL0xIBcajuB3RqQlkWBMZXiJRZLhKglIdcf4SEf+UwfvLPxk0xkXoSYDujWN1xKySScba2D8Fd9P2395cljeGIo2hv7lZNS/bKe4PhFTCE4aj/ZTPB6FceVQtwQf6y2hxbcj89Mueh3PLRxSaqAo7fwi13nUTI0TTUJdWdvMXG8kmlWMiSExAUnKo5mZ5d/CMuXOHk5PB56lbbW7yEpohNMfhUNwQep9RV9JzyoKgl5Hs7pvxLcIPZoBzAL25Bk/Ha6OVcnVmKhKbnFQXKPM7lFLtmh5w/A6gztwuOhHyAyav0lJsi66OsztnYEHqIGQfC4a4MmKEwFOCNn1E/eQdRe5aCajmEEfw2Lh/B/ByRZIlDZ40M3snpuZNpPbez16RhGzIX8Htx5Y+X4t7pjTZHfCSQQs0Ag7ty6q8ZNmeXepw1YPLhLmtktqrJwyCJ8hV6+E2Wy8campZ3Oulj9J9BMZuNpl/21HcDIUdrhroZRy42dZgzlxpSO4ZbuD9p07iED4l3iIWwXIwY/1aG9v5xM4Y7qq54znEyZ7NT8+DKXRCD+Eka3oMn44MnLyw9JHOQ1pxdthS/I6AdzslHqiXzCb03MJbKDNBvn49nzu8+TJF0+j5q4MdF40FH/AM9gRxk6cY6VbQdT3vByZ0N8P8fBkIQvOQ3CD6HuLYKkYlWq67kPc+bKRnHmgV7ej5g+l7htiRh6GdA90U1dhDKL2Rvcg6diKPkuDj6nkrvML5fUEpoOm5bU94cxaMhjcwE3488PU5eXkXLkofHC4PZsfmg4PSUgzj/bhTPcPj52XOKl2T0dCttP+bXW282h/wDqJ18yNf8AdA4+l8M6olJJfMnNNzc5HcmqNhHA4LGbxM6IaOJuYA4iZA7QOO1FbV5J8H2ctw3kdyQzd3PnbeRsJlt7qNyZuyC5bxOADuxxQhHBkj3IFce4PHjT/wCEsH5ugGGf6xokEw/xwtju85Y2cY//ALFlv762b9NPTJ9zzL+UDk8H8P68p+rJPNqtlrwjoY5wwM5Jk02w4Grdh4fXE3XoQdwXEWPjw/xwL/1kDDj68fuLYf6UwmGePnF/hUO9Pqe4VhB/4RrWfbZmV1rdT7kbjdfaga4mdnqrGx4V48cT+FRMR+RcpBnHv/pkz7leOly5/Cum/n/xQYQQZ/5s6g2Rn9xPuLCfFA1wmoO0pErl/juaN5QM24TUj9SFYwTCfG8dwv8ACmnfqe5Vgtv5tdH1RJJe8dVjXbur5idzHkfnaBbWww9SAYlZYdhjL8wnpIRy3xhDM2ZdhFfNmbxJ2Y2zcYhcNWg0tuPTTV9ATwl0ffJ8Xvr7XwjwrMRhmCWRMSyICORfq/CL9QmPzs9JMQmoQil8L7RGgI6+FIUdCYjEXJ1hJxzKXkbE7Qa7CNVc0GMo7a1nm7qLZ4eexktOCMnPHGS3H66mN3AxXBqhxqoGfzmpGjmQ4o1NSQCCjG5DKRNo4HEfUi5b44suXzVno8E8SP8A8p6+ydT73s1wM3hjMu8qC3YQ+0SkfYQ2vmyNguWx8b5aXc7tZIDBzExs4/6U1exwD7+XsI9z6C+PuM4l3LQ+dXiFk6n3vYfuLX2KW/fh6RNehHiNWcaoyejWHuKLrhlOJ4Ryzl1UMjyF2AzO1Gy8amI5jNZv3Issfn5ejuK2+4riPcufwqsQtPpwWm2p9RQSYSVxpthc5Wvh2+ma02zk/nUjbbLEdzsN/lEkbpR2i4wjpP3NzWrH0to/3JySZPWpJVKiWQ27bbKUtgXLEMWeNcnXmG84mVcTyuxyHbzsCSM8tCTgvYAxx7SG34ckW7Hm34ezV88wdr5y3/6TWIWfqR2mcH2YEfcVrPhk5zWJBI/L+B/1aa2IKbuqevQ37tImf3Pmk8P2Y6sqSragpu5MSVE6dypycscdtrEGDwFggz8sEMWa5vdJZ/K8M64ZO9p2P4Lbk5wsyZNNztIIn/v+HyGgl3RMGay0/wDhGYnE9oZt/q19fcjqS5qc4TETT691t/q0uoqV0sPSJMhxBoDi2Ql36Cq3Fy5wbdxcm3wfqC5q4+4iE1LmcjsMfR/J4YEfc3njZxdc4wVm7g8g5YOt/mVS+wz4/YkbNAkZB2yD+mghCDIsz+4/OJlMBkHjXiJLYwdCNhNYILnr+9y5l3FP0+4puV+JC1VPKhjG5jJt83LAZySCL38nLHDBDuwqwkG2YI4HM/LIvsGc6VSO+BaEt6dr5EaEsY/TIW2dOSOIPjXEwY+97MiNNDfVIoJ5zolFdjDluUV6Fg3HaUghLQ9XvEN9NLIPtRLmT5sGmXhmTBkJyE7yNA7YvUH0FIGMnFQhC2TiJZP0aZc09XtEsg9NAMbj1FM4ahs+8Ui2PhdRZmIyBM7JL7NM/VqUEd4S2MhPRrXMM2ewydmPr27kfylj8wHpjFxIyEgH9dbxSY7TMa6/4Xb5nPsXOGo4VKOsHw0xLGmLtCxBfa+F9oeRPhfC+0siHohCEID7TVG1OyTB3O1QDUqNCEB8JSao1tYEwuNQ3g1YEUNwgMjxMZ/Bxue4JAT/AMlxdy5xf1a0jERvdlby53ZFmbfoD8hcL8Tt9MlffAp57skz0pNSNR7g+5uXOhAmW9S1e9RFvsiLmSvF+1Ve84loqsCXCD9mqt5cuXeHcQEgdtLI4ukJaDbt9Bed5xWFeSnHgbFzUlRNL9XsZYzlRGsfikkmMzi5Yy58mSIt7z8y6SRyucT+i6wHVOIVRAjkNTPiGmspLA1NZbhg5bcHh5CQCg8xWa8PtITWqG+EI+psUGuEH3plH6+qsFhmGIzDB3C2ooa8mIDTqey481CTIYzxs8c5rcbiLehhgH5K9BEIS4S0on5XZoc2evseEcY7ZNT6aW4b3SfYjUzs7ahuNIa0dSVsXL9KYDuqQ80yEIL0f7Sht/wwd1WE0H8Mu942/bW5I85d4Sv8ykj225dT6CEwfDuIVydmMt9mhv2hPZoIMfFQ34hPSeiWhiP6cpcd/SK+sCw8YS8dwRZk0HYHtBrhYILcHl8vh6qkDJcHtLYwSAJqBt78CyvECi6kndQOHspZ6DuXAlLw12CC4yivkOODw8vSzwN4fnVHb0XXgyEI+DskBGUAzGJNcgSQRNmoxtIIOSPRtkG43lWtyiFIrryPn9s1gbhntmw6Iz279nr5M/gzZExu4bleElongSOgDgIYN3fHBF0IuXk+Qsfb4f1o2IMo5a7cPfE2wRxjnWSDIOa3dmz3IYtRpp8kakVJQ+JDmVzD3Ny2w6dyqXNAwEqCO8OyZ/Fx/DDcyX2/T+upESiF9uBCbBqEHwVDeTCXtiN2z14EEb8mzswkLBBG4jh9/JB5Sy+pKPrgkvnEy2yYjdO5zAeOAc1gzkl8LDLBAO4aAQsrrV5d5WleU3PKkk9LilJmjuYykcbjbNrgjtuoW2W74d25v+QsNjTw8Wp7bNEk7xnMme3S14F2AlzWblzwbvvfHyKY4mEvbPGctcmDtrscZGza7vkgh6fLycnm+FYvI6HrSUTCm2MpltuVtCNL0A5rBAFuFu/dF4faXRHF0e7S6fofEhsNmObM5i7ZMCTLOElQZHT2AkDfJASPPHBDmjGTfgyZvIFnU+yIh2oC2bgS5cTCEIThLn6PlcwltNs5a+CZoQYyaJJhG9jHBni5YIOU8XguZV0FzTWn2zwhk+DEbkL2jkf21vlJk+BjWBzQgxt7vXGQZPoxrcKHcXWY12nwx8lf1LuQ+X9ztBpiWNMXWG6HZr7gg3F8dmvtZnkT4SyJi+EPSMRfq+18ID7gTUpNQCs/5l8KQlICOlkUlKjQmI8ajuOEphFDccJYAzfEAn3ref8Ado/sLM2dwox/IWgYmPNmlbwno41wbe4UY+MPs8hOmuK+JudCPqVk9yJGEIMZCCTB6vpI0W7be6XT89L9nqQEXKFUHC0hKrmH4QMlniKwIMltcm8ryi3zwjFtXkj2oenHB4wDHGOPPb9/wcvl8qkgha+RIOIcUrhvN8UGdROcTXZzknMEzDT3jCDY/GzQMTe9BycsGeHLDn08+RPdkwklFLzySzbEIPivEJy+d54HWS5eyiPC3JB5ORQ5Hh3VH8V9mZmlsDQbRvPoCO4MjiNk8jNBpRBiJFmijJbJAWDi766D3N1Q5p+VySoGbSUytpOTzOcffDPBswzRuG4vegh6ReUWf2KtnF3KeOMcv/DYhD+RHqaWUHU0nk9N+PgtGtNT2XDDA0LntvW/AaE9bIu0JxNT5CzueSueTJnWhdjaDl1WOWJGcyaTCA0A4IYIBkcE8HJDahhtXbmZd54wlbkjcbaZBPfHcDqwR3IPLWm/nHtEpMJqju8RR3nQTG3+LaW84YxDWiIEdmT4YO101MmmmQfkW1DZjuvB/YVpNPBcHa7vfW5I85cYb+oSQ+zQl9pb4iFcnakjTIO1w0vUE40kwfD+2luB6je0tTEP08SqxlFyUUWjfUXJ4oU28n7OV2qbFUIGD2MjmVHLAGBxARsUPTi8iImZdY3ccO0HidRSO80dRc/LuW1VHzoyt5J8WLk0bSk0xBARtGNtGOawZMmRvyN4G+behjHkcZix9JTJOOvG1YTxzLQz2Zy6SvXYAhdzXcewbG2sBHATrXbhL60QdwfbbimDJp/YW1tn8YHhmeLkjmlSEcSRszCc82pmYyyVQEFo+MIo4IiZ4+iPOLylybfB+tHLhm5KzFLbb2ak+DlDBGML+NqIkHLAHKHgcjvhw9311vBCW7npEvT/AHFIidU2ihIuGPt6PnjGm2Y3MhsPfczJpKztlgjjHMwvLkHv8mbodPOtsJxCeQq+X3P0isPRLXW/dIhmompX6tM7VAR3g7rcg+8GtEwrnG0y9vd6dvf9dcH9pWGH8w8UVA4lpdOC5tAfUi/vLpvhmYtvKa74FlhrnMg9CNyaakKrl7i6NWA13RbDF9r4X2szyJ8Rr4X3GlkQ9Fo018IQH3AmpUCagBKjTUqNAfCVGmEUciEwuNV8wJaGrCNc3UDzZm5FgDI8VJhtOzyjW+HvRj09/ch1OX7Cr2Y/g9ovTUeaOCTarCF7Bg2twekjij99TCXBaq+e46/dm/7YZFZPucqBbi45GMXziXp9n8uNMubSNFzid4qQqhbi3b07yyeYUHMG08qR7LZa0g8bT6VPw28kGiG1fi83oLWLmnb8G4q9wO56PyF625aPELMjl9B4iNpgN8MxhkI9AS8Sfx5MkRnl2K14cvDjZ7ivJPS9cMsJyU/P5a0ns72m5AzfzDO240MUGcnWggi5M+RaJb2a35fkIcEIQlvu1sbWvwwJazJ2dH1pKabHKfc3Ln8A2x3cbB3MA7KSZxP797l5BDh3Otbh3OoodP4b1Q2qSRvpkzZjsOWj9482sMZh22xQxs+SAcHJDkjjJd3NyFaxc1Cdp56j2x7QQl5NtXxabyS4MJw+DbjUNxpDuqQQdzUUN5c4q0yRsJfqPLfUtqwmlvaLfqcPpqHIx/DCEs3FImAx+MCWumPT+jAtzDecv8G3zH0gR7g7ZEItkJb7NCuTryR7VV9QTyV03J3FQTc2Rkw1DR78cY4M+X/zVgMl0feIJbKO19RHEXEKIptFxhSO+Bn4+cRhGyb3CT52PzyS83+hDfnOYL27hakdjuf/AMU5j6XqwRLSGZCcVWDfaLem8NcIueuSngj6nzJ2CIGX/wAIzCPaLQp9MSRk6g5K5/8AODKmE5xmE7ZvtLmZTccHpJK5gufUWkfDBj1TbiZtDglu68MT51eXJfwR9f8ARrZp8JlZOdJguP4NtlREjuW8nufedOL82Rfv8JvCO43EI1UEjP0IPc08/r3Fre0OP5YbU9KjaHly4J4b9LGpbkn4I+v+hmnwmTj5ymFba4UvuoHAPp25A5jj/o5IE4nOuwlbjy2q3J5EA6ReZ/sLTnBHHE2w3d8VWDeYPCjH8MN+lWTT8l1tx9f9DNPhMb/hXYVlubNLa+jt93SLyPo/5l9E512F/ZSHEQkH/wDUHn8y2Ybxxc03hv0qZtjjhDeG/SrYvyX7Uf8At/oVo8JjP8KbDfZx8nubr7U04P4ovP8AQmSXnHUJOaslbeUyesmjpwTZ70ypty2bfLLFybq14ZHgyfhhvP1UTC45ZkbEMYgzjt8VTsTcpLrStDcc4fyJZd9Da6kpNcouoBvWY9Zdo3IvM9B1I4puaeIH3Q4jaPvIP7q9ASOaDfNxkEZfQJZ9Ey2laOsvYdKdJn/MhR8/5lIW0enwlOHA2w7pU1KjQC7gy6iLi/UID7TUpNQAlJqjoAUYiZGo8awBHeOLQ1l+IlSbE3Jb4nDgg89dhUk4GxbkWDzR4OragIUhvgTDoQd4b+6tDEptEmypXoHHLSKzK5Xjw8Ys9TAHGB2chI85h01HBATzlIcc4h4L/wCQOLf/APn41sH2Uu5a4a4Nybl3V1rbj6lK4+iPZ9zGyc5BwL/q64zeZ/Frf+2mN+cQQri0PAHGD1yUrk/8SLYB8QlwyPapflP2/cjrR4fcxv8AhCOC3BlwBxmBGTrkprJB/TcUMnOIbi4mD+J5CDtk/wCIPK+X1VsDxwNzpJnd6xlHflP2/eIrR4THx84RuVvtP3GcT9Pp3KfyRkzfk8JN5BOcI3GPafuP4n5Cacf3g3938fg8K2DUudt5iW4I4t/tptEt+37i4jwmLj5xDdyT4Ng/iUP2klyftpf8IBnpufuS4ia/U8Sx5x7nh31sBCE743rpYyOLmn0/3Vhflv2/ckbWjwmT/d8HtexDwlr7zI/FWj9PooHjJd1fub1d2g9Rp18/Q6y1B4TTHdN8hV+f8y1lvs+D3N1txHhOPb44ElBCCLg/iI7g4kblpJYzBH5nhXaM5p42Zs5sRmYG3j2iy43Ix3N7JH50K6SVj2GRkL3YyE8hUYxkVxKUUcCci+wNvjUsZctEtoR2n7CFunTkgdsXCCglvi94gfFQTiqIcwMyEETZh9mrS4O2qfT2gfpNP6KsG9vhrmptFt1ST5tizGzzCk+ZI4uoTUSx20zT9pGNLINa5XEgdv5aB3BcPUS+Lwt+4geqT9hCENnJctkUhmS0S2lkt/8AppbjSIO1+rSALC4NMtjUcZBlHqpmpxbPDWxAxGcT0aYPUJd4nnpeoXVKgbi4swQ55KxzcY23DOD8GMPs4/yq4w3xEcNpgSn52a29adODvIPL5FHtjuKnqSl/HbcZJabYJo01AubWf5EfmxK7wnFlya6F8sSwlJu3wL0PTErmjd6O6IyuBkXlfDvGQjKYEpuqfgE0aaZgk+1B5UK9CSOqGcyHpGXeNOoeRWjfAutTpLiWljcDKi4tgH6hCUvQNX2vhfaAEsi/VGcOBjQAQlpc3UFQN2QyXFDqStJfKW5ClMEawOpK4nGIk0JLafNbl0H4Y8/YH561ZuZRLIrWrKAccpF4mYiVJUkw9y9E6johLbl4Tgsg/l5fP8iBWkrl45azG2s7g0uRyNvJGY2LHTgH1yb8ZPzx8qsCEtcX/EC+eYliK8RXVpCGkClm5u5u6hZOIQSj2yF1e7RcukGRFslv9tVJokgY/m0shLQyIGT5y2q94T9YgDU1C8RSCDHbH9RRxjILidNA7ZSeYPoITDCONO3Z9Go5CXSW+H5CYMhNRR7d0hLn0EPGwJbLxdNGoLUKb5CBj1ODuEQRwPvkJiG8cd4o7ce0uBiTHji6TTUim2ZCOLhfkKKG9Ztt8hcTQg20vGxHe1On8lU4+0EpE8uOZhb6gPpqPy9n5a6eXRbQdzhMvalk+e8XcHct8NCNMRO8QsyxJHolH1Ezi98jZx2/PQA4IS3p9MakN3F3V7zoJep2hvUjUMYxiJpdAnQ9dVeIscFfcc58QSl1EHUdWpcD4eqmcnZqOMmnaL2fdplsly7rZCKmOItkgdziJlvtVHHpcPoKQhCBB+mRs/5/6kwaB20MSP8AgxPMJ01M2glvT6Cjxobk0/MWbYJm0EKi5ab95GljtlIS0a4pA9PiqUAMgxejTG5NT7CWl2yD7bhrMEeoKXp+rW+zT+Whd2+gbfgMPzoI4csUKz/xpiRhDMB+KJkaoZcTgsCf8YbvkdU/21pgyE7U24lzSTyudsyS2bMwu2pOoRb0piT0lxtK+3UWEpPLb4F6DMP+dZR8/J4tezLYHoNMzZ3omHH58EW8tsk9aSebjGRs8CS4vKdYYH0vVox7SEJ7GmGB/nNbgh8gvJlOP5BFw8fNvrumx7ThdjlN5Sfrtn/wxt8jw8sJPpxLp5P4ml3fnwimPsWO1NeI9+DmDcvbJm0DX89/GHP3pIjcUtn1JVQC5v2y2Y8n5chf3kHx3/3QOUk2YuB7KZaluAzQraOAn5/eP9pXbc9KO8jkPU2LyPFA/oZtg+/S3EwbtuIa2vAbfFv/AHQidD/5uaXkQ/LfzVtB9SAhIlIJS/O8qkdqrccqdkVzphlEvM5j/pisrxzFJRrnXD1I1zCEdqB7MqDESn5IMhHs4CO36VYHWnOgZzace5KgAmns4t3LLTftweWSPojh9dZ235t9Pubbmtq2qirHXXgfzCy1JH7IGT7S0Sj6Lk9EysjGSSeXMIOuFoKzB/RyKlm/iNlv5EIxj7Gu5Po7G8o2dH1JUjgcyxDn1/tPFrQscAR+vH2v2V2g2bNk3G2ZMxAAPoQDFkg/o5Ewn+2lkJw7OnANcq/OPTK6nVZlc5MLd5hhCaZFHuaftEeDS46XctrUNUZb1O7uI7O2lkcXdNLuWtTrrAC3ji3pD6aj6hSXSpdwZCaXyFI4o9VCYCD/AFiWQYxW7faI1NTtI/IS9TubiiBDmhJg2GPxazvx3IM9x1ZgHB+OP4olM4Y9T9Yl3CIt3f3FKShc09NQ3BCXLakE+v5ajuCafdxqJxwzbIZLlwf211jMY5TJyOS6cfY+vEufl7cjlwMfUGrCoJgPaBsuwH9tbMg3cWW8hK7Q8lP3iVepc1Ppplz9WgZCfN9RGndXQndtQy3DCXBcQKEvial5CExN1P7iVxNRM+Dl1EsYyIQh2X+rS3Go3Gmf+n0ED1R6qwcbq3HjjaHW1IX1g3cDLwlaNx/3FR29mcd5Af7auG5FzDjGzrUk+Zz8ouWeUgkW+8QQgxDtddFy7cSyW14VhIGQnfJaWTuidBMtj4oumsTEZb07oksnCTBj00uNALbkUwerbUO3pj0UxuQguL9NZtmRM7O3ZS+Jpk6FxAyf340wZNTyPMIpTENMSPCTvkES7mp3cCGQzw6vGTLg7lu96ihjt3Uwmn++gJl3s+7S/SE7NV+oP9xMGS0PjIekzT4vXUe5qKOQhOKmDH2hdNI+YJA7fFKlkcEt3RpdvU00u5qcZAM1CXLqCE00sjgnc/LS+1Q8JBEu4O3b7tLIRRyOB9n015Weky4MdxV5CEcj09OPqJje4QeomDGMXDCvQDdv2g0ej4cHUSyXCJmnb9INDwXs93hmtwKOQezE43EImEJ2XJ8hLJxPMGoiVsXs47mn00wlzZyE4dvr92md35BEE0kJiOS2Ins1DuXCd2pDgnai7TrqRT8rI+ebTZ3BqP5q6CdssJe38SSvbS8fsfXiVHbukJcVhOHHjJxabcmgDofvKvHqEtE010UoxaQdzg0ps7Na9VBp/o0vkt6akW0E9itwtSPcJct2fUuITOJ2251EICR6VH8/yEQIGP0yACaSG9u3qmS9S53Yxpep2iAYS2QdqzxExm4cdp0x/rPORqFt3TXPPQ41R3RakY+gtObldoRn1wKjFpDbGc0c0NCwHqao+gRMH0yKrZvO1bG3FaDcD7niLno8JwDjVO6IE/tED7O100fPdNLGPu0NUmcQn24Fj/OcxEqzC7D9vVFJPGjR0SagaRxnFAaCzFm8pa4O5b4KwfnsN9pwnl7a8Ef8Ymu+Ts8sBffVhhraHJhFemZKz85IvCPGTEQuLk0wLxVDKTzpg3jdheSzoZIYISZY/i6UBIOqrrEfESrqbx+wzoSVTIQJFUoz+MYCNYI4yRw5suSPqrJ+b+SV0dzlp5SkknzSuhzKXRkc1IQWeYacEPLxPDly5tPzl1XOAmErknOMwnqSdvAsJfKW5yPDE7ODPF5PJmVzMS7KZ2lCdUx9cjYcR0unUejCEIMekvO805wFWNudE3oAbwQ6KG5HKXkFqDPtpG27qdXUW6S+tKXmVJjr9k82uSbMd/tNqOCAgR5s/Sy+QvA45g8e4fzysvE8x8fTapgT7xk3l+dkOBvGfcu8u9dzOVr4XK1XLifL7xImG7lVZ/QSoKgl9N0/MKkmRrbKUtju3MY+nkHB4V5hwzrznW48zyHEOSzKU05QjSajHFKr0AzPAwx6ox8uSPfhh6UccXqraqwbkxIwXmHij4XBVEh2gMHeXIISZFl/NzxZw7oqi5fhdN5wFhNwTEjcLa1HqGMboe95/lrGWilllylFS9N8NIHraLaFbsyx54GLGImGXuHHh5ONkJPZi7aOYNlgNc4WTweH103DjFesMWecFWjGSz3lHh9RnwQIYBQfDXkMdr3yeRmhLGuZ57jN5MiYbtmJrcHjV3ej7uCKxD9JL5pDd5hdXmIGA07sjO0exzZhH13EHR+sO0RbraWfw6umFeUfTM9oRaPVFy6O4oc4IRtK5o5YmtnGyOQMfdx5IvAmNydmodSadPzT/uR8/wBCJc6zzJNdOp4SkPOa5y8nw+HjROqwlE9p4FRQSF5LXDCCBySOzd83q+REvUPOUrypKJwLcV1R04NJppclxAmtQR273U3t3rrwbT8qpCW4ds6qHVQnFYtKitsaYOKBy2I2HBB8JjFF6Td89e2udI8cTLmzuJk+Z7A6d+KnBgj3NnNFHBFH9BdfiDTF5nhhr3ZG87Rmn6kjm7t+cEUTaqcXq7l09kM9kIHcqCDJAYcZoBE1eSEcHUVTzxcYKvwppCRt6DmPKxqKezHI3KNre0xweGOC3lj8uBUvNDkmF8kPMD0biDMJtO5lJmpJrKztMgZfHD75IRE8G9vkyqtxzOznXOZol34gmNStaGl0btzJ5YK8dxGSOLqeT74lrpQy5iXScsN+WRH/AFTZsBa4d4kYP0vVsyeGdvXbK2/MQUEFxyOPKSPJD5S5Gl6wrV9ztavoh7UbwlJy2RBdtJb2e0xQNs0fR8/lXLczshKbZ1fhcUju/KZrt4Qu9wzcJILfLBywdLdiGozytKLwz51lSTuupx4m8ZSEYwmII2Qm4Dl6ubNwlp2bc08hCc84Ry3GCUcai65qNeYjVlK6nLiDPjTYjCajbstoFBBGNtki5equJxIrTnMUTihK6JFW0oB7qHv3qCQUEdsJDRDHcJk3cq6zmT7Q5perJ3rEau50OzGTy4YPDHk/SQKHj5bJzpMKxdfQyQdTPtMakRFDc4qMUwju0y7oErfzFGsctN4lucIRyl5VRmldgl1yOZMCwZCTCHNy5eTc4UfqrzpK8fMXK/Hh/hXRNVO2lanmLppPn5GsF4cEMfxx8mSKDLAO5Fyr2mPh6nyF5kwbk8rFzusWHo5a0GZgOOzHa7yMFzwKKRfbil5biIbt8Nxi0vm4fM9NtxjbNxtrxj2BjHeP0yZevH5yj3CCIQQ+gmafZqOQhO1VHHeQiyDITtrf2Ey2MekjTLqF7NMGT/bUQC5bGMShvOH7NMcEILVUcg7vDDcj6iKNhsW3ZkcuNmF2nTV5NHA5SzHKG2nGQe/H5iZK2Y5Iz8ZPtQ5+hAqNwTaXlzvNSNWUhKdtZf4PIRmF3V8sPeID1SW0E+aTBj1P8dBBBjF6iuDsxYxk9mo4x9r3akDSyXLfYoAuXR6SEvtUICYMen3aIEWyXB+Qj9ZbQmBfCfc+Efto5O0QC7ZBavURp8XrpnskvJ+dCEjuBk2jbW3z0HeKQ3eXB3BakCWQhBXPI9GlkufhIvlwd5/eVfPSNzjRqc5i2E3+la1LQZBlHdF+rTBkHc/1ir2bwZR6RtwnXVgMjcntCKiOMcap1Jgx6a5+tKQpev5OOU1jIQzaXDJfsnz5M8MHm8sKuLkHcJhPS9DyFm24tqPRmqcvR+HdB0IQnuJo+XSkh9M0bcW+SDyOWPl3sqZVeFuHNeuBzKtaLl09O0bxtAmdijjthi+OD410gx2hpY+J2ylTMPQXXVHPvzHGVbWiKUBRg6CbyJoCntnseLQZ4A2Yo/DyjUf7m9Bio8lANqVaDpon/wAN37O9Hm+0ukGPTtiN8hBLgiWy6il2h7xR1z+56V8rk8rpuXt5TKGYWjJpptmw+gODyVTzDDvD+bVAOpJtRMuPNAakD8jXfz+vyLqCDGUZBk+gRR7Yx2/trC654jwo6ooOjKt2MlT0q0m2wZyNr+fTzfHywf0KO8oek3NUDrDxC08fA04H/bDghgy5UyvP+T4ykN97hvWhJlB/2KE0PKVZnK8P64YziaFZSd3JQP5jGPb2BWYTbESZXY44CCJFGTQ72CHJ1FtMJW4hXHkSt/3GwW3hdUXT6iWQZJkzI2LZOA47ZuvASCLq+8s3GzxgJNJxo1EOUbQ0HAEEwbRurI3Orsxfe3owf4zqRhXI6klEwbySZGmIGUplQLzAnBbvYt2zch5LRdyC9px9Iqj2Xgrrge2zoJfhPhfJHDeZy3D2nWh2nQN4vg0/5syvJ5S8nq2V+KakloZlLiallx0M8K5usJHXE7w3nkofS2UP5o/HBsbaWZ4ICb8HLv341y8np/EyQDk8plsnmIJceYvnEyCPZoGuyuDR8RuPl5Rj3epBFlhUraVu8a3N8NDH/kd5T+G+H9JOHE2kFNyiUxnHrOWm5cg8/l8KmS+i6XY1A8q1lIWgJ27HbM87YkH5/wChZvK6friWklZKkDMfc0wlTVpMpUMoTNbPio+0/Bh9KPa7S0Sg5XPGVFyeW1QYx5oBkMbmMnT8zP6sO6sHkqbjXczzPcv5BK6Ho2S1A8q2W0tLmk6mVzbH4xZDOI4svhz/ANCj1ZhvQNekblrCj5TOtnHbDtYs8Y4IvxLqB6WpxPMQ4t9qo9pc56o5mFZDk9PyuQMG8kkEtaS2XtB2wtmgoIIBwfzKjnlB0ZP6gb1RN6VaO5vLbexvyZ7zfLHmgt8vrLqCEHs/G31HIMbkfs1jdcruVGbZHGQgrnG0/lqvZyel2U4eVJLZDLgTd9+EvxiggM49ePrKwIMlzUMhvpk4NxY1RJhef0KYT9IlkHqXeugdsoydnGoAM7Tg8RDj0fTS3hB2/PUMjj23mKNbhm22BLhFeS9m3lrfxu+6fDDB+yo7eXt2IxzKZdDsYB9pGo8wmBHrghSG9SDqDgW7ISi3F1rLbDcNXOL/AIQ1iEweOHzzV0/MGl8PhoTBj4neLodDu2mkNIoRuyC2l93cTUogx2+DbjQ9FkIo/Z3VIIO5+4ljGQdzR9IgDs9T9xCCcO110ICZwhr9X2TVHqmtpdzh6PqITCyEHc8xGp/cTCd5yhRbHqeWhCR/SF+QmDtoIMZPm0D4SAHHCQPTJ4Uy3dGi3a4v00BDI37Vtpx9eDoXExu87rs9PIRMIMfZqO4Z3CDci0zj08/mfnVfNyNyFaNyilxLBkTEK2eb/JaDcXdVMGTh624ufG8JtGzOQ24/qf5lYN3jcY7qpaVtLoc3HDvy65ZdC05GF1RzgMWJbihVlE0bTdOzaXU0Mjs0ZM8DkbUeTlJFHv8AVudRaxhfiYzxEoNvWxQhlMFyMbyA5YMjeMcfv6nL1V5jqin5hN8cMVHIq29zTUkuOQ0ZMkEDgI4BZ213l5YYoc/mL4HUEsrLBvDeieST+IqefVf4snGzl0XEGlFnjITl0812P5S6hcgy603QnLTOP2JLCNx6D5xjwn3F54+lEyMOMdggXLAuSPdNDvwRwq0wjmkvLh/S8kLOAnm4JC0cPGxCwbUO5BxCD6UOdeY5WRxJKPxoomn3jt3TUimLHxbGQueAfwy3k5I/ZwK4wjldNyTGigy0LONrdTaQkPUMA3UBrbqKCPPc7vodBROSPQqRVpHOHp1ixw09xsnOIxAqzDyl5fO6OeNBujzHZPhYs8BIMip8J8XMS32I7jC7FBnIxuiSYc2ZuZZ0CBiyk5PqR/SS+dx8BoOVlIG5cmtvJd9DEuHw7qCV0JipMCMpl7s4yUpG7czXpmb2216znzxQ9SAajl2EOSm9O/f/APZi2i1ynpT3WUu+mHi2UVVKHbrf+DDdwRx7v83KuTo+aV+2riqPd/Ukj9y4CferVbQbPv8Ag5Ojy5/0i8mTRmyFMKHrum6PlFL7fUUA4/Fs/M8M4gGYX4QI0WhveSr/ABEl/wB/MeGzYNyMjhpw+zyvxfK85SfhqGuCrWH/AKRbIe0G84k7lxsTafS4hzt9rgCN3BHGQPe8nJydVY3g/NKkrHFLEDEOpGc8YSRoMDGSQTK8FtZh3uU4hkyw9CDeIsTw/ptnSWOGEZWMyMSOdyVpMzXC9eIJx7ng7K3BkXp3GqTt6pwoqSSTCpPETU7P4TMjl0W8EMebU9HF+NQRYRJuWNa4Q3928wt0cHedXTdWUvUm0e5upJRNrGmbYHYXVv18vLurn8aK4cYeYX1BVDKztrRvbZxk6G0kjyjWJ81txJKbrSeUB7ipGxnzSXAI8nElmEbprMQw5chc+eODNFf3sn0FYc8ScOHMnpejRmNkm0xjdmgb9MkA93kHBB1uIsditTyWkaeZ4li27SdRze8WJ5iZh/UnuxeBJPpKQ4zbIKAOiQOYW5D/ADEXm9lVFd/c7oMpaqm989ZnbvzEmBvhDb4D4Bk5VoGD9aM22NlaS1kzm8tl1Zy50/YMH4shmcY4M3g+VDcyrP5e3GKg8Px2bgPdmfib+/kZ9RWqWUNOq4YZRyj7RNxDcM1HszFiuPcBh/UFYjZ3/Fo7gQk6GeKPLB4fNWb4F1ZjhVP8acQzSg9Lz2XEdy2MFn4OaE0ENuPoxD7TprQK8cU22oucEra94kt7O8ti4cEUeXP8led8K5XJ21d1BgnJKwNWFB1JKo3bl4AuSNubJ3nJ1su7FlhVbKNtusrzTv78uogbb3KN8w3xgkeK0rmkykjM0tBKSbObbywQR58mbli9WHyl0g6kk7luQjKcS48DTUNGN1BHAP8An5eTlXj/AAbb0/SWD+JFbTuQzd+cn3hgZgLkjI2Nk8EHWt+VcUfDiWctOYmt/E1KtKel1Q0I+f7M0nUcwgcBibRkHFcJvQ/F0eqp44Sh2CqFZQgSQYRvPZA5xJ3Oz25w0Jfz7NbdwR3MvTyeDl6qXJ6op+d3PFE4l0ysaceyOoDW4/yR5eVeVcE8HadqDBuaVsWdO6fmkylz6Sxx3cjJmHPmJH4PBy9Pr5V2XN3gk9L1BPKS9zcugnQGTVw5nEsd7UF7B1PK8vqLSfkGGkKUhUYxSRpYTxHoi5tJNLTUPbdTz+Go+0OOIXT8tMl8reTIloYeJ11UVi2M1CE09TzBq0by9vKW43z0N90ToB/x0YUXGcg021l26H04+oP+hV5HBHLghS78cfTjW7KYbcXW6X+G4Mt/jc3J/wAg8cOHJCOXOpH6PoD9RLz6dpBLaZxLavkt5HXtNIaRQjdCAcNMGPiE+bRydolxrw2RhCDtjudml8Il1Luanx7iOL2KHoz0qWMiCXPnEsftkIQ4vfeYhLJb7M1xCAsPZ9mi2jTJq9RMGP8AR+WgC2QiXbJcUjhaiX6JAFsY0u33qZw/aI5e0QC9RM/WW0y4O3aUe3+f0edAFslxFvukEJaJqdmmZPzoCGSXjcjI2c9AihkG4ZenB9cf7ytCXC8Ts0slsigfYRMIpWa03IMziOIy+cYF4X1lNHlQT8Mxdun5Lhrcwjgg/wA2VdJNcL8O53RY6ALTYWkkaEgI2C0LZjHHD14I/KXSeL27khHLY1vy4xqPbI2IQbkNyDvh76rn0TbVMEKjGEDkJ3BpiX4298Dn5XgvhvLaLeUA1pv7yTIgyP4CFjzvY4fiuE6W74FcUnhPhxR1QEqSm6VaMJpG22SMwyx9CGCGH4uXzRrOyN6gpf3QTKkgzEZyTUDBtGcpnvwLdiLGIZPD1lIJUmLGzuNm2twcbYeTZ5fBBBnsi5SRj6W/mu7qZv8Aj11ziVLjDkNTSK4oOj8RGbeW1lLdvagJfCHaow7+TwdnywqHS+EeH9CjcEpKj2kt28dg0e/HGQPkb3VVHQ8wryZVJ/Gl5MRy4cugIEJJeGDaDRGPyavLk6drZ9yBcW8HiRJKkqiZUtLatP4ycEHAYgtxvHFH0/jIMoPIyQwEg66NpWupquGWupr0r5KjsGfNvwXYuBvRYetM+0wO7102cccPvweDf8pWk0wXw7mTieTJ7TZiHqUkBJqbajQbRljufl8ryVV0XNMTHNYM21UeNySskmAQ0eyBgbeNrIr8He2vJ9IuPeM8SJRWFUTum5PVpAO3MF41qOM2S9/k3JFHG3Nu9CPJDFBD01Jk6tfzNId55WvvNIHhHh+xnknqQUhtvZE2G0lptqj0wjz5IPj9JGugqSn6fqiRuJJP5aF/LnY7blsTtP6Fi7z7tkglc4mzaTzfxvM3rEjzYGkBoyG8T5eW3ycuaG3t3TWsEeVxscr2KTyh262YfjLa3cbW2bJD4bdscebeUb6HILS5XCP3FtfeQ8P8K6Dw3buG1CU2GW7XxjDLGaMmX4oc8WaLdUicUPSc3qSV1bNpaY83kv8Axa5umg2eP1OTlyxLn8VJfVFQUOzlraW2JoectL2yFMaAYc/vxx8o8keXJ0lx7hnWlJM6kpZsGqKlmh6dYsGEyG0jjCRzDdhjjzxR6fEg/RqRFbnS3OMlRD+Rok4w3ped1K3rGbycx5uwHbC5G7NBbg3uTwcvJDy5euqOX4B4WbHL5b7m/gspe+M2cG1myDcxZdTp+YuDJJ68F7j2xabqjapE9O02YDuONqQO2eHkPtAo4d+EXXKO3GuoxQkdUPpxMJlTYZiQA6d2TO0LkjIaJ+CIkA/SWICpFLiFwTcMqf5GmTCTyuoJW4kk3loX7I9xvGEmfUVfR+G9F0ANwWkqbC0jd27xh54zE/JvxLCJ/TNcEl+zSDknkta7FMgNgv70ZtjI/EQIc9/OI1voxRXMq/JwzqQs8qDRqIEuftprZjbljjM3MQwt4eaOHSiggzcg/a7y9QzTwXTKws2SR4N0HTdPzilpTJzeK51rvGzssZoCR+X4YlR03zf8I6JcDeyClTAdAGdveI7Nn1oIoS/j60Ma4MbOvCyun2wg1QCNoO2wtuzZG5oplAS6e7Hnhg2X4oCZ8vRV5hnTdWCrBw5mQZiRqTxlZ2gUcFvM8i8Hv+r0V44pbSFUOa+4tq8R2Mkw8oWnKRJQUmkQgU8QkZI2F2OODU6fS5cyfS9B03STMktpKT7IM5N/Vjjj/piXaN6bIIY3M3MFpB/afzKZtDNjbFKWfzzgW+T/ADLTQiamaqs9+ptMSjz/AANJzK9vTdse2zY1gHkd4pjyYXR7My0Gv0IyfzqG4I4cuLj020H8siOGPUVoxIIa59500hgiJbjd4o+wWyW+Nw0vhplv++lrcL6EKQt/rEXO7S+ITUTCaQ+CgC52f0FHIQguF0+Gmd39RLtjKT2aAB27ikdn7NR7fajUgekMaALmol2+87NBBj4d5MGTT8xARyejDpoTO08/yEICYMnZC6fXQP8ARwIH+jTLZOKXs0Ay32n0EbOP/wDYl3NNLJcIgGE2fTGIwc4/URtAy6fedNV9sf8AcRbuICYQmn+wjP8AmUf2SCWyIBlzUIWz8tLudp9RBB/OJY7ly7w0ADuEJ6/UTCD1NVMGPTuciCDQEcdwlzs1Itk7JHaaqZxOxQale8kbNzxdOMnTt/6FXt5ONlqNjGIAhOz/AHF0HF9IlkWDkuh3nNV+RZmEUKSQx/8AZjX4+vb6a5eeS+YTKsJfszwzTYGwzmjumg6JvIHywwxZod3fXaEGMukUKCD7K9+k3/tKu/DltrraV6nPOfD6v6SvUzNvVFaD8RuSTIxzv7BIGw5fBkcZjZShjj8GnkFqKrJXFUNiTSdsZ88O6PLtnyeL44IG70cDrIPkF4Iuvb31sHC4TMMflx74f9KXtkrIS25C7B6a7Bk/ryryLEw12YRK5zCZhvsmfzitKolrecOSzgI42hBjMHxVHBAyDEaDkgNf8EXSH5sSXI6gqmZVpJxTt5b+91yMI2scEBMzbNym+l1FpluRiHpTgxDD7EfTTBs5P/8AUjDudQjWNQZPU8hrbE/4I+hmcwmFQCxIcDlsyKMbscuBA2ILOHJkLfcZ4t0dvSi3ekrCXzyoJlQc0nc3NvkGcgYAdnAMOXwwZfLiguLRByOTvhkZeMhb/UcCjg/N1kDl9Ly34MWcBHb08g8+QeX1eRRKvckEGGzOclMfQ89jb1I58XylyZ27Jclw4I3bszULgMTZwTfIHlz9fLH7NSNjqRk4mEtJPp4/nYGRG7N433Aka7HByZyeHrX7mTtM63xwOi7mmZ278uMAtz9ZlRcpdt+DSF2f2hYIP3lsQbmPCTow+Zc5URMnpOXkcvJoKW7WSTjIDxbGQsccfQ1+Jv8ATXYDoOcTcYxtgrqPHjgtvxbLZc08+1ej/r8H2VDeEePtNy8MeAfUIXJB/RD4IV7DDVuLrWosmMGm3OfJJXy+l5HJNObzjaz/AMmaa0f9SuPHFoezS1mFhB5ZMkZv3YVXk0x2haEHkDQO4Rbrci015lsxgbTXGvi/wDgjcrgjkmufyyb8f9KZp27olHJ3fDTB3B+jW3oXLTbbWhIualvvEsno0anar9QH4RL1Ewn10u3cIgC3ct3dTz0wgxiH+2l27Wn10wlzvuH00BHt+x/bRbGLtrhFItjt3OHANRyD1EAdqjhDGInaJcaYT2yALmojhavUS7nacRMt6iAOETjW0JaEBMGS1wwpg7g/SIQgAnCSxj9D66EIBmndS7dzSsoQgDZyI0xad7idRCEJgtjt/HvpdzT/AGEIQhC3aTB3BE8xCEAzP+ZGpwyoQgPhff8A6iEIAXwhCA/BjQQjMhCDEH9tCFMBg9jbcLppe2XO500IUIBx8JbkbOQ3AH044EEI3JbFZ3EIQ8pgSNoJb09RLINCEPRfaE7NBCIQpgFy4gg+14iEIAINLgQhQgCD1EwftkIQBb7NA7Y0IQAS4XhaaXb09RCEAu52XedNBLdzjb6EIBYx/QTLdvih+WhCAXb4fkddM0+0Nw+ghCAXcIhCEJj/2Q==" ) parse_pic_data = string.pic.parse(pic_cover) expected_pic_string_data = string.base64.decode( "/9j/4AAQSkZJRgABAQEASABIAAD/7RBSUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEAAQBIAAAAAQABOEJJTQQNAAAAAAAEAAAAeDhCSU0D8wAAAAAACAAAAAAAAAAAOEJJTQQKAAAAAAABAAA4QklNJxAAAAAAAAoAAQAAAAAAAAACOEJJTQP1AAAAAABIAC9mZgABAGxmZgAGAAAAAAABAC9mZgABAKGZmgAGAAAAAAABADIAAAABAFoAAAAGAAAAAAABADUAAAABAC0AAAAGAAAAAAABOEJJTQP4AAAAAABwAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAADhCSU0ECAAAAAAAEAAAAAEAAAJAAAACQAAAAAA4QklNBBQAAAAAAAQAAAABOEJJTQQMAAAAAA7BAAAAAQAAAHAAAABwAAABUAAAkwAAAA6lABgAAf/Y/+AAEEpGSUYAAQIBAEgASAAA/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAcABwAwEiAAIRAQMRAf/dAAQAB//EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8A3U8pDhMVnOgrsok9vxSJnjWeFQyes9Ox6L77LSasZza7nVtLoe/eGtbH0/dVY121I/ypbOcYiyW6XH5+CbyB/iqmX1vpWC6tt5te59Lckiqp1np0u+jkZGz+ar9yMes9La/KrFhLsKgZd0NkGktbZ6tB/wAN7LK/89RmUztEgHwa8uZkfljXmmaSOxP4J95g+0qkz6x9NsyqcRjMk25Oz0R6LocLNpZbun+a9/vs/MTWfWTpDHZAD7Lm4mlttdZdVO5lJZXcdrLXNfY36CH63x+xZ7+Xv+DdDhxMR4hO2Tqg9T6hhdMrrszNwF1npMDG7zujcXES3axrfpuVXM6107CzbMO43Vvp2eraKnOpZ6sel6lzJ9PdP7iMZTNenivt4L48xLqLdIREp1Tr6ni2ZtmBL/tFT3V2ew7A5g3ODrPoq2D/AKhSA22YzEhYUf8AcmEjzT902shJL//Q3jp3TE+CTu/inYJPwWbIgAkt6cxCJkei9TQHBz+G6n5ariMak3145Lg+vqGPlZljZ034v2/0nf2fWZ/bXdls6D4JNqqERWwAAtbDWiA76TW+36L/AM9qijlIskXbnyyykST1/B5DF6th4Yzrs6zY3qfSsc4pMkWllJxbMdj2/wCF9f8AwapOwcrHoyrgDXkdOw8SnJrPPoZNDqMhrx/wO6iz+wvQBUyGDY0NrO6tu0Q0/vMEfo3f1FUvzMmp2aGYYeKBV6T9jj6weaxdu2Nd6n2f1f8AX9L6UkcuvpjvV2f8FAn4OLTW+zqvRqWPix/RHsr1iN1Za13KzrMrGb9ST0dx9HPwSz7ThvBa5u3IbvuLXe1zXerX7l1d2ddUMl/2drfsz211n0rXkVk7fX3Y9T/Vqe33ejg+p6O/9YQLepZbjebengOqpx3j1a32Em01faMd3pVWuu+w+r72U+/9F/Nf6JCR002MTv1B/wDXiQfDt1cP6yZOP1i+unp7XdQqZh5N5sx9Qx1jhj1Xv37P0dLqLN356p5eS3MsuzbrCMK2jpj+p11lo3Uv9r3GxzXvYyrLbX9H/SLssW19tprfiDGHoVW6Ngh9km3He9rGVbqHf6Lf/wAJs/m0R1LGaBjdp00a0CP3XCEhkEQBw7ba9/UoSrSnnenWN/5xdWpdnAOsyrSOnBoIfDGfrQs+k30tu301sBxadjtQdGlGdVWHF4rY15/ODWh2v8sDcoPZuEfio5T1BHQALoZDE2NFJGFBpOoI1H5FKVKDYtvQkJRsP//R3ZJRWtgeJQ2cidFF+Re221rcdzhWx7mOE+9zAxzGfu/pnOc1iyMtk121ZOanZEe2pbI4Umj84/JUqMvLttqruxDS2zfNm6Q0M+jvlrNrrnfzf/qtXg5kfTbp/KH96YYkHVqrkho1WhjdJvsbuyHGlp4YIL/7U+2tLpOK2x32p/ua0xUORI5s/lbfzFr9lbwcuCBKfXaP8WSENLLTHS8Ac1Fx8XOcf+/KFvR8J7YrD6XDhzHE/wDRs3tV08pdlZ9rGRXBH7AycI7BwMnCvwz74sqJhtg4k9nN/McgESPiuisY17Sx4DmuEEHghc/ktZi3mp7w1vLC9wbLT/W/dVTPg4PVH5e37rHOFahruEHbyOxUHCFO66gMJFlZcAS0b2amJ26u/OWdb1G4Y3qCqt1u/b6e/wDN2b/U1O7+e/Rf6P8A8+KARJ2WhsuhrweJ5T6yR4cqrVlm0WtuFdRrIA/SAh+nvdS6Rvrb++rDLK3tBY9ryBDw1wdB8H7T7HJ+OwSPq2uXlR4e7//S3RVXYQLGNsA1Ac0OH3PBUm4uL/3HpHl6bP8AyCg6s2AsD317oO+s7XCDOjtVBuPk+t9O7aXQXC4aNBJDvTdX+79JZE74j6qVzBPuy+n5NgYuKXf0ek/9aZ/5BSOLhhpP2ajT/gq//IKlXj5z7B6j7qmFu3ey8F8SXTZX6fp7/f8ATZ/omLRrZtrDNxfpG55lxnxKaSR+lf1Yfq9B06plOFTXW0Ma1ohrQABOujWqz2VXptotw6nDsNp+I9pVo8LVFUKbLE8p+yY8p0VMCsbrdNT31PfWywiWje1roB92m8O/dWyVj9XsDr6qhyAXnyH0R/1Siz/zcvJEvlLmHFxv+49Pl+jZ/wCQVB+RjtyX4rMFtltfp+7ZW1pFsbdjnN/N3e9aZBg7dD28J81mijNcXvsbBcdwa28hky3irb7fbv8A8J/I/wAIqET3P4sIZ0DGvx672UsY21geGljJAcJ2u2tRGMY1vta1skzAAJjjdtCVAubWRcAC0w07txLR+e8/vJx/Nj4kpR+dnwfOP5dH/9PbsZa7b6dppM6uDWv5HH6Tcn9DL/7munxFNP8A5BS1/uU2ukfwWRl0N6a+AZOaiRIS/eH4hF9nzB/2vfP/ABNGn/gSn9lzf/LG3/tnG/8ASCKOFJpjTmO6YJHw/wAWLVttdIyn4T/Qyb3X13Oltz2sYQ4/4NzKW117XfmO2rfBkLlyGuaWvaHNcIcD3B5Raep9RwQGtrPUMYcM3NZeyPzQ+zbXkf2/Ts/4RXMHMCuGelbHoywmKovQ6ypdlin609PZ/SKM2h37pxLrf/BMNmTV/wBNKz6xtcz9Uwcm5x+ibWDGbr3d9qcy7/MoVn3IAWZCvNfY7ullZFWNS6654ZWwSXFc1kjJybnZH2q7H9QCKmNpcA0fR/pFF1m/9/3o1tmRkWCzLeHuaZZWwEVsP7zd3vsf/wAJZ/YZWokg6lU8+fi9Mfl7kfMxznegarsbJA1z8gk/yMb+GKoHFyYj7ffP9TH/APeZWSZO7t2USTxyB+VV+I+H2RWtR2PktE/bbj5FlH8MdGbVZUA19zrTAPvaxu2fzG+jXV/4IpwHvAd9AaujwCYnc4uP5xlPxaknt4Nnlo2b7ftf/9TeKdhh09j+VKE0DjnyWZKPEKb2SAnExP080xPdVuodQxen013ZW/ZbY2lvpt3ne4Ocz2A7vzPzP0iI15Gh47FRzMXDy6BXmNa+ljt53uLGgw5kuc11fs2WOb7/AGKACpVIHxpzZQMZVIbMcjqmLi4+RkX7/TxL/s1sNk+oCxv6Ns+9n6bdv/0fqJZXVcTF+2OtFnpdO2DKva0GtrrNkVMcXB1lldd1d921myur8/1P0aV+F065rq8msWNzbTca3PeW2Wit26ytofs/o7Xv/R/o/wDCprMbBtGSWsZe4tY3Jra8mTUWuq+01tftbY302fpLGeq+utOHDpof5fykjRK/qFdXT3dRcHtobT9oLSALAwt9TVjnNb6mx351ir4/W8HIfW1hewW4pzvUsDWNFLXPrc50v9R38y936FltPp+nZ636WtQq22Y+PhWY2zEyqxVU31g4OZ6fqsqofWfUs201/T9X+b/wqEasPHDm3YgxqxU6iwnIgfZn2uaXvra/e9lll9j/AFrP0jLrv51ECOtg301jsrRO3rWI6vDsDLQ3PANQLQHNm2nD25Dd/wCjey7JZv2+p7PVUX9YpbS66yjIDGZP2LaGNLjf9AM2std7H2/oGWf6VErwekPdjCttZNQOVisa92jXuZb67KvU/SY/rCq1nqM9Bl2z89F+x0hrmCr2Ov8AtThLv54OF3rc/S9VrX7P5tA8HY7/AIK0SvkEjQwY04UHQ1slOXRqe3KX0Tudq781p7fynf8AfU0Ak0F0IGRACxBa0s/Pdq/yH5rP/JKJUtdSTJPJKiRqrEYgCg6MICEQB/Iv/9XoImU0JwQNUiXduFnOgsQOFWz8V+VhXYgdAuAG4gnbDm2fm7XfmKySYTR96BANdxsVs8cZipD69XGd04VOYy7qZqa00Orbse0N9IPrd6DW3enW+z3f+7leUiWYePZn221dQbU/LvZksY2p27a11hZWy4Pbv95s/wCD/wBLU9axa08jUayour3Gd72nj2uLe86ppE+9/wCKGtPlpj5Txfg5GJg492HlYdfUjkXX1sDLHiya9SC+sWWtc1trLaqLa6bat/8Ah96fOpxszMfeM7bZZR9mNfok/o65us42u92RV9o/8Dr9RabccCN2ReQJhrn7gd0j/o7vYndiUvADrr3bRAIcB3nX3H3NQ9d7H/msfs5P3S472dMfZQftwZ6FeNW5oqsBecVzbqrHvn81p/Qsb9D1/wBLZd+j9MnTumOcKsmvMflBljXseRY2sNYbRZUK7bdnufZ/OWs/R+mtVlNTGgAFxBkOsJcf++tU3Oe4jcZ/gjUiKuh9JMkeXkdzw/ipoDNQdz/3uw/qg/8Afk3mnSToxERo2YY4wFR+3qtMlMQlxwkiuf/ZADhCSU0EBgAAAAAABwABAAAAAQEA/+IMWElDQ19QUk9GSUxFAAEBAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYAMQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnkAAGRlc2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDPFAAD7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUrBToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoEGioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2GiYfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFqSGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pammGqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFgsdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf///9sAQwADAgICAgIDAgICAwMDAwQGBAQEBAQIBgYFBgkICgoJCAkJCgwPDAoLDgsJCQ0RDQ4PEBAREAoMEhMSEBMPEBAQ/9sAQwEDAwMEAwQIBAQIEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ/8AAEQgBaAFoAwEiAAIRAQMRAf/EAB0AAAIDAQEBAQEAAAAAAAAAAAADBAUHBggCAQn/xABlEAABAgQDAwYHCQoJBQ4GAwADAAQCBQYTBxIjFCIzAQgyQkNTERVSYmNycxYhJDGCg5KishclNEFRYZOjwtIYJlRxkaGz4vBEgdHT8gknKDU2NzhIZHSxwcPjRUdVVljzZXaW/8QAGwEBAAIDAQEAAAAAAAAAAAAAAAMFAgQGAQf/xAA6EQABAgQDBAgEBQQDAQAAAAAAAgMBBBITBRExFCEiMiNBQlFSYZGhBjNxgRUkNFNiQ3Kx4YKSwfD/2gAMAwEAAhEDEQA/AN0GT0Pr3EXBi4faIuXPXSyXLnBXzQ+ijO18xMHcSxj7VA+zQEjT+Wlj4n7aEXLf/toBZLdy6NFxBB/TRwh2+IgC2MqOyRxB2ksndWd/qIBlwf76j+/c9omcMmrw0u5d0+HGProA1PBbJZ+bQRBB6ntFHuDEPVQDM/5ku4PUS7n5vlpZO97xAMGTUIXqIGS1c8viJfCHwUEuF0xhMS53aZnmcBlwhCDKghNPTDxNNA2bgtwRbI/PUcjMYiWtsD3kcfnrBcwhvnVA1lzbLfOqBMGQn6RDcn99LGRmMZBbYYnzUHTTNsb/AJFr7fLeIg/FZbxEgZBl9JGPqIG4/wBtR/GDcvYo2xn3P65Z7ez4h+LS/i9iw01HedmUpvXgtJY3jPtLw/oJemUlwcyN6hBZP/BS7Wy52iRGJS7naJA/qdRLHb1BII3J2RgkS7by3d2Mx7fdiUkItk6HkL5VZjLg/wB9MGS5c8hQ2/E8DnTTB8O5e7ReExI0xD0umPu0D4nGCl6aYP8As0ADtiJ3cY0D1CaQUXCFJ55FIB9dAL0+00/PUjTHbFZ9IljIO5aRqcVAHE/tEE0u+HGgnDRbuIA4n76M+ndTB2+z/fUcmoTwoAcaiW49KpBO68vpqG41LfZxoAcEtdsmW9NRyDu8Lppmp6bTQEgfDJ9e4hL7vjE9ohATLdrUGl3CFueWNA7aB6hLvD9mgAZNTVMmD1dVLJbuo2jsr1zzEAwhEaZdJLIQfEH2aXcIRAMud32aYThJemL5xLuaf2EAy5qfF8hLHqXLf00shB9ol7RqfsIAHbIQl03yEW9m0uojT7NDgg7ZCIAJ/aKOT4MO65+Wl7YQpNm4cfkIIPadVybc8hYPOob5lZGs/MMyyK1qyGbYMrfTDcuItk7Q1vtMg0D0h/BtODr21IbtyF1LKqnMU/aT6lDN4/8Asp+8SPc7Kz6lxLIRwUmobc8hWHi8f78CkDlbftNNVy3HneZRTP4k85zKKcbcgvm0bOQhPZroNjt+jSyNx9zuDXlGZpbQU+xx/lRsbj5a6C36C4l7PuJbQL5R7GThI2MgvZkV4NuO5a0UWxjJ5ikti+UZB/7Cj27S6DZx9klxt+9Ua2ySD5R6mpaMmN5g8ZcIxhwE6dsqmEbpZGZBcNG1LbJoPRgLJNG5fwn9xFsYhuCj6ZNSCAnTzpbhmTh2VHGMguGt1ueW15lhL4m632s/qXGzkHpXkOLltV45gS5a6EH1FYDeXbZRG9eDqKxYm2nfIvpfGGnOfcLHcIpA+09Il2xj1OHc7tM1LS3C1g7VoFz03DTLmmo9xuMf7aYO4gGalvSS+0J6TqJhPRoJpIAuaml0EWycQpku33vaIITU7xAFsZPnOml2+61EanaphCaluygI6ZbS7ZB8NFy784gGDHbuXEIbkHbt9T2SEBI7JMSx3Lndpg+JaJ2fQQBGljti4akXFHJbt6SAWQZCD0+mmXB+zSx3O+twJZLlvvEBI4n7CWTS7FHC0u86CCf2iAj3NO53iXtHZ/XTCcS2lkt8QprYx9O5uQDUwqpAhCalpLHct6qyurOcBJ2zcjmiZb46atHOzuXl3JAOP0Y+kX1+iryrMQHkkwjJiQ2CHPsQDwBJ0M8WXkWlPOPIpQynfHcUGJYxa4Gd8e87ghLvDCpDeXkLqLx3MOexWjJ43bCk9OjPHp5LRozb3veHweSvZkveDLJ283emCCDYoHbmPoQDzQZo1TTeHTTFKne0ci+487xrVnmSG7MYlIGMez/Ob68j4qc9CYEmDiSYUhaDag09vOKCONxHF1oM0EUEMMK5+j+epWkpcD92wQzpqQmsEYoIDNg5Opagh3vWW+3gcxRX7HmzLPbmzjRbGVcWTESXvsP3ld0s8C7gHLju21zszQweHKSBYPh/zvKom9aSeU1szlAJc7cwNHLkDWOCMebd8Pvx8vWWu1IPLQqlOmpGlhaz1YQdvV4iLg+07NcvipUkwonDueVTJDB2qWt9ogub8HTh/EvPeHfOYriu5HXD1yaXNPc9ISP20YGkGmaGODk37nmr1iQefTWnTQIaUtNR6oITtLPo0wfDXhcfO8xU2faXMykeT0cvgSx88TFi4Ns2nEuyE6dtoGP+fqdVbn4JMZVbifZl6nui4PhpeoPhKnJVkrklDt6xqyZCA1BLgO3Lkm5BvQQ9D1l5frjnoVBMphs2GVNtAS7+WTPfMT1IPDlWuxIPTHInQwQwtzlPXDchCj7tBHBPkLzHhvzgMTK/oOvJkU0jHMablzV22MBrub2a573hih6ECuOb3jRXGJFUPJRUDxocDRlG405fAHfzweSpF4a62hUVKhw6kkGF8XkehLgyjtfXRbtD9n10W+9WT84zFSoMLqXl7mkjNPHcye24IHAoDQWYYN/pedkWlLsbStKO8jb6RZqHaqOQY7nB4i814B84zEyv8RB0lW0ylEbU7c9mxL4G1w0MEUXJ4I/kdBa5jfWk8oDC+cVbTZmg5owsWb4r0G9HD1FtP4etp5LCst5JQttdB2Dxnd4Xy4FDtkGTS014rb88DnCfhLnxGeC5uQN5VBn/APDkWkYZ88QdQTwckr+Thabfcsv2gsmz7+XVg9/6qmfwGYa40ZR+hs0LPSA3gxE4O+Tpqwbkb27V65GTqLN8aK8mGG9Ps5s2C0JGd7Yjv7kCy9nzjJ49ISWy3ZBzG3csnaZ7mb4uSDLy/wBKwlW5miuOhZSU29L6adx6ct6f20sZCCIuDwfryrKtp94+rYMuG9G9jG2jYCyQEDkg5d/fiWgXB6d3tFYcFdNR00pNomeX0FjcXCd55iYQZCkJ6RLJ3iZcIL0aGyBPRIjR2mr0PIS+GS6gDUuEH84mEuE1UD1dVLJq3EAW9S4P6ajkHqXRGUzT+QPqKO4Hp6emgGaYkJdu4PjIQFgMdwaXb1NTTTOv5ijjH/sICRGjJ+dR9S5wVIuDtoBZB/Q+ul+iTLlwl0htwnQSyEtE4PD66AW4toJc/wD1pZNXtvUQMd0lvu+uvIxpCowaRnEP1hFy+LDjYcN6gIMN+2y34CdAkES7AbcfCF8tZvzoCbNg3OGwjW9vIBpHH0NyKPf5Pf8AKhVbCa2l9KEqyhnA43EcZvrsM6d/eeQ3jhv7n5w50WjUdgeQZckG9qQQQZc29oLZKoqQbnmbydzeMTaHo2EEG5BHpuY+XL9HkWX0XIyTfAvESbNg2/ET2VO4A2uoO6Mn9qo5J4N7hHJ6FIYI9kqJ3N9mILhwRBgt+9D51xdA4yhyn+MSsoufaJzbim9mGMlm3tbeMgTWs/Rj8HreWvWmMFWDknNrk5Ja8MSOZN5cwDGQWS5uanvfNrF8aJGSn29DsRWWm10yAkelHnzxGji/Fm3tRdZips8y5s+He23jgG5txxjyQcPPydb1FA/+Zi2pXiDiK6PqV/NPw3k9U1pMKknctC/BTwxkbQEFBHAR0ToR5PB1ci7jnoUXJy0mzrvYwjmjR7AwjMDcjcBJ0II+Xk8HRi5EvmRkG5ldYWtrJ8Na77gUGfoR/k6S7jnaWy4JvLvZzFjZ6m/nWs9MOwxBKKo6wga61/mKDE+bPVDiZYb4mUc5s7FLZUd+z1c/YxQxwe/8heb54zcbQ3bMTGAfsdLPH0FvnNnGO5iAMZtz3GPvU6q4/C+i/uiTSaU22CYj0kmfO2ern+Ehghi5B/Lh3fnFdMKQ265H6G/Bdtaj0hNK0HijzP5hP3Ly498XbBMrfT2oJoIY/pdJZnzK2EvLiBVDQrMJBnkMA44Ca1yC9+PMuLw7xE2HDvECgHN4AJtLtvYXC/5UGOHPBB68H9mtE5l5CfdAqgnX8TDz6UEHbLSea2eXehDvzgQLRbaUTOehTdLyRnSfiilZQ0jIR3+CNIGtzhbvLbgVhzO6Xped0fUDmZU3KDxjmsA4Nrl4TR5Mn5YuToxJnPgb3W9Hlc6kY9uyfqFecye2Wh6gc2bEY5qPc+Qtdbi44cnXXUjg5+XMr54mJDx7XDfDtib7108MZ3La7uEdE8v1IV2nNj5vdNuaTb1/X8haO45lqM5a4FnbNww5skfLB2ufz1h+Ojcn3YK0KUJr45ifW9D1PxL3xhuNn9z+l/Ft6x4mY2fUswrOdc2OUbS1uz6yR7o2U0Gb44U/TdJYT1ZMqbpuXMHUyZAYRmYNIAxkgiNDkg3Vh/MrGT7ok0KUxjwElRNbvN+D4/fXoTnUD/3j55b7wG/88sD5m5LWJE0Y9ccqJ+xyqOVjVIuXDFv9Oo9iEHp6XZ9deP8AnqTgZK0peSF14AS7a44Bl7w3g3/oL2IO4Xsc8C8B42TBnXePkwEIJj/fEEobbIXqD0un1outlWthCOlr7oGErzfQo6LnBKAxIkZXLO2eUzEG0wXeHvw+HciXrjnIM2b7B+pGzkNyAlgf66Bea+dhTfinFyebMEPwsYH8Gz9Pegy/sLfMQKg91vNP91P8rlTG9bLv54XMAo/f9eBWU+m4tmY84E7nMlfeZXzM27htWFQEKY2nKhjgudPJegXB85iRyuU4uThtJJaFhB8FcGCPcDeighi6HJw8/hzcqh0HUGJlPy+oKpw8nBpTYbD8ZW2gTR2YjQw8kXJyE5IujFH0oVeYH4XvMbK4eTeqKk/ACQO38ZN969gJHF0LnJy7u5vZ1uRRYcXMLVuyyyJlcEVKNM5ym0FwDod69DcPclt722x+Hl+tyJfNfpek6toN5NplIQnP4xtwXL2nudFWnPcbkJhvJ2wrI4yTkYw+j3F5npuaYsSmVuPcLOKtADafhgZTe1I97pjHurTlmtokqaqc4kaFcB74ldNyel2fi2QS0LQFy5kHn6f+dWjN4T8Gc9DyFx+C7iePsN5O5qTa/GNs4zbXnjNumj+OOJdI8H2i5hdcu8ri0iesPraXWhRcWx29L6akqmZzAnc3LneK0uDt3Rdp3itpWbRMHXyM+iZ4I7lAMZEvtP20DGmWyfI9GpyxC3pj8hHU7FF30KBkuE7uPqRoAt2uGlkIPtDJlvUSyWuEX6CALnodxCLhNPRue03EICwJbSyDGL/20wlu3aUe2O5qdBAHtUEuW+8R2feQJfd6P6RAMJ3nEUe3d7b5CZ6UpkDITTt6kZO7TQRjlvDZ7pPtpg24+EO8NSB90PUudNSBjHbVFOz1xdCNDjMaxa50LOnWA9MfnrC+eA4ILD+Vsf5XNe9ycOCLlW8aaw/nKUHXFfjp9tRsh28DTayOY7oYLccVrwcWOFMNo2hJzrEeNJx/N7p8c7wfxElGsSOZEjHnIWOOPPZii5P8QrznL5eOYzxmxcs7kEycjbx3O0zR/ET+le0ObfQ9WUTQ7yU1lLdkdEekyQXYI4LOTc6PLEsjw/5veJktxglc2qCmw+55pOo3cbnam2pBDmigJ4IeXrK9YnkIcerVDyJ23+NRI55Ev8UTSj9m1IPF0bCC4LcHbjhyZ1VuGbid8zfbWxjX6amtzi9SHdjh/WrVOcxhfXldOKbc0LLTOzy3ar0d0MFvNk+O6pHNvwzqilsP6gonEim9kBMnMdlmQoTXAxQZY+Xl5R8sSxRMttyyV1b4RPbvRp8omL80PESV0tiJMKOqB4Fh49ZDsxn3INpH0IPD6nKtM56FcSuW0PK6O8ZBJNJlMYHEYenGMI4Ovycnh6X4lm+KnMnqiWvCPcPP4wy7iQMyFgC6H5m9ulXP0PzO8VKgnG01IzDTbUn4S8digjc9X3oBra/KOOpnK/sbHRLXdqOw5u9PuC4b4oVbxIDyU8sDtHQJHCGMkfxfIVHzQyf79EvIINuAkufDgj9aCH8S9WN8O2dJYRvMPKFlu4OVOmjaAmSCNwYkEW+Tq78SyPm/4D4mUBiIzndWyFo0YkbnbmMCYBNbjig/Ny5olCieQ+l5dWuhFtCF1mL84zDv3AYuTATYJhy6ZffZhGPJBAOA0e+Hl8PkxrQOZfbJXlSaJs/iof8AbfJW0c4zBdxijTcvc0+EJ53KXGjATJBcDFxOTPEuf5t+DdeYZVRNJlVstC0au5dYDGN3AbfveHq8vKvFzaJiSVWrflkL9yXyq3nL88wYy+5cbnofC/2Fccy8ZCUXUjYprluYgz/Kg8O/4Fec5jCOvMSHFP8AuSk4Zk1YDPtNx0EO/FHB3nKrTmv4Z1hhnTc4ltYy0LA796M8Ft3AaDJk81Q3Efh1FW/uI1LRs/NvMD53mG5JJXg6tHLbkuqEdyMw8mmYMHgjgj9bpLSOa3jBKy0W3w8q2cNGj2S/BGBidBwGH8/R3OivQFaYf03iHTbylqkZ3GLvr9dvH1CD8PXXkOsOZniRJJgQlCbJPWsZLl7a9mdDy+XBFyww/RWTDjM5LpZeVlGGhIwtEwihassjbOcY4ZzfBerGMtmTR29aDaO3IQFgjjGG9B7/ACweHyVgfM/cS+W4gTgb140YNRyUmSNwWAO/nF8Xh5V2mGeBeKFP4Z4mSSZUfYmlQsmrdgEjoOcmWOK74d/7cSy95zU8dLnwagzDjHwYxzBn1fy6y3ZRlmyuXrhlnqStoRQpFR7InFaU+xp+eVJLZ9Ln8EiZHOaw7gNAOOGDNyckeXlX816bqiaSipG9Yy2ZC8YgJ4zCZwLPBnh+Ll5ff3l6UpPAfHCm8M60pIdN/fSe7C3DfdtoA7LDHFEXwZTReYuw5t+B9YYXVBOJ/WMtCA520DBnG3dwGtwZ8x4t3l3eHAo2Nnw5C+KCt4bcRLVcWZ5XrDECqMTZoOpKxeNDzEAxtM7RpA2gGGGOLl5Oj1t9bpRc4HN+ZvUjEhjEjYOTt8/mROQE5OX662DnKYb1Bith23ltLa83lsxA7bQOCwQQEg3oSQZ+Xl5Fm9F4L4uU3hnWlCkptoA85G02aDxqE0BIxm1d/PF2flKRc2zMtJXuTlGG4X0OpT1bzj+adI2dUe7ym5kYNibSGBpHbFuDuR5fx+R0lzeC9SOMKcWG8tnYdggI5JJZrBdgyZ8+Xkj+SRbRzb8F68wzqycPakZtG7WZS4YLw3YTb8McH5OXN3i5fHzm115XWIkwq2iZbLhtXdh3HH4wCGO9DBlj3Iv5l5tbTji2lq4YwFxFakVanUc8hncoOT3QmyDmvZ9MejF76h8zt4zlOHc0bFmTQEHjntHUEFzRh5Px5ekrTGig8UK6wrpeW+5sLioQObk1bAdByboYoc+cscKwt5zY8ZCD/wCR5iHGOMcEYJgz6HxeHk1lgyllyVtKchDfEIocRRUe3BuG774SIwTweWMueD+pR3jcZbiz/m90PUFCYbjkFSM9ke+MTuIw3YI9yKCDe3eWLyFolu4TzFzj7aG1qTrka3yllO4b2lIZzDsi3tTT9opDhv3uoqt437L66123LS60m/Lv5cfXAvLn0PLX6oTN4Mun9OBTO0ucTyF0Uu+iYRUddIT20o84agO52mp5aZb7roItkIj0QlmWIzs9JLSye2QMen3kaAYTtEJZLfFKhATCE/V92o5BkL6g1IJp/FvoHqDtoCP2iLgyJbjSuWu0QMfDQDNPs0xvcFw+ITTyKOMnttPp21YDb8MpVX4lN2kWkaxOdx3EtnRYRrEmDHw0zZ26B/o0EGqI4cjkuD1OuhuO5cTLZFIGPtSoQgO4PSRqamj8tA+J3iZqIAtuLikcFL4fpEwbe76izPSPc1NMO+pDNnqXSpgxj+WpHEXrZ4LGPsxKRs5BcUPEVHXAxlouoBE04CSp3vjLk7GJeX+bfMJhUlaUn7nzTwcxlrd2erTP3ei4bRRxWOQA+WPyd1WspK3GVLq0Npti4hS+49eW9TS1Lmoi3p6i8385Ag/uqUex8W1FNoDy496WyEscDlxBDHHFueDyV1DioJhQnNveTum5DN5E9Hcstptnjct4yGy5/fgh/wA26pNh4Erq5hY4Eq7zaCEtD1UcXVEsLoPAuRuabpuv3NeVEOfP7EzNMhzCDISOL3+UPg5foKZjgSaVTiJQ+FLapHkllFQ7W7mUbQuQzi38Q4E2VNVFX13C2iuiETaCEt8UJtRL+EcMQeH3a85uJG4wOxooum6SqSbnklYabyVP3d7o+9ngXP8AOcmlLyjExv7rTVFspKVjG2gkrqMMY3sRo4QR+Rl8pEyNbyUIVujDPPIksZK4VanqS44uaoUwm0CHcKE3zgsiyuk5fWDHm/kZV+8MeaeIXxI4xl1hhigj5RclzkzdRef5O4nlAYL0XjrT9bTwcxmUx2B/LXDq8ycBiMWHk0vMhgRmRiurj0jlDzCGK+0e1B6g9Ph+QjZ+zIHiLM+cBJ60qTDfYaFC7dutoGd4zYFyGcMsmoJV/NvnFHzam54Wj2dRSyOCa25lLZ06vGlzmGCGG38UMX095QbPHZ7tXWYW+Co0hwztaog7g0sez/5Tp9nnVxc5CW7f0Etwzuj9otQiKclwXE6HD00EGMno/ITNRsQmjcRpkGgFkt27TntNPzFHJ2f11IcXNotWUv5m4hKQ3AyankJfaaWn5CkOBjJxVHIPukMxez3dRV7xuS2S1qKwH3fX66WQeoQfXWBI24U47giEL1Bq0bkblH3lwdyBR3jcgtVR29wZNm6ikl3NmWWUpNrll3UlwRul6Y/XSyEIMY7feDz+p+NBCEJprokruorO4YcQ8hK09ZI4v/uJdv0P9SCEJbIVL1PXgXhIMJbtoSycQZfpoQFg4HpjtJY/YplziJfGQCycQd1Gp7RM7O71x9QiG9snYo45aQpfcRTEwiWQpxfUDe32Vnz41YDuE+bQ3GPTuqQNuPii6BFyrji3V1rPmU3MKmXlOq6xY+0ufQTO1R1/9WmDGMmqsDRC3w0ZPzpqVp3PRoBntUshBiuC66YQhLZEDb3SXVmBjcYykUwYxjS9QXCTPSoAto/Vo1Eamn3alBHnErHO5PMJI5MYcEybEaRxj6eQkGX3lwcjwHkdPuKXfSSpJu0dUvfAFzagzuGpDXeUJfN3yLRCE1EwZCXFsNvrb4EEtazg8SMH5PiJUEvqR9Uk8lLqWt4wNo5YWCCPe+Pf6SuJHRcvlFD+4l8Z3ULIg4xmjflzmcXI/D76vHDxu2H8JNxOgrOW09V1RDyySnHdkn+UH0Q/WW22madRSjOOXcSt3nEUdxiEr5tdJyiaS+ZFqSonDJg5G7Zy0jqDZhmhjzQfiXQYsYSU/irL5eObzKYy2Yy1zfYTJgXI6bx/zrbGeCNVkt7dOJc39nnNH+yrPkwJ5OXp1gb5tpB++t9OG4itdynfDzJrD6+I8wYb4ByOiak9207rCeVhUIx22zyblzxt4Pi3Ol1VcVpg/I67qRxUE7mTskBJCeQmZjFBkyEjiJeudKGOCPeXoNxgC4tfA613+rnYf6I1WP8ABiuGQyFlruXTLyILsYY/rcmVFyOJwXXTv8sjJbDvMYvSdD+5LD8mHhKqmMya7Mdo2eHFBAZuEkGW3yfH0PxLi6b5qlByl5JyzKfVRUMup4lyVSqZus7JvH8fvCW2TSn6oklzx/TcxaQD7YgoIw/pId1VY3g3Ixk4ns1VrempZakLzTVruyI8nm/uV9eUGzxDlbdk5qSbyU7R7tYXMsd2Y88P2kYb4byvDOVvGLGcTGZOpk9jfvH78uczg0Xvb/Krwbggh3SqwHbITg7nXUN9dFqrcR1rooGXBj4XZ9NFzTSyexR2mqH141ERi3DcZbflqnJ8BcfbVwTifYUdwMbknB3xoCPc1NPtFHuDHc7RLcDI2cDt6cHXg89SCcPVChKQ7l0ndpZPbW1IJ3vXGo7jsx8S5/jwoCHpiIpGmUaHHE0ku5/toZkdx2lvUjVW8bj1Bl+QrghNP2ihuG/ak9osDYbCXuLlu4Hh9NSMmpdVeMlol1WFz/HeK1kX+wdHg0xbXaX16ATh95B10u2O3/rEzl7NBCD7RWB04ahbn20JfokICQMY+F1EXBiIi3+sSyejQEi4pDdvd9TrqPbuKwG3J7RVWKP8qIfWJzHxHN8CWEfWIxu31O7Rw+IpFwlzZlHJxB3TKnOMBTB3LdtL2e2QfaJmoVZkIDJqIuI7PSS7mosAAx7Tq8RWA9JR247amrMCuzTUrtFIQC7ltLuEQ4uIGNw5cDYy1ntb0+mGAa2W21vLobJW27oshLY7jnTtrqKXw4qCqPhLy9KZdxIDEFrE9SBd3Q+GcukgxzKbWn80j6/UH5o/3loIxrr8NwD+pMehYsSVvnOapvDylKW+EspbfdfyxxrG/r6PyV0NzvExfC6ptptpFCE5FjCGQohEIXwsz0+0y4loXlAGXNNcNU+EtJz6ON42D4pek7ZpuQE9cfRXar4UT8s3MoodTmLZ52qih6gpIlx8G+y/ljfofL8lV9zhlGFelCDu6RVldcYbkuEm1NhCP+Us+886BcfiOAW6nJf0K5+U60HBjcdmQyZ2mkobcl0el2fTuKQ3IuYpindErgS+CpGmInmJepw/oICO8b3NTrjVOMhBEtk+gugIMdy0quYD0yF4caAj3LpLdlLJct+f1ExuQZUztNPpoSkcgyW+CoZB9rZ+QplzUUe3b9GhmLHp3NFLIO5poITUt953aZcHb9mhKU5Bk1FYD1GY9H0car3necNTJWTUIIvyF7LuWlm825bpWnqF6Yxj4w7aZ2nmdRMeXBE+3GgdsnFXRNaHcy7l1tK+8Oz2lCCIXhMMGPUu/UTLY7d0nU6iWMlxECBXCTG47vEVgPu1DZ3Cdtb8hWG0DYjI5cmsQAHcjjJ0BwQ/jXNPruvKifM8Sf2iZUrzFkHp3foI4vF6az9njJ43p+VzaU0fNn551MXTBmwGUMBiWYIoo4+XlLywww7vIu8ZOHDlu3cuZaZhGQY87YmTOOOLqR+BeLYW3zfQ0HCZbGVMJ3azeX4ySdyzbuXNKzdoB+N94qMcobLwzKCMhw8nKOOK3HlHH01D/hASOW02Sfz+lZvKYySqCfNmxChj22XkMIW0DKPliHuxF4fSUuwTHhI7Zpmz6mqgY9TSDw1yY8SJW+xI+522ZmIckqgme2XYLOSKDNBudLeXaMx2lrONra5wskfB+JZTNNL98o01DA/Lg9NBLaLemlkIMZLhVKZAQmmMQw3zk6AR9Mkf5FrmG9D+JB+MplqTQ49+Pu4PIgXP4X0ndJ7pHwd8g7baAnZwf3lrjeC2u8wPCdnQl5zWOnkXcoxbRWMHcT18dmvvP+ddObkd58JZExfCHpHQhCAF9peoJMgQBGl3ExL0xIBcajuB3RqQlkWBMZXiJRZLhKglIdcf4SEf+UwfvLPxk0xkXoSYDujWN1xKySScba2D8Fd9P2395cljeGIo2hv7lZNS/bKe4PhFTCE4aj/ZTPB6FceVQtwQf6y2hxbcj89Mueh3PLRxSaqAo7fwi13nUTI0TTUJdWdvMXG8kmlWMiSExAUnKo5mZ5d/CMuXOHk5PB56lbbW7yEpohNMfhUNwQep9RV9JzyoKgl5Hs7pvxLcIPZoBzAL25Bk/Ha6OVcnVmKhKbnFQXKPM7lFLtmh5w/A6gztwuOhHyAyav0lJsi66OsztnYEHqIGQfC4a4MmKEwFOCNn1E/eQdRe5aCajmEEfw2Lh/B/ByRZIlDZ40M3snpuZNpPbez16RhGzIX8Htx5Y+X4t7pjTZHfCSQQs0Ag7ty6q8ZNmeXepw1YPLhLmtktqrJwyCJ8hV6+E2Wy8campZ3Oulj9J9BMZuNpl/21HcDIUdrhroZRy42dZgzlxpSO4ZbuD9p07iED4l3iIWwXIwY/1aG9v5xM4Y7qq54znEyZ7NT8+DKXRCD+Eka3oMn44MnLyw9JHOQ1pxdthS/I6AdzslHqiXzCb03MJbKDNBvn49nzu8+TJF0+j5q4MdF40FH/AM9gRxk6cY6VbQdT3vByZ0N8P8fBkIQvOQ3CD6HuLYKkYlWq67kPc+bKRnHmgV7ej5g+l7htiRh6GdA90U1dhDKL2Rvcg6diKPkuDj6nkrvML5fUEpoOm5bU94cxaMhjcwE3488PU5eXkXLkofHC4PZsfmg4PSUgzj/bhTPcPj52XOKl2T0dCttP+bXW282h/wDqJ18yNf8AdA4+l8M6olJJfMnNNzc5HcmqNhHA4LGbxM6IaOJuYA4iZA7QOO1FbV5J8H2ctw3kdyQzd3PnbeRsJlt7qNyZuyC5bxOADuxxQhHBkj3IFce4PHjT/wCEsH5ugGGf6xokEw/xwtju85Y2cY//ALFlv762b9NPTJ9zzL+UDk8H8P68p+rJPNqtlrwjoY5wwM5Jk02w4Grdh4fXE3XoQdwXEWPjw/xwL/1kDDj68fuLYf6UwmGePnF/hUO9Pqe4VhB/4RrWfbZmV1rdT7kbjdfaga4mdnqrGx4V48cT+FRMR+RcpBnHv/pkz7leOly5/Cum/n/xQYQQZ/5s6g2Rn9xPuLCfFA1wmoO0pErl/juaN5QM24TUj9SFYwTCfG8dwv8ACmnfqe5Vgtv5tdH1RJJe8dVjXbur5idzHkfnaBbWww9SAYlZYdhjL8wnpIRy3xhDM2ZdhFfNmbxJ2Y2zcYhcNWg0tuPTTV9ATwl0ffJ8Xvr7XwjwrMRhmCWRMSyICORfq/CL9QmPzs9JMQmoQil8L7RGgI6+FIUdCYjEXJ1hJxzKXkbE7Qa7CNVc0GMo7a1nm7qLZ4eexktOCMnPHGS3H66mN3AxXBqhxqoGfzmpGjmQ4o1NSQCCjG5DKRNo4HEfUi5b44suXzVno8E8SP8A8p6+ydT73s1wM3hjMu8qC3YQ+0SkfYQ2vmyNguWx8b5aXc7tZIDBzExs4/6U1exwD7+XsI9z6C+PuM4l3LQ+dXiFk6n3vYfuLX2KW/fh6RNehHiNWcaoyejWHuKLrhlOJ4Ryzl1UMjyF2AzO1Gy8amI5jNZv3Issfn5ejuK2+4riPcufwqsQtPpwWm2p9RQSYSVxpthc5Wvh2+ma02zk/nUjbbLEdzsN/lEkbpR2i4wjpP3NzWrH0to/3JySZPWpJVKiWQ27bbKUtgXLEMWeNcnXmG84mVcTyuxyHbzsCSM8tCTgvYAxx7SG34ckW7Hm34ezV88wdr5y3/6TWIWfqR2mcH2YEfcVrPhk5zWJBI/L+B/1aa2IKbuqevQ37tImf3Pmk8P2Y6sqSragpu5MSVE6dypycscdtrEGDwFggz8sEMWa5vdJZ/K8M64ZO9p2P4Lbk5wsyZNNztIIn/v+HyGgl3RMGay0/wDhGYnE9oZt/q19fcjqS5qc4TETT691t/q0uoqV0sPSJMhxBoDi2Ql36Cq3Fy5wbdxcm3wfqC5q4+4iE1LmcjsMfR/J4YEfc3njZxdc4wVm7g8g5YOt/mVS+wz4/YkbNAkZB2yD+mghCDIsz+4/OJlMBkHjXiJLYwdCNhNYILnr+9y5l3FP0+4puV+JC1VPKhjG5jJt83LAZySCL38nLHDBDuwqwkG2YI4HM/LIvsGc6VSO+BaEt6dr5EaEsY/TIW2dOSOIPjXEwY+97MiNNDfVIoJ5zolFdjDluUV6Fg3HaUghLQ9XvEN9NLIPtRLmT5sGmXhmTBkJyE7yNA7YvUH0FIGMnFQhC2TiJZP0aZc09XtEsg9NAMbj1FM4ahs+8Ui2PhdRZmIyBM7JL7NM/VqUEd4S2MhPRrXMM2ewydmPr27kfylj8wHpjFxIyEgH9dbxSY7TMa6/4Xb5nPsXOGo4VKOsHw0xLGmLtCxBfa+F9oeRPhfC+0siHohCEID7TVG1OyTB3O1QDUqNCEB8JSao1tYEwuNQ3g1YEUNwgMjxMZ/Bxue4JAT/AMlxdy5xf1a0jERvdlby53ZFmbfoD8hcL8Tt9MlffAp57skz0pNSNR7g+5uXOhAmW9S1e9RFvsiLmSvF+1Ve84loqsCXCD9mqt5cuXeHcQEgdtLI4ukJaDbt9Bed5xWFeSnHgbFzUlRNL9XsZYzlRGsfikkmMzi5Yy58mSIt7z8y6SRyucT+i6wHVOIVRAjkNTPiGmspLA1NZbhg5bcHh5CQCg8xWa8PtITWqG+EI+psUGuEH3plH6+qsFhmGIzDB3C2ooa8mIDTqey481CTIYzxs8c5rcbiLehhgH5K9BEIS4S0on5XZoc2evseEcY7ZNT6aW4b3SfYjUzs7ahuNIa0dSVsXL9KYDuqQ80yEIL0f7Sht/wwd1WE0H8Mu942/bW5I85d4Sv8ykj225dT6CEwfDuIVydmMt9mhv2hPZoIMfFQ34hPSeiWhiP6cpcd/SK+sCw8YS8dwRZk0HYHtBrhYILcHl8vh6qkDJcHtLYwSAJqBt78CyvECi6kndQOHspZ6DuXAlLw12CC4yivkOODw8vSzwN4fnVHb0XXgyEI+DskBGUAzGJNcgSQRNmoxtIIOSPRtkG43lWtyiFIrryPn9s1gbhntmw6Iz279nr5M/gzZExu4bleElongSOgDgIYN3fHBF0IuXk+Qsfb4f1o2IMo5a7cPfE2wRxjnWSDIOa3dmz3IYtRpp8kakVJQ+JDmVzD3Ny2w6dyqXNAwEqCO8OyZ/Fx/DDcyX2/T+upESiF9uBCbBqEHwVDeTCXtiN2z14EEb8mzswkLBBG4jh9/JB5Sy+pKPrgkvnEy2yYjdO5zAeOAc1gzkl8LDLBAO4aAQsrrV5d5WleU3PKkk9LilJmjuYykcbjbNrgjtuoW2W74d25v+QsNjTw8Wp7bNEk7xnMme3S14F2AlzWblzwbvvfHyKY4mEvbPGctcmDtrscZGza7vkgh6fLycnm+FYvI6HrSUTCm2MpltuVtCNL0A5rBAFuFu/dF4faXRHF0e7S6fofEhsNmObM5i7ZMCTLOElQZHT2AkDfJASPPHBDmjGTfgyZvIFnU+yIh2oC2bgS5cTCEIThLn6PlcwltNs5a+CZoQYyaJJhG9jHBni5YIOU8XguZV0FzTWn2zwhk+DEbkL2jkf21vlJk+BjWBzQgxt7vXGQZPoxrcKHcXWY12nwx8lf1LuQ+X9ztBpiWNMXWG6HZr7gg3F8dmvtZnkT4SyJi+EPSMRfq+18ID7gTUpNQCs/5l8KQlICOlkUlKjQmI8ajuOEphFDccJYAzfEAn3ref8Ado/sLM2dwox/IWgYmPNmlbwno41wbe4UY+MPs8hOmuK+JudCPqVk9yJGEIMZCCTB6vpI0W7be6XT89L9nqQEXKFUHC0hKrmH4QMlniKwIMltcm8ryi3zwjFtXkj2oenHB4wDHGOPPb9/wcvl8qkgha+RIOIcUrhvN8UGdROcTXZzknMEzDT3jCDY/GzQMTe9BycsGeHLDn08+RPdkwklFLzySzbEIPivEJy+d54HWS5eyiPC3JB5ORQ5Hh3VH8V9mZmlsDQbRvPoCO4MjiNk8jNBpRBiJFmijJbJAWDi766D3N1Q5p+VySoGbSUytpOTzOcffDPBswzRuG4vegh6ReUWf2KtnF3KeOMcv/DYhD+RHqaWUHU0nk9N+PgtGtNT2XDDA0LntvW/AaE9bIu0JxNT5CzueSueTJnWhdjaDl1WOWJGcyaTCA0A4IYIBkcE8HJDahhtXbmZd54wlbkjcbaZBPfHcDqwR3IPLWm/nHtEpMJqju8RR3nQTG3+LaW84YxDWiIEdmT4YO101MmmmQfkW1DZjuvB/YVpNPBcHa7vfW5I85cYb+oSQ+zQl9pb4iFcnakjTIO1w0vUE40kwfD+2luB6je0tTEP08SqxlFyUUWjfUXJ4oU28n7OV2qbFUIGD2MjmVHLAGBxARsUPTi8iImZdY3ccO0HidRSO80dRc/LuW1VHzoyt5J8WLk0bSk0xBARtGNtGOawZMmRvyN4G+behjHkcZix9JTJOOvG1YTxzLQz2Zy6SvXYAhdzXcewbG2sBHATrXbhL60QdwfbbimDJp/YW1tn8YHhmeLkjmlSEcSRszCc82pmYyyVQEFo+MIo4IiZ4+iPOLylybfB+tHLhm5KzFLbb2ak+DlDBGML+NqIkHLAHKHgcjvhw9311vBCW7npEvT/AHFIidU2ihIuGPt6PnjGm2Y3MhsPfczJpKztlgjjHMwvLkHv8mbodPOtsJxCeQq+X3P0isPRLXW/dIhmompX6tM7VAR3g7rcg+8GtEwrnG0y9vd6dvf9dcH9pWGH8w8UVA4lpdOC5tAfUi/vLpvhmYtvKa74FlhrnMg9CNyaakKrl7i6NWA13RbDF9r4X2szyJ8Rr4X3GlkQ9Fo018IQH3AmpUCagBKjTUqNAfCVGmEUciEwuNV8wJaGrCNc3UDzZm5FgDI8VJhtOzyjW+HvRj09/ch1OX7Cr2Y/g9ovTUeaOCTarCF7Bg2twekjij99TCXBaq+e46/dm/7YZFZPucqBbi45GMXziXp9n8uNMubSNFzid4qQqhbi3b07yyeYUHMG08qR7LZa0g8bT6VPw28kGiG1fi83oLWLmnb8G4q9wO56PyF625aPELMjl9B4iNpgN8MxhkI9AS8Sfx5MkRnl2K14cvDjZ7ivJPS9cMsJyU/P5a0ns72m5AzfzDO240MUGcnWggi5M+RaJb2a35fkIcEIQlvu1sbWvwwJazJ2dH1pKabHKfc3Ln8A2x3cbB3MA7KSZxP797l5BDh3Otbh3OoodP4b1Q2qSRvpkzZjsOWj9482sMZh22xQxs+SAcHJDkjjJd3NyFaxc1Cdp56j2x7QQl5NtXxabyS4MJw+DbjUNxpDuqQQdzUUN5c4q0yRsJfqPLfUtqwmlvaLfqcPpqHIx/DCEs3FImAx+MCWumPT+jAtzDecv8G3zH0gR7g7ZEItkJb7NCuTryR7VV9QTyV03J3FQTc2Rkw1DR78cY4M+X/zVgMl0feIJbKO19RHEXEKIptFxhSO+Bn4+cRhGyb3CT52PzyS83+hDfnOYL27hakdjuf/AMU5j6XqwRLSGZCcVWDfaLem8NcIueuSngj6nzJ2CIGX/wAIzCPaLQp9MSRk6g5K5/8AODKmE5xmE7ZvtLmZTccHpJK5gufUWkfDBj1TbiZtDglu68MT51eXJfwR9f8ARrZp8JlZOdJguP4NtlREjuW8nufedOL82Rfv8JvCO43EI1UEjP0IPc08/r3Fre0OP5YbU9KjaHly4J4b9LGpbkn4I+v+hmnwmTj5ymFba4UvuoHAPp25A5jj/o5IE4nOuwlbjy2q3J5EA6ReZ/sLTnBHHE2w3d8VWDeYPCjH8MN+lWTT8l1tx9f9DNPhMb/hXYVlubNLa+jt93SLyPo/5l9E512F/ZSHEQkH/wDUHn8y2Ybxxc03hv0qZtjjhDeG/SrYvyX7Uf8At/oVo8JjP8KbDfZx8nubr7U04P4ovP8AQmSXnHUJOaslbeUyesmjpwTZ70ypty2bfLLFybq14ZHgyfhhvP1UTC45ZkbEMYgzjt8VTsTcpLrStDcc4fyJZd9Da6kpNcouoBvWY9Zdo3IvM9B1I4puaeIH3Q4jaPvIP7q9ASOaDfNxkEZfQJZ9Ey2laOsvYdKdJn/MhR8/5lIW0enwlOHA2w7pU1KjQC7gy6iLi/UID7TUpNQAlJqjoAUYiZGo8awBHeOLQ1l+IlSbE3Jb4nDgg89dhUk4GxbkWDzR4OragIUhvgTDoQd4b+6tDEptEmypXoHHLSKzK5Xjw8Ys9TAHGB2chI85h01HBATzlIcc4h4L/wCQOLf/APn41sH2Uu5a4a4Nybl3V1rbj6lK4+iPZ9zGyc5BwL/q64zeZ/Frf+2mN+cQQri0PAHGD1yUrk/8SLYB8QlwyPapflP2/cjrR4fcxv8AhCOC3BlwBxmBGTrkprJB/TcUMnOIbi4mD+J5CDtk/wCIPK+X1VsDxwNzpJnd6xlHflP2/eIrR4THx84RuVvtP3GcT9Pp3KfyRkzfk8JN5BOcI3GPafuP4n5Cacf3g3938fg8K2DUudt5iW4I4t/tptEt+37i4jwmLj5xDdyT4Ng/iUP2klyftpf8IBnpufuS4ia/U8Sx5x7nh31sBCE743rpYyOLmn0/3Vhflv2/ckbWjwmT/d8HtexDwlr7zI/FWj9PooHjJd1fub1d2g9Rp18/Q6y1B4TTHdN8hV+f8y1lvs+D3N1txHhOPb44ElBCCLg/iI7g4kblpJYzBH5nhXaM5p42Zs5sRmYG3j2iy43Ix3N7JH50K6SVj2GRkL3YyE8hUYxkVxKUUcCci+wNvjUsZctEtoR2n7CFunTkgdsXCCglvi94gfFQTiqIcwMyEETZh9mrS4O2qfT2gfpNP6KsG9vhrmptFt1ST5tizGzzCk+ZI4uoTUSx20zT9pGNLINa5XEgdv5aB3BcPUS+Lwt+4geqT9hCENnJctkUhmS0S2lkt/8AppbjSIO1+rSALC4NMtjUcZBlHqpmpxbPDWxAxGcT0aYPUJd4nnpeoXVKgbi4swQ55KxzcY23DOD8GMPs4/yq4w3xEcNpgSn52a29adODvIPL5FHtjuKnqSl/HbcZJabYJo01AubWf5EfmxK7wnFlya6F8sSwlJu3wL0PTErmjd6O6IyuBkXlfDvGQjKYEpuqfgE0aaZgk+1B5UK9CSOqGcyHpGXeNOoeRWjfAutTpLiWljcDKi4tgH6hCUvQNX2vhfaAEsi/VGcOBjQAQlpc3UFQN2QyXFDqStJfKW5ClMEawOpK4nGIk0JLafNbl0H4Y8/YH561ZuZRLIrWrKAccpF4mYiVJUkw9y9E6johLbl4Tgsg/l5fP8iBWkrl45azG2s7g0uRyNvJGY2LHTgH1yb8ZPzx8qsCEtcX/EC+eYliK8RXVpCGkClm5u5u6hZOIQSj2yF1e7RcukGRFslv9tVJokgY/m0shLQyIGT5y2q94T9YgDU1C8RSCDHbH9RRxjILidNA7ZSeYPoITDCONO3Z9Go5CXSW+H5CYMhNRR7d0hLn0EPGwJbLxdNGoLUKb5CBj1ODuEQRwPvkJiG8cd4o7ce0uBiTHji6TTUim2ZCOLhfkKKG9Ztt8hcTQg20vGxHe1On8lU4+0EpE8uOZhb6gPpqPy9n5a6eXRbQdzhMvalk+e8XcHct8NCNMRO8QsyxJHolH1Ezi98jZx2/PQA4IS3p9MakN3F3V7zoJep2hvUjUMYxiJpdAnQ9dVeIscFfcc58QSl1EHUdWpcD4eqmcnZqOMmnaL2fdplsly7rZCKmOItkgdziJlvtVHHpcPoKQhCBB+mRs/5/6kwaB20MSP8AgxPMJ01M2glvT6Cjxobk0/MWbYJm0EKi5ab95GljtlIS0a4pA9PiqUAMgxejTG5NT7CWl2yD7bhrMEeoKXp+rW+zT+Whd2+gbfgMPzoI4csUKz/xpiRhDMB+KJkaoZcTgsCf8YbvkdU/21pgyE7U24lzSTyudsyS2bMwu2pOoRb0piT0lxtK+3UWEpPLb4F6DMP+dZR8/J4tezLYHoNMzZ3omHH58EW8tsk9aSebjGRs8CS4vKdYYH0vVox7SEJ7GmGB/nNbgh8gvJlOP5BFw8fNvrumx7ThdjlN5Sfrtn/wxt8jw8sJPpxLp5P4ml3fnwimPsWO1NeI9+DmDcvbJm0DX89/GHP3pIjcUtn1JVQC5v2y2Y8n5chf3kHx3/3QOUk2YuB7KZaluAzQraOAn5/eP9pXbc9KO8jkPU2LyPFA/oZtg+/S3EwbtuIa2vAbfFv/AHQidD/5uaXkQ/LfzVtB9SAhIlIJS/O8qkdqrccqdkVzphlEvM5j/pisrxzFJRrnXD1I1zCEdqB7MqDESn5IMhHs4CO36VYHWnOgZzace5KgAmns4t3LLTftweWSPojh9dZ235t9Pubbmtq2qirHXXgfzCy1JH7IGT7S0Sj6Lk9EysjGSSeXMIOuFoKzB/RyKlm/iNlv5EIxj7Gu5Po7G8o2dH1JUjgcyxDn1/tPFrQscAR+vH2v2V2g2bNk3G2ZMxAAPoQDFkg/o5Ewn+2lkJw7OnANcq/OPTK6nVZlc5MLd5hhCaZFHuaftEeDS46XctrUNUZb1O7uI7O2lkcXdNLuWtTrrAC3ji3pD6aj6hSXSpdwZCaXyFI4o9VCYCD/AFiWQYxW7faI1NTtI/IS9TubiiBDmhJg2GPxazvx3IM9x1ZgHB+OP4olM4Y9T9Yl3CIt3f3FKShc09NQ3BCXLakE+v5ajuCafdxqJxwzbIZLlwf211jMY5TJyOS6cfY+vEufl7cjlwMfUGrCoJgPaBsuwH9tbMg3cWW8hK7Q8lP3iVepc1Ppplz9WgZCfN9RGndXQndtQy3DCXBcQKEvial5CExN1P7iVxNRM+Dl1EsYyIQh2X+rS3Go3Gmf+n0ED1R6qwcbq3HjjaHW1IX1g3cDLwlaNx/3FR29mcd5Af7auG5FzDjGzrUk+Zz8ouWeUgkW+8QQgxDtddFy7cSyW14VhIGQnfJaWTuidBMtj4oumsTEZb07oksnCTBj00uNALbkUwerbUO3pj0UxuQguL9NZtmRM7O3ZS+Jpk6FxAyf340wZNTyPMIpTENMSPCTvkES7mp3cCGQzw6vGTLg7lu96ihjt3Uwmn++gJl3s+7S/SE7NV+oP9xMGS0PjIekzT4vXUe5qKOQhOKmDH2hdNI+YJA7fFKlkcEt3RpdvU00u5qcZAM1CXLqCE00sjgnc/LS+1Q8JBEu4O3b7tLIRRyOB9n015Weky4MdxV5CEcj09OPqJje4QeomDGMXDCvQDdv2g0ej4cHUSyXCJmnb9INDwXs93hmtwKOQezE43EImEJ2XJ8hLJxPMGoiVsXs47mn00wlzZyE4dvr92md35BEE0kJiOS2Ins1DuXCd2pDgnai7TrqRT8rI+ebTZ3BqP5q6CdssJe38SSvbS8fsfXiVHbukJcVhOHHjJxabcmgDofvKvHqEtE010UoxaQdzg0ps7Na9VBp/o0vkt6akW0E9itwtSPcJct2fUuITOJ2251EICR6VH8/yEQIGP0yACaSG9u3qmS9S53Yxpep2iAYS2QdqzxExm4cdp0x/rPORqFt3TXPPQ41R3RakY+gtObldoRn1wKjFpDbGc0c0NCwHqao+gRMH0yKrZvO1bG3FaDcD7niLno8JwDjVO6IE/tED7O100fPdNLGPu0NUmcQn24Fj/OcxEqzC7D9vVFJPGjR0SagaRxnFAaCzFm8pa4O5b4KwfnsN9pwnl7a8Ef8Ymu+Ts8sBffVhhraHJhFemZKz85IvCPGTEQuLk0wLxVDKTzpg3jdheSzoZIYISZY/i6UBIOqrrEfESrqbx+wzoSVTIQJFUoz+MYCNYI4yRw5suSPqrJ+b+SV0dzlp5SkknzSuhzKXRkc1IQWeYacEPLxPDly5tPzl1XOAmErknOMwnqSdvAsJfKW5yPDE7ODPF5PJmVzMS7KZ2lCdUx9cjYcR0unUejCEIMekvO805wFWNudE3oAbwQ6KG5HKXkFqDPtpG27qdXUW6S+tKXmVJjr9k82uSbMd/tNqOCAgR5s/Sy+QvA45g8e4fzysvE8x8fTapgT7xk3l+dkOBvGfcu8u9dzOVr4XK1XLifL7xImG7lVZ/QSoKgl9N0/MKkmRrbKUtju3MY+nkHB4V5hwzrznW48zyHEOSzKU05QjSajHFKr0AzPAwx6ox8uSPfhh6UccXqraqwbkxIwXmHij4XBVEh2gMHeXIISZFl/NzxZw7oqi5fhdN5wFhNwTEjcLa1HqGMboe95/lrGWilllylFS9N8NIHraLaFbsyx54GLGImGXuHHh5ONkJPZi7aOYNlgNc4WTweH103DjFesMWecFWjGSz3lHh9RnwQIYBQfDXkMdr3yeRmhLGuZ57jN5MiYbtmJrcHjV3ej7uCKxD9JL5pDd5hdXmIGA07sjO0exzZhH13EHR+sO0RbraWfw6umFeUfTM9oRaPVFy6O4oc4IRtK5o5YmtnGyOQMfdx5IvAmNydmodSadPzT/uR8/wBCJc6zzJNdOp4SkPOa5y8nw+HjROqwlE9p4FRQSF5LXDCCBySOzd83q+REvUPOUrypKJwLcV1R04NJppclxAmtQR273U3t3rrwbT8qpCW4ds6qHVQnFYtKitsaYOKBy2I2HBB8JjFF6Td89e2udI8cTLmzuJk+Z7A6d+KnBgj3NnNFHBFH9BdfiDTF5nhhr3ZG87Rmn6kjm7t+cEUTaqcXq7l09kM9kIHcqCDJAYcZoBE1eSEcHUVTzxcYKvwppCRt6DmPKxqKezHI3KNre0xweGOC3lj8uBUvNDkmF8kPMD0biDMJtO5lJmpJrKztMgZfHD75IRE8G9vkyqtxzOznXOZol34gmNStaGl0btzJ5YK8dxGSOLqeT74lrpQy5iXScsN+WRH/AFTZsBa4d4kYP0vVsyeGdvXbK2/MQUEFxyOPKSPJD5S5Gl6wrV9ztavoh7UbwlJy2RBdtJb2e0xQNs0fR8/lXLczshKbZ1fhcUju/KZrt4Qu9wzcJILfLBywdLdiGozytKLwz51lSTuupx4m8ZSEYwmII2Qm4Dl6ubNwlp2bc08hCc84Ry3GCUcai65qNeYjVlK6nLiDPjTYjCajbstoFBBGNtki5equJxIrTnMUTihK6JFW0oB7qHv3qCQUEdsJDRDHcJk3cq6zmT7Q5perJ3rEau50OzGTy4YPDHk/SQKHj5bJzpMKxdfQyQdTPtMakRFDc4qMUwju0y7oErfzFGsctN4lucIRyl5VRmldgl1yOZMCwZCTCHNy5eTc4UfqrzpK8fMXK/Hh/hXRNVO2lanmLppPn5GsF4cEMfxx8mSKDLAO5Fyr2mPh6nyF5kwbk8rFzusWHo5a0GZgOOzHa7yMFzwKKRfbil5biIbt8Nxi0vm4fM9NtxjbNxtrxj2BjHeP0yZevH5yj3CCIQQ+gmafZqOQhO1VHHeQiyDITtrf2Ey2MekjTLqF7NMGT/bUQC5bGMShvOH7NMcEILVUcg7vDDcj6iKNhsW3ZkcuNmF2nTV5NHA5SzHKG2nGQe/H5iZK2Y5Iz8ZPtQ5+hAqNwTaXlzvNSNWUhKdtZf4PIRmF3V8sPeID1SW0E+aTBj1P8dBBBjF6iuDsxYxk9mo4x9r3akDSyXLfYoAuXR6SEvtUICYMen3aIEWyXB+Qj9ZbQmBfCfc+Efto5O0QC7ZBavURp8XrpnskvJ+dCEjuBk2jbW3z0HeKQ3eXB3BakCWQhBXPI9GlkufhIvlwd5/eVfPSNzjRqc5i2E3+la1LQZBlHdF+rTBkHc/1ir2bwZR6RtwnXVgMjcntCKiOMcap1Jgx6a5+tKQpev5OOU1jIQzaXDJfsnz5M8MHm8sKuLkHcJhPS9DyFm24tqPRmqcvR+HdB0IQnuJo+XSkh9M0bcW+SDyOWPl3sqZVeFuHNeuBzKtaLl09O0bxtAmdijjthi+OD410gx2hpY+J2ylTMPQXXVHPvzHGVbWiKUBRg6CbyJoCntnseLQZ4A2Yo/DyjUf7m9Bio8lANqVaDpon/wAN37O9Hm+0ukGPTtiN8hBLgiWy6il2h7xR1z+56V8rk8rpuXt5TKGYWjJpptmw+gODyVTzDDvD+bVAOpJtRMuPNAakD8jXfz+vyLqCDGUZBk+gRR7Yx2/trC654jwo6ooOjKt2MlT0q0m2wZyNr+fTzfHywf0KO8oek3NUDrDxC08fA04H/bDghgy5UyvP+T4ykN97hvWhJlB/2KE0PKVZnK8P64YziaFZSd3JQP5jGPb2BWYTbESZXY44CCJFGTQ72CHJ1FtMJW4hXHkSt/3GwW3hdUXT6iWQZJkzI2LZOA47ZuvASCLq+8s3GzxgJNJxo1EOUbQ0HAEEwbRurI3Orsxfe3owf4zqRhXI6klEwbySZGmIGUplQLzAnBbvYt2zch5LRdyC9px9Iqj2Xgrrge2zoJfhPhfJHDeZy3D2nWh2nQN4vg0/5syvJ5S8nq2V+KakloZlLiallx0M8K5usJHXE7w3nkofS2UP5o/HBsbaWZ4ICb8HLv341y8np/EyQDk8plsnmIJceYvnEyCPZoGuyuDR8RuPl5Rj3epBFlhUraVu8a3N8NDH/kd5T+G+H9JOHE2kFNyiUxnHrOWm5cg8/l8KmS+i6XY1A8q1lIWgJ27HbM87YkH5/wChZvK6friWklZKkDMfc0wlTVpMpUMoTNbPio+0/Bh9KPa7S0Sg5XPGVFyeW1QYx5oBkMbmMnT8zP6sO6sHkqbjXczzPcv5BK6Ho2S1A8q2W0tLmk6mVzbH4xZDOI4svhz/ANCj1ZhvQNekblrCj5TOtnHbDtYs8Y4IvxLqB6WpxPMQ4t9qo9pc56o5mFZDk9PyuQMG8kkEtaS2XtB2wtmgoIIBwfzKjnlB0ZP6gb1RN6VaO5vLbexvyZ7zfLHmgt8vrLqCEHs/G31HIMbkfs1jdcruVGbZHGQgrnG0/lqvZyel2U4eVJLZDLgTd9+EvxiggM49ePrKwIMlzUMhvpk4NxY1RJhef0KYT9IlkHqXeugdsoydnGoAM7Tg8RDj0fTS3hB2/PUMjj23mKNbhm22BLhFeS9m3lrfxu+6fDDB+yo7eXt2IxzKZdDsYB9pGo8wmBHrghSG9SDqDgW7ISi3F1rLbDcNXOL/AIQ1iEweOHzzV0/MGl8PhoTBj4neLodDu2mkNIoRuyC2l93cTUogx2+DbjQ9FkIo/Z3VIIO5+4ljGQdzR9IgDs9T9xCCcO110ICZwhr9X2TVHqmtpdzh6PqITCyEHc8xGp/cTCd5yhRbHqeWhCR/SF+QmDtoIMZPm0D4SAHHCQPTJ4Uy3dGi3a4v00BDI37Vtpx9eDoXExu87rs9PIRMIMfZqO4Z3CDci0zj08/mfnVfNyNyFaNyilxLBkTEK2eb/JaDcXdVMGTh624ufG8JtGzOQ24/qf5lYN3jcY7qpaVtLoc3HDvy65ZdC05GF1RzgMWJbihVlE0bTdOzaXU0Mjs0ZM8DkbUeTlJFHv8AVudRaxhfiYzxEoNvWxQhlMFyMbyA5YMjeMcfv6nL1V5jqin5hN8cMVHIq29zTUkuOQ0ZMkEDgI4BZ213l5YYoc/mL4HUEsrLBvDeieST+IqefVf4snGzl0XEGlFnjITl0812P5S6hcgy603QnLTOP2JLCNx6D5xjwn3F54+lEyMOMdggXLAuSPdNDvwRwq0wjmkvLh/S8kLOAnm4JC0cPGxCwbUO5BxCD6UOdeY5WRxJKPxoomn3jt3TUimLHxbGQueAfwy3k5I/ZwK4wjldNyTGigy0LONrdTaQkPUMA3UBrbqKCPPc7vodBROSPQqRVpHOHp1ixw09xsnOIxAqzDyl5fO6OeNBujzHZPhYs8BIMip8J8XMS32I7jC7FBnIxuiSYc2ZuZZ0CBiyk5PqR/SS+dx8BoOVlIG5cmtvJd9DEuHw7qCV0JipMCMpl7s4yUpG7czXpmb2216znzxQ9SAajl2EOSm9O/f/APZi2i1ynpT3WUu+mHi2UVVKHbrf+DDdwRx7v83KuTo+aV+2riqPd/Ukj9y4CferVbQbPv8Ag5Ojy5/0i8mTRmyFMKHrum6PlFL7fUUA4/Fs/M8M4gGYX4QI0WhveSr/ABEl/wB/MeGzYNyMjhpw+zyvxfK85SfhqGuCrWH/AKRbIe0G84k7lxsTafS4hzt9rgCN3BHGQPe8nJydVY3g/NKkrHFLEDEOpGc8YSRoMDGSQTK8FtZh3uU4hkyw9CDeIsTw/ptnSWOGEZWMyMSOdyVpMzXC9eIJx7ng7K3BkXp3GqTt6pwoqSSTCpPETU7P4TMjl0W8EMebU9HF+NQRYRJuWNa4Q3928wt0cHedXTdWUvUm0e5upJRNrGmbYHYXVv18vLurn8aK4cYeYX1BVDKztrRvbZxk6G0kjyjWJ81txJKbrSeUB7ipGxnzSXAI8nElmEbprMQw5chc+eODNFf3sn0FYc8ScOHMnpejRmNkm0xjdmgb9MkA93kHBB1uIsditTyWkaeZ4li27SdRze8WJ5iZh/UnuxeBJPpKQ4zbIKAOiQOYW5D/ADEXm9lVFd/c7oMpaqm989ZnbvzEmBvhDb4D4Bk5VoGD9aM22NlaS1kzm8tl1Zy50/YMH4shmcY4M3g+VDcyrP5e3GKg8Px2bgPdmfib+/kZ9RWqWUNOq4YZRyj7RNxDcM1HszFiuPcBh/UFYjZ3/Fo7gQk6GeKPLB4fNWb4F1ZjhVP8acQzSg9Lz2XEdy2MFn4OaE0ENuPoxD7TprQK8cU22oucEra94kt7O8ti4cEUeXP8led8K5XJ21d1BgnJKwNWFB1JKo3bl4AuSNubJ3nJ1su7FlhVbKNtusrzTv78uogbb3KN8w3xgkeK0rmkykjM0tBKSbObbywQR58mbli9WHyl0g6kk7luQjKcS48DTUNGN1BHAP8An5eTlXj/AAbb0/SWD+JFbTuQzd+cn3hgZgLkjI2Nk8EHWt+VcUfDiWctOYmt/E1KtKel1Q0I+f7M0nUcwgcBibRkHFcJvQ/F0eqp44Sh2CqFZQgSQYRvPZA5xJ3Oz25w0Jfz7NbdwR3MvTyeDl6qXJ6op+d3PFE4l0ysaceyOoDW4/yR5eVeVcE8HadqDBuaVsWdO6fmkylz6Sxx3cjJmHPmJH4PBy9Pr5V2XN3gk9L1BPKS9zcugnQGTVw5nEsd7UF7B1PK8vqLSfkGGkKUhUYxSRpYTxHoi5tJNLTUPbdTz+Go+0OOIXT8tMl8reTIloYeJ11UVi2M1CE09TzBq0by9vKW43z0N90ToB/x0YUXGcg021l26H04+oP+hV5HBHLghS78cfTjW7KYbcXW6X+G4Mt/jc3J/wAg8cOHJCOXOpH6PoD9RLz6dpBLaZxLavkt5HXtNIaRQjdCAcNMGPiE+bRydolxrw2RhCDtjudml8Il1Luanx7iOL2KHoz0qWMiCXPnEsftkIQ4vfeYhLJb7M1xCAsPZ9mi2jTJq9RMGP8AR+WgC2QiXbJcUjhaiX6JAFsY0u33qZw/aI5e0QC9RM/WW0y4O3aUe3+f0edAFslxFvukEJaJqdmmZPzoCGSXjcjI2c9AihkG4ZenB9cf7ytCXC8Ts0slsigfYRMIpWa03IMziOIy+cYF4X1lNHlQT8Mxdun5Lhrcwjgg/wA2VdJNcL8O53RY6ALTYWkkaEgI2C0LZjHHD14I/KXSeL27khHLY1vy4xqPbI2IQbkNyDvh76rn0TbVMEKjGEDkJ3BpiX4298Dn5XgvhvLaLeUA1pv7yTIgyP4CFjzvY4fiuE6W74FcUnhPhxR1QEqSm6VaMJpG22SMwyx9CGCGH4uXzRrOyN6gpf3QTKkgzEZyTUDBtGcpnvwLdiLGIZPD1lIJUmLGzuNm2twcbYeTZ5fBBBnsi5SRj6W/mu7qZv8Aj11ziVLjDkNTSK4oOj8RGbeW1lLdvagJfCHaow7+TwdnywqHS+EeH9CjcEpKj2kt28dg0e/HGQPkb3VVHQ8wryZVJ/Gl5MRy4cugIEJJeGDaDRGPyavLk6drZ9yBcW8HiRJKkqiZUtLatP4ycEHAYgtxvHFH0/jIMoPIyQwEg66NpWupquGWupr0r5KjsGfNvwXYuBvRYetM+0wO7102cccPvweDf8pWk0wXw7mTieTJ7TZiHqUkBJqbajQbRljufl8ryVV0XNMTHNYM21UeNySskmAQ0eyBgbeNrIr8He2vJ9IuPeM8SJRWFUTum5PVpAO3MF41qOM2S9/k3JFHG3Nu9CPJDFBD01Jk6tfzNId55WvvNIHhHh+xnknqQUhtvZE2G0lptqj0wjz5IPj9JGugqSn6fqiRuJJP5aF/LnY7blsTtP6Fi7z7tkglc4mzaTzfxvM3rEjzYGkBoyG8T5eW3ycuaG3t3TWsEeVxscr2KTyh262YfjLa3cbW2bJD4bdscebeUb6HILS5XCP3FtfeQ8P8K6Dw3buG1CU2GW7XxjDLGaMmX4oc8WaLdUicUPSc3qSV1bNpaY83kv8Axa5umg2eP1OTlyxLn8VJfVFQUOzlraW2JoectL2yFMaAYc/vxx8o8keXJ0lx7hnWlJM6kpZsGqKlmh6dYsGEyG0jjCRzDdhjjzxR6fEg/RqRFbnS3OMlRD+Rok4w3ped1K3rGbycx5uwHbC5G7NBbg3uTwcvJDy5euqOX4B4WbHL5b7m/gspe+M2cG1myDcxZdTp+YuDJJ68F7j2xabqjapE9O02YDuONqQO2eHkPtAo4d+EXXKO3GuoxQkdUPpxMJlTYZiQA6d2TO0LkjIaJ+CIkA/SWICpFLiFwTcMqf5GmTCTyuoJW4kk3loX7I9xvGEmfUVfR+G9F0ANwWkqbC0jd27xh54zE/JvxLCJ/TNcEl+zSDknkta7FMgNgv70ZtjI/EQIc9/OI1voxRXMq/JwzqQs8qDRqIEuftprZjbljjM3MQwt4eaOHSiggzcg/a7y9QzTwXTKws2SR4N0HTdPzilpTJzeK51rvGzssZoCR+X4YlR03zf8I6JcDeyClTAdAGdveI7Nn1oIoS/j60Ma4MbOvCyun2wg1QCNoO2wtuzZG5oplAS6e7Hnhg2X4oCZ8vRV5hnTdWCrBw5mQZiRqTxlZ2gUcFvM8i8Hv+r0V44pbSFUOa+4tq8R2Mkw8oWnKRJQUmkQgU8QkZI2F2OODU6fS5cyfS9B03STMktpKT7IM5N/Vjjj/piXaN6bIIY3M3MFpB/afzKZtDNjbFKWfzzgW+T/ADLTQiamaqs9+ptMSjz/AANJzK9vTdse2zY1gHkd4pjyYXR7My0Gv0IyfzqG4I4cuLj020H8siOGPUVoxIIa59500hgiJbjd4o+wWyW+Nw0vhplv++lrcL6EKQt/rEXO7S+ITUTCaQ+CgC52f0FHIQguF0+Gmd39RLtjKT2aAB27ikdn7NR7fajUgekMaALmol2+87NBBj4d5MGTT8xARyejDpoTO08/yEICYMnZC6fXQP8ARwIH+jTLZOKXs0Ay32n0EbOP/wDYl3NNLJcIgGE2fTGIwc4/URtAy6fedNV9sf8AcRbuICYQmn+wjP8AmUf2SCWyIBlzUIWz8tLudp9RBB/OJY7ly7w0ADuEJ6/UTCD1NVMGPTuciCDQEcdwlzs1Itk7JHaaqZxOxQale8kbNzxdOMnTt/6FXt5ONlqNjGIAhOz/AHF0HF9IlkWDkuh3nNV+RZmEUKSQx/8AZjX4+vb6a5eeS+YTKsJfszwzTYGwzmjumg6JvIHywwxZod3fXaEGMukUKCD7K9+k3/tKu/DltrraV6nPOfD6v6SvUzNvVFaD8RuSTIxzv7BIGw5fBkcZjZShjj8GnkFqKrJXFUNiTSdsZ88O6PLtnyeL44IG70cDrIPkF4Iuvb31sHC4TMMflx74f9KXtkrIS25C7B6a7Bk/ryryLEw12YRK5zCZhvsmfzitKolrecOSzgI42hBjMHxVHBAyDEaDkgNf8EXSH5sSXI6gqmZVpJxTt5b+91yMI2scEBMzbNym+l1FpluRiHpTgxDD7EfTTBs5P/8AUjDudQjWNQZPU8hrbE/4I+hmcwmFQCxIcDlsyKMbscuBA2ILOHJkLfcZ4t0dvSi3ekrCXzyoJlQc0nc3NvkGcgYAdnAMOXwwZfLiguLRByOTvhkZeMhb/UcCjg/N1kDl9Ly34MWcBHb08g8+QeX1eRRKvckEGGzOclMfQ89jb1I58XylyZ27Jclw4I3bszULgMTZwTfIHlz9fLH7NSNjqRk4mEtJPp4/nYGRG7N433Aka7HByZyeHrX7mTtM63xwOi7mmZ278uMAtz9ZlRcpdt+DSF2f2hYIP3lsQbmPCTow+Zc5URMnpOXkcvJoKW7WSTjIDxbGQsccfQ1+Jv8ATXYDoOcTcYxtgrqPHjgtvxbLZc08+1ej/r8H2VDeEePtNy8MeAfUIXJB/RD4IV7DDVuLrWosmMGm3OfJJXy+l5HJNObzjaz/AMmaa0f9SuPHFoezS1mFhB5ZMkZv3YVXk0x2haEHkDQO4Rbrci015lsxgbTXGvi/wDgjcrgjkmufyyb8f9KZp27olHJ3fDTB3B+jW3oXLTbbWhIualvvEsno0anar9QH4RL1Ewn10u3cIgC3ct3dTz0wgxiH+2l27Wn10wlzvuH00BHt+x/bRbGLtrhFItjt3OHANRyD1EAdqjhDGInaJcaYT2yALmojhavUS7nacRMt6iAOETjW0JaEBMGS1wwpg7g/SIQgAnCSxj9D66EIBmndS7dzSsoQgDZyI0xad7idRCEJgtjt/HvpdzT/AGEIQhC3aTB3BE8xCEAzP+ZGpwyoQgPhff8A6iEIAXwhCA/BjQQjMhCDEH9tCFMBg9jbcLppe2XO500IUIBx8JbkbOQ3AH044EEI3JbFZ3EIQ8pgSNoJb09RLINCEPRfaE7NBCIQpgFy4gg+14iEIAINLgQhQgCD1EwftkIQBb7NA7Y0IQAS4XhaaXb09RCEAu52XedNBLdzjb6EIBYx/QTLdvih+WhCAXb4fkddM0+0Nw+ghCAXcIhCEJj/2Q==" ) test.equal( parse_pic_data, expected_pic_string_data.{description="", picture_type=1, mime="image/jpeg"} ) # From http://towerofbabel.free.fr/test/music.ogg flac_cover = string.base64.decode( "AAAAAwAAAAppbWFnZS9qcGVnAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLz/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBhQSERQUExQVFRUUFxQWFBQUFRQVFRQVFBUVFBQVFBQXHCYeFxkjGRQUHy8gJCcpLCwsFR4xNTAqNSYrLCkBCQoKDgwOFQ8PGikcHBwpKSkpKSkpKSkpKSkpKSkpKSwsKSkpKSkpKSkpKSkpLCwpKSkpKSkpKSkpKSkpLCkpKf/AABEIAMIBAwMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAEAAECAwUGB//EADwQAAIBAgMFBQUGBQUBAQAAAAABAgMRBCExBRJBUXFhgZGhsQYTIjLBFBVS0eHwIzNicvFCQ2OCkhYH/8QAGQEAAwEBAQAAAAAAAAAAAAAAAQIDAAQF/8QAJBEBAQACAQQCAgMBAAAAAAAAAAECEQMSEyExQVEyYQQUgSL/2gAMAwEAAhEDEQA/APD6eqCWgelqguwYyKRKw6Q7NWQsPuk2hkFjxiMPYcwIWJbot0UTMcdzI3Ip5mZY6nEW8VtiTMC0nBFUWXQY0LlRNCBu7J2FKs0oJtvRLUxcNVSvdXuss2rPg8tenadL7NbclQqRnF2ady3Hr5Syz6Q22NgyoScZxaa1TyMb7LmdF7TbfnXqym3dyZmYSF3c3LZD42UThqKjEdZ/X8hVKvl68yie0KUPmmul7vwRyKDk91ZgOMxdwHF+0EHo210M6rtOL5+BpBF1MQDykVRrRfFehNz0/feNopSK3MeTISCxKpYn7wGk7C3gU46nVBNsSu49H6oeEyjaEr7veBgYhCMydH5l1QdYBo/MuqNKwQqqwmTZGSAxJi3SKRJB2CQkMxXCx0hCGaMyMmQJyiL3RtMg1cdE1TJOJg2imWRZBRJJB2C+EwzD17ACZZGQdp3EdGe8y3F7WhSVlnLkvVsy8RjdzKPzc+X6mY2LbtTDHQjFbTnPV2XJZAohWFUJDiEYKROFVrQgIwC6VdPXJlriZ4RRxHB9wdtYeuiu5biNEUGaLFIqxj0LIlOJ4AFQIQjCnR+ZdUadjNofMuqNUwKmRcS1ohIG2kQXaT3LCiXQWXHLJ9l+BtjpTujMNpYbeaSzcmksuLySsUYjDOLaeTTaafNZNGlbSm4xIixtlWRRbGmihMtjIYtWukiuVMs3SCdjF2hKl2DKlmEbxOKRh2H3bDOVk3+7hM6FkNj8PuxSfK77xb4HGbrIkRZORGwDoDiEZiEIRhIQhGCEIQjCsc7q3ISIpE0zFIqxL0LkinErQxlAhCMyyh80eqNUysP80eqNmwKMVDSRJxHURNjpW0JMkRsFlsar52sQc7jNDWNGNIiyY1h9lRsSirCUSTQZSWLISZbTZQkWwkEul6oXIe7sy2lVyHfBhA+Cp79SMObXqG7ewmb8unIt9ncPetHvfk7eZo7bw18yHJlrKOjjn/NcBUjZkGg/H0LMCaKzyT0hYaxJIYwIiJDWAJhDjGEh7DEomYkTihJEoxMVKKKMZwCkgbGrNAGBRCEEVuG+ePVG0kY2G+ePVepttE8zYotES+LyeWbtny6Lt+hFRE2ZS4EXAJaIOKN1Mp3RbhZuk1TG2UPujqJa4DOAZQQa5Dyw8nHJat8M8h/dnW+xXuKl6NbKWbp8N66+W743sNKDhJNriHYaakkzV9rPZx0pylGL3b6NWt2GVhaNl6jkXMlFssp08m7ZLXsvkieGpXkhyul9lsL/ABIP8Tt5NerNLbWD4guDXu1FrWNmuqdzqtq4ZSSkvlkr35XzV+xnJzflHVw6uNjyraWFzeRhVKdjvdrbNOWx+CsVwqWUsY9iO6XOJFxKE2raGsTaItGFEQ9hgabZJEoxIl1OBtNaeKJqIki6FM1CIqIJtBad5pKABtSNnHoxTQAIQgiuwvzx6r1N9RzMHB/zIf3I6WMCPLdaUwircGUS7c5ElTI7PpQ6Y7ohiw6v8N2stVbO2f1Rb9mDuB0s5URnSNT7IM8KbrbprLcCLgaM8KUzw48yLYD3R5ZvO2WWSt3vtLXTHVMpCVXKcpayk+sm/Vj7pNRJNX1KSp1CxobHoXkvHwAYo39i0LeSHK05LJLvOp9nqvvKHu3rDLPjF/t+RzE9fBfVmhgcS6dpLh59hPPDqmj8efTkt2rs23Thlp2HJ7SwGuR6SoRrU1Uhmn5PimuaZzu1dnvP9CWPh0ZyPM8bgGrtIzpRO2xuB5o57HYC12joxu3NfDGsNYvcCDiMXapoaxY4jboBRhC4RGBKlSCKdHMAIU6NwuOHCcNhQn3AtGUCqBk7chZx6P1R0k6Jg+0cbOHSX0AaMUQhBMIwS/iQ/uXqdQoHMbP/AJtP+6PqdaoHPz+4tx/KtUwilhydCjmaNGh2HPb4WmOwlPDBUMMGU8OFU8MSua042csIL7Kayww7whpkbtsOeFB6mGOglhNQWrSytZa3vx5W6ZlcckcuNz1TDA8oWN2rh7GdXw9jpwycmeOgDiPYtcLEXEtEDUIZo6jZtC0V4vvMDBUrvwXidbSpbse4YqmENH1fjkgisrQfQrjDN9yLsZH4BpCWsrZ/tHLCzcrb0HffhpfPWL4SXM7TDYihiob9JqUXr+KD5TjqjzbaMMvEzcDtGpRnv05uEuafimtGuobx9Xk+HNcfFeg7R2Ws7eWZzuO2Zbh5Ghs3/wDRoy+HEwt/yU1fxhf08DT+34asv4daEuxy3Jf+Z2Ymrj7h7lMvTz7G7Mu9Ldpk18G4npGM2YrcPUwcbstchpSW6cbul1Chx8DYq7Mjf4ml3r6D08ItfD/AxeoHDDh+GwneEUMN+hpYfCijKFjRsP7riHukU1oLgLowJ0rnPe1qzp9JeqOpS7zmPbB/FT6S9UY0vlzghxAU0J2b/Op/3R9TtIQOL2b/ADqf90fU7qlE5P5F1YvxfIihRNGhRBqETTw0DitdmGK+jRvqG08KLDwNTC0ERyy07MMAtPAjzwLtodBhcDvIsrbOsifd8jbjvTkK2EAquGOnxmFMqvR7Dqwz2hni57EYYAr0ToMRSMzEQOzCvO5IwqtIpdOxdtfZE6NeDhP3lOdpby0S4qS4CcTqx9OK+xOx6N5LrfwOkkvh6sy9iUc+i/X6GzP/AEopEcqqhHN9fQnjl8AqC9QjEwvF2GhHI42GXRnP1oWduWnedViqWbXMxsThr9UWxLWVYhMJnRaI/Z3y/wAFCh4za4tdCyjGUtW7drfkERwS4+HALoYa+oKO6hh8NfsSDqVC/QnTp3D8Lh/0JZGiNDChLhYu3bIjYmptROOV/DqCF9Wd8ihy1Abaqs7aanK+1WtPpL1R08nc532xVnS6S9UCnx9uaEIQqwvZn86n/dH1O8oM4LZs7VqbXCUfU7SO0Z8HB9YK3ocv8jG5a06OGyS7beHNGhI53D7WqLWFJ90o+aYT9+TvlRjbkpv1bOG8Wf07sM8I6zDTzNjCVEcJR9pmmr0Xbskvqg+h7ZxWtKfimSz4OT6dM5+PXt6psWa4hW06sXHgec4b27ox1c4vluv6XLqvt5QlrUffGX5C4zOYXC4f648sJc+rqbGMqGNiGV//AEVGayqR78vUHqYlPSUX0lH8xsMbPcdOVlniqMQzMrsPq3eifdn6GdiIvk/BnZhXn8mIHEcQNhNZg8Wro7Mb4cGcrc2THJmlVfxRM/Zj+Hv/ACC60viiP1J3BKnLJ9WEuWVjPjUy7y+FUeZJ3BnY6nm/3kZdamn1NvGRvmZVWnyKTMlwZ7pPqL7M+NgmSfIjuvkP1l6VMaKRdThcnGl3l9Olp+7A6xmK2hRDFCy6alVJ28fEUqwtyPMdLd8Hq1+CZVVxFsgWVQXY6XTkDV58u8Uq1imKuLs+k6UcrnOe2E7yp9il6o6CrWscv7Szu4dJeqBs+MYghCMosov4lbmjapYmpwT7rmVgP5sL/iXqdXTnFaZE88un4V48bkz44ir+FruZKpiZ85LusaLxCJe/JdyfSt4r9s2GJmv9TfUs+3yXFPy9A37Sv3cZVU+XgvyG7k+iduz1Q8NoPi7dGycdo/1S8E0SqJ8ovuX5AcsNJvJWXZw69g+OUqeUygieJz4PrFJ+JNYlfhz/ALimGzprk+jQRT2dO6e7dKztnZrlcNuJZckltTsl2u/loX0dsSSyc10djZwmycPJZxcHyvfzCI+zNB8X5krycfyeY5sWHtDL8c31syUds34eKi/VHQ4X2dpQkpRvlzzXgyzEbGhJ3ul/1jkJeTjP0ZsbD7eSytH/AM/ky2W2r8Idzf5k9qYyjTSUowrSvb5d23WUbIwq1SEn8kYdicn6sbHpvkl3Gs9sR5Lul+ZbT25DSz8Uc/JU1qm/ElQxNG/y3vwbl9Ginj9puupyUlnGor/8ba7ncHrYRf1dXTl9LmRS2goK1NuHZGcreDbRN7Vm/wDcly11JdVnyfpl+BNTC/1eUl6or+zdq8SqO1Ki/wBx/vvJffU0s5LvUfqN15B2sRFKhb9Gix02uDA1tl/0P/qvoS+9+cYea+pu5kM4cV8pPtBqlexZ96R4w8JfoM8fTesZeKB3K3Z/YV1Cuc7Bn2qk/wAS6pMZqi/9S7016B7rdj9s1yuyz3lkGLD0+Eo+JGWzk9Gu5g7sHsVl1alzB9oo5w6S9Uda9kPt8jmva3Dbjp9ql6ofDkmV0F4rjNueEOMWIuwq+OPVdhvpwXHveZztJfEuqND3b/QnnNqYZ9LVVeK4jPFX0afUyFJrmXLF5E+g3dHvGSXInHFyfJLv/MzY4lpZP99RSxDfF9L5B6C9xsrEK3zL6DxqRlo33XMfD4dyeq77+hox+FZWb5u3oTymjzK5fB6uGd7qXj+ZCW0akclJqxKVZrXdS7LFDxN+T6DY37Tzk+PDf2Ntl1JbjfxWyf4ra9/E6TDTkuR5zKraWWXb2mpgNry4yfYS5ePfmH4uX4rvo4hg20MY1FqObel9F2s56jtWfMI+92/mSOTpsrtucsAYjD1JPOWfPL0SBKuBktXfxN330ZdjK3hb8TonNpy3hl8uZqYd3bz6WK/dy7TqPsvPPqTWGXJFf7MR/rWuWlUaVrPzIwrNO7b8Tp6mz7g89jR5BnPjfYXgynpiSxz4t9iy8yMcRla5rz2AtQf7j/bDOXjbt8gVYlw5Po0XQxylpnbNt5JEKmyrOyv+Ylsx2azNcsbGkzhR2i9LJ8rXCY4jLNeasUKlaO6nFLrmVTpSjZt378hfFVnVPY111bPIaNS+gDDGR5X55J2JPHq3Z35i9NVmc+RrIuQB948bO3EqqY7PibpyHuYtVV2tG/FmJ7TV3Jwu27KVr9xfSx2Wb8vyANt1t5x7/UfjxsyLyZS41mCEI6nMeDzQVKr/AJBYahLWVjUu2/HYV3OEIVK06X85wnShGLs24wjOLlJKzu8s4vIapsFxcE6M3KpUdKMY4nDyk5pJtfDBq2dr8GmnYVP2i+aUZ1qUqmdVU1TlCc7OO/HfacG03dZ6vOzsq8PtFU841sQm5ud9yi/jlbelnLJvdjfnYHgBD9nZp2eHqprVPEYdO2Tcs4fL8S+LTPUitiTWf2au7cVWpNX+N/MqdtKc/DtQntq6lH3+ItK90qdBX3vmWUsk+WhCW07Rcff4rdeqtTtlucN7/jh/5N4bwKp7HqLJYepm7JRxOHe9JvdtG0HvNNWaV7cQavhJKG/KhVUFPcb99R+fL4bKF21vJPk8iOI2spyjKVbENwlvwahRjuyvfeVpa3zuSjthKMoqtiLTd5fw6HxPLNu+ei71cGo1ynpNbGm20qNRtPdaWKwze8s3GyjnJLVariPX2Ju/DKE6c2pyp3qUasJ+7gqk4uVNJwlutNarNc7ko7deb+0YhOWbap0E75q+Tybu8+Nwf70ildSq1JpTjB1NyMae/BU5yUY33pbkUld2Vk87B8FtgB8wnD+ZT715adCVHEK/Ujd6bHUvsbCtJZLzDaWOS/wZ2+RdQjcduiZ3FuwxPIksUYOHxDi72yev0NCGLT/Ujnx6Xw5ZlGj78Sq8mBRmTVQncVJR6xjXHyJwxr5Iz/eIeNRi9I9VjWjXi+NuonDlmZyYoSa4sTp16qnVBzpcQXEpNNLiu8sjXY1aeWiv0NLZWsljDqYOKadn0IOi3z3eTWXjqa7b/CvIjKlc6Jyo9r6Ys6edk0lwSVskQWEbWfr1NSphFe9yipgG9H0KTl/ad4v0y3R1V7LzfIr3GtcufaaFTAS56eBS8LKza15q/kVnIncL9AI9jBsdJu11a1zRnRfL8wLadO2723LYZbpNeKBEIRVMo6hQhAoVKjqidTXxEIX5CeipaeIo8BCMHwU9RIQjJ5LKej7iMRCBWqTZKWqEIBUovXqKEtf3zEISKRZxLmxCFy+FsBGFm+bCmxxHPl7dmPooMdPMQhL6N9LZMlFjiJmXJlkdBCJ1TBG43AQgw4d6jSefcOIZNWytrUcQx4HiszI29rDo/UYR08H5Ry8v41lCEI73I//Z" ) parse_flac_cover = null.get(file.metadata.flac.cover.decode(flac_cover)) excepted_flac_string_data = string.base64.decode( "/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBhQSERQUExQVFRUUFxQWFBQUFRQVFRQVFBUVFBQVFBQXHCYeFxkjGRQUHy8gJCcpLCwsFR4xNTAqNSYrLCkBCQoKDgwOFQ8PGikcHBwpKSkpKSkpKSkpKSkpKSkpKSwsKSkpKSkpKSkpKSkpLCwpKSkpKSkpKSkpKSkpLCkpKf/AABEIAMIBAwMBIgACEQEDEQH/xAAbAAABBQEBAAAAAAAAAAAAAAAEAAECAwUGB//EADwQAAIBAgMFBQUGBQUBAQAAAAABAgMRBCExBRJBUXFhgZGhsQYTIjLBFBVS0eHwIzNicvFCQ2OCkhYH/8QAGQEAAwEBAQAAAAAAAAAAAAAAAQIDAAQF/8QAJBEBAQACAQQCAgMBAAAAAAAAAAECEQMSEyExQVEyYQQUgSL/2gAMAwEAAhEDEQA/APD6eqCWgelqguwYyKRKw6Q7NWQsPuk2hkFjxiMPYcwIWJbot0UTMcdzI3Ip5mZY6nEW8VtiTMC0nBFUWXQY0LlRNCBu7J2FKs0oJtvRLUxcNVSvdXuss2rPg8tenadL7NbclQqRnF2ady3Hr5Syz6Q22NgyoScZxaa1TyMb7LmdF7TbfnXqym3dyZmYSF3c3LZD42UThqKjEdZ/X8hVKvl68yie0KUPmmul7vwRyKDk91ZgOMxdwHF+0EHo210M6rtOL5+BpBF1MQDykVRrRfFehNz0/feNopSK3MeTISCxKpYn7wGk7C3gU46nVBNsSu49H6oeEyjaEr7veBgYhCMydH5l1QdYBo/MuqNKwQqqwmTZGSAxJi3SKRJB2CQkMxXCx0hCGaMyMmQJyiL3RtMg1cdE1TJOJg2imWRZBRJJB2C+EwzD17ACZZGQdp3EdGe8y3F7WhSVlnLkvVsy8RjdzKPzc+X6mY2LbtTDHQjFbTnPV2XJZAohWFUJDiEYKROFVrQgIwC6VdPXJlriZ4RRxHB9wdtYeuiu5biNEUGaLFIqxj0LIlOJ4AFQIQjCnR+ZdUadjNofMuqNUwKmRcS1ohIG2kQXaT3LCiXQWXHLJ9l+BtjpTujMNpYbeaSzcmksuLySsUYjDOLaeTTaafNZNGlbSm4xIixtlWRRbGmihMtjIYtWukiuVMs3SCdjF2hKl2DKlmEbxOKRh2H3bDOVk3+7hM6FkNj8PuxSfK77xb4HGbrIkRZORGwDoDiEZiEIRhIQhGCEIQjCsc7q3ISIpE0zFIqxL0LkinErQxlAhCMyyh80eqNUysP80eqNmwKMVDSRJxHURNjpW0JMkRsFlsar52sQc7jNDWNGNIiyY1h9lRsSirCUSTQZSWLISZbTZQkWwkEul6oXIe7sy2lVyHfBhA+Cp79SMObXqG7ewmb8unIt9ncPetHvfk7eZo7bw18yHJlrKOjjn/NcBUjZkGg/H0LMCaKzyT0hYaxJIYwIiJDWAJhDjGEh7DEomYkTihJEoxMVKKKMZwCkgbGrNAGBRCEEVuG+ePVG0kY2G+ePVepttE8zYotES+LyeWbtny6Lt+hFRE2ZS4EXAJaIOKN1Mp3RbhZuk1TG2UPujqJa4DOAZQQa5Dyw8nHJat8M8h/dnW+xXuKl6NbKWbp8N66+W743sNKDhJNriHYaakkzV9rPZx0pylGL3b6NWt2GVhaNl6jkXMlFssp08m7ZLXsvkieGpXkhyul9lsL/ABIP8Tt5NerNLbWD4guDXu1FrWNmuqdzqtq4ZSSkvlkr35XzV+xnJzflHVw6uNjyraWFzeRhVKdjvdrbNOWx+CsVwqWUsY9iO6XOJFxKE2raGsTaItGFEQ9hgabZJEoxIl1OBtNaeKJqIki6FM1CIqIJtBad5pKABtSNnHoxTQAIQgiuwvzx6r1N9RzMHB/zIf3I6WMCPLdaUwircGUS7c5ElTI7PpQ6Y7ohiw6v8N2stVbO2f1Rb9mDuB0s5URnSNT7IM8KbrbprLcCLgaM8KUzw48yLYD3R5ZvO2WWSt3vtLXTHVMpCVXKcpayk+sm/Vj7pNRJNX1KSp1CxobHoXkvHwAYo39i0LeSHK05LJLvOp9nqvvKHu3rDLPjF/t+RzE9fBfVmhgcS6dpLh59hPPDqmj8efTkt2rs23Thlp2HJ7SwGuR6SoRrU1Uhmn5PimuaZzu1dnvP9CWPh0ZyPM8bgGrtIzpRO2xuB5o57HYC12joxu3NfDGsNYvcCDiMXapoaxY4jboBRhC4RGBKlSCKdHMAIU6NwuOHCcNhQn3AtGUCqBk7chZx6P1R0k6Jg+0cbOHSX0AaMUQhBMIwS/iQ/uXqdQoHMbP/AJtP+6PqdaoHPz+4tx/KtUwilhydCjmaNGh2HPb4WmOwlPDBUMMGU8OFU8MSua042csIL7Kayww7whpkbtsOeFB6mGOglhNQWrSytZa3vx5W6ZlcckcuNz1TDA8oWN2rh7GdXw9jpwycmeOgDiPYtcLEXEtEDUIZo6jZtC0V4vvMDBUrvwXidbSpbse4YqmENH1fjkgisrQfQrjDN9yLsZH4BpCWsrZ/tHLCzcrb0HffhpfPWL4SXM7TDYihiob9JqUXr+KD5TjqjzbaMMvEzcDtGpRnv05uEuafimtGuobx9Xk+HNcfFeg7R2Ws7eWZzuO2Zbh5Ghs3/wDRoy+HEwt/yU1fxhf08DT+34asv4daEuxy3Jf+Z2Ymrj7h7lMvTz7G7Mu9Ldpk18G4npGM2YrcPUwcbstchpSW6cbul1Chx8DYq7Mjf4ml3r6D08ItfD/AxeoHDDh+GwneEUMN+hpYfCijKFjRsP7riHukU1oLgLowJ0rnPe1qzp9JeqOpS7zmPbB/FT6S9UY0vlzghxAU0J2b/Op/3R9TtIQOL2b/ADqf90fU7qlE5P5F1YvxfIihRNGhRBqETTw0DitdmGK+jRvqG08KLDwNTC0ERyy07MMAtPAjzwLtodBhcDvIsrbOsifd8jbjvTkK2EAquGOnxmFMqvR7Dqwz2hni57EYYAr0ToMRSMzEQOzCvO5IwqtIpdOxdtfZE6NeDhP3lOdpby0S4qS4CcTqx9OK+xOx6N5LrfwOkkvh6sy9iUc+i/X6GzP/AEopEcqqhHN9fQnjl8AqC9QjEwvF2GhHI42GXRnP1oWduWnedViqWbXMxsThr9UWxLWVYhMJnRaI/Z3y/wAFCh4za4tdCyjGUtW7drfkERwS4+HALoYa+oKO6hh8NfsSDqVC/QnTp3D8Lh/0JZGiNDChLhYu3bIjYmptROOV/DqCF9Wd8ihy1Abaqs7aanK+1WtPpL1R08nc532xVnS6S9UCnx9uaEIQqwvZn86n/dH1O8oM4LZs7VqbXCUfU7SO0Z8HB9YK3ocv8jG5a06OGyS7beHNGhI53D7WqLWFJ90o+aYT9+TvlRjbkpv1bOG8Wf07sM8I6zDTzNjCVEcJR9pmmr0Xbskvqg+h7ZxWtKfimSz4OT6dM5+PXt6psWa4hW06sXHgec4b27ox1c4vluv6XLqvt5QlrUffGX5C4zOYXC4f648sJc+rqbGMqGNiGV//AEVGayqR78vUHqYlPSUX0lH8xsMbPcdOVlniqMQzMrsPq3eifdn6GdiIvk/BnZhXn8mIHEcQNhNZg8Wro7Mb4cGcrc2THJmlVfxRM/Zj+Hv/ACC60viiP1J3BKnLJ9WEuWVjPjUy7y+FUeZJ3BnY6nm/3kZdamn1NvGRvmZVWnyKTMlwZ7pPqL7M+NgmSfIjuvkP1l6VMaKRdThcnGl3l9Olp+7A6xmK2hRDFCy6alVJ28fEUqwtyPMdLd8Hq1+CZVVxFsgWVQXY6XTkDV58u8Uq1imKuLs+k6UcrnOe2E7yp9il6o6CrWscv7Szu4dJeqBs+MYghCMosov4lbmjapYmpwT7rmVgP5sL/iXqdXTnFaZE88un4V48bkz44ir+FruZKpiZ85LusaLxCJe/JdyfSt4r9s2GJmv9TfUs+3yXFPy9A37Sv3cZVU+XgvyG7k+iduz1Q8NoPi7dGycdo/1S8E0SqJ8ovuX5AcsNJvJWXZw69g+OUqeUygieJz4PrFJ+JNYlfhz/ALimGzprk+jQRT2dO6e7dKztnZrlcNuJZckltTsl2u/loX0dsSSyc10djZwmycPJZxcHyvfzCI+zNB8X5krycfyeY5sWHtDL8c31syUds34eKi/VHQ4X2dpQkpRvlzzXgyzEbGhJ3ul/1jkJeTjP0ZsbD7eSytH/AM/ky2W2r8Idzf5k9qYyjTSUowrSvb5d23WUbIwq1SEn8kYdicn6sbHpvkl3Gs9sR5Lul+ZbT25DSz8Uc/JU1qm/ElQxNG/y3vwbl9Ginj9puupyUlnGor/8ba7ncHrYRf1dXTl9LmRS2goK1NuHZGcreDbRN7Vm/wDcly11JdVnyfpl+BNTC/1eUl6or+zdq8SqO1Ki/wBx/vvJffU0s5LvUfqN15B2sRFKhb9Gix02uDA1tl/0P/qvoS+9+cYea+pu5kM4cV8pPtBqlexZ96R4w8JfoM8fTesZeKB3K3Z/YV1Cuc7Bn2qk/wAS6pMZqi/9S7016B7rdj9s1yuyz3lkGLD0+Eo+JGWzk9Gu5g7sHsVl1alzB9oo5w6S9Uda9kPt8jmva3Dbjp9ql6ofDkmV0F4rjNueEOMWIuwq+OPVdhvpwXHveZztJfEuqND3b/QnnNqYZ9LVVeK4jPFX0afUyFJrmXLF5E+g3dHvGSXInHFyfJLv/MzY4lpZP99RSxDfF9L5B6C9xsrEK3zL6DxqRlo33XMfD4dyeq77+hox+FZWb5u3oTymjzK5fB6uGd7qXj+ZCW0akclJqxKVZrXdS7LFDxN+T6DY37Tzk+PDf2Ntl1JbjfxWyf4ra9/E6TDTkuR5zKraWWXb2mpgNry4yfYS5ePfmH4uX4rvo4hg20MY1FqObel9F2s56jtWfMI+92/mSOTpsrtucsAYjD1JPOWfPL0SBKuBktXfxN330ZdjK3hb8TonNpy3hl8uZqYd3bz6WK/dy7TqPsvPPqTWGXJFf7MR/rWuWlUaVrPzIwrNO7b8Tp6mz7g89jR5BnPjfYXgynpiSxz4t9iy8yMcRla5rz2AtQf7j/bDOXjbt8gVYlw5Po0XQxylpnbNt5JEKmyrOyv+Ylsx2azNcsbGkzhR2i9LJ8rXCY4jLNeasUKlaO6nFLrmVTpSjZt378hfFVnVPY111bPIaNS+gDDGR5X55J2JPHq3Z35i9NVmc+RrIuQB948bO3EqqY7PibpyHuYtVV2tG/FmJ7TV3Jwu27KVr9xfSx2Wb8vyANt1t5x7/UfjxsyLyZS41mCEI6nMeDzQVKr/AJBYahLWVjUu2/HYV3OEIVK06X85wnShGLs24wjOLlJKzu8s4vIapsFxcE6M3KpUdKMY4nDyk5pJtfDBq2dr8GmnYVP2i+aUZ1qUqmdVU1TlCc7OO/HfacG03dZ6vOzsq8PtFU841sQm5ud9yi/jlbelnLJvdjfnYHgBD9nZp2eHqprVPEYdO2Tcs4fL8S+LTPUitiTWf2au7cVWpNX+N/MqdtKc/DtQntq6lH3+ItK90qdBX3vmWUsk+WhCW07Rcff4rdeqtTtlucN7/jh/5N4bwKp7HqLJYepm7JRxOHe9JvdtG0HvNNWaV7cQavhJKG/KhVUFPcb99R+fL4bKF21vJPk8iOI2spyjKVbENwlvwahRjuyvfeVpa3zuSjthKMoqtiLTd5fw6HxPLNu+ei71cGo1ynpNbGm20qNRtPdaWKwze8s3GyjnJLVariPX2Ju/DKE6c2pyp3qUasJ+7gqk4uVNJwlutNarNc7ko7deb+0YhOWbap0E75q+Tybu8+Nwf70ildSq1JpTjB1NyMae/BU5yUY33pbkUld2Vk87B8FtgB8wnD+ZT715adCVHEK/Ujd6bHUvsbCtJZLzDaWOS/wZ2+RdQjcduiZ3FuwxPIksUYOHxDi72yev0NCGLT/Ujnx6Xw5ZlGj78Sq8mBRmTVQncVJR6xjXHyJwxr5Iz/eIeNRi9I9VjWjXi+NuonDlmZyYoSa4sTp16qnVBzpcQXEpNNLiu8sjXY1aeWiv0NLZWsljDqYOKadn0IOi3z3eTWXjqa7b/CvIjKlc6Jyo9r6Ys6edk0lwSVskQWEbWfr1NSphFe9yipgG9H0KTl/ad4v0y3R1V7LzfIr3GtcufaaFTAS56eBS8LKza15q/kVnIncL9AI9jBsdJu11a1zRnRfL8wLadO2723LYZbpNeKBEIRVMo6hQhAoVKjqidTXxEIX5CeipaeIo8BCMHwU9RIQjJ5LKej7iMRCBWqTZKWqEIBUovXqKEtf3zEISKRZxLmxCFy+FsBGFm+bCmxxHPl7dmPooMdPMQhL6N9LZMlFjiJmXJlkdBCJ1TBG43AQgw4d6jSefcOIZNWytrUcQx4HiszI29rDo/UYR08H5Ry8v41lCEI73I//Z" ) test.equal( parse_flac_cover, excepted_flac_string_data.{ picture_type=3, mime="image/jpeg", description="", width=0, height=0, color_depth=0, number_of_colors=null } ) encoded_flac_cover = file.metadata.flac.cover.encode( picture_type=parse_flac_cover.picture_type, mime=parse_flac_cover.mime, description=parse_flac_cover.description, width=parse_flac_cover.width, height=parse_flac_cover.height, color_depth=parse_flac_cover.color_depth, number_of_colors=parse_flac_cover.number_of_colors, (parse_flac_cover : string) ) # The re-encoded tag doesn't match the original one b/c mime string has padding in it.. expected_encoded_flac_codec = string.base64.decode( "AAAAAwAAAAJpbWFnZS9qcGVnAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAl//2P/gABBKRklGAAEBAAABAAEAAP/bAIQACQYGFBIRFBQTFBUVFRQXFBYUFBQVFBUVFBUUFRUUFBUUFBccJh4XGSMZFBQfLyAkJyksLCwVHjE1MCo1JissKQEJCgoODA4VDw8aKRwcHCkpKSkpKSkpKSkpKSkpKSkpLCwpKSkpKSkpKSkpKSksLCkpKSkpKSkpKSkpKSksKSkp/8AAEQgAwgEDAwEiAAIRAQMRAf/EABsAAAEFAQEAAAAAAAAAAAAAAAQAAQIDBQYH/8QAPBAAAgECAwUFBQYFBQEBAAAAAAECAxEEITEFEkFRcWGBkaGxBhMiMsEUFVLR4fAjM2Jy8UJDY4KSFgf/xAAZAQADAQEBAAAAAAAAAAAAAAABAgMABAX/xAAkEQEBAAIBBAICAwEAAAAAAAAAAQIRAxITITFBUTJhBBSBIv/aAAwDAQACEQMRAD8A8Pp6oJaB6WqC7BjIpErDpDs1ZCw+6TaGQWPGIw9hzAhYlui3RRMxx3MjcinmZljqcRbxW2JMwLScEVRZdBjQuVE0IG7snYUqzSgm29EtTFw1VK91e6yzas+Dy16dp0vs1tyVCpGcXZp3LcevlLLPpDbY2DKhJxnFprVPIxvsuZ0XtNt+derKbd3JmZhIXdzctkPjZROGoqMR1n9fyFUq+XrzKJ7QpQ+aa6Xu/BHIoOT3VmA4zF3AcX7QQejbXQzqu04vn4GkEXUxAPKRVGtF8V6E3PT9942ilIrcx5MhILEqlifvAaTsLeBTjqdUE2xK7j0fqh4TKNoSvu94GBiEIzJ0fmXVB1gGj8y6o0rBCqrCZNkZIDEmLdIpEkHYJCQzFcLHSEIZozIyZAnKIvdG0yDVx0TVMk4mDaKZZFkFEkkHYL4TDMPXsAJlkZB2ncR0Z7zLcXtaFJWWcuS9WzLxGN3Mo/Nz5fqZjYtu1MMdCMVtOc9XZclkCiFYVQkOIRgpE4VWtCAjALpV09cmWuJnhFHEcH3B21h66K7luI0RQZosUirGPQsiU4ngAVAhCMKdH5l1Rp2M2h8y6o1TAqZFxLWiEgbaRBdpPcsKJdBZccsn2X4G2OlO6Mw2lht5pLNyaSy4vJKxRiMM4tp5NNpp81k0aVtKbjEiLG2VZFFsaaKEy2Mhi1a6SK5UyzdIJ2MXaEqXYMqWYRvE4pGHYfdsM5WTf7uEzoWQ2Pw+7FJ8rvvFvgcZusiRFk5EbAOgOIRmIQhGEhCEYIQhCMKxzurchIikTTMUirEvQuSKcStDGUCEIzLKHzR6o1TKw/zR6o2bAoxUNJEnEdRE2OlbQkyRGwWWxqvnaxBzuM0NY0Y0iLJjWH2VGxKKsJRJNBlJYshJltNlCRbCQS6Xqhch7uzLaVXId8GED4Knv1Iw5teobt7CZvy6ci32dw960e9+Tt5mjtvDXzIcmWso6OOf81wFSNmQaD8fQswJorPJPSFhrEkhjAiIkNYAmEOMYSHsMSiZiROKEkSjExUoooxnAKSBsas0AYFEIQRW4b549UbSRjYb549V6m20TzNii0RL4vJ5Zu2fLou36EVETZlLgRcAlog4o3UyndFuFm6TVMbZQ+6OolrgM4BlBBrkPLDycclq3wzyH92db7Fe4qXo1spZunw3rr5bvjew0oOEk2uIdhpqSTNX2s9nHSnKUYvdvo1a3YZWFo2XqORcyUWyynTybtktey+SJ4aleSHK6X2Wwv8AEg/xO3k16s0ttYPiC4Ne7UWtY2a6p3Oq2rhlJKS+WSvflfNX7GcnN+UdXDq42PKtpYXN5GFUp2O92ts05bH4KxXCpZSxj2I7pc4kXEoTatoaxNoi0YURD2GBptkkSjEiXU4G01p4omoiSLoUzUIiogm0Fp3mkoAG1I2cejFNAAhCCK7C/PHqvU31HMwcH/Mh/cjpYwI8t1pTCKtwZRLtzkSVMjs+lDpjuiGLDq/w3ay1Vs7Z/VFv2YO4HSzlRGdI1PsgzwputumstwIuBozwpTPDjzItgPdHlm87ZZZK3e+0tdMdUykJVcpylrKT6yb9WPuk1Ek1fUpKnULGhseheS8fABijf2LQt5IcrTksku86n2eq+8oe7esMs+MX+35HMT18F9WaGBxLp2kuHn2E88OqaPx59OS3auzbdOGWnYcntLAa5HpKhGtTVSGafk+Ka5pnO7V2e8/0JY+HRnI8zxuAau0jOlE7bG4HmjnsdgLXaOjG7c18Maw1i9wIOIxdqmhrFjiNugFGELhEYEqVIIp0cwAhTo3C44cJw2FCfcC0ZQKoGTtyFnHo/VHSTomD7Rxs4dJfQBoxRCEEwjBL+JD+5ep1Cgcxs/8Am0/7o+p1qgc/P7i3H8q1TCKWHJ0KOZo0aHYc9vhaY7CU8MFQwwZTw4VTwxK5rTjZywgvsprLDDvCGmRu2w54UHqYY6CWE1BatLK1lre/HlbpmVxyRy43PVMMDyhY3auHsZ1fD2OnDJyZ46AOI9i1wsRcS0QNQhmjqNm0LRXi+8wMFSu/BeJ1tKlux7hiqYQ0fV+OSCKytB9CuMM33IuxkfgGkJaytn+0csLNytvQd9+Gl89YvhJcztMNiKGKhv0mpRev4oPlOOqPNtowy8TNwO0alGe/Tm4S5p+Ka0a6hvH1eT4c1x8V6DtHZazt5ZnO47ZluHkaGzf/ANGjL4cTC3/JTV/GF/TwNP7fhqy/h1oS7HLcl/5nZiauPuHuUy9PPsbsy70t2mTXwbiekYzZitw9TBxuy1yGlJbpxu6XUKHHwNirsyN/iaXevoPTwi18P8DF6gcMOH4bCd4RQw36Glh8KKMoWNGw/uuIe6RTWguAujAnSuc97WrOn0l6o6lLvOY9sH8VPpL1RjS+XOCHEBTQnZv86n/dH1O0hA4vZv8AOp/3R9TuqUTk/kXVi/F8iKFE0aFEGoRNPDQOK12YYr6NG+obTwosPA1MLQRHLLTswwC08CPPAu2h0GFwO8iyts6yJ93yNuO9OQrYQCq4Y6fGYUyq9HsOrDPaGeLnsRhgCvROgxFIzMRA7MK87kjCq0il07F219kTo14OE/eU52lvLRLipLgJxOrH04r7E7Ho3kut/A6SS+HqzL2JRz6L9fobM/8ASikRyqqEc319CeOXwCoL1CMTC8XYaEcjjYZdGc/WhZ25ad51WKpZtczGxOGv1RbEtZViEwmdFoj9nfL/AAUKHjNri10LKMZS1bt2t+QRHBLj4cAuhhr6go7qGHw1+xIOpUL9CdOncPwuH/QlkaI0MKEuFi7dsiNiam1E45X8OoIX1Z3yKHLUBtqqztpqcr7Va0+kvVHTydznfbFWdLpL1QKfH25oQhCrC9mfzqf90fU7ygzgtmztWptcJR9TtI7RnwcH1grehy/yMblrTo4bJLtt4c0aEjncPtaotYUn3Sj5phP35O+VGNuSm/Vs4bxZ/TuwzwjrMNPM2MJURwlH2maavRduyS+qD6HtnFa0p+KZLPg5Pp0zn49e3qmxZriFbTqxceB5zhvbujHVzi+W6/pcuq+3lCWtR98ZfkLjM5hcLh/rjywlz6upsYyoY2IZX/8ARUZrKpHvy9QepiU9JRfSUfzGwxs9x05WWeKoxDMyuw+rd6J92foZ2Ii+T8GdmFefyYgcRxA2E1mDxaujsxvhwZytzZMcmaVV/FEz9mP4e/8AILrS+KI/UncEqcsn1YS5ZWM+NTLvL4VR5kncGdjqeb/eRl1qafU28ZG+ZlVafIpMyXBnuk+ovsz42CZJ8iO6+Q/WXpUxopF1OFycaXeX06Wn7sDrGYraFEMULLpqVUnbx8RSrC3I8x0t3werX4JlVXEWyBZVBdjpdOQNXny7xSrWKYq4uz6TpRyuc57YTvKn2KXqjoKtaxy/tLO7h0l6oGz4xiCEIyiyi/iVuaNqlianBPuuZWA/mwv+Jep1dOcVpkTzy6fhXjxuTPjiKv4Wu5kqmJnzku6xovEIl78l3J9K3iv2zYYma/1N9Sz7fJcU/L0DftK/dxlVT5eC/IbuT6J27PVDw2g+Lt0bJx2j/VLwTRKonyi+5fkByw0m8lZdnDr2D45Sp5TKCJ4nPg+sUn4k1iV+HP8AuKYbOmuT6NBFPZ07p7t0rO2dmuVw24llySW1OyXa7+WhfR2xJLJzXR2NnCbJw8lnFwfK9/MIj7M0HxfmSvJx/J5jmxYe0MvxzfWzJR2zfh4qL9UdDhfZ2lCSlG+XPNeDLMRsaEne6X/WOQl5OM/RmxsPt5LK0f8Az+TLZbavwh3N/mT2pjKNNJSjCtK9vl3bdZRsjCrVISfyRh2Jyfqxsem+SXcaz2xHku6X5ltPbkNLPxRz8lTWqb8SVDE0b/Le/BuX0aKeP2m66nJSWcaiv/xtrudwethF/V1dOX0uZFLaCgrU24dkZyt4NtE3tWb/ANyXLXUl1WfJ+mX4E1ML/V5SXqiv7N2rxKo7UqL/AHH++8l99TSzku9R+o3XkHaxEUqFv0aLHTa4MDW2X/Q/+q+hL735xh5r6m7mQzhxXyk+0GqV7Fn3pHjDwl+gzx9N6xl4oHcrdn9hXUK5zsGfaqT/ABLqkxmqL/1LvTXoHut2P2zXK7LPeWQYsPT4Sj4kZbOT0a7mDuwexWXVqXMH2ijnDpL1R1r2Q+3yOa9rcNuOn2qXqh8OSZXQXiuM254Q4xYi7Cr449V2G+nBce95nO0l8S6o0Pdv9Cec2phn0tVV4riM8VfRp9TIUmuZcsXkT6Dd0e8ZJciccXJ8ku/8zNjiWlk/31FLEN8X0vkHoL3GysQrfMvoPGpGWjfdcx8Ph3J6rvv6GjH4VlZvm7ehPKaPMrl8Hq4Z3upeP5kJbRqRyUmrEpVmtd1LssUPE35PoNjftPOT48N/Y22XUluN/FbJ/itr38TpMNOS5HnMqtpZZdvaamA2vLjJ9hLl49+Yfi5fiu+jiGDbQxjUWo5t6X0XaznqO1Z8wj73b+ZI5Omyu25ywBiMPUk85Z88vRIEq4GS1d/E3ffRl2MreFvxOic2nLeGXy5mph3dvPpYr93LtOo+y88+pNYZckV/sxH+ta5aVRpWs/MjCs07tvxOnqbPuDz2NHkGc+N9heDKemJLHPi32LLzIxxGVrmvPYC1B/uP9sM5eNu3yBViXDk+jRdDHKWmds23kkQqbKs7K/5iWzHZrM1yxsaTOFHaL0snytcJjiMs15qxQqVo7qcUuuZVOlKNm3fvyF8VWdU9jXXVs8ho1L6AMMZHlfnknYk8erdnfmL01WZz5Gsi5AH3jxs7cSqpjs+JunIe5i1VXa0b8WYntNXcnC7bspWv3F9LHZZvy/IA23W3nHv9R+PGzIvJlLjWYIQjqcx4PNBUqv8AkFhqEtZWNS7b8dhXc4QhUrTpfznCdKEYuzbjCM4uUkrO7yzi8hqmwXFwTozcqlR0oxjicPKTmkm18MGrZ2vwaadhU/aL5pRnWpSqZ1VTVOUJzs478d9pwbTd1nq87Oyrw+0VTzjWxCbm533KL+OVt6Wcsm92N+dgeAEP2dmnZ4eqmtU8Rh07ZNyzh8vxL4tM9SK2JNZ/Zq7txVak1f438yp20pz8O1Ce2rqUff4i0r3Sp0Ffe+ZZSyT5aEJbTtFx9/it16q1O2W5w3v+OH/k3hvAqnseoslh6mbslHE4d70m920bQe801ZpXtxBq+Ekob8qFVQU9xv31H58vhsoXbW8k+TyI4jaynKMpVsQ3CW/BqFGO7K995WlrfO5KO2Eoyiq2ItN3l/DofE8s2756LvVwajXKek1sabbSo1G091pYrDN7yzcbKOcktVquI9fYm78MoTpzanKnepRqwn7uCqTi5U0nCW601qs1zuSjt15v7RiE5ZtqnQTvmr5PJu7z43B/vSKV1KrUmlOMHU3Ixp78FTnJRjfeluRSV3ZWTzsHwW2AHzCcP5lPvXlp0JUcQr9SN3psdS+xsK0lkvMNpY5L/Bnb5F1CNx26JncW7DE8iSxRg4fEOLvbJ6/Q0IYtP9SOfHpfDlmUaPvxKryYFGZNVCdxUlHrGNcfInDGvkjP94h41GL0j1WNaNeL426icOWZnJihJrixOnXqqdUHOlxBcSk00uK7yyNdjVp5aK/Q0tlayWMOpg4pp2fQg6LfPd5NZeOprtv8K8iMqVzonKj2vpizp52TSXBJWyRBYRtZ+vU1KmEV73KKmAb0fQpOX9p3i/TLdHVXsvN8ivca1y59poVMBLnp4FLwsrNrXmr+RWcidwv0Aj2MGx0m7XVrXNGdF8vzAtp07bvbcthluk14oEQhFUyjqFCEChUqOqJ1NfEQhfkJ6Klp4ijwEIwfBT1EhCMnksp6PuIxEIFapNkpaoQgFSi9eooS1/fMQhIpFnEubEIXL4WwEYWb5sKbHEc+Xt2Y+igx08xCEvo30tkyUWOImZcmWR0EInVMEbjcBCDDh3qNJ59w4hk1bK2tRxDHgeKzMjb2sOj9RhHTwflHLy/jWUIQjvcj/9kAAAAAAA==" ) test.equal(encoded_flac_cover, expected_encoded_flac_codec) test.pass() end def test_metadata_json_stringify() = file_metadata = [ ("filename", "test.mp3"), ("temporary", "false"), ("initial_uri", "test.mp3"), ("encoder", "Lavf59.27.100"), ("status", "ready"), ("rid", "0"), ("artist", "Artist") ] expected_json_string = '{ "artist": "Artist" }' json_string = metadata.json.stringify(file_metadata) test.equal(json_string, expected_json_string) empty_file_metadata = [] expected_json_string = '{}' json_string = metadata.json.stringify(empty_file_metadata) test.equal(json_string, expected_json_string) test.pass() end def test_metadata_json_parse() = metadata_json_string = '{"artist": "Artist"}' expected_parsed_metadata = [("artist", "Artist")] parsed_metadata = metadata.json.parse(metadata_json_string) test.equal(parsed_metadata, expected_parsed_metadata) empty_metadata_json_string = '{}' expected_parsed_metadata = [] parsed_metadata = metadata.json.parse(empty_metadata_json_string) test.equal(parsed_metadata, expected_parsed_metadata) test.pass() end test.check(f) test.check(test_metadata_json_stringify) test.check(test_metadata_json_parse) liquidsoap-2.4.2/tests/language/metrics.liq000066400000000000000000000006441513273233300210010ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = n = liquidsoap.functions.count() print( "Functions: #{n}." ) test.metric( category="language", name="functions", value=float(n), unit="functions" ) n = protocol.count() print( "Protocols: #{n}." ) test.metric( category="language", name="protocols", value=float(n), unit="protocols" ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/null.liq000066400000000000000000000013731513273233300203050ustar00rootroot00000000000000def f() = test.equal(null ?? "bla", "bla") test.equal(null("foo") ?? "bla", "foo") test.equal(null.case(null, {true}, fun (_) -> false), true) test.equal(null.case("x", {true}, fun (_) -> false), false) test.equal(null.get("x"), "x") test.equal(null.get(default="x", "y"), "y") test.equal(null.get(default="x", null), "x") test.equal( null.find( fun (x) -> if x mod 2 == 0 then 2 * x else null end, [1, 3, 2, 5] ), 4 ) def f((x:int?)) = if true then 1 else x end end def g() = if true then null else (1 : int?) end end # Subtyping def f() = if true then null else 1 end end def f() = if true then 1 else null end end _ = [null, 3] _ = [3, null] test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/number.liq000066400000000000000000000034561513273233300206270ustar00rootroot00000000000000def f() = test.equal(float.is_nan(nan), true) test.equal(float.is_nan(1.), false) test.equal(float.is_nan(infinity), false) test.equal(float.is_infinite(nan), false) test.equal(float.is_infinite(1.), false) test.equal(float.is_infinite(infinity), true) test.equal(float.is_infinite(-(infinity)), true) test.equal(int(nan), 0) test.raises({int(raise=true, nan)}, error.invalid) test.equal(int(infinity), max_int) test.equal(int(-(infinity)), min_int) test.raises({int(raise=true, infinity)}, error.invalid) test.raises({int(raise=true, -(infinity))}, error.invalid) test.equal(ceil(1.), 1.) test.equal(ceil(1.4), 2.) test.equal(ceil(1.5), 2.) test.equal(ceil(1.6), 2.) test.equal(ceil(0.1), 1.) test.equal(1_1.2_2, 11.22) test.equal(ceil(0.), 0.) test.equal(ceil(-0.1), -0.) test.equal(ceil(-1.), -1.) test.equal(ceil(-1.4), -1.) test.equal(ceil(-1.5), -1.) test.equal(ceil(-1.6), -1.) test.equal(float.is_nan(ceil(nan)), true) test.equal(floor(1.), 1.) test.equal(floor(1.4), 1.) test.equal(floor(1.5), 1.) test.equal(floor(1.6), 1.) test.equal(floor(0.1), 0.) test.equal(floor(0.), 0.) test.equal(floor(-0.1), -1.) test.equal(floor(-1.), -1.) test.equal(floor(-1.4), -2.) test.equal(floor(-1.5), -2.) test.equal(floor(-1.6), -2.) test.equal(float.is_nan(floor(nan)), true) test.equal(sign(0.), 1.) test.equal(sign(0.1), 1.) test.equal(sign(1.), 1.) test.equal(sign(2.), 1.) test.equal(sign(-0.1), -1.) test.equal(sign(-1.), -1.) test.equal(sign(-2.), -1.) test.equal(sign(nan), 1.) test.equal(round(0.), 0.) test.equal(round(infinity), infinity) test.equal(float.is_nan(round(nan)), true) def f(x) = print( "x.foo is: " ^ x.foo ) x + 1 end test.equal(f(1.{foo="gni"}), 2) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/osc.liq000066400000000000000000000023151513273233300201140ustar00rootroot00000000000000#!../../liquidsoap ../test.liq %ifndef osc.native test.skip() %else success = ref(false) settings.oscnative.port.set(7778) def f() = # Test lo success := false osc.on_float( "/bla", fun (x) -> if x == 42. then print( "got float" ) success := true end ) osc.send_float(host="localhost", port=settings.osc.port(), "/bla", 42.) sleep(1.) if not success() then test.fail() end # Test native OSC success := false osc.native.on_float( "/bla", fun (x) -> if x == 42. then print( "got float" ) success := true end ) osc.native.send_float( host="localhost", port=settings.oscnative.port(), "/bla", 42. ) sleep(1.) if not success() then test.fail() end success := false osc.native.on_string( "/bla", fun (x) -> if x == "xxx" then print( "got string" ) success := true end ) osc.native.send_string( host="localhost", port=settings.oscnative.port(), "/bla", "xxx" ) sleep(1.) if not success() then test.fail() end test.pass() end test.check(f) %endif liquidsoap-2.4.2/tests/language/pattern.liq000066400000000000000000000034141513273233300210060ustar00rootroot00000000000000def c(x, y) = if x != y then print( "#{x} != #{y}" ) test.fail() end end def f() = # Tuples let tuple = (123, "aabbcc", true, {foo=1}) let (x, y, z, t) = tuple c(x, 123) c(y, "aabbcc") c(z, true) c(t, {foo=1}) # List let l = [1, 2, 3, 4, 5, 6, 7, 8, 9] let [x, y, ...z, t, u, v] = l c(x, 1) c(y, 2) c(z, [3, 4, 5, 6]) c(t, 7) c(u, 8) c(v, 9) let [x, y] = [1, 2, 3] c(x, 1) c(y, 2) let [..._, x, y] = [1, 2, 3] c(x, 2) c(y, 3) let [..._, x, y] = [1, 2, 3] c(x, 2) c(y, 3) let [x, _, ...z] = [1, 2, 3] c(x, 1) c(z, [3]) # Record r = {foo=123, gni="gno", bla=3.24} let {foo, gni, bla} = r c(foo, 123) c(gni, "gno") c(bla, 3.24) # Values with methods r = 123.{foo=123, gni="gno", bla=3.24} let {foo, gni, bla, ...v} = r c(v, 123) c(foo, 123) c(gni, "gno") c(bla, 3.24) r = 123.{foo=321, gni="gnu", bla=4.12} let {foo, gni, bla, ..._} = r c(foo, 321) c(gni, "gnu") c(bla, 4.12) # Combined patterns # Patterns inside records r' = {foo=tuple, gni=l, r=r} old_r = r let {foo, foo = (x, y, z, t), gni = [a, b, ...s, d], r, r = {gni, ...v}} = r' c(foo, tuple) c(x, 123) c(y, "aabbcc") c(z, true) c(t, {foo=1}) c(a, 1) c(b, 2) c(s, [3, 4, 5, 6, 7, 8]) c(d, 9) c(r, old_r) c(v, 123) c(gni, "gnu") x = {foo=123, gni="gno", bla=3.24} y = {foo=123, gni="gnu", bla=3.24} z = {foo=123, gni="gno", bla=3.24} t = {foo=123, gni="gno", bla=4.24} l = [x, y, z, t] let [{foo}, {gni}, ..._, {bla}] = l c(foo, 123) c(gni, "gnu") c(bla, 4.24) let ({foo = [{foo}, {gni}, ..._, {bla}]}, z) = ( {foo=l}, "the letter z" ) c(foo, 123) c(gni, "gnu") c(bla, 4.24) c( z, "the letter z" ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/pp.liq000066400000000000000000000006331513273233300177500ustar00rootroot00000000000000def f() = %ifdef sine x = 0 %endif test.equal(x, 0) %ifdef sine x = 1 %else x = 2 %endif test.equal(x, 1) %ifdef xxx x = 3 %else x = 4 %endif test.equal(x, 4) %ifversion >= 0.0 x = 5 %endif test.equal(x, 5) %ifversion >= 2 x = 6 %endif test.equal(x, 6) x = 6 %ifversion < 2.0 x = 7 %endif %ifversion == 5145 x = 8 %endif test.equal(x, 6) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/predicate.liq000066400000000000000000000014531513273233300212720ustar00rootroot00000000000000def predicate.to_list(n, p) = list.init(n, fun (_) -> p()) end def predicate.nth(n) = i = ref(-1) fun () -> begin ref.incr(i) i() == n end end def predicate.from(n) = i = ref(-1) fun () -> begin ref.incr(i) i() >= n end end def tl(p, l) = test.equal(predicate.to_list(4, p), l) end def f() = tl(predicate.nth(2), [false, false, true, false]) tl(predicate.from(2), [false, false, true, true]) tl(predicate.activates(predicate.from(2)), [false, false, true, false]) tl(predicate.changes(predicate.nth(2)), [false, true, true, false]) tl(predicate.once(predicate.from(2)), [false, false, true, false]) p = predicate.signal() test.equal(p(), false) p.signal() test.equal(p(), true) test.equal(p(), false) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/process.liq000066400000000000000000000016001513273233300210020ustar00rootroot00000000000000test.skip() first = ref(true) thread.run.recurrent( { if first() then begin first := false 2. end else begin test.equal( process.read( "printf toto" ), "toto" ) p = process.run( "exit 2" ) test.equal(p.status, "exit") test.equal(p.status.code, 2) p = process.run( timeout=0.5, "sleep 1" ) test.equal(p.status, "timeout") test.equal( process.quote.command( stdin="bla", stdout="blo", stderr="blo", "foo", args=["gni", 'gno"gna'] ), "'foo' 'gni' 'gno\"gna' <'bla' >'blo' 2>&1" ) test.pass() (-1.) end end } ) output.dummy(blank()) liquidsoap-2.4.2/tests/language/rec.liq000066400000000000000000000005031513273233300200760ustar00rootroot00000000000000# Some tests related to recursion and polymorphism. def f() = def id(x) = x end _ = id(3) _ = id("a") def rec recid(x) = x end _ = recid(3) _ = recid("a") def rec nid(n, x) = if n == 0 then x else nid(n - 1, x) end end _ = nid(2, 3) _ = nid(2, "a") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/record.liq000066400000000000000000000163321513273233300206120ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = # Basic checks n = 2 r = n.{a=8, b=12, f=fun (x) -> 2 * x} test.equal(1 + r, 3) test.equal(r.b, 12) test.equal(r.a, 8) test.equal(r.f(5), 10) r = 2.{a=5} ignore(r) # Test overriding with subfields r = () let r.f = () let r.f.variant = 7 let r.f = r.f.variant ignore(r) # Test replacing subfields r = () let r.a = () let r.a.b = 5 let replaces r.a = 2 ignore(r.a.b) def replaces r.a(x) = 2 * x end ignore(r.a.b) # A resettable counter n = ref(0) def counter() = n := n() + 1 n() end def counter.reset() = n := 0 end ignore(counter()) ignore(counter()) test.equal(counter(), 3) counter.reset() test.equal(counter(), 1) # Float / field disambiguation r = 3.{s="a"} _ = r.s # Open r = () def r.f(n) = 2 * n end open r test.equal(f(3), 6) # Test subtyping in lists a = "a" b = "b" let a.x = 5 let b.x = 3 let b.y = 1. l = [a, b] ignore(list.hd(l).x) l = [b, a] ignore(list.hd(l).x) def f(c) = [a, c] end ignore(f) # Subsequent increase of the type _ = if true then {a=4, b=5} else {a=4} end _ = if true then {a=4} else {a=4, b=5} end # Fields with incompatible types _ = [{a=4, b=5, c=6}, {a="a", b=2, d="d"}] # Equality test.equal({a=5} == {a=5}, true) test.equal({a=5} == {a=6}, false) test.equal({a=5} == {a=6, b=4}, false) test.equal([{a=5}] == [{a=5}], true) test.equal(({a=5}) == ({a=5}), true) # The following is weird but expected test.equal({a=5} == {a=5, b=6}, true) test.equal("bla".{x=2} == "bla", true) test.equal("bla" == "bla".{x=2}, true) # Test optional fields def f(x) = (x?.foo ?? 2) + 1 end test.equal(f(()), 3) test.equal(f({}), 3) test.equal(f({foo=1}), 2) test.equal(f({foo=null}), 3) def f(x) = ret = x?.foo(123) ret ?? 1 end test.equal(f(()), 1) test.equal(f({}), 1) test.equal(f({foo=(fun (x) -> x)}), 123) test.equal(f({foo=null}), 1) def f(x) = [x, {foo=123}] end ignore(list.hd(f(()))?.foo) ignore((f({foo=345}) : [{foo?: int}])) x = {} ignore(x.foo ?? 123) def f(x) = ignore((x.add ?? 1)) if false then x else () end x end x = f(()) (x : {add?: int}) x = (() : {foo?: int?}) ignore(f(())) def f(x) = x.foo.gni.bla(1, 2, 3).blo end test.equal( f( { foo= { gni= { bla= ( fun (_, _, _, ~foo="gni") -> begin ignore(foo) {blo=123} end ) } } } ), 123 ) def f(x) = x.foo?.gni.bla(1, 2, 3) end test.equal(f({foo=null}), null) # We want to make sure that: def f(x) = x?.foo end # is properly typed as: ('B.{foo? : 'A}) -> 'A? test.equal(f({}), null) x = ({foo=null} : {foo: int?}) test.equal(f(x), null) x = ({foo=null} : {foo?: int?}) test.equal(f(x), null) test.equal(f(true), null) test.equal(f({foo=1}), null(1)) # We want to make sure that: def f(x) = x?.foo(1)?.gni end # is properly typed as: ('A.{foo? : (int) -> 'B.{gni? : 'C}}) -> 'C? # i.e. that { foo = (fun (_) -> { } ) } is a valid argument test.equal(f({foo=(fun (_) -> {})}), null) test.equal(f(112.{foo=(fun (_) -> false)}), null) test.equal(f({foo=(fun (_) -> {gni=2})}), null(2)) test.equal(f(345.{foo=(fun (_) -> "aabb".{gni=2})}), null(2)) # We want to make sure that: def f(x) = x?.foo(1).gni ?? 1 end # is properly typed as: ('A.{foo? : (int) -> 'B.{gni? : int}}) -> int # i.e. that { foo = (fun (_) -> { } ) } is a valid argument test.equal(f({}), 1) test.equal(f(2), 1) test.equal(f({foo=(fun (_) -> {})}), 1) test.equal(f("aabb".{foo=(fun (_) -> 123)}), 1) test.equal(f({foo=(fun (_) -> {gni=2})}), 2) test.equal(f(456.{foo=(fun (_) -> true.{gni=2})}), 2) # Spread patterns: x = {foo=123, gni="aabb"} let {foo, ...y} = x test.equal(foo, 123) test.equal(y.gni, "aabb") test.equal(y, {gni="aabb"}) test.equal(y?.foo, null) x = 1.{foo=123, gni="aabb"} let {foo, ...y} = x test.equal(foo, 123) test.equal(y.gni, "aabb") test.equal(y, 1) test.equal(y?.foo, null) x = 1.{foo=123, gni="aabb"} let {foo, ...y} = x test.equal(foo, 123) test.equal(y.gni, "aabb") test.equal(y, 1) test.equal(y?.foo, null) x = {foo=3.14}.{foo=123, gni="aabb"} let {foo, ...y} = x test.equal(foo, 123) test.equal(y, {gni="aabb"}) test.equal(y?.foo, null) x = {foo=123, gni="aabb"} y = {bla=3.14, ...x} test.equal(y, {gni="aabb", foo=123, bla=3.14}) x = 1.{foo=123, gni="aabb"} y = {bla=3.14, ...x} test.equal(y, {gni="aabb", foo=123, bla=3.14}) # Make sure that a function that takes a record of the # type { foo?: int} can take a record of the type: {foo?: never} def f(x) = (x?.foo ?? 1) + 2 end x = {foo=123} let {foo = _, ...y} = x test.equal(f(y), 3) # Make sure that we infer optional methods correctly def f(x) = ignore(x?.foo) end def g(x) = f(x.{gni=123}) end g({blo="bla"}) # Allow optional method extraction let {x?} = () test.equal(x, null) let {x?} = {x=123} test.equal(x, 123) # The following is allowed: def json.parse = true end def json.parse.foo = false end def json.parse.gni(_) = true end let json.parse.foo = 456 x = 1 y = x.{foo=123} - 3 test.equal("#{y}", "#{-2}") x = {foo=123} y = {gni="aabb"} test.equal({...x, ...y}, {gni="aabb", foo=123}) test.equal({...x, gna=3.14, ...y}, {gni="aabb", gna=3.14, foo=123}) test.equal( {...x, gna=3.14, ...y, foo="bar"}, {foo="bar", gni="aabb", gna=3.14} ) x = {foo=1} y = {foo=2} test.equal({...x, ...y}, {foo=2}) test.equal({...y, ...x}, {foo=1}) x = {foo=1} y = {foo=null} test.equal({...x, ...y}, {foo=null}) test.equal({...y, ...x}, {foo=1}) x = {foo=null} y = {foo=null} test.equal({...x, ...y}, {foo=null}) test.equal({...y, ...x}, {foo=null}) x = {foo=1} y = {foo="a"} test.equal({...x, ...y}, {foo="a"}) test.equal({...y, ...x}, {foo=1}) x = {foo=1} y = {foo={bar=1}} test.equal({...x, ...y}, {foo={bar=1}}) test.equal({...y, ...x}, {foo=1}) # A required method that is a function returning `int?` def f(x) = x.foo() ?? 123 end test.equal(f({foo=fun () -> null}), 123) def f(x) = x = (x : {foo: int}) let {foo, ...x} = x ignore(foo) (x.foo ?? "aabb") end test.equal(f({foo=123}), "aabb") # Test priority for ?. x = ({} : {bla?: int}) test.equal(x?.bla == x?.bla, true) # Test destructuring of record x = {foo={gni="aabb"}} foo = 123 let {foo = {gni}} = x test.equal(foo, 123) test.equal(gni, "aabb") gni = 123 let {foo = {gni = gno}, foo, foo = gna} = x test.equal(foo, {gni="aabb"}) test.equal(gni, 123) test.equal(gno, "aabb") test.equal(gna, {gni="aabb"}) x = {gni=123, bla=true} let {gni = _, ...rest} = x test.equal(rest, {bla=true}) x = {gni={foo="aabb"}, bla="blo"} gni = 345 let {gni = {foo}, gni = gno, ...rest} = x test.equal(gni, 345) test.equal(foo, "aabb") test.equal(gno, {foo="aabb"}) test.equal(rest, {bla="blo"}) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/ref.liq000066400000000000000000000005141513273233300201030ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def id(x) = x end def incr(n) = n + 1 end def f() = n = ref(0) n := 5 test.equal(n(), 5) s = ref("a") s := "b" test.equal(s(), "b") f = ref(id) f := incr r = ref.make({0}, fun (x) -> ignore(x + 1)) _ = ref.map(float_of_int, int_of_float, r) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/regexp.liq000066400000000000000000000016741513273233300206310ustar00rootroot00000000000000def f() = # Test basic syntax ignore(r/bla/gims) ignore(r/gni/) ignore(r/bla\foo\/gni/) # Test equality test.equal(r/bla/gims, r/bla/gims) # Test test test.equal(r/^foo[\d]+bar$/.test("foo1234bar"), true) test.equal(r/^foo[\d]+BaR$/i.test("foo1234bar"), true) test.equal(r/^gni/.test("bla\ngni"), false) test.equal(r/bla$/.test("bla\ngni"), false) test.equal(r/^gni/m.test("bla\ngni"), true) test.equal(r/bla$/m.test("bla\ngni"), true) test.equal(r/./.test("\n"), false) test.equal(r/./s.test("\n"), true) test.equal(r/^\/bla/.test("/blabla"), true) test.equal(r/^\/bla/.test("blabla"), false) # Test replace test.equal( r/gni/.replace(fun (_) -> "gno", "blagniblagnibla"), "blagnoblagnibla" ) test.equal( r/gni/g.replace(fun (_) -> "gno", "blagniblagnibla"), "blagnoblagnobla" ) # Test string escape. x = r/^\/foo$/g test.equal("#{x}", "r/^\\/foo$/g") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/replaygain.liq000066400000000000000000000054331513273233300214670ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def test_metadata_replaygain() = test.equal(metadata.replaygain([("r128_track_gain", "0")]), 0.) test.equal(metadata.replaygain([("r128_track_gain", "256")]), 1.) test.equal(metadata.replaygain([("r128_track_gain", "32767")]), 32767. / 256.) test.equal( metadata.replaygain([("r128_track_gain", "-32768")]), -32768. / 256. ) test.equal(metadata.replaygain([("r128_track_gain", "foo")]), null) test.equal( metadata.replaygain( [ ( "r128_track_gain", "0 foo" ) ] ), null ) test.equal(metadata.replaygain([("r128_track_gain", "")]), null) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "1 dB" ) ] ), 1. ) test.equal(metadata.replaygain([("replaygain_track_gain", "1")]), 1.) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "-1 dB" ) ] ), -1. ) test.equal(metadata.replaygain([("replaygain_track_gain", "-1")]), -1.) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "+0.424046 dB" ) ] ), 0.424046 ) test.equal( metadata.replaygain([("replaygain_track_gain", "+0.424046")]), 0.424046 ) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "-10.38500 dB" ) ] ), -10.38500 ) test.equal( metadata.replaygain([("replaygain_track_gain", "-10.38500")]), -10.38500 ) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "1 dB" ) ] ), 1. ) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "1 2 dB" ) ] ), 1. ) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "1 db" ) ] ), 1. ) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "1 DB" ) ] ), 1. ) test.equal( metadata.replaygain( [ ( "replaygain_track_gain", "1 foo" ) ] ), 1. ) test.equal(metadata.replaygain([("replaygain_track_gain", "")]), null) test.equal(metadata.replaygain([("replaygain_track_gain", "foo")]), null) test.equal(metadata.replaygain([]), null) test.equal( metadata.replaygain( [("r128_track_gain", "foo"), ("replaygain_track_gain", "1")] ), null ) test.equal( metadata.replaygain( [("replaygain_track_gain", "1"), ("r128_track_gain", "foo")] ), null ) test.pass() end test.check(test_metadata_replaygain) liquidsoap-2.4.2/tests/language/socket.liq000066400000000000000000000017401513273233300206210ustar00rootroot00000000000000def f() = test.equal(socket.internet_address.ipv6.any.is_ipv6, true) test.equal(socket.internet_address.ipv6.loopback.to_string(), "::1") let [h] = null.get(host.of_name("localhost")).addresses test.equal(socket.address.internet_address(h, 80).port, 80) fname = file.temp("unix", "socket") file.remove(fname) thread.run( fun () -> begin s = socket.unix.listen(fname) test.equal(s.type, "unix") s.write.wait( fun () -> begin s.write("done!") s.close() end ) end ) thread.run( delay=0.1, fun () -> begin s = socket.unix.client(fname) s.read.wait( fun () -> begin if s.read() == "done!" then s.close() test.pass() else test.fail() end end ) end ) end test.check(f) liquidsoap-2.4.2/tests/language/sqlite.liq000077500000000000000000000041231513273233300206330ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = %ifndef sqlite print( "WARNING: Not compiled with sqlite support. Passing test." ) %else # Escaping test.equal(sqlite.escape("bla"), "'bla'") test.equal(sqlite.escape("a'b"), "'a''b'") # Table creation tmp = file.temp("test", ".sql") on_cleanup({file.remove(tmp)}) db = sqlite(tmp) db.table.drop("test") db.exec( "CREATE TABLE test (n INTEGER, s STRING, f FLOAT);" ) db.table.create( "test2", preserve=true, [ ( "id", "INTEGER UNIQUE" ), ("value", "STRING") ] ) # Insertion db.insert(table="test", {n=5, s="bla", f=2.5}) db.insert(table="test", {n=5, s="bli", f=1.}) db.insert(table="test", {n=20, s="blu", f=0.2}) db.insert(table="test", {n=666, s="blu", f=null}) db.exec( "INSERT INTO test VALUES (5,#{sqlite.escape('hello')},1.2)" ) db.insert(table="test2", replace=true, {id=0, value="a"}) db.insert(table="test2", replace=true, {id=0, value="b"}) # Count test.equal(db.count(table="test", where="n=5"), 3) # Select l = db.select(table="test", where="n=5") test.equal(list.length(l), 3) print( "result: #{l}" ) # Iterate print( "# Iterate" ) db.select.iter(print, table="test", where="n=5") # Delete db.delete(table="test2", where="id=0") db.delete(table="test2", where="id=55") # # Table existence # test.equal(db.table.exists("test"), true) # test.equal(db.table.exists("ghost"), false) # Insertion of lists db.table.create( "abc", preserve=true, [("a", "STRING"), ("b", "STRING"), ("c", "STRING")] ) db.insert.list(table="abc", [("c", "123"), ("a", "456"), ("b", "789")]) # Query parse let sqlite.query ([{a}] : [{a: int}]) = db.query( "SELECT a FROM abc LIMIT 1" ) test.equal(a, 456) # Manual row manipulation let [row] = db.query( "SELECT a FROM abc LIMIT 1" ) let [(label, v)] = row.to_list() test.equal(label, "a") test.equal(v, "456") let sqlite.row (r : {a: string, b: string}) = row test.equal(r.a, "456") %endif test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/stdlib.liq000066400000000000000000000005201513273233300206050ustar00rootroot00000000000000def f() = test.equal(environment.get("BLAXXX"), "") test.equal(file.is_directory("/"), true) test.equal(file.is_directory("~"), true) test.equal(file.is_directory("XXX"), false) test.equal(liquidsoap.version.at_least("2.0.0"), true) test.equal(liquidsoap.version.at_least("666.0.0"), false) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/string.liq000077500000000000000000000137361513273233300206520ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = test.equal(string.length(""), 0) test.equal(string.length("abc"), 3) test.equal(string.capitalize("aBc"), "ABc") test.equal(string.case("aBc"), "abc") test.equal(string.case(lower=false, "aBc"), "ABC") test.equal(string.concat(["ab", "c", "d"]), "abcd") test.equal(r/.*ab.*/.test("cccabc"), true) test.equal(r/.*ab.*/.test("cccbac"), false) test.equal( string.trim( " abc " ), "abc" ) test.equal(string.sub("abcd", start=1, length=2), "bc") test.equal(string.sub("ab", start=0, length=10), "") test.equal(string.sub("ab", start=10, length=5), "") test.equal(string.contains(prefix="ab", "abcd"), true) test.equal(string.contains(prefix="ba", "abcd"), false) test.equal(string.contains(prefix="abcd", "ab"), false) test.equal(string.contains(suffix="cd", "abcd"), true) test.equal(string.contains(suffix="dc", "abcd"), false) test.equal(string.contains(substring="bc", "abcd"), true) test.equal(string.contains(substring="bc", "acbd"), false) test.equal(string.spaces(0), "") test.equal( string.spaces(4), " " ) test.equal(string.of_int(45), "45") test.equal(string.of_int(digits=4, 45), "0045") test.equal(string.binary.to_int(little_endian=true, "abcd"), 1684234849) test.equal(string.binary.to_int(little_endian=false, "abcd"), 1633837924) test.equal(string.binary.of_int(little_endian=true, 1684234849), "abcd") test.equal(string.binary.of_int(little_endian=false, 1633837924), "abcd") s = r/([^=]*)=(.*)/.exec("ab=cde") test.equal(s[1], "ab") test.equal(s[2], "cde") test.equal( string.unescape("\\/\\a\\b\\e\\f\\n\\r\\t\\v\\'\\?\\x61\\141\\u0061"), "/\a\b\e\f\n\r\t\v\'\?aaa" ) test.equal( string.escape("/\\\a\b\e\f\n\r\t\v\'\?"), "/\\\\\\u0007\\b\\u001B\\u000C\\n\\r\\t\\u000B\\'?" ) test.equal( string.escape( encoding="ascii", "\\foo \"😅 o" ), "\\\\foo \\\"\\xF0\\x9F\\x98\\x85 o" ) test.equal("\xF0\x9F\x98\x85", "😅") test.equal("\342\234\250", "✨") test.equal("\u2728", "✨") test.equal("\/", "/") test.equal(string.escape.all("✨"), "\\u2728") test.equal(string.escape.all(format="hex", "✨"), "\\xE2\\x9C\\xA8") test.equal(string.escape.all(format="octal", "✨"), "\\342\\234\\250") test.equal(string.unescape("\\u2728"), "✨") test.equal(string.unescape("\\xE2\\x9C\\xA8"), "✨") test.equal(string.unescape("\\342\\234\\250"), "✨") test.equal( string.quote( encoding="ascii", "foo \"😅 o" ), "\"foo \\\"\\xF0\\x9F\\x98\\x85 o\"" ) test.equal( string.quote( "foo \"😅 o" ), "\"foo \\\"😅 o\"" ) test.equal(string.quote("aa'bb"), "\"aa'bb\"") test.equal(string.residual(prefix="ab", "abcd"), "cd") test.equal(string.residual(prefix="xx", "abcd"), null) test.equal(string.nth("abcde", 2), 99) test.equal(string.index(substring="cd", "abcde"), 2) test.equal(string.index(substring="e", "abcde"), 4) test.equal(string.index(substring="xx", "abcde"), -1) test.equal(string.split.first(separator="xx", "abcxxdef"), ("abc", "def")) # Test that we revert to ascii on invalid utf8 strings when encoding is not specified: invalid_utf8_string = "\xfc\xa1\xa1\xa1\xa1\xa1" test.equal( string.quote(invalid_utf8_string), string.quote(encoding="ascii", invalid_utf8_string) ) # But raise an exception when encoding is specified: try ignore(string.quote(encoding="utf8", invalid_utf8_string)) test.fail() catch _ : [error.string] do () end test.equal( string.annotate.parse( "foo=bla,\"foo\"=\"✨✅\",\"gni:gno\"=\"bla\\\"blu:\":😈uri" ), ([("foo", "✨✅"), ("gni:gno", "bla\"blu:")], "😈uri") ) test.equal( string.data_uri.encode(mime="foo/bar", "✨"), "data:foo/bar;base64,4pyo" ) test.equal( string.data_uri.encode(base64=false, mime="foo/bar", "✨"), "data:foo/bar,✨" ) test.equal( string.data_uri.decode("data:foo/bar;base64,4pyo"), null("✨".{mime="foo/bar"}) ) test.equal( string.data_uri.decode("data:foo/bar,✨"), null("✨".{mime="foo/bar"}) ) test.equal( url.encode( "foo bla blo" ), "foo+bla+blo" ) test.equal( url.decode("foo%20bla%20blo"), "foo bla blo" ) test.equal( "blo#{(1, 2, 3)}", "blo(1, 2, 3)" ) s = "王^小東=" test.equal(string.length(s), 5) test.equal(string.chars(s), ["王", "^", "小", "東", "="]) test.equal(string.sub(start=1, length=2, s), "^小") test.equal(string.length(encoding="ascii", s), 11) test.equal( string.chars(encoding="ascii", s), [ "\xE7", "\x8E", "\x8B", "^", "\xE5", "\xB0", "\x8F", "\xE6", "\x9D", "\xB1", "=" ] ) test.equal(string.sub(encoding="ascii", start=1, length=2, s), "\x8E\x8B") try ignore(string.chars(encoding="utf16le", s)) test.fail() catch e : [error.invalid] do () end test.equal(string.escape.html("&"), "&") test.equal(string.escape.html("<"), "<") test.equal(string.escape.html(">"), ">") test.equal(string.escape.html('"'), """) test.equal(string.escape.html("'"), "'") test.equal(string.escape.html("&<>\"'"), "&<>"'") test.equal( string.escape.html( "not escaped" ), "not escaped" ) test.equal( string.escape.html( ""double escape"" ), "&quot;double escape&quot;" ) test.equal(string.escape.html("\\"), "\\") test.equal(string.escape.html("/"), "/") test.equal(string.escape.html("`"), "`") s = "blablo" flag = string.flags.binary(s) test.equal(flag(), false) flag := true test.equal(flag(), true) test.equal("#{s}", "blablo") test.equal(string(s), "blablo") test.equal(string(print_binary=false, s), "") flag := false test.equal(flag(), false) test.equal("#{s}", "blablo") test.equal(string(s), "blablo") test.equal(string(print_binary=false, s), "blablo") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/test_cover.liq000066400000000000000000000002521513273233300215030ustar00rootroot00000000000000def f() = cover_png = file.cover("./cover.mp3") expected_png = file.contents("cover.png") test.equal("#{cover_png}", expected_png) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/thread.liq000066400000000000000000000021121513273233300205720ustar00rootroot00000000000000def f() = count = ref(0) def cb() = count := count() + 1 end thread.when({true}, cb) thread.pause(1.) test.equal(count(), 1) # Testing a step with initial true: # True for the first 2s then false for 2s, then true t = time() def p() = cur_t = time() (t <= cur_t and cur_t < t + 2.) or t + 4. <= cur_t end count = ref(0) def cb() = count := count() + 1 end thread.when(init=false, p, cb) thread.pause(5.) test.equal(count(), 1) # Testing a step with initial false: # false for the first 2s then true t = time() def p() = t + 2. <= time() end count = ref(0) def cb() = count := count() + 1 end thread.when(p, cb) thread.pause(3.) test.equal(count(), 1) # Testing when triggering on each change # True for the first 2s then false t = time() def p() = cur_t = time() t <= cur_t and cur_t < t + 2. end count = ref(0) def cb() = count := count() + 1 end thread.when(changed=false, every=0.5, p, cb) thread.pause(5.) test.equal(count(), 4) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/time.liq000066400000000000000000000007001513273233300202620ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def check_value(v, v') = if v != v' then print( "Expected: #{v}, got: #{v'}" ) test.fail() end end def f() = # This check has race conditions issues. check_value(int_of_float(time()), int_of_float(time.make(time.local()))) time.zone.set("Europe/Paris") check_value( time.string(time=1662367396.), "Monday, 05 September 2022 10:43:16" ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/type_errors.liq000066400000000000000000000214331513273233300217070ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def section(name) = print( "\n*** #{name} ***\n\n" ) end def incorrect(expr) = print( "Incorrect expression #{expr}...\n" ) try let eval _ = expr test.fail() catch err do print( "Got err: #{err}" ) end print("\n") end def correct(expr) = print( "Correct expression #{expr}...\n" ) let eval _ = expr print("\n") end def f() = section("LISTS") incorrect('ignore([4,"x"])') correct( 'ignore([input.harbor("foo"), sine()])' ) correct( 'ignore([sine(), input.harbor("foo")])' ) correct( 'ignore([1, ...[2,3,4], ...[5,6], 7])' ) correct( 'let [x,y,...z] = [1,2]' ) correct( 'let [] = [1,2]' ) incorrect( 'let [...z, x, ...t] = [1,2]' ) section("BASIC") incorrect('[1]==["1"]') incorrect('1==["1"]') incorrect('1==(1,"1")') # In some of those examples the type error could be reported for a # sub-expression since we have location information. # With the concise error, it's still pretty good currently. incorrect('(1,1)==(1,"1")') incorrect('(1,1)==("1",1)') incorrect('1==request.create("")') incorrect('fun(x)->x(snd(x))') correct( '{a = 5, b = 3} == {a = 6}' ) correct( 'true ? "foo" : "bar"' ) incorrect( 'false ? true : "bar"' ) section("SUBTYPING") incorrect('(1:unit)') # Next one requires the inference of a subtype (fixed vs. variable arity) correct('ignore(stereo(add([])))') correct('ignore((blank():source(audio=pcm,video=canvas)))') section("CONSTRAINTS") incorrect('"bl"+"a"') incorrect('(fun(a,b)->a+b)==(fun(a,b)->a+b)') incorrect('fun(x)->x(x)') # TODO is it an accident that we get same varname incorrect( 'def f(x) y=snd(x) y(x) end' ) section( "LET GENERALIZATION" ) correct( 'def f(x) = y=x ; y end ignore(f(3)+snd(f((1,2))))' ) incorrect( 'def f(x) = y=x ; y end ignore(f(3)+"3")' ) section("ARGUMENTS") # The errors should be about the type of the param, not of the function. incorrect('1+"1"') # Also, a special simple error is expected for obvious labelling mistakes. incorrect('fallback(transitions=[],xxxxxxxxxxx=[])') incorrect('fallback(transitions=[],transitions=[])') section("FUNCTIONS") # Partial application is not supported anymore incorrect( 'def f(x,y) = y end ignore(f(2))' ) # Invalid type generalization incorrect( 'def f(x=null) = ref(x) end r = f(); r.set(1); r.set("aabb")' ) incorrect('fallback(transitions=[fun(~l)->1],[blank()])') incorrect('fallback(transitions=[fun(~l=1)->1],[blank()])') incorrect('fallback(transitions=[fun(x,y=blank())->y],[blank()])') incorrect('fallback(transitions=[fun(x,y)->0],[blank()])') correct('fallback(transitions=[fun(x,y,a=2)->x],[blank()])') incorrect('fallback(transitions=[fun(x,y)->y+1],[blank()])') correct( 'x=fun(f)->f(3) y=x(fun(f,u="1")->u) ignore(y)' ) incorrect( 'f=fun(~l,~l) -> l' ) section( "CONTENT KIND" ) incorrect('output.file(%vorbis(stereo),"foo",mean(blank()))') incorrect('output.file(%vorbis(stereo),"foo",video.add_image(blank()))') incorrect( 'def f(x) = output.file(%vorbis(stereo),"",x) \ output.file(%vorbis(mono),"",x) end' ) incorrect( 'add([output.file(%vorbis(stereo),"",blank()),output.file(%vorbis(mono),"",blank())])' ) incorrect('add([mean(blank()),audio_to_stereo(add([]))])') section( "TRACK TYPES" ) correct('(source.tracks(blank()).audio:pcm)') correct('(source.tracks(blank()).audio:canvas)') correct('(source.tracks(single("")).midi:midi)') correct('(source.tracks(single("")).audio:ffmpeg.copy)') section("PATTERNS") incorrect( "let [x = {foo}, y = (foo), z] = l" ) incorrect( "let _.{foo=123} = {foo=123}" ) incorrect( "let v.{foo=123} = {foo=123}" ) incorrect("def f(x) = let { foo? } = x foo end f(()) + 123") section("ENCODERS") correct('%ffmpeg(%video(codec="h264_nvenc"))') correct('%ffmpeg(%video(codec="h264_nvenc",hwaccel="none"))') correct( '%ffmpeg(%video(codec="h264_nvenc",hwaccel="auto",hwaccel_device="none"))' ) correct('%ffmpeg(%video(codec="h264_nvenc",hwaccel_device="foo"))') correct( '%ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100 ))' ) correct( '%ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="96k" ))' ) correct( '%ffmpeg(format="mpegts", %audio( codec="aac", channels=2, ar=44100, b="192k" ))' ) correct( '%ffmpeg( format="mpegts", %audio( codec="aac", b="128k", channels=2, ar=44100 ), %video( codec="libx264", b="5M" ) )' ) correct( '%ffmpeg( format="mp4", movflags="+dash+skip_sidx+skip_trailer+frag_custom", frag_duration=10, %audio( codec="aac", b="128k", channels=2, ar=44100), %video( codec="libx264", b="5M" ) )' ) correct( '%ffmpeg( format="mpegts", %audio.raw( codec="aac", b="128k", channels=2, ar=44100 ), %video.raw( codec="libx264", b="5M" ) )' ) correct( '%ffmpeg( format="mp4", movflags="+dash+skip_sidx+skip_trailer+frag_custom", frag_duration=10, %audio.raw( codec="aac", b="128k", channels=2, ar=44100), %video.raw( codec="libx264", b="5M" ) )' ) correct( '%ffmpeg( format="mpegts", %audio.copy, %video.copy)' ) correct( '%ffmpeg( format="mp4", movflags="+dash+skip_sidx+skip_trailer+frag_custom", frag_duration=10, %audio.copy, %video.copy)' ) correct( '%ffmpeg(%audio.copy(ignore_keyframe), %video.copy(ignore_keyframe))' ) correct( '%ffmpeg(%audio.copy(wait_for_keyframe), %video.copy(wait_for_keyframe))' ) # Conventions for content type in %ffmpeg encoder: correct( '%ffmpeg(%foo_audio, %bla_video_gni)' ) correct( '%ffmpeg(%foo(audio_content), %bla(video_content))' ) incorrect( '%ffmpeg(%foo, %bla_gni)' ) correct( '%ffmpeg(%foo_audio.raw, %bla_video_gni.raw)' ) correct( '%ffmpeg(%foo.raw(audio_content), %bla.raw(video_content))' ) incorrect( '%ffmpeg(%foo.raw, %bla_gni.raw)' ) correct( '%ffmpeg(%foo_audio.copy, %bla_video_gni.copy)' ) incorrect( '%ffmpeg(%foo.copy(audio_content), %bla.copy(video_content))' ) correct( '%ffmpeg(%foo.copy, %bla_gni.copy)' ) # The following is not technically checking on type errors but runtime invalid values. section( "INVALID VALUES" ) incorrect('crossfade(input.http(self_sync=true,"http://foo.bar"))') incorrect( 'crossfade(fallback([input.http("http://foo.bar"), \ input.http(self_sync=true,"http://foo.bar")]))' ) incorrect( 'crossfade(sequence([input.http("http://foo.bar"), \ input.http(self_sync=true,"http://foo.bar")]))' ) incorrect( 'crossfade(add([input.http("http://foo.bar"), \ input.http(self_sync=true,"http://foo.bar")]))' ) section("METHODS") correct( 'x = {foo="bar", gni=123,}' ) correct( '_ = "aabb".{foo="bar", gni=123,}' ) section( "OPTIONAL METHODS" ) incorrect("(({}:{foo?:int}):{foo:int?})") incorrect( "fun (x) -> x?.foo.gni.gna + 1" ) correct("(({}:{foo?:int}):{foo?:int?})") correct( "fun (x) -> x?.foo.gni.gna ?? 1" ) correct( "fun (x) -> x.foo.gni?.bla(1,2,3).blo" ) correct( "fun (x) -> x?.foo(123).gni ?? null" ) section("EVAL") incorrect("let eval x = '123' ignore(x+1) ignore(x / 2.)") section("PATTERNS") incorrect(" def f(x) = let y.{foo} = x y.foo + 1 end ") incorrect( ' def g() = s = single("foo") s = s enc = %ffmpeg( format="mkv", %audio(codec="aac"), %video(codec="aac") ) output.file(fallible=true, enc, "/tmp/bla.mkv", s) ignore(ffmpeg.decode.audio(s)) end' ) section("FORMATS") audio_formats_with_stereo = ["external(process=\"gni\",", "flac(", "opus(", "shine(", "vorbis(", "mp3("] def check_stereo_syntax(format) = correct("%#{format}mono)") correct("%#{format}stereo)") correct("%#{format}mono=false)") correct("%#{format}stereo=false)") end list.iter(check_stereo_syntax, audio_formats_with_stereo) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/typing.liq000066400000000000000000000141441513273233300206450ustar00rootroot00000000000000#!../../liquidsoap ../test.liq # TODO Throughout this file, parsing locations displayed in error messages # are often much too inaccurate. def f() = # Check that some polymorphism is allowed. # id : (string,'a)->'a def id(a, b) = log(a) b end ignore("bla" == id("bla", "bla")) ignore(0 == id("zero", 0)) # Reporting locations for the next error is non-trivial, because it is about # an instantiation of the type of id. The deep error doesn't have relevant # information about why the int should be a string, the outer one has. # id(0,0) # Polymorphism is limited to outer generalizations, this is not system F. # apply : ((string)->'a)->'a def apply(f) = f("bla") end # f is not polymorphic, the following is forbidden: # f(0) # f(f) ignore(apply) # The level checks forbid abusive generalization. # id' : ('a)->'a def id'(x) = # If one isn't careful about levels/scoping, f2 gets the type ('a)->'b and # so does twisted_id. def f2(y) = x end f2(x) end ignore(id') # Generalization and aliasing (see #941). def id(x) = x end _ = id(3) _ = id("a") id' = id _ = id'(3) _ = id'("a") # Testing generalization of recursive functions. def rec recid(x) = x end ignore(recid(3) == 3) ignore(recid("a") == "a") # More errors... # 0=="0" # [3,""] # Subtyping, functions and lists. f1 = fun () -> 3 f2 = fun (a=1) -> a # This is OK, l1 is a list of elements of type f1. l1 = [f1, f2] list.iter(fun (f) -> log(string(f())), l1) # Forbidden. Indeed, f1 doesn't accept any argument -- although f2 does. Here # the error message may even be too detailed since it goes back to the # definition of l1 and requires that f1 has type (int)->int. # list.iter(fun (f) -> log(string(f(42))), l1) # This used to be forbidden, but is allowed now that we have dropped currying: # we can now subtype a (?int)->int into an (int)->int (which is the type # inferred in the iter). list.iter(fun (f) -> log(string(f(42))), [f2]) # Unlike l1, this is not OK, since we don't leave open subtyping constraints # while inferring types. I hope we can make the inference smarter in the # future, without obfuscating the error messages too much. The type error here # shows the use of all the displayed positions: # f1 has type t1, f2 has type t2, t1 should be <: t2 # l2 = [ f2, f1 ] # An error where contravariance flips the roles of both sides. # [fun (x) -> x+1, fun (y) -> y^"."] # An error without much locations. # TODO An explanation about the missing label would help a lot here. # def f(f) # f(output.icecast.vorbis) # f(output.icecast.mp3) # end # This causes an occur-check error. # TODO The printing of the types breaks the sharing of one EVAR # across two types. Here the sharing is actually the origin of the occur-check # error. And it's not easy to understand.. # omega = fun (x) -> x(x) # The argument f of the function g is inferred as taking a non-optional # parameter x, whereas f has an optional parameter x. Subtyping should however # accept this now that we have disable partial application. g = fun (f) -> f(x=3) f = fun (~x=3) -> x + 5 ignore(g(f)) # Now let's test ad-hoc polymorphism. echo = fun (x) -> process.run(process.quote.command("echo", args=[(x : string)])) ignore(echo) ignore("bla") ignore((1, 3.12)) ignore(1 + 1) ignore(1. + 2.14) # string is not a Num # echo("bl"+"a") ignore(1 <= 2) ignore((1, 2) == (1, 3)) # float <> int # echo(1 == 2.) # source is not an Ord # echo(blank()==blank()) # Test record subtyping. def f(x) = "" ^ x end ignore(f("a".{n=3})) # Ensure that we have correct subtyping for arguments, which should be # contravariant, see #1465. # We have string.{l : int} < string # Thus (string) -> int < (string.{l : int}) -> int def g(f) = f("a".{l=3}) end def f(x) = 0 end ignore(g(f)) def sum_eq(a, b) = a + b == a end ignore(sum_eq) (noise() : source(audio=pcm, video=canvas)) (noise() : source(audio=pcm(mono))) (noise() : source(audio=pcm("5.1"))) (noise() : source) (single("annotate:foo=\"bla\":/nonexistent") : source(audio=ffmpeg.copy, video=ffmpeg.copy, midi=midi) ) (single("annotate:foo=\"bla\":/nonexistent") : source(audio=ffmpeg.audio.raw, video=ffmpeg.video.raw, midi=midi) ) (single("annotate:foo=\"bla\":/nonexistent") : source( audio=ffmpeg.audio.raw(sample_rate=44100, channel_layout="5.1"), video=ffmpeg.video.raw(pixel_format=yuva420p), midi=midi ) ) let ([x] : [int]) = [123] ([] : [(string*int)] as json.object) ([("bla", 3.14)] : [(string*float)] as json.object) ({foo=123} : {foo: int}) ({foo=123} : {"✨ name ✨" as foo: int}) (123.{foo=3.14, gni="aabbcc"} : int.{ foo: float, gni: string }) (123.{foo=3.14, gni="aabbcc"} : int.{ foo: float }.{ gni: string }) (123.{foo=3.14, gni="aabbcc"} : int.{ foo: float }.{ gni: int }.{ gni: string } ) (source.tracks(single("")).audio.{foo=3.14, gni="aabbcc"} : ffmpeg.copy.{ foo: float }.{ gni: int }.{ gni: string } ) (blank().{foo=3.14, gni="aabbcc"} : source(audio=pcm).{ foo: float }.{ gni: int }.{ gni: string } ) (blank().{foo=3.14, gni="aabbcc"} : source.{ foo: float }.{ gni: int }.{ gni: string } ) (123 : {foo: int}.foo) (123.{foo=3.14, gni="aabbcc"} : int.{ "✨ name ✨" as foo: float, gni: string } ) # Nullable type with methods: (123.{foo="aabb"} : int?.{ foo: string }) (() : {}) (() : unit.{ }) ({foo=123} : {foo?: int}) ({} : {foo?: int}) ({foo=null} : {foo?: int}) ({foo=123} : {foo?: int}) ({foo=null(null)} : {foo?: int?}) ([({} : {foo?: int}), (), {foo=123}] : [{foo?: int}]) ([(), {foo=123}] : [{foo?: int}]) # This one is more subtle than it seems because we have no field video on the # left and a video:never field on the right when testing for the subtyping, # see #3210. s = (sine() : source(video=none)) s = (sine() : source(video=none, ...)) # Allow float params. s = (sine() : source(audio=pcm(5.1))) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/url.liq000066400000000000000000000007371513273233300201400ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def f() = # Correctly parse urls with empty arguments. # https://github.com/savonet/liquidsoap/issues/123 test.equal( url.split("abc?a=aa&b=&c=cc"), ("abc", [("a", "aa"), ("b", ""), ("c", "cc")]) ) # Trailing & (#4592) test.equal( url.split("abc?a=aa&b=&c=cc&"), ("abc", [("a", "aa"), ("b", ""), ("c", "cc")]) ) # No argument (#3126). test.equal(url.split("/bla"), ("/bla", [])) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/various.liq000066400000000000000000000002351513273233300210170ustar00rootroot00000000000000def f() = test.equal(false ? 123 : 456 == 123, false) test.equal(path.basename(liquidsoap.script.path), "various.liq") test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/write_cover.liq000066400000000000000000000021611513273233300216570ustar00rootroot00000000000000settings.init.force_start := true target = argv(1) png = string.base64.decode( "iVBORw0KGgoAAAANSUhEUgAAAIAAAACAEAIAAAAczCrfAAAACXBIWXMAAAAAAAAAAQCEeRdzAAACoklEQVR4nO3E4UYYAACF0StJkiRJkiRJkiRJkiRJkiTJli1btmzZsmXLli3atGnTpk2bNm3atEmSJEmSJEmSJEmSJEmSJEnS9h73Oz+O9N/VFbPvurxg9l0XZ8y+6/yE2XedHTH7rtMDZt91ssfsu453mH3X0Raz7zrcYPZdB2vMvmt/hdl37S0x+67dBWbftTPH7Lu2Z5h919YUs+/anGD2XRtjzL5rfYTZd60NMfuu1QFm37Xyh9l3Lf9i9l1LP5h91+I3Zt+18IXZd81/YvZdcx+YfdfsO2bfNfOG2XdNv2L2XVMtzL5rspnZd000Mfuu8UZm3zXWwOy7RuuZfddIHbPvGq5l9l1DNcy+a7Ca2XcNVDH7rv4KZt/1t4zZd/WVMPuu30XMvqu3gNl3/cxj9l09Ocy+63sWs+/qzmD2XV/TmH1XVwqz7/qcxOy7OhOYfdfHOGbf1RHD7LveRzH7rvYIZt/1NozZd7WFMPuu10HMvqs1gNl3tVwy+64X58y+q/mU2Xc9O2b2XU2HzL7ryT6z72rcZfZdj7aZfVfDJrPverDO7LvqV5l9171lZt9Vt8jsu+7MM/uu2llm33Vrmtl31Uwy+64b48y+q3qU2XddG2b2XVWDzL6rsp/Zd1X0Mfuu8l5m31XWw+y7SruZfVdJF7PvKu5k9l1FHcy+q7Cd2XcVtDH7rvxWZt+V95LZd+U+Z/ZdOU+ZfVf2Y2bflfWQ2Xdl3mf2XRl3mX1X+m1m35V2k9l3pV5n9l0plcy+K7mc2XcllTL7rsRiZt+VUMjsu+LzmX1XXC6z74rNZvZdMZnMvis6ndl3RaUy+67IZGbfFZHI7LvC45l9V1gss+8KjWb2XSGRzL4rOJzZdwWFMvuuwGBm3xUQyOw7YOsfJwPDsnrI0ewAAAAASUVORK5CYII=" ) file.write(data=png, target) exit(0) liquidsoap-2.4.2/tests/language/xml_test.liq000066400000000000000000000051771513273233300212000ustar00rootroot00000000000000def f() = s = ' gni bla 1.23 false 123 ' let xml.parse (x : { bla: { foo: string.{ xml_params: {opt: float} }, bar: (string? * string?.{ xml_params: [(string * string)] }), blo: float, blu: bool, ble: int, xml_params: [(string * string)].{ bla: bool } } } ) = s test.equal( x, { bla= { xml_params=[("param", "1"), ("bla", "true")].{bla=true}, ble=123, blu=false, blo=1.23, bar=(null, "bla".{xml_params=[("option", "aab")]}), foo="gni".{xml_params={opt=12.3}} } } ) test.equal( xml.stringify( { bla= { xml_params=[("param", "1"), ("bla", "true")], bar="bla".{xml_params=[("option", "aab")]}, foo=true.{xml_params={opt=12.3}} } } ), ' bla true ' ) let xml.parse (x : ( string * { xml_params: [(string * string)], xml_children: [ ( string * { xml_params: [(string * string)], xml_children: [(string * {xml_text: string})] } ) ] } ) ) = s test.equal( x, ( "bla", { xml_children= [ ( "foo", { xml_children=[("xml_text", {xml_text="gni"})], xml_params=[("opt", "12.3")] } ), ("bar", {xml_children=[], xml_params=[]}), ( "bar", { xml_children=[("xml_text", {xml_text="bla"})], xml_params=[("option", "aab")] } ), ( "blo", {xml_children=[("xml_text", {xml_text="1.23"})], xml_params=[]} ), ( "blu", {xml_children=[("xml_text", {xml_text="false"})], xml_params=[]} ), ( "ble", {xml_children=[("xml_text", {xml_text="123"})], xml_params=[]} ) ], xml_params=[("param", "1"), ("bla", "true")] } ) ) test.equal( xml.stringify(x), ' gni bla 1.23 false 123 ' ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/language/yaml.liq000066400000000000000000000011141513273233300202660ustar00rootroot00000000000000def f() = j = json.object() j.add("foo", 1) j.add("bla", "bar") j.add("baz", 3.14) j.add("key_with_methods", "value".{method=123}) j.add("record", {a=1, b="ert"}) j.remove("foo") j = yaml.stringify(j) test.equal(j, 'record: a: 1 b: ert key_with_methods: value bla: bar baz: 3.14 ') let yaml.parse (x : { bla: string, baz: float, key_with_methods: string, record: {a: float, b: string} } ) = j test.equal( x, {bla="bar", baz=3.14, key_with_methods="value", record={a=1., b="ert"}} ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/media/000077500000000000000000000000001513273233300161145ustar00rootroot00000000000000liquidsoap-2.4.2/tests/media/.gitignore000066400000000000000000000000061513273233300201000ustar00rootroot00000000000000*.mp4 liquidsoap-2.4.2/tests/media/dune000066400000000000000000000123621513273233300167760ustar00rootroot00000000000000; Regenerate using dune build @gendune --auto-promote (include dune.inc) (executable (name gen_dune) (libraries liquidsoap_build_tools) (modules gen_dune)) (rule (alias gendune) (deps (source_tree .)) (target dune.inc.gen) (action (with-stdout-to dune.inc.gen (run ./gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) (rule (alias mediatest) (package liquidsoap) (target first-concat.mp4) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "testsrc=duration=20:size=1280x720:rate=30" -vf "setpts=N+1235" %{target}))) (rule (alias mediatest) (package liquidsoap) (target second-concat.mp4) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "testsrc=duration=30:size=1280x720:rate=30" -vf "setpts=N+756" %{target}))) (rule (alias mediatest) (package liquidsoap) (target third-concat.mp4) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "testsrc=duration=10:size=1280x720:rate=30" -vf "setpts=N+245" %{target}))) (rule (alias mediatest) (package liquidsoap) (target background.jpg) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i testsrc=size=1280x720 -t 1 -f mjpeg %{target}))) (rule (alias mediatest) (package liquidsoap) (target logo.png) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i testsrc=size=50x50 -t 1 -f mjpeg %{target}))) (rule (alias mediatest) (package liquidsoap) (deps first-concat.mp4 second-concat.mp4 third-concat.mp4 ffmpeg_copy_concat.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy+concat" liquidsoap %{test_liq} ffmpeg_copy_concat.liq))) (rule (alias mediatest) (package liquidsoap) (deps all_media_files ffmpeg_raw_implicit_conversion.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw implicit conversion" liquidsoap %{test_liq} ffmpeg_raw_implicit_conversion.liq))) (rule (alias mediatest) (package liquidsoap) (deps all_media_files ffmpeg_complex_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg complex filter" liquidsoap %{test_liq} ffmpeg_complex_filter.liq))) (rule (alias mediatest) (package liquidsoap) (target long-video.mp4) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "testsrc=d=30:s=1920x1080:r=24,format=yuv420p" -f lavfi -i "sine=f=440:b=4" -shortest %{target}))) (rule (alias mediatest) (package liquidsoap) (deps long-video.mp4 ffmpeg_distributed_hls.liq ffmpeg_distributed_hls_state.json ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg distributed HLS encoding" liquidsoap %{test_liq} ffmpeg_distributed_hls.liq -- long-video.mp4))) (rule (alias mediatest) (package liquidsoap) (deps all_media_files background.jpg logo.png ffmpeg_transparency_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg transparency filter" liquidsoap %{test_liq} ffmpeg_transparency_filter.liq))) (rule (alias mediatest) (package liquidsoap) (deps all_media_files background.jpg logo.png ffmpeg_filter_changing_rate.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter with changing video rate" liquidsoap %{test_liq} ./ffmpeg_filter_changing_rate.liq -- ./@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4 ./@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4))) (rule (alias mediatest) (package liquidsoap) (deps all_media_files lufs_integrated.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Integrated LUFS computation" liquidsoap %{test_liq} lufs_integrated.liq))) (rule (alias mediatest) (package liquidsoap) (deps long-video.mp4) (target test-image.png) (action (run ffmpeg -hide_banner -loglevel error -i long-video.mp4 -frames:v 1 %{target}))) (rule (alias mediatest) (package liquidsoap) (deps test-image.png image_decoder_duration.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Image decoder with fixed duration" liquidsoap %{test_liq} image_decoder_duration.liq))) liquidsoap-2.4.2/tests/media/dune.inc000066400000000000000000002031401513273233300175420ustar00rootroot00000000000000 (rule (alias encoder_audio_only) (package liquidsoap) (target @fdkaac[aot='mpeg4_aac_lc',channels=1]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%fdkaac(aot=\"mpeg4_aac_lc\",channels=1)" audio_only "@fdkaac[aot='mpeg4_aac_lc',channels=1].aac")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @fdkaac[channels=2]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%fdkaac(channels=2)" audio_only "@fdkaac[channels=2].aac")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @shine[channels=1]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%shine(channels=1)" audio_only "@shine[channels=1].mp3")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @shine[channels=2]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%shine(channels=2)" audio_only "@shine[channels=2].mp3")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @flac[stereo]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%flac(stereo)" audio_only "@flac[stereo].flac")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @flac[mono]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%flac(mono)" audio_only "@flac[mono].flac")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @wav[stereo]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%wav(stereo)" audio_only "@wav[stereo].wav")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @wav[mono]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%wav(mono)" audio_only "@wav[mono].wav")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @mp3[mono]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%mp3(mono)" audio_only "@mp3[mono].mp3")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @mp3[stereo]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%mp3(stereo)" audio_only "@mp3[stereo].mp3")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ogg[@vorbis[mono]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ogg(%vorbis(mono))" audio_only "@ogg[@vorbis[mono]].ogg")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ogg[@vorbis[stereo]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ogg(%vorbis(stereo))" audio_only "@ogg[@vorbis[stereo]].ogg")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ogg[@flac[mono]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ogg(%flac(mono))" audio_only "@ogg[@flac[mono]].ogg")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ogg[@flac[stereo]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ogg(%flac(stereo))" audio_only "@ogg[@flac[stereo]].ogg")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ogg[@opus[mono]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ogg(%opus(mono))" audio_only "@ogg[@opus[mono]].ogg")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ogg[@opus[stereo]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ogg(%opus(stereo))" audio_only "@ogg[@opus[stereo]].ogg")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",samplerate=\"48k\"))" audio_only "@ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\"))" audio_only "@ffmpeg[format='mp4',@audio[codec='aac']].mp4")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(pcm_s16,codec=\"aac\"))" audio_only "@ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4")))) (rule (alias encoder_audio_only) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(pcm_f32,codec=\"aac\"))" audio_only "@ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4")))) (rule (alias encoder_video_only) (package liquidsoap) (target @ffmpeg[format='mp4',@video[codec='libx264']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_video_only.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%video(codec=\"libx264\"))" video_only "@ffmpeg[format='mp4',@video[codec='libx264']].mp4")))) (rule (alias encoder_audio_video) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_video.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=1),%video(codec=\"libx264\"))" audio_video "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4")))) (rule (alias encoder_audio_video) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_video.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=2),%video(codec=\"libx264\"))" audio_video "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4")))) (rule (alias encoder_audio_video) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_audio_video.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=2),%video(codec=\"libx264\",r=12))" audio_video "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4")))) (rule (alias encoder_multitrack) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']]_encoder.liq) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_multitrack.liq.in)) (action (with-stdout-to %{target} (run %{mk_encoder_test} "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=2),%audio_2(codec=\"aac\",channels=1),%video(codec=\"libx264\"),%video_2(codec=\"libx264\"))" multitrack "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']].mp4")))) (rule (alias mediatest) (package liquidsoap) (target @fdkaac[aot='mpeg4_aac_lc',channels=1].aac) (deps (:encoder @fdkaac[aot='mpeg4_aac_lc',channels=1]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%fdkaac(aot=\"mpeg4_aac_lc\",channels=1)"))) (rule (alias mediatest) (package liquidsoap) (target @fdkaac[channels=2].aac) (deps (:encoder @fdkaac[channels=2]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%fdkaac(channels=2)"))) (rule (alias mediatest) (package liquidsoap) (target @shine[channels=1].mp3) (deps (:encoder @shine[channels=1]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%shine(channels=1)"))) (rule (alias mediatest) (package liquidsoap) (target @shine[channels=2].mp3) (deps (:encoder @shine[channels=2]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%shine(channels=2)"))) (rule (alias mediatest) (package liquidsoap) (target @flac[stereo].flac) (deps (:encoder @flac[stereo]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%flac(stereo)"))) (rule (alias mediatest) (package liquidsoap) (target @flac[mono].flac) (deps (:encoder @flac[mono]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%flac(mono)"))) (rule (alias mediatest) (package liquidsoap) (target @wav[stereo].wav) (deps (:encoder @wav[stereo]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%wav(stereo)"))) (rule (alias mediatest) (package liquidsoap) (target @wav[mono].wav) (deps (:encoder @wav[mono]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%wav(mono)"))) (rule (alias mediatest) (package liquidsoap) (target @mp3[mono].mp3) (deps (:encoder @mp3[mono]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%mp3(mono)"))) (rule (alias mediatest) (package liquidsoap) (target @mp3[stereo].mp3) (deps (:encoder @mp3[stereo]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%mp3(stereo)"))) (rule (alias mediatest) (package liquidsoap) (target @ogg[@vorbis[mono]].ogg) (deps (:encoder @ogg[@vorbis[mono]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ogg(%vorbis(mono))"))) (rule (alias mediatest) (package liquidsoap) (target @ogg[@vorbis[stereo]].ogg) (deps (:encoder @ogg[@vorbis[stereo]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ogg(%vorbis(stereo))"))) (rule (alias mediatest) (package liquidsoap) (target @ogg[@flac[mono]].ogg) (deps (:encoder @ogg[@flac[mono]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ogg(%flac(mono))"))) (rule (alias mediatest) (package liquidsoap) (target @ogg[@flac[stereo]].ogg) (deps (:encoder @ogg[@flac[stereo]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ogg(%flac(stereo))"))) (rule (alias mediatest) (package liquidsoap) (target @ogg[@opus[mono]].ogg) (deps (:encoder @ogg[@opus[mono]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ogg(%opus(mono))"))) (rule (alias mediatest) (package liquidsoap) (target @ogg[@opus[stereo]].ogg) (deps (:encoder @ogg[@opus[stereo]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ogg(%opus(stereo))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",samplerate=\"48k\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[codec='aac']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(pcm_s16,codec=\"aac\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(pcm_f32,codec=\"aac\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=1),%video(codec=\"libx264\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=2),%video(codec=\"libx264\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=2),%video(codec=\"libx264\",r=12))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@video[codec='libx264']].mp4) (deps (:encoder @ffmpeg[format='mp4',@video[codec='libx264']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%video(codec=\"libx264\"))"))) (rule (alias mediatest) (package liquidsoap) (target @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']].mp4) (deps (:encoder @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']]_encoder.liq) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} %{encoder} liquidsoap %{test_liq} %{encoder} -- "%ffmpeg(format=\"mp4\",%audio(codec=\"aac\",channels=2),%audio_2(codec=\"aac\",channels=1),%video(codec=\"libx264\"),%video_2(codec=\"libx264\"))"))) (rule (alias mediatest) (package liquidsoap) (target all_media_files) (deps @fdkaac[aot='mpeg4_aac_lc',channels=1].aac @fdkaac[channels=2].aac @shine[channels=1].mp3 @shine[channels=2].mp3 @flac[stereo].flac @flac[mono].flac @wav[stereo].wav @wav[mono].wav @mp3[mono].mp3 @mp3[stereo].mp3 @ogg[@vorbis[mono]].ogg @ogg[@vorbis[stereo]].ogg @ogg[@flac[mono]].ogg @ogg[@flac[stereo]].ogg @ogg[@opus[mono]].ogg @ogg[@opus[stereo]].ogg @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4 @ffmpeg[format='mp4',@audio[codec='aac']].mp4 @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4 @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4 @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4 @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4 @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4 @ffmpeg[format='mp4',@video[codec='libx264']].mp4 @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']].mp4) (action (run touch %{target}))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @fdkaac[aot='mpeg4_aac_lc',channels=1].aac" liquidsoap %{test_liq} mono.liq -- "@fdkaac[aot='mpeg4_aac_lc',channels=1].aac"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @fdkaac[aot='mpeg4_aac_lc',channels=1].aac" liquidsoap %{test_liq} stereo.liq -- "@fdkaac[aot='mpeg4_aac_lc',channels=1].aac"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @fdkaac[aot='mpeg4_aac_lc',channels=1].aac" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@fdkaac[aot='mpeg4_aac_lc',channels=1].aac"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @fdkaac[channels=2].aac" liquidsoap %{test_liq} mono.liq -- "@fdkaac[channels=2].aac"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @fdkaac[channels=2].aac" liquidsoap %{test_liq} stereo.liq -- "@fdkaac[channels=2].aac"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @fdkaac[channels=2].aac" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@fdkaac[channels=2].aac"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @shine[channels=1].mp3" liquidsoap %{test_liq} mono.liq -- "@shine[channels=1].mp3"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @shine[channels=1].mp3" liquidsoap %{test_liq} stereo.liq -- "@shine[channels=1].mp3"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @shine[channels=1].mp3" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@shine[channels=1].mp3"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @shine[channels=2].mp3" liquidsoap %{test_liq} mono.liq -- "@shine[channels=2].mp3"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @shine[channels=2].mp3" liquidsoap %{test_liq} stereo.liq -- "@shine[channels=2].mp3"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @shine[channels=2].mp3" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@shine[channels=2].mp3"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @flac[stereo].flac" liquidsoap %{test_liq} mono.liq -- "@flac[stereo].flac"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @flac[stereo].flac" liquidsoap %{test_liq} stereo.liq -- "@flac[stereo].flac"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @flac[stereo].flac" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@flac[stereo].flac"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @flac[mono].flac" liquidsoap %{test_liq} mono.liq -- "@flac[mono].flac"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @flac[mono].flac" liquidsoap %{test_liq} stereo.liq -- "@flac[mono].flac"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @flac[mono].flac" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@flac[mono].flac"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @wav[stereo].wav" liquidsoap %{test_liq} mono.liq -- "@wav[stereo].wav"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @wav[stereo].wav" liquidsoap %{test_liq} stereo.liq -- "@wav[stereo].wav"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @wav[stereo].wav" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@wav[stereo].wav"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @wav[mono].wav" liquidsoap %{test_liq} mono.liq -- "@wav[mono].wav"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @wav[mono].wav" liquidsoap %{test_liq} stereo.liq -- "@wav[mono].wav"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @wav[mono].wav" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@wav[mono].wav"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @mp3[mono].mp3" liquidsoap %{test_liq} mono.liq -- "@mp3[mono].mp3"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @mp3[mono].mp3" liquidsoap %{test_liq} stereo.liq -- "@mp3[mono].mp3"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @mp3[mono].mp3" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@mp3[mono].mp3"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @mp3[stereo].mp3" liquidsoap %{test_liq} mono.liq -- "@mp3[stereo].mp3"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @mp3[stereo].mp3" liquidsoap %{test_liq} stereo.liq -- "@mp3[stereo].mp3"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @mp3[stereo].mp3" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@mp3[stereo].mp3"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ogg[@vorbis[mono]].ogg" liquidsoap %{test_liq} mono.liq -- "@ogg[@vorbis[mono]].ogg"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ogg[@vorbis[mono]].ogg" liquidsoap %{test_liq} stereo.liq -- "@ogg[@vorbis[mono]].ogg"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ogg[@vorbis[mono]].ogg" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ogg[@vorbis[mono]].ogg"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ogg[@vorbis[stereo]].ogg" liquidsoap %{test_liq} mono.liq -- "@ogg[@vorbis[stereo]].ogg"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ogg[@vorbis[stereo]].ogg" liquidsoap %{test_liq} stereo.liq -- "@ogg[@vorbis[stereo]].ogg"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ogg[@vorbis[stereo]].ogg" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ogg[@vorbis[stereo]].ogg"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ogg[@flac[mono]].ogg" liquidsoap %{test_liq} mono.liq -- "@ogg[@flac[mono]].ogg"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ogg[@flac[mono]].ogg" liquidsoap %{test_liq} stereo.liq -- "@ogg[@flac[mono]].ogg"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ogg[@flac[mono]].ogg" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ogg[@flac[mono]].ogg"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ogg[@flac[stereo]].ogg" liquidsoap %{test_liq} mono.liq -- "@ogg[@flac[stereo]].ogg"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ogg[@flac[stereo]].ogg" liquidsoap %{test_liq} stereo.liq -- "@ogg[@flac[stereo]].ogg"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ogg[@flac[stereo]].ogg" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ogg[@flac[stereo]].ogg"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ogg[@opus[mono]].ogg" liquidsoap %{test_liq} mono.liq -- "@ogg[@opus[mono]].ogg"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ogg[@opus[mono]].ogg" liquidsoap %{test_liq} stereo.liq -- "@ogg[@opus[mono]].ogg"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ogg[@opus[mono]].ogg" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ogg[@opus[mono]].ogg"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ogg[@opus[stereo]].ogg" liquidsoap %{test_liq} mono.liq -- "@ogg[@opus[stereo]].ogg"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ogg[@opus[stereo]].ogg" liquidsoap %{test_liq} stereo.liq -- "@ogg[@opus[stereo]].ogg"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ogg[@opus[stereo]].ogg" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ogg[@opus[stereo]].ogg"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4" liquidsoap %{test_liq} mono.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4" liquidsoap %{test_liq} stereo.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ffmpeg[format='mp4',@audio[codec='aac']].mp4" liquidsoap %{test_liq} mono.liq -- "@ffmpeg[format='mp4',@audio[codec='aac']].mp4"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ffmpeg[format='mp4',@audio[codec='aac']].mp4" liquidsoap %{test_liq} stereo.liq -- "@ffmpeg[format='mp4',@audio[codec='aac']].mp4"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ffmpeg[format='mp4',@audio[codec='aac']].mp4" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac']].mp4"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4" liquidsoap %{test_liq} mono.liq -- "@ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4" liquidsoap %{test_liq} stereo.liq -- "@ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ffmpeg[format='mp4',@audio[pcm_s16,codec='aac']].mp4"))) (rule (alias mono) (package liquidsoap) (deps all_media_files mono.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Mono decoding test for @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4" liquidsoap %{test_liq} mono.liq -- "@ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4"))) (rule (alias stereo) (package liquidsoap) (deps all_media_files stereo.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Stereo decoding test for @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4" liquidsoap %{test_liq} stereo.liq -- "@ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4"))) (rule (alias ffmpeg_audio_decoder) (package liquidsoap) (deps all_media_files ffmpeg_audio_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg audio decoder test for @ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4" liquidsoap %{test_liq} ffmpeg_audio_decoder.liq -- "@ffmpeg[format='mp4',@audio[pcm_f32,codec='aac']].mp4"))) (rule (alias ffmpeg_video_decoder) (package liquidsoap) (deps all_media_files ffmpeg_video_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video decoder test for @ffmpeg[format='mp4',@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_video_decoder.liq -- "@ffmpeg[format='mp4',@video[codec='libx264']].mp4"))) (rule (alias video_size) (package liquidsoap) (deps all_media_files video_size.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video size test for @ffmpeg[format='mp4',@video[codec='libx264']].mp4" liquidsoap %{test_liq} video_size.liq -- "@ffmpeg[format='mp4',@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_video_decoder) (package liquidsoap) (deps all_media_files ffmpeg_video_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_video_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias video_size) (package liquidsoap) (deps all_media_files video_size.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video size test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} video_size.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_video_decoder) (package liquidsoap) (deps all_media_files ffmpeg_video_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_video_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias video_size) (package liquidsoap) (deps all_media_files video_size.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video size test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} video_size.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_video_decoder) (package liquidsoap) (deps all_media_files ffmpeg_video_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_video_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias video_size) (package liquidsoap) (deps all_media_files video_size.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg video size test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} video_size.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_add_text) (package liquidsoap) (deps all_media_files ffmpeg_add_text.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg add text filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_add_text.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_copy_decoder) (package liquidsoap) (deps all_media_files ffmpeg_copy_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_copy_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_copy_and_encode_decoder) (package liquidsoap) (deps all_media_files ffmpeg_copy_and_encode_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy+encode decode test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_copy_and_encode_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_filter) (package liquidsoap) (deps all_media_files ffmpeg_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_filter.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_filter_parse) (package liquidsoap) (deps all_media_files ffmpeg_filter_parse.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter parse test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_filter_parse.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_bitstream_filter) (package liquidsoap) (deps all_media_files ffmpeg_bitstream_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg bitstream filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_bitstream_filter.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_raw_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_raw_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_raw_and_encode_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_and_encode_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw+encode decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_raw_and_encode_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_raw_and_copy_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_and_copy_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw+copy decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_raw_and_copy_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_add_text) (package liquidsoap) (deps all_media_files ffmpeg_add_text.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg add text filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_add_text.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_copy_decoder) (package liquidsoap) (deps all_media_files ffmpeg_copy_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_copy_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_copy_and_encode_decoder) (package liquidsoap) (deps all_media_files ffmpeg_copy_and_encode_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy+encode decode test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_copy_and_encode_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_filter) (package liquidsoap) (deps all_media_files ffmpeg_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_filter.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_filter_parse) (package liquidsoap) (deps all_media_files ffmpeg_filter_parse.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter parse test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_filter_parse.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_bitstream_filter) (package liquidsoap) (deps all_media_files ffmpeg_bitstream_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg bitstream filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_bitstream_filter.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_raw_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_raw_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_raw_and_encode_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_and_encode_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw+encode decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_raw_and_encode_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_raw_and_copy_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_and_copy_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw+copy decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" liquidsoap %{test_liq} ffmpeg_raw_and_copy_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4"))) (rule (alias ffmpeg_add_text) (package liquidsoap) (deps all_media_files ffmpeg_add_text.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg add text filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_add_text.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_copy_decoder) (package liquidsoap) (deps all_media_files ffmpeg_copy_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_copy_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_copy_and_encode_decoder) (package liquidsoap) (deps all_media_files ffmpeg_copy_and_encode_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg copy+encode decode test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_copy_and_encode_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_filter) (package liquidsoap) (deps all_media_files ffmpeg_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_filter.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_filter_parse) (package liquidsoap) (deps all_media_files ffmpeg_filter_parse.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg filter parse test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_filter_parse.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_bitstream_filter) (package liquidsoap) (deps all_media_files ffmpeg_bitstream_filter.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg bitstream filter test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_bitstream_filter.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_raw_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_raw_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_raw_and_encode_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_and_encode_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw+encode decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_raw_and_encode_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias ffmpeg_raw_and_copy_decoder) (package liquidsoap) (deps all_media_files ffmpeg_raw_and_copy_decoder.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "FFmpeg raw+copy decoder test for @ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4" liquidsoap %{test_liq} ffmpeg_raw_and_copy_decoder.liq -- "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264',r=12]].mp4"))) (rule (alias multitrack) (package liquidsoap) (deps all_media_files multitrack.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "multitrack.liq" liquidsoap %{test_liq} multitrack.liq -- ""))) (rule (alias ffmpeg_inline_encode_decode) (package liquidsoap) (deps all_media_files ffmpeg_inline_encode_decode.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "ffmpeg_inline_encode_decode.liq" liquidsoap %{test_liq} ffmpeg_inline_encode_decode.liq -- ""))) (rule (alias ffmpeg_inline_encode_decode_audio) (package liquidsoap) (deps all_media_files ffmpeg_inline_encode_decode_audio.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "ffmpeg_inline_encode_decode_audio.liq" liquidsoap %{test_liq} ffmpeg_inline_encode_decode_audio.liq -- ""))) (rule (alias ffmpeg_inline_encode_decode_video) (package liquidsoap) (deps all_media_files ffmpeg_inline_encode_decode_video.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "ffmpeg_inline_encode_decode_video.liq" liquidsoap %{test_liq} ffmpeg_inline_encode_decode_video.liq -- ""))) (rule (alias ffmpeg_raw_hls) (package liquidsoap) (deps all_media_files ffmpeg_raw_hls.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "ffmpeg_raw_hls.liq" liquidsoap %{test_liq} ffmpeg_raw_hls.liq -- ""))) (rule (alias pcm_s16_decode) (package liquidsoap) (deps all_media_files pcm_s16_decode.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "pcm_s16_decode.liq" liquidsoap %{test_liq} pcm_s16_decode.liq -- ""))) (rule (alias pcm_f32_decode) (package liquidsoap) (deps all_media_files pcm_f32_decode.liq ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "pcm_f32_decode.liq" liquidsoap %{test_liq} pcm_f32_decode.liq -- ""))) (alias (name mediatest) (deps (alias encoder_audio_only) (alias encoder_audio_video) (alias encoder_multitrack) (alias encoder_video_only) (alias ffmpeg_add_text) (alias ffmpeg_audio_decoder) (alias ffmpeg_bitstream_filter) (alias ffmpeg_copy_and_encode_decoder) (alias ffmpeg_copy_decoder) (alias ffmpeg_filter) (alias ffmpeg_filter_parse) (alias ffmpeg_inline_encode_decode) (alias ffmpeg_inline_encode_decode_audio) (alias ffmpeg_inline_encode_decode_video) (alias ffmpeg_raw_and_copy_decoder) (alias ffmpeg_raw_and_encode_decoder) (alias ffmpeg_raw_decoder) (alias ffmpeg_raw_hls) (alias ffmpeg_video_decoder) (alias mono) (alias multitrack) (alias pcm_f32_decode) (alias pcm_s16_decode) (alias stereo) (alias video_size))) liquidsoap-2.4.2/tests/media/encoder_audio_only.liq.in000066400000000000000000000002751513273233300230750ustar00rootroot00000000000000 log.level.set(4) file = "@FILE@" s = sine(duration=10.) s = once(s) clock.assign_new(sync="none",[s]) output.file( fallible=true,on_close=fun (_) -> test.pass(), @FORMAT@,file,s) liquidsoap-2.4.2/tests/media/encoder_audio_video.liq.in000066400000000000000000000004751513273233300232240ustar00rootroot00000000000000 log.level.set(4) file = "@FILE@" let {audio} = source.tracks(sine(duration=10.)) let {video} = source.tracks(video.testsrc.ffmpeg(duration=10.)) s = source({audio=audio, video=video}) s = once(s) clock.assign_new(sync="none",[s]) output.file( fallible=true,on_close=fun (_) -> test.pass(), @FORMAT@,file,s) liquidsoap-2.4.2/tests/media/encoder_multitrack.liq.in000066400000000000000000000006641513273233300231140ustar00rootroot00000000000000 log.level.set(4) file = "@FILE@" a = source.tracks(sine(duration=10.)).audio a_2 = source.tracks(sine(duration=10.)).audio v = source.tracks(video.testsrc.ffmpeg(duration=10.)).video v_2 = source.tracks(video.testsrc.ffmpeg(duration=10.)).video s = source({audio=a, audio_2=a_2, video=v, video_2=v_2}) s = once(s) clock.assign_new(sync="none",[s]) output.file( fallible=true,on_close=fun (_) -> test.pass(), @FORMAT@,file,s) liquidsoap-2.4.2/tests/media/encoder_video_only.liq.in000066400000000000000000000003151513273233300230750ustar00rootroot00000000000000 log.level.set(4) file = "@FILE@" s = video.testsrc.ffmpeg(duration=10.) s = once(s) clock.assign_new(sync="none",[s]) output.file( fallible=true,on_close=fun (_) -> test.pass(), @FORMAT@,file,s) liquidsoap-2.4.2/tests/media/ffmpeg_add_text.liq000066400000000000000000000031151513273233300217430ustar00rootroot00000000000000log.level.set(4) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_filter-#{random.int()}.mp4"} s = single(fname) s = once(s) s = video.add_text.ffmpeg( "bla blo", s ) #clock.assign_new(id='test_clock', sync='none', [s]) def on_close(filename) = ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) output_format = json.parse(default=[("streams", [[("samplerate", "0")]])], ojson) output_streams = list.assoc(default=[], "streams", output_format) params = ["channel_layout", "sample_rate", "sample_fmt", "codec_name", "pix_fmt"] def m(s) = def f(e) = let (p, _) = e list.mem(p, params) end list.filter(f, s) end output_streams = list.map(m, output_streams) def cmp(c, c') = if c < c' then -1 elsif c' < c then 1 else 0 end end output_streams = list.map((fun (stream) -> list.sort(cmp, stream)), output_streams) def cmd_l(l, l') = cmp(list.assoc("codec_name", l), list.assoc("codec_name", l')) end output_streams = list.sort(cmd_l, output_streams) expected = [ [ ("channel_layout", "stereo"), ("codec_name", "aac"), ("sample_fmt", "fltp"), ("sample_rate", "44100") ], [("codec_name", "h264"), ("pix_fmt", "yuv420p")] ] if output_streams == expected then test.pass() else test.fail() end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mkv", %audio(codec = "aac"), %video(codec = "libx264")), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_audio_decoder.liq000066400000000000000000000016261513273233300227420ustar00rootroot00000000000000log.level.set(5) settings.frame.audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_audio_decoder-#{random.int()}.wav"} s = single(fname) s = once(s) done = ref(false) ##clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : {streams: [{channels: int, sample_rate: string}]} ) = j let [stream] = parsed.streams if stream.channels == 1 and stream.sample_rate == "48000" then test.pass() else test.fail() end end end output.file(fallible=true, on_close=on_close, %wav(mono), out, s) liquidsoap-2.4.2/tests/media/ffmpeg_bitstream_filter.liq000066400000000000000000000037701513273233300235150ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_copy_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) def on_close(encoded_fname) = if not done() then done := true process.run("sync") ijson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(fname) }" ) ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(encoded_fname) }" ) let json.parse (iparsed : { streams: [ { index: int, channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_type: string, pix_fmt: string? } ] } ) = ijson let json.parse (oparsed : { streams: [ { index: int, channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_type: string, pix_fmt: string? } ] } ) = ojson filter = fun (type, l) -> list.filter(fun (s) -> s.codec_type == type, l) sort = fun (l) -> list.sort(fun (s1, s2) -> if s1.index < s2.index then -1 else 1 end, l) let [{index, ...iaudio}] = sort(filter("audio", iparsed.streams)) let [{index, ...ivideo}] = sort(filter("video", iparsed.streams)) let [{index, ...oaudio}] = sort(filter("audio", oparsed.streams)) let [{index, ...ovideo}] = sort(filter("video", oparsed.streams)) if iaudio == oaudio and ivideo == ovideo then test.pass() else test.fail() end end end s = ffmpeg.filter.bitstream.h264_mp4toannexb(s) s = ffmpeg.filter.bitstream.aac_adtstoasc(s) clock.assign_new(sync='none', [s]) output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mkv", %audio.copy, %video.copy), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_complex_filter.liq000066400000000000000000000032451513273233300231670ustar00rootroot00000000000000def filter(s, s') = def mkfilter_multi(graph) = s = ffmpeg.filter.audio.input(graph, pass_metadata=true, s) s = ffmpeg.filter.dynaudnorm( graph, s, gausssize=7, correctdc=true, altboundary=true, maxgain=80., b=true, targetrms=1. ) s = ffmpeg.filter.volume(graph, s, volume="-18dB", precision=2, replaygain=0) %ifdef ffmpeg.filter.aexciter s = ffmpeg.filter.aexciter(graph, s, freq=6000.) %endif s = ffmpeg.filter.volume(graph, s, volume="+20dB", precision=2, replaygain=0) s = ffmpeg.filter.aresample(graph, s, sample_rate=192000) s = ffmpeg.filter.alimiter( graph, s, limit=0.8, attack=3., release=50., asc=false, asc_level=0., level=true ) s = ffmpeg.filter.aresample(graph, s, sample_rate=48000) s' = ffmpeg.filter.audio.input(graph, s') s = ffmpeg.filter.amix(graph, inputs=2, [s, s'], []) s = ffmpeg.filter.ametadata(graph, mode=1, key="tag", value="test", s) ffmpeg.filter.audio.output(graph, pass_metadata=true, s) end ffmpeg.filter.create(mkfilter_multi) end s = single("annotate:tag=\"s\":@mp3[stereo].mp3") s' = single("annotate:tag=\"s'\":@mp3[mono].mp3") audio = filter(source.tracks(s).audio, source.tracks(s').audio) s = source({audio=audio, metadata=track.metadata(audio)}) s.on_metadata( synchronous=true, fun (m) -> if m["tag"] == "test" then test.pass() end ) clock.assign_new(sync="none", [s]) enc = %ffmpeg(%audio.raw(codec = "aac")) filename = file.temp("tmp", ".aac") on_cleanup({file.remove(filename)}) output.file(fallible=true, enc, filename, s) liquidsoap-2.4.2/tests/media/ffmpeg_copy_and_encode_decoder.liq000066400000000000000000000030051513273233300247430ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_copy_and_encode_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mkv", %audio(codec = "aac"), %video.copy), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_copy_concat.liq000066400000000000000000000016211513273233300224500ustar00rootroot00000000000000# For some reason, ffmpeg uses weird frame rates when creating the samples below. settings.ffmpeg.content.copy.relaxed_compatibility_check := true out = {"concat-#{random.int()}.mp4"} s1 = single("first-concat.mp4") s2 = single("second-concat.mp4") s3 = single("third-concat.mp4") s = sequence([s1, s2, s3, switch([])]) clock.assign_new(sync='none', [s]) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse {streams = [{duration}]} = ojson # Actual time is 59.93.. if int(float_of_string(duration)) == 59 then test.pass() else test.fail() end end end output.file(fallible=true, on_close=on_close, %ffmpeg(%video.copy), out, s) liquidsoap-2.4.2/tests/media/ffmpeg_copy_decoder.liq000066400000000000000000000036321513273233300226120ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_copy_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(encoded_fname) = if not done() then done := true process.run("sync") ijson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(fname) }" ) ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(encoded_fname) }" ) let json.parse (iparsed : { streams: [ { index: int, channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_type: string, pix_fmt: string? } ] } ) = ijson let json.parse (oparsed : { streams: [ { index: int, channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_type: string, pix_fmt: string? } ] } ) = ojson filter = fun (type, l) -> list.filter(fun (s) -> s.codec_type == type, l) sort = fun (l) -> list.sort(fun (s1, s2) -> if s1.index < s2.index then -1 else 1 end, l) let [{index, ...iaudio}] = sort(filter("audio", iparsed.streams)) let [{index, ...ivideo}] = sort(filter("video", iparsed.streams)) let [{index, ...oaudio}] = sort(filter("audio", oparsed.streams)) let [{index, ...ovideo}] = sort(filter("video", oparsed.streams)) if iaudio == oaudio and ivideo == ovideo then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mkv", %audio.copy, %video.copy), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_distributed_hls.liq000066400000000000000000000051751513273233300233470ustar00rootroot00000000000000log.level.set(5) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) debian_version = string.trim( process.run( "cat /etc/os-release | grep VERSION_ID | cut -d'=' -f 2 | xargs" ).stdout ) if debian_version == "10" then test.skip() end fname = argv(default="", 1) main_encoder = %ffmpeg( %audio(codec = "aac", b = "128k", channels = 2, ar = 44100), %video(codec = "libx264", b = "5M", flags = "+global_header") ) mpegts = %ffmpeg(format = "mpegts", %audio.copy, %video.copy) mp4 = %ffmpeg( format = "mp4", movflags = "+dash+skip_sidx+skip_trailer+frag_custom", frag_duration = 2, %audio.copy, %video.copy ) s = single(fname) encoded = ffmpeg.encode.audio_video(main_encoder, s) streams = [("mp4", mp4), ("mpegts", mpegts)] output_dir = file.temp_dir("liq", "hls") state_file = path.concat(path.dirname(argv(0)), "ffmpeg_distributed_hls_state.json") process.run( "cp #{state_file} #{output_dir}/state.json" ) def cleanup() = file.rmdir(output_dir) end on_cleanup(cleanup) is_done = ref(false) def check_stream() = if not !is_done then is_done := true ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ output_dir }/mp4.m3u8" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = ojson video_stream = list.find((fun (stream) -> stream.codec_name == "h264"), parsed.streams) audio_stream = list.find((fun (stream) -> stream.codec_name == "aac"), parsed.streams) if null.get(video_stream.codec_name) == "h264" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end def segment_name(metadata) = let {position, extname, stream_name} = metadata if position > 10 then check_stream() end timestamp = int_of_float(time()) "#{stream_name}_#{timestamp}_#{position}.#{extname}" end #clock.assign_new(id='test_clock', sync='none', [encoded]) output.file.hls( playlist="live.m3u8", segment_duration=2.0, segments=5, segments_overhead=5, segment_name=segment_name, output_dir, persist_at="#{output_dir}/state.json", strict_persist=true, streams, fallible=true, encoded ) liquidsoap-2.4.2/tests/media/ffmpeg_distributed_hls_state.json000066400000000000000000000052711513273233300247300ustar00rootroot00000000000000{ "streams": [ { "name": "mp4", "position": 7, "pending_extra_tags": [], "discontinuity_count": 1 }, { "name": "mpegts", "position": 7, "pending_extra_tags": [], "discontinuity_count": 1 } ], "segments": { "mp4": [ { "id": 1, "discontinuous": false, "current_discontinuity": 0, "filename": "/tmp/mp4_1617977940_1.m4s", "init_filename": "/tmp/mp4_1617977940_0.m4s", "extra_tags": [], "len": 441000 }, { "id": 2, "discontinuous": false, "current_discontinuity": 0, "filename": "/tmp/mp4_1617977950_2.m4s", "init_filename": "/tmp/mp4_1617977940_0.m4s", "extra_tags": [], "len": 439236 }, { "id": 3, "discontinuous": false, "current_discontinuity": 0, "filename": "/tmp/mp4_1617977960_3.m4s", "init_filename": "/tmp/mp4_1617977940_0.m4s", "extra_tags": [], "len": 340452 }, { "id": 5, "discontinuous": true, "current_discontinuity": 0, "filename": "/tmp/mp4_1617977985_5.m4s", "init_filename": "/tmp/mp4_1617977985_4.m4s", "extra_tags": [], "len": 441000 }, { "id": 6, "discontinuous": false, "current_discontinuity": 1, "filename": "/tmp/mp4_1617977995_6.m4s", "init_filename": "/tmp/mp4_1617977985_4.m4s", "extra_tags": [], "len": 386316 } ], "mpegts": [ { "id": 1, "discontinuous": false, "current_discontinuity": 0, "filename": "/tmp/mpegts_1617977940_1.ts", "init_filename": null, "extra_tags": [], "len": 441000 }, { "id": 2, "discontinuous": false, "current_discontinuity": 0, "filename": "/tmp/mpegts_1617977950_2.ts", "init_filename": null, "extra_tags": [], "len": 439236 }, { "id": 3, "discontinuous": false, "current_discontinuity": 0, "filename": "/tmp/mpegts_1617977960_3.ts", "init_filename": null, "extra_tags": [], "len": 340452 }, { "id": 5, "discontinuous": true, "current_discontinuity": 0, "filename": "/tmp/mpegts_1617977985_5.ts", "init_filename": null, "extra_tags": [], "len": 441000 }, { "id": 6, "discontinuous": false, "current_discontinuity": 1, "filename": "/tmp/mpegts_1617977995_6.ts", "init_filename": null, "extra_tags": [], "len": 386316 } ] } } liquidsoap-2.4.2/tests/media/ffmpeg_drop_tracks.liq000066400000000000000000000047011513273233300224640ustar00rootroot00000000000000output_dir = file.temp_dir("liq", "hls") def cleanup() = file.rmdir(output_dir) end input_a = sine(duration=10.) input_b = sine(duration=10.) input_c = sine(duration=10.) let {audio = audio_a} = source.tracks(input_a) let {audio = audio_b} = source.tracks(input_b) let {audio = audio_c} = source.tracks(input_c) muxed_input = source({audio_a=audio_a, audio_b=audio_b, audio_c=audio_c}) streams = [ ( "a", %ffmpeg( format = "mpegts", %audio_a(codec = "aac"), %audio_b.drop, %audio_c.drop ) ), ( "b", %ffmpeg( format = "mpegts", %audio_a.drop, %audio_b(codec = "aac"), %audio_c.drop ) ), ( "c", %ffmpeg( format = "mpegts", %audio_a.drop, %audio_b.drop, %audio_c(codec = "aac") ) ) ] is_done = ref(false) def check_stream() = if not is_done() then is_done := true def check_stream(name) = file.write( append=true, data="\r\n#EXT-X-ENDLIST\r\n", "#{output_dir}/#{name}.m3u8" ) stream = process.quote("#{output_dir}/#{name}.m3u8") ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{stream}" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string? } ] } ) = ojson audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" end if check_stream("a") and check_stream("b") and check_stream("c") then test.pass() else test.fail() end end end def segment_name(metadata) = let {position, extname, stream_name} = metadata if position > 2 then check_stream() end timestamp = int_of_float(time()) "#{stream_name}_#{timestamp}_#{position}.#{extname}" end output.file.hls( fallible=true, playlist="live.m3u8", segment_duration=2.0, segments=5, segments_overhead=5, segment_name=segment_name, output_dir, streams, muxed_input ) liquidsoap-2.4.2/tests/media/ffmpeg_filter.liq000066400000000000000000000040331513273233300214340ustar00rootroot00000000000000log.level.set(4) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_filter-#{random.int()}.mp4"} def f(s) = tracks = source.tracks(s) def mkfilter(graph) = a = ffmpeg.filter.audio.input(graph, tracks.audio) a = ffmpeg.filter.flanger(graph, a, delay=10.) a = ffmpeg.filter.highpass(graph, a, frequency=4000.) a = ffmpeg.filter.audio.output(graph, a) v = ffmpeg.filter.video.input(graph, tracks.video) v = ffmpeg.filter.hflip(graph, v) v = ffmpeg.filter.video.output(graph, v) source({audio=a, video=v}) end ffmpeg.filter.create(mkfilter) end s = single(fname) s = once(s) s = f(s) done = ref(false) clock.assign_new(id='test_clock', sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg( format = "mkv", %audio.raw(codec = "aac"), %video.raw(codec = "libx264") ), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_filter_changing_rate.liq000066400000000000000000000047671513273233300243230ustar00rootroot00000000000000log.level := 4 settings.decoder.decoders := ["ffmpeg"] fname = argv(default="", 1) fname2 = argv(default="", 2) out = {"#{fname}+ffmpeg_filter_changing_rate-#{random.int()}.mp4"} test.skip() def f(s) = tracks = source.tracks(s) def mkfilter(graph) = a = ffmpeg.filter.audio.input(graph, tracks.audio) a = ffmpeg.filter.flanger(graph, a, delay=10.) a = ffmpeg.filter.highpass(graph, a, frequency=4000.) a = ffmpeg.filter.audio.output(graph, a) v = ffmpeg.filter.video.input(graph, tracks.video) v = ffmpeg.filter.hflip(graph, v) v = ffmpeg.filter.video.output(graph, v) source({audio=a, video=v, metadata=track.metadata(a)}) end ffmpeg.filter.create(mkfilter) end s = single(fname) s' = single(fname2) last = single(id="last", fname) last = metadata.map(insert_missing=true, fun (_) -> [("title", "done")], last) s = sequence([s, s', s, s', s, last, s, s', s]) s = f(s) done = ref(false) passed = ref(false) #clock.assign_new(id='test_clock', sync='none', [s]) def on_done(filename) = if done() and not passed() then process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then passed := true test.pass() else test.fail() end end end def reopen_on_metadata(metadata) = if metadata["title"] == "done" then begin done := true true end else false end end output.file( fallible=true, reopen_on_metadata=reopen_on_metadata, on_close=on_done, %ffmpeg( format = "mp4", %audio.raw(codec = "aac"), %video.raw(codec = "libx264") ), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_filter_parse.liq000066400000000000000000000044531513273233300226340ustar00rootroot00000000000000log.level.set(4) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_filter-#{random.int()}.mp4"} def f(s) = let {audio = a, video = v} = source.tracks(s) def mkfilter(graph) = a = ffmpeg.filter.audio.input(graph, a) v = ffmpeg.filter.video.input(graph, v) let {audio = audio_out, video = video_out} = ffmpeg.filter.parse( graph, description="[v_in]hue=s=0[hue];[hue]fps=fps=30[fps];[fps]format[v_out];[a_in]volume=0.5[volume];[volume]aformat[a_out]", inputs={audio=[("a_in", a)], video=[("v_in", v)]}, outputs={audio=["a_out"], video=["v_out"]} ) a = list.assoc("a_out", audio_out) v = list.assoc("v_out", video_out) a = ffmpeg.filter.audio.output(graph, a) v = ffmpeg.filter.video.output(graph, v) source({audio=a, video=v}) end ffmpeg.filter.create(mkfilter) end s = single(fname) s = once(s) s = f(s) done = ref(false) clock.assign_new(id='test_clock', sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg( format = "mkv", %audio.raw(codec = "aac"), %video.raw(codec = "libx264") ), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_inline_encode_decode.liq000066400000000000000000000042121513273233300242440ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) out_copy = "ffmpeg_inline_encode_decode_copy.mp4" if file.exists(out_copy) then file.remove(out_copy) end out_encode = "ffmpeg_inline_encode_decode_encode.mp4" if file.exists(out_encode) then file.remove(out_encode) end let {audio} = source.tracks(sine(duration=10.)) let {video} = source.tracks(video.testsrc.ffmpeg(duration=10.)) s = source({audio=audio, video=video}) s = once(s) todo = ref(2) done = ref(false) def on_close(_) = def check(out) = process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(out) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" end todo := !todo - 1 if not done() and !todo == 0 then done := true if check(out_copy) and check(out_encode) then test.pass() else test.fail() end end end s = ffmpeg.encode.audio_video( id="encoder", %ffmpeg(%video(codec = "libx264", b = "5m"), %audio(codec = "aac")), s ) output.file( fallible=true, on_close=on_close, %ffmpeg(%video.copy, %audio.copy), out_copy, s ) s = ffmpeg.decode.audio_video(id="decoder", s) #clock.assign_new(sync='none', [s]) output.file( fallible=true, on_close=on_close, %ffmpeg(%video(codec = "libx264", b = "5m"), %audio(codec = "aac")), out_encode, s ) liquidsoap-2.4.2/tests/media/ffmpeg_inline_encode_decode_audio.liq000066400000000000000000000027101513273233300254260ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) out_copy = "ffmpeg_inline_encode_decode_audio_copy.mp4" if file.exists(out_copy) then file.remove(out_copy) end out_encode = "ffmpeg_inline_encode_decode_audio_encode.mp4" if file.exists(out_encode) then file.remove(out_encode) end s = once(blank(duration=10.)) todo = ref(2) done = ref(false) def on_close(_) = def check(out) = j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(out) }" ) let json.parse (parsed : { streams: [ { channel_layout: string, sample_rate: string, sample_fmt: string, codec_name: string } ] } ) = j let [stream] = parsed.streams stream.channel_layout == "stereo" and stream.codec_name == "aac" and stream.sample_fmt == "fltp" and stream.sample_rate == "44100" end todo := !todo - 1 if not done() and !todo == 0 then done := true if check(out_copy) and check(out_encode) then test.pass() else test.fail() end end end s = ffmpeg.encode.audio(%ffmpeg(%audio(codec = "aac")), s) output.file(fallible=true, on_close=on_close, %ffmpeg(%audio.copy), out_copy, s) s = ffmpeg.decode.audio(s) clock.assign_new(sync='none', [s]) output.file( fallible=true, on_close=on_close, %ffmpeg(%audio(codec = "aac")), out_encode, s ) liquidsoap-2.4.2/tests/media/ffmpeg_inline_encode_decode_video.liq000066400000000000000000000025731513273233300254420ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) out_copy = "ffmpeg_inline_encode_decode_video_copy.mp4" if file.exists(out_copy) then file.remove(out_copy) end out_encode = "ffmpeg_inline_encode_decode_video_encode.mp4" if file.exists(out_encode) then file.remove(out_encode) end let {audio} = source.tracks(sine(duration=10.)) let {video} = source.tracks(video.testsrc.ffmpeg(duration=10.)) s = source({audio=audio, video=video}) todo = ref(2) done = ref(false) def on_close(_) = def check(out) = process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(out) }" ) let json.parse (parsed : {streams: [{codec_name: string, pix_fmt: string}]} ) = j let [stream] = parsed.streams stream.codec_name == "h264" and stream.pix_fmt == "yuv420p" end todo := !todo - 1 if not done() and !todo == 0 then done := true if check(out_copy) and check(out_encode) then test.pass() else test.fail() end end end s = ffmpeg.encode.video(%ffmpeg(%video(codec = "libx264")), s) output.file(fallible=true, on_close=on_close, %ffmpeg(%video.copy), out_copy, s) s = ffmpeg.decode.video(s) clock.assign_new(sync='none', [s]) output.file( fallible=true, on_close=on_close, %ffmpeg(%video(codec = "libx264")), out_encode, s ) liquidsoap-2.4.2/tests/media/ffmpeg_raw_and_copy_decoder.liq000066400000000000000000000037061513273233300243070ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_raw_and_copy_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(encoded_fname) = if not done() then done := true process.run("sync") ijson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(fname) }" ) ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(encoded_fname) }" ) let json.parse (iparsed : { streams: [ { index: int, channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_type: string, pix_fmt: string? } ] } ) = ijson let json.parse (oparsed : { streams: [ { index: int, channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_type: string, pix_fmt: string? } ] } ) = ojson filter = fun (type, l) -> list.filter(fun (s) -> s.codec_type == type, l) sort = fun (l) -> list.sort(fun (s1, s2) -> if s1.index < s2.index then -1 else 1 end, l) let [{index = _, ...iaudio}] = sort(filter("audio", iparsed.streams)) let [{index = _, ...ivideo}] = sort(filter("video", iparsed.streams)) let [{index = _, ...oaudio}] = sort(filter("audio", oparsed.streams)) let [{index = _, ...ovideo}] = sort(filter("video", oparsed.streams)) if iaudio == oaudio and ivideo == ovideo then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mkv", %audio.copy, %video.raw(codec = "libx264")), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_raw_and_encode_decoder.liq000066400000000000000000000030251513273233300245640ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_raw_and_encode_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mkv", %audio(codec = "aac"), %video.raw(codec = "libx264")), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_raw_decoder.liq000066400000000000000000000030471513273233300224310ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_raw_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = ojson video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg( format = "mkv", %audio.raw(codec = "aac"), %video.raw(codec = "libx264") ), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_raw_hls.liq000066400000000000000000000047471513273233300216220ustar00rootroot00000000000000log.level.set(5) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) debian_version = string.trim( process.run( "cat /etc/os-release | grep VERSION_ID | cut -d'=' -f 2 | xargs" ).stdout ) if debian_version == "10" then test.skip() end raw_encoder = %ffmpeg(%audio.raw, %video.raw) mp4 = %ffmpeg( format = "mp4", %audio.raw(codec = "aac", b = "128k", channels = 2, ar = 44100), %video.raw( codec = "libx264", b = "5M", tune = "zerolatency", x264opts = "keyint=12:min-keyint=12" ) ) a = source.tracks(sine(duration=10.)).audio v = source.tracks(video.testsrc.ffmpeg(duration=10.)).video s = source({audio=a, video=v}) raw = ffmpeg.raw.encode.audio_video(raw_encoder, s) streams = [("mp4", mp4)] output_dir = file.temp_dir("liq", "hls") def cleanup() = file.rmdir(output_dir) end on_cleanup(cleanup) is_done = ref(false) def check_stream() = if not !is_done then is_done := true file.write( append=true, data="\r\n#EXT-X-ENDLIST\r\n", "#{output_dir}/mp4.m3u8" ) ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ output_dir }/mp4.m3u8" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = ojson video_stream = list.find((fun (stream) -> stream.codec_name == "h264"), parsed.streams) audio_stream = list.find((fun (stream) -> stream.codec_name == "aac"), parsed.streams) if null.get(video_stream.codec_name) == "h264" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end def segment_name(metadata) = let {position, extname, stream_name} = metadata if position > 1 then check_stream() end timestamp = int_of_float(time()) "#{stream_name}_#{timestamp}_#{position}.#{extname}" end clock.assign_new(sync='none', [raw]) output.file.hls( playlist="live.m3u8", segment_duration=2.0, segments=5, segments_overhead=5, segment_name=segment_name, output_dir, streams, fallible=true, raw ) liquidsoap-2.4.2/tests/media/ffmpeg_raw_implicit_conversion.liq000066400000000000000000000023321513273233300250770ustar00rootroot00000000000000log.level.set(5) settings.decoder.decoders.set(["ffmpeg"]) out = {"ffmpeg_raw_implicit_conversion-#{random.int()}.ts"} files = [ "@ffmpeg[format='mp4',@audio[codec='aac',samplerate='48k']].mp4", "@wav[mono].wav", "@wav[stereo].wav" ] s = sequence(list.map(fun (fname) -> once(single(fname)), files)) clock.assign_new(sync='none', [s]) def on_close(filename) = ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string, sample_rate: string, sample_fmt: string, codec_name: string, duration: string } ] } ) = ojson audio_stream = list.hd(parsed.streams) if audio_stream.channel_layout == "stereo" and audio_stream.codec_name == "aac" and audio_stream.sample_fmt == "fltp" and audio_stream.sample_rate == "44100" and float_of_string(audio_stream.duration) > 29. then test.pass() else test.fail() end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mpegts", %audio.raw(codec = "aac", samplerate = 44100)), out, s ) liquidsoap-2.4.2/tests/media/ffmpeg_transparency_filter.liq000066400000000000000000000037671513273233300242420ustar00rootroot00000000000000log.level.set(4) background = single("background.jpg") playlist = single("@mp3[stereo].mp3") logo = single("logo.png") out = {"transparency-#{random.int()}.mp4"} def logo_overlay(background, logo) = def mkfilter(graph) = background = ffmpeg.filter.video.input(graph, background) logo = ffmpeg.filter.video.input(graph, logo) v = ffmpeg.filter.overlay(graph, background, logo, y="50", x="25") ffmpeg.filter.video.output(graph, v) end ffmpeg.filter.create(mkfilter) end video = logo_overlay(source.tracks(background).video, source.tracks(logo).video) s = source({audio=source.tracks(playlist).audio, video=video}) s = max_duration(3., s) done = ref(false) clock.assign_new(id='test_clock', sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = j video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) audio_stream = list.find( (fun (stream) -> null.defined(stream.sample_rate)), parsed.streams ) if null.get(video_stream.codec_name) == "h264" and null.get(video_stream.pix_fmt) == "yuv420p" and null.get(audio_stream.channel_layout) == "stereo" and null.get(audio_stream.codec_name) == "aac" and null.get(audio_stream.sample_fmt) == "fltp" and null.get(audio_stream.sample_rate) == "44100" then test.pass() else test.fail() end end end encoder = %ffmpeg(%audio(codec = "aac"), %video.raw(codec = "libx264")) output.file(encoder, on_close=on_close, fallible=true, out, s) liquidsoap-2.4.2/tests/media/ffmpeg_video_decoder.liq000066400000000000000000000017411513273233300227450ustar00rootroot00000000000000log.level.set(5) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) video.frame.rate.set(45) settings.decoder.decoders.set(["ffmpeg"]) fname = argv(default="", 1) out = {"#{fname}+ffmpeg_video_decoder-#{random.int()}.mp4"} s = single(fname) s = once(s) done = ref(false) ##clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : {streams: [{r_frame_rate: string, codec_name: string}]} ) = j let [stream] = parsed.streams if stream.r_frame_rate == "45/1" and stream.codec_name == "h264" then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %ffmpeg(format = "mp4", %video(codec = "libx264")), out, s ) liquidsoap-2.4.2/tests/media/gen_dune.ml000066400000000000000000000134501513273233300202350ustar00rootroot00000000000000let audio_decoding_tests = [ ("Mono decoding", "mono.liq"); ("Stereo decoding", "stereo.liq"); ("FFmpeg audio decoder", "ffmpeg_audio_decoder.liq"); ] let video_decoding_tests = [ ("FFmpeg video decoder", "ffmpeg_video_decoder.liq"); ("FFmpeg video size", "video_size.liq"); ] let audio_video_decoding_tests = [ ("FFmpeg add text filter", "ffmpeg_add_text.liq"); ("FFmpeg copy decoder", "ffmpeg_copy_decoder.liq"); ("FFmpeg copy+encode decode", "ffmpeg_copy_and_encode_decoder.liq"); ("FFmpeg filter", "ffmpeg_filter.liq"); ("FFmpeg filter parse", "ffmpeg_filter_parse.liq"); ("FFmpeg bitstream filter", "ffmpeg_bitstream_filter.liq"); ("FFmpeg raw decoder", "ffmpeg_raw_decoder.liq"); ("FFmpeg raw+encode decoder", "ffmpeg_raw_and_encode_decoder.liq"); ("FFmpeg raw+copy decoder", "ffmpeg_raw_and_copy_decoder.liq"); ] let standalone_tests = [ "multitrack.liq"; "ffmpeg_inline_encode_decode.liq"; "ffmpeg_inline_encode_decode_audio.liq"; "ffmpeg_inline_encode_decode_video.liq"; "ffmpeg_raw_hls.liq"; "pcm_s16_decode.liq"; "pcm_f32_decode.liq"; ] let audio_formats = [ {|%fdkaac(aot="mpeg4_aac_lc",channels=1).aac|}; "%fdkaac(channels=2).aac"; "%shine(channels=1).mp3"; "%shine(channels=2).mp3"; "%flac(stereo).flac"; "%flac(mono).flac"; "%wav(stereo).wav"; "%wav(mono).wav"; "%mp3(mono).mp3"; "%mp3(stereo).mp3"; "%ogg(%vorbis(mono)).ogg"; "%ogg(%vorbis(stereo)).ogg"; "%ogg(%flac(mono)).ogg"; "%ogg(%flac(stereo)).ogg"; "%ogg(%opus(mono)).ogg"; "%ogg(%opus(stereo)).ogg"; {|%ffmpeg(format="mp4",%audio(codec="aac",samplerate="48k")).mp4|}; {|%ffmpeg(format="mp4",%audio(codec="aac")).mp4|}; {|%ffmpeg(format="mp4",%audio(pcm_s16,codec="aac")).mp4|}; {|%ffmpeg(format="mp4",%audio(pcm_f32,codec="aac")).mp4|}; ] let video_formats = [{|%ffmpeg(format="mp4",%video(codec="libx264")).mp4|}] let audio_video_formats = [ {|%ffmpeg(format="mp4",%audio(codec="aac",channels=1),%video(codec="libx264")).mp4|}; {|%ffmpeg(format="mp4",%audio(codec="aac",channels=2),%video(codec="libx264")).mp4|}; {|%ffmpeg(format="mp4",%audio(codec="aac",channels=2),%video(codec="libx264",r=12)).mp4|}; ] let multitrack_formats = [ {|%ffmpeg(format="mp4",%audio(codec="aac",channels=2),%audio_2(codec="aac",channels=1),%video(codec="libx264"),%video_2(codec="libx264")).mp4|}; ] let formats = audio_formats @ audio_video_formats @ video_formats @ multitrack_formats let encoder_format format = match List.rev (String.split_on_char '.' format) with | _ :: l -> String.concat "." (List.rev l) | _ -> assert false let escaped_format = String.map (function | '%' -> '@' | '"' -> '\'' | '(' -> '[' | ')' -> ']' | c -> c) let encoder_script format = Printf.sprintf "%s_encoder.liq" (escaped_format (encoder_format format)) let mediatests = ref [] let mediatest (type a) : (a, unit, string, string) format4 -> a = Printf.ksprintf (fun s -> mediatests := Printf.sprintf "(alias %s)" s :: !mediatests; s) let mk_encoder source format = Printf.printf {| (rule (alias %s) (package liquidsoap) (target %s) (deps (:mk_encoder_test ./mk_encoder_test.sh) (:encoder_in ./encoder_%s.liq.in)) (action (with-stdout-to %%{target} (run %%{mk_encoder_test} %S %s %S)))) |} (mediatest "encoder_%s" source) (encoder_script format) source (encoder_format format) source (escaped_format format) let mk_encoded_file format = Printf.printf {| (rule (alias mediatest) (package liquidsoap) (target %s) (deps (:encoder %s) (package liquidsoap) ../../src/bin/liquidsoap.exe (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %%{run_test} %%{encoder} liquidsoap %%{test_liq} %%{encoder} -- %S))) |} (escaped_format format) (encoder_script format) (encoder_format format) let () = List.iter (mk_encoder "audio_only") audio_formats; List.iter (mk_encoder "video_only") video_formats; List.iter (mk_encoder "audio_video") audio_video_formats; List.iter (mk_encoder "multitrack") multitrack_formats; List.iter mk_encoded_file formats; Printf.printf {| (rule (alias mediatest) (package liquidsoap) (target all_media_files) (deps %s) (action (run touch %%{target}))) |} (String.concat "\n" (List.map escaped_format formats)) let file_test ~label ~test fname = Printf.printf {| (rule (alias %s) (package liquidsoap) (deps all_media_files %s ../../src/bin/liquidsoap.exe (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %%{run_test} %S liquidsoap %%{test_liq} %s -- %S))) |} (mediatest "%s" (Filename.remove_extension test)) test label test fname let () = List.iter (fun format -> let fname = escaped_format format in List.iter (fun (name, test) -> file_test ~label:(name ^ " test for " ^ fname) ~test fname) audio_decoding_tests) audio_formats let () = List.iter (fun format -> let fname = escaped_format format in List.iter (fun (name, test) -> file_test ~label:(name ^ " test for " ^ fname) ~test fname) video_decoding_tests) (video_formats @ audio_video_formats) let () = List.iter (fun format -> let fname = escaped_format format in List.iter (fun (name, test) -> file_test ~label:(name ^ " test for " ^ fname) ~test fname) audio_video_decoding_tests) audio_video_formats let () = List.iter (fun test -> file_test ~label:test ~test "") standalone_tests let () = Printf.printf {|(alias (name mediatest) (deps %s)) |} (String.concat "\n " (List.sort_uniq Stdlib.compare !mediatests)) liquidsoap-2.4.2/tests/media/image_decoder_duration.liq000066400000000000000000000004101513273233300232720ustar00rootroot00000000000000s = single("annotate:duration=0.2:test-image.png") track_count = ref(0) s.on_track( synchronous=true, fun (_) -> begin ref.incr(track_count) if track_count() > 2 then test.pass() end end ) s = (s : source(video=canvas)) output.dummy(s) liquidsoap-2.4.2/tests/media/lufs_integrated.liq000066400000000000000000000005141513273233300220020ustar00rootroot00000000000000def f() = lufs_integrated = ref(0.) def process(s) = s = lufs(s) s.on_frame(synchronous=true, {lufs_integrated := s.lufs_integrated()}) s end r = request.create("@wav[stereo].wav") request.process(process=process, r) test.almost_equal(digits=3, lufs_integrated(), -0.69) test.pass() end test.check(f) liquidsoap-2.4.2/tests/media/mk_encoder_test.sh000077500000000000000000000002111513273233300216120ustar00rootroot00000000000000#!/bin/sh FORMAT="$1" SOURCE="$2" FILE="$3" sed -e "s#@FORMAT@#${FORMAT}#g" "encoder_${SOURCE}.liq.in" | sed -e "s#@FILE@#${FILE}#g" liquidsoap-2.4.2/tests/media/mk_stream_test.sh000077500000000000000000000003721513273233300214760ustar00rootroot00000000000000#!/bin/sh BASEDIR=$(dirname "$0") CWD=$(cd "$BASEDIR" && pwd) FILE="$1" FORMAT=$(echo "$FILE" | cut -d '.' -f 1 | sed -e "s#@#%#g") MODE="$2" sed -e "s#@FORMAT@#${FORMAT}#g" "${CWD}/test_stream_${MODE}.liq.in" >| "${CWD}/test_stream_${MODE}.liq" liquidsoap-2.4.2/tests/media/mono.liq000066400000000000000000000015671513273233300176040ustar00rootroot00000000000000log.level.set(5) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) settings.decoder.priorities.ffmpeg.set(-1) fname = argv(default="", 1) out = {"#{fname}+mono-#{random.int()}.wav"} s = single(fname) s = once(s) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : {streams: [{channels: int, sample_rate: string}]} ) = j let [stream] = parsed.streams if stream.channels == 1 and stream.sample_rate == "48000" then test.pass() else test.fail() end end end output.file(fallible=true, on_close=on_close, %wav(mono), out, s) liquidsoap-2.4.2/tests/media/multitrack.liq000066400000000000000000000053141513273233300210050ustar00rootroot00000000000000s1 = sine(440.) audio = source.tracks(s1).audio out = {"test-multistream-#{random.int()}.mp4"} s2 = sine(880.) audio_2 = source.tracks(s2).audio s3 = single( "@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4" ) video = source.tracks(s3).video s4 = blank() metadata = source.tracks(s4).metadata s5 = blank() s5 = chop(s5) track_marks = source.tracks(s5).track_marks s = source( id="muxed", { audio=audio, audio_2=audio_2, video=video, metadata=metadata, track_marks=track_marks } ) meta_seen = ref(false) track_seen = ref(false) s.on_metadata( synchronous=true, fun (m) -> if m["foo"] == "bla" then meta_seen := true end ) s.on_track(synchronous=true, fun (_) -> track_seen := true) # Remove track marks and metadata let {metadata = _, track_marks = _, ...tracks} = source.tracks(s) file_no_meta_seen = ref(true) file_track_seen = ref(0) s = source(id="muxed_final", tracks) s.on_metadata(synchronous=true, fun (_) -> file_no_meta_seen := false) s.on_track(synchronous=true, fun (_) -> ref.incr(file_track_seen)) should_stop = ref(false) s = switch(track_sensitive=false, [({not should_stop()}, s)]) enc = %ffmpeg( format = "mp4", %audio(codec = "aac"), %audio_2(codec = "libmp3lame", channels = 1), %video.copy ) def on_close(filename) = test.equal(meta_seen(), true) test.equal(track_seen(), true) test.equal(file_no_meta_seen(), true) test.equal(file_track_seen(), 0) process.run("sync") ojson = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : { streams: [ { channel_layout: string?, sample_rate: string?, sample_fmt: string?, codec_name: string?, pix_fmt: string? } ] } ) = ojson test.equal(list.length(parsed.streams), 3) video_stream = list.find((fun (stream) -> null.defined(stream.pix_fmt)), parsed.streams) aac_stream = list.find((fun (stream) -> stream.codec_name == "aac"), parsed.streams) mp3_stream = list.find((fun (stream) -> stream.codec_name == "mp3"), parsed.streams) test.equal(video_stream.codec_name, "h264") test.equal(video_stream.pix_fmt, "yuv420p") test.equal(aac_stream.channel_layout, "stereo") test.equal(mp3_stream.channel_layout, "mono") test.pass() end output.file(fallible=true, on_close=on_close, enc, out, s) meta_count = ref(0) thread.run( every=2., fun () -> begin if meta_count() < 2 then s4.insert_metadata([("foo", "bla")]) ref.incr(meta_count) else should_stop := true end end ) liquidsoap-2.4.2/tests/media/pcm_f32_decode.liq000066400000000000000000000002551513273233300213610ustar00rootroot00000000000000s = once(single("@mp3[mono].mp3")) s = (s : source(audio=pcm_f32(mono))) source.methods(s).on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/media/pcm_s16_decode.liq000066400000000000000000000002551513273233300214000ustar00rootroot00000000000000s = once(single("@mp3[mono].mp3")) s = (s : source(audio=pcm_s16(mono))) source.methods(s).on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/media/stereo.liq000066400000000000000000000015731513273233300201320ustar00rootroot00000000000000log.level.set(5) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) settings.decoder.priorities.ffmpeg.set(-1) fname = argv(default="", 1) out = {"#{fname}+stereo-#{random.int()}.wav"} s = single(fname) s = once(s) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : {streams: [{channels: int, sample_rate: string}]} ) = j let [stream] = parsed.streams if stream.channels == 2 and stream.sample_rate == "48000" then test.pass() else test.fail() end end end output.file(fallible=true, on_close=on_close, %wav(stereo), out, s) liquidsoap-2.4.2/tests/media/stream_audio.liq.in000077500000000000000000000020241513273233300217050ustar00rootroot00000000000000log.level.set(4) settings.ffmpeg.log.level.set(12) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) out = {"stream_audio-#{random.int()}.wav"} s = noise(duration=2.) s = once(s) #clock.assign_new(sync='none',[s]) output.udp(id="output",port=5001,host="localhost",fallible=true,@FORMAT@,s) s = input.udp(id="input",port=5001,host="localhost",buffer=0.1,"application/ffmpeg") s = (s:source(1,0,0)) #clock.assign_new(sync='none',[s]) done = ref(false) def on_close (filename) = if not done() then done := true process.run("sync") j = process.read("ffprobe -v quiet -print_format json -show_streams #{process.quote(filename)}") let json.parse ( parsed: { streams: [{ channels: int, sample_rate: string }] }) = j let [stream] = parsed.streams if stream.channels == 1 and stream.sample_rate == "48000" then test.pass() else test.fail() end end end output.file(fallible=true, on_close=on_close, %wav(mono), out, s) liquidsoap-2.4.2/tests/media/stream_video.liq.in000077500000000000000000000021441513273233300217150ustar00rootroot00000000000000log.level.set(4) settings.ffmpeg.log.level.set(12) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) out = {"stream_video-#{random.int()}.mp4"} s = noise(duration=2.) s = once(s) #clock.assign_new(sync='none',[s]) output.udp(id="output",port=5001,host="localhost",fallible=true,@FORMAT@,s) s = input.udp(id="input",port=5001,host="localhost",buffer=0.1,"application/ffmpeg") s = (s:source(1,1,0)) #clock.assign_new(sync='none',[s]) done = ref(false) def on_close (filename) = if not done() then done := true process.run("sync") j = process.read("ffprobe -v quiet -print_format json -show_streams #{process.quote(filename)}") let json.parse ( parsed: { streams: [{ r_frame_rate: string, codec_name: string }] }) = j let [stream] = parsed.streams if stream.r_frame_rate == "45/1" and stream.codec_name == "h264" then test.pass() else test.fail() end end end output.file(fallible=true, on_close=on_close, %ffmpeg(format="mp4",%audio(codec="aac",channels=1),%video(codec="libx264")), out, s) liquidsoap-2.4.2/tests/media/video_size.liq000066400000000000000000000023111513273233300207600ustar00rootroot00000000000000log.level.set(5) audio.samplerate.set(48000) settings.audio.converter.samplerate.converters.set(["native"]) video.frame.rate.set(45) settings.decoder.decoders.set(["ffmpeg"]) fname = argv( default="@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@video[codec='libx264']].mp4", 1 ) out = {"#{fname}+test_videos_size-#{random.int()}.avi"} s = (single(fname) : source(video=canvas(width=47, height=52))) s = once(s) s = source({audio=source.tracks(blank()).audio, video=source.tracks(s).video}) done = ref(false) #clock.assign_new(sync='none', [s]) def on_close(filename) = if not done() then done := true process.run("sync") j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : {streams: ({width: int, height: int} * _)}) = j let stream = fst(parsed.streams) print( "Output video size is #{stream.width}x#{stream.height}." ) if stream.width == 47 and stream.height == 52 then test.pass() else test.fail() end end end output.file( fallible=true, on_close=on_close, %avi(width = 47, height = 52), out, s ) liquidsoap-2.4.2/tests/performance/000077500000000000000000000000001513273233300173365ustar00rootroot00000000000000liquidsoap-2.4.2/tests/performance/.gitignore000066400000000000000000000000171513273233300213240ustar00rootroot00000000000000big-record.liq liquidsoap-2.4.2/tests/performance/dune000066400000000000000000000015421513273233300202160ustar00rootroot00000000000000(executable (name gen_bigrecord) (preprocess (pps ppx_string)) (libraries mem_usage liquidsoap_core liquidsoap_lang) (modules gen_bigrecord)) (rule (alias perftest) (target big-record.liq) (deps (:gen_bigrecord ./gen_bigrecord.exe)) (action (run %{gen_bigrecord} %{target}))) (rule (alias perftest) (deps (:liquidsoap ../../src/bin/liquidsoap.exe) (source_tree ../../src/libs) (:small_record ./small-record.liq) (:big_record ./big-record.liq) (:erathostenes ./erathostenes.liq) (:performance performance.liq) (:memory memory.liq) (:test_liq ../test.liq)) (action (progn (run %{liquidsoap} %{test_liq} %{performance} %{small_record}) (run %{liquidsoap} %{test_liq} %{performance} %{big_record}) (run %{liquidsoap} %{test_liq} %{performance} %{erathostenes}) (run %{liquidsoap} %{test_liq} %{performance} %{memory})))) liquidsoap-2.4.2/tests/performance/erathostenes.liq000077500000000000000000000012521513273233300225540ustar00rootroot00000000000000#!../../liquidsoap ../test.liq performance.liq def erathostenes(n) = l = list.init(n - 2, fun (i) -> i + 2) l = ref(l) p = ref([]) while not list.is_empty(l()) do i = list.hd(default=0, l()) p := list.add(i, p()) l := list.filter(fun (j) -> j mod i != 0, l()) end list.rev(p()) end time( "Erathostenes (imperative)", {erathostenes(10000)} ) def erathostenes(n) = def rec aux(p, l) = list.case( l, p, fun (i, l) -> aux(list.add(i, p), list.filter(fun (j) -> j mod i != 0, l)) ) end l = list.init(n - 2, fun (i) -> i + 2) list.rev(aux([], l)) end time( "Erathostenes (recursive)", {erathostenes(10000)} ) exit(0) liquidsoap-2.4.2/tests/performance/gen_bigrecord.ml000066400000000000000000000033511513273233300224630ustar00rootroot00000000000000let parse_memory_consumption script = Gc.full_major (); Gc.full_major (); let mem_before = Mem_usage.info () in let _, tm = Liquidsoap_lang.Runtime.parse script in Gc.full_major (); Gc.full_major (); let mem_after = Mem_usage.info () in ignore tm; Printf.printf "Big record memory consumption: %s\n%!" Mem_usage.( prettify_bytes (mem_after.process_private_memory - mem_before.process_private_memory)) (* Remove type information. *) let rec strip tm = let { Liquidsoap_lang.Term.term; methods; _ } = tm in let methods = Liquidsoap_lang.Term.Methods.fold (fun _ v ret -> strip v @ ret) methods [] in term :: methods let term_memory_consumption script = Gc.full_major (); Gc.full_major (); let mem_before = Mem_usage.info () in let _, terms = Liquidsoap_lang.Runtime.parse script in let terms = strip terms in Gc.full_major (); Gc.full_major (); let mem_after = Mem_usage.info () in ignore terms; Printf.printf "Big record term-only memory consumption: %s\n%!" Mem_usage.( prettify_bytes (mem_after.process_private_memory - mem_before.process_private_memory)) let () = let indexes = List.init 5000 string_of_int in let methods = String.concat "\n" (List.map (fun idx -> [%string "let r.a%{idx} = %{idx}"]) indexes) in let sums = String.concat "\n" (List.map (fun idx -> [%string "n := n() + r.a%{idx}"]) indexes) in let script = Printf.sprintf "%s" [%string {| def sum () = r = () %{methods} n = ref(0) %{sums} end time("sum of fields (big record)", sum) exit(0) |}] in parse_memory_consumption script; term_memory_consumption script; let fd = open_out Sys.argv.(1) in output_string fd script; close_out fd liquidsoap-2.4.2/tests/performance/memory.liq000077500000000000000000000004411513273233300213570ustar00rootroot00000000000000#!/usr/bin/env -S ../../liquidsoap ../test.liq performance.liq m = runtime.memory().process_managed_memory print( "Managed memory: #{m} octets" ) test.metric( category="memory", name="Process managed memory", value=float(m) / (1024. * 1024.), unit="Mio", min=0. ) test.pass() liquidsoap-2.4.2/tests/performance/performance.liq000066400000000000000000000006271513273233300223530ustar00rootroot00000000000000profile = (environment.get("LIQ_PROFILE") != "") def time(name, f) = if profile then profiler.enable() end print( "Computing #{name}..." ) t = time() f() t = time() - t print( "Computing #{name} took #{string.float(decimal_places=2, t)} seconds." ) test.metric(category="performance", name=name, value=t, unit="s", min=0.) if profile then print(profiler.stats.string()) end end liquidsoap-2.4.2/tests/performance/small-record.liq000077500000000000000000000024441513273233300224400ustar00rootroot00000000000000#!/usr/bin/env -S ../../liquidsoap ../test.liq performance.liq def r2() = r = {a0=0, a1=1} x = ref(0) for _ = 1 to 200000 do x := x() + r.a0 + r.a1 end end def r5() = r = {a0=0, a1=1, a2=2, a3=3, a4=4} x = ref(0) for _ = 1 to 200000 do x := x() + r.a0 + r.a1 + r.a2 + r.a3 + r.a4 end end def r10() = r = {a0=0, a1=1, a2=2, a3=3, a4=4, a5=5, a6=6, a7=7, a8=8, a9=9} x = ref(0) for _ = 1 to 200000 do x := x() + r.a0 + r.a1 + r.a2 + r.a3 + r.a4 + r.a5 + r.a6 + r.a7 + r.a8 + r.a9 end end def r20() = r = { a00=0, a01=1, a02=2, a03=3, a04=4, a05=5, a06=6, a07=7, a08=8, a09=9, a10=0, a11=1, a12=2, a13=3, a14=4, a15=5, a16=6, a17=7, a18=8, a19=9 } x = ref(0) for _ = 1 to 200000 do x := x() + r.a00 + r.a01 + r.a02 + r.a03 + r.a04 + r.a05 + r.a06 + r.a07 + r.a08 + r.a09 + r.a10 + r.a11 + r.a12 + r.a13 + r.a14 + r.a15 + r.a16 + r.a17 + r.a18 + r.a19 end end time( "record with 2 fields", r2 ) time( "record with 5 fields", r5 ) time( "record with 10 fields", r10 ) time( "record with 20 fields", r20 ) exit(0) liquidsoap-2.4.2/tests/regression/000077500000000000000000000000001513273233300172155ustar00rootroot00000000000000liquidsoap-2.4.2/tests/regression/.gitignore000066400000000000000000000000141513273233300212000ustar00rootroot00000000000000*.mkv *.aac liquidsoap-2.4.2/tests/regression/115-1.liq000066400000000000000000000002721513273233300203710ustar00rootroot00000000000000s = add( normalize=true, [ sine(), sequence([sine(amplitude=0.3, duration=1.), once(sine(duration=1.))]) ] ) output.dummy(s) thread.run(delay=3., test.pass) liquidsoap-2.4.2/tests/regression/115-2.liq000066400000000000000000000003251513273233300203710ustar00rootroot00000000000000sa = sine() sb_on = ref(false) sb = switch([({!sb_on}, once(sine(220., duration=2.)))]) thread.run(delay=1., {sb_on := true}) output.dummy(smooth_add(p=0.3, normal=sa, special=sb)) thread.run(delay=4., test.pass) liquidsoap-2.4.2/tests/regression/3553.liq000066400000000000000000000030301513273233300203170ustar00rootroot00000000000000playlist_content = ' Playlist file:///C:/Users/user/Music/testing/test-file-1.ogg Very good song Other 61445 file:///C:/Users/user/Music/testing/test-file-2.ogg 1240 ' expected_content = [ ( [ ("location", "file:///C:/Users/user/Music/testing/test-file-1.ogg"), ( "title", "Very good song" ), ("annotation", "Other"), ("duration", "61445") ], "file:///C:/Users/user/Music/testing/test-file-1.ogg" ), ( [ ("location", "file:///C:/Users/user/Music/testing/test-file-2.ogg"), ("duration", "1240") ], "file:///C:/Users/user/Music/testing/test-file-2.ogg" ) ] playlist_file = file.temp("playlist", ".xspf") file.write(data=playlist_content, playlist_file) on_cleanup({file.remove(playlist_file)}) playlist_content = playlist.parse(playlist_file) def f() = test.equal(playlist_content, expected_content) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/AC5109.liq000066400000000000000000000013421513273233300205260ustar00rootroot00000000000000# See: https://github.com/AzuraCast/AzuraCast/issues/5109. # Crossfade transition sources with breaks where triggering # end of transition too early. s1 = sine(duration=20.) s2 = sine(duration=20.) s = sequence([s1, s2]) def transition(a, b) = s1 = sine(duration=0.5) s1 = metadata.map(insert_missing=true, fun (_) -> [("type", "s1")], s1) s2 = sine(duration=0.5) s2 = metadata.map(insert_missing=true, fun (_) -> [("type", "s2")], s2) sequence( [(a.source : source), (s1 : source), (s2 : source), (b.source : source)] ) end s = cross(transition, s) def check(m) = if m["type"] == "s2" then test.pass() end end s.on_metadata(synchronous=true, check) clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/regression/BUG403.liq000066400000000000000000000013451513273233300205730ustar00rootroot00000000000000# This is the test for bug #403 from our old trac. # # Make a switch() declare itself ready and arrange to use it for the next # frame where it isn't ready anymore. # # Two switches A and B # A is only ready for a short period of time due to its predicates. # B reselects at the end of a frame just before A becomes unavailable # as a result, B has selected = A, attempts to stream it # but A finds itself not ready anymore. # In other words, B committed but A did not. r = ref(false) def pred() = v = !r r := false v end thread.run(delay=2., {r := true}) mixer = fallback( id="mixer", track_sensitive=false, [source.available(sine(duration=3.), pred), blank()] ) output.dummy(mixer) thread.run(delay=3., test.pass) liquidsoap-2.4.2/tests/regression/GH-action-919422659.liq000066400000000000000000000012251513273233300225110ustar00rootroot00000000000000# In https://github.com/savonet/liquidsoap/runs/919422659, # GH1159.liq started failing. Further investigation revealed # that GH1159.liq was originally intended to use with the FLAC # decoder so it was set back to only use it. This test is a # copy of it with decoder forced to FFMPEG, the one originally # failing in the github action run & serves as regresstion test # for this case. settings.decoder.decoders.set(["ffmpeg"]) s = single("../media/@flac[stereo].flac") def f(rem, _) = if rem > 0. then test.pass() else test.fail() end end s.on_position(synchronous=true, remaining=true, position=0.1, f) clock.assign_new(sync="none", [s]) output.dummy(s) liquidsoap-2.4.2/tests/regression/GH1129.liq000066400000000000000000000022411513273233300205360ustar00rootroot00000000000000# We want: # Track selection 1 -> s1 ready, s2 not ready, s3 ready -> switch to s3, s2 gets ready # Track selection 2 -> s1 ready, s2 ready, s3 ready -> switch to s1 # Bug appears when it switches to s3 again on step 2 selected = ref([]) s1 = blank(duration=0.04) def m1(_) = [("id", "s1")] end s1 = metadata.map(insert_missing=true, id="s1", m1, s1) s2_ready = ref(false) s2 = switch(id="s2", [({!s2_ready}, blank(duration=0.04))]) def m2(_) = [("id", "s2")] end s2 = metadata.map(insert_missing=true, id="s2", m2, s2) s3 = blank(id="s3", duration=0.04) def m3(_) = [("id", "s3")] end s3 = metadata.map(insert_missing=true, id="s3", m3, s3) def f(m) = s2_ready := !s2_ready or m["id"] == "s3" selected := list.cons(m["id"], !selected) end s = rotate([s1, s2, s3]) s.on_track(synchronous=true, f) output.dummy(s) def on_done() = s = list.rev(!selected) if list.nth(default="", s, 0) == "s1" and list.nth(default="", s, 1) == "s3" and list.nth(default="", s, 2) == "s1" and list.nth(default="", s, 3) == "s2" and list.nth(default="", s, 4) == "s3" then test.pass() else test.fail() end end thread.run(delay=1., on_done) liquidsoap-2.4.2/tests/regression/GH1146.liq000066400000000000000000000006261513273233300205420ustar00rootroot00000000000000# In #1146, we encountered a case where native flac # decoding was stuck at the end of a track. This test # makes sure we don't have such regression in the # future. track_count = ref(0) s = single("../media/@flac[stereo].flac") def f(_) = track_count := !track_count + 1 if !track_count > 2 then test.pass() end end s.on_track(synchronous=true, f) clock.assign_new(sync="none", [s]) output.dummy(s) liquidsoap-2.4.2/tests/regression/GH1151.liq000066400000000000000000000003211513273233300205260ustar00rootroot00000000000000test.skip() def f() = if process.read.lines( 'echo "aa\rbb\ncc\r\ndd\r\r\nee"' ) != ["aa\rbb", "cc", "dd\r", "ee"] then test.fail() else test.pass() end end test.check(f) liquidsoap-2.4.2/tests/regression/GH1159.liq000066400000000000000000000006061513273233300205440ustar00rootroot00000000000000# In #1159, we realized that the decoder # was retuning remaining = 0 too early, # triggering an early `on_end` call. settings.decoder.decoders.set(["flac"]) s = single("../media/@flac[stereo].flac") def f(rem, _) = if rem > 0. then test.pass() else test.fail() end end s.on_position(synchronous=true, remaining=true, position=0.1, f) clock.assign_new(sync="none", [s]) output.dummy(s) liquidsoap-2.4.2/tests/regression/GH1279.liq000066400000000000000000000013601513273233300205450ustar00rootroot00000000000000selected = ref([]) s1 = blank(duration=0.04) def m1(_) = [("id", "s1")] end s1 = metadata.map(insert_missing=true, id="s1", m1, s1) s2 = blank(duration=0.04) def m2(_) = [("id", "s2")] end s2 = metadata.map(insert_missing=true, id="s2", m2, s2) def f(m) = selected := list.add(m["id"], !selected) end s = rotate(weights=[2, 1], [s1, s2]) s.on_track(synchronous=true, f) output.dummy(s) def on_done() = s = list.rev(!selected) if list.nth(default="", s, 0) == "s1" and list.nth(default="", s, 1) == "s1" and list.nth(default="", s, 2) == "s2" and list.nth(default="", s, 3) == "s1" and list.nth(default="", s, 4) == "s1" then test.pass() else test.fail() end end thread.run(delay=1., on_done) liquidsoap-2.4.2/tests/regression/GH1327.liq000066400000000000000000000021211513273233300205330ustar00rootroot00000000000000log.level.set(5) selected = ref([]) s1 = blank(duration=0.1) def m1(_) = [("id", "s1")] end s1 = metadata.map(insert_missing=true, id="s1", m1, s1) s2 = blank(duration=0.1) def m2(_) = [("id", "s2")] end s2 = metadata.map(insert_missing=true, id="s2", m2, s2) def f(m) = selected := list.add(m["id"], !selected) end s = rotate(weights=[2, 1], [s1, s2]) s.on_track(synchronous=true, f) def m_noise(_) = [("id", "noise")] end noise = metadata.map(insert_missing=true, id="noise", m_noise, noise()) ready = ref(false) s = switch(track_sensitive=false, [({!ready}, s), ({true}, noise)]) def f(_) = ready := true end s.on_track(synchronous=true, f) output.dummy(s) def on_done() = print(!selected) s = list.rev(!selected) if list.nth(default="", s, 0) == "s1" and list.nth(default="", s, 1) == "s1" and list.nth(default="", s, 2) == "s2" and list.nth(default="", s, 3) == "s1" and list.nth(default="", s, 4) == "s1" and list.nth(default="", s, 5) == "s2" then test.pass() else test.fail() end end thread.run(delay=1., on_done) liquidsoap-2.4.2/tests/regression/GH2585.liq000066400000000000000000000006271513273233300205530ustar00rootroot00000000000000settings.log.level.set(4) output.dummy(fallible=true, sine()) count = ref(0) thread.run.recurrent( { if count() == 5 then begin print( "sending kill -s INT #{process.pid()}" ) process.run( "kill -s INT #{process.pid()}" ) (-1.) end else begin count := count() + 1 1. end end } ) liquidsoap-2.4.2/tests/regression/GH2602.liq000066400000000000000000000006431513273233300205370ustar00rootroot00000000000000def f() = s = input.external.rawaudio( channels=1, samplerate=44100, restart=true, "ffmpeg -re -f lavfi -i sine=frequency=1000:duration=60 -f s16le -acodec \ pcm_s16le -ar 44100 -" ) s = ffmpeg.encode.audio(%ffmpeg(%audio(codec = "libmp3lame", channels = 1)), s) s.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2756-2.liq000066400000000000000000000024401513273233300207050ustar00rootroot00000000000000def f() = tmp = file.temp("foo", "bla") on_cleanup({file.remove(tmp)}) s = sine() insert_metadata = s.insert_metadata err = ref(null) def on_frame() = null.case( err(), {()}, fun (e) -> begin err := null error.raise(e) end ) end s.on_frame(synchronous=true, on_frame) callstack = ref([]) def reopen_on_metadata(_) = print( "Got metadata!" ) callstack := [...(callstack()), "metadata"] false end def reopen_on_error(_) = print( "Got error!" ) callstack := [...(callstack()), "error"] null end clock.assign_new( on_error=fun (_) -> begin callstack := [ ...(callstack()), "streaming error" ] end, [s] ) output.file( reopen_on_metadata=reopen_on_metadata, reopen_on_error=reopen_on_error, %wav, tmp, s ) thread.run(delay=1., {insert_metadata([("foo", "bla")])}) thread.run(delay=5., {err := error.failure}) thread.run( delay=6., { if callstack() == [ "metadata", "error", "streaming error" ] then test.pass() else test.fail() end } ) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2756.liq000066400000000000000000000027231513273233300205520ustar00rootroot00000000000000def f() = tmp = file.temp("foo", "bla") on_cleanup({file.remove(tmp)}) s = sine() insert_metadata = s.insert_metadata err = ref(null) def on_frame() = null.case( err(), {()}, fun (e) -> begin err := null error.raise(e) end ) end s.on_frame(synchronous=true, on_frame) reopen_when_ref = ref(true) reopen_delay = ref(120.) callstack = ref([]) def reopen_on_metadata(_) = print( "Got metadata!" ) callstack := [...(callstack()), "metadata"] true end def reopen_on_error(_) = print( "Got error!" ) callstack := [...(callstack()), "error"] 2. end def reopen_when() = r = reopen_when_ref() if r then print( "Got reopen!" ) callstack := [...(callstack()), "reload"] reopen_when_ref := false end r end output.file( reopen_on_metadata=reopen_on_metadata, reopen_on_error=reopen_on_error, reopen_when=reopen_when, reopen_delay=reopen_delay, %wav, tmp, s ) thread.run(delay=1., {insert_metadata([("foo", "bla")])}) thread.run(delay=5., {err := error.failure}) thread.run( delay=8., { reopen_delay := 1. reopen_when_ref := true } ) thread.run( delay=10., { if callstack() == ["metadata", "error", "reload"] then test.pass() else test.fail() end } ) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2758.liq000066400000000000000000000004741513273233300205550ustar00rootroot00000000000000def f() = s = http.get('http://ifconfig.me/all.json', http_version="1.1") test.equal(s.status_message, "OK") test.equal(s.status_code, 200) test.equal(s.http_version, "1.1") let json.parse ({user_agent} : {user_agent: string}) = s test.equal(user_agent, http.user_agent) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH2842.liq000066400000000000000000000007731513273233300205510ustar00rootroot00000000000000log.level := 5 def f() = was_called = ref(false) filename = file.temp("bla", "blo") on_cleanup({file.remove(filename)}) def filename() = if was_called() then test.fail() end was_called := true "bla" end o = output.file( fallible=true, %ffmpeg( format = "mp3", %audio(codec = "libmp3lame", samplerate = 48000, b = "320k") ), filename, once(sine(duration=1., 480.)) ) o.on_stop(synchronous=true, test.pass) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2850.liq000066400000000000000000000020511513273233300205370ustar00rootroot00000000000000test.skip() def f() = has_source = ref(false) last_connect = ref(false) error_seen = ref(false) shutdown = ref(fun () -> ()) def on_start() = if last_connect() then test.pass() else print( "Shutting down initial source" ) fn = shutdown() fn() end end def on_error(_) = if not error_seen() then error_seen := true elsif not has_source() then s = input.harbor(port=8005, password="hackme", "test") o = output.dummy(fallible=true, s) shutdown := o.shutdown has_source := true else last_connect := true error_seen := false has_source := false end end s = sine() o = output.url( restart_delay=0.5, url="icecast://source:hackme@localhost:8005/test", %ffmpeg( format = "ogg", content_type = "application/ogg", %audio(codec = "flac") ), s ) o.on_start(synchronous=true, on_start) o.on_error(synchronous=true, on_error) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2867.liq000066400000000000000000000001521513273233300205470ustar00rootroot00000000000000def f() = output.file(%mp3(stereo = false), "/tmp/bla", mean(blank())) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH2871.liq000066400000000000000000000004171513273233300205460ustar00rootroot00000000000000def f() = s = metronome() s = bpm(s) output.dummy(fallible=true, s) thread.run.recurrent( { if s.bpm() != 0. then begin test.pass() (-1.) end else 1. end } ) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2872.liq000066400000000000000000000006241513273233300205470ustar00rootroot00000000000000def f() = track_count = ref(0) s = input.external.rawaudio( "ffmpeg -re -f lavfi -i sine=frequency=1000 -t 1 -ac 2 -f s16le -", restart=true, buffer=1. ) s.on_track( synchronous=true, fun (_) -> begin track_count := !track_count + 1 if !track_count > 3 then test.pass() end end ) output.dummy(fallible=true, s) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2897.liq000066400000000000000000000005611513273233300205560ustar00rootroot00000000000000def f() = def a() = def next() = error.raise(error.not_found, "test") end request.dynamic(next) end output.dummy(fallible=true, a()) def a() = def next() = error.raise(error.not_found, "test") end native.request.dynamic(next) end output.dummy(fallible=true, a()) thread.run(delay=1., test.pass) end test.check(f) liquidsoap-2.4.2/tests/regression/GH2902.liq000066400000000000000000000021301513273233300205330ustar00rootroot00000000000000def f() = let {uri, timeout, cmd, extname} = protocol.process.parse( default_timeout=200., "timeout=123,.foo,bla,gni,gno:uri:uro:ura" ) test.equal(uri, "uri:uro:ura") test.equal(timeout, 123.) test.equal(cmd, "bla,gni,gno") test.equal(extname, ".foo") let {uri, timeout, cmd, extname} = protocol.process.parse( default_timeout=200., "timeout=123.123,.foo,bla,gni,gno:uri:uro:ura" ) test.equal(uri, "uri:uro:ura") test.equal(timeout, 123.123) test.equal(cmd, "bla,gni,gno") test.equal(extname, ".foo") let {uri, timeout, cmd, extname} = protocol.process.parse( default_timeout=100., "timeout=123,.foo,bla,gni,gno:uri:uro:ura" ) test.equal(uri, "uri:uro:ura") test.equal(timeout, 100.) test.equal(cmd, "bla,gni,gno") test.equal(extname, ".foo") let {uri, timeout, cmd, extname} = protocol.process.parse(default_timeout=200., ".foo,bla,gni,gno:uri:uro:ura") test.equal(uri, "uri:uro:ura") test.equal(timeout, 200.) test.equal(cmd, "bla,gni,gno") test.equal(extname, ".foo") test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH2926.liq000066400000000000000000000006001513273233300205410ustar00rootroot00000000000000tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) def f() = enc = %ffmpeg(format = "mpegts", %audio(codec = "aac")) streams = [("enc", enc)] o = output.file.hls( fallible=true, persist_at="./config", path.concat(tmp_dir, "hls"), streams, once(sine(duration=1.)) ) o.on_stop(synchronous=true, test.pass) end test.check(f) liquidsoap-2.4.2/tests/regression/GH3093.liq000066400000000000000000000001621513273233300205400ustar00rootroot00000000000000def f() = x = false ignore(x) # x = true if x then test.pass() else test.fail() end end test.check(f) liquidsoap-2.4.2/tests/regression/GH3132.liq000066400000000000000000000005671513273233300205430ustar00rootroot00000000000000last_source = sine() last_source = metadata.map(insert_missing=true, fun (_) -> [("last", "true")], last_source) s = sequence([sine(duration=5.), blank(duration=5.), last_source]) s = blank.eat(max_blank=1., s) s.on_metadata( synchronous=true, fun (m) -> if m["last"] == "true" then test.pass() end ) clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/regression/GH3134.liq000066400000000000000000000010651513273233300205370ustar00rootroot00000000000000def f() = retry_delay_called_count = ref(0) def retry_delay() = ref.incr(retry_delay_called_count) 0.1 end last_call = ref(null) def next() = t = time() called_at = last_call() last_call := t if null.defined(called_at) then if t - null.get(called_at) < 0.1 then test.fail() end if 2 < retry_delay_called_count() then test.pass() end request.create("invalid") else null end end s = request.dynamic(retry_delay=retry_delay, next) output.dummy(fallible=true, s) end test.check(f) liquidsoap-2.4.2/tests/regression/GH3224.liq000066400000000000000000000001561513273233300205370ustar00rootroot00000000000000def f() = test.equal(r/\\/.replace(fun (_) -> "?", "foo\\bar"), "foo?bar") test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH3239-2.liq000066400000000000000000000010271513273233300207020ustar00rootroot00000000000000tmp = file.temp("foo", "mp3") on_cleanup({file.remove(tmp)}) a = sine(duration=2.) b = sine(duration=2.) # This function will be type-checked as a generic # fun (source('a), source('a)) -> source('a). We # need to make sure that it returns a mono source # when used as transition for a mono crossfade. def fn(a, b) = add([fade.out(a.source), fade.in(b.source)]) end s = sequence([a, b]) s = cross(fn, s) clock.assign_new(sync='none', [s]) o = output.file(fallible=true, %mp3(mono), tmp, s) o.on_stop(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/GH3239-bis.liq000066400000000000000000000005571513273233300213250ustar00rootroot00000000000000# Simpler version of GH3239 to work on a specific issue with # source,dynamic tmp = file.temp("foo", "mp3") on_cleanup({file.remove(tmp)}) def f() = s = once(sine(duration=2.)) let {audio = a} = source.tracks(s) track.audio.amplify(1., a) end s = source({audio=f()}) o = output.file(fallible=true, %mp3(mono), tmp, s) o.on_stop(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/GH3239.liq000066400000000000000000000010451513273233300205430ustar00rootroot00000000000000tmp = file.temp("foo", "mp3") on_cleanup({file.remove(tmp)}) a = sine(duration=2.) b = sine(duration=2.) # This function will be type-checked as a generic # fun (source('a), source('a)) -> source('a). We # need to make sure that it returns a mono source # when used as transition for a mono fallback. def transition(a, b) = add([fade.out(a), fade.in(b)]) end s = fallback(transitions=[transition, transition], [a, b]) clock.assign_new(sync='none', [s]) o = output.file(fallible=true, %mp3(mono), tmp, s) o.on_stop(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/GH3276.liq000066400000000000000000000007351513273233300205510ustar00rootroot00000000000000log.level := 5 port = 8125 thread.run( delay=7., fun () -> begin s = input.http("http://localhost:#{port}/test") track_count = ref(0) s.on_track( synchronous=true, fun (_) -> begin if track_count() > 1 then test.pass() end track_count := track_count() + 1 end ) output.dummy(fallible=true, s) end ) output.harbor(%vorbis, chop(every=5., sine()), port=port, mount="/test") liquidsoap-2.4.2/tests/regression/GH3303.liq000066400000000000000000000003401513273233300205300ustar00rootroot00000000000000def f() = def a(_, res) = res.json({a=null}) end def b(_, res) = res.json({b=null}) end harbor.http.register(port=3303, "/a", a) harbor.http.register(port=3303, "/b", b) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH3316.liq000077500000000000000000000015151513273233300205440ustar00rootroot00000000000000#!../../liquidsoap ../test.liq log.level := 4 s = sine() count = ref(0) def on_close(filename) = count := count() + 1 if count() <= 3 then j = process.read( "ffprobe -v quiet -print_format json -show_streams #{ process.quote(filename) }" ) let json.parse (parsed : {streams: [{codec_name: string}]}) = j if (list.hd(parsed.streams).codec_name != "flac") then test.fail() end if count() == 3 then test.pass() end end end dir = file.temp_dir("flac") on_cleanup({file.rmdir(dir)}) last_time = ref(time()) def reopen_when() = current_time = time() time_diff = current_time - last_time() last_time := current_time 2. < time_diff end output.file( %flac, reopen_delay=2., reopen_when=reopen_when, on_close=on_close, {time.string("#{dir}/%Hh%M_%S.flac")}, s ) liquidsoap-2.4.2/tests/regression/GH3645.liq000066400000000000000000000005411513273233300205440ustar00rootroot00000000000000radio = single("../media/@wav[stereo].wav") append = request.queue() radio = fallback(track_sensitive=true, [append, radio]) thread.run( delay=3., {append.push.uri("annotate:foo=bla:../media/@wav[stereo].wav")} ) radio.on_metadata( synchronous=true, fun (m) -> if m["foo"] == "bla" then test.pass() end ) output.dummy(fallible=true, radio) liquidsoap-2.4.2/tests/regression/GH3675.liq000066400000000000000000000010651513273233300205510ustar00rootroot00000000000000s = sine() reopen_count = ref(0) filename = ref("") def reopen_when() = ref.incr(reopen_count) count = reopen_count() if count < 2 then true else fd = file.open(!filename) s = fd.read() fd.close() if string.sub(encoding="ascii", s, start=0, length=4) != "RIFF" then test.fail() else test.pass() end false end end def filename() = f = file.temp("tmp", ".wav") filename := f on_cleanup({file.remove(f)}) f end output.file(%wav, filename, reopen_delay=1., reopen_when=reopen_when, s) liquidsoap-2.4.2/tests/regression/GH3813.liq000066400000000000000000000003411513273233300205370ustar00rootroot00000000000000def f() = r = request.create("annotate:initial_uri=bogus:#{argv(0)}") if not request.resolve(r) then test.fail() end m = request.metadata(r) test.not.equal(m["initial_uri"], "bogus") test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH3840.liq000066400000000000000000000015551513273233300205470ustar00rootroot00000000000000out_file = file.temp(cleanup=true, "output", ".mp4") audio = once(single("../media/@shine[channels=2].mp3")) video = single( "../media/@ffmpeg[format='mp4',@audio[codec='aac',channels=1],@video[codec='libx264']].mp4" ) stream = source(source.tracks(audio).{video=source.tracks(video).video}) clock.assign_new(sync='none', [stream]) enc = %ffmpeg(%audio.copy, %video.copy) def on_stop() = j = process.read( "ffprobe -v quiet -print_format json -show_streams #{out_file}" ) let json.parse ({streams} : {streams: [{codec_name: string}]}) = j if not list.exists(fun (s) -> s.codec_name == "mp3", streams) then test.fail() end if not list.exists(fun (s) -> s.codec_name == "h264", streams) then test.fail() end test.pass() end o = output.file(fallible=true, enc, out_file, stream) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/regression/GH3881.liq000066400000000000000000000005331513273233300205470ustar00rootroot00000000000000def f() = skip_occured = ref(false) def next() = if skip_occured() then test.pass() end request.create("../media/@wav[stereo].wav") end s = request.dynamic(next) output.dummy(fallible=true, s) thread.run( delay=1., fun () -> begin skip_occured := true s.skip() end ) end test.check(f) liquidsoap-2.4.2/tests/regression/GH3960.liq000066400000000000000000000010471513273233300205460ustar00rootroot00000000000000call_counts = ref([]) def test_decoder(~metadata:_, filename) = call_count = list.assoc(default=ref(0), filename, call_counts()) ref.incr(call_count) if 1 < call_count() then print( "Call count for #{filename}: #{call_count()}" ) test.fail() end call_counts := [...list.assoc.remove.all(filename, call_counts()), (filename, call_count)] [] end decoder.metadata.add("test_decoder", test_decoder) radio = playlist("../media") output.dummy(radio, fallible=true) thread.run(delay=4., fun () -> test.pass()) liquidsoap-2.4.2/tests/regression/GH4071.liq000066400000000000000000000007131513273233300205370ustar00rootroot00000000000000def f() = s = request.queue() s.push(request.create("https://download.samplelib.com/mp3/sample-15s.mp3")) s.push(request.create("https://download.samplelib.com/mp3/sample-15s.mp3")) s.push(request.create("https://download.samplelib.com/mp3/sample-15s.mp3")) s.push(request.create("https://download.samplelib.com/mp3/sample-15s.mp3")) test.equal(list.length(s.queue()), 4) test.equal(list.length(s.queue()), 4) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH4090.liq000066400000000000000000000011471513273233300205420ustar00rootroot00000000000000let {audio = a} = source.tracks(sine()) track_l = track.audio.amplify(2., track.audio.stereo.pan(-1., a)) track_r = track.audio.amplify(0.5, track.audio.stereo.pan(1., a)) s = source({audio=track.audio.add(normalize=false, [track_l, track_r])}) r = rms.stereo(s, duration=1.) rl = rms(stereo.left(s), duration=1.) rr = rms(stereo.right(s), duration=1.) output.dummy(r) output.dummy(rl) output.dummy(rr) def check_peak() = let (r_l, r_r) = r.rms() rl = rl.rms() rr = rr.rms() test.almost_equal(r_l, rl) test.almost_equal(r_r, rr) test.pass() end thread.run(check_peak, delay=1.) output.dummy(s) liquidsoap-2.4.2/tests/regression/GH4116.liq000066400000000000000000000002671513273233300205430ustar00rootroot00000000000000s = single("./theora-test.mp4") enc = %theora tmp = file.temp("foo", "ogg") on_cleanup({file.remove(tmp)}) output.file(fallible=true, enc, tmp, s) thread.run(delay=2., test.pass) liquidsoap-2.4.2/tests/regression/GH4124.liq000066400000000000000000000002031513273233300205300ustar00rootroot00000000000000s = sine() s = filter.iir.butterworth.high(s, frequency=250., order=2) o = output.dummy(s) o.on_start(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/GH4140.liq000066400000000000000000000001421513273233300205300ustar00rootroot00000000000000daytime = time.predicate("9h-21h") def f() = print(daytime()) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH4144.liq000066400000000000000000000015021513273233300205350ustar00rootroot00000000000000def metadata_test(_) = data = {test={test="好吗"}} try http.response( status_code=200, headers=[ ("Content-Type", "application/json"), ("Access-Control-Allow-Origin", "*") ], content_type="application/json; charset=UTF-8", data=json.stringify(data, compact=true) ) catch error do log.severe(error.message, label="http") http.response( status_code=500, content_type="application/json; charset=UTF-8", data='{"status"="error","message"="Failed to serialize response data"}' ^ "\n" ) end end port = 4144 harbor.http.register.simple("/test", metadata_test, port=port, method="GET") def check() = resp = http.get("http://localhost:#{port}/test") test.equal(resp, '{"test":{"test":"好吗"}}') test.pass() end test.check(check) liquidsoap-2.4.2/tests/regression/GH4159.liq000066400000000000000000000002421513273233300205430ustar00rootroot00000000000000s = debug.is_ready(sine(duration=0.2)) s = source.dynamic(track_sensitive=false, {s}) o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/GH4163.liq000066400000000000000000000022211513273233300205350ustar00rootroot00000000000000first_blank = metadata.map( insert_missing=true, id="first_blank", fun (_) -> [("title", "first_blank")], blank(duration=3.) ) after_blank = metadata.map( insert_missing=true, id="after_blank", fun (_) -> [("title", "after_blank")], sine(duration=10.) ) blank_sequence = sequence(id="blank_sequence", [first_blank, after_blank]) blank_strip = blank.strip(id="blank_strip", max_blank=5., start_blank=true, blank_sequence) fallback_sine = metadata.map( insert_missing=true, id="fallback_sine", fun (_) -> [("title", "fallback")], sine() ) s = fallback( id="main_source", track_sensitive=false, [blank_strip, fallback_sine] ) # We want to make sure that blank.strip keeps eating the blank source before switching back: expected_titles = ["fallback", "after_blank"] seen_titles = ref([]) def on_metadata(m) = seen_titles := [...seen_titles(), m["title"]] if seen_titles() == expected_titles then test.pass() elsif list.length(seen_titles()) == 2 then test.fail() end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync='none', [s]) output.dummy(s) liquidsoap-2.4.2/tests/regression/GH4246.liq000066400000000000000000000003001513273233300205330ustar00rootroot00000000000000enable_replaygain_metadata() enable_autocue_metadata() audio = once(single("../media/@shine[channels=2].mp3")) o = output.dummy(fallible=true, audio) o.on_start(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/GH4281-2.liq000066400000000000000000000012071513273233300207000ustar00rootroot00000000000000d = sequence([sine(duration=3.), sine(duration=3.)]) d = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "delay")]), d ) f = sequence([sine(duration=3.), sine(duration=3.)]) f = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "fallback")]), f ) s = fallback([delay(initial=true, 1., d), f]) meta = ref([]) s.on_metadata( synchronous=true, fun (m) -> begin meta := [...meta(), m["title"]] if meta() == ["fallback", "delay", "fallback", "delay"] then test.pass() end end ) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/regression/GH4281.liq000066400000000000000000000011711513273233300205410ustar00rootroot00000000000000d = sequence([sine(duration=3.), sine(duration=3.)]) d = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "delay")]), d ) f = sequence([sine(duration=3.), sine(duration=3.)]) f = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "fallback")]), f ) s = fallback([delay(1., d), f]) meta = ref([]) s.on_metadata( synchronous=true, fun (m) -> begin meta := [...meta(), m["title"]] if meta() == ["delay", "fallback", "delay", "fallback"] then test.pass() end end ) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/regression/GH4395.liq000066400000000000000000000010231513273233300205430ustar00rootroot00000000000000port = 9502 settings.srt.prefer_address := "ipv4" test.skip() track_count = ref(0) def on_track(_) = ref.incr(track_count) if track_count() > 1 then test.pass() end end s = input.srt(mode="caller", content_type="application/ogg", port=port) s.on_track(synchronous=true, on_track) output.dummy(fallible=true, s) output.srt(id="output.srt", mode="listener", port=port, %opus, sine()) thread.run(delay=3., s.disconnect) thread.run(delay=4., s.connect) thread.run(delay=6., s.disconnect) thread.run(delay=7., s.connect) liquidsoap-2.4.2/tests/regression/GH4499.liq000066400000000000000000000005451513273233300205600ustar00rootroot00000000000000s = sine() a = ref([]) def test1() = o = output.dummy(fallible=true, s) a := [("test", o)] end def test2() = if list.assoc.mem("test", a()) then o = list.assoc("test", a()) o.shutdown() a := list.assoc.remove("test", a()) test.equal(list.length(a()), 0) test.pass() end end thread.run(test1) thread.run(delay=1., test2) liquidsoap-2.4.2/tests/regression/GH4629.liq000066400000000000000000000003611513273233300205470ustar00rootroot00000000000000port = 4629 s = input.harbor(port=port, "bla") s.on_disconnect(synchronous=true, test.pass) output.dummy(fallible=true, s) o = output.icecast(port=port, mount="bla", %mp3, sine()) s.on_connect(synchronous=false, fun (_) -> o.shutdown()) liquidsoap-2.4.2/tests/regression/GH4711.liq000066400000000000000000000003721513273233300205410ustar00rootroot00000000000000def f() = def a(_, res) = try res.json( json.value( "A string" ) ) catch _ do res.json(json.value(1234)) end end harbor.http.register(port=3303, "/a", a) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/GH4745.liq000066400000000000000000000003411513273233300205440ustar00rootroot00000000000000# Make sure that fetch method always call fetch function # even when `prefetch=0`. def f() = s = request.dynamic( prefetch=0, { test.pass() null } ) s.fetch() end test.check(f) liquidsoap-2.4.2/tests/regression/GH4748.liq000066400000000000000000000003441513273233300205520ustar00rootroot00000000000000def f() = r = request.queue() o = output.dummy(fallible=true, r) o.on_position(synchronous=true, position=1., fun (_, _) -> test.pass()) r.skip() r.push(request.create("../media/@wav[stereo].wav")) end test.check(f) liquidsoap-2.4.2/tests/regression/GH4819-bis.liq000066400000000000000000000020111513273233300213150ustar00rootroot00000000000000log.level := 4 log.file.path := "/tmp/bla.log" log.stdout := true s = sine() recordings = ref([]) def shutdown_recordings() = log( "Stopping recordings" ) list.iter( fun (recording) -> begin log( "Shutting down #{recording.id()}" ) recording.shutdown() end, recordings() ) recordings := [] end def start_recording(path) = log( "Starting recording" ) log( "Recording file: #{path}" ) recording = output.file(%mp3, append=true, {path}, s) recordings := [recording, ...recordings()] end tmp = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp) tmp2 = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp2) thread.run( delay=1., fun () -> begin shutdown_recordings() tmp = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp) tmp2 = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp2) let [o] = recordings() o.on_start(synchronous=true, test.pass) end ) liquidsoap-2.4.2/tests/regression/GH4819.liq000066400000000000000000000020401513273233300205440ustar00rootroot00000000000000log.level := 4 log.file.path := "/tmp/bla.log" log.stdout := true s = sine() s = mksafe(buffer(s)) recordings = ref([]) def shutdown_recordings() = log( "Stopping recordings" ) list.iter( fun (recording) -> begin log( "Shutting down #{recording.id()}" ) recording.shutdown() end, recordings() ) recordings := [] end def start_recording(path) = log( "Starting recording" ) log( "Recording file: #{path}" ) recording = output.file(%mp3, append=true, {path}, s) recordings := [recording, ...recordings()] end tmp = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp) tmp2 = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp2) thread.run( delay=1., fun () -> begin shutdown_recordings() tmp = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp) tmp2 = file.temp(cleanup=true, "bla", "mp3") start_recording(tmp2) let [o] = recordings() o.on_start(synchronous=true, test.pass) end ) liquidsoap-2.4.2/tests/regression/LS268.liq000066400000000000000000000006061513273233300205040ustar00rootroot00000000000000# In LS-268 we realized that an incorrect assumption had # been made in code from LS-394, resulting in a crash in # case of source re-awakening. p = input.http("http://localhost:8000/nonexistent") o = output.dummy(fallible=true, p) on_cleanup(test.pass) thread.run(delay=2., {o.shutdown()}) thread.run(delay=3., {ignore(output.dummy(fallible=true, p))}) thread.run(delay=4., {test.pass()}) liquidsoap-2.4.2/tests/regression/LS354-1.liq000066400000000000000000000003461513273233300206370ustar00rootroot00000000000000s = blank(duration=1.) s.on_track(synchronous=true, fun (_) -> test.pass()) r = ref(false) d = source.dynamic({if !r then s else null end}) output.dummy(mksafe(d)) thread.run(delay=2., {r := true}) thread.run(delay=4., test.fail) liquidsoap-2.4.2/tests/regression/LS354-2.liq000066400000000000000000000005571513273233300206440ustar00rootroot00000000000000s1 = source.fail(id="fail") s2 = blank(id="blank", duration=1.) s2.on_track(synchronous=true, fun (_) -> test.pass()) r = ref(0) d = source.dynamic({if r() == 1 then s1 elsif r() == 2 then s2 else null end}) output.dummy(mksafe(d)) thread.run(delay=2., {r := 1}) thread.run(delay=3., {r := 2}) thread.run( delay=5., { test.fail() shutdown() } ) liquidsoap-2.4.2/tests/regression/LS460.liq000066400000000000000000000007061513273233300204770ustar00rootroot00000000000000# Scenario: # Let foo start using q, then stop it, skip in q. # When foo restarts it doesn't know that q isn't ready anymore, # which can lead to a crash. q = once(sine(duration=10.)) output.dummy(id="bar", mksafe(q)) output.dummy(id="foo", fallback([amplify(1., q), blank(duration=1.)])) def at(t, s) = thread.run(delay=t, {ignore(server.execute(s))}) end at(3., "foo.stop") at(4., "bar.skip") at(5., "foo.start") thread.run(delay=6., test.pass) liquidsoap-2.4.2/tests/regression/LS503.liq000066400000000000000000000007651513273233300205020ustar00rootroot00000000000000# In LS-503 we realized that a source may throw an # exception during output_get_ready call in the initial # main phase. This code reproduces the issue by throwing # an exception in output.icecast. # Reopen stderr to /dev/null to # disable printing expected exception #reopen.stderr("/dev/null") on_cleanup(test.pass) def on_error(~restart_in:_, _) = test.pass() end p = noise() o = output.icecast(%wav, fallible=true, host="nonexistent", mount="test", p) o.on_error(synchronous=true, on_error) liquidsoap-2.4.2/tests/regression/append-merge.liq000066400000000000000000000012411513273233300222660ustar00rootroot00000000000000music = chop(every=1., metadata=[("source", "s1")], sine(amplitude=0.1, 440.)) def next(_) = s = sine(amplitude=0.1, duration=.5, 880.) metadata.map(insert_missing=true, fun (_) -> [("source", "s2")], s) end s = append(merge=true, music, next) count_s1 = ref(0) count_s2 = ref(0) s.on_metadata( synchronous=true, fun (m) -> begin s = m["source"] if s == "s1" then ref.incr(count_s1) elsif s == "s2" then ref.incr(count_s2) end if count_s1() > 2 and count_s2() > 2 then test.pass() end end ) s.on_track( synchronous=true, fun (m) -> if m["source"] == "s2" then test.fail() end ) output.dummy(s) liquidsoap-2.4.2/tests/regression/append-merge2.liq000066400000000000000000000014651513273233300223600ustar00rootroot00000000000000music = chop(every=1., metadata=[("title", "music")], sine(amplitude=0.1, 440.)) jingles = chop(every=1., metadata=[("title", "jingle")], sine(amplitude=0.1, 220.)) def next(_) = sequence(merge=true, [jingles, jingles, source.fail()]) end s = append(music, next) meta_seen = ref([]) s.on_metadata( synchronous=true, fun (m) -> begin meta_seen := [...meta_seen(), m["title"]] if list.length(meta_seen()) == 9 then test.equal( meta_seen(), [ "music", "jingle", "jingle", "music", "jingle", "jingle", "music", "jingle", "jingle" ] ) test.pass() end end ) clock.assign_new(sync='none', [s]) output.dummy(s) liquidsoap-2.4.2/tests/regression/append.liq000066400000000000000000000010101513273233300211630ustar00rootroot00000000000000music = chop(every=1., metadata=[("source", "s1")], sine(amplitude=0.1, 440.)) def next(_) = sine(amplitude=0.1, duration=.5, 880.) end s = append(music, next) count_s1 = ref(0) count_s2 = ref(0) s.on_track( synchronous=true, fun (m) -> begin s = m["source"] if s == "s1" then ref.incr(count_s1) else test.equal(m["source"], "") ref.incr(count_s2) end if count_s1() > 2 and count_s2() > 2 then test.pass() end end ) output.dummy(s) liquidsoap-2.4.2/tests/regression/default_format.liq000066400000000000000000000001501513273233300227140ustar00rootroot00000000000000s = source({audio=source.tracks(blank()).audio}) output.dummy(blank()) thread.run(delay=0.1, test.pass) liquidsoap-2.4.2/tests/regression/dune000066400000000000000000000023201513273233300200700ustar00rootroot00000000000000; Regenerate using dune build @gendune --auto-promote (include dune.inc) (rule (alias gendune) (deps (source_tree .)) (target dune.inc.gen) (action (with-stdout-to dune.inc.gen (run ./gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) (executable (name gen_dune) (libraries liquidsoap_build_tools) (modules gen_dune)) (rule (alias citest) (package liquidsoap) (deps ../../src/bin/liquidsoap.exe (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "server port" liquidsoap --check "harbor.http.register.simple(port=2000,method=\"GET\",\"/foo\",(fun (_) -> \"test\"))"))) (rule (alias citest) (package liquidsoap) (deps ../../src/bin/liquidsoap.exe (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (:run_test ../run_test.exe)) (action (pipe-stdout (echo "print(123)") (run %{run_test} "stdin script" liquidsoap --check "-")))) (rule (alias citest) (package liquidsoap) (target theora-test.mp4) (action (run ffmpeg -f lavfi -i testsrc=duration=10:size=1280x720:rate=30 -f lavfi -i "sine=frequency=1000:duration=10" %{target}))) liquidsoap-2.4.2/tests/regression/dune.inc000066400000000000000000001115141513273233300206460ustar00rootroot00000000000000 (rule (alias 115-1) (package liquidsoap) (deps 115-1.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} 115-1.liq liquidsoap %{test_liq} 115-1.liq))) (rule (alias 115-2) (package liquidsoap) (deps 115-2.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} 115-2.liq liquidsoap %{test_liq} 115-2.liq))) (rule (alias 3553) (package liquidsoap) (deps 3553.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} 3553.liq liquidsoap %{test_liq} 3553.liq))) (rule (alias AC5109) (package liquidsoap) (deps AC5109.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} AC5109.liq liquidsoap %{test_liq} AC5109.liq))) (rule (alias BUG403) (package liquidsoap) (deps BUG403.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} BUG403.liq liquidsoap %{test_liq} BUG403.liq))) (rule (alias GH-action-919422659) (package liquidsoap) (deps GH-action-919422659.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH-action-919422659.liq liquidsoap %{test_liq} GH-action-919422659.liq))) (rule (alias GH1129) (package liquidsoap) (deps GH1129.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH1129.liq liquidsoap %{test_liq} GH1129.liq))) (rule (alias GH1146) (package liquidsoap) (deps GH1146.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH1146.liq liquidsoap %{test_liq} GH1146.liq))) (rule (alias GH1151) (package liquidsoap) (deps GH1151.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH1151.liq liquidsoap %{test_liq} GH1151.liq))) (rule (alias GH1159) (package liquidsoap) (deps GH1159.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH1159.liq liquidsoap %{test_liq} GH1159.liq))) (rule (alias GH1279) (package liquidsoap) (deps GH1279.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH1279.liq liquidsoap %{test_liq} GH1279.liq))) (rule (alias GH1327) (package liquidsoap) (deps GH1327.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH1327.liq liquidsoap %{test_liq} GH1327.liq))) (rule (alias GH2585) (package liquidsoap) (deps GH2585.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2585.liq liquidsoap %{test_liq} GH2585.liq))) (rule (alias GH2602) (package liquidsoap) (deps GH2602.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2602.liq liquidsoap %{test_liq} GH2602.liq))) (rule (alias GH2756-2) (package liquidsoap) (deps GH2756-2.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2756-2.liq liquidsoap %{test_liq} GH2756-2.liq))) (rule (alias GH2756) (package liquidsoap) (deps GH2756.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2756.liq liquidsoap %{test_liq} GH2756.liq))) (rule (alias GH2758) (package liquidsoap) (deps GH2758.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2758.liq liquidsoap %{test_liq} GH2758.liq))) (rule (alias GH2842) (package liquidsoap) (deps GH2842.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2842.liq liquidsoap %{test_liq} GH2842.liq))) (rule (alias GH2850) (package liquidsoap) (deps GH2850.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2850.liq liquidsoap %{test_liq} GH2850.liq))) (rule (alias GH2867) (package liquidsoap) (deps GH2867.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2867.liq liquidsoap %{test_liq} GH2867.liq))) (rule (alias GH2871) (package liquidsoap) (deps GH2871.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2871.liq liquidsoap %{test_liq} GH2871.liq))) (rule (alias GH2872) (package liquidsoap) (deps GH2872.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2872.liq liquidsoap %{test_liq} GH2872.liq))) (rule (alias GH2897) (package liquidsoap) (deps GH2897.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2897.liq liquidsoap %{test_liq} GH2897.liq))) (rule (alias GH2902) (package liquidsoap) (deps GH2902.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2902.liq liquidsoap %{test_liq} GH2902.liq))) (rule (alias GH2926) (package liquidsoap) (deps GH2926.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH2926.liq liquidsoap %{test_liq} GH2926.liq))) (rule (alias GH3093) (package liquidsoap) (deps GH3093.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3093.liq liquidsoap %{test_liq} GH3093.liq))) (rule (alias GH3132) (package liquidsoap) (deps GH3132.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3132.liq liquidsoap %{test_liq} GH3132.liq))) (rule (alias GH3134) (package liquidsoap) (deps GH3134.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3134.liq liquidsoap %{test_liq} GH3134.liq))) (rule (alias GH3224) (package liquidsoap) (deps GH3224.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3224.liq liquidsoap %{test_liq} GH3224.liq))) (rule (alias GH3239-2) (package liquidsoap) (deps GH3239-2.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3239-2.liq liquidsoap %{test_liq} GH3239-2.liq))) (rule (alias GH3239-bis) (package liquidsoap) (deps GH3239-bis.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3239-bis.liq liquidsoap %{test_liq} GH3239-bis.liq))) (rule (alias GH3239) (package liquidsoap) (deps GH3239.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3239.liq liquidsoap %{test_liq} GH3239.liq))) (rule (alias GH3276) (package liquidsoap) (deps GH3276.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3276.liq liquidsoap %{test_liq} GH3276.liq))) (rule (alias GH3303) (package liquidsoap) (deps GH3303.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3303.liq liquidsoap %{test_liq} GH3303.liq))) (rule (alias GH3316) (package liquidsoap) (deps GH3316.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3316.liq liquidsoap %{test_liq} GH3316.liq))) (rule (alias GH3645) (package liquidsoap) (deps GH3645.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3645.liq liquidsoap %{test_liq} GH3645.liq))) (rule (alias GH3675) (package liquidsoap) (deps GH3675.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3675.liq liquidsoap %{test_liq} GH3675.liq))) (rule (alias GH3813) (package liquidsoap) (deps GH3813.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3813.liq liquidsoap %{test_liq} GH3813.liq))) (rule (alias GH3840) (package liquidsoap) (deps GH3840.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3840.liq liquidsoap %{test_liq} GH3840.liq))) (rule (alias GH3881) (package liquidsoap) (deps GH3881.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3881.liq liquidsoap %{test_liq} GH3881.liq))) (rule (alias GH3960) (package liquidsoap) (deps GH3960.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH3960.liq liquidsoap %{test_liq} GH3960.liq))) (rule (alias GH4071) (package liquidsoap) (deps GH4071.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4071.liq liquidsoap %{test_liq} GH4071.liq))) (rule (alias GH4090) (package liquidsoap) (deps GH4090.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4090.liq liquidsoap %{test_liq} GH4090.liq))) (rule (alias GH4116) (package liquidsoap) (deps GH4116.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4116.liq liquidsoap %{test_liq} GH4116.liq))) (rule (alias GH4124) (package liquidsoap) (deps GH4124.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4124.liq liquidsoap %{test_liq} GH4124.liq))) (rule (alias GH4140) (package liquidsoap) (deps GH4140.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4140.liq liquidsoap %{test_liq} GH4140.liq))) (rule (alias GH4144) (package liquidsoap) (deps GH4144.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4144.liq liquidsoap %{test_liq} GH4144.liq))) (rule (alias GH4159) (package liquidsoap) (deps GH4159.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4159.liq liquidsoap %{test_liq} GH4159.liq))) (rule (alias GH4163) (package liquidsoap) (deps GH4163.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4163.liq liquidsoap %{test_liq} GH4163.liq))) (rule (alias GH4246) (package liquidsoap) (deps GH4246.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4246.liq liquidsoap %{test_liq} GH4246.liq))) (rule (alias GH4281-2) (package liquidsoap) (deps GH4281-2.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4281-2.liq liquidsoap %{test_liq} GH4281-2.liq))) (rule (alias GH4281) (package liquidsoap) (deps GH4281.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4281.liq liquidsoap %{test_liq} GH4281.liq))) (rule (alias GH4395) (package liquidsoap) (deps GH4395.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4395.liq liquidsoap %{test_liq} GH4395.liq))) (rule (alias GH4499) (package liquidsoap) (deps GH4499.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4499.liq liquidsoap %{test_liq} GH4499.liq))) (rule (alias GH4629) (package liquidsoap) (deps GH4629.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4629.liq liquidsoap %{test_liq} GH4629.liq))) (rule (alias GH4711) (package liquidsoap) (deps GH4711.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4711.liq liquidsoap %{test_liq} GH4711.liq))) (rule (alias GH4745) (package liquidsoap) (deps GH4745.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4745.liq liquidsoap %{test_liq} GH4745.liq))) (rule (alias GH4748) (package liquidsoap) (deps GH4748.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4748.liq liquidsoap %{test_liq} GH4748.liq))) (rule (alias GH4819-bis) (package liquidsoap) (deps GH4819-bis.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4819-bis.liq liquidsoap %{test_liq} GH4819-bis.liq))) (rule (alias GH4819) (package liquidsoap) (deps GH4819.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} GH4819.liq liquidsoap %{test_liq} GH4819.liq))) (rule (alias LS268) (package liquidsoap) (deps LS268.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} LS268.liq liquidsoap %{test_liq} LS268.liq))) (rule (alias LS354-1) (package liquidsoap) (deps LS354-1.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} LS354-1.liq liquidsoap %{test_liq} LS354-1.liq))) (rule (alias LS354-2) (package liquidsoap) (deps LS354-2.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} LS354-2.liq liquidsoap %{test_liq} LS354-2.liq))) (rule (alias LS460) (package liquidsoap) (deps LS460.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} LS460.liq liquidsoap %{test_liq} LS460.liq))) (rule (alias LS503) (package liquidsoap) (deps LS503.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} LS503.liq liquidsoap %{test_liq} LS503.liq))) (rule (alias append-merge) (package liquidsoap) (deps append-merge.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} append-merge.liq liquidsoap %{test_liq} append-merge.liq))) (rule (alias append-merge2) (package liquidsoap) (deps append-merge2.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} append-merge2.liq liquidsoap %{test_liq} append-merge2.liq))) (rule (alias append) (package liquidsoap) (deps append.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} append.liq liquidsoap %{test_liq} append.liq))) (rule (alias default_format) (package liquidsoap) (deps default_format.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} default_format.liq liquidsoap %{test_liq} default_format.liq))) (rule (alias eval-check-demeth) (package liquidsoap) (deps eval-check-demeth.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} eval-check-demeth.liq liquidsoap %{test_liq} eval-check-demeth.liq))) (rule (alias external-encoder) (package liquidsoap) (deps external-encoder.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} external-encoder.liq liquidsoap %{test_liq} external-encoder.liq))) (rule (alias fallible_ogg) (package liquidsoap) (deps fallible_ogg.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} fallible_ogg.liq liquidsoap %{test_liq} fallible_ogg.liq))) (rule (alias ffmpeg-copy-encode) (package liquidsoap) (deps ffmpeg-copy-encode.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ffmpeg-copy-encode.liq liquidsoap %{test_liq} ffmpeg-copy-encode.liq))) (rule (alias ffmpeg-copy-input-http) (package liquidsoap) (deps ffmpeg-copy-input-http.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ffmpeg-copy-input-http.liq liquidsoap %{test_liq} ffmpeg-copy-input-http.liq))) (rule (alias ffmpeg-naming-convention) (package liquidsoap) (deps ffmpeg-naming-convention.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ffmpeg-naming-convention.liq liquidsoap %{test_liq} ffmpeg-naming-convention.liq))) (rule (alias infallible-shutdown) (package liquidsoap) (deps infallible-shutdown.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} infallible-shutdown.liq liquidsoap %{test_liq} infallible-shutdown.liq))) (rule (alias init-error) (package liquidsoap) (deps init-error.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} init-error.liq liquidsoap %{test_liq} init-error.liq))) (rule (alias initial_request_queue) (package liquidsoap) (deps initial_request_queue.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} initial_request_queue.liq liquidsoap %{test_liq} initial_request_queue.liq))) (rule (alias input_rtmp) (package liquidsoap) (deps input_rtmp.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} input_rtmp.liq liquidsoap %{test_liq} input_rtmp.liq))) (rule (alias metadata-resolvers-metadata) (package liquidsoap) (deps metadata-resolvers-metadata.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} metadata-resolvers-metadata.liq liquidsoap %{test_liq} metadata-resolvers-metadata.liq))) (rule (alias metadata_cache) (package liquidsoap) (deps metadata_cache.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} metadata_cache.liq liquidsoap %{test_liq} metadata_cache.liq))) (rule (alias output_on_metadata) (package liquidsoap) (deps output_on_metadata.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} output_on_metadata.liq liquidsoap %{test_liq} output_on_metadata.liq))) (rule (alias output_on_track) (package liquidsoap) (deps output_on_track.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} output_on_track.liq liquidsoap %{test_liq} output_on_track.liq))) (rule (alias playlist-id) (package liquidsoap) (deps playlist-id.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} playlist-id.liq liquidsoap %{test_liq} playlist-id.liq))) (rule (alias replaygain) (package liquidsoap) (deps replaygain.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} replaygain.liq liquidsoap %{test_liq} replaygain.liq))) (rule (alias seek_track_map) (package liquidsoap) (deps seek_track_map.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} seek_track_map.liq liquidsoap %{test_liq} seek_track_map.liq))) (rule (alias shoutcast-args) (package liquidsoap) (deps shoutcast-args.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} shoutcast-args.liq liquidsoap %{test_liq} shoutcast-args.liq))) (rule (alias source_cleanup) (package liquidsoap) (deps source_cleanup.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} source_cleanup.liq liquidsoap %{test_liq} source_cleanup.liq))) (rule (alias source_dynamic) (package liquidsoap) (deps source_dynamic.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} source_dynamic.liq liquidsoap %{test_liq} source_dynamic.liq))) (rule (alias track_sensitive) (package liquidsoap) (deps track_sensitive.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} track_sensitive.liq liquidsoap %{test_liq} track_sensitive.liq))) (rule (alias unified-pcm-types) (package liquidsoap) (deps unified-pcm-types.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} unified-pcm-types.liq liquidsoap %{test_liq} unified-pcm-types.liq))) (rule (alias video-only) (package liquidsoap) (deps video-only.liq ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} video-only.liq liquidsoap %{test_liq} video-only.liq))) (alias (name citest) (deps (alias 115-1) (alias 115-2) (alias 3553) (alias AC5109) (alias BUG403) (alias GH-action-919422659) (alias GH1129) (alias GH1146) (alias GH1151) (alias GH1159) (alias GH1279) (alias GH1327) (alias GH2585) (alias GH2602) (alias GH2756) (alias GH2756-2) (alias GH2758) (alias GH2842) (alias GH2850) (alias GH2867) (alias GH2871) (alias GH2872) (alias GH2897) (alias GH2902) (alias GH2926) (alias GH3093) (alias GH3132) (alias GH3134) (alias GH3224) (alias GH3239) (alias GH3239-2) (alias GH3239-bis) (alias GH3276) (alias GH3303) (alias GH3316) (alias GH3645) (alias GH3675) (alias GH3813) (alias GH3840) (alias GH3881) (alias GH3960) (alias GH4071) (alias GH4090) (alias GH4116) (alias GH4124) (alias GH4140) (alias GH4144) (alias GH4159) (alias GH4163) (alias GH4246) (alias GH4281) (alias GH4281-2) (alias GH4395) (alias GH4499) (alias GH4629) (alias GH4711) (alias GH4745) (alias GH4748) (alias GH4819) (alias GH4819-bis) (alias LS268) (alias LS354-1) (alias LS354-2) (alias LS460) (alias LS503) (alias append) (alias append-merge) (alias append-merge2) (alias default_format) (alias eval-check-demeth) (alias external-encoder) (alias fallible_ogg) (alias ffmpeg-copy-encode) (alias ffmpeg-copy-input-http) (alias ffmpeg-naming-convention) (alias infallible-shutdown) (alias init-error) (alias initial_request_queue) (alias input_rtmp) (alias metadata-resolvers-metadata) (alias metadata_cache) (alias output_on_metadata) (alias output_on_track) (alias playlist-id) (alias replaygain) (alias seek_track_map) (alias shoutcast-args) (alias source_cleanup) (alias source_dynamic) (alias track_sensitive) (alias unified-pcm-types) (alias video-only))) liquidsoap-2.4.2/tests/regression/eval-check-demeth.liq000066400000000000000000000003741513273233300231760ustar00rootroot00000000000000tmp = file.temp("foo", "mp3") on_cleanup({file.remove(tmp)}) s = sine() let {audio = a} = source.tracks(s) let a = track.audio.compress(a) s = source({audio=a}) o = output.file(fallible=true, %mp3(mono), tmp, s) o.on_start(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/external-encoder.liq000066400000000000000000000003211513273233300231570ustar00rootroot00000000000000tmp = file.temp("foo", "flv") on_cleanup({file.remove(tmp)}) encoder = %external(process = "cat") o = output.file(fallible=true, encoder, "/tmp/cat", sine(duration=1.)) o.on_start(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/fallible_ogg.liq000066400000000000000000000003641513273233300223350ustar00rootroot00000000000000port = 9301 s = playlist("../media") output.icecast(fallible=true, port=port, mount="test.flac", %ogg(%flac), s) s = input.harbor("test.flac", buffer=1., port=port) o = output.dummy(fallible=true, s) o.on_start(synchronous=true, test.pass) liquidsoap-2.4.2/tests/regression/ffmpeg-copy-encode.liq000066400000000000000000000014471513273233300234010ustar00rootroot00000000000000# Make sure tracks are saved in the same order. def f() = s = once( single( "../media/@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']].mp4" ) ) def on_stop() = j = process.read( "ffprobe -v quiet -print_format json -show_streams \ ffmpeg-copy-encode.mkv" ) let json.parse ({streams = [s1, s2]} : {streams: [{channels?: int}]}) = j if s1?.channels != 2 or s2?.channels != 1 then test.fail() end test.pass() end o = output.file( fallible=true, %ffmpeg(%audio.copy, %audio_2(channels = 1, codec = "aac"), %video.copy), "ffmpeg-copy-encode.mkv", s ) o.on_stop(synchronous=true, on_stop) end test.check(f) liquidsoap-2.4.2/tests/regression/ffmpeg-copy-input-http.liq000066400000000000000000000002011513273233300242430ustar00rootroot00000000000000s = input.http("foo") output.srt(port=10001, %ffmpeg(%audio.copy), fallible=true, s) def f() = test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/ffmpeg-naming-convention.liq000066400000000000000000000027531513273233300246260ustar00rootroot00000000000000log.level := 4 def f() = s = once( single( "../media/@ffmpeg[format='mp4',@audio[codec='aac',channels=2],@audio_2[codec='aac',channels=1],@video[codec='libx264'],@video_2[codec='libx264']].mp4" ) ) let {audio, audio_2, video} = source.tracks(s) s = source( { en=audio, audio_fr=audio_2, fr=track.audio.mean(audio_2), encoded_fr=track.audio.mean(audio_2), encoded_director_cut=video, director_cut_video=video } ) s = once(s) def on_close(_) = j = process.read( "ffprobe -v quiet -print_format json -show_streams \ ffmpeg-naming-convention.mkv" ) let json.parse ({streams = [s1, s2, s3, s4, s5, s6]} : {streams: [{codec_type: string, channels?: int}]} ) = j test.equal(s1?.channels, 2) test.equal(s2?.channels, 1) test.equal(s3?.channels, 2) test.equal(s4?.channels, 1) test.equal(s5.codec_type, "video") test.equal(s6.codec_type, "video") test.pass() end audio_codec = "aac" video_codec = "libx264" output.file( fallible=true, on_close=on_close, %ffmpeg( %en.copy, %fr(audio_content, channels = 1, codec = audio_codec), %audio_fr(channels = 2, codec = "aac"), %encoded_fr(channels = 1, codec = "aac"), %director_cut_video(video_content, codec = video_codec), %encoded_director_cut(codec = "libx264") ), "ffmpeg-naming-convention.mkv", s ) end test.check(f) liquidsoap-2.4.2/tests/regression/gen_dune.ml000066400000000000000000000016231513273233300213350ustar00rootroot00000000000000let all_tests = ref [] let () = let location = Sys.getcwd () in let tests = List.filter (fun f -> Filename.extension f = ".liq") (Build_tools.read_files ~location "") in List.iter (fun test -> let target = Filename.remove_extension test in all_tests := Printf.sprintf "(alias %s)" target :: !all_tests; Printf.printf {| (rule (alias %s) (package liquidsoap) (deps %s ../media/all_media_files ../../src/bin/liquidsoap.exe ../streams/file1.png ../streams/file1.mp3 ./theora-test.mp4 (package liquidsoap) (source_tree ../../src/libs) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %%{run_test} %s liquidsoap %%{test_liq} %s))) |} target test test test) tests let () = Printf.printf {|(alias (name citest) (deps %s)) |} (String.concat "\n " (List.sort_uniq Stdlib.compare !all_tests)) liquidsoap-2.4.2/tests/regression/infallible-shutdown.liq000066400000000000000000000002651513273233300237010ustar00rootroot00000000000000#!../../src/liquidsoap ../../libs/stdlib.liq ../../libs/deprecations.liq # Make sure infallible output can shutdown properly. output.dummy(blank()) thread.run(delay=1., test.pass) liquidsoap-2.4.2/tests/regression/init-error.liq000066400000000000000000000004721513273233300220210ustar00rootroot00000000000000#!../../src/liquidsoap ../../libs/stdlib.liq ../../libs/deprecations.liq # Make sure that we properly shutdown on init error log.level.set(5) test.skip() o = output.dummy(blank()) o.on_shutdown(synchronous=true, test.pass) o.on_wake_up( synchronous=true, {output.dummy(fallible=true, source.fail.init())} ) liquidsoap-2.4.2/tests/regression/initial_request_queue.liq000066400000000000000000000003061513273233300243300ustar00rootroot00000000000000def f() = r = request.create("../media/@wav[stereo].wav") s = request.queue(queue=[r]) s.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) end test.check(f) liquidsoap-2.4.2/tests/regression/input_rtmp.liq000066400000000000000000000002471513273233300221300ustar00rootroot00000000000000#!../../src/liquidsoap ../../libs/stdlib.liq ../../libs/deprecations.liq output.dummy(input.rtmp("rtmp://127.0.0.1:53163/share"), fallible=true) thread.run(test.pass) liquidsoap-2.4.2/tests/regression/metadata-resolvers-metadata.liq000066400000000000000000000012251513273233300253040ustar00rootroot00000000000000dir = file.temp_dir(cleanup=true, "metadata-resolver") def media_protocol(~rlog:_, ~maxtime:_, arg) = "#{dir}/#{arg}" end protocol.add("media", media_protocol) def check_metadata(m) = if not list.assoc.mem("foo", m) then test.fail() end end def check_metadata_resolver(~metadata, _) = check_metadata(metadata) [] end decoder.metadata.add("check_metadata_resolver", check_metadata_resolver) def f() = file.copy("../media/@wav[stereo].wav", "#{dir}/file.wav") r = request.create("annotate:foo=123:media:file.wav") if not request.resolve(r) then test.fail() end m = request.metadata(r) check_metadata(m) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/metadata_cache.liq000066400000000000000000000011151513273233300226250ustar00rootroot00000000000000s = sine() thread.run( every=1., { begin print( "Inserting metadata" ) s.insert_metadata(new_track=true, [("foo", "bla")]) end } ) s = compress.multiband( s, [ { frequency=200., attack=10., release=30., ratio=2., threshold=-4., gain=0. }, { frequency=20000., attack=10., release=40., ratio=2., threshold=-2., gain=-2. } ] ) s.on_metadata(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/regression/output_on_metadata.liq000066400000000000000000000003101513273233300236120ustar00rootroot00000000000000s = single("../media/@mp3[stereo].mp3") o = output.dummy(fallible=true, s) o.on_metadata( synchronous=true, fun (m) -> if list.assoc.mem("on_air", m) then test.pass() else test.fail() end ) liquidsoap-2.4.2/tests/regression/output_on_track.liq000066400000000000000000000003051513273233300231420ustar00rootroot00000000000000s = single("../media/@mp3[stereo].mp3") o = output.dummy(fallible=true, s) o.on_track( synchronous=true, fun (m) -> if list.assoc.mem("on_air", m) then test.pass() else test.fail() end ) liquidsoap-2.4.2/tests/regression/playlist-id.liq000066400000000000000000000005231513273233300221570ustar00rootroot00000000000000def t(file, expected) = id = playlist.id(default="foo", file) if id != expected then print( "Test fail: #{file} maps to: #{id} instead of #{expected}" ) test.fail() end end def f() = t("bla", "bla") t("bla/gni.pls", "gni.pls") t("./gni.pls", "gni.pls") t(".", "foo") test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/replaygain.liq000066400000000000000000000002531513273233300220570ustar00rootroot00000000000000enable_replaygain_metadata() s = single("../media/@wav[stereo].wav") s = replaygain(s) s.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/regression/seek_track_map.liq000066400000000000000000000006771513273233300227060ustar00rootroot00000000000000# This is a regression test for GH3252 s = single("../streams/file1.mp3") def set_cues(s) = def f(_) = in = random.int(min=5, max=10) out = in + random.int(min=10, max=15) [("liq_cue_in", "#{in}"), ("liq_cue_out", "#{out}")] end metadata.map(insert_missing=true, f, s) end s = set_cues(s) thread.run( delay=3., { let seeked = s.seek(2.) if seeked >= 1. then test.pass() else test.fail() end } ) output.dummy(s) liquidsoap-2.4.2/tests/regression/shoutcast-args.liq000066400000000000000000000002431513273233300226720ustar00rootroot00000000000000def f() = output.shoutcast( %mp3, host="shoutcast.example.org", port=8000, password="changeme", sine() ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/regression/source_cleanup.liq000066400000000000000000000011301513273233300227260ustar00rootroot00000000000000s = sine() output.dummy(s) output_list = ref([]) def check() = runtime.gc.compact() runtime.gc.compact() runtime.gc.compact() if list.length(clock(s.clock).sources()) == 2 then test.pass() else test.fail() end end def cleanup() = l = output_list() output_list := [] list.iter(fun (o) -> o.shutdown(), l) thread.run(delay=2., check) end def create() = for i = 0 to 49 do s = switch([({true}, sine()), ({true}, s)]) o = output.dummy(s) output_list := [o, ...!output_list] end thread.run(delay=2., cleanup) end thread.run(delay=0., create) liquidsoap-2.4.2/tests/regression/source_dynamic.liq000066400000000000000000000013461513273233300227340ustar00rootroot00000000000000#!../../liquidsoap ../test.liq s_ref = ref((fallback([]) : source)) def next() = r = request.create("../streams/file1.png") ignore(request.resolve(r)) s = request.dynamic(id="image-dyn", fun () -> null) s.set_queue([r]) s end s = (source.dynamic(id="image", track_sensitive=true, next) : source(video=canvas) ) s_ref := s output.dummy(fallible=true, s) s_ref = ref((fallback([]) : source)) def next() = r = request.create("../streams/file1.mp3") ignore(request.resolve(r)) s = request.dynamic(id="audio-dyn", fun () -> null) s.set_queue([r]) s end s = (source.dynamic(id="audio", track_sensitive=true, next) : source(audio=pcm)) s_ref := s output.dummy(fallible=true, s) thread.run(delay=3., test.pass) liquidsoap-2.4.2/tests/regression/track_sensitive.liq000066400000000000000000000004561513273233300231260ustar00rootroot00000000000000s = sine() is_ready = ref(true) is_done = ref(false) s = switch([(is_ready, s)]) o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, {if not is_done() then test.fail() end}) thread.run(delay=0.1, {is_ready := false}) thread.run( delay=1., { is_done := true test.pass() } ) liquidsoap-2.4.2/tests/regression/unified-pcm-types.liq000066400000000000000000000005221513273233300232650ustar00rootroot00000000000000# Type parameters below are shared between pcm_s16 source and the decoded # source. We want to make sure that this works at runtime. def f() = s = blank() s.on_track(synchronous=true, fun (_) -> test.pass()) s = audio.decode.pcm_s16(s) output.file(%ffmpeg(%audio(codec = "aac")), "unified-pcm-types.aac", s) end test.check(f) liquidsoap-2.4.2/tests/regression/video-only.liq000066400000000000000000000004151513273233300220110ustar00rootroot00000000000000a = sine() v = blank() s = source(source.tracks(a).{video=source.tracks(v).video}) s.on_track(synchronous=true, fun (_) -> test.pass()) tmp = file.temp("foo", "flv") on_cleanup({file.remove(tmp)}) output.file(%ffmpeg(format = "flv", %video(codec = "libx264")), tmp, s) liquidsoap-2.4.2/tests/run_test.ml000066400000000000000000000072571513273233300172450ustar00rootroot00000000000000let timeout = 10 * 60 let test = Sys.argv.(1) let cmd = Sys.argv.(2) let args = try Array.sub Sys.argv 2 (Array.length Sys.argv - 2) with _ -> [||] let stdin_file = Filename.null let log_files = ref [] let cleanup () = List.iter (fun logfile -> try Unix.unlink logfile with _ -> ()) !log_files let warning_prefix, error_prefix = if Sys.getenv_opt "GITHUB_ACTIONS" <> None then ( Printf.sprintf "::warning file=%s,title=Test skipped::" test, Printf.sprintf "::error file=%s,title=Test failed::" test ) else ("", "") let () = Console.color_conf := `Always let colorized_test = Console.colorize [`white; `bold] test let colorized_timeout = Console.colorize [`magenta; `bold] "[timeout]" let colorized_ok = Console.colorize [`green; `bold] "[ok]" let colorized_skipped = Console.colorize [`yellow; `bold] "[skipped]" let colorized_failed = Console.colorize [`red; `bold] "[failed]" let run_process ~action cmd args = let start_time = Unix.time () in let logfile = Filename.temp_file "test" test in log_files := logfile :: !log_files; let stdin = Unix.openfile stdin_file [Unix.O_RDWR] 0o644 in let stdout = Unix.openfile logfile [Unix.O_RDWR; Unix.O_TRUNC] 0o644 in let print_log () = try Unix.close stdout; let buffer_size = 1024 in let buffer = Bytes.create buffer_size in let fd = Unix.openfile logfile [Unix.O_RDONLY] 0 in let rec loop () = match Unix.read fd buffer 0 buffer_size with | 0 -> () | _ -> Printf.eprintf "%s" (Bytes.unsafe_to_string buffer); loop () in loop (); print_newline (); Unix.close fd with _ -> () in let runtime () = let runtime = Unix.time () -. start_time in let min = int_of_float (runtime /. 60.) in let sec = runtime -. (float min *. 60.) in (min, int_of_float sec) in let pid_ref = ref None in let running = ref false in let on_timeout () = if !running then ( let min, sec = runtime () in Printf.eprintf "%s%s test %s: %s (time: %02dm:%02ds)\n" error_prefix action colorized_test colorized_timeout min sec; (match !pid_ref with Some p -> Unix.kill p Sys.sigkill | None -> ()); print_log (); cleanup (); exit 1) in ignore (Thread.create (fun () -> Unix.sleep timeout; on_timeout ()) ()); let pid = Unix.create_process cmd args stdin stdout stdout in running := true; pid_ref := Some pid; match Unix.waitpid [] pid with | _, Unix.WEXITED 0 -> running := false; let min, sec = runtime () in Printf.eprintf "%s test %s: %s (time: %02dm:%02ds)\n" action colorized_test colorized_ok min sec; if Sys.getenv_opt "LIQ_VERBOSE_TEST" <> None then print_log (); cleanup () | _, Unix.WEXITED 123 -> running := false; Printf.eprintf "%s%s test %s: %s\n" warning_prefix action colorized_test colorized_skipped; exit 0 | _ -> running := false; let min, sec = runtime () in Printf.eprintf "%s%s test %s: %s (time: %02dm:%02ds)\n" action error_prefix colorized_test colorized_failed min sec; print_log (); exit 1 let run () = (* Unix.putenv "MEMTRACE" (Printf.sprintf "%s.trace" test); *) if String.starts_with ~prefix:"liquidsoap" cmd then run_process ~action:"Cached" cmd (Array.concat [ [| args.(0) |]; [| "--cache-only" |]; Array.sub args 1 (Array.length args - 1); ]); run_process ~action:"Ran" cmd args let () = try run () with exn -> let bt = Printexc.get_raw_backtrace () in cleanup (); Printexc.raise_with_backtrace exn bt liquidsoap-2.4.2/tests/streams/000077500000000000000000000000001513273233300165135ustar00rootroot00000000000000liquidsoap-2.4.2/tests/streams/195.liq000066400000000000000000000003121513273233300175340ustar00rootroot00000000000000s = playlist(mode="randomize", reload=1, reload_mode="rounds", "playlist") s = test.check_non_repeating(nb_files=3, nb_rounds=10, s) clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/autocue-plot.0.new.txt000066400000000000000000000120411513273233300226210ustar00rootroot000000000000002.06 0.0 2.08 0.0 2.1 0.000416576377195 2.12 0.00083315275439 2.14 0.00124973020739 2.16 0.00166630753732 2.18 0.00208288388458 2.2 0.0024994586802 2.22 0.0029160351269 2.24 0.0033326115736 2.26 0.0037491880203 2.28 0.00416576468076 2.3 0.00458234023483 2.32 0.00499891536621 2.34 0.00541549164673 2.36 0.00583206792724 2.38 0.00624864703203 2.4 0.00666522350083 2.42 0.00708179742758 2.44 0.00749837105526 2.46 0.00791495145999 2.48 0.00833153544874 2.5 0.0087481164088 2.52 0.00916469244522 2.54 0.00958126313458 2.56 0.00999783532177 2.58 0.0104144168674 2.6 0.0108310017616 2.62 0.0112475814988 2.64 0.0116641545567 2.66 0.0120807276588 2.68 0.0124973044746 2.7 0.0129138866183 2.72 0.0133304711309 2.74 0.0137470504423 2.76 0.0141636233217 2.78 0.0145802004782 2.8 0.014996761236 2.82 0.015413337937 2.84 0.0158299150137 2.86 0.0162464876405 2.88 0.0166630596624 2.9 0.0170796361539 2.92 0.0174962126455 2.94 0.017912789137 2.96 0.0183293656286 2.98 0.0187459381533 3.0 0.019162500012 3.02 0.0195790760992 3.04 0.0199956593045 3.06 0.0204122428064 3.08 0.0208288191902 3.1 0.021245395574 3.12 0.0216619719578 3.14 0.0220785483416 3.16 0.0224951247254 3.18 0.0229117124437 3.2 0.0233282864627 3.22 0.0237448468362 3.24 0.0241614230965 3.26 0.0245779993567 3.28 0.0249945854593 3.3 0.02541117189 3.32 0.0258277484783 3.34 0.026244302347 3.36 0.0266608785747 3.38 0.0270774817818 3.4 0.0274940744896 3.42 0.0279106511607 3.44 0.0283272280437 3.46 0.0287438049267 3.48 0.0291603818097 3.5 0.0295769713511 3.52 0.029993561249 3.54 0.0304101344528 3.56 0.0308266829434 3.58 0.0312432597399 3.6 0.0316598365365 3.62 0.032076413333 3.64 0.0324929916695 3.66 0.0329095616768 3.68 0.0333261140148 3.7 0.0337426743234 3.72 0.0341592505497 3.74 0.0345758344648 3.76 0.0349924156812 3.78 0.0354089891401 3.8 0.0358255395547 3.82 0.036242115596 3.84 0.0366586916373 3.86 0.0370752676787 3.88 0.0374918359301 3.9 0.0379083869636 3.92 0.0383249454122 3.94 0.0387415209058 3.96 0.0391580963994 3.98 0.0395746931332 4.0 0.0399912663359 4.02 0.0404078160338 4.04 0.0408243914568 4.06 0.0412409668798 4.08 0.0416575484735 4.1 0.042074123486 4.12 0.0424906921952 4.14 0.0429072317634 4.16 0.043323806829 4.18 0.0437404263253 4.2 0.044157029371 4.22 0.0445736068858 4.24 0.0449901575961 4.26 0.0454066885533 4.28 0.0458232839654 4.3 0.0462398797463 4.32 0.0466564552395 4.34 0.0470730204152 4.36 0.0474895595016 4.38 0.047906134585 4.4 0.0483227096683 4.42 0.0487393191564 4.44 0.0491559015061 4.46 0.0495724538357 4.48 0.049989029078 4.5 0.0504056043203 4.52 0.0508221795626 4.54 0.0512387797972 4.56 0.0516553491452 4.58 0.0520718887496 4.6 0.0524884638596 4.62 0.0529050389696 4.64 0.0533216140796 4.66 0.053738195711 4.68 0.0541547673934 4.7 0.0545713086456 4.72 0.0549878595853 4.74 0.0554044617838 4.76 0.0558210805128 4.78 0.0562376711033 4.8 0.0566542122225 4.82 0.0570707586045 4.84 0.0574873045674 4.86 0.0579038792382 4.88 0.0583204638726 4.9 0.0587370406312 4.92 0.059153583547 4.94 0.0595701341463 4.96 0.0599867085109 4.98 0.0604033028835 5.0 0.0608198975319 5.02 0.0612364774968 5.04 0.0616530203841 5.06 0.0620695948462 5.08 0.0624861693082 5.1 0.0624861693082 5.12 0.0624861639135 5.14 0.062486135717 5.16 0.0624861129152 5.18 0.0624861129152 5.2 0.0624861129152 5.22 0.0624861592624 5.24 0.0624861710488 5.26 0.0624861710488 5.28 0.0624861145691 5.3 0.0624861447761 5.32 0.0624861749831 5.34 0.0624861749831 5.36 0.0624861705642 5.38 0.0624861299798 5.4 0.0624861299798 5.42 0.0624861299798 5.44 0.0624861808811 5.46 0.0624861989198 5.48 0.0624861785322 5.5 0.0624861259907 5.52 0.0624861259907 5.54 0.0624861259907 5.56 0.0624861259907 5.58 0.0624861279763 5.6 0.0624860927491 5.62 0.0624860927491 5.64 0.0624860927491 5.66 0.0624860927491 5.68 0.0624861168881 5.7 0.0624861097951 5.72 0.0624860786328 5.74 0.0624860545635 5.76 0.0624860545635 5.78 0.0624860545635 5.8 0.0624860629366 5.82 0.0624860333707 5.84 0.0624859749841 5.86 0.0624859749841 5.88 0.0624860043126 5.9 0.0624860486606 5.92 0.0624860636801 5.94 0.0624860636801 5.96 0.0624860636801 5.98 0.0624860636801 6.0 0.0624860636801 6.02 0.0624860783731 6.04 0.0624860783731 6.06 0.0624860510121 6.08 0.0624860236511 6.1 0.0624860236511 6.12 0.0624860236511 6.14 0.0624860229202 6.16 0.0624860031139 6.18 0.062485952494 6.2 0.0624859209496 6.22 0.0624859209496 6.24 0.0624859209496 6.26 0.0624859418273 6.28 0.0624859165453 6.3 0.0624858912633 6.32 0.0624858912633 6.34 0.0624859117633 6.36 0.0624859347346 6.38 0.0624859372061 6.4 0.0624859372061 6.42 0.0624858770244 6.44 0.0624858770244 6.46 0.0624858770244 6.48 0.0624859039202 6.5 0.0624858926407 6.52 0.0624858926407 6.54 0.0624858926407 6.56 0.0624858926407 6.58 0.0624858926407 6.6 0.0624859039376 6.62 0.0624859039376 6.64 0.0624858471899 6.66 0.0624858471899 6.68 0.0624859073677 6.7 0.0624859400375 6.72 0.062485937583 6.74 0.0624859351285 6.76 0.0624859351285 6.78 0.0624860030956 6.8 0.062486053659 6.82 0.0624860712246 6.84 0.0624860503638 6.86 0.0624860503638 6.88 0.0624859818155 6.9 0.0624859818155 6.92 0.0624859818155 6.94 0.0624859825633 6.96 0.0624859654892 6.98 0.0624859476674 7.0 0.0624859476674 7.02 0.0624860023875 liquidsoap-2.4.2/tests/streams/autocue-plot.0.old.txt000066400000000000000000000120101513273233300226020ustar00rootroot000000000000002.06 0.0624864787381 2.08 0.0624864787381 2.1 0.0624864787381 2.12 0.0624864982241 2.14 0.0624865189394 2.16 0.0624865027581 2.18 0.0624864853474 2.2 0.0624864853474 2.22 0.0624864853474 2.24 0.062486497646 2.26 0.0624865099446 2.28 0.0624864962307 2.3 0.0624864962307 2.32 0.0624864962307 2.34 0.0624864962307 2.36 0.0624865403299 2.38 0.0624865491764 2.4 0.0624865200383 2.42 0.0624865200383 2.44 0.0624865495034 2.46 0.0624865941965 2.48 0.0624866094245 2.5 0.0624866094245 2.52 0.0624866094245 2.54 0.0624866431543 2.56 0.0624867013079 2.58 0.0624867333064 2.6 0.0624867292762 2.62 0.0624867176715 2.64 0.0624867176715 2.66 0.0624867176715 2.68 0.0624867176715 2.7 0.0624867176715 2.72 0.062486716683 2.74 0.062486716683 2.76 0.0624867509787 2.78 0.0624868118568 2.8 0.062486849107 2.82 0.062486851237 2.84 0.0624868426993 2.86 0.0624868426993 2.88 0.0624868750039 2.9 0.0624869073085 2.92 0.0624869100164 2.94 0.0624869127244 2.96 0.0624868827627 2.98 0.062486852801 3.0 0.062486881318 3.02 0.0624869098351 3.04 0.0624869371698 3.06 0.0624869263631 3.08 0.0624869263631 3.1 0.0624869263631 3.12 0.0624869263631 3.14 0.0624869322481 3.16 0.062486924922 3.18 0.0624869117109 3.2 0.0624869117109 3.22 0.0624869117109 3.24 0.0624869282596 3.26 0.0624869425904 3.28 0.0624869200721 3.3 0.0624868997717 3.32 0.0624868997717 3.34 0.0624868997717 3.36 0.0624869087825 3.38 0.0624869177934 3.4 0.0624868973873 3.42 0.0624868631593 3.44 0.0624868605657 3.46 0.0624869114832 3.48 0.0624869317507 3.5 0.0624869151131 3.52 0.0624868974913 3.54 0.0624868974913 3.56 0.0624869249945 3.58 0.0624869524978 3.6 0.0624869524978 3.62 0.0624869524978 3.64 0.0624869524978 3.66 0.0624869524978 3.68 0.0624869743613 3.7 0.0624870004031 3.72 0.0624869897927 3.74 0.062486975004 3.76 0.062486975004 3.78 0.062486975004 3.8 0.0624869900123 3.82 0.0624870010876 3.84 0.0624869754854 3.86 0.0624869211004 3.88 0.0624868883846 3.9 0.062486912631 3.92 0.0624869442153 3.94 0.0624869397204 3.96 0.0624869278877 3.98 0.0624869278877 4.0 0.0624869587978 4.02 0.0624870075409 4.04 0.0624870246346 4.06 0.0624870048117 4.08 0.0624869857281 4.1 0.0624869857281 4.12 0.0624870121591 4.14 0.06248703859 4.16 0.062487029815 4.18 0.06248702104 4.2 0.06248702104 4.22 0.06248702104 4.24 0.0624870415347 4.26 0.0624870644923 4.28 0.0624870506262 4.3 0.0624870342972 4.32 0.0624870342972 4.34 0.0624870342972 4.36 0.0624870477341 4.38 0.062487061171 4.4 0.0624870381876 4.42 0.0624870152043 4.44 0.0624870152043 4.46 0.062487061171 4.48 0.062487061171 4.5 0.0624870477341 4.52 0.0624870342972 4.54 0.0624870342972 4.56 0.0624870342972 4.58 0.0624870506262 4.6 0.0624870644923 4.62 0.0624870415347 4.64 0.06248702104 4.66 0.06248702104 4.68 0.06248702104 4.7 0.062487029815 4.72 0.06248703859 4.74 0.0624870121591 4.76 0.0624869857281 4.78 0.0624869857281 4.8 0.0624870238953 4.82 0.0624870253739 4.84 0.0624869897078 4.86 0.0624869278877 4.88 0.0624869278877 4.9 0.0624869278877 4.92 0.0624869397204 4.94 0.0624869442153 4.96 0.062486912631 4.98 0.0624868883846 5.0 0.0624869211004 5.02 0.0624869754854 5.04 0.0624870010876 5.06 0.0624869900123 5.08 0.062486975004 5.1 0.062486975004 5.12 0.062486975004 5.14 0.0624870045813 5.16 0.0624869962249 5.18 0.0624869524978 5.2 0.0624869524978 5.22 0.0624869524978 5.24 0.0624869524978 5.26 0.0624869524978 5.28 0.0624869524978 5.3 0.0624869249945 5.32 0.0624868974913 5.34 0.0624868974913 5.36 0.0624869151131 5.38 0.0624869317507 5.4 0.0624869114832 5.42 0.0624868605657 5.44 0.0624868631593 5.46 0.0624868973873 5.48 0.0624869177934 5.5 0.0624868997717 5.52 0.0624868997717 5.54 0.0624868997717 5.56 0.0624868997717 5.58 0.0624869403725 5.6 0.0624869448083 5.62 0.0624869282596 5.64 0.0624869117109 5.66 0.0624869117109 5.68 0.0624869117109 5.7 0.062486924922 5.72 0.0624869322481 5.74 0.0624869263631 5.76 0.0624869263631 5.78 0.0624869263631 5.8 0.0624869263631 5.82 0.0624869371698 5.84 0.0624869098351 5.86 0.062486852801 5.88 0.062486852801 5.9 0.0624869127244 5.92 0.0624869127244 5.94 0.0624869073085 5.96 0.0624869073085 5.98 0.0624868426993 6.0 0.0624868426993 6.02 0.0624868426993 6.04 0.0612371142123 6.06 0.0599873751427 6.08 0.0587376031454 6.1 0.0574878109004 6.12 0.0562380450147 6.14 0.054988310681 6.16 0.0537385771975 6.18 0.0524888428441 6.2 0.0512391084906 6.22 0.0499893741372 6.24 0.0487396397838 6.26 0.0474899230695 6.28 0.0462401770416 6.3 0.0449904073565 6.32 0.0437406265972 6.34 0.0424908944087 6.36 0.0412411622202 6.38 0.0399914300317 6.4 0.0387416884018 6.42 0.037491929702 6.44 0.0362421816222 6.46 0.0349924512214 6.48 0.0337427365553 6.5 0.0324930009715 6.52 0.0312432481153 6.54 0.0299935181907 6.56 0.0287437882661 6.58 0.0274940583415 6.6 0.0262443341767 6.62 0.024994594139 6.64 0.023744864432 6.66 0.0224951347251 6.68 0.0212454050181 6.7 0.019995686454 6.72 0.018745955313 6.74 0.0174962195027 6.76 0.0162464844719 6.78 0.0149967548971 6.8 0.0137470253224 6.82 0.0124972997339 6.84 0.0112475697605 6.86 0.00999783159157 6.88 0.00874810264263 6.9 0.00749838138192 6.92 0.0062486511516 6.94 0.00499892123694 6.96 0.00374918891684 6.98 0.00249945685188 7.0 0.00124972842594 7.02 0.0 liquidsoap-2.4.2/tests/streams/autocue-plot.0.png000066400000000000000000000321201513273233300217760ustar00rootroot00000000000000PNG  IHDRK pHYs+ IDATxTT;#L\,LX4UcpB!jT$1F]6dOn]UizxrD*%4t(:( Ǵt~/|3R_ykX1J(@0 P `@(mmmK,INN6ͅΝ oMDJt8zeGKKK}ݾǎ$iΜ9> O޹sĉw]VV={HtR JL㏟9sO??23g|g JII2e/SD!+++HvvdpEE]lY@bZ>D2xϞ= O$z7TUD=CRU1QU1 IUH4ѧNgcc7|?":/^ѡ¨mb2!&LtFAf+ƍS D `m{{fwoO{qƍ=o~?d2om2^yO?d2UVVnݺu6mx<ǟy5|嗏=l~%I/3bĈ'?tҀ;cرo_5_>Ϸv777Yf͚5o?g̘1f̘+W̞=G}gZ#_Μ9s\?^~ez|իWvtt 駟>|+WΝ??>6m$I'Nhnn~o~GOZRRu 6DKßa?8(ߖG&Mb yyyp~ξEM63p` CyI/󳲲 |k&$$:thϯ_b2PTIzzzjjjΝ;bĈ:ZO:_G_?q$I5553{#_ool6L&s]?sN8`>SwﮪzG/}/hГ$X,~뭷z11n{꫓'OO#G9pžaCx`EYYم ~466vuu???@~~~]]͛7$I,Kuu石o޼_zgϞ}?ڵk۶mrʀš.\xׯ?~g<@UUձc` %Ir8IIIknn|?hfk%%%O}G}4ذumڴLp嗏=l~_nj1bĉ?'|ʕ+sG}щ'_|۷:OӒ[nذ! nK3UȰӦM?̙3FW uF:jԨ۽{3Ǝ{崴^m)))Ugg{w?M$)~X}˔.ax0Djq݁[[[m6ۀ񭭭FZ֐f |o߾UV_~^zu?gΜE۷oFyM q iԨQSN}xȑ#GBe/ru}-Z/:rƍ'O|KKK 7o=g޾}|ڵm۶]r%eee .<׏?xxرcd`GyuuuSL;yĉoC۷oiiiK.}W8<'x":[m߾}ƌcҤIgϞݼysOޓ'ON6-==lժUӫ`b "1A `@(@0 P `LϳK,Sz/W1-4 ?i듦n !zP-pd*]½h^V7cBfMgJWU K}j19R2k$LIdr҅ˬ `HD `hG>w,Ïe|0බ%K$''sE2r֬Yf;|u9k0<ߡ ?SPPt:].fs8--- hѢgyʕ+gΜIIIQ^ )JˮlnR (O]vIiX֯_`gϞ0#xGDaKoJWACv{NNebbb~~`mЃwh"egW]yDpCCCVVVlܷz뮻5jTNNζmd-`x&IV(ItwttX,#Vyw0s\Q4G:GQ, in2>qYhtW2ް, 34:FgaX`@'xKt:@-Y$99l6;w.Ǐ7}w!J:&:{zz Ng}}l%gϞ6!P~u(-I*};w8qbrr;" o^Cęg+JͮLnhh up\\/\[[ѱ{iӦ C5`]>o#A~_oof#Dpnn 455edd=: "qـ#ć...v:7nܨ...|s皛N*Gـzʘ'ŋO<ŋ+V+--ѣ&i޽ ~g{7n?~|ַ֮]+#GVUU)S0?'󓒒-[WWW(o,1W@M&~phkpUn{GJ҅蟘iH68\cXІx3 ?26e`! `3hԎ яe`=!E?:`=!U#bXO`@H_܊&X7`@8bX7`@xF /q2n/V]ܤt a@*+(52y0lB$IN[BP _&X`@XEHXPP:@ 39+{Rj (a:P K:Pk (3ψ 5DcXx! D itP+]&X`@S7aX`@^A,k B k @B>,k ȎϐFq0 ;.X(:`@^-6׭t  Ȉw[lZCram,xk10 _DE0},B0րx"&tnLLLϯ{ڵk322~Z60gNEٕ `m YYYG].WWWW+++˷n*_xղ[l$q_hM%j|'ׯ__rʕ+>Ykk| r/W_Q,?iJW e`<_RR   :Ngcc7C}ƌlٲB1F+hxɓ'\x}Ŋqqq=zd2ݻ7` :`M#GSLIOOw555dEBC'&j2#<>;%!Un{GJ҅hiHh9, {>CXV? bX``Py WBXV9=_hm `X`` Af0 y f0Wljvt%N>COXV3:`X.eW67)]n$~ElfW^&Ոѱ f$IrzJ`/t&X`K0Չq0X`ga(ܑC``4ܓR`/X`?iJW=)Uml  c3 e`!a 콂 #AjC 3@*0 π20_@V=?UΑ@ ` gy`X=`[CA }bLQZvesU` 7lV]yV ]a0,&IV#+@0hՀ~ e`5 /<X `gPqG<6^a#y@GCX=)GCH_ 4"UX"2`hgOZ0:`e*:` B,+Knkk[dIrrl.,,ĬY&2pD;'JDMsJW:b2Bt|!ݞ_QQؘ9ʆzpE,+Ht744deev\]]] tҊ+l6ʕ+^3ԃe`XVxLcǎo#! o~t:~?*uBmxȄX)8))oZa1bDffݻSRR6nd%}( ~CAt^p!HSSSFFѣ#0`K0W^hB_@heB#R+] ,:u# ,3`ܑC0+ '`0:`9 pp0#_hX:!πGА1 u p[[ے%Kfsaas|ҥ 6s=w} /.ƙghKt8zeGKKK.]ZQQQVVZ^^^]]=cƌ.XxQ'֮]$IjhhX,ׯcM:;;J{`uK[B1!>tnLLLϯc O4I$%_F/Kt744deev\=u#GHaD e(%j|'/rzz ^ @?Ht|a:g…n{ݣGHc@tQ+x$xXְ/[fϞ=f g;nut%N>ePCt{…#MMM\nݺ-[N $IN[B4gPɠ<|05*k{>g҅ELF4$| `X,G"} ;rD$ @'e`H/GOZmUߓRR֩ ` lt|#D{8BGUi2p耍"1&١tZE'D.c8WB` s:p`#8ᐾ5H™gQD @x #uUkqEl,CQZvesUhl/a#WS]ysl +&IV!uhCK`8<n CgHɬ!"uWsLȬi!CC/@ Kě>"XWH_J XCEgOZA:` b8TN @q4!!,<t!!^PCBk/U a/a8xVq /V /ڰ <:`M#U]ܤt@k @ +*W]y%l~vM$׭t!jGk /M , Ak @C`(mmmK,INN6ͅΝ {Ǘ/_>vXtE W ghw)((p:.f90}7JKK~x@'Dpyyӧw9q;vtww1xb":aCK4{RKt744deev\]]]Mtwtt 8clZ}>ppֱ <4=`=a&%%y#^7&&jF88T&}}}aA:ePCt{…#MMMGppDnQ/P,>DpqqlllqFuuuqqq',At/^x%%%/^loo_bE\\\4=j2`qƺ/=P,9rdUUn2eJzzIMM c'd2֮]+IRFFdzG}h7I=ThkX0V@09 @ڽL iHjteeŅe+_V]ܤtC+{mJb#(JͮLD+W nI.D]`0[` |+X/!oEƙg9 b➔B0,:`qX`dܓrXh"p$8#?ˋ_92"}`,6^x*C?Xlф`Y`hc2e/ #y0,!o㞔]t :`"v>@'DG @Hh  LG@ H p–1&١t!+DƘq?*]b@$2ItK!:`ȱp!`D;X И /qbq ~ Ҳ+BpH_:`ȡ(52A db$IrzJxx 60XYv r#5mɒ%fܹsQ|s<=XSPPt:].fs8---Qys6^0 5-v>}zΝ'NLNNޱcGwwwYYYTޜ֘C'x&ItCSU1 IUH*GUH3$444deev\]]]s֩0` pGGb m9 pX=*^" kqIII^7덉ZχZPbeDU1*F!Iez`$$Rq4gΜ <Ԕ1za}}}4| t666_޸qX٪I`OOw݄}3f͚5UUUONMMU4xȑUUUv}ʔ)nh;`K0E(p|ҥ 6s=w} /ޮ©?n;C m6}t{l_]].;s>@yÛZ'N9iH*hb^EhѢgyʕ+gΜIIIʳSZ8v$Is̑{g06mY[[+Iݻ6W!/[<׬Yrno>f̘e˖m^M̫T.+>>~Ϟ= uj5l2INڦ1\aÆ&M$r{$$$ja]6##G?AEvh"CMeffajG*p9r„ ˗/|$I.]b ͶrJ?"_Neeeyy֭[LJhb^E[wuרQrrrm&`V🝊 D r(9Q5v[JOOyڦ>q/zwLKKkmmP̙3)))'O0cS+xUV_ ;ҼJ}9W"ccc<͛%I*++{PV9s$$$x^a3?_q;???..FS9s&&&楗^~Ns 0\rRXG2m+Ȭ,I9dܹIIIrԂ[[[ccc.]*fʗc^xqLLLyyVNOOe*z{{'Mp8DNMsb8yhh^1EN6M=o&IE%cǎ.#r nݺ-[׻zꔔ\17vj|?3g3WS rL}}}u tF\@q0 P `@(@0 P ? //IENDB`liquidsoap-2.4.2/tests/streams/autocue-plot.1.new.txt000066400000000000000000000154651513273233300226370ustar00rootroot000000000000002.06 0.0 2.08 0.0 2.1 0.0 2.12 0.0 2.14 0.0 2.16 0.0 2.18 0.0 2.2 0.0 2.22 0.0 2.24 0.0 2.26 0.0 2.28 0.0 2.3 0.0 2.32 0.0 2.34 0.0 2.36 0.0 2.38 0.0 2.4 0.0 2.42 0.0 2.44 0.0 2.46 0.0 2.48 0.0 2.5 0.0 2.52 0.0 2.54 0.0 2.56 0.0 2.58 0.0 2.6 0.0 2.62 0.0 2.64 0.0 2.66 0.0 2.68 0.0 2.7 0.0 2.72 0.0 2.74 0.0 2.76 0.0 2.78 0.0 2.8 0.0 2.82 0.0 2.84 0.0 2.86 0.0 2.88 0.0 2.9 0.0 2.92 0.0 2.94 0.0 2.96 0.0 2.98 0.0 3.0 0.0 3.02 0.0 3.04 0.0 3.06 0.0 3.08 0.0 3.1 0.0 3.12 0.0 3.14 0.0 3.16 0.0 3.18 0.0 3.2 0.0 3.22 0.0 3.24 0.0 3.26 0.0 3.28 0.0 3.3 0.0 3.32 0.0 3.34 0.0 3.36 0.0 3.38 0.0 3.4 0.0 3.42 0.0 3.44 0.0 3.46 0.0 3.48 0.0 3.5 0.0 3.52 0.0 3.54 0.0 3.56 0.0 3.58 0.0 3.6 0.0 3.62 0.0 3.64 0.0 3.66 0.0 3.68 0.0 3.7 0.0 3.72 0.0 3.74 0.0 3.76 0.0 3.78 0.0 3.8 0.0 3.82 0.0 3.84 0.0 3.86 0.0 3.88 0.0 3.9 0.0 3.92 0.0 3.94 0.0 3.96 0.0 3.98 0.0 4.0 0.0 4.02 0.0 4.04 0.0 4.06 0.0 4.08 0.0 4.1 0.0 4.12 0.0 4.14 0.0 4.16 0.0 4.18 0.0 4.2 0.0 4.22 0.0 4.24 0.0 4.26 0.0 4.28 0.0 4.3 0.0 4.32 0.0 4.34 0.0 4.36 0.0 4.38 0.0 4.4 0.0 4.42 0.0 4.44 0.0 4.46 0.0 4.48 0.0 4.5 0.0 4.52 0.0 4.54 0.0 4.56 0.0 4.58 0.0 4.6 0.0 4.62 0.0 4.64 0.0 4.66 0.0 4.68 0.0 4.7 0.0 4.72 0.0 4.74 0.0 4.76 0.0 4.78 0.0 4.8 0.0 4.82 0.0 4.84 0.0 4.86 0.0 4.88 0.0 4.9 0.0 4.92 0.0 4.94 0.0 4.96 0.0 4.98 0.0 5.0 0.0 5.02 0.0 5.04 0.0 5.06 0.0 5.08 0.0 5.1 0.0 5.12 0.0 5.14 0.0 5.16 0.0 5.18 0.0 5.2 0.0 5.22 0.0 5.24 0.0 5.26 0.0 5.28 0.0 5.3 0.0 5.32 0.0 5.34 0.0 5.36 0.0 5.38 0.0 5.4 0.0 5.42 0.0 5.44 0.0 5.46 0.0 5.48 0.0 5.5 0.0 5.52 0.0 5.54 0.0 5.56 0.0 5.58 0.0 5.6 0.0 5.62 0.0 5.64 0.0 5.66 0.0 5.68 0.0 5.7 0.0 5.72 0.0 5.74 0.0 5.76 0.0 5.78 0.0 5.8 0.0 5.82 0.0 5.84 0.0 5.86 0.0 5.88 0.0 5.9 0.0 5.92 0.0 5.94 0.0 5.96 0.0 5.98 0.0 6.0 0.0 6.02 0.0 6.04 0.0 6.06 0.0 6.08 0.0 6.1 0.000416576377195 6.12 0.00083315275439 6.14 0.00124973020739 6.16 0.00166630753732 6.18 0.00208288388458 6.2 0.0024994586802 6.22 0.0029160351269 6.24 0.0033326115736 6.26 0.0037491880203 6.28 0.00416576468076 6.3 0.00458234023483 6.32 0.00499891536621 6.34 0.00541549164673 6.36 0.00583206792724 6.38 0.00624864703203 6.4 0.00666522350083 6.42 0.00708179742758 6.44 0.00749837105526 6.46 0.00791495145999 6.48 0.00833153544874 6.5 0.0087481164088 6.52 0.00916469244522 6.54 0.00958126313458 6.56 0.00999783532177 6.58 0.0104144168674 6.6 0.0108310017616 6.62 0.0112475814988 6.64 0.0116641545567 6.66 0.0120807276588 6.68 0.0124973044746 6.7 0.0129138866183 6.72 0.0133304711309 6.74 0.0137470504423 6.76 0.0141636233217 6.78 0.0145802004782 6.8 0.014996761236 6.82 0.015413337937 6.84 0.0158299150137 6.86 0.0162464876405 6.88 0.0166630596624 6.9 0.0170796361539 6.92 0.0174962126455 6.94 0.017912789137 6.96 0.0183293656286 6.98 0.0187459381533 7.0 0.019162500012 7.02 0.0195790760992 7.04 0.0199956593045 7.06 0.0204122428064 7.08 0.0208288191902 7.1 0.021245395574 7.12 0.0216619719578 7.14 0.0220785483416 7.16 0.0224951247254 7.18 0.0229117124437 7.2 0.0233282864627 7.22 0.0237448468362 7.24 0.0241614230965 7.26 0.0245779993567 7.28 0.0249945854593 7.3 0.02541117189 7.32 0.0258277484783 7.34 0.026244302347 7.36 0.0266608785747 7.38 0.0270774817818 7.4 0.0274940744896 7.42 0.0279106511607 7.44 0.0283272280437 7.46 0.0287438049267 7.48 0.0291603818097 7.5 0.0295769713511 7.52 0.029993561249 7.54 0.0304101344528 7.56 0.0308266829434 7.58 0.0312432597399 7.6 0.0316598365365 7.62 0.032076413333 7.64 0.0324929916695 7.66 0.0329095616768 7.68 0.0333261140148 7.7 0.0337426743234 7.72 0.0341592505497 7.74 0.0345758344648 7.76 0.0349924156812 7.78 0.0354089891401 7.8 0.0358255395547 7.82 0.036242115596 7.84 0.0366586916373 7.86 0.0370752676787 7.88 0.0374918359301 7.9 0.0379083869636 7.92 0.0383249454122 7.94 0.0387415209058 7.96 0.0391580963994 7.98 0.0395746931332 8.0 0.0399912663359 8.02 0.0404078160338 8.04 0.0408243914568 8.06 0.0412409668798 8.08 0.0416575484735 8.1 0.042074123486 8.12 0.0424906921952 8.14 0.0429072317634 8.16 0.043323806829 8.18 0.0437404263253 8.2 0.044157029371 8.22 0.0445736068858 8.24 0.0449901575961 8.26 0.0454066885533 8.28 0.0458232839654 8.3 0.0462398797463 8.32 0.0466564552395 8.34 0.0470730204152 8.36 0.0474895595016 8.38 0.047906134585 8.4 0.0483227096683 8.42 0.0487393191564 8.44 0.0491559015061 8.46 0.0495724538357 8.48 0.049989029078 8.5 0.0504056043203 8.52 0.0508221795626 8.54 0.0512387797972 8.56 0.0516553491452 8.58 0.0520718887496 8.6 0.0524884638596 8.62 0.0529050389696 8.64 0.0533216140796 8.66 0.053738195711 8.68 0.0541547673934 8.7 0.0545713086456 8.72 0.0549878595853 8.74 0.0554044617838 8.76 0.0558210805128 8.78 0.0562376711033 8.8 0.0566542122225 8.82 0.0570707586045 8.84 0.0574873045674 8.86 0.0579038792382 8.88 0.0583204638726 8.9 0.0587370406312 8.92 0.059153583547 8.94 0.0595701341463 8.96 0.0599867085109 8.98 0.0604033028835 9.0 0.0608198975319 9.02 0.0612364774968 9.04 0.0616530203841 9.06 0.0620695948462 9.08 0.0624861693082 9.1 0.0624861693082 9.12 0.0624861639135 9.14 0.062486135717 9.16 0.0624861129152 9.18 0.0624861129152 9.2 0.0624861129152 9.22 0.0624861592624 9.24 0.0624861710488 9.26 0.0624861710488 9.28 0.0624861145691 9.3 0.0624861447761 9.32 0.0624861749831 9.34 0.0624861749831 9.36 0.0624861705642 9.38 0.0624861299798 9.4 0.0624861299798 9.42 0.0624861299798 9.44 0.0624861808811 9.46 0.0624861989198 9.48 0.0624861785322 9.5 0.0624861259907 9.52 0.0624861259907 9.54 0.0624861259907 9.56 0.0624861259907 9.58 0.0624861279763 9.6 0.0624860927491 9.62 0.0624860927491 9.64 0.0624860927491 9.66 0.0624860927491 9.68 0.0624861168881 9.7 0.0624861097951 9.72 0.0624860786328 9.74 0.0624860545635 9.76 0.0624860545635 9.78 0.0624860545635 9.8 0.0624860629366 9.82 0.0624860333707 9.84 0.0624859749841 9.86 0.0624859749841 9.88 0.0624860043126 9.9 0.0624860486606 9.92 0.0624860636801 9.94 0.0624860636801 9.96 0.0624860636801 9.98 0.0624860636801 10.0 0.0624860636801 10.02 0.0624860783731 10.04 0.0624860783731 10.06 0.0624860510121 10.08 0.0624860236511 10.1 0.0624860236511 10.12 0.0624860236511 10.14 0.0624860229202 10.16 0.0624860031139 10.18 0.062485952494 10.2 0.0624859209496 10.22 0.0624859209496 10.24 0.0624859209496 10.26 0.0624859418273 10.28 0.0624859165453 10.3 0.0624858912633 10.32 0.0624858912633 10.34 0.0624859117633 10.36 0.0624859347346 10.38 0.0624859372061 10.4 0.0624859372061 10.42 0.0624858770244 10.44 0.0624858770244 10.46 0.0624858770244 10.48 0.0624859039202 10.5 0.0624858926407 10.52 0.0624858926407 10.54 0.0624858926407 10.56 0.0624858926407 10.58 0.0624858926407 10.6 0.0624859039376 10.62 0.0624859039376 10.64 0.0624858471899 10.66 0.0624858471899 10.68 0.0624859073677 10.7 0.0624859400375 10.72 0.062485937583 10.74 0.0624859351285 10.76 0.0624859351285 10.78 0.0624860030956 10.8 0.062486053659 10.82 0.0624860712246 10.84 0.0624860503638 10.86 0.0624860503638 10.88 0.0624859818155 10.9 0.0624859818155 10.92 0.0624859818155 10.94 0.0624859825633 10.96 0.0624859654892 10.98 0.0624859476674 11.0 0.0624859476674 11.02 0.0624860023875 liquidsoap-2.4.2/tests/streams/autocue-plot.1.old.txt000066400000000000000000000121051513273233300226100ustar00rootroot000000000000002.06 0.0624864787381 2.08 0.0622345171303 2.1 0.0619825555225 2.12 0.0617306131649 2.14 0.0614786718598 2.16 0.0612266942347 2.18 0.0609747155406 2.2 0.0607227539061 2.22 0.0604707922717 2.24 0.0602188424895 2.26 0.0599668926081 2.28 0.0597149177688 2.3 0.0594629560905 2.32 0.0592109944121 2.34 0.0589590327338 2.36 0.0587071124874 2.38 0.058455158907 2.4 0.0582031698743 2.42 0.0579512081 2.44 0.0576992735334 2.46 0.0574473527291 2.48 0.0571954045942 2.5 0.0569434424595 2.52 0.0566914803247 2.54 0.0564395486555 2.56 0.0561876386761 2.58 0.0559357048146 2.6 0.0556837385889 2.62 0.0554317656763 2.64 0.0551798031051 2.66 0.0549278405338 2.68 0.0546758779626 2.7 0.0544239153913 2.72 0.0541719519631 2.74 0.0539199893958 2.76 0.0536680562841 2.78 0.0534161456195 2.8 0.0531642143612 2.82 0.0529122530636 2.84 0.0526602827587 2.86 0.0524083196833 2.88 0.0521563835718 2.9 0.0519044471998 2.92 0.0516524861023 2.94 0.0514005249829 2.96 0.0511485371001 2.98 0.0508965494589 3.0 0.0506446094553 3.02 0.0503926692218 3.04 0.0501407278097 3.06 0.0498887557254 3.08 0.0496367923127 3.1 0.0493848288999 3.12 0.0491328654871 3.14 0.0488809066779 3.16 0.0486289375401 3.18 0.0483769639052 3.2 0.0481250005515 3.22 0.0478730371978 3.24 0.0476210864559 3.26 0.0473691338992 3.28 0.0471171534415 3.3 0.0468651748288 3.32 0.0466132115233 3.34 0.0463612482177 3.36 0.0461092915613 3.38 0.0458573348322 3.4 0.0456053565609 3.42 0.0453533684221 3.44 0.0451014033922 3.46 0.0448494767904 3.48 0.0445975279027 3.5 0.0443455526609 3.52 0.0440935768588 3.54 0.0438416135624 3.56 0.0435896694518 3.58 0.0433377251194 3.6 0.0430857616013 3.62 0.0428337980831 3.64 0.042581834565 3.66 0.0423298710469 3.68 0.0420779222514 3.7 0.0418259760763 3.72 0.0415740053056 3.74 0.0413220318575 3.76 0.0410700682486 3.78 0.0408181046397 3.8 0.0405661507741 3.82 0.0403141942501 3.84 0.0400622141217 3.86 0.0398102158623 3.88 0.0395582317596 3.9 0.0393062837517 3.92 0.0390543401346 3.94 0.0388023738587 3.96 0.038550403092 3.98 0.0382984396731 4.0 0.0380464950744 4.02 0.0377945610126 4.04 0.0375426075426 4.06 0.0372906319038 4.08 0.037038656863 4.1 0.0367866932109 4.12 0.0365347450124 4.14 0.0362827966006 4.16 0.0360308276756 4.18 0.0357788588213 4.2 0.0355268950268 4.22 0.0352749312323 4.24 0.0350229789247 4.26 0.0347710278223 4.28 0.0345190561927 4.3 0.0342670833243 4.32 0.0340151194763 4.34 0.0337631556283 4.36 0.0335111989865 4.38 0.0332592422362 4.4 0.0330072661394 4.42 0.032755290228 4.44 0.0325033264571 4.46 0.0322513864109 4.48 0.0319994224545 4.5 0.0317474516714 4.52 0.0314954809966 4.54 0.0312435171486 4.56 0.0309915533006 4.58 0.0307395974855 4.6 0.030487640337 4.62 0.0302356652587 4.64 0.0299836915474 4.66 0.0297317277529 4.68 0.0294797639584 4.7 0.0292278042683 4.72 0.0289758445075 4.74 0.0287238684925 4.76 0.0284718926906 4.78 0.0282199290385 4.8 0.0279679824693 4.82 0.0277160193191 4.84 0.0274640398313 4.86 0.0272120492414 4.88 0.0269600858225 4.9 0.0267081224036 4.92 0.0264561639945 4.94 0.0262042024129 4.96 0.0259522258104 4.98 0.0257002524808 5.0 0.0254483025449 5.02 0.0251963610828 5.04 0.0249444076922 5.06 0.0246924396016 5.08 0.0244404700621 5.1 0.0241885064532 5.12 0.0239365428443 5.14 0.0236845904461 5.16 0.0234326235843 5.18 0.0231806436685 5.2 0.0229286801504 5.22 0.0226767166323 5.24 0.0224247531141 5.26 0.022172789596 5.28 0.0219208260778 5.3 0.0216688530223 5.32 0.0214168801886 5.34 0.0211649168922 5.36 0.0209129594935 5.38 0.0206610016272 5.4 0.0204090315732 5.42 0.0201570517954 5.44 0.0199050894741 5.46 0.0196531370815 5.48 0.0194011801213 5.5 0.0191492112204 5.52 0.0188972479148 5.54 0.0186452846093 5.56 0.0183933213038 5.58 0.0181413697856 5.6 0.0178894075862 5.62 0.0176374394281 5.64 0.0173854714034 5.66 0.0171335080498 5.68 0.0168815446961 5.7 0.0166295848583 5.72 0.0163776233715 5.74 0.0161256584163 5.76 0.0158736950035 5.78 0.0156217315908 5.8 0.015369768178 5.82 0.0151178073798 5.84 0.0148658374204 5.86 0.0146138607357 5.88 0.0143618976196 5.9 0.0141099480345 5.92 0.0138579846768 5.94 0.0136060201397 5.96 0.0133540568038 5.98 0.0131020799208 6.0 0.0128501168454 6.02 0.01259815377 6.04 0.0123461923815 6.06 0.0120942288594 6.08 0.0118422586987 6.1 0.0115902844557 6.12 0.0113383155272 6.14 0.0110863529599 6.16 0.010834390564 6.18 0.0105824279928 6.2 0.0103304654215 6.22 0.0100785028502 6.24 0.00982654027899 6.26 0.00957458126401 6.28 0.00932261633903 6.3 0.00907064664445 6.32 0.00881867471717 6.34 0.0085667125824 6.36 0.00831475044762 6.38 0.00806278831284 6.4 0.00781082427456 6.42 0.00755885679476 6.44 0.00730689145609 6.46 0.00705492968174 6.48 0.00680297107969 6.5 0.00655100826039 6.52 0.00629904195874 6.54 0.00604708028039 6.56 0.00579511860204 6.58 0.00554315692369 6.6 0.0052911964066 6.62 0.0050392326893 6.64 0.00478727105484 6.66 0.00453530942037 6.68 0.00428334778591 6.7 0.00403138839799 6.72 0.0037794264744 6.74 0.00352746360942 6.76 0.00327550090159 6.78 0.00302353929378 6.8 0.00277157768596 6.82 0.00251961688183 6.84 0.00226765519365 6.86 0.00201569185314 6.88 0.0017637303715 6.9 0.0015117704399 6.92 0.00125980869992 6.94 0.00100784702358 6.96 0.000755884862267 6.98 0.000503922752395 7.0 0.000251961376198 7.02 0.0 liquidsoap-2.4.2/tests/streams/autocue-plot.1.png000066400000000000000000000340021513273233300220000ustar00rootroot00000000000000PNG  IHDRK pHYs+ IDATxXu{P ȃdY`.20Fjֺ3S{p캊X.q8^xWW\Jd3,2lJh:8$qi03O{Wt: @`d@ Ȁ@02  d˗GDDƌ/\JNOOZUUU6-22l6_|ً_n=*¼y2xK.**:}t^^ޔ)S"""ՕBAVX!bA1i$͚5{|C-xQ)@^doCoo'N @5^z;C*02 Kmkk… F]wo/8{Oo=vW^y?) 97n޼aÆ 6l]?/fΜ9~ƹsΙ3u<.i20xo齇W ]^^>QFl6WTT:--ɓ7o۷ltqA ֶ'--;qDUUURR??̚5<::ʕ+fL&رcdee8p`̙aaa|ɓ{zzAhoo鉊:::}TW4T{^4s&]`^oa2F/Vq}EFFUUUiiiœ'O x}8p`ݺu=ŋ} r`f2͛t QvS Zcǎ5%%%OfRQQUUU%%%))) Nமa7z8^XjjC(l٥K,;0"77駟<⋽ *++oܸ*<ZVV6D9>>ƍ|пzܹ/^w>544,Y׮];v/~>CY,G!@O駟 `6,X>7W'?13$bk…W^xq}ڷo̙3fԩSwiӦ8q'%%%:::77wݺu?X,eeedHtN^ /c@`d@  W^ywy@>\tXI_O0K/tw]@0߱cfϞO?~0nܸ|?l٦Mvn0ܟO%K,^/>gp9gee`>w}}W^ݻwoccOsss,Yrk׮;vEOx衇,ѣG`Qg>{WYYxĉ)S_(..: IKKl:⼼KBW#^4a|/ڸ836s+**_{{ر {KUN@//බy5L}O<_\\ԴvuwK04vp8=bӳ{ӧ ߹sJe(p~nL.a֬Y?5kV[[_I%7a(`*xx %u'&&׻9w\LL̸qF81߆Lgm04GY{< ?:333Vk]]eee^,^` 'N] &}~,F4HpҲlٲȆOKKKA轵wN3---..O?moo߽{w@@={<)c_\{qDhj4(x̘1%666999::|ҤI-.))IOO? ߟbM#@/Ư㿅#T6Ư? 0~!Gۂ#C#3ƯG ae +Ưt!, ӑ%2 h рX^#Z~h@Oh> -hE =рNp/@ ڷϡ g4_rN[N!{Z_> Ұ!v3 c !F4U_A<0 h;`kL&m9t 3i~X2Ưt!, hg'&` b#M0 !6\ T+cKDb1~s aԋ3A T+H ;`HCрAH!,Yz0~!,͠ gHh@hAKiDG.5F4p_Ab쀥st %<k J+Hh@`1 (;`Hh@iH_Ȃh@Q?CC!ڸ836ӻfy̘1'N\j7|#RR +(z?&ptq``m*** ***RRR~X 0Bv8ÞpٳG?2>CwjB# WP8<.)?M d0_41CA̴Zuuuׯeffaƌb-?b VWWWRRRJJfkiiYlYdddCC맥 zg-,,lG(|Qst .P+#q w` Dhk^A``";`(4/P,XMhD#EE h`DCt76> oe9W]rN?c/T xƠhDâ #Uh`X_A`b";`(b4i+(g!^Lcb ` ã G@hƯ ĎfZ.4w#Z`WP Mm=&;;`XTWPub= hg-hhWPmn 9-N=-]B7xQ+A4C_Au`m"7쀡:fƯFE$W/<G*EkC<ƯRq1*Ehh "uF43ԋ $vP/XGhDCca}}p[0! f|44+;LcAC`= c Y$ab DDD#E@hWvbiAЈhACt4:_A3td+];`HGs@E ` Z"C777/_<""h4fddx…aժU┬}LDCCKtZUUe"##f˗}Y|Ǐ_fшq#u>}://oʔ)NO_~ΝhDCH_h|؄aHHHZZZII׋7n_RԲuF434Is?o:;;XٳGuF4+hzۿH&ptk֮]vx@ԚuM004Iv8Þp-[;w< +hn4ϟ߿nnnX]lp'hXP0D'}(HgΝ;3nܸ-noow8+V7 G_&шB|d833jչ_^VV9?O?~ʕ+NgFF4E\hzѐW0xٲeIIIWpBkk5kn5<9b0yOCTUN@/b :njcXbcc[ZZ'Mb]tzѐ m[x oe9W 2@TG\gd0 EC.쀡m0G#_hN;GF4$ #ZPѐg OшdzN,-hЈ4?C^84!Ư:Zi)dOKh_Ȏ0BT_A?`h+ o0 ~R(׀}D4TN)f 2&#tCƂ1~` 43Ƃ1~!/쀡7:5`?b c `"#Ư( aA5hD4O0h+ `"^c }"74^>,CX"a #!,hg ? 1~=i'hDCLV4 9a<C` #!\Sq/Y$$st .!,hF4B_-hH;34;ы+EAvc .l|쀡)LcA` pCC:4Ћ9v@/^ ֭S9MN0# ,(4"EWP Y4uȒKw] 0dDX^;vbiA+h u < N7Mh_`00dF#Zx0vbiA+ h 5l yF1##ƻŝ{}FĉO>Dl30;==jVUUlH|e/l7|ʕ+AAAN?-X?CqJ+??_ZaGGGhh͛}_l6lIq K{Qgo g.4!ñ Ð=: 88X!Фڸ836ӗŗ.]ZfMddڵk^3A#Zc*x0-4kB&p{x!::ѣ2eHC_Ò:ǰgF>ZiiisR'B#Z%uv3)QFs=QQQVbp31шvPCANLLw?sܹqxԨQqqq6J A9hDWP#CA̴Zuuuׯeff͛}R4U+Re˒V^}…5kfe9r`0;,~gҥKgϞ]tiKK/,7hDgCR1c,KlllrrrtttKKKyyIXf9<<|ѢEǏz衇$6hb NIԂ4o_he  Wtd.Ղ/0p CѪ0[#IDAT"0ThU` `c  ՠd_#Y$Ti,b Z`e x0L_^ 2Lc);` 0ԇF0~xGH aX!,`P435jE#Z N,-hm-;$Z0hDˋ3 F#ZF_K ZKhD˅34Ѳ_G:֘SȞ.w!:ψW`hh)1~v0-vtz)kZDN49Œl&YdNs02Xl_AA#ZT4!5LD+_`h`EBy\ W DDw_A'|B#ڿh>5ZlON7e0ۓyF0~;`hsU)J` N/r XWxW ]aKD0 z7+kt  vbiAh/тFHi^Fn1="lO]Ic  ݡ!ƯQ#&= ==, b<4πt: 4n 3q15n$Ӎ ;`p[B4nnn^|yDDhȨnKnz}{/BkkCS+@Rpwwwzzjlf^,^bEIIInnnSSSQQQYY̙3;;;%6}0~H)|Ajkk]7ob;:::zWTTPPPIq(YGW xS8+w ̤wMHHpxx֭!!!N*fxh^_:kkkl[#Z\ZZ*=R4# uvyd29v_n۶-::zѢE~:9$u;aόtqww%KZZZ ƍ{'ь_R:v`2^r~J nF e:oDI Rpbbb}}s sdMvSO>onь_A833jչ_^VVW_}u׮];vxĮ:F4W$]WWWRRRJJfkiiYlYdddCC맮IBO Eq%Hҟ] @)wcƌX,---&Mb{Arss?t_ڥF4WtN^ƀae}pٗ1hƯYt# }dhBC|#+@.00 mg0;`@.00QW#/ʡY$G*b CX:MPU4EXZ;7? 7_JӍ ;`Aɛ`*i#K.e%wnCFUAFXZ4 ^Ӎ ;`*]_Rȋxd0 %4yd0 'J6Zڞnx+`'p1+@"ru:YzѕM3 `@t: 4$Pտ+;LA!P4W$f P>dCѤ/ 0 U H f N\~Wҩ&S$dNsD`޾ېmMPxX_*B2`v<ވ t!,(ECX˗/0555^/>vتU Å D.3?6i>#uwww[֪*i6/_?xΝYY2 5)|Ajkk]7oe]Al!#)CHR>: IKK+))}1R7Hs?o:;;}\ &= zە*p8}\ /` P)p {ƻŀy݈f P)8</_* 7>Ồ( {$ Gp*i  :Eb +W; aϽ h@`@|+p:5`>}|5`üyM0^0\þwi,@`@-\`0ZJBn9vvwyE?Fկ~u5+O>G;¦O;]m>ٳgq/r%9vتU Å yF1##FQ Sk52t+V())mjj****++9sB^T\\l|+WTTT:uJ:x.j577]m?-]gmll䓹sTWW_xqѢEO?o-w]|GsILLlUUU---fYwܙ5 ӭVkUUf4͗/_VHyCTč رBK6l۴L0a߾}Ǐ_r VTXX(w![r VUBNs7n=.cI̙:lhh=zΝ;j׮] l6 ֺ;::BCC7oެ<Fv[n =:u 6M7z耀` ƍcbb~_]-]TByС{GZ ?)K1UVV5u8iҤC[Հ>: IKKSHFD pLҥKk֬\vܵ/**ڳg܅ l63fĉVo.w=v؄{]JJJvkk׮\~~{W_}ŋ[lA9Z@mmm\\xͦo*un۶-::zѢErٳg G:thʔ)rWt˵k֮]vx@Z ܶm[EEE[[[AAAEEEJJr.766Ziiis-[|w;w̞=~#裏:tuEG GOOݻO /̟?_ieFQZn̙`ƶ;v<)w]׿l6lhnn{;s8ÞĈ "+W+?{5{)((ΖA߿?777,,LZw}Mx>͛ͯzuuf)Gbbb}}s(d"EW_ݵk׎;{9k͛7\~ӟ )##Cihh1c܅ܲ`AN8{ɓ&L+6ϟO!::OlTWW* sJEMM̴Zuuuׯeff[Z~IEAʒ^5q̙ UTT]u>[XXh:::=7y䦦&AZZZ\\ܧ~޾{={]~Wqq ׯrJ[[{Am&w]H+F#Ȁ@02  `d@ 8%%h4:u*V2r-DDJ,((p8N355f]p!|8 Š+ :kjj?k׮̔;wz<WWW nݺ(VH p}3g _YjnH ̜9s޼y|A*@DR[ZZdgg;΁Hչ׋]/Q!u&%%^1L^rE2:!!`Az^ upWWWCCo4# LfxvĘL ~e0B/4HIv=J[[b6mZ؃322㎐*Lbuc LPYI} p?^|(OO}Pxڵyyy6lٸqc\\\YY7 N0A.RplllCCj7o^FFFwwwSSSZZZ؃kjj-ZtM7IR>}h ld!_U s]{rWqȀ0oZh4p N8qw_{ӦM>C?O+** s=xP__?=/~ 7 W_}FT1\sMffC=_(뒓 ?9~;>>^B@^֭[x-[lٲG}… O|e˖?G7l2#?N2eѢE#~ׯg}d2AT׻y沲^x<+W_駟.X@Çwvv>S;v=_6lؾ}3<#4|çK8_K_7n炂%K?l6?'xbʔ)>o͚5 ,7p` uuu.±c󳲲 3g|[&$$ݻwk4 7eʔ#SAljjZr5\3Sv?GW^O].SO=5uÇ Ԕ| 111˖- rW^y;h4 s]?s>|hhhjhh뮻/]%f90ɒx^}W_}5p|L7˞ѣyyy7hѢ/f_dRVV{ &'';w.==}hhH5444s񾭿[d6u0^7 Zi<˕@d2M2naΜ9n~~~mmmzzz\\qeݛ6mZ|}}d]bŚ5kv=B9*N ⋏luuuíOl#G psss]]݂ < N3orԩS?޽;%Kx}۷oՃ:(//oooя~:00p{';vʕ+K,oNJJjllsvv+W:4^lَ;>K.ϟQXggիO>w#nkhh8p=0Dܹs?Al6l{:;;s}O :VZaÆxvرpBv79ĉwu]\XX?x~;>>^ _&GիWy5w׿< 8ۼ^͛z{{+**~a?~ʕo~O>|󩧞ڱc__rÆ ۷ogD71Ipv$` 7o|/Ӈd.\8=&&fʕc~m`۲nbA8vc~~~VVeee3g|[nMHHػw3*i20իǎ[r𕔔YG+V8{loYlY`wh0A?:|;!CCC>`UUUCC\y &zg̘xqƌv}ȞzPdXVV{ &'';w.==}hh_̙3{oɒ%A2Y6 iJ.ar0d2tww^xbjjꈑ)))SNx∑&)qݛ6mZ|ڦL_NP|mm+֬Y{#"[ {W=z߷o3g89!!LZ+WN:q݁-Ywm۷oF "/~q:{5kLSO /=z{zzZ[[ʾW\9tĿ}ٲe;v.]o?>坝W>}t__ndpT ;?vb7o^\\ѣG333G\tݻkkk׭[OpyժU6lcǎ loɓZO=zthhh6m%zkCCCcc#=:=^Öj$MF@02  `d@ Ȁ@028‘ oq6n8)w =˕+'~'mo꿣2]BfU "m^U3x3 A8:g=iD qq z"-BW1}>0r@-ڥvVr֏{9(<,sy$.?ľ5Z.&B VTA*..NII1Ndp}}n4oᆟg}}}Q*,*Htl .75ky3gEx_Ivg^ѓW"w*++AhiiOJJz't:NZ]]FLqaדaUo[O\ &M8J޻wjLLLo:]vM:u͚5,3͎O~@+Ns`` G}Wg͚oD@dSj ۛxd2y^G/^|G7m_*-KQ.9<3W3."u{I9 m۶[nINNOrʗ_~Yj'E[rطm jHfv^q111&7'l6AXhO-Z{`*1c"x[B bB+mmmeڴiyF4E,ٵ~o$p?^|(s GG͘1c֬YQ` #V@'//oN{ڵ۷O᭽|YYY|ڶm[LLۃ)#zgN[mrW@; GW Vu޼yMMMiii +((Xr3vYYYRMelqpt2_Iyf:d#b<`cd8|Jaip "-0$Gܡ;*. X;hԋmYt.-hI{jK_/̐Z-h -KD?P5Xj P;XdpXP;X6eEgjGh m]m Kh }[Oޖ-+$}VگdpNT)5]DV2xRיˍr" OhR%.1qzx^7|̗+w4.hbre?7hP=g]V.AGѣg#HXA0Ak 4Veq !UCmY,h&mˢIDjMWmY>@b4aa\j4V+d0*X>@`uvs #UO[y @ 6wA\XBdA4B->@`x2WKym#F4 eyZE FSu[VլU,@4a!"mAzK=[ P][ *jb/ WRߙd,#ob/Q/R ]5aض~0CBMXŶeVr.: f/]!Ki]]])))FԩS >xۮ藯)`<88XPPp8NgjjfpB؃O<_]]]LBSE^ѓW"o %vLIIٹs)/#7۷m!w^՚_WW`DB-}VگҒx%;;t 7f^%%%ΝRze\OT)5K{@FRpoooRRRz].W}#GVUU9rd<|q(/I^w+A^x˿h;}Ͽk"VOe>Ifv^q111&@CI%F$P  -/4aA!eф-Ӆ +(mYKΥݞ-hJ 3i[&h˲W䕦H_(Nr :l˪~W%`-h趬>xtd,ᶬVt)5g.7]L0H:sa:]ͲXT07ϝU:ბC#_ U0kჾ=g]g0k'?4LtAKf'=dA4Toig0ҕ4ڲNr ZN>˝tؘ'7\ uo f$mYtF<+0b FT D Q1i`z@C|o  @`/06nepT7l =C=y)b}[hNzv]dk $ Qg/z eb:Q;/- %ujrc~|Y3 Thz~5ڲhN{hŠw,QB$B[U#!O_mYԋ;|PeP)"e;,j^$U5}!K$, b- ( 2pWWWqqqJJh,,,|8.N[:$b!(\SSs]veffܹ񔗏8͛7,yd" @{Z֜oݺb'?jS[(Ғx%;;t 1f+ტ`0Œ:{{{L&rB裏>7|sTkƘ$8|PXHI^w+A~ꩧ_~Y<> @i`vi >}Ν;˓ë o9e?O,>+mmmeڴi! v\^wݺu+++  ,>ࢢ"xƢP?}ׯ_ )- BHk׮۰aCGGGOOƍʾ ;`EEA[%:cccVy222" ɨw  /qPტCq!H9u0 @0B#E`@a#M.E`Ka!,m/$@#XmYFP47e*AwmYGodUzp1_UȆ7V«(mYDGcrm=y[tmYFc}V_,""1sJrWe \rx\gf.7], 1 !aRthRlO4mH `a#1. 2@x`M- G<? _!w!\XBDg>O_[tx(" x07d0`- `_QprXh0_qі`<0o4F#-?G mYF UeD6ڲ +y8W2$ (=y)rWe -t`@` tf'[i  AsJrWGd0[0KF )f`}B8:\XBH:!8bՇf{ ;8amo꿣2]B c0 JC[q2іNr _/̐,@.܂Fqqrm~q=`#a@`bd0I~"d0=2pWWWqqqJJh,,,xʔ)111 ([cX|Y0:[ZZdgg;΁H7nLMM}GEY{V; u&%%*>^IDAT^1L^r7x!##nfff* j[:^WB 9|(uj_-!:fo2ƈ _s57tSUU̙3_x +1m*`@҇xbL6-\sMVV  o:a+M , r҇\TTp8Z[[/_XTTW˕ HjՇ]2p-r;Ge܅@7` $d Cz|Ch־AYO-QB 2P XXB !GBk"'7Ak_ B` k;0f/&a`LFXbbe2"5 ȅ, Ȃ,<2MlE`@b6E`@J6!d+M  H -0G 4;>J"± @ZswNY*D)יˍr ztq&hZ=-gсqltEfMJc\6NCBhre?7-e"o hDDkBebIi? _!w!  Cxp$۲ZაmY@$`-` Ж!#ڲh)HZj²W\:Yz і͠ AA(mY@H`uA(  V7Ґ@`A( V1B" LJ;!F4Bk]1EV|e.1P2V%PڲЋ7aUj_uGPڲ 4aal>- `T>Xr.nAC8J c04,@*..NII1N o_|3̙3'11q?O{zz_ sR`AAhnnv:6… a ^n]]]]yyŋkjj.\800 ldZBC|Ҫ?))'c/?<ȑ# TUUSy+M* [W{Z999uuua ~gM7x N3zˎ/4-'%+++JvvqH'o kUk+g=A=g]€NHIIIWL&u\ yg322^kVB" :^WB<88ziӦE^2qrtE6n;펉1ƈ _oCPf C'`BP:sssY,1W ~kjjmTɈ7W䕦] ڲ =CA.**r8/_nll,** o+k⋏=X+}[O:B[ON<O^^ނ NgwwڵkSSS;;;?w2WWW3x׮] Q}.w] ڳ% IRcccVy222 ޸/--n2R9Q;,w h˂PH4K.wLC$HӐ <A- ZT~p1_Uɪ]Z,w!8VA[x[V4^@[4VAYO-qΐXX vrq012F+ ^BajÊK -@" !g l_ d0ԂV@h˂*()J܄U5}! іфGlB[N R `ɹ f\*&h˂2 b+M @h˂)Nl)t-VQA[-h}3͎'}- ą~oEǜRsr܅G[& XG.9<3WI_@eA `EoB 2SܝXi(4^4-hA@.!#X~|[!Xf>WRݿK ,3ESV/dӄჀЖ&,=A@ih˂d`9Vr ( mYRJL! ('75іh#eC3pXHC˃ZX,! <ԅ `P#2"eP)CD+=yP5k^ѓW"waԪgUoIP=Nn*VQgO@["AKDERU mY,KF  &_Ivg<X:m=y[h4,&,<ڲAMX]]])))FԩSa>x`IIIrr`rA@hBt:SSSm6ۅ ?_^dIY ϝ- A:kjj?k׮̔;wz@b=Ơ|hR;X~,hR58R>@Fe) /ڲTPޖFpDx @9`uaRD}@i؞9!)P jA( p8|EXx~0C@tK.Dexh@"dp88|ZEXp.dЖ4p @8AQ^e)P;ڲ _@ˎ 4 2XFpX2X.px @", cmG$'7 0eoے"w-l>d+W-R"rwNY*@ d4]rx\gf.7]H, Є5\HW`۶,iɽn8/72q~۾r ]Г*..NII1NWp =-+zTtl .[xѠ9~]233SRRvxżOG[T{Z999uuu" . %DT5mN}%{Wz[%+++JvvWp-K܅h7)))dz.KA\aïx$9J$Xxhˊ lv݁WnwLLTj RJ*N4 g'f Sq+mmmeڴiortQQhmm|rcccQQU rpp~BBݻOe˖Ǐ]P 866jΛ7/##WW `d@ ]U CIj_v`ҹ϶n+#NYYY*D|EO>rm۶-&&fr ?O?-ᐻ7 ^plܸoKOOGJ#&Vn{3gΌͭM0,ˢEBEE3`@02  `d@ Ȁ@02 m<IENDB`liquidsoap-2.4.2/tests/streams/autocue-plot.liq000066400000000000000000000026261513273233300216510ustar00rootroot00000000000000base_dir = path.dirname(argv(0)) tests = [ ( { cue_in=0., cue_out=7., fade_in=2., fade_out_type="lin", fade_out=1., start_next=2. }, {cue_in=0., cue_out=10., fade_in=3., fade_in_type="lin", fade_out=2.} ), ( { cue_in=0., cue_out=7., fade_in=2., fade_out_type="lin", fade_out=5., start_next=6. }, {cue_in=0., cue_out=10., fade_in=3., fade_in_type="lin", fade_out=2.} ), ( { cue_in=0., cue_out=7., fade_in=2., fade_out_type="lin", fade_out=3., start_next=4. }, {cue_in=0., cue_out=10., fade_in=1., fade_in_type="lin", fade_out=2.} ) ] def rec execute(pos) = dir = file.temp_dir("plot") on_cleanup({file.rmdir(dir)}) def on_stop() = file.copy( path.concat(dir, "old.txt"), path.concat(base_dir, "autocue-plot.#{pos}.old.txt.gen") ) file.copy( path.concat(dir, "new.txt"), path.concat(base_dir, "autocue-plot.#{pos}.new.txt.gen") ) if pos == list.length(tests) - 1 then test.pass() else execute(pos + 1) end end let (old_autocue, new_autocue) = list.nth(tests, pos) autocue.plot( dir=dir, png="autocue-plot.#{pos}.png.gen", old_autocue=old_autocue, new_autocue=new_autocue, sync="none", on_stop=on_stop ) end execute(0) liquidsoap-2.4.2/tests/streams/autostart.liq000066400000000000000000000011121513273233300212430ustar00rootroot00000000000000test_no_autostart = ref(true) test_autostart = ref(false) def on_unwanted_autostart() = test_no_autostart := false end def on_wanted_autostart() = test_autostart := true end s = blank() clock.assign_new(sync="none", [s]) o = output.dummy(id="no_autostart", start=false, s) o.on_start(synchronous=true, on_unwanted_autostart) o = output.dummy(id="autostart", start=true, s) o.on_start(synchronous=true, on_wanted_autostart) def on_done() = if test_no_autostart() and test_autostart() then test.pass() else test.fail() end end thread.run(delay=1., on_done) liquidsoap-2.4.2/tests/streams/cross-override-end.liq000066400000000000000000000025301513273233300227340ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, id="a", update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, id="b", update=false, (fun (_) -> [("source", "b"), ("liq_cross_end_duration", "1.1")]), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, id="c", update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) s = crossfade(end_duration=5., start_duration=4., s) track_count = ref(0) def on_metadata(m) = ref.incr(track_count) if track_count() == 1 then if m["source"] != "a" then test.fail() end end if track_count() == 2 then if m["source"] != "b" then test.fail() end if s.start_duration() != 4. then test.fail() end if s.end_duration() != 1.1 then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if s.start_duration() != 4. then test.fail() end if s.end_duration() != 5. then test.fail() end success := true end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync="none", [s]) def on_stop() = if success() then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/cross-override-start.liq000066400000000000000000000025321513273233300233250ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, id="a", update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, id="b", update=false, (fun (_) -> [("source", "b"), ("liq_cross_start_duration", "1.1")]), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, id="c", update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) s = crossfade(start_duration=5., end_duration=4., s) track_count = ref(0) def on_metadata(m) = ref.incr(track_count) if track_count() == 1 then if m["source"] != "a" then test.fail() end end if track_count() == 2 then if m["source"] != "b" then test.fail() end if s.end_duration() != 4. then test.fail() end if s.start_duration() != 1.1 then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if s.end_duration() != 4. then test.fail() end if s.start_duration() != 5. then test.fail() end success := true end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync="none", [s]) def on_stop() = if success() then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/cross-override.liq000066400000000000000000000024761513273233300222010ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, id="a", update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, id="b", update=false, (fun (_) -> [("source", "b"), ("liq_cross_duration", "1.1")]), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, id="c", update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) s = crossfade(duration=5., s) track_count = ref(0) def on_metadata(m) = ref.incr(track_count) if track_count() == 1 then if m["source"] != "a" then test.fail() end end if track_count() == 2 then if m["source"] != "b" then test.fail() end if s.start_duration() != 1.1 then test.fail() end if s.end_duration() != 1.1 then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if s.start_duration() != 5. then test.fail() end if s.end_duration() != 5. then test.fail() end success := true end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync="none", [s]) def on_stop() = if success() then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/cross-persist-end-override.liq000066400000000000000000000025571513273233300244340ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, id="a", update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, id="b", update=false, (fun (_) -> [("source", "b"), ("liq_cross_end_duration", "1.1")]), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, id="c", update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) s = crossfade(persist_override=true, start_duration=5., end_duration=4., s) track_count = ref(0) def on_metadata(m) = ref.incr(track_count) if track_count() == 1 then if m["source"] != "a" then test.fail() end end if track_count() == 2 then if m["source"] != "b" then test.fail() end if s.start_duration() != 5. then test.fail() end if s.end_duration() != 1.1 then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if s.start_duration() != 5. then test.fail() end if s.end_duration() != 1.1 then test.fail() end success := true end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync="none", [s]) def on_stop() = if !success then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/cross-persist-override.liq000066400000000000000000000025261513273233300236640ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, id="a", update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, id="b", update=false, (fun (_) -> [("source", "b"), ("liq_cross_duration", "1.1")]), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, id="c", update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) s = crossfade(persist_override=true, duration=5., s) track_count = ref(0) def on_metadata(m) = ref.incr(track_count) if track_count() == 1 then if m["source"] != "a" then test.fail() end end if track_count() == 2 then if m["source"] != "b" then test.fail() end if s.start_duration() != 1.1 then test.fail() end if s.end_duration() != 1.1 then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if s.start_duration() != 1.1 then test.fail() end if s.end_duration() != 1.1 then test.fail() end success := true end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync="none", [s]) def on_stop() = if !success then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/cross-persist-start-override.liq000066400000000000000000000025611513273233300250160ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, id="a", update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, id="b", update=false, (fun (_) -> [("source", "b"), ("liq_cross_start_duration", "1.1")]), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, id="c", update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) s = crossfade(persist_override=true, start_duration=5., end_duration=4., s) track_count = ref(0) def on_metadata(m) = ref.incr(track_count) if track_count() == 1 then if m["source"] != "a" then test.fail() end end if track_count() == 2 then if m["source"] != "b" then test.fail() end if s.start_duration() != 1.1 then test.fail() end if s.end_duration() != 4. then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if s.start_duration() != 1.1 then test.fail() end if s.end_duration() != 4. then test.fail() end success := true end end s.on_metadata(synchronous=true, on_metadata) clock.assign_new(sync="none", [s]) def on_stop() = if !success then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/cross.liq000066400000000000000000000020701513273233300203520ustar00rootroot00000000000000video.frame.rate.set(24) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "b")]), b ) s = sequence([a, b]) def t(a, b) = log.important("Crossing!") log.important( "Levels: #{a.db_level} / #{b.db_level}" ) log.important( "Metadata: #{a.metadata} / #{b.metadata}" ) sequence([a.source, b.source]) end s = cross(duration=3., width=1., t, s) seen_a = ref(false) seen_b = ref(false) def check_duplicate(m) = if m["title"] == "a" then if seen_a() then test.fail() else seen_a := true end end if m["title"] == "b" then if seen_b() then test.fail() else seen_b := true end end end s.on_metadata(synchronous=true, check_duplicate) clock.assign_new(sync="none", [s]) def on_stop() = if seen_a() and seen_b() then test.pass() else test.fail() end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/crossfade-plot.liq000066400000000000000000000021311513273233300221440ustar00rootroot00000000000000log.level := 5 a = sine(duration=20., 440.) a = metadata.map( insert_missing=true, update=false, fun (_) -> [ ("liq_cross_duration", "5."), ("liq_fade_out_type", "exp"), ("liq_fade_out_curve", "10"), ("liq_fade_out_delay", "2"), ("liq_fade_out", "3.") ], a ) b = sine(duration=20., 880.) b = metadata.map( insert_missing=true, update=false, fun (_) -> [ ("liq_fade_out_type", "lin"), ("liq_fade_in_delay", "2"), ("liq_fade_in", "2.") ], b ) b = once(b) s = sequence([a, b]) dir = file.temp_dir("plot") on_cleanup({file.rmdir(dir)}) s = cross.plot(dir=dir, png="crossfade-plot.png.gen", s) clock.assign_new(sync='none', [s]) base_dir = path.dirname(argv(0)) def on_stop() = file.copy( path.concat(dir, "old.txt"), path.concat(base_dir, "crossfade-plot.old.txt.gen") ) file.copy( path.concat(dir, "new.txt"), path.concat(base_dir, "crossfade-plot.new.txt.gen") ) test.pass() end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/crossfade-plot.new.txt000066400000000000000000000073221513273233300227750ustar00rootroot000000000000000.0 0.0 0.02 0.0 0.04 0.0 0.06 0.0 0.08 0.0 0.1 0.0 0.12 0.0 0.14 0.0 0.16 0.0 0.18 0.0 0.2 0.0 0.22 0.0 0.24 0.0 0.26 0.0 0.28 0.0 0.3 0.0 0.32 0.0 0.34 0.0 0.36 0.0 0.38 0.0 0.4 0.0 0.42 0.0 0.44 0.0 0.46 0.0 0.48 0.0 0.5 0.0 0.52 0.0 0.54 0.0 0.56 0.0 0.58 0.0 0.6 0.0 0.62 0.0 0.64 0.0 0.66 0.0 0.68 0.0 0.7 0.0 0.72 0.0 0.74 0.0 0.76 0.0 0.78 0.0 0.8 0.0 0.82 0.0 0.84 0.0 0.86 0.0 0.88 0.0 0.9 0.0 0.92 0.0 0.94 0.0 0.96 0.0 0.98 0.0 1.0 0.0 1.02 0.0 1.04 0.0 1.06 0.0 1.08 0.0 1.1 0.0 1.12 0.0 1.14 0.0 1.16 0.0 1.18 0.0 1.2 0.0 1.22 0.0 1.24 0.0 1.26 0.0 1.28 0.0 1.3 0.0 1.32 0.0 1.34 0.0 1.36 0.0 1.38 0.0 1.4 0.0 1.42 0.0 1.44 0.0 1.46 0.0 1.48 0.0 1.5 0.0 1.52 0.0 1.54 0.0 1.56 0.0 1.58 0.0 1.6 0.0 1.62 0.0 1.64 0.0 1.66 0.0 1.68 0.0 1.7 0.0 1.72 0.0 1.74 0.0 1.76 0.0 1.78 0.0 1.8 0.0 1.82 0.0 1.84 0.0 1.86 0.0 1.88 0.0 1.9 0.0 1.92 0.0 1.94 0.0 1.96 0.0 1.98 0.0 2.0 0.0 2.02 0.0 2.04 0.00708973694512 2.06 0.0141581616199 2.08 0.0211719473154 2.1 0.0282181599771 2.12 0.0353730139025 2.14 0.0425384216707 2.16 0.0495535656695 2.18 0.0564585261744 2.2 0.0634908599484 2.22 0.070746027805 2.24 0.0779871063963 2.26 0.0849489697191 2.28 0.0917451050335 2.3 0.0987635599198 2.32 0.106119041707 2.34 0.113435791122 2.36 0.120344373769 2.38 0.127031683892 2.4 0.134036259891 2.42 0.14149205561 2.44 0.148884475847 2.46 0.155739777818 2.48 0.162318262751 2.5 0.169308959862 2.52 0.176865069512 2.54 0.184333160573 2.56 0.191135181868 2.58 0.197604841611 2.6 0.204581659834 2.62 0.212238083415 2.64 0.219781845299 2.66 0.226530585918 2.68 0.23289142047 2.7 0.239854359805 2.72 0.247611097317 2.74 0.255230530024 2.76 0.261925989967 2.78 0.268177999329 2.8 0.275127059777 2.82 0.28298411122 2.84 0.29067921475 2.86 0.297321394017 2.88 0.303464578188 2.9 0.310399759748 2.92 0.318357125122 2.94 0.326127899475 2.96 0.332716798067 2.98 0.338751157047 3.0 0.345672459719 3.02 0.353730139025 3.04 0.361576584201 3.06 0.368112202116 3.08 0.374037735906 3.1 0.380945159691 3.12 0.389103152927 3.14 0.397025268927 3.16 0.403507606166 3.18 0.409324314765 3.2 0.416217859662 3.22 0.42447616683 3.24 0.432473953652 3.26 0.438903010215 3.28 0.444610893624 3.3 0.451490559633 3.32 0.459849180732 3.34 0.467922638378 3.36 0.474298414265 3.38 0.479897472483 3.4 0.486763259605 3.42 0.495222194635 3.44 0.503371323103 3.46 0.509693818315 3.48 0.515184051342 3.5 0.522035959576 3.52 0.530595208537 3.54 0.538820007829 3.56 0.545089222364 3.58 0.550470630201 3.6 0.557308659547 3.62 0.56596822244 3.64 0.574268692555 3.66 0.580484626414 3.68 0.58575720906 3.7 0.592581359519 3.72 0.601341236342 3.74 0.60971737728 3.76 0.615880030464 3.78 0.621043787919 3.8 0.62785405949 3.82 0.636714250245 3.84 0.645166062006 3.86 0.651275434513 3.88 0.656330366778 3.9 0.663126759461 3.92 0.672087264147 3.94 0.680614746731 3.96 0.686670838563 3.98 0.691616945637 4.0 0.698399459433 4.02 0.70746027805 4.04 0.708973694512 4.06 0.707908080993 4.08 0.70573157718 4.1 0.705453999427 4.12 0.70746027805 4.14 0.708973694512 4.16 0.707908080993 4.18 0.70573157718 4.2 0.705453999427 4.22 0.70746027805 4.24 0.708973694512 4.26 0.707908080993 4.28 0.70573157718 4.3 0.705453999427 4.32 0.70746027805 4.34 0.708973694512 4.36 0.707908080993 4.38 0.70573157718 4.4 0.705453999427 4.42 0.70746027805 4.44 0.708973694512 4.46 0.707908080993 4.48 0.70573157718 4.5 0.705453999427 4.52 0.70746027805 4.54 0.708973694512 4.56 0.707908080993 4.58 0.70573157718 4.6 0.705453999427 4.62 0.70746027805 4.64 0.708973694512 4.66 0.707908080993 4.68 0.70573157718 4.7 0.705453999427 4.72 0.70746027805 4.74 0.708973694512 4.76 0.707908080993 4.78 0.70573157718 4.8 0.705453999427 4.82 0.70746027805 4.84 0.708973694512 4.86 0.707908080993 4.88 0.70573157718 4.9 0.705453999427 4.92 0.70746027805 4.94 0.708973694512 4.96 0.707908080993 liquidsoap-2.4.2/tests/streams/crossfade-plot.old.txt000066400000000000000000000120441513273233300227570ustar00rootroot000000000000000.0 0.713154285204 0.02 0.701950441315 0.04 0.709342595328 0.06 0.708619360241 0.08 0.702401756959 0.1 0.713154285204 0.12 0.701950441315 0.14 0.709342595328 0.16 0.708619360241 0.18 0.702401756959 0.2 0.713154285204 0.22 0.701950441315 0.24 0.709342595328 0.26 0.708619360241 0.28 0.702401756959 0.3 0.713154285204 0.32 0.701950441315 0.34 0.709342595328 0.36 0.708619360241 0.38 0.702401756959 0.4 0.713154285204 0.42 0.701950441315 0.44 0.709342595328 0.46 0.708619360241 0.48 0.702401756959 0.5 0.713154285204 0.52 0.701950441315 0.54 0.709342595328 0.56 0.708619360241 0.58 0.702401756959 0.6 0.713154285204 0.62 0.701950441315 0.64 0.709342595328 0.66 0.708619360241 0.68 0.702401756959 0.7 0.713154285204 0.72 0.701950441315 0.74 0.709342595328 0.76 0.708619360241 0.78 0.702401756959 0.8 0.713154285204 0.82 0.701950441315 0.84 0.709342595328 0.86 0.708619360241 0.88 0.702401756959 0.9 0.713154285204 0.92 0.701950441315 0.94 0.709342595328 0.96 0.708619360241 0.98 0.702401756959 1.0 0.713154285204 1.02 0.701950441315 1.04 0.709342595328 1.06 0.708619360241 1.08 0.702401756959 1.1 0.713154285204 1.12 0.701950441315 1.14 0.709342595328 1.16 0.708619360241 1.18 0.702401756959 1.2 0.713154285204 1.22 0.701950441315 1.24 0.709342595328 1.26 0.708619360241 1.28 0.702401756959 1.3 0.713154285204 1.32 0.701950441315 1.34 0.709342595328 1.36 0.708619360241 1.38 0.702401756959 1.4 0.713154285204 1.42 0.701950441315 1.44 0.709342595328 1.46 0.708619360241 1.48 0.702401756959 1.5 0.713154285204 1.52 0.701950441315 1.54 0.709342595328 1.56 0.708619360241 1.58 0.702401756959 1.6 0.713154285204 1.62 0.701950441315 1.64 0.709342595328 1.66 0.708619360241 1.68 0.702401756959 1.7 0.713154285204 1.72 0.701950441315 1.74 0.709342595328 1.76 0.708619360241 1.78 0.702401756959 1.8 0.713154285204 1.82 0.701950441315 1.84 0.709342595328 1.86 0.708619360241 1.88 0.702401756959 1.9 0.713154285204 1.92 0.701950441315 1.94 0.709342595328 1.96 0.708619360241 1.98 0.657099693223 2.0 0.624129561055 2.02 0.5747026364 2.04 0.54329805696 2.06 0.50773883893 2.08 0.470823464422 2.1 0.447199194024 2.12 0.411783399118 2.14 0.389280939516 2.16 0.363801655726 2.18 0.33735071407 2.2 0.320423046256 2.22 0.29504666451 2.24 0.278922852503 2.26 0.260666157205 2.28 0.241713309335 2.3 0.229583966945 2.32 0.21140113907 2.34 0.199847827784 2.36 0.186766343283 2.38 0.17318611438 2.4 0.164494922396 2.42 0.151466501102 2.44 0.143188096687 2.46 0.133814812762 2.48 0.124084233569 2.5 0.117856584001 2.52 0.10852145641 2.54 0.102589625308 2.56 0.0958733832008 2.58 0.0889011985603 2.6 0.0844387542676 2.62 0.0777499872548 2.64 0.0734995494039 2.66 0.0686871609525 2.68 0.0636914523752 2.7 0.0604938329323 2.72 0.0557012661325 2.74 0.0526555991913 2.76 0.0492073814953 2.78 0.045627879902 2.8 0.0433365470664 2.82 0.0399026670903 2.84 0.037720256228 2.86 0.0352495095912 2.88 0.0326847646442 2.9 0.031042814539 2.92 0.0285824762133 2.94 0.0270186153606 2.96 0.0252482573429 2.98 0.0234106173055 3.0 0.0222339702593 3.02 0.0204712050083 3.04 0.0193505546046 3.06 0.018082046962 3.08 0.0167654003585 3.1 0.0159221575229 3.12 0.0146592252213 3.14 0.0138561489815 3.16 0.012947232846 3.18 0.0120038943505 3.2 0.0113995460708 3.22 0.0104947597275 3.24 0.00991923531962 3.26 0.0092679777579 3.28 0.00859212621019 3.3 0.00815895335983 3.32 0.00751078980944 3.34 0.00709831341383 3.36 0.00663167628768 3.38 0.00614748751327 3.4 0.00583696721759 3.42 0.00537268193332 3.44 0.00507703454365 3.46 0.00474268374015 3.48 0.00439582734388 3.5 0.00417319144396 3.52 0.00384066069469 3.54 0.00362872494577 3.56 0.00338916143441 3.58 0.00314070798703 3.6 0.00298104400838 3.62 0.00274291950875 3.64 0.00259096577148 3.66 0.00241932032279 3.68 0.00224137566934 3.7 0.00212683304397 3.72 0.00195635357812 3.74 0.00184737883026 3.76 0.00172439880003 3.78 0.0015969759051 3.8 0.00151476414213 3.82 0.00139275446099 3.84 0.00131457550475 3.86 0.00122646577058 3.88 0.0011352432975 3.9 0.00107619760973 3.92 0.000988918046954 3.94 0.000932805239642 3.96 0.000869681164407 3.98 0.000804397427041 4.0 0.000761950957501 4.02 0.000699556611949 4.04 0.000659254891246 4.06 0.000614033822956 4.08 0.000567336001882 4.1 0.000536783391934 4.12 0.000492220083695 4.14 0.000463247501602 4.16 0.000430854498341 4.18 0.000397474068227 4.2 0.00037544378108 4.22 0.000343656969375 4.24 0.000322802069818 4.26 0.000299600776805 4.28 0.000275762674289 4.3 0.000259838898267 4.32 0.000237206846368 4.34 0.000222168520518 4.36 0.000205553375695 4.38 0.000188552649678 4.4 0.000177004380076 4.42 0.00016093200022 4.44 0.00015006143155 4.46 0.000138165468121 4.48 0.000126063936449 4.5 0.000117650854196 4.52 0.000106278684745 4.54 9.83944445905e-05 4.56 8.98799223907e-05 4.58 8.12888168624e-05 4.6 7.51221945105e-05 4.62 6.7117872981e-05 4.64 6.13734307108e-05 4.66 5.52818170267e-05 4.68 4.92060417442e-05 4.7 4.46490782489e-05 4.72 3.90579252044e-05 4.74 3.48467151169e-05 4.76 3.04911912469e-05 4.78 2.62177288418e-05 4.8 2.28141363166e-05 4.82 1.89520940494e-05 4.84 1.58394928271e-05 4.86 1.27279316669e-05 4.88 9.74588287003e-06 4.9 7.16871675763e-06 4.92 4.54563650176e-06 4.94 2.22022292949e-06 4.96 0.0 liquidsoap-2.4.2/tests/streams/crossfade-plot.png000066400000000000000000000363031513273233300221530ustar00rootroot00000000000000PNG  IHDRK pHYs+ IDATxTT;( 0ZE4R&&`]CqUjk5oJ66ic"]&Q$֚+Q%8DD嗈(c3c={w>3kߟ,< `d@ Ȁ@02 njjJKK LNNr0رc'O|=8p@XIJJ2Lf9,,h4^|y~ҥK###+++UV=ȂpgAׯ$c7n|W~gw}M???래"a&B;B`M_I տx`Ŋ ;w|~m7-[fg?Wgg<`ߓ́>uK9ItlllEEsEFF$666 _<:#$W$W$W$WEtJJdnvtt :8""~OeeePP3 kťյdddfffZ?-**t]mٲd2=SW^vګZXXe&Bǧ`0GDD477:855~_" M= ?j P21!ېiӦ] @5I&]\@0u¶eʕ3g||?I?OtN{筛_|N駟޹sg[[Nt3gδpՇ~800𩧞$u̸qf͚ŋ {Go[ONN>};3~_c=S  ׷ugy~˖-[l㏭={vѢE'N,//ohhXt<`yȒMoo߱^}{N[dԾ͛7gffZ/G1|ACCXg ,$۶m۳g?WUzzo}vמ4|CӶ@Q1l{̙awRRҷ^ۧ +V;%%h4Z3x{{߼ybYf>k(((pPFeeeĨ(mԩe֭~"t0zIIIM+V%I)--]|ql'NX'ܺu>kkk۶mۄ ?.IRiiibb5xyy-]~ϭ[^z饹st:{`KGǏ_j}az{{{?CΗ0z!!!Id _~_Mۓ_|x===C[dgg>555]]]ϟ$&&uvv&$$H4o޼ѝǎs|Kٳٳׯ_7V__z߸qѣ?½[\\|2}`pO$IF1$$dŊYK~700кǚV`\2==G<={,Zh4Ι3r}'O]`ADDDvvO<1G.\X\\\RRB6_Z,Ȁ@0_ҶyP>\XIC{&矟4iUe`d@=z~ϟ??[pg}6԰~zΝmmm:)yW>ÁO={q͚5?V#<2yd+zwƏ_rT'#Ŏ=t|;_~eyyyggEG}zG}w /6o/͛7gff?яc?˗۞gY_stm۶ٳg~_۷owsX<]Taqqqf͛7[,;wN8h\hmk}XmYPP0T1$Y7z{{̜:ubٺuaaV :`p[n-_ܶ'44߁?e˖6ov0#zyy-]Ծ^ziܹ:.**J/WZ5K{{{{˵s>`p)S2eJEEE---_#=^?~m333_hQppKf̘kwԩC͛74toe?H,ˆlK 뽼w666:a„~#zky'x?u󫯾ʕ+[lٚ5k<8T܏*NW?~}۞k׮|PVV{Yf dɒ͘1cݺu/+W?gϞE9sTVV޽ӻɓ ,~'… KJJ`żKIbBȀ@02  `d@ @t755&''WUU 5rʕþ}Pӓd2fsXXh|{=#GHl2'o>Oxt_wf3A }N3pnn3g;k֬МlJn:'GqC ȿP83t~83>+κ;A }N3E`0X7 ^y_:tPBBmg%)M-lȶ}dِ{+syKҲjƒ~LIw>P} :y8,oCjmAϚrϋS;@oʿvSu擖XI$@˖-˫sss%Iڻw$)dޅ3νZ,NoOm0} t|Cn4<" H] GHbqm{__y󦟟Pu#G|w@:~SE03FBԎFE>1!tVlllE߭=w\dd$wߍp>}]:>VU$IeY}j c7z 8%%d2X7;::JJJRRR|ӟ ׀֫rW kťյdddff~s GQQN;pW/IE  (jdYOqq`hnn.-- w0 %UwAg/T-Ix*;vbB!A 7S*,p C^)X ې V_dY]50k$/=rJC*E.e`WŠcA.BhvjG h p:`QGjE.5`SЀCֹ!rW1!0ʹ67cU`L`e`tK.cB.4Ġa.",Q+ \xϋS.@ LxVʲipah\iF@p}ufe5"5\iM*MHc`f/vp=^=,\)hIꋬ:`L>k 0\gb p%:`ZE Zat+1 WF  W6p1f*$uGB\U9fL )))&ƺQRR2+VHtI۞O>dʔ)grM0h:%IyBxڵqqquuu---Ot:݁yӧO_~_D /$S\\l0#""KKKÇ_PP|)S۷D  ! ~0< [}qw]ԎFEb ކ׀1F= n5` op;C :`-Xx[0~H_Cn",+Xra1"CnA S;/7]EX[o0$ᣎc+Ki `~1(wb*wwpȉ7]T#'1p :`E Ǹ Wpp{2^8a{ks3&]܅c5$ic܈s0܈[=](5?}܅xFppKnD!"5,q~# Ak/Fk{kW1jt{]l /Ƃp/5cA SZ;1F0^VU\1I*b0^>%IsKBJM$Is'Nhmmݿ',Xe`TT׫Jp7w:tanp;''Չn"4CBBJX{{^wy3ʜ?[  uGR&!*:x9X1NhEz_[rG?^5]Q%V*V_ȋ)hBKrSB</dG+AYV#A^0 eG %0 WthLAˋ A hthLAˈA `PCgt 8P:`@:])Z}19oU KdH LA2A U"P:`@t D2.wOB`@LAD e0 E#(0 X_(0 @$e܇E ȃ&X_( ȃP2 ݭ`Ʌf]0$թ. ȃ)hiXP8SR&t0 :`7a3Bt755&''WUU9+Wt 6<@MX ===III&l6˗/;V~~iIDAT}}} 1?EXF gΜٻwYBCCsrr|eͻv"=Uh /TDh f@@@bbbAAlݺ522'?V] (Z}19oUQQQ{fsWWנ?7xCHuht.dʿ.I!5XBg ֠ =zm7nlڴiӦMU IߪF/TGh f۶m_]\ ]WP## ioe^^^z۷/8xJ:o2܊)hWᝃ;Ch[[[kܹs~~~F[NW7oܷoN+**rX;.W#ǎ.!>5pJJdnvtt }&N~zŒ,f;n=J kťյdddfffZ?-**tY /:1z `b\ZZ.@QX=FP/&/t=mWSZrJv4Z, "BWA)`3T2#xMrW>\rzUWv0 )p x,w*_h0 3G@ Ȍ)葢62 /IR˭N Q_h ȏ&y,~f *|fx3H_h  ȏ)aSЎ mHvIt" FU(/aN`ȗ7ZBZE@<(_h׀E _h0LAD mx _h0p?<:`@&ItB '  mV\bN`zW!` Z')hP :f]  (Ȃɳ>i yKdH @PSf}L OC +x P#?n2] x< (Ȍ`I;]PL0,3xe%V-w `@Y<*Oh8  w! x,s+pG:VCs?63^ }`@q?^ wg@t755&''WUU 57\pa``izO?Td\<f3 4{zzL&Syyl 3/_tp^^lo~sʕ'NL0!!!ԩS" d$IgX^~}uuuLL$IӧO߸q+2woܸ7o_z:п pC~Gd q*y LL 5}%I HLL,((p^^^,P{c6`_JhWWWGEE6]]]xŌM6@@)V- X ֠ =zm:.""ȑ#5k}}}CoodJLL|>#U(h)w!.F  v=^^^zƍwm߿ԩYYYNKgg2Zf]08!4ccckkk;w.22o7.**l6;y,Q NcwKdH `pSChLfGGGIIIJJ3߽uVeee5\ik׮KOOkii̴~ZTT8`я~/^׬Y /,:`Hh A??~h Yjĉ^ƛ|'F$,h3e3OM1WP > ,2FuwU9s3BP"P#?n2]ŘT\1I*%",IR}u ]$M7}"ESuL 8@\ ESo1o zo.j#:`@8R/w#C n^،* `^; Nup +͖ @`@8}q@1@Bct*>?ֱ0jH_I0楿 (楿H (FT@0  b3g`t`@; M  2;`^8 2;જks3&]J0 쀛NwI4y܅D ƂTCi\ƂTCQ#PT\8?FTC90/0v0&w]/0&/T[\D'?B*#oB*#,4k"fB0>܏D  ,O\G|[]哖XG4GpL@#&}7!U,܇TI,4 !Un%:BCCy۷y~OӖJ&Jh$%%Lrf4/_| AAA{z}___[[/)F@e|ת]< Ih]6...==%##733iQQN;pu}vEb =(sc)}$4}|| C|||DDDsssiiixx]lyzJd*dLK:6.`(QlĄoC`WY{GZ0Yhn=dAѣ[E Ȃ4bt+@.0?҈[FX@ڛ / #Ў6`@;Ft hLj hJ r20_@v0)N0/ ;Дy3$I:R`L +U8nOh8  $8:u0 ( hPˡ3^XY30A0+E! X|4hsi!mJCdh伙&[KdH "GgXt:m]8SlH3IKܵj"&DT0`{+Ӻm\FDL0 hٲ3rW`INhXzٲ![Z50&[|A!`@lO e"m=yÙ74߃'ib%aVW_[,",1{R#7" /TRA A:`@i`@Sv c׊Yh@Q`@;000_+ˆl[b6>`x{ǸO_>PPd':BCC >z u:]]]"EJ0B'))d2氰0xAǻvJHHd 3 + 3gݻw֬Y999ك/Yp Y$";r&.,,4 111̀Ă51׊%4DGG殮.e0*oIc︪#"4[[['6e0*5.Ix< 7{kE Ehioo.?ΎK_>+vկ*%>5plllmmsEFFX;.q@^j^OqB8%%d2X7;::JJJRRRDk_+`@<vڸ ___s6t:݁Dk_+`@<S\\l0#""KKK:uVI"##u:C=$`@iZ LuЪ=I:;zoĄ6&hH.ǫ *|6BEޒ4P81!up_{< F"0G@kD4<0o7SX @Jgʿ.I!5XT-Sb Wb"p+P4w;mvtP)U6*:`~cA .AͩZ:|hh SЀPګN׆>ېF(ϣ{+35qF Fkc}gC,0ԋ=jf^P ̺;$ FUЀƩ},-IV3r(6;E:`_{Y8Š4Ԃ)#|; C-#|ڳ=$GCr:=+S0jQQQ{ *PIYwY4iB x=zZe5*Pd/> %NPn P2_a(7ӮITMnK;uN瞺\F*Se@{+Wt6l aQ Y'))d2氰0xeSYYi&a|ǻvJHHtclQ'pݺuٍ%%%-_芑7W\9qĄ N:%# ?~58_BN2,ڲo>I7o zg_̑#Gv-IlŹbrwyM'$IڿQ -[]2\1:SLٳgĉׯ_brE`0X7 (bl߾= 9gIf3Ō܅HŠql֭?O.DVh-DGGfYGQh6mچ .]Fi'_OvxŌM6[(~ᇹo,Gg( +CkdG)__{ĉ?qĂ 但. <---=\DDĪUd,bu~NqȑC͚5K2FTBN76mڴiӦy >Q Zin֬Ycg׮]$544 //nn܏';11׷TR'd2]_s8b:6m2eJ[[uSkc)F8$$~u݄^WW1wy3V~ׯ__ZZo߾ŋUX7nۿԩS*crϟ?,(FsܹH???/"=ӹc=&w-*fƍc*fPmmm}}}֭z}t"O.F)))&ƺQRRbϟLK/޽{ΝO>x֭~2bd9Mrf}EV2b_{pǼ,X`6׮]V__bKR}]ƍ}]|#GDEE͘1Q-jw^I233wPQ LOOꫯ:;;+**VX1a„'N.ÙbyQ}6Q Yb\rIII~L9sΘ1cƍ,-_\|%;͛7dbyM&ӦM̙3aebyQQ+,CgXF815`T@02  `d@ Ȁ@02 c3< HIENDB`liquidsoap-2.4.2/tests/streams/crossfade.liq000066400000000000000000000013571513273233300212010ustar00rootroot00000000000000video.frame.rate.set(24) a = sine(duration=20., 440.) a = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "a")]), a ) b = sine(duration=20., 880.) b = metadata.map( insert_missing=true, update=false, (fun (_) -> [("title", "b")]), b ) s = sequence([a, b]) s = crossfade(s) dup_a = ref(0) dup_b = ref(0) def check_duplicate(m) = if m["title"] == "a" then dup_a := dup_a() + 1 end if m["title"] == "b" then dup_b := dup_b() + 1 end end s.on_metadata(synchronous=true, check_duplicate) clock.assign_new(sync="none", [s]) def on_stop() = if dup_a() == 1 and dup_b() == 1 then test.pass() else () end end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/crossfade2.liq000066400000000000000000000003361513273233300212570ustar00rootroot00000000000000a = sine(duration=5., amplitude=0.01, 440.) b = sine(amplitude=0.1, 880.) s = sequence([a, b]) s = crossfade(duration=3., s) output.dummy(fallible=true, s) def on_done() = test.pass() end thread.run(delay=8., on_done) liquidsoap-2.4.2/tests/streams/ctype1.liq000066400000000000000000000006041513273233300204270ustar00rootroot00000000000000# Testing tricky situation wrt channels. Because we have a function, the sineF # source only knows that it is of type ('a,0,0). The fact that it has to be mono # (because of the add) has to be determined by unification at execution time. log.level.set(4) def f() = sine(id="sineF", 880.) end s = mean(sine(id="sine")) s = add([s, f()]) output.dummy(s) thread.run(delay=1., test.pass) liquidsoap-2.4.2/tests/streams/ctype2.liq000066400000000000000000000007471513273233300204400ustar00rootroot00000000000000# Testing tricky situation wrt channels. Because we have a function, the sineF # source only knows that it is of type ('a,0,0). The fact that it has to be mono # (because of the add) has to be determined by unification at execution time. log.level.set(4) first = ref(true) def f() = if first() then first := false sine(id="sineF", 880.) else null end end s = mean(sine(id="sine")) s = add([s, source.dynamic(f)]) output.dummy(s) thread.run(delay=1., test.pass) liquidsoap-2.4.2/tests/streams/cue-cut.liq000066400000000000000000000004711513273233300205710ustar00rootroot00000000000000s = playlist(prefix="annotate:liq_cue_in=1.,liq_cue_out=3.:", "playlist") is_first = ref(true) def on_frame() = if is_first() then if source.duration(s) == 2. then test.pass() else test.fail() end end is_first := false end s.on_frame(synchronous=true, on_frame) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/dtmf.liq000066400000000000000000000010671513273233300201600ustar00rootroot00000000000000log.level.set(4) keys = "1234567890*#ABCD" last_key = ref("") detected = ref("") def f(k) = # Skip test for now. # test.skip() print( "Detected key #{k}" ) if last_key() != k then detected := !detected ^ k end last_key := k if k == "D" then if !detected == keys then test.pass() else test.fail() end end end s = amplify(0.5, dtmf(duration=1., keys)) # s = add([s, amplify(0.5, noise())]) # Removed to make the test deterministic s = dtmf.detect(debug=false, s, f) clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/dtmf_pcm_s16.liq000066400000000000000000000012061513273233300215030ustar00rootroot00000000000000log.level.set(4) keys = "1234567890*#ABCD" last_key = ref("") detected = ref("") def f(k) = # Skip test for now. # test.skip() print( "Detected key #{k}" ) if last_key() != k then detected := !detected ^ k end last_key := k if k == "D" then if !detected == keys then test.pass() else test.fail() end end end s = amplify(0.5, dtmf(duration=1., keys)) s = audio.encode.pcm_s16(s) s = add([s, blank()]) s = audio.decode.pcm_s16(s) # s = add([s, amplify(0.5, noise())]) # Removed to make the test deterministic s = dtmf.detect(debug=false, s, f) clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/dune000066400000000000000000000210331513273233300173700ustar00rootroot00000000000000; Regenerate using dune build @gendune --auto-promote (include dune.inc) (rule (alias gendune) (deps (source_tree .)) (target dune.inc.gen) (action (with-stdout-to dune.inc.gen (run ./gen_dune.exe)))) (rule (alias gendune) (action (diff dune.inc dune.inc.gen))) (executable (name gen_dune) (libraries liquidsoap_build_tools) (modules gen_dune)) (rule (alias citest) (target file1.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=5" -ac 2 -metadata "title=Test Title" %{target}))) (rule (alias citest) (target file2.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=440:duration=5" -ac 2 %{target}))) (rule (alias citest) (target file3.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=880:duration=5" -ac 2 %{target}))) (rule (alias citest) (target jingle1.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=2" -ac 2 %{target}))) (rule (alias citest) (target jingle2.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=440:duration=2" -ac 2 %{target}))) (rule (alias citest) (target jingle3.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=880:duration=2" -ac 2 %{target}))) (rule (alias citest) (target file1.png) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i color=size=320x240:color=blue -vf "drawtext=fontsize=30:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text='Test 1'" -frames:v 1 %{target}))) (rule (alias citest) (target file2.png) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i color=size=500x100:color=red -vf "drawtext=fontsize=30:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:text='Test 2'" -frames:v 1 %{target}))) (rule (alias citest) (target jingles) (action (with-stdout-to %{target} (run echo "jingle1.mp3\njingle2.mp3\njingle3.mp3")))) (rule (alias citest) (target playlist) (deps ./file1.mp3 ./file2.mp3 ./file3.mp3) (action (with-stdout-to %{target} (run echo "file1.mp3\nfile2.mp3\nfile3.mp3")))) (rule (alias citest) (target huge_playlist) (deps ./file1.mp3) (action (with-stdout-to %{target} (system "for i in `seq 1 100000`; do echo \"file$i.mp3\"; done")))) (rule (alias citest) (targets ssl.cert ssl.key) (action (run openssl req -x509 -newkey rsa:4096 -keyout ssl.key -out ssl.cert -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=localhost"))) (rule (alias citest) (package liquidsoap) (targets crossfade-plot.png.gen crossfade-plot.old.txt.gen crossfade-plot.new.txt.gen) (deps ../../src/bin/liquidsoap.exe ./crossfade-plot.liq (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Crossfade Plot" liquidsoap --stdlib %{stdlib} %{test_liq} ./crossfade-plot.liq))) (rule (alias crossfade-plot) (action (diff crossfade-plot.png crossfade-plot.png.gen))) (rule (alias crossfade-plot) (action (diff crossfade-plot.old.txt crossfade-plot.old.txt.gen))) (rule (alias crossfade-plot) (action (diff crossfade-plot.new.txt crossfade-plot.new.txt.gen))) (rule (alias citest) (package liquidsoap) (targets autocue-plot.0.png.gen autocue-plot.1.png.gen autocue-plot.2.png.gen autocue-plot.0.old.txt.gen autocue-plot.0.new.txt.gen autocue-plot.1.old.txt.gen autocue-plot.1.new.txt.gen autocue-plot.2.old.txt.gen autocue-plot.2.new.txt.gen) (deps ../../src/bin/liquidsoap.exe ./autocue-plot.liq (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Autocue Plot" liquidsoap --stdlib %{stdlib} %{test_liq} ./autocue-plot.liq))) (rule (alias autocue-plot) (action (diff autocue-plot.0.png autocue-plot.0.png.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.0.old.txt autocue-plot.0.old.txt.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.0.new.txt autocue-plot.0.new.txt.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.1.png autocue-plot.1.png.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.1.old.txt autocue-plot.1.old.txt.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.1.new.txt autocue-plot.1.new.txt.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.2.png autocue-plot.2.png.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.2.old.txt autocue-plot.2.old.txt.gen))) (rule (alias autocue-plot) (action (diff autocue-plot.2.new.txt autocue-plot.2.new.txt.gen))) (rule (alias citest) (package liquidsoap) (deps ../../src/bin/liquidsoap.exe ./icecast_ssl.liq ./ssl.cert ./ssl.key (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Icecast SSL connection" liquidsoap --stdlib %{stdlib} %{test_liq} ./icecast_ssl.liq))) (rule (alias citest) (package liquidsoap) (deps ../../src/bin/liquidsoap.exe ./icecast_tls.liq ./ssl.cert ./ssl.key (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Icecast TLS connection" liquidsoap --stdlib %{stdlib} %{test_liq} ./icecast_tls.liq))) (rule (alias citest) (package liquidsoap) (deps ../../src/bin/liquidsoap.exe ./icecast_ssl_tls.liq ./ssl.cert ./ssl.key (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Icecast SSL+TLS connection" liquidsoap --stdlib %{stdlib} %{test_liq} ./icecast_ssl_tls.liq))) (rule (alias citest) (package liquidsoap) (deps ../../src/bin/liquidsoap.exe ./icecast_tls_ssl.liq ./ssl.cert ./ssl.key (package liquidsoap) (:stdlib ../../src/libs/stdlib.liq) (source_tree ../../src/lib) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} "Icecast TLS+SSL connection" liquidsoap --stdlib %{stdlib} %{test_liq} ./icecast_tls_ssl.liq))) (rule (alias citest) (target replaygain_track_gain.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" -metadata "REPLAYGAIN_TRACK_GAIN=-32.0 dB" %{target}))) (rule (alias citest) (target r128_track_gain.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" -metadata "R128_TRACK_GAIN=-4096" %{target}))) (rule (alias citest) (target replaygain_r128_track_gain.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" -metadata "REPLAYGAIN_TRACK_GAIN=-32.0 dB" -metadata "R128_TRACK_GAIN=-4096" %{target}))) (rule (alias citest) (target replaygain_track_gain.opus) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" -metadata "REPLAYGAIN_TRACK_GAIN=-32.0 dB" %{target}))) (rule (alias citest) (target r128_track_gain.opus) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" -metadata "R128_TRACK_GAIN=-4096" %{target}))) (rule (alias citest) (target replaygain_r128_track_gain.opus) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" -metadata "REPLAYGAIN_TRACK_GAIN=-32.0 dB" -metadata "R128_TRACK_GAIN=-4096" %{target}))) (rule (alias citest) (target without_replaygain_track_gain.mp3) (action (run ffmpeg -hide_banner -loglevel error -f lavfi -i "sine=frequency=220:duration=1" %{target}))) (alias (name plot) (deps (alias crossfade-plot) (alias autocue-plot))) (alias (name citest) (deps (alias plot))) liquidsoap-2.4.2/tests/streams/dune.inc000066400000000000000000001331121513273233300201420ustar00rootroot00000000000000 (rule (alias 195) (package liquidsoap) (deps 195.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} 195.liq liquidsoap %{test_liq} 195.liq))) (rule (alias autostart) (package liquidsoap) (deps autostart.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} autostart.liq liquidsoap %{test_liq} autostart.liq))) (rule (alias cross-override-end) (package liquidsoap) (deps cross-override-end.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross-override-end.liq liquidsoap %{test_liq} cross-override-end.liq))) (rule (alias cross-override-start) (package liquidsoap) (deps cross-override-start.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross-override-start.liq liquidsoap %{test_liq} cross-override-start.liq))) (rule (alias cross-override) (package liquidsoap) (deps cross-override.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross-override.liq liquidsoap %{test_liq} cross-override.liq))) (rule (alias cross-persist-end-override) (package liquidsoap) (deps cross-persist-end-override.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross-persist-end-override.liq liquidsoap %{test_liq} cross-persist-end-override.liq))) (rule (alias cross-persist-override) (package liquidsoap) (deps cross-persist-override.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross-persist-override.liq liquidsoap %{test_liq} cross-persist-override.liq))) (rule (alias cross-persist-start-override) (package liquidsoap) (deps cross-persist-start-override.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross-persist-start-override.liq liquidsoap %{test_liq} cross-persist-start-override.liq))) (rule (alias cross) (package liquidsoap) (deps cross.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cross.liq liquidsoap %{test_liq} cross.liq))) (rule (alias crossfade) (package liquidsoap) (deps crossfade.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} crossfade.liq liquidsoap %{test_liq} crossfade.liq))) (rule (alias crossfade2) (package liquidsoap) (deps crossfade2.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} crossfade2.liq liquidsoap %{test_liq} crossfade2.liq))) (rule (alias ctype1) (package liquidsoap) (deps ctype1.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ctype1.liq liquidsoap %{test_liq} ctype1.liq))) (rule (alias ctype2) (package liquidsoap) (deps ctype2.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ctype2.liq liquidsoap %{test_liq} ctype2.liq))) (rule (alias cue-cut) (package liquidsoap) (deps cue-cut.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} cue-cut.liq liquidsoap %{test_liq} cue-cut.liq))) (rule (alias dtmf) (package liquidsoap) (deps dtmf.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} dtmf.liq liquidsoap %{test_liq} dtmf.liq))) (rule (alias dtmf_pcm_s16) (package liquidsoap) (deps dtmf_pcm_s16.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} dtmf_pcm_s16.liq liquidsoap %{test_liq} dtmf_pcm_s16.liq))) (rule (alias fades-overrides) (package liquidsoap) (deps fades-overrides.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} fades-overrides.liq liquidsoap %{test_liq} fades-overrides.liq))) (rule (alias fades-persistent-override) (package liquidsoap) (deps fades-persistent-override.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} fades-persistent-override.liq liquidsoap %{test_liq} fades-persistent-override.liq))) (rule (alias ffmpeg-errors) (package liquidsoap) (deps ffmpeg-errors.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ffmpeg-errors.liq liquidsoap %{test_liq} ffmpeg-errors.liq))) (rule (alias ffmpeg-trim) (package liquidsoap) (deps ffmpeg-trim.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} ffmpeg-trim.liq liquidsoap %{test_liq} ffmpeg-trim.liq))) (rule (alias file_output) (package liquidsoap) (deps file_output.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} file_output.liq liquidsoap %{test_liq} file_output.liq))) (rule (alias harbor_kick_client) (package liquidsoap) (deps harbor_kick_client.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} harbor_kick_client.liq liquidsoap %{test_liq} harbor_kick_client.liq))) (rule (alias harbor_metadata) (package liquidsoap) (deps harbor_metadata.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} harbor_metadata.liq liquidsoap %{test_liq} harbor_metadata.liq))) (rule (alias harbor_metadata_2) (package liquidsoap) (deps harbor_metadata_2.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} harbor_metadata_2.liq liquidsoap %{test_liq} harbor_metadata_2.liq))) (rule (alias harbor_metadata_3) (package liquidsoap) (deps harbor_metadata_3.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} harbor_metadata_3.liq liquidsoap %{test_liq} harbor_metadata_3.liq))) (rule (alias hls_custom_tags) (package liquidsoap) (deps hls_custom_tags.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} hls_custom_tags.liq liquidsoap %{test_liq} hls_custom_tags.liq))) (rule (alias hls_discontinuity) (package liquidsoap) (deps hls_discontinuity.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} hls_discontinuity.liq liquidsoap %{test_liq} hls_discontinuity.liq))) (rule (alias hls_global_headers) (package liquidsoap) (deps hls_global_headers.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} hls_global_headers.liq liquidsoap %{test_liq} hls_global_headers.liq))) (rule (alias hls_id3v2) (package liquidsoap) (deps hls_id3v2.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} hls_id3v2.liq liquidsoap %{test_liq} hls_id3v2.liq))) (rule (alias hls_large_frame) (package liquidsoap) (deps hls_large_frame.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} hls_large_frame.liq liquidsoap %{test_liq} hls_large_frame.liq))) (rule (alias hls_main_playlist) (package liquidsoap) (deps hls_main_playlist.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} hls_main_playlist.liq liquidsoap %{test_liq} hls_main_playlist.liq))) (rule (alias huge-playlist) (package liquidsoap) (deps huge-playlist.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} huge-playlist.liq liquidsoap %{test_liq} huge-playlist.liq))) (rule (alias icecast_last_meta) (package liquidsoap) (deps icecast_last_meta.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} icecast_last_meta.liq liquidsoap %{test_liq} icecast_last_meta.liq))) (rule (alias icecast_no_restart) (package liquidsoap) (deps icecast_no_restart.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} icecast_no_restart.liq liquidsoap %{test_liq} icecast_no_restart.liq))) (rule (alias image) (package liquidsoap) (deps image.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} image.liq liquidsoap %{test_liq} image.liq))) (rule (alias many-playlists) (package liquidsoap) (deps many-playlists.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} many-playlists.liq liquidsoap %{test_liq} many-playlists.liq))) (rule (alias merge_metadata) (package liquidsoap) (deps merge_metadata.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} merge_metadata.liq liquidsoap %{test_liq} merge_metadata.liq))) (rule (alias metadata-override) (package liquidsoap) (deps metadata-override.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} metadata-override.liq liquidsoap %{test_liq} metadata-override.liq))) (rule (alias never) (package liquidsoap) (deps never.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} never.liq liquidsoap %{test_liq} never.liq))) (rule (alias no-cue-cut) (package liquidsoap) (deps no-cue-cut.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} no-cue-cut.liq liquidsoap %{test_liq} no-cue-cut.liq))) (rule (alias non_repeating) (package liquidsoap) (deps non_repeating.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} non_repeating.liq liquidsoap %{test_liq} non_repeating.liq))) (rule (alias on_end) (package liquidsoap) (deps on_end.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_end.liq liquidsoap %{test_liq} on_end.liq))) (rule (alias on_end_partial) (package liquidsoap) (deps on_end_partial.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_end_partial.liq liquidsoap %{test_liq} on_end_partial.liq))) (rule (alias on_frame) (package liquidsoap) (deps on_frame.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_frame.liq liquidsoap %{test_liq} on_frame.liq))) (rule (alias on_metadata) (package liquidsoap) (deps on_metadata.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_metadata.liq liquidsoap %{test_liq} on_metadata.liq))) (rule (alias on_offset) (package liquidsoap) (deps on_offset.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_offset.liq liquidsoap %{test_liq} on_offset.liq))) (rule (alias on_offset_partial) (package liquidsoap) (deps on_offset_partial.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_offset_partial.liq liquidsoap %{test_liq} on_offset_partial.liq))) (rule (alias on_track) (package liquidsoap) (deps on_track.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} on_track.liq liquidsoap %{test_liq} on_track.liq))) (rule (alias playlist-watch) (package liquidsoap) (deps playlist-watch.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} playlist-watch.liq liquidsoap %{test_liq} playlist-watch.liq))) (rule (alias radio) (package liquidsoap) (deps radio.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} radio.liq liquidsoap %{test_liq} radio.liq))) (rule (alias radio2) (package liquidsoap) (deps radio2.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} radio2.liq liquidsoap %{test_liq} radio2.liq))) (rule (alias random) (package liquidsoap) (deps random.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} random.liq liquidsoap %{test_liq} random.liq))) (rule (alias replaygain) (package liquidsoap) (deps replaygain.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} replaygain.liq liquidsoap %{test_liq} replaygain.liq))) (rule (alias request) (package liquidsoap) (deps request.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} request.liq liquidsoap %{test_liq} request.liq))) (rule (alias rotate) (package liquidsoap) (deps rotate.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} rotate.liq liquidsoap %{test_liq} rotate.liq))) (rule (alias say) (package liquidsoap) (deps say.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} say.liq liquidsoap %{test_liq} say.liq))) (rule (alias sharing) (package liquidsoap) (deps sharing.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} sharing.liq liquidsoap %{test_liq} sharing.liq))) (rule (alias sine.detect.full_conv) (package liquidsoap) (deps sine.detect.full_conv.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} sine.detect.full_conv.liq liquidsoap %{test_liq} sine.detect.full_conv.liq))) (rule (alias sine.detect) (package liquidsoap) (deps sine.detect.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} sine.detect.liq liquidsoap %{test_liq} sine.detect.liq))) (rule (alias sine.detect.pcm_f32) (package liquidsoap) (deps sine.detect.pcm_f32.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} sine.detect.pcm_f32.liq liquidsoap %{test_liq} sine.detect.pcm_f32.liq))) (rule (alias sine.detect.pcm_s16) (package liquidsoap) (deps sine.detect.pcm_s16.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} sine.detect.pcm_s16.liq liquidsoap %{test_liq} sine.detect.pcm_s16.liq))) (rule (alias soundtouch-tracks) (package liquidsoap) (deps soundtouch-tracks.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} soundtouch-tracks.liq liquidsoap %{test_liq} soundtouch-tracks.liq))) (rule (alias source-cue) (package liquidsoap) (deps source-cue.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} source-cue.liq liquidsoap %{test_liq} source-cue.liq))) (rule (alias srt_listen_callback) (package liquidsoap) (deps srt_listen_callback.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} srt_listen_callback.liq liquidsoap %{test_liq} srt_listen_callback.liq))) (rule (alias srt_multiple_outputs) (package liquidsoap) (deps srt_multiple_outputs.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} srt_multiple_outputs.liq liquidsoap %{test_liq} srt_multiple_outputs.liq))) (rule (alias srt_passphrase) (package liquidsoap) (deps srt_passphrase.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} srt_passphrase.liq liquidsoap %{test_liq} srt_passphrase.liq))) (rule (alias srt_raw_pcm) (package liquidsoap) (deps srt_raw_pcm.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} srt_raw_pcm.liq liquidsoap %{test_liq} srt_raw_pcm.liq))) (rule (alias stretch-clock-propagation) (package liquidsoap) (deps stretch-clock-propagation.liq ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %{run_test} stretch-clock-propagation.liq liquidsoap %{test_liq} stretch-clock-propagation.liq))) (alias (name citest) (deps (alias 195) (alias autostart) (alias cross) (alias cross-override) (alias cross-override-end) (alias cross-override-start) (alias cross-persist-end-override) (alias cross-persist-override) (alias cross-persist-start-override) (alias crossfade) (alias crossfade2) (alias ctype1) (alias ctype2) (alias cue-cut) (alias dtmf) (alias dtmf_pcm_s16) (alias fades-overrides) (alias fades-persistent-override) (alias ffmpeg-errors) (alias ffmpeg-trim) (alias file_output) (alias harbor_kick_client) (alias harbor_metadata) (alias harbor_metadata_2) (alias harbor_metadata_3) (alias hls_custom_tags) (alias hls_discontinuity) (alias hls_global_headers) (alias hls_id3v2) (alias hls_large_frame) (alias hls_main_playlist) (alias huge-playlist) (alias icecast_last_meta) (alias icecast_no_restart) (alias image) (alias many-playlists) (alias merge_metadata) (alias metadata-override) (alias never) (alias no-cue-cut) (alias non_repeating) (alias on_end) (alias on_end_partial) (alias on_frame) (alias on_metadata) (alias on_offset) (alias on_offset_partial) (alias on_track) (alias playlist-watch) (alias radio) (alias radio2) (alias random) (alias replaygain) (alias request) (alias rotate) (alias say) (alias sharing) (alias sine.detect) (alias sine.detect.full_conv) (alias sine.detect.pcm_f32) (alias sine.detect.pcm_s16) (alias soundtouch-tracks) (alias source-cue) (alias srt_listen_callback) (alias srt_multiple_outputs) (alias srt_passphrase) (alias srt_raw_pcm) (alias stretch-clock-propagation))) liquidsoap-2.4.2/tests/streams/fades-overrides.liq000066400000000000000000000032711513273233300223070ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, update=false, ( fun (_) -> [ ("source", "b"), ("liq_fade_out", "1.1"), ("liq_fade_out_type", "exp"), ("liq_fade_in_type", "lin"), ("liq_fade_in", "1.") ] ), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) fade_in = fade.in(duration=5., type="sin", s) fade_out = fade.out(duration=5., type="sin", fade_in) track_count = ref(0) def on_track(m) = ref.incr(track_count) if track_count() == 2 then if m["source"] != "b" then test.fail() end if fade_in.fade_duration() != 1. then test.fail() end if fade_out.fade_duration() != 1.1 then test.fail() end if fade_in.fade_type() != "lin" then test.fail() end if fade_out.fade_type() != "exp" then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if fade_in.fade_duration() != 5. then test.fail() end if fade_out.fade_duration() != 5. then test.fail() end if fade_in.fade_type() != "sin" then test.fail() end if fade_out.fade_type() != "sin" then test.fail() end success := true end end fade_out.on_track(synchronous=true, on_track) clock.assign_new(sync="none", [fade_out]) def on_stop() = if success() then test.pass() else test.fail() end end o = output.dummy(fallible=true, fade_out) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/fades-persistent-override.liq000066400000000000000000000033521513273233300243220ustar00rootroot00000000000000success = ref(false) a = sine(duration=5., 440.) a = metadata.map( insert_missing=true, update=false, (fun (_) -> [("source", "a")]), a ) b = sine(duration=5., 880.) b = metadata.map( insert_missing=true, update=false, ( fun (_) -> [ ("source", "b"), ("liq_fade_out", "1.1"), ("liq_fade_out_type", "lin"), ("liq_fade_in_type", "exp"), ("liq_fade_in", "1.") ] ), b ) c = sine(duration=5., 440.) c = metadata.map( insert_missing=true, update=false, (fun (_) -> [("source", "c")]), c ) s = sequence([a, b, c]) fade_in = fade.in(persist_overrides=true, duration=5., type="sin", s) fade_out = fade.out(persist_overrides=true, duration=5., type="sin", fade_in) track_count = ref(0) def on_track(m) = ref.incr(track_count) if track_count() == 2 then if m["source"] != "b" then test.fail() end if fade_in.fade_duration() != 1. then test.fail() end if fade_out.fade_duration() != 1.1 then test.fail() end if fade_in.fade_type() != "exp" then test.fail() end if fade_out.fade_type() != "lin" then test.fail() end end if track_count() == 3 then if m["source"] != "c" then test.fail() end if fade_in.fade_duration() != 1. then test.fail() end if fade_out.fade_duration() != 1.1 then test.fail() end if fade_in.fade_type() != "exp" then test.fail() end if fade_out.fade_type() != "lin" then test.fail() end success := true end end fade_out.on_track(synchronous=true, on_track) clock.assign_new(sync="none", [fade_out]) def on_stop() = if !success then test.pass() else test.fail() end end o = output.dummy(fallible=true, fade_out) o.on_stop(synchronous=true, on_stop) liquidsoap-2.4.2/tests/streams/ffmpeg-errors.liq000066400000000000000000000006511513273233300220020ustar00rootroot00000000000000def f() = def on_error(e) = e = error.methods(e) test.equal(e.kind, "ffmpeg") test.equal( e.message, "Avutil.Error(No such file or directory)" ) let [..._, {filename}] = e.trace test.equal(filename, "src/core/optionals/ffmpeg/ffmpeg_io.ml") test.pass() end s = input.ffmpeg("invalid") s.on_error(synchronous=true, on_error) output.dummy(s, fallible=true) end test.check(f) liquidsoap-2.4.2/tests/streams/ffmpeg-trim.liq000066400000000000000000000003661513273233300214440ustar00rootroot00000000000000def f() = def on_error(_) = test.fail() end # Test string.trim on url input. output.dummy( input.ffmpeg( on_connect=test.pass, on_error=on_error, " ./file1.mp3\n" ), fallible=true ) end test.check(f) liquidsoap-2.4.2/tests/streams/file_output.liq000066400000000000000000000005751513273233300215700ustar00rootroot00000000000000s = sine() close_count = ref(0) tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) output_file = {"#{tmp_dir}/bla-#{close_count()}.mp3"} def on_close(fname) = test.equal(fname, output_file()) close_count := close_count() + 1 if close_count() > 3 then test.pass() end end o = output.file(on_close=on_close, %mp3, output_file, s) thread.run(every=2., o.reopen) liquidsoap-2.4.2/tests/streams/gen_dune.ml000066400000000000000000000026641513273233300206410ustar00rootroot00000000000000let test_names = ref [] let test_name s = let test_name = Filename.remove_extension s in test_names := Printf.sprintf "(alias %s)" test_name :: !test_names; test_name let static_tests = [ "icecast_ssl.liq"; "icecast_tls.liq"; "icecast_tls_ssl.liq"; "icecast_ssl_tls.liq"; "crossfade-plot.liq"; "autocue-plot.liq"; ] let () = let location = Sys.getcwd () in let tests = List.filter (fun f -> (not (List.mem (Filename.basename f) static_tests)) && Filename.extension f = ".liq") (Build_tools.read_files ~location "") in List.iter (fun test -> Printf.printf {| (rule (alias %s) (package liquidsoap) (deps %s ./file1.mp3 ./file2.mp3 ./file3.mp3 ./jingle1.mp3 ./jingle2.mp3 ./jingle3.mp3 ./file1.png ./file2.png ./jingles ./playlist ./huge_playlist ./replaygain_track_gain.mp3 ./r128_track_gain.mp3 ./replaygain_r128_track_gain.mp3 ./replaygain_track_gain.opus ./r128_track_gain.opus ./replaygain_r128_track_gain.opus ./without_replaygain_track_gain.mp3 ../../src/bin/liquidsoap.exe (package liquidsoap) (:test_liq ../test.liq) (:run_test ../run_test.exe)) (action (run %%{run_test} %s liquidsoap %%{test_liq} %s))) |} (test_name test) test test test) tests let () = Printf.printf {|(alias (name citest) (deps %s)) |} (String.concat "\n " (List.sort_uniq Stdlib.compare !test_names)) liquidsoap-2.4.2/tests/streams/harbor_kick_client.liq000066400000000000000000000020571513273233300230420ustar00rootroot00000000000000# Test that a second harbor client can # disconnect existing one if needed. def fn() = kick_client = ref(fun () -> error.raise(error.assertion)) first_client_connected = ref(false) def auth(_) = if !first_client_connected then fn = !kick_client fn() end true end def on_connect(_) = if !first_client_connected then test.pass() else thread.run( delay=1., { output.icecast( %mp3, password="testtest", user="testtest", mount="harbor_kick_client", port=9878, noise() ) } ) end first_client_connected := true end s = input.harbor( buffer=0.1, auth=auth, on_connect=on_connect, port=9878, "harbor_kick_client" ) kick_client := s.stop output.dummy(fallible=true, s) output.icecast( %mp3, password="testtest", user="testtest", mount="harbor_kick_client", port=9878, noise() ) end test.check(fn) liquidsoap-2.4.2/tests/streams/harbor_metadata.liq000066400000000000000000000017751513273233300223510ustar00rootroot00000000000000port = 3461 def fn() = def on_metadata(m) = if m["title"] == "song title" and m["metadata_url"] == "metadata url" then test.pass() end end def on_connect(_) = thread.run( delay=1., { icy.update_metadata( password="testtest", user="testtest", host="localhost", port=port, mount="test", [ ( "song", "song title" ), ( "url", "metadata url" ) ] ) } ) end s = input.harbor( buffer=0.1, password="testtest", user="testtest", "test", port=port, on_connect=on_connect ) s.on_metadata(synchronous=true, on_metadata) output.dummy(fallible=true, s) output.icecast( %mp3, password="testtest", user="testtest", mount="test", port=port, noise() ) end test.check(fn) liquidsoap-2.4.2/tests/streams/harbor_metadata_2.liq000066400000000000000000000021241513273233300225570ustar00rootroot00000000000000port = 3464 def fn() = def on_metadata(m) = if m["title"] == "the real title" and m["metadata_url"] == "metadata url" then test.pass() end end def on_connect(_) = thread.run( delay=1., { icy.update_metadata( password="testtest", user="testtest", host="localhost", port=port, mount="test", [ ( "song", "song title" ), ( "title", "the real title" ), ( "url", "metadata url" ) ] ) } ) end s = input.harbor( buffer=0.1, password="testtest", user="testtest", "test", port=port, on_connect=on_connect ) s.on_metadata(synchronous=true, on_metadata) output.dummy(fallible=true, s) output.icecast( %mp3, password="testtest", user="testtest", mount="test", port=port, noise() ) end test.check(fn) liquidsoap-2.4.2/tests/streams/harbor_metadata_3.liq000066400000000000000000000021721513273233300225630ustar00rootroot00000000000000port = 3463 def fn() = def on_metadata(m) = print(m) if m["title"] == "" and m["artist"] == "the artist" and m["metadata_url"] == "metadata url" then test.pass() end end def on_connect(_) = thread.run( delay=1., { icy.update_metadata( password="testtest", user="testtest", host="localhost", port=port, mount="test", [ ( "song", "song title" ), ( "artist", "the artist" ), ( "url", "metadata url" ) ] ) } ) end s = input.harbor( buffer=0.1, password="testtest", user="testtest", "test", port=port, on_connect=on_connect ) s.on_metadata(synchronous=true, on_metadata) output.dummy(fallible=true, s) output.icecast( %mp3, password="testtest", user="testtest", mount="test", port=port, noise() ) end test.check(fn) liquidsoap-2.4.2/tests/streams/hls_custom_tags.liq000066400000000000000000000034071513273233300224240ustar00rootroot00000000000000s = sine() s = mksafe(s) tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) main_tags = ref(false) aac_flags = ref(false) mp4_flags = ref(false) aac_insert_flags = ref(false) test.skip() def on_file_change({state, path = p}) = fname = path.basename(p) if state == "created" or state == "updated" then if fname == "main.m3u8" and string.contains(substring="X-CUSTOM", file.contents(p)) then main_tags := true end if fname == "aac.m3u8" then contents = file.contents(p) if string.contains(substring="X-CUSTOM", contents) then aac_flags := true end if string.contains(substring="X-CUSTOM-AAC-INSERT", contents) then aac_insert_flags := true end end if fname == "mp4.m3u8" then contents = file.contents(p) if string.contains(substring="X-CUSTOM", contents) then mp4_flags := true end end end if main_tags() and aac_flags() and mp4_flags() and aac_insert_flags() then test.pass() end end o = output.file.hls( playlist="main.m3u8", extra_tags=[ "X-CUSTOM \n" ], tmp_dir, [ ( "aac", %ffmpeg(format = "adts", %audio(codec = "aac")).{ extra_tags=["X-CUSTOM"] } ), ( "mp4", %ffmpeg( format = "mp4", frag_duration = 10, movflags = "+dash+skip_sidx+skip_trailer+frag_custom", %audio(codec = "aac") ).{extra_tags=["X-CUSTOM"]} ) ], s ) o.on_file_change(synchronous=true, on_file_change) thread.run( delay=2., every=2., {list.hd(o.streams()).insert_tag("X-CUSTOM-AAC-INSERT")} ) clock.assign_new(sync="none", [s]) liquidsoap-2.4.2/tests/streams/hls_discontinuity.liq000066400000000000000000000040431513273233300227760ustar00rootroot00000000000000tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) s = sine() is_on = ref(true) s = switch(track_sensitive=false, [(is_on, s)]) clock.assign_new(sync="none", [s]) enc = %ffmpeg(format = "mpegts", %audio(codec = "aac")) streams = [("stream", enc)] has_restarted = ref(false) segments_count = ref(-1) first_playlist = "#EXTM3U\r\n#EXT-X-TARGETDURATION:2\r\n#EXT-X-VERSION:3\r\n#EXT-X-MEDIA-SEQUENCE:0\r\n#EXT-X-DISCONTINUITY-SEQUENCE:0\r\n#EXTINF:2.000,\r\nstream_1.ts\r\n#EXTINF:1.980,\r\nstream_2.ts\r\n#EXTINF:1.980,\r\nstream_3.ts\r\n#EXTINF:1.960,\r\nstream_4.ts\r\n" second_playlist = "#EXTM3U\r\n#EXT-X-TARGETDURATION:2\r\n#EXT-X-VERSION:3\r\n#EXT-X-MEDIA-SEQUENCE:2\r\n#EXT-X-DISCONTINUITY-SEQUENCE:0\r\n#EXTINF:1.980,\r\nstream_3.ts\r\n#EXTINF:1.960,\r\nstream_4.ts\r\n#EXTINF:0.040,\r\nstream_5.ts\r\n#EXT-X-DISCONTINUITY\r\n#EXTINF:2.000,\r\nstream_7.ts\r\n" third_playlist = "#EXTM3U\r\n#EXT-X-TARGETDURATION:2\r\n#EXT-X-VERSION:3\r\n#EXT-X-MEDIA-SEQUENCE:6\r\n#EXT-X-DISCONTINUITY-SEQUENCE:1\r\n#EXTINF:2.000,\r\nstream_7.ts\r\n#EXTINF:1.980,\r\nstream_8.ts\r\n#EXTINF:1.980,\r\nstream_9.ts\r\n#EXTINF:1.960,\r\nstream_10.ts\r\n" def on_file_change({path = fname}) = if path.basename(fname) == "stream.m3u8" then ref.incr(segments_count) if not has_restarted() and segments_count() == 4 then test.equal(file.contents(fname), first_playlist) is_on := false thread.run( delay=0.1, { has_restarted := true segments_count := 0 is_on := true } ) elsif has_restarted() and segments_count() == 1 then test.equal(file.contents(fname), second_playlist) elsif has_restarted() and segments_count() == 4 then test.equal(file.contents(fname), third_playlist) test.pass() end end end o = output.file.hls( fallible=true, persist_at="./hls_discontinuity.json", segments=4, segment_duration=2., tmp_dir, streams, s ) o.on_file_change(synchronous=true, on_file_change) liquidsoap-2.4.2/tests/streams/hls_global_headers.liq000066400000000000000000000011741513273233300230260ustar00rootroot00000000000000tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) s = video.testsrc.ffmpeg(duration=10.) s = mksafe(s) def main_playlist_writer(~extra_tags:_, ~prefix:_, ~version:_, streams) = let [{codecs = c1, ..._}, {codecs = c2, ..._}] = streams if c1 == "avc1.64001f" and c2 == "avc1.64001f" then test.pass() else test.fail() end "" end output.file.hls( main_playlist_writer=main_playlist_writer, segment_duration=2.0, tmp_dir, [ ("ts", %ffmpeg(format = "mpegts", %video(codec = "libx264", b = "500k"))), ("mp4", %ffmpeg(format = "mp4", %video(codec = "libx264", b = "500k"))) ], s ) liquidsoap-2.4.2/tests/streams/hls_id3v2.liq000066400000000000000000000063221513273233300210220ustar00rootroot00000000000000tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) # Too flakey. test.skip() def run_check() = to_check = ref( { aac=null, shine=null, lame=null, fdkaac=null, ts_with_meta=null, ts=null, mp4=null } ) def check_done() = print( "run check: #{to_check()}" ) let {aac, shine, lame, fdkaac, ts_with_meta, ts, mp4} = to_check() if null.defined(ts) then test.fail( "ts shouldn't have metadata!" ) end if null.defined(mp4) then test.fail( "mp4 should have metadata but it's not supported by the demuxer yet." ) end if null.defined(ts_with_meta) then ts_with_meta = null.get(ts_with_meta) if ts_with_meta["title"] == "test title" and ts_with_meta["album"] == "foolol" then test.pass() end end end ts_with_meta = input.hls("#{tmp_dir}/ts_with_meta.m3u8") ts_with_meta.on_metadata( synchronous=true, fun (m) -> begin print( "ts_with_meta #{m}" ) if m["title"] != "" then to_check := to_check().{ts_with_meta=m} end check_done() end ) output.dummy(fallible=true, ts_with_meta) ts = input.hls("#{tmp_dir}/ts.m3u8") ts.on_metadata( synchronous=true, fun (m) -> begin if m["title"] != "" then to_check := to_check().{ts=m} end check_done() end ) output.dummy(fallible=true, ts) mp4 = input.hls("#{tmp_dir}/mp4.m3u8") mp4.on_metadata( synchronous=true, fun (m) -> begin if m["title"] != "" then to_check := to_check().{mp4=m} end check_done() end ) output.dummy(fallible=true, mp4) end s = sine() s = metadata.map( insert_missing=true, fun (_) -> [ ( "title", "test title" ), ("album", "foolol") ], s ) s = mksafe(s) check_running = ref(false) segments_created = ref(0) def segment_name(metadata) = let {position, extname, stream_name} = metadata "segment-#{stream_name}_#{position}.#{extname}" end def on_file_change({state, path = fname}) = if state == "created" and string.contains(prefix="segment-", path.basename(fname)) and string.contains(suffix="mp3", fname) then ref.incr(segments_created) end if segments_created() > 4 and not check_running() then check_running := true run_check() end end o = output.file.hls( segment_duration=2., segment_name=segment_name, tmp_dir, [ ("aac", %ffmpeg(format = "adts", %audio(codec = "aac")).{id3_version=3}), ( "ts_with_meta", %ffmpeg(format = "mpegts", %audio(codec = "aac")).{id3_version=4} ), ("ts", %ffmpeg(format = "mpegts", %audio(codec = "aac")).{id3=false}), ("shine", %shine), ("lame", %mp3), ("fdkaac", %fdkaac), ( "mp4", %ffmpeg( format = "mp4", frag_duration = 10, movflags = "+dash+skip_sidx+skip_trailer+frag_custom", %audio(codec = "aac") ) ) ], s ) o.on_file_change(synchronous=true, on_file_change) liquidsoap-2.4.2/tests/streams/hls_large_frame.liq000066400000000000000000000010511513273233300223310ustar00rootroot00000000000000frame.duration := 0.1 tmp_dir = file.temp_dir("tmp") on_cleanup({file.rmdir(tmp_dir)}) s = video.testsrc.ffmpeg(duration=10.) s = mksafe(s) enc = %ffmpeg( format = "mpegts", %video( codec = "libx264", maxrate = "4000k", bufsize = "10000k", preset = "ultrafast", tune = "zerolatency", x264opts = "keyint=12:min-keyint=12" ) ).{bandwidth=4000000} streams = [("radio", enc)] o = output.file.hls(segment_duration=2.0, tmp_dir, streams, s) o.on_file_change(synchronous=true, fun (_) -> test.pass()) liquidsoap-2.4.2/tests/streams/hls_main_playlist.liq000066400000000000000000000027211513273233300227370ustar00rootroot00000000000000def f() = playlist = hls.playlist.main( extra_tags=["foo", "bar"], version=54, prefix="prefix", [ "stream_a".{bandwidth=123, codecs="codecs_a"}, "steam_b".{bandwidth=456, codecs="codecs_b", video_size=(12, 34)} ] ) test.equal( playlist, '#EXTM3U\r #EXT-X-VERSION:54\r foo\r bar\r #EXT-X-STREAM-INF:BANDWIDTH=123,CODECS="codecs_a"\r prefix/stream_a.m3u8\r #EXT-X-STREAM-INF:BANDWIDTH=456,CODECS="codecs_b",RESOLUTION=12x34\r prefix/steam_b.m3u8\r ' ) playlist = hls.playlist.main( extra_tags=["foo", "bar"], version=54, prefix="prefix/", [ "stream_a".{bandwidth=123, codecs="codecs_a"}, "steam_b".{bandwidth=456, codecs="codecs_b", video_size=(12, 34)} ] ) test.equal( playlist, '#EXTM3U\r #EXT-X-VERSION:54\r foo\r bar\r #EXT-X-STREAM-INF:BANDWIDTH=123,CODECS="codecs_a"\r prefix/stream_a.m3u8\r #EXT-X-STREAM-INF:BANDWIDTH=456,CODECS="codecs_b",RESOLUTION=12x34\r prefix/steam_b.m3u8\r ' ) playlist = hls.playlist.main( version=54, [ "stream_a".{bandwidth=123, codecs="codecs_a"}, "steam_b".{bandwidth=456, codecs="codecs_b", video_size=(12, 34)} ] ) test.equal( playlist, '#EXTM3U\r #EXT-X-VERSION:54\r #EXT-X-STREAM-INF:BANDWIDTH=123,CODECS="codecs_a"\r stream_a.m3u8\r #EXT-X-STREAM-INF:BANDWIDTH=456,CODECS="codecs_b",RESOLUTION=12x34\r steam_b.m3u8\r ' ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/streams/huge-playlist.liq000066400000000000000000000006071513273233300220140ustar00rootroot00000000000000# Test huge playlists, see #2162 log.level.set(5) uname_s = string.trim( process.run( "uname -s" ).stdout ) if uname_s == "Darwin" then test.skip() end def f() = s = playlist(mode="normal", "huge_playlist") print( "\nWe have #{s.length()} songs!\n" ) output.dummy(mksafe(s)) end test.check(f) def on_done() = test.pass() end thread.run(delay=5., on_done) liquidsoap-2.4.2/tests/streams/icecast_last_meta.liq000066400000000000000000000010761513273233300226720ustar00rootroot00000000000000port = 6723 settings.icecast.prefer_address := "ipv4" s = sine() insert_metadata = s.insert_metadata output.dummy(s) thread.run( delay=0.1, { insert_metadata( [ ( "title", "some title" ) ] ) } ) thread.run( delay=0.3, {output.icecast(port=port, mount="metadata_test", %mp3, s)} ) i = input.harbor(buffer=2., port=port, "metadata_test") i.on_metadata( synchronous=true, fun (m) -> if m["title"] == "some title" then test.pass() end ) output.dummy(fallible=true, i) liquidsoap-2.4.2/tests/streams/icecast_no_restart.liq000066400000000000000000000015701513273233300231000ustar00rootroot00000000000000port = 6756 settings.icecast.prefer_address := "ipv4" # Too Flakey.. test.skip() s = sine() o = output.icecast(port=port, mount="icecast_no_restart", timeout=2., %mp3, s) i = input.harbor(port=port, "icecast_no_restart") d = output.dummy(fallible=true, i) has_connected = ref(false) has_errored = ref(false) pass = ref(true) def on_error(~restart_in, err) = print( "Error: #{err}" ) if has_errored() then test.fail() else has_errored := true restart_in(null) thread.run(delay=4., {if pass() then test.pass() else test.fail() end}) end end def on_connect() = if has_connected() then test.fail() else has_connected := true o.on_error(synchronous=true, on_error) d.shutdown() end end o.on_connect(synchronous=true, on_connect) def on_disconnect() = pass := true end o.on_disconnect(synchronous=true, on_disconnect) liquidsoap-2.4.2/tests/streams/icecast_ssl.liq000066400000000000000000000010241513273233300215130ustar00rootroot00000000000000transport = http.transport.ssl(certificate="./ssl.cert", key="./ssl.key") settings.icecast.prefer_address := "ipv4" port = 1443 test.skip() def on_wake_up() = s = sine() output.icecast( port=port, mount="ssl_test", connection_timeout=0.1, timeout=0.1, transport=transport, %vorbis, s ) end i = input.harbor(buffer=2., port=port, transport=transport, "ssl_test") i.on_wake_up(synchronous=false, on_wake_up) i.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, i) liquidsoap-2.4.2/tests/streams/icecast_ssl_tls.liq000066400000000000000000000011301513273233300223730ustar00rootroot00000000000000log.level := 4 settings.icecast.prefer_address := "ipv4" tls = http.transport.tls(certificate="./ssl.cert", key="./ssl.key") ssl = http.transport.ssl(certificate="./ssl.cert", key="./ssl.key") port = 1444 #test.skip() def on_wake_up() = s = sine() output.icecast( port=port, mount="tls_test", connection_timeout=0.1, timeout=0.1, transport=ssl, %vorbis, s ) end i = input.harbor(buffer=2., port=port, transport=tls, "tls_test") i.on_wake_up(synchronous=false, on_wake_up) i.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, i) liquidsoap-2.4.2/tests/streams/icecast_tls.liq000066400000000000000000000010271513273233300215170ustar00rootroot00000000000000log.level := 4 settings.icecast.prefer_address := "ipv4" transport = http.transport.tls(certificate="./ssl.cert", key="./ssl.key") port = 1441 def on_wake_up() = s = sine() output.icecast( port=port, mount="tls_test", connection_timeout=0.1, timeout=0.1, transport=transport, %vorbis, s ) end i = input.harbor(buffer=2., port=port, transport=transport, "tls_test") i.on_wake_up(synchronous=false, on_wake_up) i.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, i) liquidsoap-2.4.2/tests/streams/icecast_tls_ssl.liq000066400000000000000000000011271513273233300224010ustar00rootroot00000000000000log.level := 4 settings.icecast.prefer_address := "ipv4" test.skip() tls = http.transport.tls(certificate="./ssl.cert", key="./ssl.key") ssl = http.transport.ssl(certificate="./ssl.cert", key="./ssl.key") port = 1442 def on_wake_up() = s = sine() output.icecast( port=port, mount="tls_test", connection_timeout=0.1, timeout=0.1, transport=tls, %vorbis, s ) end i = input.harbor(buffer=2., port=port, transport=ssl, "tls_test") i.on_wake_up(synchronous=false, on_wake_up) i.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, i) liquidsoap-2.4.2/tests/streams/image.liq000066400000000000000000000002171513273233300203040ustar00rootroot00000000000000#!../../liquidsoap ../test.liq s = (single("file1.png") : source(video=canvas)) output.dummy(fallible=true, s) thread.run(delay=3., test.pass) liquidsoap-2.4.2/tests/streams/many-playlists.liq000066400000000000000000000006621513273233300222140ustar00rootroot00000000000000#!../../liquidsoap ../test.liq # Test large number of playlists, see #2586 log.level.set(4) def f() = n = 1000 print( newline=false, "Loading #{n} playlists... " ) l = list.init( 100, fun (i) -> playlist(id="playlist#{i}", mode="normal", "playlist") ) print("done") s = random(l) output.dummy(mksafe(s)) end test.check(f) def on_done() = test.pass() end thread.run(delay=5., on_done) liquidsoap-2.4.2/tests/streams/merge_metadata.liq000066400000000000000000000015321513273233300221620ustar00rootroot00000000000000log.level.set(4) def f(m) = if m == [ ( "s", "That's me!" ), ( "s'", "That's me!" ), ("test", "blo") ] then test.pass() else test.fail() end end s = sine() s' = sine() s' = insert_metadata(s') def insert() = print( "Inserting metadata" ) s.insert_metadata( [ ("test", "bla"), ( "s", "That's me!" ) ] ) s'.insert_metadata( [ ("test", "blo"), ( "s'", "That's me!" ) ] ) end s = source( { audio=source.tracks(s).audio, metadata= track.metadata.merge( [source.tracks(s).metadata, source.tracks(s').metadata] ) } ) s.on_metadata(synchronous=true, f) thread.run(delay=1., insert) output.dummy(s) liquidsoap-2.4.2/tests/streams/metadata-override.liq000066400000000000000000000042001513273233300226130ustar00rootroot00000000000000def filter_meta(meta) = [("title", meta["title"])] end def f() = fname = "file1.mp3" meta = file.metadata(fname) test.equal( filter_meta(meta), [ ( "title", "Test Title" ) ] ) r = request.create( "annotate:title=\"Annotate Override\":#{fname}" ) test.equal(request.resolve(r), true) test.equal( filter_meta(request.metadata(r)), [ ( "title", "Annotate Override" ) ] ) decoder.metadata.add( priority=-1, "test-no-override", fun (~metadata:_, _) -> [ ( "title", "Metadata decoder no override" ) ] ) meta = file.metadata(fname) test.equal( filter_meta(meta), [ ( "title", "Test Title" ) ] ) r = request.create( "annotate:title=\"Annotate Override\":#{fname}" ) test.equal(request.resolve(r), true) test.equal( filter_meta(request.metadata(r)), [ ( "title", "Annotate Override" ) ] ) decoder.metadata.add( priority=4, "test-override", fun (~metadata:_, _) -> [ ( "title", "Metadata decoder override" ) ] ) meta = file.metadata(fname) test.equal( filter_meta(meta), [ ( "title", "Metadata decoder override" ) ] ) r = request.create( "annotate:title=\"Annotate Override\":#{fname}" ) test.equal(request.resolve(r), true) test.equal( filter_meta(request.metadata(r)), [ ( "title", "Annotate Override" ) ] ) decoder.metadata.add( priority=10, "test-override-annotate", fun (~metadata:_, _) -> [ ( "title", "Metadata decoder annotate override" ) ] ) r = request.create( "annotate:title=\"Annotate Override\":#{fname}" ) test.equal(request.resolve(r), true) test.equal( filter_meta(request.metadata(r)), [ ( "title", "Metadata decoder annotate override" ) ] ) test.pass() end test.check(f) liquidsoap-2.4.2/tests/streams/never.liq000066400000000000000000000003411513273233300203370ustar00rootroot00000000000000#!../../liquidsoap ../test.liq # Test none/never annotations in sources, see #3222. s = (single("file1.png") : source(audio=none, ...)) output.dummy(fallible=true, (s : source(video=canvas))) thread.run(delay=3., test.pass) liquidsoap-2.4.2/tests/streams/no-cue-cut.liq000066400000000000000000000005741513273233300212070ustar00rootroot00000000000000s = playlist( cue_in_metadata=null, cue_out_metadata=null, prefix="annotate:liq_cue_in=1.,liq_cue_out=3.:", "playlist" ) is_first = ref(true) def on_frame() = if is_first() then if 5. <= source.duration(s) then test.pass() else test.fail() end end is_first := false end s.on_frame(synchronous=true, on_frame) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/non_repeating.liq000066400000000000000000000007021513273233300220510ustar00rootroot00000000000000log.level.set(3) first = ref("") def filter(r) = ignore(request.resolve(r)) m = request.metadata(r) if first() == "" then first := m["filename"] false else m["filename"] != first() end end s = playlist( check_next=filter, mode="randomize", reload_mode="rounds", "playlist" ) s = test.check_non_repeating(nb_files=2, nb_rounds=10, s) clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/on_end.liq000066400000000000000000000004741513273233300204710ustar00rootroot00000000000000def f() = s = single("./file1.mp3") s.on_position( remaining=true, position=1.2, synchronous=true, fun (pos, _) -> begin test.almost_equal(pos, s.duration() - s.elapsed()) test.pass() end ) clock.assign_new(sync='none', [s]) output.dummy(s) end test.check(f) liquidsoap-2.4.2/tests/streams/on_end_partial.liq000066400000000000000000000007111513273233300221770ustar00rootroot00000000000000def f() = s = single("./file1.mp3") s.on_position( remaining=true, position=1.2, allow_partial=true, synchronous=true, fun (pos, _) -> begin if s.duration() - s.elapsed() < 1.2 then test.fail() end test.almost_equal(pos, s.duration() - s.elapsed()) test.pass() end ) s.on_track(synchronous=true, fun (_) -> s.skip()) clock.assign_new(sync='none', [s]) output.dummy(s) end test.check(f) liquidsoap-2.4.2/tests/streams/on_frame.liq000066400000000000000000000002661513273233300210140ustar00rootroot00000000000000log.level.set(4) n = ref(0) def f() = n := !n + 1 print( "Frame #{!n}" ) if !n > 10 then test.pass() end end s = sine() s.on_frame(synchronous=true, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/on_metadata.liq000066400000000000000000000004161513273233300214770ustar00rootroot00000000000000log.level.set(4) def f(_) = print( "Got metadata!" ) test.pass() end s = sine() def insert() = print( "Inserting metadata" ) s.insert_metadata([("test", "bla")]) end s.on_metadata(synchronous=true, f) thread.run(delay=1., insert) output.dummy(s) liquidsoap-2.4.2/tests/streams/on_offset.liq000066400000000000000000000005221513273233300212030ustar00rootroot00000000000000def f() = s = single("./file1.mp3") s.on_position( position=1.2, synchronous=true, fun (pos, _) -> begin if s.elapsed() < 1.2 then test.fail() end if pos != s.elapsed() then test.fail() end test.pass() end ) clock.assign_new(sync='none', [s]) output.dummy(s) end test.check(f) liquidsoap-2.4.2/tests/streams/on_offset_partial.liq000066400000000000000000000005001513273233300227130ustar00rootroot00000000000000def f() = s = single("./file1.mp3") s.on_position( position=9999999999., allow_partial=true, synchronous=true, fun (pos, _) -> begin if pos != s.elapsed() then test.fail() end test.pass() end ) clock.assign_new(sync='none', [s]) output.dummy(s) end test.check(f) liquidsoap-2.4.2/tests/streams/on_track.liq000066400000000000000000000002501513273233300210170ustar00rootroot00000000000000log.level.set(4) def f(_) = print( "Got track!" ) test.pass() end s = sequence([sine(duration=1.), sine()]) s.on_track(synchronous=true, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/playlist-watch.liq000066400000000000000000000013171513273233300221710ustar00rootroot00000000000000# Test file watcher logic in playlist. log.level.set(5) def f() = test.skip() reloaded = ref("") def on_reload(uri) = reloaded := uri end s = playlist( on_reload=on_reload, reload_mode="watch", "invalid uri" ) playlist = file.temp("foo", "bar") on_cleanup({file.remove(playlist)}) s.reload(uri=playlist) if !reloaded != playlist then test.fail() end reloaded := "" ignore( process.run( "echo bla >> #{playlist} && sleep 0.1" ) ) if !reloaded != playlist then test.fail() end reloaded := "" s.reload( uri="invalid uri again" ) if !reloaded != "invalid uri again" then test.fail() end test.pass() end test.check(f) liquidsoap-2.4.2/tests/streams/radio.liq000066400000000000000000000003011513273233300203120ustar00rootroot00000000000000# Basic radio test mic = sine() bed = mksafe(playlist("playlist")) radio = add([mic, bed]) output.dummy(fallible=true, radio) def on_done() = test.pass() end thread.run(delay=10., on_done) liquidsoap-2.4.2/tests/streams/radio2.liq000066400000000000000000000006471513273233300204110ustar00rootroot00000000000000# Basic radio test day = playlist("playlist") night = playlist("playlist") jingles = playlist("playlist") # Day / night switch radio = switch([({8h-20h}, day), ({20h-8h}, night)]) # Crossfade radio = crossfade(fade_out=3., fade_in=3., duration=5., radio) # Add jingles radio = random(weights=[1, 4], [jingles, radio]) output.dummy(fallible=true, radio) def on_done() = test.pass() end thread.run(delay=10., on_done) liquidsoap-2.4.2/tests/streams/random.liq000066400000000000000000000025771513273233300205150ustar00rootroot00000000000000# We want: # Track selection 1 -> s1 ready, s2 not ready -> switch to s1 # Track selection 2 -> s1 not ready, s2 ready -> switch to s2 # Track selection 3 -> s1 ready, s2 with weight 0 -> switch to s1 selected = ref([]) s1 = blank(duration=0.04) def m1(_) = [("id", "s1")] end s1 = metadata.map(insert_missing=true, id="s1-map", m1, s1) s1_ready = ref(true) s1 = switch(id="s1", track_sensitive=false, [(s1_ready, s1)]) s1_weight = ref(1) s2 = blank(duration=0.04) def m2(_) = [("id", "s2")] end s2 = metadata.map(insert_missing=true, id="s2-map", m2, s2) s2_ready = ref(false) s2 = switch(id="s2", track_sensitive=false, [(s2_ready, s2)]) s2_weight = ref(1) round = ref(1) def f(m) = if round() == 1 then s1_ready := false s2_ready := true elsif round() == 2 then s1_ready := true s2_ready := true s2_weight := 0 else s1_ready := true s1_weight := 2 s2_ready := true s2_weight := 1 end round := round() + 1 selected := list.cons(m["id"], selected()) end s = random(weights=[s1_weight, s2_weight], [s1, s2]) s.on_track(synchronous=true, f) output.dummy(fallible=true, s) def on_done() = s = list.rev(selected()) if list.nth(default="", s, 0) == "s1" and list.nth(default="", s, 1) == "s2" and list.nth(default="", s, 2) == "s1" then test.pass() else test.fail() end end thread.run(delay=1., on_done) liquidsoap-2.4.2/tests/streams/replaygain.liq000066400000000000000000000024431513273233300213600ustar00rootroot00000000000000#!../../liquidsoap ../test.liq def test_file_replaygain() = g = file.replaygain("replaygain_track_gain.mp3") test.not.equal(g, null) test.almost_equal(null.get(g), -32.) g = file.replaygain("r128_track_gain.mp3") test.not.equal(g, null) test.almost_equal(null.get(g), -16.) g = file.replaygain("replaygain_r128_track_gain.mp3") test.not.equal(g, null) test.almost_equal(null.get(g), -16.) g = file.replaygain("replaygain_track_gain.opus") test.not.equal(g, null) test.almost_equal(null.get(g), -32.) g = file.replaygain("r128_track_gain.opus") test.not.equal(g, null) test.almost_equal(null.get(g), -16.) g = file.replaygain("replaygain_r128_track_gain.opus") test.not.equal(g, null) test.almost_equal(null.get(g), -16.) g = file.replaygain("replaygain_track_gain.mp3", compute=true) test.not.equal(g, null) test.almost_equal(null.get(g), -32.) g = file.replaygain("replaygain_track_gain.mp3", compute=false) test.not.equal(g, null) test.almost_equal(null.get(g), -32.) g = file.replaygain("without_replaygain_track_gain.mp3", compute=true) test.not.equal(g, null) test.almost_equal(null.get(g), 7.39) g = file.replaygain("without_replaygain_track_gain.mp3", compute=false) test.equal(g, null) test.pass() end test.check(test_file_replaygain) liquidsoap-2.4.2/tests/streams/request.liq000066400000000000000000000034641513273233300207210ustar00rootroot00000000000000# Basic request tests settings.log.level.set(4) test.check( fun () -> begin test.almost_equal(digits=1, request.duration("file1.mp3") ?? 0., 5.06) test.almost_equal( digits=1, request.duration("annotate:cue_out=1.1:file1.mp3") ?? 0., 1.1 ) test.almost_equal( digits=1, request.duration("annotate:cue_in=1.06:file1.mp3") ?? 0., 4. ) test.almost_equal( digits=1, request.duration("annotate:cue_in=1.00,cue_out=3.1:file1.mp3") ?? 0., 2.1 ) r = request.dynamic(prefetch=1, fun () -> request.create("invalid")) if not r.add(request.create("file2.mp3")) then print( "Failed to add request" ) test.fail() end r.set_queue([request.create("file2.mp3"), request.create("file3.mp3")]) q = r.queue() if list.length(q) != 2 then print( "Invalid request queue" ) test.fail() end def next() = request.create("file1.mp3") end r = request.dynamic(prefetch=1, next) o = output.dummy(fallible=true, r) thread.run.recurrent( fun () -> if not r.is_ready() then 0.1 else c = r.current() if not null.defined(c) then print( "Invalid current track" ) test.fail() end c = null.get(c) if request.filename(c) != "file1.mp3" then print( "Invalid filename" ) test.fail() end o.stop() test.pass() (-1.) end ) end ) liquidsoap-2.4.2/tests/streams/rotate.liq000066400000000000000000000025151513273233300205230ustar00rootroot00000000000000def mksource(id="", url) = metadata.map( insert_missing=true, id=id, fun (_) -> [("source", id)], mksafe(playlist(url)) ) end jingles = mksource("jingles", "jingles") music = mksource("music", "playlist") music2 = mksource("music2", "playlist") rounds = 10 round_len = 8 played = ref([]) njingles = ref(0) nmusic = ref(0) nmusic2 = ref(0) def ot(m) = label = m["source"] if label != "" then played := [label, ...played()] if label == "music" then ref.incr(nmusic) end if label == "music2" then ref.incr(nmusic2) end if label == "jingles" then ref.incr(njingles) end print( "\n**** track: #{label}\n" ) if list.length(played()) == round_len * rounds then print( "PLAYED: #{list.rev(played())}\n" ) if njingles() == rounds and nmusic() == 3 * rounds and nmusic2() == 4 * rounds then test.pass() else print( "jingles: #{njingles()} / music : #{nmusic()} / music2: #{nmusic2()}" ) test.fail() end end end end radio = rotate(id="rotate", weights=[3, 1], [music, jingles]) radio = rotate(id="rotate2", weights=[1, 1], [radio, music2]) radio.on_track(synchronous=true, ot) clock.assign_new(sync="none", [radio]) output.dummy(fallible=true, radio) liquidsoap-2.4.2/tests/streams/say.liq000066400000000000000000000006351513273233300200220ustar00rootroot00000000000000s = if file.which(null.get(settings.protocol.gtts.path())) == null and file.which(null.get(settings.protocol.text2wave.path())) == null then print( "Could not test gtts" ) chop(every=1., sine()) else single( "say:Hello, world!" ) end n = ref(0) def f(_) = if n() < 1 then n := n() + 1 else test.pass() end end s.on_track(synchronous=true, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/sharing.liq000066400000000000000000000005361513273233300206610ustar00rootroot00000000000000#!../../liquidsoap ../test.liq # Test whether modifying one source does not affect a copy of it. See #2170. s = sine() s = add(normalize=false, [s, amplify(0., s)]) s = rms(s) rms = s.rms output.dummy(s) def on_done() = print( "RMS: #{rms()}" ) if rms() > 0.5 then test.pass() else test.fail() end end thread.run(delay=2., on_done) liquidsoap-2.4.2/tests/streams/sine.detect.full_conv.liq000066400000000000000000000004701513273233300234160ustar00rootroot00000000000000log.level.set(4) def f(freq) = print( "Detected sine at #{freq}Hz." ) test.pass() end s = sine(440.) s = (s : source(audio=pcm)) s = audio.encode.pcm_s16(s) s = audio.decode.pcm_s16(s) s = audio.encode.pcm_f32(s) s = audio.decode.pcm_f32(s) s = sine.detect(debug=false, [440.], s, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/sine.detect.liq000066400000000000000000000002541513273233300214300ustar00rootroot00000000000000log.level.set(4) def f(freq) = print( "Detected sine at #{freq}Hz." ) test.pass() end s = sine(440.) s = sine.detect(debug=false, [440.], s, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/sine.detect.pcm_f32.liq000066400000000000000000000003501513273233300226550ustar00rootroot00000000000000log.level.set(4) def f(freq) = print( "Detected sine at #{freq}Hz." ) test.pass() end s = sine(440.) s = (s : source(audio=pcm_s16)) s = audio.decode.pcm_s16(s) s = sine.detect(debug=false, [440.], s, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/sine.detect.pcm_s16.liq000066400000000000000000000003501513273233300226740ustar00rootroot00000000000000log.level.set(4) def f(freq) = print( "Detected sine at #{freq}Hz." ) test.pass() end s = sine(440.) s = (s : source(audio=pcm_s16)) s = audio.decode.pcm_s16(s) s = sine.detect(debug=false, [440.], s, f) output.dummy(s) liquidsoap-2.4.2/tests/streams/soundtouch-tracks.liq000066400000000000000000000004511513273233300227020ustar00rootroot00000000000000# Ensure that soundtouch preserves tracks, see #2467. n = ref(0) def ot(_) = print( "New track!" ) ref.incr(n) if n() >= 3 then test.pass() shutdown() end end s = chop(every=1., sine()) s = soundtouch(s) s.on_track(synchronous=true, ot) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/streams/source-cue.liq000077500000000000000000000053541513273233300213060ustar00rootroot00000000000000#!../../liquidsoap ../test.liq append_cuefile = file.temp("append", ".cue") create_cuefile = file.temp("create", ".cue") on_cleanup( { file.remove(append_cuefile) file.remove(create_cuefile) } ) s = sine() track_pos = ref(1) def f(m) = track_pos := track_pos() + 1 s.insert_metadata(new_track=true, m) end s = source.cue(title="tit", performer="perf", file="bla.mp3", append_cuefile, s) s = source.cue( title="tit", performer="perf", file="bla.mp3", comment="this is a comment", year=2023, last_tracks=2, map_metadata=fun (m) -> list.assoc.filter_map( fun (x, y) -> if y == "title2" then ( x, "title 2" ) else (x, y) end, m ), create_cuefile, s ) append_expected = 'TITLE "tit" PERFORMER "perf" FILE "bla.mp3" MP3 TRACK 01 AUDIO TITLE "title1" PERFORMER "artist1" REM ALBUM "album1" REM DATE 2021 ISRC bla INDEX 01 00:00:00 TRACK 02 AUDIO TITLE "title2" PERFORMER "artist2" REM ALBUM "album2" INDEX 01 00:02:00 TRACK 03 AUDIO TITLE "title3" PERFORMER "artist3" REM ALBUM "album3" INDEX 01 00:03:00\n' create_expected = 'TITLE "tit" PERFORMER "perf" REM COMMENT "this is a comment" REM DATE 2023 FILE "bla.mp3" MP3 TRACK 02 AUDIO TITLE "title 2" PERFORMER "artist2" REM ALBUM "album2" INDEX 01 00:02:00 TRACK 03 AUDIO TITLE "title3" PERFORMER "artist3" REM ALBUM "album3" INDEX 01 00:03:00\n' def check() = test.equal(file.contents(append_cuefile), append_expected) test.equal(file.contents(create_cuefile), create_expected) test.pass() end s.on_frame( synchronous=true, fun () -> begin if track_pos() == 1 and source.time(s) == 0. then f( [ ("artist", "artist1"), ("album", "album1"), ("title", "title1"), ("cue_year", "2021"), ("isrc", "bla") ] ) elsif track_pos() == 2 and source.time(s) == 2. then f([("artist", "artist2"), ("album", "album2"), ("title", "title2")]) elsif track_pos() == 2 and source.time(s) == 2.1 then # This one should be deduplicated. f( [ ("artist", "artist2"), ("album", "album2"), ("title", "title2"), ("foo", "bar") ] ) elsif track_pos() == 3 and source.time(s) == 3. then f([("artist", "artist3"), ("album", "album3"), ("title", "title3")]) elsif track_pos() == 4 then check() end end ) clock.assign_new(sync="none", [s]) output.dummy(s) liquidsoap-2.4.2/tests/streams/srt_listen_callback.liq000066400000000000000000000007561513273233300232340ustar00rootroot00000000000000log.level.set(4) port = 8001 settings.srt.prefer_address := "ipv4" def fn() = def listen_callback(~hs_version:_, ~peeraddr:_, ~streamid, _) = if streamid == null("foobar") then test.pass() end false end output.srt( %wav, blank(), port=port, mode="listener", listen_callback=listen_callback ) thread.run( delay=1., { output.dummy( mksafe(input.srt(mode="caller", port=port, streamid="foobar")) ) } ) end test.check(fn) liquidsoap-2.4.2/tests/streams/srt_multiple_outputs.liq000066400000000000000000000006771513273233300235620ustar00rootroot00000000000000port = 8002 settings.srt.prefer_address := "ipv4" def fn() = connected = ref(0) def on_connect() = connected := !connected + 1 if !connected == 2 then test.pass() end end o = output.srt(%wav, blank(), port=port, mode="listener") o.on_connect(synchronous=true, on_connect) output.dummy(fallible=true, input.srt(mode="caller", port=port)) output.dummy(fallible=true, input.srt(mode="caller", port=port)) end test.check(fn) liquidsoap-2.4.2/tests/streams/srt_passphrase.liq000066400000000000000000000005651513273233300222710ustar00rootroot00000000000000port = 8003 settings.srt.prefer_address := "ipv4" def fn() = o = output.srt( %wav, blank(), port=port, mode="listener", passphrase="foobarfoobarfoobar" ) o.on_connect(synchronous=true, test.pass) output.dummy( fallible=true, input.srt(mode="caller", port=port, passphrase="foobarfoobarfoobar") ) end test.check(fn) liquidsoap-2.4.2/tests/streams/srt_raw_pcm.liq000066400000000000000000000006261513273233300215460ustar00rootroot00000000000000log.level := 4 settings.srt.prefer_address := "ipv4" port = 8004 s = input.srt( port=port, content_type="application/ffmpeg;format=s16le,ch_layout=mono,sample_rate=48000" ) s.on_track(synchronous=true, fun (_) -> test.pass()) output.dummy(fallible=true, s) enc = %ffmpeg(format = "s16le", %audio(codec = "pcm_s16le", ac = 1, ar = 48000)) output.srt(fallible=true, port=port, enc, sine()) liquidsoap-2.4.2/tests/streams/stretch-clock-propagation.liq000066400000000000000000000006401513273233300243100ustar00rootroot00000000000000# Stretch (as well as soundtouch) uses the Child_support clock control # mechanism. We want to make sure that it properly propagates end-of-tracks # to its underlying outputs/sources. s = once(blank(duration=10.)) def on_stop() = test.pass() end o = output.dummy(fallible=true, s) o.on_stop(synchronous=true, on_stop) s = stretch(ratio=2., s) #clock.assign_new(sync="none", [s]) output.dummy(fallible=true, s) liquidsoap-2.4.2/tests/test.liq000066400000000000000000000123411513273233300165240ustar00rootroot00000000000000log.level := 4 settings.init.force_start := true test = () let test.done = ref(false) runtime.gc.set(runtime.gc.get().{space_overhead=20, allocation_policy=2}) # End test successfully. def test.pass() = if not test.done() then test.done := true print( "Test passed!" ) shutdown() end end # End test with a failure. def test.fail(reason=null) = reason = if null.defined(reason) then ": #{null.get(reason)}" else "!" end print( "Test failed#{reason}" ) exit(1) end # End test with signal 2 def test.skip() = print( "Test skipped.." ) exit(123) end # Check that files are never repeated in source s, possibly by rounds. The # function triggers test.fail on repeated filenames, only clearing its list of # seen filenames once all nb_files have been seen. def test.check_non_repeating(~nb_files, ~nb_rounds, s) = # List of seen filenames seen = ref([]) # Number of rounds to test iterations = ref(0) def already(fname) = list.assoc(default=false, fname, seen()) end def check(m) = fname = m["filename"] print( "I: Playing #{fname}" ) if iterations() < nb_rounds and already(fname) then print( "I: Already seen #{fname}" ) test.fail() else if list.length(seen()) < nb_files - 1 then seen := list.add((fname, true), seen()) else print( "I: ===" ) seen := [] iterations := iterations() + 1 if iterations() == nb_rounds then print( "I: Test passed" ) test.pass() end end end end s.on_track(synchronous=true, check) s end def test.equal(v, v') = if # We compare strings for methods and plain object for type. v != v' or "#{v}" != "#{v'}" then msg = "expected:\r\n#{string.quote(string(v'))}\r\ngot:\r\n#{ string.quote(string(v)) }" print(msg) thread.run({test.fail()}) error.raise(error.failure, msg) end end let test.not = () def test.not.equal(first, second) = if first == second or "#{first}" == "#{second}" then msg = "expected\r\n #{string.quote(string(first))}\r\nto differ from:\r\n#{ string.quote(string(second)) }" print(msg) thread.run({test.fail()}) error.raise(error.failure, msg) end end # Compares two float values for approximate equality. # Values are considered almost equal if their difference # is within 10^-digits. # @param ~digits The number of decimal digits to compare. Default is 7. # @flag hidden def test._almost_equal(~digits=7, first, second) = diff = abs(float(first) - float(second)) if diff * float(pow(10, digits)) < 0.5 then true.{diff=diff} else false.{diff=diff} end end # Compares two float values for approximate equality. # Values are considered almost equal if their difference # is within 10^-digits. # @param ~digits The number of decimal digits to compare. Default is 7. def test.almost_equal(~digits=7, first, second) = is_almost_equal = test._almost_equal(digits=digits, first, second) if not is_almost_equal then thread.run({test.fail()}) error.raise( error.failure, "#{string.quote(string(first))} != #{ string.quote(string(second)) } up to #{digits} digits, diff = #{is_almost_equal.diff}." ) end end # Compares two float values for approximate inequality. # Values are considered not almost equal if their difference # is greater than 10^-digits. # @param ~digits The number of decimal digits to compare. Default is 7. def test.not_almost_equal(~digits=7, first, second) = is_almost_equal = test._almost_equal(digits=digits, first, second) if is_almost_equal then thread.run({test.fail()}) error.raise( error.failure, "#{string.quote(string(first))} == #{ string.quote(string(second)) } up to #{digits} digits, diff = #{is_almost_equal.diff}." ) end end def test.raises(fn, e=null) = try ignore(fn()) error.raise( error.failure, "Exception #{e} was not raised!" ) catch exn do if null.defined(e) then e = null.get(e) if exn.kind != e.kind then error.raise( error.failure, "An exception #{exn} of kind #{exn.kind} was raised instead of an \ error of kind #{e.kind}." ) end end () end end # Asynchronous test handler with dummy output # Best practice is to run all manual tests # through this one. def test.check(f) = thread.run(delay=0.1, f) end # Add a metric def test.metric(~category, ~name, ~value, ~unit, ~min:min_value=null) = min_value = null.defined(min_value) ? "\n min: #{(null.get(min_value) : float)}" : "" data = "- name: \"#{(name : string)}\"\n category: \"#{(category : string)}\"\n \ value: #{(value : float)}\n unit: \"#{(unit : string)}\"\n time: #{ (time() : float) }#{min_value}\n\n" file.write(append=true, data=data, "/tmp/metrics.yaml") end def on_error(~backtrace, err) = print( "Uncaught error while running test: #{err}\n#{backtrace}" ) test.fail() end thread.on_error(null, on_error)