From 3f71a76fdd253c91a8d9a99ebe5523971a3324cb Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Fri, 1 May 2026 00:05:55 +0100 Subject: [PATCH 1/2] Start of work to use yaml for some of the configuration --- .gitignore | 3 + Makefile | 21 +- docker-setup.sh | 27 +- files/datafiles/andys_computer.R | 6 - files/datafiles/back_store.R | 6 - files/datafiles/box.R | 2 - files/datafiles/car_park.R | 4 - files/datafiles/conference.R | 4 - files/datafiles/config | 87 - files/datafiles/config.yaml.sample | 73 + files/datafiles/config2 | 63 - files/datafiles/crate.R | 4 - files/datafiles/cupboard.R | 7 - files/datafiles/directors.R | 5 - files/datafiles/help.yaml.sample | 1608 +++++++++++++ files/datafiles/library.R | 4 - files/datafiles/main_office.R | 6 - files/datafiles/reception.R | 6 - files/datafiles/rooms.yaml.sample | 206 ++ files/datafiles/safe.R | 1 - files/datafiles/store_room.R | 4 - files/datafiles/toilets.R | 1 - files/datafiles/wizroom.R | 8 - files/helpfiles/accreq | 7 - files/helpfiles/addhistory | 4 - files/helpfiles/adminfiles | 6 - files/helpfiles/afk | 11 - files/helpfiles/allclones | 4 - files/helpfiles/arrest | 10 - files/helpfiles/autopromo | 4 - files/helpfiles/ban | 5 - files/helpfiles/bank | 8 - files/helpfiles/bbcast | 6 - files/helpfiles/bcast | 6 - files/helpfiles/beep | 5 - files/helpfiles/bfrom | 6 - files/helpfiles/bring | 4 - files/helpfiles/cafk | 4 - files/helpfiles/calendar | 10 - files/helpfiles/call | 12 - files/helpfiles/cash | 4 - files/helpfiles/cbuff | 5 - files/helpfiles/cedit | 4 - files/helpfiles/cemote | 4 - files/helpfiles/charecho | 9 - files/helpfiles/chear | 7 - files/helpfiles/clearline | 7 - files/helpfiles/clone | 6 - files/helpfiles/cls | 4 - files/helpfiles/cmdcount | 5 - files/helpfiles/cname | 9 - files/helpfiles/colour | 6 - files/helpfiles/connect | 5 - files/helpfiles/copyto | 7 - files/helpfiles/create | 6 - files/helpfiles/csay | 6 - files/helpfiles/cshout | 4 - files/helpfiles/ctells | 4 - files/helpfiles/ctopic | 5 - files/helpfiles/demote | 9 - files/helpfiles/desc | 6 - files/helpfiles/destroy | 6 - files/helpfiles/disconnect | 4 - files/helpfiles/dmail | 12 - files/helpfiles/donate | 4 - files/helpfiles/dsug | 12 - files/helpfiles/dump | 10 - files/helpfiles/echo | 6 - files/helpfiles/emote | 9 - files/helpfiles/entpro | 7 - files/helpfiles/ewiz | 8 - files/helpfiles/examine | 5 - files/helpfiles/expire | 7 - files/helpfiles/femote | 5 - files/helpfiles/files | 6 - files/helpfiles/fix | 6 - files/helpfiles/flagged | 6 - files/helpfiles/fmail | 6 - files/helpfiles/force | 6 - files/helpfiles/forwarding | 4 - files/helpfiles/friends | 8 - files/helpfiles/from | 7 - files/helpfiles/fsay | 5 - files/helpfiles/fsmail | 6 - files/helpfiles/fwho | 5 - files/helpfiles/gcom | 8 - files/helpfiles/go | 7 - files/helpfiles/greet | 5 - files/helpfiles/grepu | 11 - files/helpfiles/guess | 4 - files/helpfiles/hangman | 7 - files/helpfiles/help | 6 - files/helpfiles/history | 5 - files/helpfiles/home | 7 - files/helpfiles/ignall | 5 - files/helpfiles/ignbeeps | 4 - files/helpfiles/igngreets | 4 - files/helpfiles/ignlist | 5 - files/helpfiles/ignlogons | 5 - files/helpfiles/ignpics | 4 - files/helpfiles/ignshout | 4 - files/helpfiles/igntell | 4 - files/helpfiles/ignuser | 7 - files/helpfiles/ignwiz | 5 - files/helpfiles/inmsg | 7 - files/helpfiles/invis | 5 - files/helpfiles/invite | 5 - files/helpfiles/join | 5 - files/helpfiles/kill | 4 - files/helpfiles/knock | 5 - files/helpfiles/last | 5 - files/helpfiles/lban | 6 - files/helpfiles/listen | 5 - files/helpfiles/lmail | 9 - files/helpfiles/logging | 8 - files/helpfiles/look | 5 - files/helpfiles/macros | 5 - files/helpfiles/makeinvis | 4 - files/helpfiles/makevis | 4 - files/helpfiles/map | 4 - files/helpfiles/memcount | 4 - files/helpfiles/minlogin | 9 - files/helpfiles/mode | 8 - files/helpfiles/money | 10 - files/helpfiles/monitor | 6 - files/helpfiles/move | 7 - files/helpfiles/mutter | 4 - files/helpfiles/muzzle | 8 - files/helpfiles/mybgone | 6 - files/helpfiles/myclones | 5 - files/helpfiles/mykey | 9 - files/helpfiles/mylock | 7 - files/helpfiles/myname | 6 - files/helpfiles/mypaint | 5 - files/helpfiles/myroom | 8 - files/helpfiles/netdata | 7 - files/helpfiles/netstat | 4 - files/helpfiles/news | 4 - files/helpfiles/nocopys | 5 - files/helpfiles/nuke | 5 - files/helpfiles/outmsg | 7 - files/helpfiles/passwd | 6 - files/helpfiles/pemote | 5 - files/helpfiles/people | 7 - files/helpfiles/picture | 4 - files/helpfiles/preview | 6 - files/helpfiles/private | 8 - files/helpfiles/promote | 9 - files/helpfiles/prompt | 10 - files/helpfiles/ptell | 4 - files/helpfiles/public | 7 - files/helpfiles/purge | 16 - files/helpfiles/quit | 4 - files/helpfiles/ranks | 5 - files/helpfiles/rcountu | 7 - files/helpfiles/read | 8 - files/helpfiles/reboot | 5 - files/helpfiles/recaps | 4 - files/helpfiles/recount | 6 - files/helpfiles/reload | 4 - files/helpfiles/reminder | 10 - files/helpfiles/resite | 8 - files/helpfiles/retire | 5 - files/helpfiles/revafk | 5 - files/helpfiles/revedit | 5 - files/helpfiles/review | 5 - files/helpfiles/revshout | 4 - files/helpfiles/revtell | 4 - files/helpfiles/rloadrm | 9 - files/helpfiles/rmadmin | 12 - files/helpfiles/rmail | 8 - files/helpfiles/rnet | 5 - files/helpfiles/rooms | 6 - files/helpfiles/rstat | 5 - files/helpfiles/rsug | 4 - files/helpfiles/rules | 4 - files/helpfiles/samesite | 9 - files/helpfiles/save | 4 - files/helpfiles/say | 5 - files/helpfiles/sayto | 7 - files/helpfiles/search | 9 - files/helpfiles/semote | 6 - files/helpfiles/set | 11 - files/helpfiles/set_age | 4 - files/helpfiles/set_alert | 7 - files/helpfiles/set_autofwd | 7 - files/helpfiles/set_colour | 6 - files/helpfiles/set_command | 7 - files/helpfiles/set_email | 16 - files/helpfiles/set_gender | 5 - files/helpfiles/set_hide | 9 - files/helpfiles/set_icq | 6 - files/helpfiles/set_pager | 7 - files/helpfiles/set_password | 7 - files/helpfiles/set_rdesc | 7 - files/helpfiles/set_recap | 6 - files/helpfiles/set_revbuf | 5 - files/helpfiles/set_room | 9 - files/helpfiles/set_show | 11 - files/helpfiles/set_wrap | 7 - files/helpfiles/set_www | 6 - files/helpfiles/setcmdlev | 8 - files/helpfiles/sfrom | 5 - files/helpfiles/shackle | 4 - files/helpfiles/shoot | 5 - files/helpfiles/shout | 5 - files/helpfiles/show | 7 - files/helpfiles/shutdown | 5 - files/helpfiles/sing | 5 - files/helpfiles/site | 5 - files/helpfiles/smail | 7 - files/helpfiles/sos | 6 - files/helpfiles/spodlist | 11 - files/helpfiles/sreboot | 6 - files/helpfiles/suggest | 6 - files/helpfiles/suicide | 5 - files/helpfiles/swban | 9 - files/helpfiles/switch | 7 - files/helpfiles/system | 7 - files/helpfiles/tell | 6 - files/helpfiles/terminal | 4 - files/helpfiles/think | 5 - files/helpfiles/time | 5 - files/helpfiles/topic | 4 - files/helpfiles/tpromote | 11 - files/helpfiles/twiz | 7 - files/helpfiles/unarrest | 8 - files/helpfiles/unban | 5 - files/helpfiles/uncall | 4 - files/helpfiles/unfix | 6 - files/helpfiles/uninvite | 5 - files/helpfiles/unmuzzle | 5 - files/helpfiles/unretire | 5 - files/helpfiles/unshackle | 5 - files/helpfiles/ustat | 5 - files/helpfiles/verify | 10 - files/helpfiles/version | 5 - files/helpfiles/viewlog | 6 - files/helpfiles/vis | 4 - files/helpfiles/visit | 6 - files/helpfiles/wake | 5 - files/helpfiles/who | 6 - files/helpfiles/wipe | 14 - files/helpfiles/wizlist | 5 - files/helpfiles/write | 5 - files/helpfiles/wrules | 5 - files/helpfiles/xcom | 8 - src/amnuts.c | 1018 +------- src/commands/help.c | 92 +- src/commands/reload_room.c | 122 +- src/includes/globals.h | 18 + src/includes/prototypes.h | 19 +- src/includes/yaml.h | 33 + src/vendors/libyaml/LICENSE | 20 + src/vendors/libyaml/api.c | 1393 +++++++++++ src/vendors/libyaml/dumper.c | 394 +++ src/vendors/libyaml/emitter.c | 2358 ++++++++++++++++++ src/vendors/libyaml/loader.c | 544 +++++ src/vendors/libyaml/parser.c | 1375 +++++++++++ src/vendors/libyaml/reader.c | 469 ++++ src/vendors/libyaml/scanner.c | 3598 ++++++++++++++++++++++++++++ src/vendors/libyaml/writer.c | 141 ++ src/vendors/libyaml/yaml.h | 1985 +++++++++++++++ src/vendors/libyaml/yaml_private.h | 684 ++++++ src/yaml_config.c | 868 +++++++ src/yaml_gate.c | 96 + src/yaml_help.c | 223 ++ src/yaml_rooms.c | 663 +++++ src/yaml_util.c | 112 + supervisord.conf | 8 + utils/convert_to_yaml.py | 478 ++++ 271 files changed, 17536 insertions(+), 2741 deletions(-) delete mode 100644 files/datafiles/andys_computer.R delete mode 100644 files/datafiles/back_store.R delete mode 100644 files/datafiles/box.R delete mode 100644 files/datafiles/car_park.R delete mode 100644 files/datafiles/conference.R delete mode 100644 files/datafiles/config create mode 100644 files/datafiles/config.yaml.sample delete mode 100644 files/datafiles/config2 delete mode 100644 files/datafiles/crate.R delete mode 100644 files/datafiles/cupboard.R delete mode 100644 files/datafiles/directors.R create mode 100644 files/datafiles/help.yaml.sample delete mode 100644 files/datafiles/library.R delete mode 100644 files/datafiles/main_office.R delete mode 100644 files/datafiles/reception.R create mode 100644 files/datafiles/rooms.yaml.sample delete mode 100644 files/datafiles/safe.R delete mode 100644 files/datafiles/store_room.R delete mode 100644 files/datafiles/toilets.R delete mode 100644 files/datafiles/wizroom.R delete mode 100644 files/helpfiles/accreq delete mode 100644 files/helpfiles/addhistory delete mode 100644 files/helpfiles/adminfiles delete mode 100644 files/helpfiles/afk delete mode 100644 files/helpfiles/allclones delete mode 100644 files/helpfiles/arrest delete mode 100644 files/helpfiles/autopromo delete mode 100644 files/helpfiles/ban delete mode 100644 files/helpfiles/bank delete mode 100644 files/helpfiles/bbcast delete mode 100644 files/helpfiles/bcast delete mode 100644 files/helpfiles/beep delete mode 100644 files/helpfiles/bfrom delete mode 100644 files/helpfiles/bring delete mode 100644 files/helpfiles/cafk delete mode 100644 files/helpfiles/calendar delete mode 100644 files/helpfiles/call delete mode 100644 files/helpfiles/cash delete mode 100644 files/helpfiles/cbuff delete mode 100644 files/helpfiles/cedit delete mode 100644 files/helpfiles/cemote delete mode 100644 files/helpfiles/charecho delete mode 100644 files/helpfiles/chear delete mode 100644 files/helpfiles/clearline delete mode 100644 files/helpfiles/clone delete mode 100644 files/helpfiles/cls delete mode 100644 files/helpfiles/cmdcount delete mode 100644 files/helpfiles/cname delete mode 100644 files/helpfiles/colour delete mode 100644 files/helpfiles/connect delete mode 100644 files/helpfiles/copyto delete mode 100644 files/helpfiles/create delete mode 100644 files/helpfiles/csay delete mode 100644 files/helpfiles/cshout delete mode 100644 files/helpfiles/ctells delete mode 100644 files/helpfiles/ctopic delete mode 100644 files/helpfiles/demote delete mode 100644 files/helpfiles/desc delete mode 100644 files/helpfiles/destroy delete mode 100644 files/helpfiles/disconnect delete mode 100644 files/helpfiles/dmail delete mode 100644 files/helpfiles/donate delete mode 100644 files/helpfiles/dsug delete mode 100644 files/helpfiles/dump delete mode 100644 files/helpfiles/echo delete mode 100644 files/helpfiles/emote delete mode 100644 files/helpfiles/entpro delete mode 100644 files/helpfiles/ewiz delete mode 100644 files/helpfiles/examine delete mode 100644 files/helpfiles/expire delete mode 100644 files/helpfiles/femote delete mode 100644 files/helpfiles/files delete mode 100644 files/helpfiles/fix delete mode 100644 files/helpfiles/flagged delete mode 100644 files/helpfiles/fmail delete mode 100644 files/helpfiles/force delete mode 100644 files/helpfiles/forwarding delete mode 100644 files/helpfiles/friends delete mode 100644 files/helpfiles/from delete mode 100644 files/helpfiles/fsay delete mode 100644 files/helpfiles/fsmail delete mode 100644 files/helpfiles/fwho delete mode 100644 files/helpfiles/gcom delete mode 100644 files/helpfiles/go delete mode 100644 files/helpfiles/greet delete mode 100644 files/helpfiles/grepu delete mode 100644 files/helpfiles/guess delete mode 100644 files/helpfiles/hangman delete mode 100644 files/helpfiles/help delete mode 100644 files/helpfiles/history delete mode 100644 files/helpfiles/home delete mode 100644 files/helpfiles/ignall delete mode 100644 files/helpfiles/ignbeeps delete mode 100644 files/helpfiles/igngreets delete mode 100644 files/helpfiles/ignlist delete mode 100644 files/helpfiles/ignlogons delete mode 100644 files/helpfiles/ignpics delete mode 100644 files/helpfiles/ignshout delete mode 100644 files/helpfiles/igntell delete mode 100644 files/helpfiles/ignuser delete mode 100644 files/helpfiles/ignwiz delete mode 100644 files/helpfiles/inmsg delete mode 100644 files/helpfiles/invis delete mode 100644 files/helpfiles/invite delete mode 100644 files/helpfiles/join delete mode 100644 files/helpfiles/kill delete mode 100644 files/helpfiles/knock delete mode 100644 files/helpfiles/last delete mode 100644 files/helpfiles/lban delete mode 100644 files/helpfiles/listen delete mode 100644 files/helpfiles/lmail delete mode 100644 files/helpfiles/logging delete mode 100644 files/helpfiles/look delete mode 100644 files/helpfiles/macros delete mode 100644 files/helpfiles/makeinvis delete mode 100644 files/helpfiles/makevis delete mode 100644 files/helpfiles/map delete mode 100644 files/helpfiles/memcount delete mode 100644 files/helpfiles/minlogin delete mode 100644 files/helpfiles/mode delete mode 100644 files/helpfiles/money delete mode 100644 files/helpfiles/monitor delete mode 100644 files/helpfiles/move delete mode 100644 files/helpfiles/mutter delete mode 100644 files/helpfiles/muzzle delete mode 100644 files/helpfiles/mybgone delete mode 100644 files/helpfiles/myclones delete mode 100644 files/helpfiles/mykey delete mode 100644 files/helpfiles/mylock delete mode 100644 files/helpfiles/myname delete mode 100644 files/helpfiles/mypaint delete mode 100644 files/helpfiles/myroom delete mode 100644 files/helpfiles/netdata delete mode 100644 files/helpfiles/netstat delete mode 100644 files/helpfiles/news delete mode 100644 files/helpfiles/nocopys delete mode 100644 files/helpfiles/nuke delete mode 100644 files/helpfiles/outmsg delete mode 100644 files/helpfiles/passwd delete mode 100644 files/helpfiles/pemote delete mode 100644 files/helpfiles/people delete mode 100644 files/helpfiles/picture delete mode 100644 files/helpfiles/preview delete mode 100644 files/helpfiles/private delete mode 100644 files/helpfiles/promote delete mode 100644 files/helpfiles/prompt delete mode 100644 files/helpfiles/ptell delete mode 100644 files/helpfiles/public delete mode 100644 files/helpfiles/purge delete mode 100644 files/helpfiles/quit delete mode 100644 files/helpfiles/ranks delete mode 100644 files/helpfiles/rcountu delete mode 100644 files/helpfiles/read delete mode 100644 files/helpfiles/reboot delete mode 100644 files/helpfiles/recaps delete mode 100644 files/helpfiles/recount delete mode 100644 files/helpfiles/reload delete mode 100644 files/helpfiles/reminder delete mode 100644 files/helpfiles/resite delete mode 100644 files/helpfiles/retire delete mode 100644 files/helpfiles/revafk delete mode 100644 files/helpfiles/revedit delete mode 100644 files/helpfiles/review delete mode 100644 files/helpfiles/revshout delete mode 100644 files/helpfiles/revtell delete mode 100644 files/helpfiles/rloadrm delete mode 100644 files/helpfiles/rmadmin delete mode 100644 files/helpfiles/rmail delete mode 100644 files/helpfiles/rnet delete mode 100644 files/helpfiles/rooms delete mode 100644 files/helpfiles/rstat delete mode 100644 files/helpfiles/rsug delete mode 100644 files/helpfiles/rules delete mode 100644 files/helpfiles/samesite delete mode 100644 files/helpfiles/save delete mode 100644 files/helpfiles/say delete mode 100644 files/helpfiles/sayto delete mode 100644 files/helpfiles/search delete mode 100644 files/helpfiles/semote delete mode 100644 files/helpfiles/set delete mode 100644 files/helpfiles/set_age delete mode 100644 files/helpfiles/set_alert delete mode 100644 files/helpfiles/set_autofwd delete mode 100644 files/helpfiles/set_colour delete mode 100644 files/helpfiles/set_command delete mode 100644 files/helpfiles/set_email delete mode 100644 files/helpfiles/set_gender delete mode 100644 files/helpfiles/set_hide delete mode 100644 files/helpfiles/set_icq delete mode 100644 files/helpfiles/set_pager delete mode 100644 files/helpfiles/set_password delete mode 100644 files/helpfiles/set_rdesc delete mode 100644 files/helpfiles/set_recap delete mode 100644 files/helpfiles/set_revbuf delete mode 100644 files/helpfiles/set_room delete mode 100644 files/helpfiles/set_show delete mode 100644 files/helpfiles/set_wrap delete mode 100644 files/helpfiles/set_www delete mode 100644 files/helpfiles/setcmdlev delete mode 100644 files/helpfiles/sfrom delete mode 100644 files/helpfiles/shackle delete mode 100644 files/helpfiles/shoot delete mode 100644 files/helpfiles/shout delete mode 100644 files/helpfiles/show delete mode 100644 files/helpfiles/shutdown delete mode 100644 files/helpfiles/sing delete mode 100644 files/helpfiles/site delete mode 100644 files/helpfiles/smail delete mode 100644 files/helpfiles/sos delete mode 100644 files/helpfiles/spodlist delete mode 100644 files/helpfiles/sreboot delete mode 100644 files/helpfiles/suggest delete mode 100644 files/helpfiles/suicide delete mode 100644 files/helpfiles/swban delete mode 100644 files/helpfiles/switch delete mode 100644 files/helpfiles/system delete mode 100644 files/helpfiles/tell delete mode 100644 files/helpfiles/terminal delete mode 100644 files/helpfiles/think delete mode 100644 files/helpfiles/time delete mode 100644 files/helpfiles/topic delete mode 100644 files/helpfiles/tpromote delete mode 100644 files/helpfiles/twiz delete mode 100644 files/helpfiles/unarrest delete mode 100644 files/helpfiles/unban delete mode 100644 files/helpfiles/uncall delete mode 100644 files/helpfiles/unfix delete mode 100644 files/helpfiles/uninvite delete mode 100644 files/helpfiles/unmuzzle delete mode 100644 files/helpfiles/unretire delete mode 100644 files/helpfiles/unshackle delete mode 100644 files/helpfiles/ustat delete mode 100644 files/helpfiles/verify delete mode 100644 files/helpfiles/version delete mode 100644 files/helpfiles/viewlog delete mode 100644 files/helpfiles/vis delete mode 100644 files/helpfiles/visit delete mode 100644 files/helpfiles/wake delete mode 100644 files/helpfiles/who delete mode 100644 files/helpfiles/wipe delete mode 100644 files/helpfiles/wizlist delete mode 100644 files/helpfiles/write delete mode 100644 files/helpfiles/wrules delete mode 100644 files/helpfiles/xcom create mode 100644 src/includes/yaml.h create mode 100644 src/vendors/libyaml/LICENSE create mode 100644 src/vendors/libyaml/api.c create mode 100644 src/vendors/libyaml/dumper.c create mode 100644 src/vendors/libyaml/emitter.c create mode 100644 src/vendors/libyaml/loader.c create mode 100644 src/vendors/libyaml/parser.c create mode 100644 src/vendors/libyaml/reader.c create mode 100644 src/vendors/libyaml/scanner.c create mode 100644 src/vendors/libyaml/writer.c create mode 100644 src/vendors/libyaml/yaml.h create mode 100644 src/vendors/libyaml/yaml_private.h create mode 100644 src/yaml_config.c create mode 100644 src/yaml_gate.c create mode 100644 src/yaml_help.c create mode 100644 src/yaml_rooms.c create mode 100644 src/yaml_util.c create mode 100644 utils/convert_to_yaml.py diff --git a/.gitignore b/.gitignore index 9489a22..c7600df 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ amnutsIdent amnutsTalker supervisord.pid supervisord.log +files/datafiles/config.yml +files/datafiles/rooms.yml +files/datafiles/.yml diff --git a/Makefile b/Makefile index e43ec15..1f2860e 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,12 @@ VENDOR_LIBTELNET_OBJ_DIR = $(TALKER_OBJ_DIR) VENDOR_LIBTELNET_SRC = $(wildcard $(VENDOR_LIBTELNET_SRC_DIR)/*.c) VENDOR_LIBTELNET_OBJS = $(addprefix $(VENDOR_LIBTELNET_OBJ_DIR)/,$(notdir $(VENDOR_LIBTELNET_SRC:.c=.o))) +# libyaml: https://github.com/yaml/libyaml +VENDOR_LIBYAML_SRC_DIR = $(TALKER_SRC_DIR)/vendors/libyaml +VENDOR_LIBYAML_OBJ_DIR = $(TALKER_OBJ_DIR) +VENDOR_LIBYAML_SRC = $(wildcard $(VENDOR_LIBYAML_SRC_DIR)/*.c) +VENDOR_LIBYAML_OBJS = $(addprefix $(VENDOR_LIBYAML_OBJ_DIR)/,$(notdir $(VENDOR_LIBYAML_SRC:.c=.o))) + # # Platform-specific libraries that need to be included # @@ -94,6 +100,7 @@ distclean: clean rm -f $(IDENTD_SRC_DIR)/*.[ch]~ $(IDENTD_SRC_DIR)/*.[ch].bak rm -f $(VENDOR_SDS_SRC_DIR)/*.[ch]~ $(VENDOR_SDS_SRC_DIR)/*.[ch].bak rm -f $(VENDOR_LIBTELNET_SRC_DIR)/*.[ch]~ $(VENDOR_LIBTELNET_SRC_DIR)/*.[ch].bak + rm -f $(VENDOR_LIBYAML_SRC_DIR)/*.[ch]~ $(VENDOR_LIBYAML_SRC_DIR)/*.[ch].bak rm -f $(TALKER_BIN) $(BINDIR)/$(TALKER_BIN) rm -f $(IDENTD_BIN) $(BINDIR)/$(IDENTD_BIN) rm -f $(INCDIR)/*.[ch]~ $(INCDIR)/*.[ch].bak @@ -104,23 +111,24 @@ clean: rm -f $(IDENTD_OBJS) $(IDENTD_OBJS:.o=.d) rm -f $(VENDOR_SDS_OBJS) $(VENDOR_SDS_OBJS:.o=.d) rm -f $(VENDOR_LIBTELNET_OBJS) $(VENDOR_LIBTELNET_OBJS:.o=.d) + rm -f $(VENDOR_LIBYAML_OBJS) $(VENDOR_LIBYAML_OBJS:.o=.d) install: $(BINDIR)/$(TALKER_BIN) $(BINDIR)/$(IDENTD_BIN) build: $(TALKER_BIN) $(IDENTD_BIN) -compile: $(TALKER_OBJS) $(IDENTD_OBJS) $(VENDOR_SDS_OBJS) $(VENDOR_LIBTELNET_OBJS) +compile: $(TALKER_OBJS) $(IDENTD_OBJS) $(VENDOR_SDS_OBJS) $(VENDOR_LIBTELNET_OBJS) $(VENDOR_LIBYAML_OBJS) print-%: ; @echo $* = $($*) -vpath %.c $(TALKER_SRC_DIR) $(TALKER_SRC_DIR)/commands $(IDENTD_SRC_DIR) $(VENDOR_SDS_SRC_DIR) $(VENDOR_LIBTELNET_SRC_DIR) +vpath %.c $(TALKER_SRC_DIR) $(TALKER_SRC_DIR)/commands $(IDENTD_SRC_DIR) $(VENDOR_SDS_SRC_DIR) $(VENDOR_LIBTELNET_SRC_DIR) $(VENDOR_LIBYAML_SRC_DIR) $(BINDIR)/$(TALKER_BIN) $(BINDIR)/$(IDENTD_BIN): $(BINDIR)/%: % @echo "Installing $< ..." chmod $(PERMS) $< mv $< $(BINDIR) -$(TALKER_BIN): $(TALKER_OBJS) $(VENDOR_SDS_OBJS) $(VENDOR_LIBTELNET_OBJS) +$(TALKER_BIN): $(TALKER_OBJS) $(VENDOR_SDS_OBJS) $(VENDOR_LIBTELNET_OBJS) $(VENDOR_LIBYAML_OBJS) @echo "Linking $@ ..." $(CC) $(LD_FLAGS) $^ $(TALKER_LIBS) -o $@ @@ -148,4 +156,9 @@ $(VENDOR_LIBTELNET_OBJS): $(VENDOR_LIBTELNET_OBJ_DIR)/%.o: %.c @test -d $(VENDOR_LIBTELNET_OBJ_DIR) || mkdir $(VENDOR_LIBTELNET_OBJ_DIR) $(CC) $(C_FLAGS) $(CC_FLAGS) $(TALKER_FLAGS) -c -o $@ $< --include $(TALKER_OBJS:.o=.d) $(IDENTD_OBJS:.o=.d) $(VENDOR_SDS_OBJS:.o=.d) $(VENDOR_LIBTELNET_OBJS:.o=.d) +$(VENDOR_LIBYAML_OBJS): $(VENDOR_LIBYAML_OBJ_DIR)/%.o: %.c + @echo "Compiling libyaml library $< ... ($@)" + @test -d $(VENDOR_LIBYAML_OBJ_DIR) || mkdir $(VENDOR_LIBYAML_OBJ_DIR) + $(CC) $(C_FLAGS) $(CC_FLAGS) -I$(VENDOR_LIBYAML_SRC_DIR) $(TALKER_FLAGS) -DHAVE_CONFIG_H=0 -DYAML_VERSION_MAJOR=0 -DYAML_VERSION_MINOR=2 -DYAML_VERSION_PATCH=5 -DYAML_VERSION_STRING="\"0.2.5\"" -Wno-pedantic -c -o $@ $< + +-include $(TALKER_OBJS:.o=.d) $(IDENTD_OBJS:.o=.d) $(VENDOR_SDS_OBJS:.o=.d) $(VENDOR_LIBTELNET_OBJS:.o=.d) $(VENDOR_LIBYAML_OBJS:.o=.d) diff --git a/docker-setup.sh b/docker-setup.sh index fda6440..1e8cd75 100644 --- a/docker-setup.sh +++ b/docker-setup.sh @@ -1,14 +1,31 @@ #!/usr/bin/env bash -CONFIG_FILE="$(pwd)/files/datafiles/config" -MAIN_PORT=$(grep "\bmainport\b" "$CONFIG_FILE" | awk '{ print $2 }') -WIZ_PORT=$(grep "\bwizport\b" "$CONFIG_FILE" | awk '{ print $2 }') -LINK_PORT=$(grep "\blinkport\b" "$CONFIG_FILE" | awk '{ print $2 }') +CONFIG_FILE="$(pwd)/files/datafiles/config.yaml" +if [ ! -f "$CONFIG_FILE" ]; then + CONFIG_FILE="$(pwd)/files/datafiles/config.yaml.sample" +fi + +read_port() { + awk -v key="$1" ' + /^[A-Za-z]/ { in_server = ($0 ~ /^server:/); in_ports = 0 } + in_server && /^ [A-Za-z]/ { in_ports = ($0 ~ /^ ports:[[:space:]]*$/) } + in_ports && /^ [A-Za-z]/ { + split($0, parts, ":") + k = parts[1]; sub(/^[[:space:]]+/, "", k) + v = parts[2]; sub(/^[[:space:]]+/, "", v); sub(/[[:space:]]+$/, "", v) + if (k == key) { print v; exit } + } + ' "$CONFIG_FILE" +} + +MAIN_PORT=$(read_port main) +WIZ_PORT=$(read_port wiz) +LINK_PORT=$(read_port link) cat << EOT > Dockerfile FROM alpine:latest -RUN apk add --no-cache build-base bash busybox-extras clang gdb lldb supervisor +RUN apk add --no-cache build-base bash busybox-extras clang gdb lldb supervisor python3 py3-yaml COPY supervisord.conf /etc/supervisord.conf WORKDIR /amnuts diff --git a/files/datafiles/andys_computer.R b/files/datafiles/andys_computer.R deleted file mode 100644 index 65032d2..0000000 --- a/files/datafiles/andys_computer.R +++ /dev/null @@ -1,6 +0,0 @@ -Walking behind Andy you notice that he is hard at work programming and -creating web pages. He turns around and breathe a sigh of relief as he -notices you're not the boss. A few quick taps on the keyboard and the -screen on his computer changes. It's still a program and web page he's -working on, but that sure as hell doesn't look like anything relating to -what he's being paid for! diff --git a/files/datafiles/back_store.R b/files/datafiles/back_store.R deleted file mode 100644 index e213bee..0000000 --- a/files/datafiles/back_store.R +++ /dev/null @@ -1,6 +0,0 @@ -Thinking that no room could be colder than the store room you find -yourself in a mild state of shock. The spiders are skiing down the snow, -the woodlice have been building snowmen and the flies are having a -friendly snow ball fight. Cold does not describe it. Cold and Messy -pretty much does though. - diff --git a/files/datafiles/box.R b/files/datafiles/box.R deleted file mode 100644 index 1a04be6..0000000 --- a/files/datafiles/box.R +++ /dev/null @@ -1,2 +0,0 @@ -This is a box. It does box things, like.. hold stuff. It's not big, it's -not clever, and it's not worth describing in detail. diff --git a/files/datafiles/car_park.R b/files/datafiles/car_park.R deleted file mode 100644 index d762354..0000000 --- a/files/datafiles/car_park.R +++ /dev/null @@ -1,4 +0,0 @@ -There is a wide expanse of concrete in front of you. Somewhere in the -distant, just on the end of your range of vision, you see... yes, it's a -car. You look up at the building behind you and wonder if it really needs -a car-park this big with the few people who actually work in there. diff --git a/files/datafiles/conference.R b/files/datafiles/conference.R deleted file mode 100644 index 2effc65..0000000 --- a/files/datafiles/conference.R +++ /dev/null @@ -1,4 +0,0 @@ -There is a musty smell here, and it is very cold. You correctly guess -that not many people use this room. It's very plain, very boring. You -give up even looking at the room and fall asleep - well why not? The boss -isn't here after all! diff --git a/files/datafiles/config b/files/datafiles/config deleted file mode 100644 index 027fb17..0000000 --- a/files/datafiles/config +++ /dev/null @@ -1,87 +0,0 @@ -# Main config file for Amnuts - -INIT: -verification amnutsabc -mainport 2402 -wizport 2403 -linkport 2404 -max_users 50 -max_clones 5 -heartbeat 2 -login_idle_time 180 -user_idle_time 3600 -time_out_afks YES -ban_swearing OFF -auto_connect YES -ignore_sigterm NO -system_logging ON -colour_def ON -prompt_def OFF -charecho_def OFF -passwordecho_def OFF -minlogin_level NONE -mesg_life 10 -mesg_check_time 01:00 -min_private 2 -ignore_mp_level ARCH -rem_user_maxlevel SUPER -rem_user_deflevel USER -wizport_level WIZ -gatecrash_level GOD -time_out_maxlevel WIZ -crash_action NONE -auto_purge NO -allow_recaps YES -auto_promote NO -personal_rooms YES -startup_room_parse YES -random_motds YES -resolve_ip AUTO -flood_protect ON -boot_off_min YES -default_warp reception -default_jail cupboard -default_bank safe -default_shoot directors - - -ROOMS: -# format -# map label name links netlink connections -general re reception cp,s1,mo,cu PUB -general cp car_park re PUB -general s1 store_room re,mo,s2,wr,cra -general mo main_office re,s1,li,cr,to,dr PUB -general s2 back_store s1,to,cr -general li library mo,cr,ac -general cr conference li,mo,s2 -general to toilets mo,s2 -general dr directors mo,sa PUB -general sa safe dr PUB ACCEPT -general wr wizroom s1 PRIV -general cu cupboard re PRIV -general ac andys_computer li PRIV -store cra crate s1,box -store box box cra - -TOPICS: -# format -# room name topic -reception ~OLWELCOME!~RS -library Ssshhhhhh.... -directors Let the slaughter begin! -andys_computer Thanks for using Amnuts :) - - -# Link to the 2nd talker. You may need to put the machine name rather than -# localhost here when you're running the 2nd talker on the same machine, it -# depends on how the /etc/hosts file is set up and also the resolver. -# none of these are real talkers or sites! - -SITES: -# format -# link name address password -talker1 localhost 5002 test123 -darkroom foo.bar.com 3456 Bloggs456 -myplace 11.11.11.11 7401 NotReal - diff --git a/files/datafiles/config.yaml.sample b/files/datafiles/config.yaml.sample new file mode 100644 index 0000000..4062a65 --- /dev/null +++ b/files/datafiles/config.yaml.sample @@ -0,0 +1,73 @@ +server: + verification: amnutsabc + ports: + main: 2402 + wiz: 2403 + link: 2404 + max_users: 50 + max_clones: 5 + heartbeat: 2 + +timeouts: + login_idle: 180 + user_idle: 3600 + timeout_afks: true + timeout_maxlevel: WIZ + +defaults: + colour: true + prompt: false + charecho: false + passwordecho: false + warp_room: reception + jail_room: cupboard + bank_room: safe + shoot_room: directors + +moderation: + ban_swearing: 'OFF' + minlogin_level: NONE + min_private: 2 + ignore_mp_level: ARCH + gatecrash_level: GOD + boot_off_min: true + flood_protect: true + +system: + logging: true + ignore_sigterm: false + auto_connect: true + crash_action: NONE + resolve_ip: AUTO + random_motds: true + +users: + auto_purge: false + allow_recaps: true + auto_promote: false + personal_rooms: true + startup_room_parse: true + rem_user_maxlevel: SUPER + rem_user_deflevel: USER + wizport_level: WIZ + +messages: + lifetime_days: 10 + check_time: 01:00 + +sites: + talker1: + address: localhost + port: 5002 + verification: test123 + allow: ALL + darkroom: + address: foo.bar.com + port: 3456 + verification: Bloggs456 + allow: ALL + myplace: + address: 11.11.11.11 + port: 7401 + verification: NotReal + allow: ALL diff --git a/files/datafiles/config2 b/files/datafiles/config2 deleted file mode 100644 index e9e1878..0000000 --- a/files/datafiles/config2 +++ /dev/null @@ -1,63 +0,0 @@ -# This is the 2nd config file for you to use with a copy of the talker -# to get 2 talkers linked together. A lot of parameters are missing here -# in the INIT section to demonstrate the fact that you can leave them as -# the software defaults. - -INIT: -verification test123 -mainport 5000 -wizport 5001 -linkport 5002 -max_users 50 -max_clones 5 -heartbeat 2 -login_idle_time 180 -user_idle_time 3600 -time_out_afks YES -ban_swearing OFF -auto_connect YES -ignore_sigterm NO -system_logging ON -colour_def ON -prompt_def OFF -charecho_def OFF -passwordecho_def OFF -minlogin_level NONE -mesg_life 10 -mesg_check_time 01:00 -min_private 2 -ignore_mp_level ARCH -rem_user_maxlevel SUPER -rem_user_deflevel USER -wizport_level WIZ -gatecrash_level GOD -time_out_maxlevel WIZ -crash_action NONE -auto_purge NO -allow_recaps YES -auto_promote NO -personal_rooms YES -startup_room_parse YES -random_motds YES -resolve_ip AUTO -flood_protect ON -boot_off_min YES -default_warp hallway -default_jail jail -default_bank lounge -default_shoot corridor - - -ROOMS: -general dr drive ha PUB CONNECT talker1 -general ha hallway dr,co,wz PUB -general wz wizroom ha PRIV -general co corridor ha,lg -general lg lounge co BOTH ACCEPT -general ja jail wz PRIV - - -# Link back to talker 1 -SITES: -talker1 localhost 2404 amnutsabc - diff --git a/files/datafiles/crate.R b/files/datafiles/crate.R deleted file mode 100644 index 2abd389..0000000 --- a/files/datafiles/crate.R +++ /dev/null @@ -1,4 +0,0 @@ -The create you now sit in is so big it feels like a complete room. But -it's cold in here, and dark. Oh, so dark... You hear a noise and see -something out of the corner of your eye scuttle across the floor of the -crate. Was that a rat? No, just a very large spider! diff --git a/files/datafiles/cupboard.R b/files/datafiles/cupboard.R deleted file mode 100644 index a277c9a..0000000 --- a/files/datafiles/cupboard.R +++ /dev/null @@ -1,7 +0,0 @@ -It's dark in here and as you enter you're sure someone locks the door -behind you. There is a faint sound of muffled laughter to be heard out -side of this room. To add torture to the experience of being jailed in -the cupboard your boss has left you a 'to-do' list which they want -completed. A small tape-recorder activates automatically every so often; -it's the boss, disturbing you and adding more items to your list just as -you're trying to work through them... diff --git a/files/datafiles/directors.R b/files/datafiles/directors.R deleted file mode 100644 index a1c79e5..0000000 --- a/files/datafiles/directors.R +++ /dev/null @@ -1,5 +0,0 @@ -There is blood on the walls, blood on the floors and the stale stench of -death. You see carcasses on the floor and realize that there have been -many battles fought here. Even through the stench you find your mind -wandering away, imagining how wonderful it would be to have there boss -here right now so that you can take your own shots at 'em. diff --git a/files/datafiles/help.yaml.sample b/files/datafiles/help.yaml.sample new file mode 100644 index 0000000..cd14fa4 --- /dev/null +++ b/files/datafiles/help.yaml.sample @@ -0,0 +1,1608 @@ +accreq: + usage: accreq [] + description: |- + This command is for new users who wish to gain a full account on the + talker. is your email address and is a comment where you + can add any relevant information that might be useful in granting your + account request. + +addhistory: + usage: addhistory + description: This allows you to add your own text to a user's history list. + +adminfiles: + usage: adminfiles [] + description: |- + This allows you to display certain files that are for admin users only. To + see what files are available to be viewed then omit the . Else + include the to view that particular file. + +afk: + usage: afk [lock] [] + description: |- + This command allows you to set yourself to "Away From Keyboard" which + shows up when people "look" at the room and you will pronounced AFK if + they try to tell you or semote something to you. Just press return to + reset unless you have specified the "lock" option which will lock your + session and prevent someone using it while you away from the terminal. + Enter your talker password and press return to unlock it. You can leave + an optional message when you go afk which will be sent to anyone trying + to tell or semote to you. + +allclones: + usage: allclones + description: This shows who has clones on the system and what rooms they are in. + +arrest: + usage: arrest + description: |- + Sends the given user to jail (if there is an allocated jail room) and + sets their level to JAILED. If the users logs off and then back on they + will remain JAILED and be sent automatically to jail room. If no jail + room is allocated then they will remain in the main room, although be + JAILED. The user does not have to be online to be arrested. The arrest + is level based, in that if an ARCH arrests someone then only an ARCH or + higher can unarrest them. The arresting level is displayed in the ustat. + +autopromo: + usage: autopromo + description: This toggle the status of the auto-promote function on and off. + +ban: + usage: ban site | user | new + description: |- + This command will ban either a site, a user, or new users from a site + from having access to the talker. + +bank: + usage: bank deposit | withdraw | balance + description: |- + This command allows you to view how much money you have in the bank. + Using the deposit option allows you to put money into the bank (if you + have any on your person), the withdraw option allows you to take out + money from the bank (if you have some in there), and the balance option + allows you to see how much you have in the bank. + +bbcast: + usage: bbcast + description: |- + The text will be broadcast throughout the talker with an audible beep, + forcing any that are ignoring to listen to it. The name of the person + using this command is not given. + +bcast: + usage: bcast + description: |- + The text will be broadcast throughout the talker, forcing any that are + ignoring to listen to it. The name of the person using this command is + not given. + +beep: + usage: beep [] + description: |- + Allows you to send an audible message to a user. If no message is supplied + then a standard "beep" message will be show to the user. + +bfrom: + usage: bfrom [] + description: |- + This shows you the people who posted to the message board, without + actually showing the content of the messages. If the is omitted, + it will default to the room you are currently in. + +bring: + usage: bring + description: Moves a user from the room they are in to the room you are currently in. + +cafk: + usage: cafk + description: Clears your AFK review buffer. + +calendar: + usage: calendar [ []] + description: |- + This will display a calendar, highlighting (if you have colours turned + on) the current date. If no options are used then it will display the + calendar for the current month and year. If the option is used + then it will display the calendar for that month and the current year. + Else it will display the calendar for which ever month and year you + supply. is a number between 1 and 12, is a number between + 1 and 99, or 1800 and 3000. + +call: + usage: call [] + description: |- + Allows you to set up a quick way of telling a user a message. If you + omit the user then you will be shown which user you have currently set + to quick call. + + Quick call is used such as: ,hi there, how are ya? + + Which will send the "hi there, how are ya?" to whomever is set in the + quick call. + +cash: + usage: cash + description: Allows you to see how much money you currently have. + +cbuff: + usage: cbuff + aliases: + - '*' + description: Clears the conversation buffer of the room you are currently in. + +cedit: + usage: cedit + description: Clears your EDIT review buffer. + +cemote: + usage: cemote + description: This allows you to emote through your clone in the given. + +charecho: + usage: charecho + description: |- + This is a command that is only of use to people who use character + mode clients. Some of these clients will not echo the characters input + themselves even when told to do so; this command forces the talker to + do it instead. The command is a toggle so using it will alternately + flick the echoing on and off. If you switch echoing on even when your + client is echoing you will see every character twice. + +chear: + usage: chear all|swears|nothing + description: |- + This sets what part of the conversation in the clones room the clone will + display to you. "all" makes it show all that is being said and emoted, + "swears" will make it just report swearing (system defined) and "nothing" + will force it to ignore everything. + +clearline: + usage: clearline + description: |- + It is possible to clear the line of a user whilst they are in the login + stage (as shown on "people"). This is useful if that line has been stuck + in the login stage for too long, or you want to clear the line quickly + before the user logs in. + +clone: + usage: clone [] + description: |- + Clone will create a clone of you in whichever room you specify. This + clone can then listen in to the conversation in that room, and you can + also talk through that clone or swap places with it. + +cls: + usage: cls + description: Clears the screen of any text. + +cmdcount: + usage: cmdcount + description: |- + This allows you to see what commands have been used, how many times and + what percentage that is of the overall usage. + +cname: + usage: cname + description: |- + This command allows you to change the name of an existing user to another + name. This will allow users to be able to change their name if they wish + without creating a new account, gain their logon time back, redo their + profile, lose their mail, etc. The user does not have to be logged on + for you to be able to use the command on them, though it works even if + they are. + +colour: + usage: colour + description: |- + Allows you to test the current colour configurations of the talker. For + this you must have "set colour" to be ON. If no colours show then you + should "set colour" to be OFF. + +connect: + usage: connect + description: |- + This command tries to make this room a gateway to another talker by + bringing up a netlink. The link must be predefined in the config file. + +copyto: + usage: copyto [...] + description: |- + This command allows you to send a copy of the next mail you send to + someone to the user(s) given. The user(s) given can also be on a remote + system, providing it has connection to the talker. This is reset to send + out no copies after every mail sent. + +create: + usage: create + description: |- + This allows you to create a new account for a user with the name and + password given. Used, for example, when a new account is needed for a + user from a new user banned site. + +csay: + usage: csay + description: |- + Will make one of your clones in the given room speak. If you see something + nasty being said in the clones room it is an alternative to shouting, + telling or actually going to the room to admonish the miscreant. + +cshout: + usage: cshout + description: Clears the shouts buffer. + +ctells: + usage: ctells + description: Clears your private tells buffer. + +ctopic: + usage: ctopic [all] + description: |- + Lets you clear the topic of the room you are in, or all the topics on + the talker. + +demote: + usage: demote [] + description: |- + Will demote a user to the next level down in the ranks list. You cannot + demote below the lowest level, and you cannot demote anyone of the same + or higher level as you. You can also demote a user even if they are not + logged on. If the option is used then the user will be demoted + directly to that given level, rather than the level below what they + currently are. + +desc: + usage: desc [] + description: |- + This sets the message for your description, i.e., that which follows + your name when other people view the room or use the "who" command, + etc. If you omit the description it displays your current one. + +destroy: + usage: destroy [] [] + description: |- + This will destroy a clone in a given room, or if omitted, the room you + are currently in. If you have the required privileges, then you can + destroy another users clone by giving the optional argument. + +disconnect: + usage: disconnect + description: Bring down the netlink in a room. + +dmail: + usage: + - dmail + - dmail to + - dmail from to + - dmail all + description: |- + This command allows you to delete messages from your mailbox. You + can delete specific single messages by supplying just a number, or + from the first message to a given number. You can also delete from a + certain message to another message, or you can delete all your mail. + If a given number to delete to is higher than the actual amount of mail + in your mailbox, then all the mail will be deleted. + +donate: + usage: donate + description: Allows you to give money to other users. + +dsug: + usage: + - dsug + - dsug to + - dsug from to + - dsug all + description: |- + This command allows you to delete suggestions from the suggestions board. + You can delete specific single suggestions by supplying just a number, + or from the first suggestion to a given number. You can also delete from + a certain suggestion to another suggestion, or you can delete all the + board. If a given number to delete to is higher than the actual amount + of suggestions on the board, then all the suggestions will be deleted. + +dump: + usage: dump -u|-r |-c|-m|-s + description: |- + This command allows you to dump talker information directly to files on + the talker shell account. If you use the -u option you will dump all the + users names. If you use the -r option then you will dump only + the users with that level. If you use the -c option then you will dump + the last 16 commands used. -m dumps the memory currently being used by + the talker, and -s dumps system information such as PID, netlink and IP + resolve status, and more. + +echo: + usage: echo + aliases: + - + + description: |- + This command allows you to echo whatever you write as the text into the + room without other users seeing who said it. + +emote: + usage: emote + aliases: + - ':' + - ; + - :' + - ;' + description: |- + Emotes allow you to express emotions. A example would be "Andy feels sad". + If you were logged in as Andy you would enter ".emote feels sad" and your + user name will be prefixed to the front. The result is seen in the room + you are currently in. You may also use a leading apostrophe to produce + results such as: "Andy's face breaks into a grin". + +entpro: + usage: entpro [] + description: |- + This command allows you to enter a lengthy description (profile) of + yourself that people will see when they use the "examine" command. Since + you can write anything you like here people tend to use it for all sorts + of things. + +ewiz: + usage: ewiz [] + description: |- + This command allows one to emote to various staff levels though they + can only specifically emote to levels equal or less that of their own. + If no level is specified it defaults to lowest staff level which means + all staff levels can hear it. Example of emoting to all staff of ARCH + or above: .ewiz arch grins. + +examine: + usage: examine + description: |- + This commands shows you the users profile and various useful statistics + relating to the users current or most recent login. + +expire: + usage: expire + description: |- + This toggles the user expire on or off. If this is set off, then the + files of the given users will not be deleted after a given amount of + non-use. If set on then all the files will be deleted after the given + period of non-use. + +femote: + usage: femote + description: |- + This will display an emote to anyone listed on your "friends" list, + and only those people, regardless of what room they are currently in. + +files: + usage: files [] + description: |- + This allows you to display certain files. To see what files are available + to be viewed then omit the name. Else include the name to + view that particular file. + +fix: + usage: fix [] + description: |- + This will fix a rooms access whether it is public or private so that it + cannot be changed using the public or private commands. If you omit the + then you will fix the access of the room you are currently in. + +flagged: + usage: flagged + description: |- + This command allows you to see what users you have flagged, be it a + user that you have given a key to, a user you are ignoring, or a user + you have flagged as a friend. + +fmail: + usage: fmail all| + description: |- + This command allows you to manually forward all of, or specific messages + from your local mail box to your email. You need to have your email + entered and verified, though you do not need to have the auto-forward set. + +force: + usage: force + description: |- + This command allows you to force a user to perform a command without them + having to type anything. For instance: ".force bod .h smail" would make + the user Bod read the help on the "smail" command. + +forwarding: + usage: forwarding + description: This will toggle the mail auto-forwarding function on or off. + +friends: + usage: friends [] + description: |- + This allows you to add people to or remove people from your friends list. + If you do not use the option then you will be shown anyone who + is currently listed. If you use the option and that person is + not currently listed then their name will be added, else the name will + be removed from the list. + +from: + usage: from + description: |- + This shows you the people who have sent you the mail that is in your + mailbox without actually showing you the contents of the mail. If you + have any new (unread) mail then you will be notified how many you have + received. + +fsay: + usage: fsay + description: |- + This will send the text as normal speech to anyone on your "friends" list, + and only those people, regardless of what room they are currently in. + +fsmail: + usage: fsmail [] + description: |- + This command allows you to send one mail message to all those people + named on your friends listed. If you do not supply any text then you + will be sent to the line editor. + +fwho: + usage: fwho + description: |- + This shows a full list of the people on your "friends" list who are + currently logged on, including descriptions, rooms they are in, etc. + +gcom: + usage: gcom [] + description: |- + This enables you to give usage of a command to the given that they + would not normally have. If the option is not used then it + will display any commands that the user has been given access to. This + works as a toggle, so that if the is not already listed for + the user then they will gain access to it else the access will be removed. + +go: + usage: go [] | [] [] + description: |- + This command moves you to another room, providing that room is adjoined + and is not set to private (unless you have been invited into that room). + If you omit the room name you will be moved straight to the main room + (i.e., that room which you start in). + +greet: + usage: greet + description: |- + Write the text to the room in large letters. The text must only be 10 + or less characters long, and only consist or the characters a-z (A-Z). + +grepu: + usage: grepu + description: |- + This command allows you to search for a user whose name has the + in it. The pattern can have the wildcards ?, for a single letter match, + or * for multiple letter matches. For example: + + .grepu an?? will match Andy, Andi, Anne and so on. + .grepu an* will match Andy, Anne, Another, Anthony and so on. + + You cannot have the wildcards placed together, such as ?*, *? or **. + +guess: + usage: guess + description: Gives a letter as a guess for your current hangman game. + +hangman: + usage: hangman start|stop|status + description: |- + This will allow you to start or stop a game of hangman, and also to see + the status of your current game. If you use the "start" option, then a + new game of hangman will be started (if you are not currently in a game) + with a random word. Use the "guess" command to guess a letter in the word. + +help: + usage: help [|commands|credits|nuts] + description: |- + This command will display the helpfile for any command given. If you + specify "commands" or omit the command then a list of all the commands + you have use of will be listed. You can also view various credits. + +history: + usage: history + description: |- + This allows you to see the history list of the given user, detailing + promotions, demotions, muzzles, etc. + +home: + usage: home + description: |- + If you are on a remote site this will bring you back to your home site + without you having to wander back to the gateway. You will be placed in + the room on your home site that is linked to the remote service which + you were just on. + +ignall: + usage: ignall + description: |- + This allows you to ignore all of the conversations, tells, shouts, etc. + Acts as a toggle, so type it again to listen. + +ignbeeps: + usage: ignbeeps + description: This will allow you to ignore beeps. Acts as a toggle on or off. + +igngreets: + usage: igngreets + description: This will allow you to ignore all greets. Acts as a toggle on or off. + +ignlist: + usage: ignlist + description: |- + Shows a list of things you can ignore, and if you are currently ignoring + them or not. + +ignlogons: + usage: ignlogons + description: |- + This will allow you to ignore all logons and logoffs. Acts as a toggle + on or off. + +ignpics: + usage: ignpics + description: This will allow you to ignore pictures. Acts as a toggle on or off. + +ignshout: + usage: ignshout + description: This allows you to ignore any shouts. Acts as a toggle on or off. + +igntell: + usage: igntell + description: This allows you to ignore any tells. Acts as a toggle on or off. + +ignuser: + usage: ignuser [] + description: |- + This allows you to ignore tells, pemotes, shouts, etc., from any one + user. If you omit the then you will be shown a list of all users + you are currently ignoring. See "version" for the amount of users you + can ignore. + +ignwiz: + usage: ignwiz + description: |- + This will allow you to ignore all wiztells and wizemotes. Acts as a + toggle on or off. + +inmsg: + usage: inmsg [] + description: |- + This sets the message that is displayed following your name when you + enter a room. i.e., if your name is Andy and you set it to "enters fast" + other people will see "Andy enters fast" when you enter a room. If you + omit the message it shows you your current message. + +invis: + usage: invis + description: |- + This will make you become invisible so that other users cannot see you + in the room or in the "who" list. + +invite: + usage: invite + description: |- + This will allow a user of your choice to be invited into a private room + which you are currently in. + +join: + usage: join + description: |- + Allows you to join no matter what room they are currently + in--providing the room is currently public, or you have been invited in. + +kill: + usage: kill + description: This will log off the given user from the talker. + +knock: + usage: knock + description: |- + This sends a message to people in a private room letting them know that + you wish to enter. It is then up to them to invite you in. + +last: + usage: last [] + description: |- + Shows you when a user was last logged on. If the option is omitted + then a list of the last number of users to log on will be displayed. + +lban: + usage: lban sites|users|new|swears + description: |- + This will list all the sites, or all the users, or all new users from a + sites currently banned from logging in. Also lists the swear words that + are banned. + +listen: + usage: listen + description: |- + This command will allow you to hear the room conversation, shouts, etc, + if you were previously ignoring. + +lmail: + usage: lmail |wizzes|all [] + description: |- + This command allows mail to be sent to all the users of a specific level. + It can also be used to send mail to all of the staff (wizzes) and also to + all of the users who have an account. If the is omitted then the + line editor will be used to input the mail message. This command should + only be used when there are a minimal amount of users on, as it may cause + the talker to lag if the mail is being sent to a large amount of people. + +logging: + usage: logging -l|-s|-r|-n|-e|-on|-off + description: |- + This allows you to toggle the logging functions. Using the -l option with + list the status of the logging function. The -s, -r, -n, and -e options + will toggle the system log, request log, netlinks log, and error log + (respectively). The -on option will turn all logging on, and -off will + turn everything off. + +look: + usage: look + description: |- + Shows the description of the current room, who is in it, how many messages + are on the board and the rooms access. + +macros: + usage: macros + description: |- + Displays a list of all the macros you currently have. See "help files" + for help on how to use macros. + +makeinvis: + usage: makeinvis + description: Forces a user to become invisible if they are visible. + +makevis: + usage: makevis + description: Forces a user to become visible if they are invisible. + +map: + usage: map + description: This will show you a map of the current area of the talker you are in. + +memcount: + usage: memcount + description: Allows you to see how much memory the talker objects are currently using. + +minlogin: + usage: minlogin -a|-n| + description: |- + This sets the minimum login level of a user that can log in. This directly + affects the main port but only affects the wizport if it is set higher + than the wizport login level. If set to -a all login attempts on the + main port will be refused. If set to -n then anyone can log in and new + users can be created. If set to no new accounts can be created + but users of level and above can still log in. + +mode: + usage: mode + description: |- + This switches you between speech mode and command mode. In speech mode + everything you type is interpreted as speech unless it is preceded by + a dot. In command mode you get a standard command type or prompt and + everything is treated as a command, to talk you must explicitly use the + "say" command. + +money: + usage: + - money -l + - money -g + - money -t + description: |- + This command allows you to manage money. Using the -l option shows you + how much money all the only users have. The -g option allows you to give + money to users (without money being donated from your account). The -t + option allows you to take money from users (without money going into + your own account). + +monitor: + usage: monitor + description: |- + This will toggle on and off the ability to see a users name on an echo, + shout, shemote, bcast, etc., if that user is invis. This is a good way + to keep track of any people who abuse the "greet" and "echo" commands. + +move: + usage: move [] + description: |- + Moves a user directly to the chosen room. If the room is omitted the + user is moved to the same room as whoever invoked the command (no matter + if that room is fixed private or not). The user is not moved if he is + already in the chosen room. + +mutter: + usage: mutter + description: This will send the text to everyone in the room except the given user. + +muzzle: + usage: muzzle + description: |- + Muzzles a user which prevents them speaking, shouting, emoting and + mailing other people. The muzzle levels are based upon the user level + of whoever muzzled the user. If an ARCH muzzles a user, for instance, + the user can be unmuzzled by any ARCH or GOD, if however he is muzzled + by a GOD then only another GOD can unmuzzle him. + +mybgone: + usage: mybgone |all + description: |- + You can use this command to expel people from your personal room. You + can just move one person by using the option, else if you supply + "all" then everyone in the room will be expelled. + +myclones: + usage: myclones + description: |- + This will show you the rooms on the system where you currently have + clones. + +mykey: + usage: mykey [] + description: |- + This command allows you to give specific users access to your personal + room even though you may have it locked. If you omit the attribute + then it will list all users you have given keys to. The does not + have to be online for you to give them a key. This command also works + like a toggle, in that if you have given a user a key you use this + command again with their name to remove that key. + +mylock: + usage: mylock + description: |- + This enables you to lock your personal room to anyone else, stopping + them from entering. This works as a toggle, so that if you the room is + locked then you would use this command to unlock it. The personal rooms + function has to be active to use this command. + +myname: + usage: myname + description: |- + This enables you to rename your personal room so it has something other + than "(~$)" as the name. The personal rooms function has to be active + to use this command. + +mypaint: + usage: mypaint [] + description: |- + With this you can set a new description for your personal room. + The personal rooms function has to be active to use this command. + +myroom: + usage: myroom [-d] + description: |- + This command allows you to move into your own personal room. If your + room does not exist then it will create one for you automatically. You + may set your own room description, topic and also have your own personal + message board. If the "-d" option is used then your room will be deleted. + The personal rooms function must be active to use this command. + +netdata: + usage: netdata + description: |- + This command shows whether any data is being received via the net + connections. It takes no arguments. The data in this case is either mail + or a message which is the passing of text (e.g., a room description) + to the remote user. + +netstat: + usage: netstat + description: This shows the current net link status. + +news: + usage: news + description: Allows you to read the news file (if there is one). + +nocopys: + usage: nocopys + description: |- + Allows you to reset your "copies to" so that you will send out no copies + on your next mail. + +nuke: + usage: nuke + description: |- + This will enable you to delete the files of a given user. This can be used + if the user has been banned, or their user files have been corrupted, etc. + +outmsg: + usage: outmsg [] + description: |- + This sets the message that is displayed following your name when you + leave a room. i.e., if your name is Andy and you set it to "leaves fast" + other people will see "Andy leaves fast" when you leave a room. If you + omit the message it shows you your current message. + +passwd: + usage: passwd [] + description: |- + You can change your old password for a new password. If you are able + to use the option then you do not need to know the user's old + password. + +pemote: + usage: pemote + aliases: + - < + - / + description: Allows you to send a private emote to the given user. + +people: + usage: people [key] + description: |- + This shows who is logged on and gives various stats for them. This will + also show a user's logon site, port and various other stats. If the + "key" option is used then information about the different login stages + will be displayed. + +picture: + usage: picture + description: Allows you to show a picture to everyone in the same room you are in. + +preview: + usage: preview [] + description: |- + This command will allow you to preview a picture before you tell it to a + user or the room you are currently in. If no picture name is given then + a list of all the pictures on file will be shown. + +private: + usage: private [] + description: |- + This will set the room you are currently in to a private status, providing + it is not fixed and that there are enough users in it. If you are of + high enough level to use the option then you are able to change + the status of the given room providing there are enough people in it or + it is not fixed. + +promote: + usage: promote [] + description: |- + Will promote a user to the next level up in the ranks list. You cannot + promote above the highest level, and you cannot promote anyone of the + same or higher level as you. You can also promote a user even if they + are not logged on. If the option is used then the user will be + promoted directly to that given level, rather than the level above what + they currently are. + +prompt: + usage: prompt + description: |- + Toggles the prompt on and off. The prompt has the following layout: + + + + The + will only appear if you are invisible. + + The ! will only appear if you are ignoring anything. + +ptell: + usage: ptell + description: Allows you to show a picture to an individual user. + +public: + usage: public [] + description: |- + This will set the room you are currently in to a public status, providing + that it is not a fixed room. If you are of high enough level to use the + option then you can change any room to public, providing it is + not fixed. + +purge: + usage: + - purge -d + - purge -s + - purge -t + description: |- + This gives you a manual way of purging user files. If the "-d" option is + used then a purge will be conducted with the default time settings. If + the "-s" option is used then anyone from the supplied will be + purged. If the "-t" option is used then anyone who last logged on more + that ago will be purged. + + A purge is automatically done when the talker is booted if the auto_purge + option is active, and also every time the default non-use time period + has expired (take taken from after boot-up time). + + CAUTION: With a purge the system may be temporarily lagged. + +quit: + usage: quit + description: Quits your current session on the talker. + +ranks: + usage: ranks + description: |- + Displays the ranks and the commands per rank used on the talker. Your rank + will be displayed in highlights providing you have set colour to be on. + +rcountu: + usage: rcountu + description: |- + This command allows you to re-check the nodes in the complete linked list + of users to see if any should be added or removed, and also check the + recorded levels of users. This would mainly be used if you had manually + altered the user files, or added any file or deleted them. + +read: + usage: read [] [] + description: |- + This displays the message board of the given room, providing you have + access to that room (i.e., it is not fixed to private). If no room is + given it defaults to your current room. If you use the option, + then you will read only that specific message number for the room given, + or the room you are in if no room name is supplied. + +reboot: + usage: reboot [|cancel] + description: |- + This will reboot the talker, either immediately or after + seconds. The cancel option will cancel any timed reboot. + +recaps: + usage: recaps + description: Toggles on and off the ability for users to be able to recap their name. + +recount: + usage: recount [motds] + description: |- + This allows the boards to be recounted in case they have been edited + manually and the count has an error. If the "motds" option is used the + pre- and post-login motds will be recounted. + +reload: + usage: reload + description: Reloads the bullets of your gun--used in the fighting game. + +reminder: + usage: + - (to view) + - reminder all | today | [ []] + - (to manage) + - reminder set | del + description: |- + This command allows you to set reminders. You will be notified of the + number of reminders you have for that day when logging in, and reminders + will also be signified on the calendar output. If the date for a reminder + has passed then the reminder will automatically be removed. + +resite: + usage: resite |-a + description: |- + If you are running the ident daemon then this command will allow you + to get the site of the given user again. This is useful if the ident + daemon was not running at the time they logged in. If you use the -a + option then it will get the sites of all the users logged on. + +retire: + usage: retire + description: |- + This allows you to retire a staff member from "active wiz duty", in that + it removes their name from the wizlist. + +revafk: + usage: revafk + description: |- + This will allow you to review any tells or pemotes you received whilst + being afk. + +revedit: + usage: revedit + description: |- + This will allow you to review any tells or pemotes you received whilst + using the line editor. + +review: + usage: review + description: |- + This will show the last few lines of conversation, emoting and echoes + that took place in the room which you are in. + +revshout: + usage: revshout + description: This reviews the past few shouts sent over the talker. + +revtell: + usage: revtell + description: This will allow you to review the last number of tells set to you. + +rloadrm: + usage: rloadrm -a| + description: |- + Reload room descriptions from rooms.yaml without rebooting the talker. + Useful after editing rooms.yaml on disk to correct typos or refresh a + description. If the "-a" option is used then all room descriptions are + reloaded; otherwise just the description for the named room. Personal + rooms are skipped (their descriptions live elsewhere). Only descriptions + are refreshed -- runtime changes to topic, access, links, and netlinks + are preserved. If rooms.yaml is malformed the command reports the error + and leaves in-memory state untouched. + +rmadmin: + usage: rmadmin -l|-m|-u |-d + description: |- + This command is to aid management of personal rooms for admins. If you + use the "-l" option then it will list all personal rooms currently active + on the talker with some information about each room. If the "-m" option + is used then it will display the amount of memory the personal rooms is + currently using. If the "-u" option is used together with a name of a + user then it will unload that user's personal room from memory. If the + "-d" option is used with a name, then it will unload that user's personal + room from memory and delete all associated room files (message board, + room description, etc.). + +rmail: + usage: rmail [new|] + description: |- + If the option "new" is used then you will read only those messages that + have been sent to you since you last checked your mailbox. If a message + number is given then you will be shown only that message, otherwise any + mail that you currently have in your mailbox. Message numbers can be + checked using the "from" command. + +rnet: + usage: rnet + description: |- + This shows the list of rooms and associated net connections linked + to them. + +rooms: + usage: rooms [-l] + description: |- + Displays a list of the rooms and their current topics. If the "-l" (ell) + option is used then any rooms that require you to be of a certain level + and above to enter will be displayed. + +rstat: + usage: rstat + description: |- + Gives statistics of the service the given room is linked to providing + that room has an active netlink. + +rsug: + usage: rsug + description: Reads the suggestions board. + +rules: + usage: rules + description: Displays a list of rules that are currently enforced on the talker. + +samesite: + usage: samesite user|site [all] + description: |- + The will display the users with the same site as a give user or given + site. If you omit the "all" option then it will only check those users + currently logged on, else it will check through all the users. If you use + the "user" option, then you will be prompted for a user name to check + against, else if you use the "site" option you will be prompted for a + site string. You can use a full or partial site string, but no wildcards. + +save: + usage: save + description: This will save the userfiles of everyone who is currently logged on. + +say: + usage: say + description: |- + This command is only needed when in COMMAND mode, since in SPEECH mode + everything you type that is not a command is interpreted as speech. + +sayto: + usage: sayto + aliases: + - '-' + description: |- + This is used to direct speech to someone in the same room as yourself. + The text will be seen by anyone in the room, though it will be displayed + such as: "() Andy says: ..." + +search: + usage: search [...] + description: |- + This will search for the words in the word list in all of the message + boards that you could possibly see. i.e., it wont show you anything from + boards that are in rooms fixed to private if you do not have access to + those rooms. The word search is done on an OR basis, ie if any of the + words are in a given message it will be shown as opposed to all of the + words having to be there. + +semote: + usage: semote + aliases: + - '&' + - '!' + description: |- + Allows you to shout an emote to everyone logged on no matter what room + they are in. + +set: + usage: set [ []] + description: |- + Allows you to set certain attributes that will be shown when someone + uses the "ustat" command on you. By omitting the you will + display the current settings of your attributes, and also a description + of what the attributes mean. Some attributes are a toggle between on + and off, and others require you entering a . Use ".set show" + to show what attributes you are able to change via the set command. You + can also see detailed info by using ".help set show" for the list of + other help files for the set command. + +set_age: + usage: set age + description: This allows you to set your age for others to see with the .ustat command. + +set_alert: + usage: set alert + description: |- + This attribute is a toggle, meaning that if it is off, using this turns + it on, if it is on, using this turns it off, and it will indicate as such. + This particular toggle will turn on or off whether or not you are alerted + when one of your friends in your friends list logs into the talker. + +set_autofwd: + usage: set autofwd + description: |- + This attribute is a toggle, meaning that if it is off, using this turns it + on, if it is on, using this turns it off, and it will indicate as such. + This particular toggle will turn on or off whether or not you have your + local mail forwarded to your email address. + +set_colour: + usage: set colour + description: |- + This attribute is a toggle, meaning that if it is off, using this turns + it on, if it is on, using this turns it off, and it will indicate as such. + This toggle will turn on or off the ability to see colours. + +set_command: + usage: set command + description: |- + This attribute is a toggle, meaning that if it is off, using this turns it + on, if it is on, using this turns it off, and it will indicate as such. + This toggle affects the help file (.help), by showing whether you see + the commands listed by level, or by functionality. Try both! + +set_email: + usage: set email [] + description: |- + This setting takes your email address, and places it in your userfile for + use in local mail to email autoforwarding (if system allows), and will + show in the .ustat command depending on if you have also used ".set hide" + to hide your email address from other users. + + Using this command alone will clear your email setting. Setting your + email will send you a short form letter to the set email address, + that will include a verification code to use with the .verify command. + Your email address MUST be verified with this code to be able to use + the forwarding if the talker allows. + + Your email address is kept hidden by default. See ".help set hide" + for more details. + +set_gender: + usage: set gender m|f|n + description: |- + This attribute is used to set your gender, to "m" (male), "f" (female) + or "n" (neuter). + +set_hide: + usage: set hide + description: |- + This attribute is a toggle, meaning that if it is off, using this turns it + on, if it is on, using this turns it off, and it will indicate as such. + This will hide your email address from view (using the .ustat command) + to other users that are not part of the talker staff. This talker, by + default, does NOT show your email to other users that are not part of + the administrative staff. + +set_icq: + usage: set icq [] + description: |- + Use this attribute to set your ICQ User Identification Number to be shown + to other users. Using this command without entering a number will clear + the attribute. + +set_pager: + usage: set pager + description: |- + This attribute is used to set your pager length. It can be set to a + number between 15 and 99. Default page length is 23. The pager effects + any files viewed, such as textfiles, helpfiles, and any other text the + talker supplies. + +set_password: + usage: set password + description: |- + This attribute is a toggle, meaning that if it is off, using this turns it + on, if it is on, using this turns it off, and it will indicate as such. + Default will not show your password typed out during login. Setting this + toggle will allow you to see your password as you type it in at login. + +set_rdesc: + usage: set rdesc + description: |- + This attribute is a toggle, meaning that if it is off, using this turns it + on, if it is on, using this turns it off, and it will indicate as such. + This setting will turn off room descriptions as you move from room to + room, or when using the "look" command. Default setting is on. + +set_recap: + usage: set recap + description: |- + Use this setting to change the way your name looks. For example, if + your username is Doggiedog, and you would prefer it to be DoggieDog, + use this command to change its appearance. + +set_revbuf: + usage: set revbuff + description: |- + This attribute toggles (switches between two states) whether you see + the review buffer in ascending or descending order by date. + +set_room: + usage: set room + description: |- + This attribute is a toggle, meaning that if it is off, using this turns + it on, if it is on, using this turns it off, and it will indicate as such. + This setting changes where you will enter upon login. Default is the main + room. You can change to the room you left from if so desired, provided + that the room you left is public. If the room you left from when you + login is now private, you will be sent to the main room by default. + +set_show: + usage: set show + description: |- + This will show you a quick menu of what attributes can be changed. + Use ".help set " to view help on these: + + gender age email www + hide wrap pager colour + room autofwd password rdesc + command recap icq alert + revbuff + +set_wrap: + usage: set wrap + description: |- + This attribute is a toggle, meaning that if it is off, using this turns it + on, if it is on, using this turns it off, and it will indicate as such. + This will attempt to wrap all screen output to 80 columns if your client + is unable to do so. + +set_www: + usage: set www [] + description: |- + Use this to enter your homepage address that is shown to other users + with the "ustat" command. Using this attribute with no address will + clear the current setting. + +setcmdlev: + usage: setcmdlev |norm + description: |- + With this command you can alter the level of a command for the time that + the talker is active. (i.e., if you reboot or shutdown the talker then + the command will revert back to its normal level). If you give the option + "norm" then you will revert the command back to its normal level. You + can only alter the levels of commands you have access to. + +sfrom: + usage: sfrom + description: |- + This shows you the suggestions posted without actually showing the + content of the messages. + +shackle: + usage: shackle + description: This will stop a user from moving out of the room they are currently in. + +shoot: + usage: shoot + description: |- + Attempts to shoot another user in the shooting game. This can only be + done from a specific room (see ".rooms level" for the room). + +shout: + usage: shout + aliases: + - '[' + description: This allows you to "shout" to the whole talker. + +show: + usage: show + aliases: + - '''' + description: |- + This is used to show a person what to type. The text is preceded with + "Type --> " and it will be display to all the users in the room you + are in. + +shutdown: + usage: shutdown [|cancel] + description: |- + This will shutdown the talker, either immediately or after + seconds. The cancel option will cancel any timed shutdown. + +sing: + usage: sing [] + description: |- + Displays the text enclosed in musical notes to the room you are currently + in. For example: Andy sings o/~ lalala o/~ + +site: + usage: site + description: |- + This shows you the internet address from which the given user is logging + in from. + +smail: + usage: smail [@] [] + description: |- + Allows you to send mail to a user (possibly a remote talker via a netlink + service if your talker is currently connected to it). If you omit the + text then you will be put into the line editor and allowed to enter you + message that way. This is recommended if you have a lot you want to write. + +sos: + usage: sos + description: |- + Use this to speak to any of the wizzes that are currently on. The usage + of this command should only be when you are very stuck, or if someone is + being abusive or harassing, etc, and you need to inform the wizzes of it. + +spodlist: + usage: + - spodlist + - spodlist + - spodlist + description: |- + This command will allow you to see your position, or the position of any + other user, on the spod list. If you use the command without any options, + then it will show your position on the spod list. If you use it with + the option, then it will show whoever is in that position of the + spodlist. If you use the option, then it will show the position + of that user. + +sreboot: + usage: sreboot [|cancel] + description: |- + This will seamless reboot the talker, either immediately or after + seconds. The cancel option will cancel any timed seamless + reboot. + +suggest: + usage: suggest [] + description: |- + Allows you to suggest something that you think needs changing or adding + to the talker. The staff really DO look at this and take notice of it, so + please make yourself heard and shape the talker to make it a better place. + +suicide: + usage: suicide + description: |- + This allows you to delete your own account, also wiping your profile + and any mail you may have. + +swban: + usage: swban + description: |- + This toggles a ban on swearing on and off or to a minimum level. If the + swear ban is off, then the user can say anything. If the swear ban is + set to minimum, then any swear words that the user says will be blanked + out with whatever word is hard coded into the talker code. If the swear + ban is set to off then the user will not be able to say anything if it + has a swear word in it. + +switch: + usage: switch [] + description: |- + This allows you to swap places with one of your clones in the given room. + It saves you destroying the current clone, creating another in your + current room then moving yourself to the old clones room. If you only have + one clone active then you can omit the room name to switch with the clone. + +system: + usage: system -m|-n|-r|-u|-a + description: |- + This shows various system parameters. If you use the -m option, then you + will see memory information, -n you will see netlink information, -r will + give you room information and -u will give you use related information. + The -a option shows all information. + +tell: + usage: tell + aliases: + - '>' + description: |- + This allows you to speak privately to another user without having to + find a private room. + +terminal: + usage: terminal + description: Displays your terminal settings that the talker knows about. + +think: + usage: think [] + description: |- + Displays the text enclosed in think bubbles to the room you are currently + in. For example: Andy thinks . o O ( therefor I am ) + +time: + usage: time + description: |- + Displays the current system time of where the talker is running from, + and also the uptime of the talker and boot time of the talker. + +topic: + usage: topic + description: You can set the topic of the room you are in to the text given. + +tpromote: + usage: tpromote [] + description: |- + Will promote a user temporarily to the next level up in the ranks + list. This promotion will only last as long as the user is logged + on. Once they log out they will be returned to their normal level. You + cannot promote above the highest level, and you cannot promote anyone + of the same or higher level as you. You can also tpromote a user even if + they are logged on. If the option is used then the user will be + promoted directly to that given level, rather than the level above that + which they currently are. + +twiz: + usage: twiz [] + description: |- + This command allows the Wiz to tell to various Wiz levels though they + can only specifically tell to levels equal or less that of their own. + If no level is specified it defaults to WIZ which means all the Wiz + levels can hear it. Example of tell to all ARCHs: .twiz arch hiya! + +unarrest: + usage: unarrest + description: |- + Resets the user's level to what it was before they were arrested and moves + them out of the jail to the main room (ie, the room you start off in). + The user does not have to be online to be unarrested. The unarrest is + level based, in that is an ARCH arrested someone then only an ARCH or + higher can unarrest them. The arresting level is displayed in the ustat. + +unban: + usage: unban site | user | new + description: |- + This will remove a site, user, or new users from site ban if the ban is + currently set. + +uncall: + usage: uncall + description: Will reset your quick call so that it is unset. + +unfix: + usage: unfix [] + description: |- + This will unfix a rooms access so that it can then be set to public or + private using the appropriate commands. If you omit the room, then you + will unfix the access of the room you are currently in. + +uninvite: + usage: uninvite + description: |- + This will allow you to cancel the invitation for a user to join you in + a private room. You can only uninvite a user you have previously invited. + +unmuzzle: + usage: unmuzzle + description: |- + Removes a muzzle from a user. This will only work if the user was muzzled + by someone of equal or lesser level that yourself. + +unretire: + usage: unretire + description: |- + This command allows you to unretire a wiz, making their name show up + again on the wizlist. + +unshackle: + usage: unshackle + description: |- + This will allow a user to move out of the room that they are currently + in if they are already shackled. + +ustat: + usage: ustat [] + description: |- + Shows the attributes and details of the user. If you omit the user ustat + defaults to you. + +verify: + usage: verify + description: |- + This will allow you verify that you have been sent an email from the + talker. You will be sent this when you set your email address using + the "set" command (providing you specify a correct email address). + Once you get the email you will have a verification code which you must + supply before you can have local mail forwarded to your email address. + Use the "set" command to turn on or off auto-forwarding. Use the "fmail" + command to manually forward select local mail. + +version: + usage: version + description: |- + Shows the current talker version, and also information on various settings + on the talker. + +viewlog: + usage: viewlog sys|net|req|err| [] + description: |- + Shows various logs. If is specified then you will only view that + amount of lines from the end of the log. In doing this, you do not have + to page through the log. + +vis: + usage: vis + description: This will make you visible again if you are invisible. + +visit: + usage: visit + description: |- + Allows you to move to another user's personal room, providing it already + exists and is not locked. The personal rooms function must be active to + use this command. + +wake: + usage: wake + description: |- + This sends a message and a beeping noise to the users terminal hopefully + attracting their attention. + +who: + usage: who + aliases: + - '@' + description: |- + This shows a full list of people currently logged on to the talker, + including descriptions, areas, etc. + +wipe: + usage: + - wipe + - wipe to + - wipe from to + - wipe all + description: |- + This command allows you to delete messages from the board of the room + that you are in. You can delete specific single messages by supplying + just a number, or from the first message to a given number. You can + also delete from a certain message to another message, or you can delete + all the board. If a given number to delete to is higher than the actual + amount of message on the board, then all the messages will be deleted. + If you are in your personal room (if this function is currently active) + then you can wipe any message, regardless of who posted it. + +wizlist: + usage: wizlist + description: |- + Displays a list of the current staff members and shows which, if any, + are presently logged on. + +write: + usage: write [] + description: |- + This writes a message on the board of the room you are in. If you omit + the text you are put into the line editor to enter your message in that. + +wrules: + usage: wrules + description: |- + Displays a list of rules for the admin that are currently enforced on + the talker. + +xcom: + usage: xcom [] + description: |- + This enables you to remove usage of a command from the given that + they would normally have. If the option is not used then it + will display any commands that the user has been removed access from. This + works as a toggle, so that if the is not already listed for the + user then they will lose access to it else the access will be given back. diff --git a/files/datafiles/library.R b/files/datafiles/library.R deleted file mode 100644 index e7ad2a1..0000000 --- a/files/datafiles/library.R +++ /dev/null @@ -1,4 +0,0 @@ -There is a slight hush on the air in this room. You look around and -notice, much to your shock, someone hard at work filing. Unable to -believe this you thrust your head through the door to the main office and -back again, just to check you're still in the same building. diff --git a/files/datafiles/main_office.R b/files/datafiles/main_office.R deleted file mode 100644 index 572f5c2..0000000 --- a/files/datafiles/main_office.R +++ /dev/null @@ -1,6 +0,0 @@ -The room stretches out much like the car park. You notice, however, that -there is a room in one corner made of glass which you correctly guess is -the bosses room, for only they could be paranoid enough to need to look -out all the time. You hear friendly talking and look around to the other -people... playing computer games and reading newspapers. You are happy to -witness such dedication to the art of schiving off work. diff --git a/files/datafiles/reception.R b/files/datafiles/reception.R deleted file mode 100644 index c60a122..0000000 --- a/files/datafiles/reception.R +++ /dev/null @@ -1,6 +0,0 @@ -The sound of people hard at work fills the room.... Not this room, -obviously, but a room at a place similar to this, I'm sure. Here you can -just hear the general chatter of the day, the shouts and laughter of -people doing generally bugger all. Occasionally a phone will ring and -someone will grudgingly pick it up, but only after trying to get others to -answer it first. All in all, you find this isn't a bad place to be. diff --git a/files/datafiles/rooms.yaml.sample b/files/datafiles/rooms.yaml.sample new file mode 100644 index 0000000..58297cd --- /dev/null +++ b/files/datafiles/rooms.yaml.sample @@ -0,0 +1,206 @@ +rooms: + reception: + map: general + label: re + links: + - car_park + - store_room + - main_office + - cupboard + access: PUB + topic: ~OLWELCOME!~RS + description: | + The sound of people hard at work fills the room.... Not this room, + obviously, but a room at a place similar to this, I'm sure. Here you can + just hear the general chatter of the day, the shouts and laughter of + people doing generally bugger all. Occasionally a phone will ring and + someone will grudgingly pick it up, but only after trying to get others to + answer it first. All in all, you find this isn't a bad place to be. + + car_park: + map: general + label: cp + links: + - reception + access: PUB + description: | + There is a wide expanse of concrete in front of you. Somewhere in the + distant, just on the end of your range of vision, you see... yes, it's a + car. You look up at the building behind you and wonder if it really needs + a car-park this big with the few people who actually work in there. + + store_room: + map: general + label: s1 + links: + - reception + - main_office + - back_store + - wizroom + - crate + description: | + The icicles hang down and your breath turns to frost in the air. There is + much paper and booklets around here and the temptation to burn them and + make a nice roaring fire is almost overwhelming... after all, the + air-conditioning is out of bounds, so you have to stay warm somehow! + + main_office: + map: general + label: mo + links: + - reception + - store_room + - library + - conference + - toilets + - directors + access: PUB + description: | + The room stretches out much like the car park. You notice, however, that + there is a room in one corner made of glass which you correctly guess is + the bosses room, for only they could be paranoid enough to need to look + out all the time. You hear friendly talking and look around to the other + people... playing computer games and reading newspapers. You are happy to + witness such dedication to the art of schiving off work. + + back_store: + map: general + label: s2 + links: + - store_room + - toilets + - conference + description: |+ + Thinking that no room could be colder than the store room you find + yourself in a mild state of shock. The spiders are skiing down the snow, + the woodlice have been building snowmen and the flies are having a + friendly snow ball fight. Cold does not describe it. Cold and Messy + pretty much does though. + + library: + map: general + label: li + links: + - main_office + - conference + - andys_computer + topic: Ssshhhhhh.... + description: | + There is a slight hush on the air in this room. You look around and + notice, much to your shock, someone hard at work filing. Unable to + believe this you thrust your head through the door to the main office and + back again, just to check you're still in the same building. + + conference: + map: general + label: cr + links: + - library + - main_office + - back_store + description: | + There is a musty smell here, and it is very cold. You correctly guess + that not many people use this room. It's very plain, very boring. You + give up even looking at the room and fall asleep - well why not? The boss + isn't here after all! + + toilets: + map: general + label: to + links: + - main_office + - back_store + description: | + Cold, damp, and never flush. Need anymore be said? + + directors: + map: general + label: dr + links: + - main_office + - safe + access: PUB + topic: Let the slaughter begin! + description: | + There is blood on the walls, blood on the floors and the stale stench of + death. You see carcasses on the floor and realize that there have been + many battles fought here. Even through the stench you find your mind + wandering away, imagining how wonderful it would be to have there boss + here right now so that you can take your own shots at 'em. + + safe: + map: general + label: sa + links: + - directors + access: PUB + netlink: ACCEPT + description: | + Where the money is kept... + + wizroom: + map: general + label: wr + links: + - store_room + access: PRIV + description: | + The portal to the real world shimmers and vibrates. The dark blue oval + shapes looks right at home here in the room, with it's strange containers + on dusty shelves. On the heavy-set desk there are many weighty books + inscribed with a language not of this, or any earthly world. Occasionally + one of the mysterious items in the jars gurgles or winks at you + coquettishly. This makes you sick to your stomach, but you're glad for the + attention - even if it is from a... splodge, swimming around in it's own + mucus. + + cupboard: + map: general + label: cu + links: + - reception + access: PRIV + description: | + It's dark in here and as you enter you're sure someone locks the door + behind you. There is a faint sound of muffled laughter to be heard out + side of this room. To add torture to the experience of being jailed in + the cupboard your boss has left you a 'to-do' list which they want + completed. A small tape-recorder activates automatically every so often; + it's the boss, disturbing you and adding more items to your list just as + you're trying to work through them... + + andys_computer: + map: general + label: ac + links: + - library + access: PRIV + topic: Thanks for using Amnuts :) + description: | + Walking behind Andy you notice that he is hard at work programming and + creating web pages. He turns around and breathe a sigh of relief as he + notices you're not the boss. A few quick taps on the keyboard and the + screen on his computer changes. It's still a program and web page he's + working on, but that sure as hell doesn't look like anything relating to + what he's being paid for! + + crate: + map: store + label: cra + links: + - store_room + - box + description: | + The create you now sit in is so big it feels like a complete room. But + it's cold in here, and dark. Oh, so dark... You hear a noise and see + something out of the corner of your eye scuttle across the floor of the + crate. Was that a rat? No, just a very large spider! + + box: + map: store + label: box + links: + - crate + description: | + This is a box. It does box things, like.. hold stuff. It's not big, it's + not clever, and it's not worth describing in detail. diff --git a/files/datafiles/safe.R b/files/datafiles/safe.R deleted file mode 100644 index 5a55715..0000000 --- a/files/datafiles/safe.R +++ /dev/null @@ -1 +0,0 @@ -Where the money is kept... diff --git a/files/datafiles/store_room.R b/files/datafiles/store_room.R deleted file mode 100644 index 28ff5a9..0000000 --- a/files/datafiles/store_room.R +++ /dev/null @@ -1,4 +0,0 @@ -The icicles hang down and your breath turns to frost in the air. There is -much paper and booklets around here and the temptation to burn them and -make a nice roaring fire is almost overwhelming... after all, the -air-conditioning is out of bounds, so you have to stay warm somehow! diff --git a/files/datafiles/toilets.R b/files/datafiles/toilets.R deleted file mode 100644 index 574edd0..0000000 --- a/files/datafiles/toilets.R +++ /dev/null @@ -1 +0,0 @@ -Cold, damp, and never flush. Need anymore be said? diff --git a/files/datafiles/wizroom.R b/files/datafiles/wizroom.R deleted file mode 100644 index b53e01a..0000000 --- a/files/datafiles/wizroom.R +++ /dev/null @@ -1,8 +0,0 @@ -The portal to the real world shimmers and vibrates. The dark blue oval -shapes looks right at home here in the room, with it's strange containers -on dusty shelves. On the heavy-set desk there are many weighty books -inscribed with a language not of this, or any earthly world. Occasionally -one of the mysterious items in the jars gurgles or winks at you -coquettishly. This makes you sick to your stomach, but you're glad for the -attention - even if it is from a... splodge, swimming around in it's own -mucus. diff --git a/files/helpfiles/accreq b/files/helpfiles/accreq deleted file mode 100644 index f8f4f10..0000000 --- a/files/helpfiles/accreq +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS accreq -~OLUsage :~RS accreq [] - -This command is for new users who wish to gain a full account on the -talker. is your email address and is a comment where you -can add any relevant information that might be useful in granting your -account request. diff --git a/files/helpfiles/addhistory b/files/helpfiles/addhistory deleted file mode 100644 index 86df8b1..0000000 --- a/files/helpfiles/addhistory +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS addhistory -~OLUsage :~RS addhistory - -This allows you to add your own text to a user's history list. diff --git a/files/helpfiles/adminfiles b/files/helpfiles/adminfiles deleted file mode 100644 index 7ea0540..0000000 --- a/files/helpfiles/adminfiles +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS adminfiles -~OLUsage :~RS adminfiles [] - -This allows you to display certain files that are for admin users only. To -see what files are available to be viewed then omit the . Else -include the to view that particular file. diff --git a/files/helpfiles/afk b/files/helpfiles/afk deleted file mode 100644 index f20f54e..0000000 --- a/files/helpfiles/afk +++ /dev/null @@ -1,11 +0,0 @@ -~OLCommand :~RS afk -~OLUsage :~RS afk [lock] [] - -This command allows you to set yourself to "Away From Keyboard" which -shows up when people "look" at the room and you will pronounced AFK if -they try to tell you or semote something to you. Just press return to -reset unless you have specified the "lock" option which will lock your -session and prevent someone using it while you away from the terminal. -Enter your talker password and press return to unlock it. You can leave -an optional message when you go afk which will be sent to anyone trying -to tell or semote to you. diff --git a/files/helpfiles/allclones b/files/helpfiles/allclones deleted file mode 100644 index 2423ea9..0000000 --- a/files/helpfiles/allclones +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS allclones -~OLUsage :~RS allclones - -This shows who has clones on the system and what rooms they are in. diff --git a/files/helpfiles/arrest b/files/helpfiles/arrest deleted file mode 100644 index c8004f4..0000000 --- a/files/helpfiles/arrest +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS arrest -~OLUsage :~RS arrest - -Sends the given user to jail (if there is an allocated jail room) and -sets their level to JAILED. If the users logs off and then back on they -will remain JAILED and be sent automatically to jail room. If no jail -room is allocated then they will remain in the main room, although be -JAILED. The user does not have to be online to be arrested. The arrest -is level based, in that if an ARCH arrests someone then only an ARCH or -higher can unarrest them. The arresting level is displayed in the ustat. diff --git a/files/helpfiles/autopromo b/files/helpfiles/autopromo deleted file mode 100644 index 14c564c..0000000 --- a/files/helpfiles/autopromo +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS autopromo -~OLUsage :~RS autopromo - -This toggle the status of the auto-promote function on and off. diff --git a/files/helpfiles/ban b/files/helpfiles/ban deleted file mode 100644 index 083496b..0000000 --- a/files/helpfiles/ban +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ban -~OLUsage :~RS ban site | user | new - -This command will ban either a site, a user, or new users from a site -from having access to the talker. diff --git a/files/helpfiles/bank b/files/helpfiles/bank deleted file mode 100644 index bb12bbe..0000000 --- a/files/helpfiles/bank +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS bank -~OLUsage :~RS bank deposit | withdraw | balance - -This command allows you to view how much money you have in the bank. -Using the deposit option allows you to put money into the bank (if you -have any on your person), the withdraw option allows you to take out -money from the bank (if you have some in there), and the balance option -allows you to see how much you have in the bank. diff --git a/files/helpfiles/bbcast b/files/helpfiles/bbcast deleted file mode 100644 index 151ad70..0000000 --- a/files/helpfiles/bbcast +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS bbcast -~OLUsage :~RS bbcast - -The text will be broadcast throughout the talker with an audible beep, -forcing any that are ignoring to listen to it. The name of the person -using this command is not given. diff --git a/files/helpfiles/bcast b/files/helpfiles/bcast deleted file mode 100644 index 1b7e94b..0000000 --- a/files/helpfiles/bcast +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS bcast -~OLUsage :~RS bcast - -The text will be broadcast throughout the talker, forcing any that are -ignoring to listen to it. The name of the person using this command is -not given. diff --git a/files/helpfiles/beep b/files/helpfiles/beep deleted file mode 100644 index bc35ca1..0000000 --- a/files/helpfiles/beep +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS beep -~OLUsage :~RS beep [] - -Allows you to send an audible message to a user. If no message is supplied -then a standard "beep" message will be show to the user. diff --git a/files/helpfiles/bfrom b/files/helpfiles/bfrom deleted file mode 100644 index 1785ba2..0000000 --- a/files/helpfiles/bfrom +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS bfrom -~OLUsage :~RS bfrom [] - -This shows you the people who posted to the message board, without -actually showing the content of the messages. If the is omitted, -it will default to the room you are currently in. diff --git a/files/helpfiles/bring b/files/helpfiles/bring deleted file mode 100644 index 4447c0e..0000000 --- a/files/helpfiles/bring +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS bring -~OLUsage :~RS bring - -Moves a user from the room they are in to the room you are currently in. diff --git a/files/helpfiles/cafk b/files/helpfiles/cafk deleted file mode 100644 index 16b5a42..0000000 --- a/files/helpfiles/cafk +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS cafk -~OLUsage :~RS cafk - -Clears your AFK review buffer. diff --git a/files/helpfiles/calendar b/files/helpfiles/calendar deleted file mode 100644 index 7fc0de3..0000000 --- a/files/helpfiles/calendar +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS calendar -~OLUsage :~RS calendar [ []] - -This will display a calendar, highlighting (if you have colours turned -on) the current date. If no options are used then it will display the -calendar for the current month and year. If the option is used -then it will display the calendar for that month and the current year. -Else it will display the calendar for which ever month and year you -supply. is a number between 1 and 12, is a number between -1 and 99, or 1800 and 3000. diff --git a/files/helpfiles/call b/files/helpfiles/call deleted file mode 100644 index c17a6c7..0000000 --- a/files/helpfiles/call +++ /dev/null @@ -1,12 +0,0 @@ -~OLCommand :~RS call -~OLUsage :~RS call [] -~OLAliases :~RS , - -Allows you to set up a quick way of telling a user a message. If you -omit the user then you will be shown which user you have currently set -to quick call. - -Quick call is used such as: ,hi there, how are ya? - -Which will send the "hi there, how are ya?" to whomever is set in the -quick call. diff --git a/files/helpfiles/cash b/files/helpfiles/cash deleted file mode 100644 index 02a7a55..0000000 --- a/files/helpfiles/cash +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS cash -~OLUsage :~RS cash - -Allows you to see how much money you currently have. diff --git a/files/helpfiles/cbuff b/files/helpfiles/cbuff deleted file mode 100644 index a6b4274..0000000 --- a/files/helpfiles/cbuff +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS cbuff -~OLUsage :~RS cbuff -~OLAliases :~RS * - -Clears the conversation buffer of the room you are currently in. diff --git a/files/helpfiles/cedit b/files/helpfiles/cedit deleted file mode 100644 index ddee54a..0000000 --- a/files/helpfiles/cedit +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS cedit -~OLUsage :~RS cedit - -Clears your EDIT review buffer. diff --git a/files/helpfiles/cemote b/files/helpfiles/cemote deleted file mode 100644 index d3749db..0000000 --- a/files/helpfiles/cemote +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS cemote -~OLUsage :~RS cemote - -This allows you to emote through your clone in the given. diff --git a/files/helpfiles/charecho b/files/helpfiles/charecho deleted file mode 100644 index 04beb68..0000000 --- a/files/helpfiles/charecho +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS charecho -~OLUsage :~RS charecho - -This is a command that is only of use to people who use character -mode clients. Some of these clients will not echo the characters input -themselves even when told to do so; this command forces the talker to -do it instead. The command is a toggle so using it will alternately -flick the echoing on and off. If you switch echoing on even when your -client is echoing you will see every character twice. diff --git a/files/helpfiles/chear b/files/helpfiles/chear deleted file mode 100644 index cad3812..0000000 --- a/files/helpfiles/chear +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS chear -~OLUsage :~RS chear all|swears|nothing - -This sets what part of the conversation in the clones room the clone will -display to you. "all" makes it show all that is being said and emoted, -"swears" will make it just report swearing (system defined) and "nothing" -will force it to ignore everything. diff --git a/files/helpfiles/clearline b/files/helpfiles/clearline deleted file mode 100644 index cea7419..0000000 --- a/files/helpfiles/clearline +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS clearline -~OLUsage :~RS clearline - -It is possible to clear the line of a user whilst they are in the login -stage (as shown on "people"). This is useful if that line has been stuck -in the login stage for too long, or you want to clear the line quickly -before the user logs in. diff --git a/files/helpfiles/clone b/files/helpfiles/clone deleted file mode 100644 index 0a7f1f9..0000000 --- a/files/helpfiles/clone +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS clone -~OLUsage :~RS clone [] - -Clone will create a clone of you in whichever room you specify. This -clone can then listen in to the conversation in that room, and you can -also talk through that clone or swap places with it. diff --git a/files/helpfiles/cls b/files/helpfiles/cls deleted file mode 100644 index ce61aeb..0000000 --- a/files/helpfiles/cls +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS cls -~OLUsage :~RS cls - -Clears the screen of any text. diff --git a/files/helpfiles/cmdcount b/files/helpfiles/cmdcount deleted file mode 100644 index 69fe335..0000000 --- a/files/helpfiles/cmdcount +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS cmdcount -~OLUsage :~RS cmdcount - -This allows you to see what commands have been used, how many times and -what percentage that is of the overall usage. diff --git a/files/helpfiles/cname b/files/helpfiles/cname deleted file mode 100644 index e953723..0000000 --- a/files/helpfiles/cname +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS cname -~OLUsage :~RS cname - -This command allows you to change the name of an existing user to another -name. This will allow users to be able to change their name if they wish -without creating a new account, gain their logon time back, redo their -profile, lose their mail, etc. The user does not have to be logged on -for you to be able to use the command on them, though it works even if -they are. diff --git a/files/helpfiles/colour b/files/helpfiles/colour deleted file mode 100644 index 7c11b61..0000000 --- a/files/helpfiles/colour +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS colour -~OLUsage :~RS colour - -Allows you to test the current colour configurations of the talker. For -this you must have "set colour" to be ON. If no colours show then you -should "set colour" to be OFF. diff --git a/files/helpfiles/connect b/files/helpfiles/connect deleted file mode 100644 index 30b9955..0000000 --- a/files/helpfiles/connect +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS connect -~OLUsage :~RS connect - -This command tries to make this room a gateway to another talker by -bringing up a netlink. The link must be predefined in the config file. diff --git a/files/helpfiles/copyto b/files/helpfiles/copyto deleted file mode 100644 index a19f596..0000000 --- a/files/helpfiles/copyto +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS copyto -~OLUsage :~RS copyto [...] - -This command allows you to send a copy of the next mail you send to -someone to the user(s) given. The user(s) given can also be on a remote -system, providing it has connection to the talker. This is reset to send -out no copies after every mail sent. diff --git a/files/helpfiles/create b/files/helpfiles/create deleted file mode 100644 index 6320a17..0000000 --- a/files/helpfiles/create +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS create -~OLUsage :~RS create - -This allows you to create a new account for a user with the name and -password given. Used, for example, when a new account is needed for a -user from a new user banned site. diff --git a/files/helpfiles/csay b/files/helpfiles/csay deleted file mode 100644 index bf10882..0000000 --- a/files/helpfiles/csay +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS csay -~OLUsage :~RS csay - -Will make one of your clones in the given room speak. If you see something -nasty being said in the clones room it is an alternative to shouting, -telling or actually going to the room to admonish the miscreant. diff --git a/files/helpfiles/cshout b/files/helpfiles/cshout deleted file mode 100644 index 9128900..0000000 --- a/files/helpfiles/cshout +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS cshout -~OLUsage :~RS cshout - -Clears the shouts buffer. diff --git a/files/helpfiles/ctells b/files/helpfiles/ctells deleted file mode 100644 index 282ec1e..0000000 --- a/files/helpfiles/ctells +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS ctells -~OLUsage :~RS ctells - -Clears your private tells buffer. diff --git a/files/helpfiles/ctopic b/files/helpfiles/ctopic deleted file mode 100644 index f5ab299..0000000 --- a/files/helpfiles/ctopic +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ctopic -~OLUsage :~RS ctopic [all] - -Lets you clear the topic of the room you are in, or all the topics on -the talker. diff --git a/files/helpfiles/demote b/files/helpfiles/demote deleted file mode 100644 index b86e2e6..0000000 --- a/files/helpfiles/demote +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS demote -~OLUsage :~RS demote [] - -Will demote a user to the next level down in the ranks list. You cannot -demote below the lowest level, and you cannot demote anyone of the same -or higher level as you. You can also demote a user even if they are not -logged on. If the option is used then the user will be demoted -directly to that given level, rather than the level below what they -currently are. diff --git a/files/helpfiles/desc b/files/helpfiles/desc deleted file mode 100644 index e9b1d60..0000000 --- a/files/helpfiles/desc +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS desc -~OLUsage :~RS desc [] - -This sets the message for your description, i.e., that which follows -your name when other people view the room or use the "who" command, -etc. If you omit the description it displays your current one. diff --git a/files/helpfiles/destroy b/files/helpfiles/destroy deleted file mode 100644 index 09b629b..0000000 --- a/files/helpfiles/destroy +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS destroy -~OLUsage :~RS destroy [] [] - -This will destroy a clone in a given room, or if omitted, the room you -are currently in. If you have the required privileges, then you can -destroy another users clone by giving the optional argument. diff --git a/files/helpfiles/disconnect b/files/helpfiles/disconnect deleted file mode 100644 index 70f48df..0000000 --- a/files/helpfiles/disconnect +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS disconnect -~OLUsage :~RS disconnect - -Bring down the netlink in a room. diff --git a/files/helpfiles/dmail b/files/helpfiles/dmail deleted file mode 100644 index d757171..0000000 --- a/files/helpfiles/dmail +++ /dev/null @@ -1,12 +0,0 @@ -~OLCommand :~RS dmail -~OLUsage :~RS dmail -~OL :~RS dmail to -~OL :~RS dmail from to -~OL :~RS dmail all - -This command allows you to delete messages from your mailbox. You -can delete specific single messages by supplying just a number, or -from the first message to a given number. You can also delete from a -certain message to another message, or you can delete all your mail. -If a given number to delete to is higher than the actual amount of mail -in your mailbox, then all the mail will be deleted. diff --git a/files/helpfiles/donate b/files/helpfiles/donate deleted file mode 100644 index a398fdf..0000000 --- a/files/helpfiles/donate +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS donate -~OLUsage :~RS donate - -Allows you to give money to other users. diff --git a/files/helpfiles/dsug b/files/helpfiles/dsug deleted file mode 100644 index eed15c9..0000000 --- a/files/helpfiles/dsug +++ /dev/null @@ -1,12 +0,0 @@ -~OLCommand :~RS dsug -~OLUsage :~RS dsug -~OL :~RS dsug to -~OL :~RS dsug from to -~OL :~RS dsug all - -This command allows you to delete suggestions from the suggestions board. -You can delete specific single suggestions by supplying just a number, -or from the first suggestion to a given number. You can also delete from -a certain suggestion to another suggestion, or you can delete all the -board. If a given number to delete to is higher than the actual amount -of suggestions on the board, then all the suggestions will be deleted. diff --git a/files/helpfiles/dump b/files/helpfiles/dump deleted file mode 100644 index 38a8e12..0000000 --- a/files/helpfiles/dump +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS dump -~OLUsage :~RS dump -u|-r |-c|-m|-s - -This command allows you to dump talker information directly to files on -the talker shell account. If you use the -u option you will dump all the -users names. If you use the -r option then you will dump only -the users with that level. If you use the -c option then you will dump -the last 16 commands used. -m dumps the memory currently being used by -the talker, and -s dumps system information such as PID, netlink and IP -resolve status, and more. diff --git a/files/helpfiles/echo b/files/helpfiles/echo deleted file mode 100644 index 401e564..0000000 --- a/files/helpfiles/echo +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS echo -~OLUsage :~RS echo -~OLAliases :~RS + - -This command allows you to echo whatever you write as the text into the -room without other users seeing who said it. diff --git a/files/helpfiles/emote b/files/helpfiles/emote deleted file mode 100644 index 1c36195..0000000 --- a/files/helpfiles/emote +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS emote -~OLUsage :~RS emote -~OLAliases :~RS ; or : (apostrophe: :' or ;') - -Emotes allow you to express emotions. A example would be "Andy feels sad". -If you were logged in as Andy you would enter ".emote feels sad" and your -user name will be prefixed to the front. The result is seen in the room -you are currently in. You may also use a leading apostrophe to produce -results such as: "Andy's face breaks into a grin". diff --git a/files/helpfiles/entpro b/files/helpfiles/entpro deleted file mode 100644 index 7b52527..0000000 --- a/files/helpfiles/entpro +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS entpro -~OLUsage :~RS entpro [] - -This command allows you to enter a lengthy description (profile) of -yourself that people will see when they use the "examine" command. Since -you can write anything you like here people tend to use it for all sorts -of things. diff --git a/files/helpfiles/ewiz b/files/helpfiles/ewiz deleted file mode 100644 index 80c935c..0000000 --- a/files/helpfiles/ewiz +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS ewiz -~OLUsage :~RS ewiz [] - -This command allows one to emote to various staff levels though they -can only specifically emote to levels equal or less that of their own. -If no level is specified it defaults to lowest staff level which means -all staff levels can hear it. Example of emoting to all staff of ARCH -or above: .ewiz arch grins. diff --git a/files/helpfiles/examine b/files/helpfiles/examine deleted file mode 100644 index 6a9b280..0000000 --- a/files/helpfiles/examine +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS examine -~OLUsage :~RS examine - -This commands shows you the users profile and various useful statistics -relating to the users current or most recent login. diff --git a/files/helpfiles/expire b/files/helpfiles/expire deleted file mode 100644 index 0cb11ff..0000000 --- a/files/helpfiles/expire +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS expire -~OLUsage :~RS expire - -This toggles the user expire on or off. If this is set off, then the -files of the given users will not be deleted after a given amount of -non-use. If set on then all the files will be deleted after the given -period of non-use. diff --git a/files/helpfiles/femote b/files/helpfiles/femote deleted file mode 100644 index bcaefd1..0000000 --- a/files/helpfiles/femote +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS femote -~OLUsage :~RS femote - -This will display an emote to anyone listed on your "friends" list, -and only those people, regardless of what room they are currently in. diff --git a/files/helpfiles/files b/files/helpfiles/files deleted file mode 100644 index cbbb1c3..0000000 --- a/files/helpfiles/files +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS files -~OLUsage :~RS files [] - -This allows you to display certain files. To see what files are available -to be viewed then omit the name. Else include the name to -view that particular file. diff --git a/files/helpfiles/fix b/files/helpfiles/fix deleted file mode 100644 index 53a323c..0000000 --- a/files/helpfiles/fix +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS fix -~OLUsage :~RS fix [] - -This will fix a rooms access whether it is public or private so that it -cannot be changed using the public or private commands. If you omit the - then you will fix the access of the room you are currently in. diff --git a/files/helpfiles/flagged b/files/helpfiles/flagged deleted file mode 100644 index f7f03f1..0000000 --- a/files/helpfiles/flagged +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS flagged -~OLUsage :~RS flagged - -This command allows you to see what users you have flagged, be it a -user that you have given a key to, a user you are ignoring, or a user -you have flagged as a friend. diff --git a/files/helpfiles/fmail b/files/helpfiles/fmail deleted file mode 100644 index 6e753d9..0000000 --- a/files/helpfiles/fmail +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS fmail -~OLUsage :~RS fmail all| - -This command allows you to manually forward all of, or specific messages -from your local mail box to your email. You need to have your email -entered and verified, though you do not need to have the auto-forward set. diff --git a/files/helpfiles/force b/files/helpfiles/force deleted file mode 100644 index b483a2f..0000000 --- a/files/helpfiles/force +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS force -~OLUsage :~RS force - -This command allows you to force a user to perform a command without them -having to type anything. For instance: ".force bod .h smail" would make -the user Bod read the help on the "smail" command. diff --git a/files/helpfiles/forwarding b/files/helpfiles/forwarding deleted file mode 100644 index 5dc0f27..0000000 --- a/files/helpfiles/forwarding +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS forwarding -~OLUsage :~RS forwarding - -This will toggle the mail auto-forwarding function on or off. diff --git a/files/helpfiles/friends b/files/helpfiles/friends deleted file mode 100644 index c33683a..0000000 --- a/files/helpfiles/friends +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS friends -~OLUsage :~RS friends [] - -This allows you to add people to or remove people from your friends list. -If you do not use the option then you will be shown anyone who -is currently listed. If you use the option and that person is -not currently listed then their name will be added, else the name will -be removed from the list. diff --git a/files/helpfiles/from b/files/helpfiles/from deleted file mode 100644 index dbedf0f..0000000 --- a/files/helpfiles/from +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS from -~OLUsage :~RS from - -This shows you the people who have sent you the mail that is in your -mailbox without actually showing you the contents of the mail. If you -have any new (unread) mail then you will be notified how many you have -received. diff --git a/files/helpfiles/fsay b/files/helpfiles/fsay deleted file mode 100644 index 72c0aec..0000000 --- a/files/helpfiles/fsay +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS fsay -~OLUsage :~RS fsay - -This will send the text as normal speech to anyone on your "friends" list, -and only those people, regardless of what room they are currently in. diff --git a/files/helpfiles/fsmail b/files/helpfiles/fsmail deleted file mode 100644 index 7f829e1..0000000 --- a/files/helpfiles/fsmail +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS fsmail -~OLUsage :~RS fsmail [] - -This command allows you to send one mail message to all those people -named on your friends listed. If you do not supply any text then you -will be sent to the line editor. diff --git a/files/helpfiles/fwho b/files/helpfiles/fwho deleted file mode 100644 index 3a91b60..0000000 --- a/files/helpfiles/fwho +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS fwho -~OLUsage :~RS fwho - -This shows a full list of the people on your "friends" list who are -currently logged on, including descriptions, rooms they are in, etc. diff --git a/files/helpfiles/gcom b/files/helpfiles/gcom deleted file mode 100644 index 9d0f1da..0000000 --- a/files/helpfiles/gcom +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS gcom -~OLUsage :~RS gcom [] - -This enables you to give usage of a command to the given that they -would not normally have. If the option is not used then it -will display any commands that the user has been given access to. This -works as a toggle, so that if the is not already listed for -the user then they will gain access to it else the access will be removed. diff --git a/files/helpfiles/go b/files/helpfiles/go deleted file mode 100644 index b3ce36b..0000000 --- a/files/helpfiles/go +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS go -~OLUsage :~RS go [] | [] [] - -This command moves you to another room, providing that room is adjoined -and is not set to private (unless you have been invited into that room). -If you omit the room name you will be moved straight to the main room -(i.e., that room which you start in). diff --git a/files/helpfiles/greet b/files/helpfiles/greet deleted file mode 100644 index 74cb426..0000000 --- a/files/helpfiles/greet +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS greet -~OLUsage :~RS greet - -Write the text to the room in large letters. The text must only be 10 -or less characters long, and only consist or the characters a-z (A-Z). diff --git a/files/helpfiles/grepu b/files/helpfiles/grepu deleted file mode 100644 index e857310..0000000 --- a/files/helpfiles/grepu +++ /dev/null @@ -1,11 +0,0 @@ -~OLCommand :~RS grepu -~OLUsage :~RS grepu - -This command allows you to search for a user whose name has the -in it. The pattern can have the wildcards ?, for a single letter match, -or * for multiple letter matches. For example: - -.grepu an?? will match Andy, Andi, Anne and so on. -.grepu an* will match Andy, Anne, Another, Anthony and so on. - -You cannot have the wildcards placed together, such as ?*, *? or **. diff --git a/files/helpfiles/guess b/files/helpfiles/guess deleted file mode 100644 index a5a46d8..0000000 --- a/files/helpfiles/guess +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS guess -~OLUsage :~RS guess - -Gives a letter as a guess for your current hangman game. diff --git a/files/helpfiles/hangman b/files/helpfiles/hangman deleted file mode 100644 index 8d39d75..0000000 --- a/files/helpfiles/hangman +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS hangman -~OLUsage :~RS hangman start|stop|status - -This will allow you to start or stop a game of hangman, and also to see -the status of your current game. If you use the "start" option, then a -new game of hangman will be started (if you are not currently in a game) -with a random word. Use the "guess" command to guess a letter in the word. diff --git a/files/helpfiles/help b/files/helpfiles/help deleted file mode 100644 index b24d3dd..0000000 --- a/files/helpfiles/help +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS help -~OLUsage :~RS help [|commands|credits|nuts] - -This command will display the helpfile for any command given. If you -specify "commands" or omit the command then a list of all the commands -you have use of will be listed. You can also view various credits. diff --git a/files/helpfiles/history b/files/helpfiles/history deleted file mode 100644 index 5ac8062..0000000 --- a/files/helpfiles/history +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS history -~OLUsage :~RS history - -This allows you to see the history list of the given user, detailing -promotions, demotions, muzzles, etc. diff --git a/files/helpfiles/home b/files/helpfiles/home deleted file mode 100644 index 849b95d..0000000 --- a/files/helpfiles/home +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS home -~OLUsage :~RS home - -If you are on a remote site this will bring you back to your home site -without you having to wander back to the gateway. You will be placed in -the room on your home site that is linked to the remote service which -you were just on. diff --git a/files/helpfiles/ignall b/files/helpfiles/ignall deleted file mode 100644 index 986eeac..0000000 --- a/files/helpfiles/ignall +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ignall -~OLUsage :~RS ignall - -This allows you to ignore all of the conversations, tells, shouts, etc. -Acts as a toggle, so type it again to listen. diff --git a/files/helpfiles/ignbeeps b/files/helpfiles/ignbeeps deleted file mode 100644 index 977672b..0000000 --- a/files/helpfiles/ignbeeps +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS ignbeeps -~OLUsage :~RS ignbeeps - -This will allow you to ignore beeps. Acts as a toggle on or off. diff --git a/files/helpfiles/igngreets b/files/helpfiles/igngreets deleted file mode 100644 index 87fcd3b..0000000 --- a/files/helpfiles/igngreets +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS igngreets -~OLUsage :~RS igngreets - -This will allow you to ignore all greets. Acts as a toggle on or off. diff --git a/files/helpfiles/ignlist b/files/helpfiles/ignlist deleted file mode 100644 index 5bddb0d..0000000 --- a/files/helpfiles/ignlist +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ignlist -~OLUsage :~RS ignlist - -Shows a list of things you can ignore, and if you are currently ignoring -them or not. diff --git a/files/helpfiles/ignlogons b/files/helpfiles/ignlogons deleted file mode 100644 index bc7245d..0000000 --- a/files/helpfiles/ignlogons +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ignlogons -~OLUsage :~RS ignlogons - -This will allow you to ignore all logons and logoffs. Acts as a toggle -on or off. diff --git a/files/helpfiles/ignpics b/files/helpfiles/ignpics deleted file mode 100644 index 32a2be5..0000000 --- a/files/helpfiles/ignpics +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS ignpics -~OLUsage :~RS ignpics - -This will allow you to ignore pictures. Acts as a toggle on or off. diff --git a/files/helpfiles/ignshout b/files/helpfiles/ignshout deleted file mode 100644 index 2dd3b49..0000000 --- a/files/helpfiles/ignshout +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS ignshout -~OLUsage :~RS ignshout - -This allows you to ignore any shouts. Acts as a toggle on or off. diff --git a/files/helpfiles/igntell b/files/helpfiles/igntell deleted file mode 100644 index e094df1..0000000 --- a/files/helpfiles/igntell +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS igntell -~OLUsage :~RS igntell - -This allows you to ignore any tells. Acts as a toggle on or off. diff --git a/files/helpfiles/ignuser b/files/helpfiles/ignuser deleted file mode 100644 index 635f4ab..0000000 --- a/files/helpfiles/ignuser +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS ignuser -~OLUsage :~RS ignuser [] - -This allows you to ignore tells, pemotes, shouts, etc., from any one -user. If you omit the then you will be shown a list of all users -you are currently ignoring. See "version" for the amount of users you -can ignore. diff --git a/files/helpfiles/ignwiz b/files/helpfiles/ignwiz deleted file mode 100644 index fddbf62..0000000 --- a/files/helpfiles/ignwiz +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ignwiz -~OLUsage :~RS ignwiz - -This will allow you to ignore all wiztells and wizemotes. Acts as a -toggle on or off. diff --git a/files/helpfiles/inmsg b/files/helpfiles/inmsg deleted file mode 100644 index d698424..0000000 --- a/files/helpfiles/inmsg +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS inmsg -~OLUsage :~RS inmsg [] - -This sets the message that is displayed following your name when you -enter a room. i.e., if your name is Andy and you set it to "enters fast" -other people will see "Andy enters fast" when you enter a room. If you -omit the message it shows you your current message. diff --git a/files/helpfiles/invis b/files/helpfiles/invis deleted file mode 100644 index 0a132fd..0000000 --- a/files/helpfiles/invis +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS invis -~OLUsage :~RS invis - -This will make you become invisible so that other users cannot see you -in the room or in the "who" list. diff --git a/files/helpfiles/invite b/files/helpfiles/invite deleted file mode 100644 index 5caebff..0000000 --- a/files/helpfiles/invite +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS invite -~OLUsage :~RS invite - -This will allow a user of your choice to be invited into a private room -which you are currently in. diff --git a/files/helpfiles/join b/files/helpfiles/join deleted file mode 100644 index 928f0b7..0000000 --- a/files/helpfiles/join +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS join -~OLUsage :~RS join - -Allows you to join no matter what room they are currently -in--providing the room is currently public, or you have been invited in. diff --git a/files/helpfiles/kill b/files/helpfiles/kill deleted file mode 100644 index db72f76..0000000 --- a/files/helpfiles/kill +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS kill -~OLUsage :~RS kill - -This will log off the given user from the talker. diff --git a/files/helpfiles/knock b/files/helpfiles/knock deleted file mode 100644 index 792581e..0000000 --- a/files/helpfiles/knock +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS knock -~OLUsage :~RS knock - -This sends a message to people in a private room letting them know that -you wish to enter. It is then up to them to invite you in. diff --git a/files/helpfiles/last b/files/helpfiles/last deleted file mode 100644 index 909d9ef..0000000 --- a/files/helpfiles/last +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS last -~OLUsage :~RS last [] - -Shows you when a user was last logged on. If the option is omitted -then a list of the last number of users to log on will be displayed. diff --git a/files/helpfiles/lban b/files/helpfiles/lban deleted file mode 100644 index 2289dfd..0000000 --- a/files/helpfiles/lban +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS lban -~OLUsage :~RS lban sites|users|new|swears - -This will list all the sites, or all the users, or all new users from a -sites currently banned from logging in. Also lists the swear words that -are banned. diff --git a/files/helpfiles/listen b/files/helpfiles/listen deleted file mode 100644 index 475cfef..0000000 --- a/files/helpfiles/listen +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS listen -~OLUsage :~RS listen - -This command will allow you to hear the room conversation, shouts, etc, -if you were previously ignoring. diff --git a/files/helpfiles/lmail b/files/helpfiles/lmail deleted file mode 100644 index 61176be..0000000 --- a/files/helpfiles/lmail +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS lmail -~OLUsage :~RS lmail |wizzes|all [] - -This command allows mail to be sent to all the users of a specific level. -It can also be used to send mail to all of the staff (wizzes) and also to -all of the users who have an account. If the is omitted then the -line editor will be used to input the mail message. This command should -only be used when there are a minimal amount of users on, as it may cause -the talker to lag if the mail is being sent to a large amount of people. diff --git a/files/helpfiles/logging b/files/helpfiles/logging deleted file mode 100644 index ef239bf..0000000 --- a/files/helpfiles/logging +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS logging -~OLUsage :~RS logging -l|-s|-r|-n|-e|-on|-off - -This allows you to toggle the logging functions. Using the -l option with -list the status of the logging function. The -s, -r, -n, and -e options -will toggle the system log, request log, netlinks log, and error log -(respectively). The -on option will turn all logging on, and -off will -turn everything off. diff --git a/files/helpfiles/look b/files/helpfiles/look deleted file mode 100644 index 7026027..0000000 --- a/files/helpfiles/look +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS look -~OLUsage :~RS look - -Shows the description of the current room, who is in it, how many messages -are on the board and the rooms access. diff --git a/files/helpfiles/macros b/files/helpfiles/macros deleted file mode 100644 index 28b95b9..0000000 --- a/files/helpfiles/macros +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS macros -~OLUsage :~RS macros - -Displays a list of all the macros you currently have. See "help files" -for help on how to use macros. diff --git a/files/helpfiles/makeinvis b/files/helpfiles/makeinvis deleted file mode 100644 index dda8643..0000000 --- a/files/helpfiles/makeinvis +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS makeinvis -~OLUsage :~RS makeinvis - -Forces a user to become invisible if they are visible. diff --git a/files/helpfiles/makevis b/files/helpfiles/makevis deleted file mode 100644 index e79a89a..0000000 --- a/files/helpfiles/makevis +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS makevis -~OLUsage :~RS makevis - -Forces a user to become visible if they are invisible. diff --git a/files/helpfiles/map b/files/helpfiles/map deleted file mode 100644 index 1e4f890..0000000 --- a/files/helpfiles/map +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS map -~OLUsage :~RS map - -This will show you a map of the current area of the talker you are in. diff --git a/files/helpfiles/memcount b/files/helpfiles/memcount deleted file mode 100644 index ae42adb..0000000 --- a/files/helpfiles/memcount +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS memcount -~OLUsage :~RS memcount - -Allows you to see how much memory the talker objects are currently using. diff --git a/files/helpfiles/minlogin b/files/helpfiles/minlogin deleted file mode 100644 index 41867de..0000000 --- a/files/helpfiles/minlogin +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS minlogin -~OLUsage :~RS minlogin -a|-n| - -This sets the minimum login level of a user that can log in. This directly -affects the main port but only affects the wizport if it is set higher -than the wizport login level. If set to -a all login attempts on the -main port will be refused. If set to -n then anyone can log in and new -users can be created. If set to no new accounts can be created -but users of level and above can still log in. diff --git a/files/helpfiles/mode b/files/helpfiles/mode deleted file mode 100644 index 53afb25..0000000 --- a/files/helpfiles/mode +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS mode -~OLUsage :~RS mode - -This switches you between speech mode and command mode. In speech mode -everything you type is interpreted as speech unless it is preceded by -a dot. In command mode you get a standard command type or prompt and -everything is treated as a command, to talk you must explicitly use the -"say" command. diff --git a/files/helpfiles/money b/files/helpfiles/money deleted file mode 100644 index 0df028b..0000000 --- a/files/helpfiles/money +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS money -~OLUsage :~RS money -l -~OL :~RS money -g -~OL :~RS money -t - -This command allows you to manage money. Using the -l option shows you -how much money all the only users have. The -g option allows you to give -money to users (without money being donated from your account). The -t -option allows you to take money from users (without money going into -your own account). diff --git a/files/helpfiles/monitor b/files/helpfiles/monitor deleted file mode 100644 index 65ddeee..0000000 --- a/files/helpfiles/monitor +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS monitor -~OLUsage :~RS monitor - -This will toggle on and off the ability to see a users name on an echo, -shout, shemote, bcast, etc., if that user is invis. This is a good way -to keep track of any people who abuse the "greet" and "echo" commands. diff --git a/files/helpfiles/move b/files/helpfiles/move deleted file mode 100644 index 20545b3..0000000 --- a/files/helpfiles/move +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS move -~OLUsage :~RS move [] - -Moves a user directly to the chosen room. If the room is omitted the -user is moved to the same room as whoever invoked the command (no matter -if that room is fixed private or not). The user is not moved if he is -already in the chosen room. diff --git a/files/helpfiles/mutter b/files/helpfiles/mutter deleted file mode 100644 index f2b2c26..0000000 --- a/files/helpfiles/mutter +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS mutter -~OLUsage :~RS mutter - -This will send the text to everyone in the room except the given user. diff --git a/files/helpfiles/muzzle b/files/helpfiles/muzzle deleted file mode 100644 index ffa45be..0000000 --- a/files/helpfiles/muzzle +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS muzzle -~OLUsage :~RS muzzle - -Muzzles a user which prevents them speaking, shouting, emoting and -mailing other people. The muzzle levels are based upon the user level -of whoever muzzled the user. If an ARCH muzzles a user, for instance, -the user can be unmuzzled by any ARCH or GOD, if however he is muzzled -by a GOD then only another GOD can unmuzzle him. diff --git a/files/helpfiles/mybgone b/files/helpfiles/mybgone deleted file mode 100644 index 044086b..0000000 --- a/files/helpfiles/mybgone +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS mybgone -~OLUsage :~RS mybgone |all - -You can use this command to expel people from your personal room. You -can just move one person by using the option, else if you supply -"all" then everyone in the room will be expelled. diff --git a/files/helpfiles/myclones b/files/helpfiles/myclones deleted file mode 100644 index 742a23f..0000000 --- a/files/helpfiles/myclones +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS myclones -~OLUsage :~RS myclones - -This will show you the rooms on the system where you currently have -clones. diff --git a/files/helpfiles/mykey b/files/helpfiles/mykey deleted file mode 100644 index 83445d9..0000000 --- a/files/helpfiles/mykey +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS mykey -~OLUsage :~RS mykey [] - -This command allows you to give specific users access to your personal -room even though you may have it locked. If you omit the attribute -then it will list all users you have given keys to. The does not -have to be online for you to give them a key. This command also works -like a toggle, in that if you have given a user a key you use this -command again with their name to remove that key. diff --git a/files/helpfiles/mylock b/files/helpfiles/mylock deleted file mode 100644 index 5d9d303..0000000 --- a/files/helpfiles/mylock +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS mylock -~OLUsage :~RS mylock - -This enables you to lock your personal room to anyone else, stopping -them from entering. This works as a toggle, so that if you the room is -locked then you would use this command to unlock it. The personal rooms -function has to be active to use this command. diff --git a/files/helpfiles/myname b/files/helpfiles/myname deleted file mode 100644 index 7bca5a3..0000000 --- a/files/helpfiles/myname +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS myname -~OLUsage :~RS myname - -This enables you to rename your personal room so it has something other -than "(~$)" as the name. The personal rooms function has to be active -to use this command. diff --git a/files/helpfiles/mypaint b/files/helpfiles/mypaint deleted file mode 100644 index d27ead8..0000000 --- a/files/helpfiles/mypaint +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS mypaint -~OLUsage :~RS mypaint [] - -With this you can set a new description for your personal room. -The personal rooms function has to be active to use this command. diff --git a/files/helpfiles/myroom b/files/helpfiles/myroom deleted file mode 100644 index 8f96281..0000000 --- a/files/helpfiles/myroom +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS myroom -~OLUsage :~RS myroom [-d] - -This command allows you to move into your own personal room. If your -room does not exist then it will create one for you automatically. You -may set your own room description, topic and also have your own personal -message board. If the "-d" option is used then your room will be deleted. -The personal rooms function must be active to use this command. diff --git a/files/helpfiles/netdata b/files/helpfiles/netdata deleted file mode 100644 index 6c03540..0000000 --- a/files/helpfiles/netdata +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS netdata -~OLUsage :~RS netdata - -This command shows whether any data is being received via the net -connections. It takes no arguments. The data in this case is either mail -or a message which is the passing of text (e.g., a room description) -to the remote user. diff --git a/files/helpfiles/netstat b/files/helpfiles/netstat deleted file mode 100644 index 4c1eeaf..0000000 --- a/files/helpfiles/netstat +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS netstat -~OLUsage :~RS netstat - -This shows the current net link status. diff --git a/files/helpfiles/news b/files/helpfiles/news deleted file mode 100644 index 7d93adf..0000000 --- a/files/helpfiles/news +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS news -~OLUsage :~RS news - -Allows you to read the news file (if there is one). diff --git a/files/helpfiles/nocopys b/files/helpfiles/nocopys deleted file mode 100644 index baf8ef2..0000000 --- a/files/helpfiles/nocopys +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS nocopys -~OLUsage :~RS nocopys - -Allows you to reset your "copies to" so that you will send out no copies -on your next mail. diff --git a/files/helpfiles/nuke b/files/helpfiles/nuke deleted file mode 100644 index 694c4c8..0000000 --- a/files/helpfiles/nuke +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS nuke -~OLUsage :~RS nuke - -This will enable you to delete the files of a given user. This can be used -if the user has been banned, or their user files have been corrupted, etc. diff --git a/files/helpfiles/outmsg b/files/helpfiles/outmsg deleted file mode 100644 index 63650b9..0000000 --- a/files/helpfiles/outmsg +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS outmsg -~OLUsage :~RS outmsg [] - -This sets the message that is displayed following your name when you -leave a room. i.e., if your name is Andy and you set it to "leaves fast" -other people will see "Andy leaves fast" when you leave a room. If you -omit the message it shows you your current message. diff --git a/files/helpfiles/passwd b/files/helpfiles/passwd deleted file mode 100644 index b4cb418..0000000 --- a/files/helpfiles/passwd +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS passwd -~OLUsage :~RS passwd [] - -You can change your old password for a new password. If you are able -to use the option then you do not need to know the user's old -password. diff --git a/files/helpfiles/pemote b/files/helpfiles/pemote deleted file mode 100644 index 792a551..0000000 --- a/files/helpfiles/pemote +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS pemote -~OLUsage :~RS pemote -~OLAliases :~RS < or / - -Allows you to send a private emote to the given user. diff --git a/files/helpfiles/people b/files/helpfiles/people deleted file mode 100644 index d93156c..0000000 --- a/files/helpfiles/people +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS people -~OLUsage :~RS people [key] - -This shows who is logged on and gives various stats for them. This will -also show a user's logon site, port and various other stats. If the -"key" option is used then information about the different login stages -will be displayed. diff --git a/files/helpfiles/picture b/files/helpfiles/picture deleted file mode 100644 index 4d7ce73..0000000 --- a/files/helpfiles/picture +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS picture -~OLUsage :~RS picture - -Allows you to show a picture to everyone in the same room you are in. diff --git a/files/helpfiles/preview b/files/helpfiles/preview deleted file mode 100644 index c6080de..0000000 --- a/files/helpfiles/preview +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS preview -~OLUsage :~RS preview [] - -This command will allow you to preview a picture before you tell it to a -user or the room you are currently in. If no picture name is given then -a list of all the pictures on file will be shown. diff --git a/files/helpfiles/private b/files/helpfiles/private deleted file mode 100644 index b7262ea..0000000 --- a/files/helpfiles/private +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS private -~OLUsage :~RS private [] - -This will set the room you are currently in to a private status, providing -it is not fixed and that there are enough users in it. If you are of -high enough level to use the option then you are able to change -the status of the given room providing there are enough people in it or -it is not fixed. diff --git a/files/helpfiles/promote b/files/helpfiles/promote deleted file mode 100644 index 522f551..0000000 --- a/files/helpfiles/promote +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS promote -~OLUsage :~RS promote [] - -Will promote a user to the next level up in the ranks list. You cannot -promote above the highest level, and you cannot promote anyone of the -same or higher level as you. You can also promote a user even if they -are not logged on. If the option is used then the user will be -promoted directly to that given level, rather than the level above what -they currently are. diff --git a/files/helpfiles/prompt b/files/helpfiles/prompt deleted file mode 100644 index 9135b4c..0000000 --- a/files/helpfiles/prompt +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS prompt -~OLUsage :~RS prompt - -Toggles the prompt on and off. The prompt has the following layout: - - - -The + will only appear if you are invisible. - -The ! will only appear if you are ignoring anything. diff --git a/files/helpfiles/ptell b/files/helpfiles/ptell deleted file mode 100644 index a72293b..0000000 --- a/files/helpfiles/ptell +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS ptell -~OLUsage :~RS ptell - -Allows you to show a picture to an individual user. diff --git a/files/helpfiles/public b/files/helpfiles/public deleted file mode 100644 index 2ec587d..0000000 --- a/files/helpfiles/public +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS public -~OLUsage :~RS public [] - -This will set the room you are currently in to a public status, providing -that it is not a fixed room. If you are of high enough level to use the - option then you can change any room to public, providing it is -not fixed. diff --git a/files/helpfiles/purge b/files/helpfiles/purge deleted file mode 100644 index 796b3c4..0000000 --- a/files/helpfiles/purge +++ /dev/null @@ -1,16 +0,0 @@ -~OLCommand :~RS purge -~OLUsage :~RS purge -d -~OL :~RS purge -s -~OL :~RS purge -t - -This gives you a manual way of purging user files. If the "-d" option is -used then a purge will be conducted with the default time settings. If -the "-s" option is used then anyone from the supplied will be -purged. If the "-t" option is used then anyone who last logged on more -that ago will be purged. - -A purge is automatically done when the talker is booted if the auto_purge -option is active, and also every time the default non-use time period -has expired (take taken from after boot-up time). - -CAUTION: With a purge the system may be temporarily lagged. diff --git a/files/helpfiles/quit b/files/helpfiles/quit deleted file mode 100644 index 830f4b5..0000000 --- a/files/helpfiles/quit +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS quit -~OLUsage :~RS quit - -Quits your current session on the talker. diff --git a/files/helpfiles/ranks b/files/helpfiles/ranks deleted file mode 100644 index e0e4922..0000000 --- a/files/helpfiles/ranks +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ranks -~OLUsage :~RS ranks - -Displays the ranks and the commands per rank used on the talker. Your rank -will be displayed in highlights providing you have set colour to be on. diff --git a/files/helpfiles/rcountu b/files/helpfiles/rcountu deleted file mode 100644 index d1a72f8..0000000 --- a/files/helpfiles/rcountu +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS rcountu -~OLUsage :~RS rcountu - -This command allows you to re-check the nodes in the complete linked list -of users to see if any should be added or removed, and also check the -recorded levels of users. This would mainly be used if you had manually -altered the user files, or added any file or deleted them. diff --git a/files/helpfiles/read b/files/helpfiles/read deleted file mode 100644 index d048425..0000000 --- a/files/helpfiles/read +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS read -~OLUsage :~RS read [] [] - -This displays the message board of the given room, providing you have -access to that room (i.e., it is not fixed to private). If no room is -given it defaults to your current room. If you use the option, -then you will read only that specific message number for the room given, -or the room you are in if no room name is supplied. diff --git a/files/helpfiles/reboot b/files/helpfiles/reboot deleted file mode 100644 index 150e19e..0000000 --- a/files/helpfiles/reboot +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS reboot -~OLUsage :~RS reboot [|cancel] - -This will reboot the talker, either immediately or after -seconds. The cancel option will cancel any timed reboot. diff --git a/files/helpfiles/recaps b/files/helpfiles/recaps deleted file mode 100644 index 738fd21..0000000 --- a/files/helpfiles/recaps +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS recaps -~OLUsage :~RS recaps - -Toggles on and off the ability for users to be able to recap their name. diff --git a/files/helpfiles/recount b/files/helpfiles/recount deleted file mode 100644 index 07e669b..0000000 --- a/files/helpfiles/recount +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS recount -~OLUsage :~RS recount [motds] - -This allows the boards to be recounted in case they have been edited -manually and the count has an error. If the "motds" option is used the -pre- and post-login motds will be recounted. diff --git a/files/helpfiles/reload b/files/helpfiles/reload deleted file mode 100644 index f4f1405..0000000 --- a/files/helpfiles/reload +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS reload -~OLUsage :~RS reload - -Reloads the bullets of your gun--used in the fighting game. diff --git a/files/helpfiles/reminder b/files/helpfiles/reminder deleted file mode 100644 index 131ee10..0000000 --- a/files/helpfiles/reminder +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS reminder -~OLUsage :~RS (to view) -~OL :~RS reminder all | today | [ []] -~OL :~RS (to manage) -~OL :~RS reminder set | del - -This command allows you to set reminders. You will be notified of the -number of reminders you have for that day when logging in, and reminders -will also be signified on the calendar output. If the date for a reminder -has passed then the reminder will automatically be removed. diff --git a/files/helpfiles/resite b/files/helpfiles/resite deleted file mode 100644 index e313433..0000000 --- a/files/helpfiles/resite +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS resite -~OLUsage :~RS resite |-a - -If you are running the ident daemon then this command will allow you -to get the site of the given user again. This is useful if the ident -daemon was not running at the time they logged in. If you use the -a -option then it will get the sites of all the users logged on. - diff --git a/files/helpfiles/retire b/files/helpfiles/retire deleted file mode 100644 index 9dcfe32..0000000 --- a/files/helpfiles/retire +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS retire -~OLUsage :~RS retire - -This allows you to retire a staff member from "active wiz duty", in that -it removes their name from the wizlist. diff --git a/files/helpfiles/revafk b/files/helpfiles/revafk deleted file mode 100644 index f2819f7..0000000 --- a/files/helpfiles/revafk +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS revafk -~OLUsage :~RS revafk - -This will allow you to review any tells or pemotes you received whilst -being afk. diff --git a/files/helpfiles/revedit b/files/helpfiles/revedit deleted file mode 100644 index 066b27b..0000000 --- a/files/helpfiles/revedit +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS revedit -~OLUsage :~RS revedit - -This will allow you to review any tells or pemotes you received whilst -using the line editor. diff --git a/files/helpfiles/review b/files/helpfiles/review deleted file mode 100644 index 006d0e5..0000000 --- a/files/helpfiles/review +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS review -~OLUsage :~RS review - -This will show the last few lines of conversation, emoting and echoes -that took place in the room which you are in. diff --git a/files/helpfiles/revshout b/files/helpfiles/revshout deleted file mode 100644 index ee7aa72..0000000 --- a/files/helpfiles/revshout +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS revshout -~OLUsage :~RS revshout - -This reviews the past few shouts sent over the talker. diff --git a/files/helpfiles/revtell b/files/helpfiles/revtell deleted file mode 100644 index abf44d4..0000000 --- a/files/helpfiles/revtell +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS revtell -~OLUsage :~RS revtell - -This will allow you to review the last number of tells set to you. diff --git a/files/helpfiles/rloadrm b/files/helpfiles/rloadrm deleted file mode 100644 index 36eb75b..0000000 --- a/files/helpfiles/rloadrm +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS rloadrm -~OLUsage :~RS rloadrm -a| - -With this you can reload room descriptions should you need to. This -command would generally be used if the room description file had been -altered and you wanted to implement the changes (e.g., spelling mistakes -corrected) without rebooting the talker. If the "-a" option is used then -all room descriptions will be reloaded, else just the description for -the room supplied. This will not effect personal rooms. diff --git a/files/helpfiles/rmadmin b/files/helpfiles/rmadmin deleted file mode 100644 index d52d1e2..0000000 --- a/files/helpfiles/rmadmin +++ /dev/null @@ -1,12 +0,0 @@ -~OLCommand :~RS rmadmin -~OLUsage :~RS rmadmin -l|-m|-u |-d - -This command is to aid management of personal rooms for admins. If you -use the "-l" option then it will list all personal rooms currently active -on the talker with some information about each room. If the "-m" option -is used then it will display the amount of memory the personal rooms is -currently using. If the "-u" option is used together with a name of a -user then it will unload that user's personal room from memory. If the -"-d" option is used with a name, then it will unload that user's personal -room from memory and delete all associated room files (message board, -room description, etc.). diff --git a/files/helpfiles/rmail b/files/helpfiles/rmail deleted file mode 100644 index 1e7a8f8..0000000 --- a/files/helpfiles/rmail +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS rmail -~OLUsage :~RS rmail [new|] - -If the option "new" is used then you will read only those messages that -have been sent to you since you last checked your mailbox. If a message -number is given then you will be shown only that message, otherwise any -mail that you currently have in your mailbox. Message numbers can be -checked using the "from" command. diff --git a/files/helpfiles/rnet b/files/helpfiles/rnet deleted file mode 100644 index 4ffaafb..0000000 --- a/files/helpfiles/rnet +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS rnet -~OLUsage :~RS rnet - -This shows the list of rooms and associated net connections linked -to them. diff --git a/files/helpfiles/rooms b/files/helpfiles/rooms deleted file mode 100644 index 6794a18..0000000 --- a/files/helpfiles/rooms +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS rooms -~OLUsage :~RS rooms [-l] - -Displays a list of the rooms and their current topics. If the "-l" (ell) -option is used then any rooms that require you to be of a certain level -and above to enter will be displayed. diff --git a/files/helpfiles/rstat b/files/helpfiles/rstat deleted file mode 100644 index bc102cb..0000000 --- a/files/helpfiles/rstat +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS rstat -~OLUsage :~RS rstat - -Gives statistics of the service the given room is linked to providing -that room has an active netlink. diff --git a/files/helpfiles/rsug b/files/helpfiles/rsug deleted file mode 100644 index bd37d20..0000000 --- a/files/helpfiles/rsug +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS rsug -~OLUsage :~RS rsug - -Reads the suggestions board. diff --git a/files/helpfiles/rules b/files/helpfiles/rules deleted file mode 100644 index 59bb6cf..0000000 --- a/files/helpfiles/rules +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS rules -~OLUsage :~RS rules - -Displays a list of rules that are currently enforced on the talker. diff --git a/files/helpfiles/samesite b/files/helpfiles/samesite deleted file mode 100644 index 80a7e97..0000000 --- a/files/helpfiles/samesite +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS samesite -~OLUsage :~RS samesite user|site [all] - -The will display the users with the same site as a give user or given -site. If you omit the "all" option then it will only check those users -currently logged on, else it will check through all the users. If you use -the "user" option, then you will be prompted for a user name to check -against, else if you use the "site" option you will be prompted for a -site string. You can use a full or partial site string, but no wildcards. diff --git a/files/helpfiles/save b/files/helpfiles/save deleted file mode 100644 index dd0cdc9..0000000 --- a/files/helpfiles/save +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS save -~OLUsage :~RS save - -This will save the userfiles of everyone who is currently logged on. diff --git a/files/helpfiles/say b/files/helpfiles/say deleted file mode 100644 index e3a6fb7..0000000 --- a/files/helpfiles/say +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS say -~OLUsage :~RS say - -This command is only needed when in COMMAND mode, since in SPEECH mode -everything you type that is not a command is interpreted as speech. diff --git a/files/helpfiles/sayto b/files/helpfiles/sayto deleted file mode 100644 index b6763c6..0000000 --- a/files/helpfiles/sayto +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS sayto -~OLUsage :~RS sayto -~OLAliases :~RS - - -This is used to direct speech to someone in the same room as yourself. -The text will be seen by anyone in the room, though it will be displayed -such as: "() Andy says: ..." diff --git a/files/helpfiles/search b/files/helpfiles/search deleted file mode 100644 index 58dd357..0000000 --- a/files/helpfiles/search +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS search -~OLUsage :~RS search [...] - -This will search for the words in the word list in all of the message -boards that you could possibly see. i.e., it wont show you anything from -boards that are in rooms fixed to private if you do not have access to -those rooms. The word search is done on an OR basis, ie if any of the -words are in a given message it will be shown as opposed to all of the -words having to be there. diff --git a/files/helpfiles/semote b/files/helpfiles/semote deleted file mode 100644 index 6ebc638..0000000 --- a/files/helpfiles/semote +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS semote -~OLUsage :~RS semote -~OLAliases :~RS & or ! - -Allows you to shout an emote to everyone logged on no matter what room -they are in. diff --git a/files/helpfiles/set b/files/helpfiles/set deleted file mode 100644 index 8df2800..0000000 --- a/files/helpfiles/set +++ /dev/null @@ -1,11 +0,0 @@ -~OLCommand :~RS set -~OLUsage :~RS set [ []] - -Allows you to set certain attributes that will be shown when someone -uses the "ustat" command on you. By omitting the you will -display the current settings of your attributes, and also a description -of what the attributes mean. Some attributes are a toggle between on -and off, and others require you entering a . Use ".set show" -to show what attributes you are able to change via the set command. You -can also see detailed info by using ".help set show" for the list of -other help files for the set command. diff --git a/files/helpfiles/set_age b/files/helpfiles/set_age deleted file mode 100644 index 18390eb..0000000 --- a/files/helpfiles/set_age +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS set age -~OLUsage :~RS set age - -This allows you to set your age for others to see with the .ustat command. diff --git a/files/helpfiles/set_alert b/files/helpfiles/set_alert deleted file mode 100644 index 21261e1..0000000 --- a/files/helpfiles/set_alert +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set alert -~OLUsage :~RS set alert - -This attribute is a toggle, meaning that if it is off, using this turns -it on, if it is on, using this turns it off, and it will indicate as such. -This particular toggle will turn on or off whether or not you are alerted -when one of your friends in your friends list logs into the talker. diff --git a/files/helpfiles/set_autofwd b/files/helpfiles/set_autofwd deleted file mode 100644 index 0a70d42..0000000 --- a/files/helpfiles/set_autofwd +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set autofwd -~OLUsage :~RS set autofwd - -This attribute is a toggle, meaning that if it is off, using this turns it -on, if it is on, using this turns it off, and it will indicate as such. -This particular toggle will turn on or off whether or not you have your -local mail forwarded to your email address. diff --git a/files/helpfiles/set_colour b/files/helpfiles/set_colour deleted file mode 100644 index 1e456cc..0000000 --- a/files/helpfiles/set_colour +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS set colour -~OLUsage :~RS set colour - -This attribute is a toggle, meaning that if it is off, using this turns -it on, if it is on, using this turns it off, and it will indicate as such. -This toggle will turn on or off the ability to see colours. diff --git a/files/helpfiles/set_command b/files/helpfiles/set_command deleted file mode 100644 index 6eee0d0..0000000 --- a/files/helpfiles/set_command +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set command -~OLUsage :~RS set command - -This attribute is a toggle, meaning that if it is off, using this turns it -on, if it is on, using this turns it off, and it will indicate as such. -This toggle affects the help file (.help), by showing whether you see -the commands listed by level, or by functionality. Try both! diff --git a/files/helpfiles/set_email b/files/helpfiles/set_email deleted file mode 100644 index 0a4d7f1..0000000 --- a/files/helpfiles/set_email +++ /dev/null @@ -1,16 +0,0 @@ -~OLCommand :~RS set email -~OLUsage :~RS set email [] - -This setting takes your email address, and places it in your userfile for -use in local mail to email autoforwarding (if system allows), and will -show in the .ustat command depending on if you have also used ".set hide" -to hide your email address from other users. - -Using this command alone will clear your email setting. Setting your -email will send you a short form letter to the set email address, -that will include a verification code to use with the .verify command. -Your email address MUST be verified with this code to be able to use -the forwarding if the talker allows. - -Your email address is kept hidden by default. See ".help set hide" -for more details. diff --git a/files/helpfiles/set_gender b/files/helpfiles/set_gender deleted file mode 100644 index a72ec03..0000000 --- a/files/helpfiles/set_gender +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS set gender -~OLUsage :~RS set gender m|f|n - -This attribute is used to set your gender, to "m" (male), "f" (female) -or "n" (neuter). diff --git a/files/helpfiles/set_hide b/files/helpfiles/set_hide deleted file mode 100644 index 672df82..0000000 --- a/files/helpfiles/set_hide +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS set hide -~OLUsage :~RS set hide - -This attribute is a toggle, meaning that if it is off, using this turns it -on, if it is on, using this turns it off, and it will indicate as such. -This will hide your email address from view (using the .ustat command) -to other users that are not part of the talker staff. This talker, by -default, does NOT show your email to other users that are not part of -the administrative staff. diff --git a/files/helpfiles/set_icq b/files/helpfiles/set_icq deleted file mode 100644 index 413ffe6..0000000 --- a/files/helpfiles/set_icq +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS set icq -~OLUsage :~RS set icq [] - -Use this attribute to set your ICQ User Identification Number to be shown -to other users. Using this command without entering a number will clear -the attribute. diff --git a/files/helpfiles/set_pager b/files/helpfiles/set_pager deleted file mode 100644 index 1a53e69..0000000 --- a/files/helpfiles/set_pager +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set pager -~OLUsage :~RS set pager - -This attribute is used to set your pager length. It can be set to a -number between 15 and 99. Default page length is 23. The pager effects -any files viewed, such as textfiles, helpfiles, and any other text the -talker supplies. diff --git a/files/helpfiles/set_password b/files/helpfiles/set_password deleted file mode 100644 index 6c17c93..0000000 --- a/files/helpfiles/set_password +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set password -~OLUsage :~RS set password - -This attribute is a toggle, meaning that if it is off, using this turns it -on, if it is on, using this turns it off, and it will indicate as such. -Default will not show your password typed out during login. Setting this -toggle will allow you to see your password as you type it in at login. diff --git a/files/helpfiles/set_rdesc b/files/helpfiles/set_rdesc deleted file mode 100644 index e91ee57..0000000 --- a/files/helpfiles/set_rdesc +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set rdesc -~OLUsage :~RS set rdesc - -This attribute is a toggle, meaning that if it is off, using this turns it -on, if it is on, using this turns it off, and it will indicate as such. -This setting will turn off room descriptions as you move from room to -room, or when using the "look" command. Default setting is on. diff --git a/files/helpfiles/set_recap b/files/helpfiles/set_recap deleted file mode 100644 index 9941892..0000000 --- a/files/helpfiles/set_recap +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS set recap -~OLUsage :~RS set recap - -Use this setting to change the way your name looks. For example, if -your username is Doggiedog, and you would prefer it to be DoggieDog, -use this command to change its appearance. diff --git a/files/helpfiles/set_revbuf b/files/helpfiles/set_revbuf deleted file mode 100644 index d3781d8..0000000 --- a/files/helpfiles/set_revbuf +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS set revbuff -~OLUsage :~RS set revbuff - -This attribute toggles (switches between two states) whether you see -the review buffer in ascending or descending order by date. diff --git a/files/helpfiles/set_room b/files/helpfiles/set_room deleted file mode 100644 index 56de585..0000000 --- a/files/helpfiles/set_room +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS set room -~OLUsage :~RS set room - -This attribute is a toggle, meaning that if it is off, using this turns -it on, if it is on, using this turns it off, and it will indicate as such. -This setting changes where you will enter upon login. Default is the main -room. You can change to the room you left from if so desired, provided -that the room you left is public. If the room you left from when you -login is now private, you will be sent to the main room by default. diff --git a/files/helpfiles/set_show b/files/helpfiles/set_show deleted file mode 100644 index f756dd5..0000000 --- a/files/helpfiles/set_show +++ /dev/null @@ -1,11 +0,0 @@ -~OLCommand :~RS set show -~OLUsage :~RS set show - -This will show you a quick menu of what attributes can be changed. -Use ".help set " to view help on these: - -gender age email www -hide wrap pager colour -room autofwd password rdesc -command recap icq alert -revbuff diff --git a/files/helpfiles/set_wrap b/files/helpfiles/set_wrap deleted file mode 100644 index 76b9d56..0000000 --- a/files/helpfiles/set_wrap +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS set wrap -~OLUsage :~RS set wrap - -This attribute is a toggle, meaning that if it is off, using this turns it -on, if it is on, using this turns it off, and it will indicate as such. -This will attempt to wrap all screen output to 80 columns if your client -is unable to do so. diff --git a/files/helpfiles/set_www b/files/helpfiles/set_www deleted file mode 100644 index abc7739..0000000 --- a/files/helpfiles/set_www +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS set www -~OLUsage :~RS set www [] - -Use this to enter your homepage address that is shown to other users -with the "ustat" command. Using this attribute with no address will -clear the current setting. diff --git a/files/helpfiles/setcmdlev b/files/helpfiles/setcmdlev deleted file mode 100644 index d590464..0000000 --- a/files/helpfiles/setcmdlev +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS setcmdlev -~OLUsage :~RS setcmdlev |norm - -With this command you can alter the level of a command for the time that -the talker is active. (i.e., if you reboot or shutdown the talker then -the command will revert back to its normal level). If you give the option -"norm" then you will revert the command back to its normal level. You -can only alter the levels of commands you have access to. diff --git a/files/helpfiles/sfrom b/files/helpfiles/sfrom deleted file mode 100644 index cab7998..0000000 --- a/files/helpfiles/sfrom +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS sfrom -~OLUsage :~RS sfrom - -This shows you the suggestions posted without actually showing the -content of the messages. diff --git a/files/helpfiles/shackle b/files/helpfiles/shackle deleted file mode 100644 index 223e7d3..0000000 --- a/files/helpfiles/shackle +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS shackle -~OLUsage :~RS shackle - -This will stop a user from moving out of the room they are currently in. diff --git a/files/helpfiles/shoot b/files/helpfiles/shoot deleted file mode 100644 index b9f0eea..0000000 --- a/files/helpfiles/shoot +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS shoot -~OLUsage :~RS shoot - -Attempts to shoot another user in the shooting game. This can only be -done from a specific room (see ".rooms level" for the room). diff --git a/files/helpfiles/shout b/files/helpfiles/shout deleted file mode 100644 index c783bdc..0000000 --- a/files/helpfiles/shout +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS shout -~OLUsage :~RS shout -~OLAliases :~RS [ - -This allows you to "shout" to the whole talker. diff --git a/files/helpfiles/show b/files/helpfiles/show deleted file mode 100644 index 4991c73..0000000 --- a/files/helpfiles/show +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS show -~OLUsage :~RS show -~OLAliases :~RS ' - -This is used to show a person what to type. The text is preceded with -"Type --> " and it will be display to all the users in the room you -are in. diff --git a/files/helpfiles/shutdown b/files/helpfiles/shutdown deleted file mode 100644 index 1cbb174..0000000 --- a/files/helpfiles/shutdown +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS shutdown -~OLUsage :~RS shutdown [|cancel] - -This will shutdown the talker, either immediately or after -seconds. The cancel option will cancel any timed shutdown. diff --git a/files/helpfiles/sing b/files/helpfiles/sing deleted file mode 100644 index 563b9ec..0000000 --- a/files/helpfiles/sing +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS sing -~OLUsage :~RS sing [] - -Displays the text enclosed in musical notes to the room you are currently -in. For example: Andy sings o/~ lalala o/~ diff --git a/files/helpfiles/site b/files/helpfiles/site deleted file mode 100644 index 0cf0de1..0000000 --- a/files/helpfiles/site +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS site -~OLUsage :~RS site - -This shows you the internet address from which the given user is logging -in from. diff --git a/files/helpfiles/smail b/files/helpfiles/smail deleted file mode 100644 index 4620c87..0000000 --- a/files/helpfiles/smail +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS smail -~OLUsage :~RS smail [@] [] - -Allows you to send mail to a user (possibly a remote talker via a netlink -service if your talker is currently connected to it). If you omit the -text then you will be put into the line editor and allowed to enter you -message that way. This is recommended if you have a lot you want to write. diff --git a/files/helpfiles/sos b/files/helpfiles/sos deleted file mode 100644 index 4ad2896..0000000 --- a/files/helpfiles/sos +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS sos -~OLUsage :~RS sos - -Use this to speak to any of the wizzes that are currently on. The usage -of this command should only be when you are very stuck, or if someone is -being abusive or harassing, etc, and you need to inform the wizzes of it. diff --git a/files/helpfiles/spodlist b/files/helpfiles/spodlist deleted file mode 100644 index a60cfcd..0000000 --- a/files/helpfiles/spodlist +++ /dev/null @@ -1,11 +0,0 @@ -~OLCommand :~RS spodlist -~OLUsage :~RS spodlist -~OL :~RS spodlist -~OL :~RS spodlist - -This command will allow you to see your position, or the position of any -other user, on the spod list. If you use the command without any options, -then it will show your position on the spod list. If you use it with -the option, then it will show whoever is in that position of the -spodlist. If you use the option, then it will show the position -of that user. diff --git a/files/helpfiles/sreboot b/files/helpfiles/sreboot deleted file mode 100644 index 18d93fd..0000000 --- a/files/helpfiles/sreboot +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS sreboot -~OLUsage :~RS sreboot [|cancel] - -This will seamless reboot the talker, either immediately or after - seconds. The cancel option will cancel any timed seamless -reboot. diff --git a/files/helpfiles/suggest b/files/helpfiles/suggest deleted file mode 100644 index 250f0fb..0000000 --- a/files/helpfiles/suggest +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS suggest -~OLUsage :~RS suggest [] - -Allows you to suggest something that you think needs changing or adding -to the talker. The staff really DO look at this and take notice of it, so -please make yourself heard and shape the talker to make it a better place. diff --git a/files/helpfiles/suicide b/files/helpfiles/suicide deleted file mode 100644 index 3ef2f98..0000000 --- a/files/helpfiles/suicide +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS suicide -~OLUsage :~RS suicide - -This allows you to delete your own account, also wiping your profile -and any mail you may have. diff --git a/files/helpfiles/swban b/files/helpfiles/swban deleted file mode 100644 index 58d0d40..0000000 --- a/files/helpfiles/swban +++ /dev/null @@ -1,9 +0,0 @@ -~OLCommand :~RS swban -~OLUsage :~RS swban - -This toggles a ban on swearing on and off or to a minimum level. If the -swear ban is off, then the user can say anything. If the swear ban is -set to minimum, then any swear words that the user says will be blanked -out with whatever word is hard coded into the talker code. If the swear -ban is set to off then the user will not be able to say anything if it -has a swear word in it. diff --git a/files/helpfiles/switch b/files/helpfiles/switch deleted file mode 100644 index f6ad437..0000000 --- a/files/helpfiles/switch +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS switch -~OLUsage :~RS switch [] - -This allows you to swap places with one of your clones in the given room. -It saves you destroying the current clone, creating another in your -current room then moving yourself to the old clones room. If you only have -one clone active then you can omit the room name to switch with the clone. diff --git a/files/helpfiles/system b/files/helpfiles/system deleted file mode 100644 index f94daa9..0000000 --- a/files/helpfiles/system +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS system -~OLUsage :~RS system -m|-n|-r|-u|-a - -This shows various system parameters. If you use the -m option, then you -will see memory information, -n you will see netlink information, -r will -give you room information and -u will give you use related information. -The -a option shows all information. diff --git a/files/helpfiles/tell b/files/helpfiles/tell deleted file mode 100644 index 941894a..0000000 --- a/files/helpfiles/tell +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS tell -~OLUsage :~RS tell -~OLAliases :~RS > - -This allows you to speak privately to another user without having to -find a private room. diff --git a/files/helpfiles/terminal b/files/helpfiles/terminal deleted file mode 100644 index eecb84e..0000000 --- a/files/helpfiles/terminal +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS terminal -~OLUsage :~RS terminal - -Displays your terminal settings that the talker knows about. diff --git a/files/helpfiles/think b/files/helpfiles/think deleted file mode 100644 index a30d927..0000000 --- a/files/helpfiles/think +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS think -~OLUsage :~RS think [] - -Displays the text enclosed in think bubbles to the room you are currently -in. For example: Andy thinks . o O ( therefor I am ) diff --git a/files/helpfiles/time b/files/helpfiles/time deleted file mode 100644 index 05dd30b..0000000 --- a/files/helpfiles/time +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS time -~OLUsage :~RS time - -Displays the current system time of where the talker is running from, -and also the uptime of the talker and boot time of the talker. diff --git a/files/helpfiles/topic b/files/helpfiles/topic deleted file mode 100644 index ba4cd7a..0000000 --- a/files/helpfiles/topic +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS topic -~OLUsage :~RS topic - -You can set the topic of the room you are in to the text given. diff --git a/files/helpfiles/tpromote b/files/helpfiles/tpromote deleted file mode 100644 index 5b1658c..0000000 --- a/files/helpfiles/tpromote +++ /dev/null @@ -1,11 +0,0 @@ -~OLCommand :~RS tpromote -~OLUsage :~RS tpromote [] - -Will promote a user temporarily to the next level up in the ranks -list. This promotion will only last as long as the user is logged -on. Once they log out they will be returned to their normal level. You -cannot promote above the highest level, and you cannot promote anyone -of the same or higher level as you. You can also tpromote a user even if -they are logged on. If the option is used then the user will be -promoted directly to that given level, rather than the level above that -which they currently are. diff --git a/files/helpfiles/twiz b/files/helpfiles/twiz deleted file mode 100644 index 1f61719..0000000 --- a/files/helpfiles/twiz +++ /dev/null @@ -1,7 +0,0 @@ -~OLCommand :~RS twiz -~OLUsage :~RS twiz [] - -This command allows the Wiz to tell to various Wiz levels though they -can only specifically tell to levels equal or less that of their own. -If no level is specified it defaults to WIZ which means all the Wiz -levels can hear it. Example of tell to all ARCHs: .twiz arch hiya! diff --git a/files/helpfiles/unarrest b/files/helpfiles/unarrest deleted file mode 100644 index 9946d5e..0000000 --- a/files/helpfiles/unarrest +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS unarrest -~OLUsage :~RS unarrest - -Resets the user's level to what it was before they were arrested and moves -them out of the jail to the main room (ie, the room you start off in). -The user does not have to be online to be unarrested. The unarrest is -level based, in that is an ARCH arrested someone then only an ARCH or -higher can unarrest them. The arresting level is displayed in the ustat. diff --git a/files/helpfiles/unban b/files/helpfiles/unban deleted file mode 100644 index b7a0d71..0000000 --- a/files/helpfiles/unban +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS unban -~OLUsage :~RS unban site | user | new - -This will remove a site, user, or new users from site ban if the ban is -currently set. diff --git a/files/helpfiles/uncall b/files/helpfiles/uncall deleted file mode 100644 index e331bcf..0000000 --- a/files/helpfiles/uncall +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS uncall -~OLUsage :~RS uncall - -Will reset your quick call so that it is unset. diff --git a/files/helpfiles/unfix b/files/helpfiles/unfix deleted file mode 100644 index 06607f8..0000000 --- a/files/helpfiles/unfix +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS unfix -~OLUsage :~RS unfix [] - -This will unfix a rooms access so that it can then be set to public or -private using the appropriate commands. If you omit the room, then you -will unfix the access of the room you are currently in. diff --git a/files/helpfiles/uninvite b/files/helpfiles/uninvite deleted file mode 100644 index b02b8dd..0000000 --- a/files/helpfiles/uninvite +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS uninvite -~OLUsage :~RS uninvite - -This will allow you to cancel the invitation for a user to join you in -a private room. You can only uninvite a user you have previously invited. diff --git a/files/helpfiles/unmuzzle b/files/helpfiles/unmuzzle deleted file mode 100644 index 9b797c2..0000000 --- a/files/helpfiles/unmuzzle +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS unmuzzle -~OLUsage :~RS unmuzzle - -Removes a muzzle from a user. This will only work if the user was muzzled -by someone of equal or lesser level that yourself. diff --git a/files/helpfiles/unretire b/files/helpfiles/unretire deleted file mode 100644 index 8ea2006..0000000 --- a/files/helpfiles/unretire +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS unretire -~OLUsage :~RS unretire - -This command allows you to unretire a wiz, making their name show up -again on the wizlist. diff --git a/files/helpfiles/unshackle b/files/helpfiles/unshackle deleted file mode 100644 index 66b094d..0000000 --- a/files/helpfiles/unshackle +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS unshackle -~OLUsage :~RS unshackle - -This will allow a user to move out of the room that they are currently -in if they are already shackled. diff --git a/files/helpfiles/ustat b/files/helpfiles/ustat deleted file mode 100644 index c66fb65..0000000 --- a/files/helpfiles/ustat +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS ustat -~OLUsage :~RS ustat [] - -Shows the attributes and details of the user. If you omit the user ustat -defaults to you. diff --git a/files/helpfiles/verify b/files/helpfiles/verify deleted file mode 100644 index c1e2bfd..0000000 --- a/files/helpfiles/verify +++ /dev/null @@ -1,10 +0,0 @@ -~OLCommand :~RS verify -~OLUsage :~RS verify - -This will allow you verify that you have been sent an email from the -talker. You will be sent this when you set your email address using -the "set" command (providing you specify a correct email address). -Once you get the email you will have a verification code which you must -supply before you can have local mail forwarded to your email address. -Use the "set" command to turn on or off auto-forwarding. Use the "fmail" -command to manually forward select local mail. diff --git a/files/helpfiles/version b/files/helpfiles/version deleted file mode 100644 index 19e5f82..0000000 --- a/files/helpfiles/version +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS version -~OLUsage :~RS version - -Shows the current talker version, and also information on various settings -on the talker. diff --git a/files/helpfiles/viewlog b/files/helpfiles/viewlog deleted file mode 100644 index 1694844..0000000 --- a/files/helpfiles/viewlog +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS viewlog -~OLUsage :~RS viewlog sys|net|req|err| [] - -Shows various logs. If is specified then you will only view that -amount of lines from the end of the log. In doing this, you do not have -to page through the log. diff --git a/files/helpfiles/vis b/files/helpfiles/vis deleted file mode 100644 index 88cd06f..0000000 --- a/files/helpfiles/vis +++ /dev/null @@ -1,4 +0,0 @@ -~OLCommand :~RS vis -~OLUsage :~RS vis - -This will make you visible again if you are invisible. diff --git a/files/helpfiles/visit b/files/helpfiles/visit deleted file mode 100644 index 3393a24..0000000 --- a/files/helpfiles/visit +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS visit -~OLUsage :~RS visit - -Allows you to move to another user's personal room, providing it already -exists and is not locked. The personal rooms function must be active to -use this command. diff --git a/files/helpfiles/wake b/files/helpfiles/wake deleted file mode 100644 index 13e467e..0000000 --- a/files/helpfiles/wake +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS wake -~OLUsage :~RS wake - -This sends a message and a beeping noise to the users terminal hopefully -attracting their attention. diff --git a/files/helpfiles/who b/files/helpfiles/who deleted file mode 100644 index 5e680d1..0000000 --- a/files/helpfiles/who +++ /dev/null @@ -1,6 +0,0 @@ -~OLCommand :~RS who -~OLUsage :~RS who -~OLAliases :~RS @ - -This shows a full list of people currently logged on to the talker, -including descriptions, areas, etc. diff --git a/files/helpfiles/wipe b/files/helpfiles/wipe deleted file mode 100644 index d55171e..0000000 --- a/files/helpfiles/wipe +++ /dev/null @@ -1,14 +0,0 @@ -~OLCommand :~RS wipe -~OLUsage :~RS wipe -~OL :~RS wipe to -~OL :~RS wipe from to -~OL :~RS wipe all - -This command allows you to delete messages from the board of the room -that you are in. You can delete specific single messages by supplying -just a number, or from the first message to a given number. You can -also delete from a certain message to another message, or you can delete -all the board. If a given number to delete to is higher than the actual -amount of message on the board, then all the messages will be deleted. -If you are in your personal room (if this function is currently active) -then you can wipe any message, regardless of who posted it. diff --git a/files/helpfiles/wizlist b/files/helpfiles/wizlist deleted file mode 100644 index ca9f11b..0000000 --- a/files/helpfiles/wizlist +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS wizlist -~OLUsage :~RS wizlist - -Displays a list of the current staff members and shows which, if any, -are presently logged on. diff --git a/files/helpfiles/write b/files/helpfiles/write deleted file mode 100644 index 3065753..0000000 --- a/files/helpfiles/write +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS write -~OLUsage :~RS write [] - -This writes a message on the board of the room you are in. If you omit -the text you are put into the line editor to enter your message in that. diff --git a/files/helpfiles/wrules b/files/helpfiles/wrules deleted file mode 100644 index b15090e..0000000 --- a/files/helpfiles/wrules +++ /dev/null @@ -1,5 +0,0 @@ -~OLCommand :~RS wrules -~OLUsage :~RS wrules - -Displays a list of rules for the admin that are currently enforced on -the talker. diff --git a/files/helpfiles/xcom b/files/helpfiles/xcom deleted file mode 100644 index 4aefe75..0000000 --- a/files/helpfiles/xcom +++ /dev/null @@ -1,8 +0,0 @@ -~OLCommand :~RS xcom -~OLUsage :~RS xcom [] - -This enables you to remove usage of a command from the given that -they would normally have. If the option is not used then it -will display any commands that the user has been removed access from. This -works as a toggle, so that if the is not already listed for the -user then they will lose access to it else the access will be given back. diff --git a/src/amnuts.c b/src/amnuts.c index 4fe94ea..9b7dc76 100644 --- a/src/amnuts.c +++ b/src/amnuts.c @@ -50,6 +50,12 @@ main(int argc, char **argv) /* Startup and do initial counts and parses */ init_signals(); create_system(); + yaml_check_migration_gate(); + yaml_load_config(DATAFILES "/config.yaml"); + yaml_load_rooms(DATAFILES "/rooms.yaml"); + yaml_load_help(DATAFILES "/help.yaml"); + validate_config(); + write_syslog(SYSLOG, 0, "------------------------------------------------------------------------------\nSERVER BOOTING\n"); printf @@ -65,9 +71,6 @@ main(int argc, char **argv) #else printf("Netlinks : Disabled\n"); #endif - - load_and_parse_config(); - printf("Flood protection is %s.\n", offon[amsys->flood_protect]); if (amsys->personal_rooms) { if (amsys->startup_room_parse) { @@ -998,132 +1001,21 @@ socket_listen(const char *host, const char *serv) *****************************************************************************/ -#define ML_ENTRY(a) ML_EXPAND a - -#define CONFIG_LIST \ - ML_ENTRY((INIT, "INIT" )) \ - ML_ENTRY((ROOMS, "ROOMS" )) \ - ML_ENTRY((TOPICS, "TOPICS")) \ - ML_ENTRY((SITES, "SITES" )) \ - ML_ENTRY((COUNT, NULL )) - +/* + * Cross-cutting validation run after the YAML config + rooms have been + * loaded but before help is loaded. Checks port presence and uniqueness, + * rejects an empty room list, and reconciles netlink service names with + * room names. Loaders themselves enforce field presence within their own + * files; this function handles checks that span both. + */ void -load_and_parse_config(void) +validate_config(void) { - - enum config_value { -#define ML_EXPAND(value,name) CONFIG_ ## value, - CONFIG_LIST -#undef ML_EXPAND - }; - static const char *const sections[] = { -#define ML_EXPAND(value,name) name, - CONFIG_LIST -#undef ML_EXPAND - }; - FILE *fp; - char line[81]; /* Should be long enough */ - char filename[80], *s, *t; - const char *const *section; - int c, i; - unsigned got_sections; - RM_OBJECT rm1, rm2; #ifdef NETLINKS NL_OBJECT nl; + RM_OBJECT rm1; #endif - printf("Parsing config file \"%s\"...\n", confile); - sprintf(filename, "%s/%s", DATAFILES, confile); - fp = fopen(filename, "r"); - if (!fp) { - perror("Amnuts: Cannot open config file"); - boot_exit(1); - } - section = NULL; - got_sections = 0; - /* Main reading loop */ - config_line = 0; - for (s = fgets(line, 81, fp); s; s = fgets(line, 81, fp)) { - ++config_line; - /* Handle comments */ - t = strchr(s, '#'); - if (t) { - *t = '\0'; - } - for (i = 0; i < 8; ++i) { - *wrd[i] = '\0'; - } - sscanf(s, "%s %s %s %s %s %s %s %s", wrd[0], wrd[1], wrd[2], wrd[3], - wrd[4], wrd[5], wrd[6], wrd[7]); - if (!*wrd[0]) { - continue; - } - /* See if new section */ - t = strchr(wrd[0], ':'); - if (t && !t[1]) { - *t = '\0'; - for (section = sections; *section; ++section) { - if (!strcmp(wrd[0], *section)) { - break; - } - } - if (!*section) { - fprintf(stderr, "Amnuts: Unknown section header on line %d.\n", - config_line); - fclose(fp); - boot_exit(1); - } - if (got_sections & BIT(section - sections)) { - fprintf(stderr, "Amnuts: Unexpected %s section header on line %d.\n", - *section, config_line); - fclose(fp); - boot_exit(1); - } - got_sections |= BIT(section - sections); - continue; - } - if (!section) { - fprintf(stderr, "Amnuts: Section header expected on line %d.\n", - config_line); - fclose(fp); - boot_exit(1); - } - switch ((enum config_value) (section - sections)) { - case CONFIG_INIT: - parse_init_section(); - break; - case CONFIG_ROOMS: - parse_rooms_section(); - break; - case CONFIG_TOPICS: - parse_topics_section(remove_first(s)); - break; - case CONFIG_SITES: -#ifdef NETLINKS - parse_sites_section(); -#endif - break; - default: - break; - } - } - fclose(fp); - /* - * See if required sections were present (SITES and TOPICS is optional) - * and if required parameters were set. - */ - if (!(got_sections & BIT(CONFIG_INIT))) { - fprintf(stderr, "Amnuts: INIT section missing from config file.\n"); - boot_exit(1); - } - if (!(got_sections & BIT(CONFIG_ROOMS))) { - fprintf(stderr, "Amnuts: ROOMS section missing from config file.\n"); - if (got_sections & BIT(CONFIG_TOPICS)) { - fprintf(stderr, - "Amnuts: TOPICS section must come after ROOMS section in the config file.\n"); - } - boot_exit(1); - } if (!*amsys->mport_port) { fprintf(stderr, "Amnuts: Main port number not set in config file.\n"); boot_exit(1); @@ -1159,37 +1051,12 @@ load_and_parse_config(void) #endif #endif if (!room_first) { - fprintf(stderr, "Amnuts: No rooms configured in config file.\n"); + fprintf(stderr, "Amnuts: No rooms configured.\n"); boot_exit(1); } - /* Parsing done, now check data is valid. Check room stuff first. */ - for (rm1 = room_first; rm1; rm1 = rm1->next) { - for (i = 0; i < MAX_LINKS; ++i) { - if (!*rm1->link_label[i]) { - break; - } - for (rm2 = room_first; rm2; rm2 = rm2->next) { - if (!strcmp(rm1->link_label[i], rm2->label)) { - break; - } - } - if (!rm2) { - fprintf(stderr, "Amnuts: Room %s has undefined link label \"%s\".\n", - rm1->name, rm1->link_label[i]); - boot_exit(1); - } - if (rm2 == rm1) { - fprintf(stderr, "Amnuts: Room %s cannot link to itself.\n", - rm1->name); - boot_exit(1); - } - rm1->link[i] = rm2; - } - } - #ifdef NETLINKS - /* Check service names */ + /* Service names must not collide with room names */ for (nl = nl_first; nl; nl = nl->next) { for (rm1 = room_first; rm1; rm1 = rm1->next) { if (!strcmp(nl->service, rm1->name)) { @@ -1203,7 +1070,7 @@ load_and_parse_config(void) } } - /* Check external links */ + /* Resolve room netlink_name to netlink object */ for (rm1 = room_first; rm1; rm1 = rm1->next) { if (!*rm1->netlink_name) { continue; @@ -1221,858 +1088,9 @@ load_and_parse_config(void) rm1->netlink = nl; } #endif - - /* Load room descriptions */ - for (rm1 = room_first; rm1; rm1 = rm1->next) { - sprintf(filename, "%s/%s.R", DATAFILES, rm1->name); - fp = fopen(filename, "r"); - if (!fp) { - fprintf(stderr, "Amnuts: Cannot open description file for room %s.\n", - rm1->name); - write_syslog(SYSLOG | ERRLOG, 0, - "ERROR: Cannot open description file for room %s.\n", - rm1->name); - continue; - } - i = 0; - for (c = getc(fp); c != EOF; c = getc(fp)) { - if (i == ROOM_DESC_LEN) { - break; - } - rm1->desc[i++] = c; - } - if (c != EOF) { - fprintf(stderr, "Amnuts: Description too long for room %s.\n", - rm1->name); - write_syslog(SYSLOG | ERRLOG, 0, - "ERROR: Description too long for room %s.\n", rm1->name); - } - rm1->desc[i++] = '\0'; - fclose(fp); - } -} - -#define INITOPT_LIST \ - ML_ENTRY((MAIN_PORT, "mainport" )) \ - ML_ENTRY((WIZ_PORT, "wizport" )) \ - ML_ENTRY((LINK_PORT, "linkport" )) \ - ML_ENTRY((SYSTEM_LOGGING, "system_logging" )) \ - ML_ENTRY((MINLOGIN_LEVEL, "minlogin_level" )) \ - ML_ENTRY((MESG_LIFE, "mesg_life" )) \ - ML_ENTRY((WIZPORT_LEVEL, "wizport_level" )) \ - ML_ENTRY((PROMPT_DEF, "prompt_def" )) \ - ML_ENTRY((GATECRASH_LEVEL, "gatecrash_level" )) \ - ML_ENTRY((MIN_PRIVATE, "min_private" )) \ - ML_ENTRY((IGNORE_MP_LEVEL, "ignore_mp_level" )) \ - ML_ENTRY((REM_USER_MAXLEVEL, "rem_user_maxlevel" )) \ - ML_ENTRY((REM_USER_DEFLEVEL, "rem_user_deflevel" )) \ - ML_ENTRY((VERIFICATION, "verification" )) \ - ML_ENTRY((MESG_CHECK_TIME, "mesg_check_time" )) \ - ML_ENTRY((MAX_USERS, "max_users" )) \ - ML_ENTRY((HEARTBEAT, "heartbeat" )) \ - ML_ENTRY((LOGIN_IDLE_TIME, "login_idle_time" )) \ - ML_ENTRY((USER_IDLE_TIME, "user_idle_time" )) \ - ML_ENTRY((PASSWORDECHO_DEF, "passwordecho_def" )) \ - ML_ENTRY((IGNORE_SIGTERM, "ignore_sigterm" )) \ - ML_ENTRY((AUTO_CONNECT, "auto_connect" )) \ - ML_ENTRY((MAX_CLONES, "max_clones" )) \ - ML_ENTRY((BAN_SWEARING, "ban_swearing" )) \ - ML_ENTRY((CRASH_ACTION, "crash_action" )) \ - ML_ENTRY((COLOUR_DEF, "colour_def" )) \ - ML_ENTRY((TIME_OUT_AFKS, "time_out_afks" )) \ - ML_ENTRY((CHARECHO_DEF, "charecho_def" )) \ - ML_ENTRY((TIME_OUT_MAXLEVEL, "time_out_maxlevel" )) \ - ML_ENTRY((AUTO_PURGE, "auto_purge" )) \ - ML_ENTRY((ALLOW_RECAPS, "allow_recaps" )) \ - ML_ENTRY((AUTO_PROMOTE, "auto_promote" )) \ - ML_ENTRY((PERSONAL_ROOMS, "personal_rooms" )) \ - ML_ENTRY((RANDOM_MOTDS, "random_motds" )) \ - ML_ENTRY((STARTUP_ROOM_PARSE, "startup_room_parse")) \ - ML_ENTRY((RESOLVE_IP, "resolve_ip" )) \ - ML_ENTRY((FLOOD_PROTECT, "flood_protect" )) \ - ML_ENTRY((BOOT_OFF_MIN, "boot_off_min" )) \ - ML_ENTRY((DEF_WARP, "default_warp" )) \ - ML_ENTRY((DEF_JAIL, "default_jail" )) \ - ML_ENTRY((DEF_BANK, "default_bank" )) \ - ML_ENTRY((DEF_SHOOT, "default_shoot" )) \ - ML_ENTRY((COUNT, NULL )) - -/* - * Parse init section - */ -void -parse_init_section(void) -{ - - enum initopt_value { -#define ML_EXPAND(value,name) INITOPT_ ## value, - INITOPT_LIST -#undef ML_EXPAND - }; - static const char *const options[] = { -#define ML_EXPAND(value,name) name, - INITOPT_LIST -#undef ML_EXPAND - }; - int val; - const char *const *initopt; - - for (initopt = options; *initopt; ++initopt) { - if (!strcmp(*initopt, wrd[0])) { - break; - } - } - if (!*initopt) { - fprintf(stderr, "Amnuts: Unknown INIT option on line %d.\n", config_line); - boot_exit(1); - } - if (!*wrd[1]) { - fprintf(stderr, "Amnuts: Required parameter missing on line %d.\n", - config_line); - boot_exit(1); - } - if (*wrd[2]) { - fprintf(stderr, - "Amnuts: Unexpected word following init parameter on line %d.\n", - config_line); - boot_exit(1); - } - val = atoi(wrd[1]); - switch ((enum initopt_value) (initopt - options)) { - - case INITOPT_MAIN_PORT: - /* main port */ - if (strlen(wrd[1]) >= MAXSERV) { /* XXX: Use NI_MAXSERV */ - fprintf(stderr, "Amnuts: %s has Illegal port number on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - strcpy(amsys->mport_port, wrd[1]); - break; - - case INITOPT_WIZ_PORT: - /* wiz */ - if (strlen(wrd[1]) >= MAXSERV) { /* XXX: Use NI_MAXSERV */ - fprintf(stderr, "Amnuts: %s has Illegal port number on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef WIZPORT - strcpy(amsys->wport_port, wrd[1]); -#endif - break; - - case INITOPT_LINK_PORT: - /* link */ - if (strlen(wrd[1]) >= MAXSERV) { /* XXX: Use NI_MAXSERV */ - fprintf(stderr, "Amnuts: %s has Illegal port number on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef NETLINKS - strcpy(amsys->nlink_port, wrd[1]); -#endif - break; - - case INITOPT_SYSTEM_LOGGING: - val = onoff_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be ON or OFF on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - /* set the bits correctly */ - if (val) { - amsys->logging = SYSLOG | REQLOG | NETLOG | ERRLOG; - } else { - amsys->logging = 0; - } - break; - - case INITOPT_MINLOGIN_LEVEL: - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS && strcmp(wrd[1], "NONE")) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->minlogin_level = lvl; - } - break; - - case INITOPT_MESG_LIFE: - /* message lifetime */ - if (val < 1) { - fprintf(stderr, "Amnuts: %s has Illegal value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->mesg_life = val; - break; - - case INITOPT_WIZPORT_LEVEL: - /* wizport_level */ - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef WIZPORT - amsys->wizport_level = lvl; -#endif - } - break; - - case INITOPT_PROMPT_DEF: - /* prompt defaults */ - val = onoff_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be ON or OFF on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->prompt_def = val; - break; - - case INITOPT_GATECRASH_LEVEL: - /* gatecrash level */ - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->gatecrash_level = lvl; - } - break; - - case INITOPT_MIN_PRIVATE: - if (val < 1) { - fprintf(stderr, - "Amnuts: %s has Number too low on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->min_private_users = val; - break; - - case INITOPT_IGNORE_MP_LEVEL: - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->ignore_mp_level = lvl; - } - break; - - case INITOPT_REM_USER_MAXLEVEL: - /* - Max level a remote user can remotely log in if he does not have a local - account. ie if level set to WIZ a GOD can only be a WIZ if logging in - from another server unless he has a local account of level GOD - */ - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef NETLINKS - amsys->rem_user_maxlevel = lvl; -#endif - } - break; - - case INITOPT_REM_USER_DEFLEVEL: - /* - Default level of remote user who does not have an account on site and - connection is from a server of version 3.3.0 or lower. - */ - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef NETLINKS - amsys->rem_user_deflevel = lvl; -#endif - } - break; - - case INITOPT_VERIFICATION: - if (strlen(wrd[1]) > VERIFY_LEN) { - fprintf(stderr, "Amnuts: %s too long on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef NETLINKS - strcpy(amsys->verification, wrd[1]); -#endif - break; - - case INITOPT_MESG_CHECK_TIME: - /* mesg_check_time */ - if (wrd[1][2] != ':' || strlen(wrd[1]) > 5 || !isdigit((int) wrd[1][0]) - || !isdigit((int) wrd[1][1]) - || !isdigit((int) wrd[1][3]) - || !isdigit((int) wrd[1][4])) { - fprintf(stderr, "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - sscanf(wrd[1], "%d:%d", &amsys->mesg_check_hour, &amsys->mesg_check_min); - if (amsys->mesg_check_hour > 23 || amsys->mesg_check_min > 59) { - fprintf(stderr, "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->mesg_check_done = time(0) + 86400 - 1; - amsys->mesg_check_done -= amsys->mesg_check_done % 86400; - amsys->mesg_check_done += - 3600 * amsys->mesg_check_hour + 60 * amsys->mesg_check_min; - break; - - case INITOPT_MAX_USERS: - if (val < 1) { - fprintf(stderr, "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->max_users = val; - break; - - case INITOPT_HEARTBEAT: - if (val < 1) { - fprintf(stderr, "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->heartbeat = val; - break; - - case INITOPT_LOGIN_IDLE_TIME: - if (val < 10) { - fprintf(stderr, - "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->login_idle_time = val; - break; - - case INITOPT_USER_IDLE_TIME: - if (val < 10) { - fprintf(stderr, - "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->user_idle_time = val; - break; - - case INITOPT_PASSWORDECHO_DEF: - val = onoff_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be ON or OFF on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->passwordecho_def = val; - break; - - case INITOPT_IGNORE_SIGTERM: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->ignore_sigterm = val; - break; - - case INITOPT_AUTO_CONNECT: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef NETLINKS - amsys->auto_connect = val; -#endif - break; - - case INITOPT_MAX_CLONES: - if (val < 0) { - fprintf(stderr, "Amnuts: %s has Invalid value on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->max_clones = val; - break; - - case INITOPT_BAN_SWEARING: - val = minmax_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be OFF, MIN or MAX on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->ban_swearing = val; - break; - - case INITOPT_CRASH_ACTION: - if (!strcmp(wrd[1], "NONE")) { - amsys->crash_action = 0; - } else if (!strcmp(wrd[1], "SHUTDOWN")) { - amsys->crash_action = 1; - } else if (!strcmp(wrd[1], "REBOOT")) { - amsys->crash_action = 2; - } else if (!strcmp(wrd[1], "SEAMLESS")) { - amsys->crash_action = 3; - } else { - fprintf(stderr, - "Amnuts: %s must be NONE, SHUTDOWN, REBOOT or SEAMLESS on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - break; - - case INITOPT_COLOUR_DEF: - val = onoff_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be ON or OFF on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->colour_def = val; - break; - - case INITOPT_TIME_OUT_AFKS: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->time_out_afks = val; - break; - - case INITOPT_CHARECHO_DEF: - val = onoff_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be ON or OFF on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->charecho_def = val; - break; - - case INITOPT_TIME_OUT_MAXLEVEL: - { - enum lvl_value lvl = get_level(wrd[1]); - if (lvl == NUM_LEVELS) { - fprintf(stderr, - "Amnuts: %s has Unknown level specifier on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->time_out_maxlevel = lvl; - } - break; - - case INITOPT_AUTO_PURGE: - /* auto purge on boot up */ - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - if (!val) { - amsys->auto_purge_date = -1; - } else { - /* XXX: Fixed to the next midnight */ - amsys->auto_purge_date = time(0) + 86400 - 1; - amsys->auto_purge_date -= amsys->auto_purge_date % 86400; - } - break; - - case INITOPT_ALLOW_RECAPS: - /* allow recapping of names */ - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->allow_recaps = val; - break; - - case INITOPT_AUTO_PROMOTE: - /* define whether auto promotes are on or off */ - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->auto_promote = val; - break; - - case INITOPT_PERSONAL_ROOMS: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->personal_rooms = val; - break; - - case INITOPT_RANDOM_MOTDS: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->random_motds = val; - break; - - case INITOPT_STARTUP_ROOM_PARSE: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->startup_room_parse = val; - break; - - case INITOPT_RESOLVE_IP: - val = resolve_check(wrd[1]); - if (val == -1) { - fprintf(stderr, - "Amnuts: %s must be OFF, AUTO, MANUAL, or IDENTD on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->resolve_ip = val; - break; - - case INITOPT_FLOOD_PROTECT: - /* turns flood protection and auto-baning on and off */ - val = onoff_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be ON or OFF on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->flood_protect = val; - break; - - case INITOPT_BOOT_OFF_MIN: - val = yn_check(wrd[1]); - if (val == -1) { - fprintf(stderr, "Amnuts: %s must be YES or NO on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - amsys->boot_off_min = val; - break; - - case INITOPT_DEF_WARP: - if (strlen(wrd[1]) >= ROOM_NAME_LEN + 1) { - fprintf(stderr, "Amnuts: %s has Illegal room name on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - strcpy(amsys->default_warp, wrd[1]); - break; - - case INITOPT_DEF_JAIL: - if (strlen(wrd[1]) >= ROOM_NAME_LEN + 1) { - fprintf(stderr, "Amnuts: %s has Illegal room name on line %d.\n", - *initopt, config_line); - boot_exit(1); - } - strcpy(amsys->default_jail, wrd[1]); - break; - - case INITOPT_DEF_BANK: - if (strlen(wrd[1]) >= ROOM_NAME_LEN + 1) { - fprintf(stderr, "Amnuts: %s has Illegal room name on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef GAMES - strcpy(amsys->default_bank, wrd[1]); -#endif - break; - - case INITOPT_DEF_SHOOT: - if (strlen(wrd[1]) >= ROOM_NAME_LEN + 1) { - fprintf(stderr, "Amnuts: %s has Illegal room name on line %d.\n", - *initopt, config_line); - boot_exit(1); - } -#ifdef GAMES - strcpy(amsys->default_shoot, wrd[1]); -#endif - break; - - default: - fprintf(stderr, "Amnuts: %s is Unknown INIT option on line %d.\n", - *initopt, config_line); - boot_exit(1); - break; - - } } -/* - * Parse rooms section - */ -void -parse_rooms_section(void) -{ - RM_OBJECT room; - char *ptr1, *ptr2, c; - int i; - if (!*wrd[3]) { - fprintf(stderr, "Amnuts: Required parameter(s) missing on line %d.\n", - config_line); - boot_exit(1); - } - if (strlen(wrd[0]) > ROOM_NAME_LEN) { - fprintf(stderr, "Amnuts: Room map name too long on line %d.\n", - config_line); - boot_exit(1); - } - if (strlen(wrd[1]) > ROOM_LABEL_LEN) { - fprintf(stderr, "Amnuts: Room label too long on line %d.\n", config_line); - boot_exit(1); - } - if (strlen(wrd[2]) > ROOM_NAME_LEN) { - fprintf(stderr, "Amnuts: Room name too long on line %d.\n", config_line); - boot_exit(1); - } - /* Check for duplicate label or name */ - for (room = room_first; room; room = room->next) { - if (!strcmp(room->label, wrd[1])) { - fprintf(stderr, "Amnuts: Duplicate room label on line %d.\n", - config_line); - boot_exit(1); - } - if (!strcmp(room->name, wrd[2])) { - fprintf(stderr, "Amnuts: Duplicate room name on line %d.\n", - config_line); - boot_exit(1); - } - } - room = create_room(); - strcpy(room->map, wrd[0]); - strcpy(room->label, wrd[1]); - strcpy(room->name, wrd[2]); - strcpy(room->show_name, room->name); - /* Parse internal links bit ie hl,gd,of etc. MUST NOT be any spaces between the commas */ - i = 0; - ptr1 = wrd[3]; - ptr2 = wrd[3]; - for (;;) { - while (*ptr2 != ',' && *ptr2) { - ++ptr2; - } - if (*ptr2 == ',' && !ptr2[1]) { - fprintf(stderr, "Amnuts: Missing link label on line %d.\n", - config_line); - boot_exit(1); - } - c = *ptr2; - *ptr2 = '\0'; - if (!strcmp(ptr1, room->label)) { - fprintf(stderr, "Amnuts: Room has a link to itself on line %d.\n", - config_line); - boot_exit(1); - } - strcpy(room->link_label[i], ptr1); - if (!c) { - break; - } - if (++i >= MAX_LINKS) { - fprintf(stderr, "Amnuts: Too many links on line %d.\n", config_line); - boot_exit(1); - } - *ptr2 = c; - ptr1 = ++ptr2; - } - /* Parse access privs */ - if (!*wrd[4]) { - room->access = 0; - return; - } - if (!strcmp(wrd[4], "BOTH")) { - room->access = 0; - } else if (!strcmp(wrd[4], "PUB")) { - room->access = FIXED; - } else if (!strcmp(wrd[4], "PRIV")) { - room->access = FIXED | PRIVATE; - } else { - fprintf(stderr, "Amnuts: Unknown room access type on line %d.\n", - config_line); - boot_exit(1); - } - /* Parse external link stuff */ -#ifdef NETLINKS - if (!*wrd[5]) { - return; - } - if (!strcmp(wrd[5], "ACCEPT")) { - if (*wrd[6]) { - fprintf(stderr, - "Amnuts: Unexpected word following ACCEPT keyword on line %d.\n", - config_line); - boot_exit(1); - } - room->inlink = 1; - return; - } - if (!strcmp(wrd[5], "CONNECT")) { - if (!*wrd[6]) { - fprintf(stderr, "Amnuts: External link name missing on line %d.\n", - config_line); - boot_exit(1); - } - strcpy(room->netlink_name, wrd[6]); - if (*wrd[7]) { - fprintf(stderr, - "Amnuts: Unexpected word following external link name on line %d.\n", - config_line); - boot_exit(1); - } - return; - } - fprintf(stderr, "Amnuts: Unknown connection option on line %d.\n", - config_line); - boot_exit(1); -#endif -} - -/* - * Parse rooms desc (topic) section - */ -void -parse_topics_section(char *topic) -{ - RM_OBJECT room; - - if (!*wrd[1]) { - fprintf(stderr, "Amnuts: Required parameter(s) missing on line %d.\n", - config_line); - boot_exit(1); - } - /* Check to see if room exists */ - for (room = room_first; room; room = room->next) - if (!strcmp(room->name, wrd[0])) { - break; - } - if (!room) { - fprintf(stderr, "Amnuts: Room does not exist on line %d.\n", config_line); - boot_exit(1); - return; - } - if (topic[strlen(topic) - 1] == '\n') { - topic[strlen(topic) - 1] = '\0'; - } - *room->topic = '\0'; - strncat(room->topic, topic, TOPIC_LEN); -} - - -#ifdef NETLINKS - -/* - * Parse sites section - */ -void -parse_sites_section(void) -{ - NL_OBJECT nl; - - if (!*wrd[3]) { - fprintf(stderr, "Amnuts: Required parameter(s) missing on line %d.\n", - config_line); - boot_exit(1); - } - nl = create_netlink(); - if (!nl) { - fprintf(stderr, - "Amnuts: Memory allocation failure creating netlink on line %d.\n", - config_line); - boot_exit(1); - return; - } - if (strlen(wrd[0]) > SERV_NAME_LEN) { - fprintf(stderr, "Amnuts: Link name length too long on line %d.\n", - config_line); - boot_exit(1); - } - strcpy(nl->service, wrd[0]); - if (strlen(wrd[1]) >= MAXHOST) { /* XXX: Use NI_MAXHOST */ - fprintf(stderr, "Amnuts: Site name length too long on line %d.\n", - config_line); - boot_exit(1); - } - strcpy(nl->site, wrd[1]); - strtolower(nl->site); - if (strlen(wrd[2]) >= MAXSERV) { /* XXX: USE NI_MAXSERV */ - fprintf(stderr, "Amnuts: Illegal port number on line %d.\n", config_line); - boot_exit(1); - } - strcpy(nl->port, wrd[2]); - if (strlen(wrd[3]) > VERIFY_LEN) { - fprintf(stderr, "Amnuts: Verification too long on line %d.\n", - config_line); - boot_exit(1); - } - strcpy(nl->verification, wrd[3]); - if (!*wrd[4] || !strcmp(wrd[4], "ALL")) { - nl->allow = ALL; - } else if (!strcmp(wrd[4], "IN")) { - nl->allow = IN; - } else if (!strcmp(wrd[4], "OUT")) { - nl->allow = OUT; - } else { - fprintf(stderr, "Amnuts: Unknown netlink access type on line %d.\n", - config_line); - boot_exit(1); - } -} -#endif diff --git a/src/commands/help.c b/src/commands/help.c index 2050f07..cd2e514 100644 --- a/src/commands/help.c +++ b/src/commands/help.c @@ -4,13 +4,57 @@ #include "commands.h" #include "prototypes.h" +/* + * Render a help entry through the user's output buffer. The level line is + * always emitted because we always have the full content available. + */ +void +render_help_entry(UR_OBJECT user, const struct help_entry *entry, + enum lvl_value level) +{ + int i; + + vwrite_user(user, "~OLCommand :~RS %s\n", entry->command); + + if (entry->usage_count == 1) { + vwrite_user(user, "~OLUsage :~RS %s\n", entry->usage[0]); + } else if (entry->usage_count > 1) { + vwrite_user(user, "~OLUsage :~RS %s\n", entry->usage[0]); + for (i = 1; i < entry->usage_count; ++i) { + vwrite_user(user, "~OL :~RS %s\n", entry->usage[i]); + } + } + + if (entry->alias_count > 0) { + write_user(user, "~OLAliases :~RS "); + for (i = 0; i < entry->alias_count; ++i) { + vwrite_user(user, "%s%s", entry->aliases[i], + i + 1 < entry->alias_count ? ", " : "\n"); + } + } + + if (entry->description && *entry->description) { + write_user(user, "\n"); + write_user(user, entry->description); + size_t dlen = strlen(entry->description); + if (dlen == 0 || entry->description[dlen - 1] != '\n') { + write_user(user, "\n"); + } + } + + /* FIXME: take into account xgcoms, command list dynamic level, etc. + * (Same FIXME existed in the legacy renderer -- kept to surface that + * dynamic level lookups still aren't reflected here.) */ + vwrite_user(user, "~OLLevel :~RS %s and above\n", + user_level[level].name); +} + /* * Show the list of commands, credits, and display the help files for the given command */ void help(UR_OBJECT user) { - char filename[80]; const struct cmd_entry *com, *c; size_t len; int found; @@ -70,12 +114,10 @@ help(UR_OBJECT user) write_user(user, "Sorry, there is no help on that topic.\n"); return; } - if (word_count < 3) { - sprintf(filename, "%s/%s", HELPFILES, com->name); - } else { - if (com == command_table + SET) { + { + const struct help_entry *entry = NULL; + if (word_count >= 3 && com == command_table + SET) { const struct set_entry *attr, *a; - len = strlen(word[2]); attr = NULL; found = 0; @@ -109,27 +151,31 @@ help(UR_OBJECT user) return; } if (word_count < 4) { - sprintf(filename, "%s/%s_%s", HELPFILES, com->name, attr->type); + /* set : try the composite key first, fall back to set */ + char composite[64]; + snprintf(composite, sizeof composite, "%s_%s", + com->name, attr->type); + entry = help_lookup(composite); + if (!entry) { + entry = help_lookup(com->name); + } } else { - sprintf(filename, "%s/%s", HELPFILES, com->name); + entry = help_lookup(com->name); } - } else { + } else if (word_count >= 3) { + /* Generic case: word_count >= 3 but com != SET. Legacy code + * fell through to the help command's own help. Mirror that. */ com = command_table + HELP; - sprintf(filename, "%s/%s", HELPFILES, com->name); + entry = help_lookup(com->name); + } else { + entry = help_lookup(com->name); } - } - switch (more(user, user->socket, filename)) { - case 0: - write_user(user, "Sorry, there is no help on that topic.\n"); - break; - case 1: - user->misc_op = 2; - break; - case 2: - /* FIXME: take into account xgcoms, command list dynamic level, etc. */ - vwrite_user(user, "~OLLevel :~RS %s and above\n", - user_level[com->level].name); - break; + + if (!entry) { + write_user(user, "Sorry, there is no help on that topic.\n"); + return; + } + render_help_entry(user, entry, com->level); } } diff --git a/src/commands/reload_room.c b/src/commands/reload_room.c index f21ba2d..cc0ba26 100644 --- a/src/commands/reload_room.c +++ b/src/commands/reload_room.c @@ -4,110 +4,86 @@ #include "prototypes.h" /* - * Reloads the description for one or all rooms--in case you have edited the - * file and do not want to reboot the talker to make the changes displayed + * Reload the descriptions of one or all rooms from rooms.yaml. + * + * Useful when an admin has edited rooms.yaml on disk and wants the + * change to take effect without a full reboot. Only descriptions are + * refreshed; topic, access flags, links, and netlink wiring are left + * as they currently sit in memory so runtime mutations (.topic, + * .private, etc.) are preserved. + * + * Usage: rloadrm -a -- reload every non-personal room + * rloadrm -- reload one room by name + * + * Personal rooms are skipped because their descriptions live in + * files/userfiles/rooms/ rather than in rooms.yaml. + * + * If rooms.yaml is missing or malformed, the command reports the + * error to the user and leaves in-memory state untouched -- it does + * NOT terminate the talker, unlike the boot-time loader. */ void reload_room_description(UR_OBJECT user) { - int c, i, error; - RM_OBJECT rm; - char filename[80]; - FILE *fp; + char errbuf[256] = ""; + int n; + const char *path = DATAFILES "/rooms.yaml"; if (word_count < 2) { write_user(user, "Usage: rloadrm -a/\n"); return; } - /* if reload all of the rooms */ if (!strcmp(word[1], "-a")) { - error = 0; - for (rm = room_first; rm; rm = rm->next) { - if (is_personal_room(rm)) { - continue; + n = yaml_reload_descriptions(path, NULL, errbuf, sizeof errbuf); + if (n < 0) { + vwrite_user(user, "Sorry, cannot reload room descriptions.\n"); + if (*errbuf) { + vwrite_user(user, " %s\n", errbuf); } - sprintf(filename, "%s/%s.R", DATAFILES, rm->name); - fp = fopen(filename, "r"); - if (!fp) { - vwrite_user(user, - "Sorry, cannot reload the description file for the room \"%s\".\n", - rm->name); - write_syslog(SYSLOG | ERRLOG, 0, - "ERROR: Cannot reload the description file for room %s.\n", - rm->name); - ++error; - continue; - } - i = 0; - for (c = getc(fp); c != EOF; c = getc(fp)) { - if (i == ROOM_DESC_LEN) { - break; - } - rm->desc[i++] = c; - } - if (c != EOF) { - vwrite_user(user, - "The description is too long for the room \"%s\".\n", - rm->name); - write_syslog(SYSLOG | ERRLOG, 0, - "ERROR: Description too long when reloading for room %s.\n", - rm->name); - } - rm->desc[i] = '\0'; - fclose(fp); - } - if (!error) { - write_user(user, "You have now reloaded all room descriptions.\n"); - } else { - write_user(user, - "You have now reloaded all room descriptions that you can.\n"); + write_syslog(SYSLOG | ERRLOG, 0, + "ERROR: rloadrm -a failed: %s\n", + *errbuf ? errbuf : "unknown"); + return; } - write_syslog(SYSLOG, 1, "%s reloaded all of the room descriptions.\n", - user->name); + vwrite_user(user, "Reloaded descriptions for %d room%s.\n", + n, PLTEXT_S(n)); + write_syslog(SYSLOG, 1, + "%s reloaded all of the room descriptions.\n", user->name); return; } - /* if it is just one room to reload */ - rm = get_room(word[1]); + /* Single-room form. */ + RM_OBJECT rm = get_room(word[1]); if (!rm) { write_user(user, nosuchroom); return; } - /* check first for personal room, and do not reload */ if (is_personal_room(rm)) { write_user(user, "Sorry, but you cannot reload personal room descriptions.\n"); return; } - sprintf(filename, "%s/%s.R", DATAFILES, rm->name); - fp = fopen(filename, "r"); - if (!fp) { + n = yaml_reload_descriptions(path, rm->name, errbuf, sizeof errbuf); + if (n < 0) { vwrite_user(user, - "Sorry, cannot reload the description file for the room \"%s\".\n", + "Sorry, cannot reload the description for the room \"%s\".\n", rm->name); + if (*errbuf) { + vwrite_user(user, " %s\n", errbuf); + } write_syslog(SYSLOG | ERRLOG, 0, - "ERROR: Cannot reload the description file for room %s.\n", - rm->name); + "ERROR: rloadrm %s failed: %s\n", + rm->name, *errbuf ? errbuf : "unknown"); return; } - i = 0; - for (c = getc(fp); c != EOF; c = getc(fp)) { - if (i == ROOM_DESC_LEN) { - break; - } - rm->desc[i++] = c; - } - if (c != EOF) { - vwrite_user(user, "The description is too long for the room \"%s\".\n", - rm->name); - write_syslog(SYSLOG | ERRLOG, 0, - "ERROR: Description too long when reloading for room %s.\n", - rm->name); + if (n == 0) { + vwrite_user(user, + "Room \"%s\" was not found in rooms.yaml.\n", rm->name); + return; } - rm->desc[i] = '\0'; - fclose(fp); vwrite_user(user, "You have now reloaded the description for the room \"%s\".\n", rm->name); - write_syslog(SYSLOG, 1, "%s reloaded the description for the room %s\n", + write_syslog(SYSLOG, 1, + "%s reloaded the description for the room %s\n", user->name, rm->name); } diff --git a/src/includes/globals.h b/src/includes/globals.h index 9ff82a3..f2f7498 100644 --- a/src/includes/globals.h +++ b/src/includes/globals.h @@ -331,6 +331,20 @@ struct command_struct { CMD_OBJECT prev; }; +/* + * help entry (parsed from files/datafiles/help.yaml). The help_table + * global is a flat array sorted by command name so help_lookup() can use + * bsearch(). + */ +struct help_entry { + char *command; + char **usage; + int usage_count; + char **aliases; + int alias_count; + char *description; +}; + /* * system structure */ @@ -530,6 +544,8 @@ extern UD_OBJECT first_user_entry; extern UD_OBJECT last_user_entry; extern CMD_OBJECT first_command; extern CMD_OBJECT last_command; +extern struct help_entry *help_table; +extern int help_table_count; extern SYS_OBJECT amsys; extern char word[MAX_WORDS][WORD_LEN + 1]; extern char wrd[8][81]; @@ -668,6 +684,8 @@ UD_OBJECT first_user_entry = NULL; UD_OBJECT last_user_entry = NULL; CMD_OBJECT first_command = NULL; CMD_OBJECT last_command = NULL; +struct help_entry *help_table = NULL; +int help_table_count = 0; SYS_OBJECT amsys = NULL; diff --git a/src/includes/prototypes.h b/src/includes/prototypes.h index 8778b89..17cf4b3 100644 --- a/src/includes/prototypes.h +++ b/src/includes/prototypes.h @@ -49,13 +49,14 @@ void accept_connection(int); char *resolve_ip(char *); #endif int socket_listen(const char *, const char *); -void load_and_parse_config(void); -void parse_init_section(void); -void parse_rooms_section(void); -void parse_topics_section(char *); -#ifdef NETLINKS -void parse_sites_section(void); -#endif +void yaml_check_migration_gate(void); +void validate_config(void); +void yaml_load_config(const char *path); +void yaml_load_rooms(const char *path); +int yaml_reload_descriptions(const char *path, const char *target, + char *errbuf, size_t errbuf_size); +void yaml_load_help(const char *path); +const struct help_entry *help_lookup(const char *command); void init_signals(void); void sig_handler(int); void boot_exit(int) @@ -120,6 +121,8 @@ void who(UR_OBJECT, int); void login_who(UR_OBJECT); void display_files(UR_OBJECT, int); void help(UR_OBJECT); +void render_help_entry(UR_OBJECT user, const struct help_entry *entry, + enum lvl_value level); void help_commands_level(UR_OBJECT); void help_commands_function(UR_OBJECT); void help_nuts_credits(UR_OBJECT); @@ -446,6 +449,7 @@ int possibly_reboot(void); * functions in rooms.c */ int is_personal_room(RM_OBJECT); +void reload_room_description(UR_OBJECT); int is_fixed_room(RM_OBJECT); int is_private_room(RM_OBJECT); int is_my_room(UR_OBJECT, RM_OBJECT); @@ -475,7 +479,6 @@ void rooms(UR_OBJECT, int, int); void clear_topic(UR_OBJECT); void join(UR_OBJECT); void set_topic(UR_OBJECT, char *); -void reload_room_description(UR_OBJECT); void reset_access(RM_OBJECT); int has_room_access(UR_OBJECT, RM_OBJECT); int check_start_room(UR_OBJECT); diff --git a/src/includes/yaml.h b/src/includes/yaml.h new file mode 100644 index 0000000..4abe8d5 --- /dev/null +++ b/src/includes/yaml.h @@ -0,0 +1,33 @@ +#ifndef AMNUTS_YAML_H +#define AMNUTS_YAML_H + +#include "../vendors/libyaml/yaml.h" +#include + +/* Print a parse error in "file:line:column: message" form to stderr, + * then call boot_exit(1). Used by every loader to fail fast. */ +void yaml_die(const char *path, yaml_parser_t *parser, const char *fmt, ...) + __attribute__((format(printf, 3, 4), noreturn)); + +/* Read the next event into `event`. On parse failure, calls yaml_die. */ +void yaml_next(const char *path, yaml_parser_t *parser, yaml_event_t *event); + +/* Expect the next event to be of `type`; fail with yaml_die otherwise. + * Returns the event so the caller can read its data. The caller owns + * the event and must yaml_event_delete() it. */ +yaml_event_t yaml_expect(const char *path, yaml_parser_t *parser, + yaml_event_type_t type, const char *context); + +/* Convert a scalar event's value to a bool. Accepts: true/false, yes/no, + * 1/0 (case insensitive). Calls yaml_die on anything else. */ +bool yaml_scalar_to_bool(const char *path, yaml_event_t *ev); + +/* Convert a scalar event's value to int. Calls yaml_die on parse failure + * or out-of-range. */ +int yaml_scalar_to_int(const char *path, yaml_event_t *ev); + +/* Map a level name ("USER", "WIZ", etc.) to enum lvl_value. Returns + * NUM_LEVELS as a sentinel for "NONE". Calls yaml_die if unknown. */ +int yaml_scalar_to_level(const char *path, yaml_event_t *ev); + +#endif /* AMNUTS_YAML_H */ diff --git a/src/vendors/libyaml/LICENSE b/src/vendors/libyaml/LICENSE new file mode 100644 index 0000000..36ca9c2 --- /dev/null +++ b/src/vendors/libyaml/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2020 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/vendors/libyaml/api.c b/src/vendors/libyaml/api.c new file mode 100644 index 0000000..805c369 --- /dev/null +++ b/src/vendors/libyaml/api.c @@ -0,0 +1,1393 @@ + +#include "yaml_private.h" + +/* + * Get the library version. + */ + +YAML_DECLARE(const char *) +yaml_get_version_string(void) +{ + return YAML_VERSION_STRING; +} + +/* + * Get the library version numbers. + */ + +YAML_DECLARE(void) +yaml_get_version(int *major, int *minor, int *patch) +{ + *major = YAML_VERSION_MAJOR; + *minor = YAML_VERSION_MINOR; + *patch = YAML_VERSION_PATCH; +} + +/* + * Allocate a dynamic memory block. + */ + +YAML_DECLARE(void *) +yaml_malloc(size_t size) +{ + return malloc(size ? size : 1); +} + +/* + * Reallocate a dynamic memory block. + */ + +YAML_DECLARE(void *) +yaml_realloc(void *ptr, size_t size) +{ + return ptr ? realloc(ptr, size ? size : 1) : malloc(size ? size : 1); +} + +/* + * Free a dynamic memory block. + */ + +YAML_DECLARE(void) +yaml_free(void *ptr) +{ + if (ptr) free(ptr); +} + +/* + * Duplicate a string. + */ + +YAML_DECLARE(yaml_char_t *) +yaml_strdup(const yaml_char_t *str) +{ + if (!str) + return NULL; + + return (yaml_char_t *)strdup((char *)str); +} + +/* + * Extend a string. + */ + +YAML_DECLARE(int) +yaml_string_extend(yaml_char_t **start, + yaml_char_t **pointer, yaml_char_t **end) +{ + yaml_char_t *new_start = (yaml_char_t *)yaml_realloc((void*)*start, (*end - *start)*2); + + if (!new_start) return 0; + + memset(new_start + (*end - *start), 0, *end - *start); + + *pointer = new_start + (*pointer - *start); + *end = new_start + (*end - *start)*2; + *start = new_start; + + return 1; +} + +/* + * Append a string B to a string A. + */ + +YAML_DECLARE(int) +yaml_string_join( + yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end, + yaml_char_t **b_start, yaml_char_t **b_pointer, SHIM(yaml_char_t **b_end)) +{ + UNUSED_PARAM(b_end) + if (*b_start == *b_pointer) + return 1; + + while (*a_end - *a_pointer <= *b_pointer - *b_start) { + if (!yaml_string_extend(a_start, a_pointer, a_end)) + return 0; + } + + memcpy(*a_pointer, *b_start, *b_pointer - *b_start); + *a_pointer += *b_pointer - *b_start; + + return 1; +} + +/* + * Extend a stack. + */ + +YAML_DECLARE(int) +yaml_stack_extend(void **start, void **top, void **end) +{ + void *new_start; + + if ((char *)*end - (char *)*start >= INT_MAX / 2) + return 0; + + new_start = yaml_realloc(*start, ((char *)*end - (char *)*start)*2); + + if (!new_start) return 0; + + *top = (char *)new_start + ((char *)*top - (char *)*start); + *end = (char *)new_start + ((char *)*end - (char *)*start)*2; + *start = new_start; + + return 1; +} + +/* + * Extend or move a queue. + */ + +YAML_DECLARE(int) +yaml_queue_extend(void **start, void **head, void **tail, void **end) +{ + /* Check if we need to resize the queue. */ + + if (*start == *head && *tail == *end) { + void *new_start = yaml_realloc(*start, + ((char *)*end - (char *)*start)*2); + + if (!new_start) return 0; + + *head = (char *)new_start + ((char *)*head - (char *)*start); + *tail = (char *)new_start + ((char *)*tail - (char *)*start); + *end = (char *)new_start + ((char *)*end - (char *)*start)*2; + *start = new_start; + } + + /* Check if we need to move the queue at the beginning of the buffer. */ + + if (*tail == *end) { + if (*head != *tail) { + memmove(*start, *head, (char *)*tail - (char *)*head); + } + *tail = (char *)*tail - (char *)*head + (char *)*start; + *head = *start; + } + + return 1; +} + + +/* + * Create a new parser object. + */ + +YAML_DECLARE(int) +yaml_parser_initialize(yaml_parser_t *parser) +{ + assert(parser); /* Non-NULL parser object expected. */ + + memset(parser, 0, sizeof(yaml_parser_t)); + if (!BUFFER_INIT(parser, parser->raw_buffer, INPUT_RAW_BUFFER_SIZE)) + goto error; + if (!BUFFER_INIT(parser, parser->buffer, INPUT_BUFFER_SIZE)) + goto error; + if (!QUEUE_INIT(parser, parser->tokens, INITIAL_QUEUE_SIZE, yaml_token_t*)) + goto error; + if (!STACK_INIT(parser, parser->indents, int*)) + goto error; + if (!STACK_INIT(parser, parser->simple_keys, yaml_simple_key_t*)) + goto error; + if (!STACK_INIT(parser, parser->states, yaml_parser_state_t*)) + goto error; + if (!STACK_INIT(parser, parser->marks, yaml_mark_t*)) + goto error; + if (!STACK_INIT(parser, parser->tag_directives, yaml_tag_directive_t*)) + goto error; + + return 1; + +error: + + BUFFER_DEL(parser, parser->raw_buffer); + BUFFER_DEL(parser, parser->buffer); + QUEUE_DEL(parser, parser->tokens); + STACK_DEL(parser, parser->indents); + STACK_DEL(parser, parser->simple_keys); + STACK_DEL(parser, parser->states); + STACK_DEL(parser, parser->marks); + STACK_DEL(parser, parser->tag_directives); + + return 0; +} + +/* + * Destroy a parser object. + */ + +YAML_DECLARE(void) +yaml_parser_delete(yaml_parser_t *parser) +{ + assert(parser); /* Non-NULL parser object expected. */ + + BUFFER_DEL(parser, parser->raw_buffer); + BUFFER_DEL(parser, parser->buffer); + while (!QUEUE_EMPTY(parser, parser->tokens)) { + yaml_token_delete(&DEQUEUE(parser, parser->tokens)); + } + QUEUE_DEL(parser, parser->tokens); + STACK_DEL(parser, parser->indents); + STACK_DEL(parser, parser->simple_keys); + STACK_DEL(parser, parser->states); + STACK_DEL(parser, parser->marks); + while (!STACK_EMPTY(parser, parser->tag_directives)) { + yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); + yaml_free(tag_directive.handle); + yaml_free(tag_directive.prefix); + } + STACK_DEL(parser, parser->tag_directives); + + memset(parser, 0, sizeof(yaml_parser_t)); +} + +/* + * String read handler. + */ + +static int +yaml_string_read_handler(void *data, unsigned char *buffer, size_t size, + size_t *size_read) +{ + yaml_parser_t *parser = (yaml_parser_t *)data; + + if (parser->input.string.current == parser->input.string.end) { + *size_read = 0; + return 1; + } + + if (size > (size_t)(parser->input.string.end + - parser->input.string.current)) { + size = parser->input.string.end - parser->input.string.current; + } + + memcpy(buffer, parser->input.string.current, size); + parser->input.string.current += size; + *size_read = size; + return 1; +} + +/* + * File read handler. + */ + +static int +yaml_file_read_handler(void *data, unsigned char *buffer, size_t size, + size_t *size_read) +{ + yaml_parser_t *parser = (yaml_parser_t *)data; + + *size_read = fread(buffer, 1, size, parser->input.file); + return !ferror(parser->input.file); +} + +/* + * Set a string input. + */ + +YAML_DECLARE(void) +yaml_parser_set_input_string(yaml_parser_t *parser, + const unsigned char *input, size_t size) +{ + assert(parser); /* Non-NULL parser object expected. */ + assert(!parser->read_handler); /* You can set the source only once. */ + assert(input); /* Non-NULL input string expected. */ + + parser->read_handler = yaml_string_read_handler; + parser->read_handler_data = parser; + + parser->input.string.start = input; + parser->input.string.current = input; + parser->input.string.end = input+size; +} + +/* + * Set a file input. + */ + +YAML_DECLARE(void) +yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file) +{ + assert(parser); /* Non-NULL parser object expected. */ + assert(!parser->read_handler); /* You can set the source only once. */ + assert(file); /* Non-NULL file object expected. */ + + parser->read_handler = yaml_file_read_handler; + parser->read_handler_data = parser; + + parser->input.file = file; +} + +/* + * Set a generic input. + */ + +YAML_DECLARE(void) +yaml_parser_set_input(yaml_parser_t *parser, + yaml_read_handler_t *handler, void *data) +{ + assert(parser); /* Non-NULL parser object expected. */ + assert(!parser->read_handler); /* You can set the source only once. */ + assert(handler); /* Non-NULL read handler expected. */ + + parser->read_handler = handler; + parser->read_handler_data = data; +} + +/* + * Set the source encoding. + */ + +YAML_DECLARE(void) +yaml_parser_set_encoding(yaml_parser_t *parser, yaml_encoding_t encoding) +{ + assert(parser); /* Non-NULL parser object expected. */ + assert(!parser->encoding); /* Encoding is already set or detected. */ + + parser->encoding = encoding; +} + +/* + * Create a new emitter object. + */ + +YAML_DECLARE(int) +yaml_emitter_initialize(yaml_emitter_t *emitter) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + memset(emitter, 0, sizeof(yaml_emitter_t)); + if (!BUFFER_INIT(emitter, emitter->buffer, OUTPUT_BUFFER_SIZE)) + goto error; + if (!BUFFER_INIT(emitter, emitter->raw_buffer, OUTPUT_RAW_BUFFER_SIZE)) + goto error; + if (!STACK_INIT(emitter, emitter->states, yaml_emitter_state_t*)) + goto error; + if (!QUEUE_INIT(emitter, emitter->events, INITIAL_QUEUE_SIZE, yaml_event_t*)) + goto error; + if (!STACK_INIT(emitter, emitter->indents, int*)) + goto error; + if (!STACK_INIT(emitter, emitter->tag_directives, yaml_tag_directive_t*)) + goto error; + + return 1; + +error: + + BUFFER_DEL(emitter, emitter->buffer); + BUFFER_DEL(emitter, emitter->raw_buffer); + STACK_DEL(emitter, emitter->states); + QUEUE_DEL(emitter, emitter->events); + STACK_DEL(emitter, emitter->indents); + STACK_DEL(emitter, emitter->tag_directives); + + return 0; +} + +/* + * Destroy an emitter object. + */ + +YAML_DECLARE(void) +yaml_emitter_delete(yaml_emitter_t *emitter) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + BUFFER_DEL(emitter, emitter->buffer); + BUFFER_DEL(emitter, emitter->raw_buffer); + STACK_DEL(emitter, emitter->states); + while (!QUEUE_EMPTY(emitter, emitter->events)) { + yaml_event_delete(&DEQUEUE(emitter, emitter->events)); + } + QUEUE_DEL(emitter, emitter->events); + STACK_DEL(emitter, emitter->indents); + while (!STACK_EMPTY(empty, emitter->tag_directives)) { + yaml_tag_directive_t tag_directive = POP(emitter, emitter->tag_directives); + yaml_free(tag_directive.handle); + yaml_free(tag_directive.prefix); + } + STACK_DEL(emitter, emitter->tag_directives); + yaml_free(emitter->anchors); + + memset(emitter, 0, sizeof(yaml_emitter_t)); +} + +/* + * String write handler. + */ + +static int +yaml_string_write_handler(void *data, unsigned char *buffer, size_t size) +{ + yaml_emitter_t *emitter = (yaml_emitter_t *)data; + + if (emitter->output.string.size - *emitter->output.string.size_written + < size) { + memcpy(emitter->output.string.buffer + + *emitter->output.string.size_written, + buffer, + emitter->output.string.size + - *emitter->output.string.size_written); + *emitter->output.string.size_written = emitter->output.string.size; + return 0; + } + + memcpy(emitter->output.string.buffer + + *emitter->output.string.size_written, buffer, size); + *emitter->output.string.size_written += size; + return 1; +} + +/* + * File write handler. + */ + +static int +yaml_file_write_handler(void *data, unsigned char *buffer, size_t size) +{ + yaml_emitter_t *emitter = (yaml_emitter_t *)data; + + return (fwrite(buffer, 1, size, emitter->output.file) == size); +} +/* + * Set a string output. + */ + +YAML_DECLARE(void) +yaml_emitter_set_output_string(yaml_emitter_t *emitter, + unsigned char *output, size_t size, size_t *size_written) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + assert(!emitter->write_handler); /* You can set the output only once. */ + assert(output); /* Non-NULL output string expected. */ + + emitter->write_handler = yaml_string_write_handler; + emitter->write_handler_data = emitter; + + emitter->output.string.buffer = output; + emitter->output.string.size = size; + emitter->output.string.size_written = size_written; + *size_written = 0; +} + +/* + * Set a file output. + */ + +YAML_DECLARE(void) +yaml_emitter_set_output_file(yaml_emitter_t *emitter, FILE *file) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + assert(!emitter->write_handler); /* You can set the output only once. */ + assert(file); /* Non-NULL file object expected. */ + + emitter->write_handler = yaml_file_write_handler; + emitter->write_handler_data = emitter; + + emitter->output.file = file; +} + +/* + * Set a generic output handler. + */ + +YAML_DECLARE(void) +yaml_emitter_set_output(yaml_emitter_t *emitter, + yaml_write_handler_t *handler, void *data) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + assert(!emitter->write_handler); /* You can set the output only once. */ + assert(handler); /* Non-NULL handler object expected. */ + + emitter->write_handler = handler; + emitter->write_handler_data = data; +} + +/* + * Set the output encoding. + */ + +YAML_DECLARE(void) +yaml_emitter_set_encoding(yaml_emitter_t *emitter, yaml_encoding_t encoding) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + assert(!emitter->encoding); /* You can set encoding only once. */ + + emitter->encoding = encoding; +} + +/* + * Set the canonical output style. + */ + +YAML_DECLARE(void) +yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + emitter->canonical = (canonical != 0); +} + +/* + * Set the indentation increment. + */ + +YAML_DECLARE(void) +yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + emitter->best_indent = (1 < indent && indent < 10) ? indent : 2; +} + +/* + * Set the preferred line width. + */ + +YAML_DECLARE(void) +yaml_emitter_set_width(yaml_emitter_t *emitter, int width) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + emitter->best_width = (width >= 0) ? width : -1; +} + +/* + * Set if unescaped non-ASCII characters are allowed. + */ + +YAML_DECLARE(void) +yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + emitter->unicode = (unicode != 0); +} + +/* + * Set the preferred line break character. + */ + +YAML_DECLARE(void) +yaml_emitter_set_break(yaml_emitter_t *emitter, yaml_break_t line_break) +{ + assert(emitter); /* Non-NULL emitter object expected. */ + + emitter->line_break = line_break; +} + +/* + * Destroy a token object. + */ + +YAML_DECLARE(void) +yaml_token_delete(yaml_token_t *token) +{ + assert(token); /* Non-NULL token object expected. */ + + switch (token->type) + { + case YAML_TAG_DIRECTIVE_TOKEN: + yaml_free(token->data.tag_directive.handle); + yaml_free(token->data.tag_directive.prefix); + break; + + case YAML_ALIAS_TOKEN: + yaml_free(token->data.alias.value); + break; + + case YAML_ANCHOR_TOKEN: + yaml_free(token->data.anchor.value); + break; + + case YAML_TAG_TOKEN: + yaml_free(token->data.tag.handle); + yaml_free(token->data.tag.suffix); + break; + + case YAML_SCALAR_TOKEN: + yaml_free(token->data.scalar.value); + break; + + default: + break; + } + + memset(token, 0, sizeof(yaml_token_t)); +} + +/* + * Check if a string is a valid UTF-8 sequence. + * + * Check 'reader.c' for more details on UTF-8 encoding. + */ + +static int +yaml_check_utf8(const yaml_char_t *start, size_t length) +{ + const yaml_char_t *end = start+length; + const yaml_char_t *pointer = start; + + while (pointer < end) { + unsigned char octet; + unsigned int width; + unsigned int value; + size_t k; + + octet = pointer[0]; + width = (octet & 0x80) == 0x00 ? 1 : + (octet & 0xE0) == 0xC0 ? 2 : + (octet & 0xF0) == 0xE0 ? 3 : + (octet & 0xF8) == 0xF0 ? 4 : 0; + value = (octet & 0x80) == 0x00 ? octet & 0x7F : + (octet & 0xE0) == 0xC0 ? octet & 0x1F : + (octet & 0xF0) == 0xE0 ? octet & 0x0F : + (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; + if (!width) return 0; + if (pointer+width > end) return 0; + for (k = 1; k < width; k ++) { + octet = pointer[k]; + if ((octet & 0xC0) != 0x80) return 0; + value = (value << 6) + (octet & 0x3F); + } + if (!((width == 1) || + (width == 2 && value >= 0x80) || + (width == 3 && value >= 0x800) || + (width == 4 && value >= 0x10000))) return 0; + + pointer += width; + } + + return 1; +} + +/* + * Create STREAM-START. + */ + +YAML_DECLARE(int) +yaml_stream_start_event_initialize(yaml_event_t *event, + yaml_encoding_t encoding) +{ + yaml_mark_t mark = { 0, 0, 0 }; + + assert(event); /* Non-NULL event object is expected. */ + + STREAM_START_EVENT_INIT(*event, encoding, mark, mark); + + return 1; +} + +/* + * Create STREAM-END. + */ + +YAML_DECLARE(int) +yaml_stream_end_event_initialize(yaml_event_t *event) +{ + yaml_mark_t mark = { 0, 0, 0 }; + + assert(event); /* Non-NULL event object is expected. */ + + STREAM_END_EVENT_INIT(*event, mark, mark); + + return 1; +} + +/* + * Create DOCUMENT-START. + */ + +YAML_DECLARE(int) +yaml_document_start_event_initialize(yaml_event_t *event, + yaml_version_directive_t *version_directive, + yaml_tag_directive_t *tag_directives_start, + yaml_tag_directive_t *tag_directives_end, + int implicit) +{ + struct { + yaml_error_type_t error; + } context; + yaml_mark_t mark = { 0, 0, 0 }; + yaml_version_directive_t *version_directive_copy = NULL; + struct { + yaml_tag_directive_t *start; + yaml_tag_directive_t *end; + yaml_tag_directive_t *top; + } tag_directives_copy = { NULL, NULL, NULL }; + yaml_tag_directive_t value = { NULL, NULL }; + + assert(event); /* Non-NULL event object is expected. */ + assert((tag_directives_start && tag_directives_end) || + (tag_directives_start == tag_directives_end)); + /* Valid tag directives are expected. */ + + if (version_directive) { + version_directive_copy = YAML_MALLOC_STATIC(yaml_version_directive_t); + if (!version_directive_copy) goto error; + version_directive_copy->major = version_directive->major; + version_directive_copy->minor = version_directive->minor; + } + + if (tag_directives_start != tag_directives_end) { + yaml_tag_directive_t *tag_directive; + if (!STACK_INIT(&context, tag_directives_copy, yaml_tag_directive_t*)) + goto error; + for (tag_directive = tag_directives_start; + tag_directive != tag_directives_end; tag_directive ++) { + assert(tag_directive->handle); + assert(tag_directive->prefix); + if (!yaml_check_utf8(tag_directive->handle, + strlen((char *)tag_directive->handle))) + goto error; + if (!yaml_check_utf8(tag_directive->prefix, + strlen((char *)tag_directive->prefix))) + goto error; + value.handle = yaml_strdup(tag_directive->handle); + value.prefix = yaml_strdup(tag_directive->prefix); + if (!value.handle || !value.prefix) goto error; + if (!PUSH(&context, tag_directives_copy, value)) + goto error; + value.handle = NULL; + value.prefix = NULL; + } + } + + DOCUMENT_START_EVENT_INIT(*event, version_directive_copy, + tag_directives_copy.start, tag_directives_copy.top, + implicit, mark, mark); + + return 1; + +error: + yaml_free(version_directive_copy); + while (!STACK_EMPTY(context, tag_directives_copy)) { + yaml_tag_directive_t value = POP(context, tag_directives_copy); + yaml_free(value.handle); + yaml_free(value.prefix); + } + STACK_DEL(context, tag_directives_copy); + yaml_free(value.handle); + yaml_free(value.prefix); + + return 0; +} + +/* + * Create DOCUMENT-END. + */ + +YAML_DECLARE(int) +yaml_document_end_event_initialize(yaml_event_t *event, int implicit) +{ + yaml_mark_t mark = { 0, 0, 0 }; + + assert(event); /* Non-NULL emitter object is expected. */ + + DOCUMENT_END_EVENT_INIT(*event, implicit, mark, mark); + + return 1; +} + +/* + * Create ALIAS. + */ + +YAML_DECLARE(int) +yaml_alias_event_initialize(yaml_event_t *event, const yaml_char_t *anchor) +{ + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *anchor_copy = NULL; + + assert(event); /* Non-NULL event object is expected. */ + assert(anchor); /* Non-NULL anchor is expected. */ + + if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0; + + anchor_copy = yaml_strdup(anchor); + if (!anchor_copy) + return 0; + + ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark); + + return 1; +} + +/* + * Create SCALAR. + */ + +YAML_DECLARE(int) +yaml_scalar_event_initialize(yaml_event_t *event, + const yaml_char_t *anchor, const yaml_char_t *tag, + const yaml_char_t *value, int length, + int plain_implicit, int quoted_implicit, + yaml_scalar_style_t style) +{ + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *anchor_copy = NULL; + yaml_char_t *tag_copy = NULL; + yaml_char_t *value_copy = NULL; + + assert(event); /* Non-NULL event object is expected. */ + assert(value); /* Non-NULL anchor is expected. */ + + if (anchor) { + if (!yaml_check_utf8(anchor, strlen((char *)anchor))) goto error; + anchor_copy = yaml_strdup(anchor); + if (!anchor_copy) goto error; + } + + if (tag) { + if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; + tag_copy = yaml_strdup(tag); + if (!tag_copy) goto error; + } + + if (length < 0) { + length = strlen((char *)value); + } + + if (!yaml_check_utf8(value, length)) goto error; + value_copy = YAML_MALLOC(length+1); + if (!value_copy) goto error; + memcpy(value_copy, value, length); + value_copy[length] = '\0'; + + SCALAR_EVENT_INIT(*event, anchor_copy, tag_copy, value_copy, length, + plain_implicit, quoted_implicit, style, mark, mark); + + return 1; + +error: + yaml_free(anchor_copy); + yaml_free(tag_copy); + yaml_free(value_copy); + + return 0; +} + +/* + * Create SEQUENCE-START. + */ + +YAML_DECLARE(int) +yaml_sequence_start_event_initialize(yaml_event_t *event, + const yaml_char_t *anchor, const yaml_char_t *tag, int implicit, + yaml_sequence_style_t style) +{ + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *anchor_copy = NULL; + yaml_char_t *tag_copy = NULL; + + assert(event); /* Non-NULL event object is expected. */ + + if (anchor) { + if (!yaml_check_utf8(anchor, strlen((char *)anchor))) goto error; + anchor_copy = yaml_strdup(anchor); + if (!anchor_copy) goto error; + } + + if (tag) { + if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; + tag_copy = yaml_strdup(tag); + if (!tag_copy) goto error; + } + + SEQUENCE_START_EVENT_INIT(*event, anchor_copy, tag_copy, + implicit, style, mark, mark); + + return 1; + +error: + yaml_free(anchor_copy); + yaml_free(tag_copy); + + return 0; +} + +/* + * Create SEQUENCE-END. + */ + +YAML_DECLARE(int) +yaml_sequence_end_event_initialize(yaml_event_t *event) +{ + yaml_mark_t mark = { 0, 0, 0 }; + + assert(event); /* Non-NULL event object is expected. */ + + SEQUENCE_END_EVENT_INIT(*event, mark, mark); + + return 1; +} + +/* + * Create MAPPING-START. + */ + +YAML_DECLARE(int) +yaml_mapping_start_event_initialize(yaml_event_t *event, + const yaml_char_t *anchor, const yaml_char_t *tag, int implicit, + yaml_mapping_style_t style) +{ + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *anchor_copy = NULL; + yaml_char_t *tag_copy = NULL; + + assert(event); /* Non-NULL event object is expected. */ + + if (anchor) { + if (!yaml_check_utf8(anchor, strlen((char *)anchor))) goto error; + anchor_copy = yaml_strdup(anchor); + if (!anchor_copy) goto error; + } + + if (tag) { + if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; + tag_copy = yaml_strdup(tag); + if (!tag_copy) goto error; + } + + MAPPING_START_EVENT_INIT(*event, anchor_copy, tag_copy, + implicit, style, mark, mark); + + return 1; + +error: + yaml_free(anchor_copy); + yaml_free(tag_copy); + + return 0; +} + +/* + * Create MAPPING-END. + */ + +YAML_DECLARE(int) +yaml_mapping_end_event_initialize(yaml_event_t *event) +{ + yaml_mark_t mark = { 0, 0, 0 }; + + assert(event); /* Non-NULL event object is expected. */ + + MAPPING_END_EVENT_INIT(*event, mark, mark); + + return 1; +} + +/* + * Destroy an event object. + */ + +YAML_DECLARE(void) +yaml_event_delete(yaml_event_t *event) +{ + yaml_tag_directive_t *tag_directive; + + assert(event); /* Non-NULL event object expected. */ + + switch (event->type) + { + case YAML_DOCUMENT_START_EVENT: + yaml_free(event->data.document_start.version_directive); + for (tag_directive = event->data.document_start.tag_directives.start; + tag_directive != event->data.document_start.tag_directives.end; + tag_directive++) { + yaml_free(tag_directive->handle); + yaml_free(tag_directive->prefix); + } + yaml_free(event->data.document_start.tag_directives.start); + break; + + case YAML_ALIAS_EVENT: + yaml_free(event->data.alias.anchor); + break; + + case YAML_SCALAR_EVENT: + yaml_free(event->data.scalar.anchor); + yaml_free(event->data.scalar.tag); + yaml_free(event->data.scalar.value); + break; + + case YAML_SEQUENCE_START_EVENT: + yaml_free(event->data.sequence_start.anchor); + yaml_free(event->data.sequence_start.tag); + break; + + case YAML_MAPPING_START_EVENT: + yaml_free(event->data.mapping_start.anchor); + yaml_free(event->data.mapping_start.tag); + break; + + default: + break; + } + + memset(event, 0, sizeof(yaml_event_t)); +} + +/* + * Create a document object. + */ + +YAML_DECLARE(int) +yaml_document_initialize(yaml_document_t *document, + yaml_version_directive_t *version_directive, + yaml_tag_directive_t *tag_directives_start, + yaml_tag_directive_t *tag_directives_end, + int start_implicit, int end_implicit) +{ + struct { + yaml_error_type_t error; + } context; + struct { + yaml_node_t *start; + yaml_node_t *end; + yaml_node_t *top; + } nodes = { NULL, NULL, NULL }; + yaml_version_directive_t *version_directive_copy = NULL; + struct { + yaml_tag_directive_t *start; + yaml_tag_directive_t *end; + yaml_tag_directive_t *top; + } tag_directives_copy = { NULL, NULL, NULL }; + yaml_tag_directive_t value = { NULL, NULL }; + yaml_mark_t mark = { 0, 0, 0 }; + + assert(document); /* Non-NULL document object is expected. */ + assert((tag_directives_start && tag_directives_end) || + (tag_directives_start == tag_directives_end)); + /* Valid tag directives are expected. */ + + if (!STACK_INIT(&context, nodes, yaml_node_t*)) goto error; + + if (version_directive) { + version_directive_copy = YAML_MALLOC_STATIC(yaml_version_directive_t); + if (!version_directive_copy) goto error; + version_directive_copy->major = version_directive->major; + version_directive_copy->minor = version_directive->minor; + } + + if (tag_directives_start != tag_directives_end) { + yaml_tag_directive_t *tag_directive; + if (!STACK_INIT(&context, tag_directives_copy, yaml_tag_directive_t*)) + goto error; + for (tag_directive = tag_directives_start; + tag_directive != tag_directives_end; tag_directive ++) { + assert(tag_directive->handle); + assert(tag_directive->prefix); + if (!yaml_check_utf8(tag_directive->handle, + strlen((char *)tag_directive->handle))) + goto error; + if (!yaml_check_utf8(tag_directive->prefix, + strlen((char *)tag_directive->prefix))) + goto error; + value.handle = yaml_strdup(tag_directive->handle); + value.prefix = yaml_strdup(tag_directive->prefix); + if (!value.handle || !value.prefix) goto error; + if (!PUSH(&context, tag_directives_copy, value)) + goto error; + value.handle = NULL; + value.prefix = NULL; + } + } + + DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy, + tag_directives_copy.start, tag_directives_copy.top, + start_implicit, end_implicit, mark, mark); + + return 1; + +error: + STACK_DEL(&context, nodes); + yaml_free(version_directive_copy); + while (!STACK_EMPTY(&context, tag_directives_copy)) { + yaml_tag_directive_t value = POP(&context, tag_directives_copy); + yaml_free(value.handle); + yaml_free(value.prefix); + } + STACK_DEL(&context, tag_directives_copy); + yaml_free(value.handle); + yaml_free(value.prefix); + + return 0; +} + +/* + * Destroy a document object. + */ + +YAML_DECLARE(void) +yaml_document_delete(yaml_document_t *document) +{ + yaml_tag_directive_t *tag_directive; + + assert(document); /* Non-NULL document object is expected. */ + + while (!STACK_EMPTY(&context, document->nodes)) { + yaml_node_t node = POP(&context, document->nodes); + yaml_free(node.tag); + switch (node.type) { + case YAML_SCALAR_NODE: + yaml_free(node.data.scalar.value); + break; + case YAML_SEQUENCE_NODE: + STACK_DEL(&context, node.data.sequence.items); + break; + case YAML_MAPPING_NODE: + STACK_DEL(&context, node.data.mapping.pairs); + break; + default: + assert(0); /* Should not happen. */ + } + } + STACK_DEL(&context, document->nodes); + + yaml_free(document->version_directive); + for (tag_directive = document->tag_directives.start; + tag_directive != document->tag_directives.end; + tag_directive++) { + yaml_free(tag_directive->handle); + yaml_free(tag_directive->prefix); + } + yaml_free(document->tag_directives.start); + + memset(document, 0, sizeof(yaml_document_t)); +} + +/** + * Get a document node. + */ + +YAML_DECLARE(yaml_node_t *) +yaml_document_get_node(yaml_document_t *document, int index) +{ + assert(document); /* Non-NULL document object is expected. */ + + if (index > 0 && document->nodes.start + index <= document->nodes.top) { + return document->nodes.start + index - 1; + } + return NULL; +} + +/** + * Get the root object. + */ + +YAML_DECLARE(yaml_node_t *) +yaml_document_get_root_node(yaml_document_t *document) +{ + assert(document); /* Non-NULL document object is expected. */ + + if (document->nodes.top != document->nodes.start) { + return document->nodes.start; + } + return NULL; +} + +/* + * Add a scalar node to a document. + */ + +YAML_DECLARE(int) +yaml_document_add_scalar(yaml_document_t *document, + const yaml_char_t *tag, const yaml_char_t *value, int length, + yaml_scalar_style_t style) +{ + struct { + yaml_error_type_t error; + } context; + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *tag_copy = NULL; + yaml_char_t *value_copy = NULL; + yaml_node_t node; + + assert(document); /* Non-NULL document object is expected. */ + assert(value); /* Non-NULL value is expected. */ + + if (!tag) { + tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG; + } + + if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; + tag_copy = yaml_strdup(tag); + if (!tag_copy) goto error; + + if (length < 0) { + length = strlen((char *)value); + } + + if (!yaml_check_utf8(value, length)) goto error; + value_copy = YAML_MALLOC(length+1); + if (!value_copy) goto error; + memcpy(value_copy, value, length); + value_copy[length] = '\0'; + + SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark); + if (!PUSH(&context, document->nodes, node)) goto error; + + return document->nodes.top - document->nodes.start; + +error: + yaml_free(tag_copy); + yaml_free(value_copy); + + return 0; +} + +/* + * Add a sequence node to a document. + */ + +YAML_DECLARE(int) +yaml_document_add_sequence(yaml_document_t *document, + const yaml_char_t *tag, yaml_sequence_style_t style) +{ + struct { + yaml_error_type_t error; + } context; + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *tag_copy = NULL; + struct { + yaml_node_item_t *start; + yaml_node_item_t *end; + yaml_node_item_t *top; + } items = { NULL, NULL, NULL }; + yaml_node_t node; + + assert(document); /* Non-NULL document object is expected. */ + + if (!tag) { + tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG; + } + + if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; + tag_copy = yaml_strdup(tag); + if (!tag_copy) goto error; + + if (!STACK_INIT(&context, items, yaml_node_item_t*)) goto error; + + SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end, + style, mark, mark); + if (!PUSH(&context, document->nodes, node)) goto error; + + return document->nodes.top - document->nodes.start; + +error: + STACK_DEL(&context, items); + yaml_free(tag_copy); + + return 0; +} + +/* + * Add a mapping node to a document. + */ + +YAML_DECLARE(int) +yaml_document_add_mapping(yaml_document_t *document, + const yaml_char_t *tag, yaml_mapping_style_t style) +{ + struct { + yaml_error_type_t error; + } context; + yaml_mark_t mark = { 0, 0, 0 }; + yaml_char_t *tag_copy = NULL; + struct { + yaml_node_pair_t *start; + yaml_node_pair_t *end; + yaml_node_pair_t *top; + } pairs = { NULL, NULL, NULL }; + yaml_node_t node; + + assert(document); /* Non-NULL document object is expected. */ + + if (!tag) { + tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG; + } + + if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error; + tag_copy = yaml_strdup(tag); + if (!tag_copy) goto error; + + if (!STACK_INIT(&context, pairs, yaml_node_pair_t*)) goto error; + + MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end, + style, mark, mark); + if (!PUSH(&context, document->nodes, node)) goto error; + + return document->nodes.top - document->nodes.start; + +error: + STACK_DEL(&context, pairs); + yaml_free(tag_copy); + + return 0; +} + +/* + * Append an item to a sequence node. + */ + +YAML_DECLARE(int) +yaml_document_append_sequence_item(yaml_document_t *document, + int sequence, int item) +{ + struct { + yaml_error_type_t error; + } context; + + assert(document); /* Non-NULL document is required. */ + assert(sequence > 0 + && document->nodes.start + sequence <= document->nodes.top); + /* Valid sequence id is required. */ + assert(document->nodes.start[sequence-1].type == YAML_SEQUENCE_NODE); + /* A sequence node is required. */ + assert(item > 0 && document->nodes.start + item <= document->nodes.top); + /* Valid item id is required. */ + + if (!PUSH(&context, + document->nodes.start[sequence-1].data.sequence.items, item)) + return 0; + + return 1; +} + +/* + * Append a pair of a key and a value to a mapping node. + */ + +YAML_DECLARE(int) +yaml_document_append_mapping_pair(yaml_document_t *document, + int mapping, int key, int value) +{ + struct { + yaml_error_type_t error; + } context; + + yaml_node_pair_t pair; + + assert(document); /* Non-NULL document is required. */ + assert(mapping > 0 + && document->nodes.start + mapping <= document->nodes.top); + /* Valid mapping id is required. */ + assert(document->nodes.start[mapping-1].type == YAML_MAPPING_NODE); + /* A mapping node is required. */ + assert(key > 0 && document->nodes.start + key <= document->nodes.top); + /* Valid key id is required. */ + assert(value > 0 && document->nodes.start + value <= document->nodes.top); + /* Valid value id is required. */ + + pair.key = key; + pair.value = value; + + if (!PUSH(&context, + document->nodes.start[mapping-1].data.mapping.pairs, pair)) + return 0; + + return 1; +} + + diff --git a/src/vendors/libyaml/dumper.c b/src/vendors/libyaml/dumper.c new file mode 100644 index 0000000..a6a9158 --- /dev/null +++ b/src/vendors/libyaml/dumper.c @@ -0,0 +1,394 @@ + +#include "yaml_private.h" + +/* + * API functions. + */ + +YAML_DECLARE(int) +yaml_emitter_open(yaml_emitter_t *emitter); + +YAML_DECLARE(int) +yaml_emitter_close(yaml_emitter_t *emitter); + +YAML_DECLARE(int) +yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document); + +/* + * Clean up functions. + */ + +static void +yaml_emitter_delete_document_and_anchors(yaml_emitter_t *emitter); + +/* + * Anchor functions. + */ + +static void +yaml_emitter_anchor_node(yaml_emitter_t *emitter, int index); + +static yaml_char_t * +yaml_emitter_generate_anchor(yaml_emitter_t *emitter, int anchor_id); + + +/* + * Serialize functions. + */ + +static int +yaml_emitter_dump_node(yaml_emitter_t *emitter, int index); + +static int +yaml_emitter_dump_alias(yaml_emitter_t *emitter, yaml_char_t *anchor); + +static int +yaml_emitter_dump_scalar(yaml_emitter_t *emitter, yaml_node_t *node, + yaml_char_t *anchor); + +static int +yaml_emitter_dump_sequence(yaml_emitter_t *emitter, yaml_node_t *node, + yaml_char_t *anchor); + +static int +yaml_emitter_dump_mapping(yaml_emitter_t *emitter, yaml_node_t *node, + yaml_char_t *anchor); + +/* + * Issue a STREAM-START event. + */ + +YAML_DECLARE(int) +yaml_emitter_open(yaml_emitter_t *emitter) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + assert(emitter); /* Non-NULL emitter object is required. */ + assert(!emitter->opened); /* Emitter should not be opened yet. */ + + STREAM_START_EVENT_INIT(event, YAML_ANY_ENCODING, mark, mark); + + if (!yaml_emitter_emit(emitter, &event)) { + return 0; + } + + emitter->opened = 1; + + return 1; +} + +/* + * Issue a STREAM-END event. + */ + +YAML_DECLARE(int) +yaml_emitter_close(yaml_emitter_t *emitter) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + assert(emitter); /* Non-NULL emitter object is required. */ + assert(emitter->opened); /* Emitter should be opened. */ + + if (emitter->closed) return 1; + + STREAM_END_EVENT_INIT(event, mark, mark); + + if (!yaml_emitter_emit(emitter, &event)) { + return 0; + } + + emitter->closed = 1; + + return 1; +} + +/* + * Dump a YAML document. + */ + +YAML_DECLARE(int) +yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + assert(emitter); /* Non-NULL emitter object is required. */ + assert(document); /* Non-NULL emitter object is expected. */ + + emitter->document = document; + + if (!emitter->opened) { + if (!yaml_emitter_open(emitter)) goto error; + } + + if (STACK_EMPTY(emitter, document->nodes)) { + if (!yaml_emitter_close(emitter)) goto error; + yaml_emitter_delete_document_and_anchors(emitter); + return 1; + } + + assert(emitter->opened); /* Emitter should be opened. */ + + emitter->anchors = (yaml_anchors_t*)yaml_malloc(sizeof(*(emitter->anchors)) + * (document->nodes.top - document->nodes.start)); + if (!emitter->anchors) goto error; + memset(emitter->anchors, 0, sizeof(*(emitter->anchors)) + * (document->nodes.top - document->nodes.start)); + + DOCUMENT_START_EVENT_INIT(event, document->version_directive, + document->tag_directives.start, document->tag_directives.end, + document->start_implicit, mark, mark); + if (!yaml_emitter_emit(emitter, &event)) goto error; + + yaml_emitter_anchor_node(emitter, 1); + if (!yaml_emitter_dump_node(emitter, 1)) goto error; + + DOCUMENT_END_EVENT_INIT(event, document->end_implicit, mark, mark); + if (!yaml_emitter_emit(emitter, &event)) goto error; + + yaml_emitter_delete_document_and_anchors(emitter); + + return 1; + +error: + + yaml_emitter_delete_document_and_anchors(emitter); + + return 0; +} + +/* + * Clean up the emitter object after a document is dumped. + */ + +static void +yaml_emitter_delete_document_and_anchors(yaml_emitter_t *emitter) +{ + int index; + + if (!emitter->anchors) { + yaml_document_delete(emitter->document); + emitter->document = NULL; + return; + } + + for (index = 0; emitter->document->nodes.start + index + < emitter->document->nodes.top; index ++) { + yaml_node_t node = emitter->document->nodes.start[index]; + if (!emitter->anchors[index].serialized) { + yaml_free(node.tag); + if (node.type == YAML_SCALAR_NODE) { + yaml_free(node.data.scalar.value); + } + } + if (node.type == YAML_SEQUENCE_NODE) { + STACK_DEL(emitter, node.data.sequence.items); + } + if (node.type == YAML_MAPPING_NODE) { + STACK_DEL(emitter, node.data.mapping.pairs); + } + } + + STACK_DEL(emitter, emitter->document->nodes); + yaml_free(emitter->anchors); + + emitter->anchors = NULL; + emitter->last_anchor_id = 0; + emitter->document = NULL; +} + +/* + * Check the references of a node and assign the anchor id if needed. + */ + +static void +yaml_emitter_anchor_node(yaml_emitter_t *emitter, int index) +{ + yaml_node_t *node = emitter->document->nodes.start + index - 1; + yaml_node_item_t *item; + yaml_node_pair_t *pair; + + emitter->anchors[index-1].references ++; + + if (emitter->anchors[index-1].references == 1) { + switch (node->type) { + case YAML_SEQUENCE_NODE: + for (item = node->data.sequence.items.start; + item < node->data.sequence.items.top; item ++) { + yaml_emitter_anchor_node(emitter, *item); + } + break; + case YAML_MAPPING_NODE: + for (pair = node->data.mapping.pairs.start; + pair < node->data.mapping.pairs.top; pair ++) { + yaml_emitter_anchor_node(emitter, pair->key); + yaml_emitter_anchor_node(emitter, pair->value); + } + break; + default: + break; + } + } + + else if (emitter->anchors[index-1].references == 2) { + emitter->anchors[index-1].anchor = (++ emitter->last_anchor_id); + } +} + +/* + * Generate a textual representation for an anchor. + */ + +#define ANCHOR_TEMPLATE "id%03d" +#define ANCHOR_TEMPLATE_LENGTH 16 + +static yaml_char_t * +yaml_emitter_generate_anchor(SHIM(yaml_emitter_t *emitter), int anchor_id) +{ + yaml_char_t *anchor = YAML_MALLOC(ANCHOR_TEMPLATE_LENGTH); + + if (!anchor) return NULL; + + sprintf((char *)anchor, ANCHOR_TEMPLATE, anchor_id); + + return anchor; +} + +/* + * Serialize a node. + */ + +static int +yaml_emitter_dump_node(yaml_emitter_t *emitter, int index) +{ + yaml_node_t *node = emitter->document->nodes.start + index - 1; + int anchor_id = emitter->anchors[index-1].anchor; + yaml_char_t *anchor = NULL; + + if (anchor_id) { + anchor = yaml_emitter_generate_anchor(emitter, anchor_id); + if (!anchor) return 0; + } + + if (emitter->anchors[index-1].serialized) { + return yaml_emitter_dump_alias(emitter, anchor); + } + + emitter->anchors[index-1].serialized = 1; + + switch (node->type) { + case YAML_SCALAR_NODE: + return yaml_emitter_dump_scalar(emitter, node, anchor); + case YAML_SEQUENCE_NODE: + return yaml_emitter_dump_sequence(emitter, node, anchor); + case YAML_MAPPING_NODE: + return yaml_emitter_dump_mapping(emitter, node, anchor); + default: + assert(0); /* Could not happen. */ + break; + } + + return 0; /* Could not happen. */ +} + +/* + * Serialize an alias. + */ + +static int +yaml_emitter_dump_alias(yaml_emitter_t *emitter, yaml_char_t *anchor) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + ALIAS_EVENT_INIT(event, anchor, mark, mark); + + return yaml_emitter_emit(emitter, &event); +} + +/* + * Serialize a scalar. + */ + +static int +yaml_emitter_dump_scalar(yaml_emitter_t *emitter, yaml_node_t *node, + yaml_char_t *anchor) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + int plain_implicit = (strcmp((char *)node->tag, + YAML_DEFAULT_SCALAR_TAG) == 0); + int quoted_implicit = (strcmp((char *)node->tag, + YAML_DEFAULT_SCALAR_TAG) == 0); + + SCALAR_EVENT_INIT(event, anchor, node->tag, node->data.scalar.value, + node->data.scalar.length, plain_implicit, quoted_implicit, + node->data.scalar.style, mark, mark); + + return yaml_emitter_emit(emitter, &event); +} + +/* + * Serialize a sequence. + */ + +static int +yaml_emitter_dump_sequence(yaml_emitter_t *emitter, yaml_node_t *node, + yaml_char_t *anchor) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + int implicit = (strcmp((char *)node->tag, YAML_DEFAULT_SEQUENCE_TAG) == 0); + + yaml_node_item_t *item; + + SEQUENCE_START_EVENT_INIT(event, anchor, node->tag, implicit, + node->data.sequence.style, mark, mark); + if (!yaml_emitter_emit(emitter, &event)) return 0; + + for (item = node->data.sequence.items.start; + item < node->data.sequence.items.top; item ++) { + if (!yaml_emitter_dump_node(emitter, *item)) return 0; + } + + SEQUENCE_END_EVENT_INIT(event, mark, mark); + if (!yaml_emitter_emit(emitter, &event)) return 0; + + return 1; +} + +/* + * Serialize a mapping. + */ + +static int +yaml_emitter_dump_mapping(yaml_emitter_t *emitter, yaml_node_t *node, + yaml_char_t *anchor) +{ + yaml_event_t event; + yaml_mark_t mark = { 0, 0, 0 }; + + int implicit = (strcmp((char *)node->tag, YAML_DEFAULT_MAPPING_TAG) == 0); + + yaml_node_pair_t *pair; + + MAPPING_START_EVENT_INIT(event, anchor, node->tag, implicit, + node->data.mapping.style, mark, mark); + if (!yaml_emitter_emit(emitter, &event)) return 0; + + for (pair = node->data.mapping.pairs.start; + pair < node->data.mapping.pairs.top; pair ++) { + if (!yaml_emitter_dump_node(emitter, pair->key)) return 0; + if (!yaml_emitter_dump_node(emitter, pair->value)) return 0; + } + + MAPPING_END_EVENT_INIT(event, mark, mark); + if (!yaml_emitter_emit(emitter, &event)) return 0; + + return 1; +} + diff --git a/src/vendors/libyaml/emitter.c b/src/vendors/libyaml/emitter.c new file mode 100644 index 0000000..c39eedf --- /dev/null +++ b/src/vendors/libyaml/emitter.c @@ -0,0 +1,2358 @@ + +#include "yaml_private.h" + +/* + * Flush the buffer if needed. + */ + +#define FLUSH(emitter) \ + ((emitter->buffer.pointer+5 < emitter->buffer.end) \ + || yaml_emitter_flush(emitter)) + +/* + * Put a character to the output buffer. + */ + +#define PUT(emitter,value) \ + (FLUSH(emitter) \ + && (*(emitter->buffer.pointer++) = (yaml_char_t)(value), \ + emitter->column++, \ + 1)) + +/* + * Put a line break to the output buffer. + */ + +#define PUT_BREAK(emitter) \ + (FLUSH(emitter) \ + && ((emitter->line_break == YAML_CR_BREAK ? \ + (*(emitter->buffer.pointer++) = (yaml_char_t) '\r') : \ + emitter->line_break == YAML_LN_BREAK ? \ + (*(emitter->buffer.pointer++) = (yaml_char_t) '\n') : \ + emitter->line_break == YAML_CRLN_BREAK ? \ + (*(emitter->buffer.pointer++) = (yaml_char_t) '\r', \ + *(emitter->buffer.pointer++) = (yaml_char_t) '\n') : 0), \ + emitter->column = 0, \ + emitter->line ++, \ + 1)) + +/* + * Copy a character from a string into buffer. + */ + +#define WRITE(emitter,string) \ + (FLUSH(emitter) \ + && (COPY(emitter->buffer,string), \ + emitter->column ++, \ + 1)) + +/* + * Copy a line break character from a string into buffer. + */ + +#define WRITE_BREAK(emitter,string) \ + (FLUSH(emitter) \ + && (CHECK(string,'\n') ? \ + (PUT_BREAK(emitter), \ + string.pointer ++, \ + 1) : \ + (COPY(emitter->buffer,string), \ + emitter->column = 0, \ + emitter->line ++, \ + 1))) + +/* + * API functions. + */ + +YAML_DECLARE(int) +yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event); + +/* + * Utility functions. + */ + +static int +yaml_emitter_set_emitter_error(yaml_emitter_t *emitter, const char *problem); + +static int +yaml_emitter_need_more_events(yaml_emitter_t *emitter); + +static int +yaml_emitter_append_tag_directive(yaml_emitter_t *emitter, + yaml_tag_directive_t value, int allow_duplicates); + +static int +yaml_emitter_increase_indent(yaml_emitter_t *emitter, + int flow, int indentless); + +/* + * State functions. + */ + +static int +yaml_emitter_state_machine(yaml_emitter_t *emitter, yaml_event_t *event); + +static int +yaml_emitter_emit_stream_start(yaml_emitter_t *emitter, + yaml_event_t *event); + +static int +yaml_emitter_emit_document_start(yaml_emitter_t *emitter, + yaml_event_t *event, int first); + +static int +yaml_emitter_emit_document_content(yaml_emitter_t *emitter, + yaml_event_t *event); + +static int +yaml_emitter_emit_document_end(yaml_emitter_t *emitter, + yaml_event_t *event); + +static int +yaml_emitter_emit_flow_sequence_item(yaml_emitter_t *emitter, + yaml_event_t *event, int first); + +static int +yaml_emitter_emit_flow_mapping_key(yaml_emitter_t *emitter, + yaml_event_t *event, int first); + +static int +yaml_emitter_emit_flow_mapping_value(yaml_emitter_t *emitter, + yaml_event_t *event, int simple); + +static int +yaml_emitter_emit_block_sequence_item(yaml_emitter_t *emitter, + yaml_event_t *event, int first); + +static int +yaml_emitter_emit_block_mapping_key(yaml_emitter_t *emitter, + yaml_event_t *event, int first); + +static int +yaml_emitter_emit_block_mapping_value(yaml_emitter_t *emitter, + yaml_event_t *event, int simple); + +static int +yaml_emitter_emit_node(yaml_emitter_t *emitter, yaml_event_t *event, + int root, int sequence, int mapping, int simple_key); + +static int +yaml_emitter_emit_alias(yaml_emitter_t *emitter, yaml_event_t *event); + +static int +yaml_emitter_emit_scalar(yaml_emitter_t *emitter, yaml_event_t *event); + +static int +yaml_emitter_emit_sequence_start(yaml_emitter_t *emitter, yaml_event_t *event); + +static int +yaml_emitter_emit_mapping_start(yaml_emitter_t *emitter, yaml_event_t *event); + +/* + * Checkers. + */ + +static int +yaml_emitter_check_empty_document(yaml_emitter_t *emitter); + +static int +yaml_emitter_check_empty_sequence(yaml_emitter_t *emitter); + +static int +yaml_emitter_check_empty_mapping(yaml_emitter_t *emitter); + +static int +yaml_emitter_check_simple_key(yaml_emitter_t *emitter); + +static int +yaml_emitter_select_scalar_style(yaml_emitter_t *emitter, yaml_event_t *event); + +/* + * Processors. + */ + +static int +yaml_emitter_process_anchor(yaml_emitter_t *emitter); + +static int +yaml_emitter_process_tag(yaml_emitter_t *emitter); + +static int +yaml_emitter_process_scalar(yaml_emitter_t *emitter); + +/* + * Analyzers. + */ + +static int +yaml_emitter_analyze_version_directive(yaml_emitter_t *emitter, + yaml_version_directive_t version_directive); + +static int +yaml_emitter_analyze_tag_directive(yaml_emitter_t *emitter, + yaml_tag_directive_t tag_directive); + +static int +yaml_emitter_analyze_anchor(yaml_emitter_t *emitter, + yaml_char_t *anchor, int alias); + +static int +yaml_emitter_analyze_tag(yaml_emitter_t *emitter, + yaml_char_t *tag); + +static int +yaml_emitter_analyze_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length); + +static int +yaml_emitter_analyze_event(yaml_emitter_t *emitter, + yaml_event_t *event); + +/* + * Writers. + */ + +static int +yaml_emitter_write_bom(yaml_emitter_t *emitter); + +static int +yaml_emitter_write_indent(yaml_emitter_t *emitter); + +static int +yaml_emitter_write_indicator(yaml_emitter_t *emitter, + const char *indicator, int need_whitespace, + int is_whitespace, int is_indention); + +static int +yaml_emitter_write_anchor(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length); + +static int +yaml_emitter_write_tag_handle(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length); + +static int +yaml_emitter_write_tag_content(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int need_whitespace); + +static int +yaml_emitter_write_plain_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int allow_breaks); + +static int +yaml_emitter_write_single_quoted_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int allow_breaks); + +static int +yaml_emitter_write_double_quoted_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int allow_breaks); + +static int +yaml_emitter_write_block_scalar_hints(yaml_emitter_t *emitter, + yaml_string_t string); + +static int +yaml_emitter_write_literal_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length); + +static int +yaml_emitter_write_folded_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length); + +/* + * Set an emitter error and return 0. + */ + +static int +yaml_emitter_set_emitter_error(yaml_emitter_t *emitter, const char *problem) +{ + emitter->error = YAML_EMITTER_ERROR; + emitter->problem = problem; + + return 0; +} + +/* + * Emit an event. + */ + +YAML_DECLARE(int) +yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event) +{ + if (!ENQUEUE(emitter, emitter->events, *event)) { + yaml_event_delete(event); + return 0; + } + + while (!yaml_emitter_need_more_events(emitter)) { + if (!yaml_emitter_analyze_event(emitter, emitter->events.head)) + return 0; + if (!yaml_emitter_state_machine(emitter, emitter->events.head)) + return 0; + yaml_event_delete(&DEQUEUE(emitter, emitter->events)); + } + + return 1; +} + +/* + * Check if we need to accumulate more events before emitting. + * + * We accumulate extra + * - 1 event for DOCUMENT-START + * - 2 events for SEQUENCE-START + * - 3 events for MAPPING-START + */ + +static int +yaml_emitter_need_more_events(yaml_emitter_t *emitter) +{ + int level = 0; + int accumulate = 0; + yaml_event_t *event; + + if (QUEUE_EMPTY(emitter, emitter->events)) + return 1; + + switch (emitter->events.head->type) { + case YAML_DOCUMENT_START_EVENT: + accumulate = 1; + break; + case YAML_SEQUENCE_START_EVENT: + accumulate = 2; + break; + case YAML_MAPPING_START_EVENT: + accumulate = 3; + break; + default: + return 0; + } + + if (emitter->events.tail - emitter->events.head > accumulate) + return 0; + + for (event = emitter->events.head; event != emitter->events.tail; event ++) { + switch (event->type) { + case YAML_STREAM_START_EVENT: + case YAML_DOCUMENT_START_EVENT: + case YAML_SEQUENCE_START_EVENT: + case YAML_MAPPING_START_EVENT: + level += 1; + break; + case YAML_STREAM_END_EVENT: + case YAML_DOCUMENT_END_EVENT: + case YAML_SEQUENCE_END_EVENT: + case YAML_MAPPING_END_EVENT: + level -= 1; + break; + default: + break; + } + if (!level) + return 0; + } + + return 1; +} + +/* + * Append a directive to the directives stack. + */ + +static int +yaml_emitter_append_tag_directive(yaml_emitter_t *emitter, + yaml_tag_directive_t value, int allow_duplicates) +{ + yaml_tag_directive_t *tag_directive; + yaml_tag_directive_t copy = { NULL, NULL }; + + for (tag_directive = emitter->tag_directives.start; + tag_directive != emitter->tag_directives.top; tag_directive ++) { + if (strcmp((char *)value.handle, (char *)tag_directive->handle) == 0) { + if (allow_duplicates) + return 1; + return yaml_emitter_set_emitter_error(emitter, + "duplicate %TAG directive"); + } + } + + copy.handle = yaml_strdup(value.handle); + copy.prefix = yaml_strdup(value.prefix); + if (!copy.handle || !copy.prefix) { + emitter->error = YAML_MEMORY_ERROR; + goto error; + } + + if (!PUSH(emitter, emitter->tag_directives, copy)) + goto error; + + return 1; + +error: + yaml_free(copy.handle); + yaml_free(copy.prefix); + return 0; +} + +/* + * Increase the indentation level. + */ + +static int +yaml_emitter_increase_indent(yaml_emitter_t *emitter, + int flow, int indentless) +{ + if (!PUSH(emitter, emitter->indents, emitter->indent)) + return 0; + + if (emitter->indent < 0) { + emitter->indent = flow ? emitter->best_indent : 0; + } + else if (!indentless) { + emitter->indent += emitter->best_indent; + } + + return 1; +} + +/* + * State dispatcher. + */ + +static int +yaml_emitter_state_machine(yaml_emitter_t *emitter, yaml_event_t *event) +{ + switch (emitter->state) + { + case YAML_EMIT_STREAM_START_STATE: + return yaml_emitter_emit_stream_start(emitter, event); + + case YAML_EMIT_FIRST_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, 1); + + case YAML_EMIT_DOCUMENT_START_STATE: + return yaml_emitter_emit_document_start(emitter, event, 0); + + case YAML_EMIT_DOCUMENT_CONTENT_STATE: + return yaml_emitter_emit_document_content(emitter, event); + + case YAML_EMIT_DOCUMENT_END_STATE: + return yaml_emitter_emit_document_end(emitter, event); + + case YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, 1); + + case YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_flow_sequence_item(emitter, event, 0); + + case YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, 1); + + case YAML_EMIT_FLOW_MAPPING_KEY_STATE: + return yaml_emitter_emit_flow_mapping_key(emitter, event, 0); + + case YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, 1); + + case YAML_EMIT_FLOW_MAPPING_VALUE_STATE: + return yaml_emitter_emit_flow_mapping_value(emitter, event, 0); + + case YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, 1); + + case YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE: + return yaml_emitter_emit_block_sequence_item(emitter, event, 0); + + case YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, 1); + + case YAML_EMIT_BLOCK_MAPPING_KEY_STATE: + return yaml_emitter_emit_block_mapping_key(emitter, event, 0); + + case YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, 1); + + case YAML_EMIT_BLOCK_MAPPING_VALUE_STATE: + return yaml_emitter_emit_block_mapping_value(emitter, event, 0); + + case YAML_EMIT_END_STATE: + return yaml_emitter_set_emitter_error(emitter, + "expected nothing after STREAM-END"); + + default: + assert(1); /* Invalid state. */ + } + + return 0; +} + +/* + * Expect STREAM-START. + */ + +static int +yaml_emitter_emit_stream_start(yaml_emitter_t *emitter, + yaml_event_t *event) +{ + emitter->open_ended = 0; + if (event->type == YAML_STREAM_START_EVENT) + { + if (!emitter->encoding) { + emitter->encoding = event->data.stream_start.encoding; + } + + if (!emitter->encoding) { + emitter->encoding = YAML_UTF8_ENCODING; + } + + if (emitter->best_indent < 2 || emitter->best_indent > 9) { + emitter->best_indent = 2; + } + + if (emitter->best_width >= 0 + && emitter->best_width <= emitter->best_indent*2) { + emitter->best_width = 80; + } + + if (emitter->best_width < 0) { + emitter->best_width = INT_MAX; + } + + if (!emitter->line_break) { + emitter->line_break = YAML_LN_BREAK; + } + + emitter->indent = -1; + + emitter->line = 0; + emitter->column = 0; + emitter->whitespace = 1; + emitter->indention = 1; + + if (emitter->encoding != YAML_UTF8_ENCODING) { + if (!yaml_emitter_write_bom(emitter)) + return 0; + } + + emitter->state = YAML_EMIT_FIRST_DOCUMENT_START_STATE; + + return 1; + } + + return yaml_emitter_set_emitter_error(emitter, + "expected STREAM-START"); +} + +/* + * Expect DOCUMENT-START or STREAM-END. + */ + +static int +yaml_emitter_emit_document_start(yaml_emitter_t *emitter, + yaml_event_t *event, int first) +{ + if (event->type == YAML_DOCUMENT_START_EVENT) + { + yaml_tag_directive_t default_tag_directives[] = { + {(yaml_char_t *)"!", (yaml_char_t *)"!"}, + {(yaml_char_t *)"!!", (yaml_char_t *)"tag:yaml.org,2002:"}, + {NULL, NULL} + }; + yaml_tag_directive_t *tag_directive; + int implicit; + + if (event->data.document_start.version_directive) { + if (!yaml_emitter_analyze_version_directive(emitter, + *event->data.document_start.version_directive)) + return 0; + } + + for (tag_directive = event->data.document_start.tag_directives.start; + tag_directive != event->data.document_start.tag_directives.end; + tag_directive ++) { + if (!yaml_emitter_analyze_tag_directive(emitter, *tag_directive)) + return 0; + if (!yaml_emitter_append_tag_directive(emitter, *tag_directive, 0)) + return 0; + } + + for (tag_directive = default_tag_directives; + tag_directive->handle; tag_directive ++) { + if (!yaml_emitter_append_tag_directive(emitter, *tag_directive, 1)) + return 0; + } + + implicit = event->data.document_start.implicit; + if (!first || emitter->canonical) { + implicit = 0; + } + + if ((event->data.document_start.version_directive || + (event->data.document_start.tag_directives.start + != event->data.document_start.tag_directives.end)) && + emitter->open_ended) + { + if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) + return 0; + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + emitter->open_ended = 0; + + if (event->data.document_start.version_directive) { + implicit = 0; + if (!yaml_emitter_write_indicator(emitter, "%YAML", 1, 0, 0)) + return 0; + if (event->data.document_start.version_directive->minor == 1) { + if (!yaml_emitter_write_indicator(emitter, "1.1", 1, 0, 0)) + return 0; + } + else { + if (!yaml_emitter_write_indicator(emitter, "1.2", 1, 0, 0)) + return 0; + } + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + + if (event->data.document_start.tag_directives.start + != event->data.document_start.tag_directives.end) { + implicit = 0; + for (tag_directive = event->data.document_start.tag_directives.start; + tag_directive != event->data.document_start.tag_directives.end; + tag_directive ++) { + if (!yaml_emitter_write_indicator(emitter, "%TAG", 1, 0, 0)) + return 0; + if (!yaml_emitter_write_tag_handle(emitter, tag_directive->handle, + strlen((char *)tag_directive->handle))) + return 0; + if (!yaml_emitter_write_tag_content(emitter, tag_directive->prefix, + strlen((char *)tag_directive->prefix), 1)) + return 0; + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + } + + if (yaml_emitter_check_empty_document(emitter)) { + implicit = 0; + } + + if (!implicit) { + if (!yaml_emitter_write_indent(emitter)) + return 0; + if (!yaml_emitter_write_indicator(emitter, "---", 1, 0, 0)) + return 0; + if (emitter->canonical) { + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + } + + emitter->state = YAML_EMIT_DOCUMENT_CONTENT_STATE; + + emitter->open_ended = 0; + return 1; + } + + else if (event->type == YAML_STREAM_END_EVENT) + { + + /** + * This can happen if a block scalar with trailing empty lines + * is at the end of the stream + */ + if (emitter->open_ended == 2) + { + if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) + return 0; + emitter->open_ended = 0; + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + if (!yaml_emitter_flush(emitter)) + return 0; + + emitter->state = YAML_EMIT_END_STATE; + + return 1; + } + + return yaml_emitter_set_emitter_error(emitter, + "expected DOCUMENT-START or STREAM-END"); +} + +/* + * Expect the root node. + */ + +static int +yaml_emitter_emit_document_content(yaml_emitter_t *emitter, + yaml_event_t *event) +{ + if (!PUSH(emitter, emitter->states, YAML_EMIT_DOCUMENT_END_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 1, 0, 0, 0); +} + +/* + * Expect DOCUMENT-END. + */ + +static int +yaml_emitter_emit_document_end(yaml_emitter_t *emitter, + yaml_event_t *event) +{ + if (event->type == YAML_DOCUMENT_END_EVENT) + { + if (!yaml_emitter_write_indent(emitter)) + return 0; + if (!event->data.document_end.implicit) { + if (!yaml_emitter_write_indicator(emitter, "...", 1, 0, 0)) + return 0; + emitter->open_ended = 0; + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + else if (!emitter->open_ended) + emitter->open_ended = 1; + if (!yaml_emitter_flush(emitter)) + return 0; + + emitter->state = YAML_EMIT_DOCUMENT_START_STATE; + + while (!STACK_EMPTY(emitter, emitter->tag_directives)) { + yaml_tag_directive_t tag_directive = POP(emitter, + emitter->tag_directives); + yaml_free(tag_directive.handle); + yaml_free(tag_directive.prefix); + } + + return 1; + } + + return yaml_emitter_set_emitter_error(emitter, + "expected DOCUMENT-END"); +} + +/* + * + * Expect a flow item node. + */ + +static int +yaml_emitter_emit_flow_sequence_item(yaml_emitter_t *emitter, + yaml_event_t *event, int first) +{ + if (first) + { + if (!yaml_emitter_write_indicator(emitter, "[", 1, 1, 0)) + return 0; + if (!yaml_emitter_increase_indent(emitter, 1, 0)) + return 0; + emitter->flow_level ++; + } + + if (event->type == YAML_SEQUENCE_END_EVENT) + { + emitter->flow_level --; + emitter->indent = POP(emitter, emitter->indents); + if (emitter->canonical && !first) { + if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) + return 0; + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + if (!yaml_emitter_write_indicator(emitter, "]", 0, 0, 0)) + return 0; + emitter->state = POP(emitter, emitter->states); + + return 1; + } + + if (!first) { + if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) + return 0; + } + + if (emitter->canonical || emitter->column > emitter->best_width) { + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 1, 0, 0); +} + +/* + * Expect a flow key node. + */ + +static int +yaml_emitter_emit_flow_mapping_key(yaml_emitter_t *emitter, + yaml_event_t *event, int first) +{ + if (first) + { + if (!yaml_emitter_write_indicator(emitter, "{", 1, 1, 0)) + return 0; + if (!yaml_emitter_increase_indent(emitter, 1, 0)) + return 0; + emitter->flow_level ++; + } + + if (event->type == YAML_MAPPING_END_EVENT) + { + emitter->flow_level --; + emitter->indent = POP(emitter, emitter->indents); + if (emitter->canonical && !first) { + if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) + return 0; + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + if (!yaml_emitter_write_indicator(emitter, "}", 0, 0, 0)) + return 0; + emitter->state = POP(emitter, emitter->states); + + return 1; + } + + if (!first) { + if (!yaml_emitter_write_indicator(emitter, ",", 0, 0, 0)) + return 0; + } + if (emitter->canonical || emitter->column > emitter->best_width) { + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + + if (!emitter->canonical && yaml_emitter_check_simple_key(emitter)) + { + if (!PUSH(emitter, emitter->states, + YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 1); + } + else + { + if (!yaml_emitter_write_indicator(emitter, "?", 1, 0, 0)) + return 0; + if (!PUSH(emitter, emitter->states, + YAML_EMIT_FLOW_MAPPING_VALUE_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); + } +} + +/* + * Expect a flow value node. + */ + +static int +yaml_emitter_emit_flow_mapping_value(yaml_emitter_t *emitter, + yaml_event_t *event, int simple) +{ + if (simple) { + if (!yaml_emitter_write_indicator(emitter, ":", 0, 0, 0)) + return 0; + } + else { + if (emitter->canonical || emitter->column > emitter->best_width) { + if (!yaml_emitter_write_indent(emitter)) + return 0; + } + if (!yaml_emitter_write_indicator(emitter, ":", 1, 0, 0)) + return 0; + } + if (!PUSH(emitter, emitter->states, YAML_EMIT_FLOW_MAPPING_KEY_STATE)) + return 0; + return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); +} + +/* + * Expect a block item node. + */ + +static int +yaml_emitter_emit_block_sequence_item(yaml_emitter_t *emitter, + yaml_event_t *event, int first) +{ + if (first) + { + if (!yaml_emitter_increase_indent(emitter, 0, + (emitter->mapping_context && !emitter->indention))) + return 0; + } + + if (event->type == YAML_SEQUENCE_END_EVENT) + { + emitter->indent = POP(emitter, emitter->indents); + emitter->state = POP(emitter, emitter->states); + + return 1; + } + + if (!yaml_emitter_write_indent(emitter)) + return 0; + if (!yaml_emitter_write_indicator(emitter, "-", 1, 0, 1)) + return 0; + if (!PUSH(emitter, emitter->states, + YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 1, 0, 0); +} + +/* + * Expect a block key node. + */ + +static int +yaml_emitter_emit_block_mapping_key(yaml_emitter_t *emitter, + yaml_event_t *event, int first) +{ + if (first) + { + if (!yaml_emitter_increase_indent(emitter, 0, 0)) + return 0; + } + + if (event->type == YAML_MAPPING_END_EVENT) + { + emitter->indent = POP(emitter, emitter->indents); + emitter->state = POP(emitter, emitter->states); + + return 1; + } + + if (!yaml_emitter_write_indent(emitter)) + return 0; + + if (yaml_emitter_check_simple_key(emitter)) + { + if (!PUSH(emitter, emitter->states, + YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 1); + } + else + { + if (!yaml_emitter_write_indicator(emitter, "?", 1, 0, 1)) + return 0; + if (!PUSH(emitter, emitter->states, + YAML_EMIT_BLOCK_MAPPING_VALUE_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); + } +} + +/* + * Expect a block value node. + */ + +static int +yaml_emitter_emit_block_mapping_value(yaml_emitter_t *emitter, + yaml_event_t *event, int simple) +{ + if (simple) { + if (!yaml_emitter_write_indicator(emitter, ":", 0, 0, 0)) + return 0; + } + else { + if (!yaml_emitter_write_indent(emitter)) + return 0; + if (!yaml_emitter_write_indicator(emitter, ":", 1, 0, 1)) + return 0; + } + if (!PUSH(emitter, emitter->states, + YAML_EMIT_BLOCK_MAPPING_KEY_STATE)) + return 0; + + return yaml_emitter_emit_node(emitter, event, 0, 0, 1, 0); +} + +/* + * Expect a node. + */ + +static int +yaml_emitter_emit_node(yaml_emitter_t *emitter, yaml_event_t *event, + int root, int sequence, int mapping, int simple_key) +{ + emitter->root_context = root; + emitter->sequence_context = sequence; + emitter->mapping_context = mapping; + emitter->simple_key_context = simple_key; + + switch (event->type) + { + case YAML_ALIAS_EVENT: + return yaml_emitter_emit_alias(emitter, event); + + case YAML_SCALAR_EVENT: + return yaml_emitter_emit_scalar(emitter, event); + + case YAML_SEQUENCE_START_EVENT: + return yaml_emitter_emit_sequence_start(emitter, event); + + case YAML_MAPPING_START_EVENT: + return yaml_emitter_emit_mapping_start(emitter, event); + + default: + return yaml_emitter_set_emitter_error(emitter, + "expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS"); + } + + return 0; +} + +/* + * Expect ALIAS. + */ + +static int +yaml_emitter_emit_alias(yaml_emitter_t *emitter, SHIM(yaml_event_t *event)) +{ + if (!yaml_emitter_process_anchor(emitter)) + return 0; + if (emitter->simple_key_context) + if (!PUT(emitter, ' ')) return 0; + emitter->state = POP(emitter, emitter->states); + + return 1; +} + +/* + * Expect SCALAR. + */ + +static int +yaml_emitter_emit_scalar(yaml_emitter_t *emitter, yaml_event_t *event) +{ + if (!yaml_emitter_select_scalar_style(emitter, event)) + return 0; + if (!yaml_emitter_process_anchor(emitter)) + return 0; + if (!yaml_emitter_process_tag(emitter)) + return 0; + if (!yaml_emitter_increase_indent(emitter, 1, 0)) + return 0; + if (!yaml_emitter_process_scalar(emitter)) + return 0; + emitter->indent = POP(emitter, emitter->indents); + emitter->state = POP(emitter, emitter->states); + + return 1; +} + +/* + * Expect SEQUENCE-START. + */ + +static int +yaml_emitter_emit_sequence_start(yaml_emitter_t *emitter, yaml_event_t *event) +{ + if (!yaml_emitter_process_anchor(emitter)) + return 0; + if (!yaml_emitter_process_tag(emitter)) + return 0; + + if (emitter->flow_level || emitter->canonical + || event->data.sequence_start.style == YAML_FLOW_SEQUENCE_STYLE + || yaml_emitter_check_empty_sequence(emitter)) { + emitter->state = YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE; + } + else { + emitter->state = YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE; + } + + return 1; +} + +/* + * Expect MAPPING-START. + */ + +static int +yaml_emitter_emit_mapping_start(yaml_emitter_t *emitter, yaml_event_t *event) +{ + if (!yaml_emitter_process_anchor(emitter)) + return 0; + if (!yaml_emitter_process_tag(emitter)) + return 0; + + if (emitter->flow_level || emitter->canonical + || event->data.mapping_start.style == YAML_FLOW_MAPPING_STYLE + || yaml_emitter_check_empty_mapping(emitter)) { + emitter->state = YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE; + } + else { + emitter->state = YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE; + } + + return 1; +} + +/* + * Check if the document content is an empty scalar. + */ + +static int +yaml_emitter_check_empty_document(SHIM(yaml_emitter_t *emitter)) +{ + return 0; +} + +/* + * Check if the next events represent an empty sequence. + */ + +static int +yaml_emitter_check_empty_sequence(yaml_emitter_t *emitter) +{ + if (emitter->events.tail - emitter->events.head < 2) + return 0; + + return (emitter->events.head[0].type == YAML_SEQUENCE_START_EVENT + && emitter->events.head[1].type == YAML_SEQUENCE_END_EVENT); +} + +/* + * Check if the next events represent an empty mapping. + */ + +static int +yaml_emitter_check_empty_mapping(yaml_emitter_t *emitter) +{ + if (emitter->events.tail - emitter->events.head < 2) + return 0; + + return (emitter->events.head[0].type == YAML_MAPPING_START_EVENT + && emitter->events.head[1].type == YAML_MAPPING_END_EVENT); +} + +/* + * Check if the next node can be expressed as a simple key. + */ + +static int +yaml_emitter_check_simple_key(yaml_emitter_t *emitter) +{ + yaml_event_t *event = emitter->events.head; + size_t length = 0; + + switch (event->type) + { + case YAML_ALIAS_EVENT: + length += emitter->anchor_data.anchor_length; + break; + + case YAML_SCALAR_EVENT: + if (emitter->scalar_data.multiline) + return 0; + length += emitter->anchor_data.anchor_length + + emitter->tag_data.handle_length + + emitter->tag_data.suffix_length + + emitter->scalar_data.length; + break; + + case YAML_SEQUENCE_START_EVENT: + if (!yaml_emitter_check_empty_sequence(emitter)) + return 0; + length += emitter->anchor_data.anchor_length + + emitter->tag_data.handle_length + + emitter->tag_data.suffix_length; + break; + + case YAML_MAPPING_START_EVENT: + if (!yaml_emitter_check_empty_mapping(emitter)) + return 0; + length += emitter->anchor_data.anchor_length + + emitter->tag_data.handle_length + + emitter->tag_data.suffix_length; + break; + + default: + return 0; + } + + if (length > 128) + return 0; + + return 1; +} + +/* + * Determine an acceptable scalar style. + */ + +static int +yaml_emitter_select_scalar_style(yaml_emitter_t *emitter, yaml_event_t *event) +{ + yaml_scalar_style_t style = event->data.scalar.style; + int no_tag = (!emitter->tag_data.handle && !emitter->tag_data.suffix); + + if (no_tag && !event->data.scalar.plain_implicit + && !event->data.scalar.quoted_implicit) { + return yaml_emitter_set_emitter_error(emitter, + "neither tag nor implicit flags are specified"); + } + + if (style == YAML_ANY_SCALAR_STYLE) + style = YAML_PLAIN_SCALAR_STYLE; + + if (emitter->canonical) + style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; + + if (emitter->simple_key_context && emitter->scalar_data.multiline) + style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; + + if (style == YAML_PLAIN_SCALAR_STYLE) + { + if ((emitter->flow_level && !emitter->scalar_data.flow_plain_allowed) + || (!emitter->flow_level && !emitter->scalar_data.block_plain_allowed)) + style = YAML_SINGLE_QUOTED_SCALAR_STYLE; + if (!emitter->scalar_data.length + && (emitter->flow_level || emitter->simple_key_context)) + style = YAML_SINGLE_QUOTED_SCALAR_STYLE; + if (no_tag && !event->data.scalar.plain_implicit) + style = YAML_SINGLE_QUOTED_SCALAR_STYLE; + } + + if (style == YAML_SINGLE_QUOTED_SCALAR_STYLE) + { + if (!emitter->scalar_data.single_quoted_allowed) + style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; + } + + if (style == YAML_LITERAL_SCALAR_STYLE || style == YAML_FOLDED_SCALAR_STYLE) + { + if (!emitter->scalar_data.block_allowed + || emitter->flow_level || emitter->simple_key_context) + style = YAML_DOUBLE_QUOTED_SCALAR_STYLE; + } + + if (no_tag && !event->data.scalar.quoted_implicit + && style != YAML_PLAIN_SCALAR_STYLE) + { + emitter->tag_data.handle = (yaml_char_t *)"!"; + emitter->tag_data.handle_length = 1; + } + + emitter->scalar_data.style = style; + + return 1; +} + +/* + * Write an anchor. + */ + +static int +yaml_emitter_process_anchor(yaml_emitter_t *emitter) +{ + if (!emitter->anchor_data.anchor) + return 1; + + if (!yaml_emitter_write_indicator(emitter, + (emitter->anchor_data.alias ? "*" : "&"), 1, 0, 0)) + return 0; + + return yaml_emitter_write_anchor(emitter, + emitter->anchor_data.anchor, emitter->anchor_data.anchor_length); +} + +/* + * Write a tag. + */ + +static int +yaml_emitter_process_tag(yaml_emitter_t *emitter) +{ + if (!emitter->tag_data.handle && !emitter->tag_data.suffix) + return 1; + + if (emitter->tag_data.handle) + { + if (!yaml_emitter_write_tag_handle(emitter, emitter->tag_data.handle, + emitter->tag_data.handle_length)) + return 0; + if (emitter->tag_data.suffix) { + if (!yaml_emitter_write_tag_content(emitter, emitter->tag_data.suffix, + emitter->tag_data.suffix_length, 0)) + return 0; + } + } + else + { + if (!yaml_emitter_write_indicator(emitter, "!<", 1, 0, 0)) + return 0; + if (!yaml_emitter_write_tag_content(emitter, emitter->tag_data.suffix, + emitter->tag_data.suffix_length, 0)) + return 0; + if (!yaml_emitter_write_indicator(emitter, ">", 0, 0, 0)) + return 0; + } + + return 1; +} + +/* + * Write a scalar. + */ + +static int +yaml_emitter_process_scalar(yaml_emitter_t *emitter) +{ + switch (emitter->scalar_data.style) + { + case YAML_PLAIN_SCALAR_STYLE: + return yaml_emitter_write_plain_scalar(emitter, + emitter->scalar_data.value, emitter->scalar_data.length, + !emitter->simple_key_context); + + case YAML_SINGLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_single_quoted_scalar(emitter, + emitter->scalar_data.value, emitter->scalar_data.length, + !emitter->simple_key_context); + + case YAML_DOUBLE_QUOTED_SCALAR_STYLE: + return yaml_emitter_write_double_quoted_scalar(emitter, + emitter->scalar_data.value, emitter->scalar_data.length, + !emitter->simple_key_context); + + case YAML_LITERAL_SCALAR_STYLE: + return yaml_emitter_write_literal_scalar(emitter, + emitter->scalar_data.value, emitter->scalar_data.length); + + case YAML_FOLDED_SCALAR_STYLE: + return yaml_emitter_write_folded_scalar(emitter, + emitter->scalar_data.value, emitter->scalar_data.length); + + default: + assert(1); /* Impossible. */ + } + + return 0; +} + +/* + * Check if a %YAML directive is valid. + */ + +static int +yaml_emitter_analyze_version_directive(yaml_emitter_t *emitter, + yaml_version_directive_t version_directive) +{ + if (version_directive.major != 1 || ( + version_directive.minor != 1 + && version_directive.minor != 2 + )) { + return yaml_emitter_set_emitter_error(emitter, + "incompatible %YAML directive"); + } + + return 1; +} + +/* + * Check if a %TAG directive is valid. + */ + +static int +yaml_emitter_analyze_tag_directive(yaml_emitter_t *emitter, + yaml_tag_directive_t tag_directive) +{ + yaml_string_t handle; + yaml_string_t prefix; + size_t handle_length; + size_t prefix_length; + + handle_length = strlen((char *)tag_directive.handle); + prefix_length = strlen((char *)tag_directive.prefix); + STRING_ASSIGN(handle, tag_directive.handle, handle_length); + STRING_ASSIGN(prefix, tag_directive.prefix, prefix_length); + + if (handle.start == handle.end) { + return yaml_emitter_set_emitter_error(emitter, + "tag handle must not be empty"); + } + + if (handle.start[0] != '!') { + return yaml_emitter_set_emitter_error(emitter, + "tag handle must start with '!'"); + } + + if (handle.end[-1] != '!') { + return yaml_emitter_set_emitter_error(emitter, + "tag handle must end with '!'"); + } + + handle.pointer ++; + + while (handle.pointer < handle.end-1) { + if (!IS_ALPHA(handle)) { + return yaml_emitter_set_emitter_error(emitter, + "tag handle must contain alphanumerical characters only"); + } + MOVE(handle); + } + + if (prefix.start == prefix.end) { + return yaml_emitter_set_emitter_error(emitter, + "tag prefix must not be empty"); + } + + return 1; +} + +/* + * Check if an anchor is valid. + */ + +static int +yaml_emitter_analyze_anchor(yaml_emitter_t *emitter, + yaml_char_t *anchor, int alias) +{ + size_t anchor_length; + yaml_string_t string; + + anchor_length = strlen((char *)anchor); + STRING_ASSIGN(string, anchor, anchor_length); + + if (string.start == string.end) { + return yaml_emitter_set_emitter_error(emitter, alias ? + "alias value must not be empty" : + "anchor value must not be empty"); + } + + while (string.pointer != string.end) { + if (!IS_ALPHA(string)) { + return yaml_emitter_set_emitter_error(emitter, alias ? + "alias value must contain alphanumerical characters only" : + "anchor value must contain alphanumerical characters only"); + } + MOVE(string); + } + + emitter->anchor_data.anchor = string.start; + emitter->anchor_data.anchor_length = string.end - string.start; + emitter->anchor_data.alias = alias; + + return 1; +} + +/* + * Check if a tag is valid. + */ + +static int +yaml_emitter_analyze_tag(yaml_emitter_t *emitter, + yaml_char_t *tag) +{ + size_t tag_length; + yaml_string_t string; + yaml_tag_directive_t *tag_directive; + + tag_length = strlen((char *)tag); + STRING_ASSIGN(string, tag, tag_length); + + if (string.start == string.end) { + return yaml_emitter_set_emitter_error(emitter, + "tag value must not be empty"); + } + + for (tag_directive = emitter->tag_directives.start; + tag_directive != emitter->tag_directives.top; tag_directive ++) { + size_t prefix_length = strlen((char *)tag_directive->prefix); + if (prefix_length < (size_t)(string.end - string.start) + && strncmp((char *)tag_directive->prefix, (char *)string.start, + prefix_length) == 0) + { + emitter->tag_data.handle = tag_directive->handle; + emitter->tag_data.handle_length = + strlen((char *)tag_directive->handle); + emitter->tag_data.suffix = string.start + prefix_length; + emitter->tag_data.suffix_length = + (string.end - string.start) - prefix_length; + return 1; + } + } + + emitter->tag_data.suffix = string.start; + emitter->tag_data.suffix_length = string.end - string.start; + + return 1; +} + +/* + * Check if a scalar is valid. + */ + +static int +yaml_emitter_analyze_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length) +{ + yaml_string_t string; + + int block_indicators = 0; + int flow_indicators = 0; + int line_breaks = 0; + int special_characters = 0; + + int leading_space = 0; + int leading_break = 0; + int trailing_space = 0; + int trailing_break = 0; + int break_space = 0; + int space_break = 0; + + int preceded_by_whitespace = 0; + int followed_by_whitespace = 0; + int previous_space = 0; + int previous_break = 0; + + STRING_ASSIGN(string, value, length); + + emitter->scalar_data.value = value; + emitter->scalar_data.length = length; + + if (string.start == string.end) + { + emitter->scalar_data.multiline = 0; + emitter->scalar_data.flow_plain_allowed = 0; + emitter->scalar_data.block_plain_allowed = 1; + emitter->scalar_data.single_quoted_allowed = 1; + emitter->scalar_data.block_allowed = 0; + + return 1; + } + + if ((CHECK_AT(string, '-', 0) + && CHECK_AT(string, '-', 1) + && CHECK_AT(string, '-', 2)) + || (CHECK_AT(string, '.', 0) + && CHECK_AT(string, '.', 1) + && CHECK_AT(string, '.', 2))) { + block_indicators = 1; + flow_indicators = 1; + } + + preceded_by_whitespace = 1; + followed_by_whitespace = IS_BLANKZ_AT(string, WIDTH(string)); + + while (string.pointer != string.end) + { + if (string.start == string.pointer) + { + if (CHECK(string, '#') || CHECK(string, ',') + || CHECK(string, '[') || CHECK(string, ']') + || CHECK(string, '{') || CHECK(string, '}') + || CHECK(string, '&') || CHECK(string, '*') + || CHECK(string, '!') || CHECK(string, '|') + || CHECK(string, '>') || CHECK(string, '\'') + || CHECK(string, '"') || CHECK(string, '%') + || CHECK(string, '@') || CHECK(string, '`')) { + flow_indicators = 1; + block_indicators = 1; + } + + if (CHECK(string, '?') || CHECK(string, ':')) { + flow_indicators = 1; + if (followed_by_whitespace) { + block_indicators = 1; + } + } + + if (CHECK(string, '-') && followed_by_whitespace) { + flow_indicators = 1; + block_indicators = 1; + } + } + else + { + if (CHECK(string, ',') || CHECK(string, '?') + || CHECK(string, '[') || CHECK(string, ']') + || CHECK(string, '{') || CHECK(string, '}')) { + flow_indicators = 1; + } + + if (CHECK(string, ':')) { + flow_indicators = 1; + if (followed_by_whitespace) { + block_indicators = 1; + } + } + + if (CHECK(string, '#') && preceded_by_whitespace) { + flow_indicators = 1; + block_indicators = 1; + } + } + + if (!IS_PRINTABLE(string) + || (!IS_ASCII(string) && !emitter->unicode)) { + special_characters = 1; + } + + if (IS_BREAK(string)) { + line_breaks = 1; + } + + if (IS_SPACE(string)) + { + if (string.start == string.pointer) { + leading_space = 1; + } + if (string.pointer+WIDTH(string) == string.end) { + trailing_space = 1; + } + if (previous_break) { + break_space = 1; + } + previous_space = 1; + previous_break = 0; + } + else if (IS_BREAK(string)) + { + if (string.start == string.pointer) { + leading_break = 1; + } + if (string.pointer+WIDTH(string) == string.end) { + trailing_break = 1; + } + if (previous_space) { + space_break = 1; + } + previous_space = 0; + previous_break = 1; + } + else + { + previous_space = 0; + previous_break = 0; + } + + preceded_by_whitespace = IS_BLANKZ(string); + MOVE(string); + if (string.pointer != string.end) { + followed_by_whitespace = IS_BLANKZ_AT(string, WIDTH(string)); + } + } + + emitter->scalar_data.multiline = line_breaks; + + emitter->scalar_data.flow_plain_allowed = 1; + emitter->scalar_data.block_plain_allowed = 1; + emitter->scalar_data.single_quoted_allowed = 1; + emitter->scalar_data.block_allowed = 1; + + if (leading_space || leading_break || trailing_space || trailing_break) { + emitter->scalar_data.flow_plain_allowed = 0; + emitter->scalar_data.block_plain_allowed = 0; + } + + if (trailing_space) { + emitter->scalar_data.block_allowed = 0; + } + + if (break_space) { + emitter->scalar_data.flow_plain_allowed = 0; + emitter->scalar_data.block_plain_allowed = 0; + emitter->scalar_data.single_quoted_allowed = 0; + } + + if (space_break || special_characters) { + emitter->scalar_data.flow_plain_allowed = 0; + emitter->scalar_data.block_plain_allowed = 0; + emitter->scalar_data.single_quoted_allowed = 0; + emitter->scalar_data.block_allowed = 0; + } + + if (line_breaks) { + emitter->scalar_data.flow_plain_allowed = 0; + emitter->scalar_data.block_plain_allowed = 0; + } + + if (flow_indicators) { + emitter->scalar_data.flow_plain_allowed = 0; + } + + if (block_indicators) { + emitter->scalar_data.block_plain_allowed = 0; + } + + return 1; +} + +/* + * Check if the event data is valid. + */ + +static int +yaml_emitter_analyze_event(yaml_emitter_t *emitter, + yaml_event_t *event) +{ + emitter->anchor_data.anchor = NULL; + emitter->anchor_data.anchor_length = 0; + emitter->tag_data.handle = NULL; + emitter->tag_data.handle_length = 0; + emitter->tag_data.suffix = NULL; + emitter->tag_data.suffix_length = 0; + emitter->scalar_data.value = NULL; + emitter->scalar_data.length = 0; + + switch (event->type) + { + case YAML_ALIAS_EVENT: + if (!yaml_emitter_analyze_anchor(emitter, + event->data.alias.anchor, 1)) + return 0; + return 1; + + case YAML_SCALAR_EVENT: + if (event->data.scalar.anchor) { + if (!yaml_emitter_analyze_anchor(emitter, + event->data.scalar.anchor, 0)) + return 0; + } + if (event->data.scalar.tag && (emitter->canonical || + (!event->data.scalar.plain_implicit + && !event->data.scalar.quoted_implicit))) { + if (!yaml_emitter_analyze_tag(emitter, event->data.scalar.tag)) + return 0; + } + if (!yaml_emitter_analyze_scalar(emitter, + event->data.scalar.value, event->data.scalar.length)) + return 0; + return 1; + + case YAML_SEQUENCE_START_EVENT: + if (event->data.sequence_start.anchor) { + if (!yaml_emitter_analyze_anchor(emitter, + event->data.sequence_start.anchor, 0)) + return 0; + } + if (event->data.sequence_start.tag && (emitter->canonical || + !event->data.sequence_start.implicit)) { + if (!yaml_emitter_analyze_tag(emitter, + event->data.sequence_start.tag)) + return 0; + } + return 1; + + case YAML_MAPPING_START_EVENT: + if (event->data.mapping_start.anchor) { + if (!yaml_emitter_analyze_anchor(emitter, + event->data.mapping_start.anchor, 0)) + return 0; + } + if (event->data.mapping_start.tag && (emitter->canonical || + !event->data.mapping_start.implicit)) { + if (!yaml_emitter_analyze_tag(emitter, + event->data.mapping_start.tag)) + return 0; + } + return 1; + + default: + return 1; + } +} + +/* + * Write the BOM character. + */ + +static int +yaml_emitter_write_bom(yaml_emitter_t *emitter) +{ + if (!FLUSH(emitter)) return 0; + + *(emitter->buffer.pointer++) = (yaml_char_t) '\xEF'; + *(emitter->buffer.pointer++) = (yaml_char_t) '\xBB'; + *(emitter->buffer.pointer++) = (yaml_char_t) '\xBF'; + + return 1; +} + +static int +yaml_emitter_write_indent(yaml_emitter_t *emitter) +{ + int indent = (emitter->indent >= 0) ? emitter->indent : 0; + + if (!emitter->indention || emitter->column > indent + || (emitter->column == indent && !emitter->whitespace)) { + if (!PUT_BREAK(emitter)) return 0; + } + + while (emitter->column < indent) { + if (!PUT(emitter, ' ')) return 0; + } + + emitter->whitespace = 1; + emitter->indention = 1; + + return 1; +} + +static int +yaml_emitter_write_indicator(yaml_emitter_t *emitter, + const char *indicator, int need_whitespace, + int is_whitespace, int is_indention) +{ + size_t indicator_length; + yaml_string_t string; + + indicator_length = strlen(indicator); + STRING_ASSIGN(string, (yaml_char_t *)indicator, indicator_length); + + if (need_whitespace && !emitter->whitespace) { + if (!PUT(emitter, ' ')) return 0; + } + + while (string.pointer != string.end) { + if (!WRITE(emitter, string)) return 0; + } + + emitter->whitespace = is_whitespace; + emitter->indention = (emitter->indention && is_indention); + + return 1; +} + +static int +yaml_emitter_write_anchor(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length) +{ + yaml_string_t string; + STRING_ASSIGN(string, value, length); + + while (string.pointer != string.end) { + if (!WRITE(emitter, string)) return 0; + } + + emitter->whitespace = 0; + emitter->indention = 0; + + return 1; +} + +static int +yaml_emitter_write_tag_handle(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length) +{ + yaml_string_t string; + STRING_ASSIGN(string, value, length); + + if (!emitter->whitespace) { + if (!PUT(emitter, ' ')) return 0; + } + + while (string.pointer != string.end) { + if (!WRITE(emitter, string)) return 0; + } + + emitter->whitespace = 0; + emitter->indention = 0; + + return 1; +} + +static int +yaml_emitter_write_tag_content(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, + int need_whitespace) +{ + yaml_string_t string; + STRING_ASSIGN(string, value, length); + + if (need_whitespace && !emitter->whitespace) { + if (!PUT(emitter, ' ')) return 0; + } + + while (string.pointer != string.end) { + if (IS_ALPHA(string) + || CHECK(string, ';') || CHECK(string, '/') + || CHECK(string, '?') || CHECK(string, ':') + || CHECK(string, '@') || CHECK(string, '&') + || CHECK(string, '=') || CHECK(string, '+') + || CHECK(string, '$') || CHECK(string, ',') + || CHECK(string, '_') || CHECK(string, '.') + || CHECK(string, '~') || CHECK(string, '*') + || CHECK(string, '\'') || CHECK(string, '(') + || CHECK(string, ')') || CHECK(string, '[') + || CHECK(string, ']')) { + if (!WRITE(emitter, string)) return 0; + } + else { + int width = WIDTH(string); + unsigned int value; + while (width --) { + value = *(string.pointer++); + if (!PUT(emitter, '%')) return 0; + if (!PUT(emitter, (value >> 4) + + ((value >> 4) < 10 ? '0' : 'A' - 10))) + return 0; + if (!PUT(emitter, (value & 0x0F) + + ((value & 0x0F) < 10 ? '0' : 'A' - 10))) + return 0; + } + } + } + + emitter->whitespace = 0; + emitter->indention = 0; + + return 1; +} + +static int +yaml_emitter_write_plain_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int allow_breaks) +{ + yaml_string_t string; + int spaces = 0; + int breaks = 0; + + STRING_ASSIGN(string, value, length); + + /** + * Avoid trailing spaces for empty values in block mode. + * In flow mode, we still want the space to prevent ambiguous things + * like {a:}. + * Currently, the emitter forbids any plain empty scalar in flow mode + * (e.g. it outputs {a: ''} instead), so emitter->flow_level will + * never be true here. + * But if the emitter is ever changed to allow emitting empty values, + * the check for flow_level is already here. + */ + if (!emitter->whitespace && (length || emitter->flow_level)) { + if (!PUT(emitter, ' ')) return 0; + } + + while (string.pointer != string.end) + { + if (IS_SPACE(string)) + { + if (allow_breaks && !spaces + && emitter->column > emitter->best_width + && !IS_SPACE_AT(string, 1)) { + if (!yaml_emitter_write_indent(emitter)) return 0; + MOVE(string); + } + else { + if (!WRITE(emitter, string)) return 0; + } + spaces = 1; + } + else if (IS_BREAK(string)) + { + if (!breaks && CHECK(string, '\n')) { + if (!PUT_BREAK(emitter)) return 0; + } + if (!WRITE_BREAK(emitter, string)) return 0; + emitter->indention = 1; + breaks = 1; + } + else + { + if (breaks) { + if (!yaml_emitter_write_indent(emitter)) return 0; + } + if (!WRITE(emitter, string)) return 0; + emitter->indention = 0; + spaces = 0; + breaks = 0; + } + } + + emitter->whitespace = 0; + emitter->indention = 0; + + return 1; +} + +static int +yaml_emitter_write_single_quoted_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int allow_breaks) +{ + yaml_string_t string; + int spaces = 0; + int breaks = 0; + + STRING_ASSIGN(string, value, length); + + if (!yaml_emitter_write_indicator(emitter, "'", 1, 0, 0)) + return 0; + + while (string.pointer != string.end) + { + if (IS_SPACE(string)) + { + if (allow_breaks && !spaces + && emitter->column > emitter->best_width + && string.pointer != string.start + && string.pointer != string.end - 1 + && !IS_SPACE_AT(string, 1)) { + if (!yaml_emitter_write_indent(emitter)) return 0; + MOVE(string); + } + else { + if (!WRITE(emitter, string)) return 0; + } + spaces = 1; + } + else if (IS_BREAK(string)) + { + if (!breaks && CHECK(string, '\n')) { + if (!PUT_BREAK(emitter)) return 0; + } + if (!WRITE_BREAK(emitter, string)) return 0; + emitter->indention = 1; + breaks = 1; + } + else + { + if (breaks) { + if (!yaml_emitter_write_indent(emitter)) return 0; + } + if (CHECK(string, '\'')) { + if (!PUT(emitter, '\'')) return 0; + } + if (!WRITE(emitter, string)) return 0; + emitter->indention = 0; + spaces = 0; + breaks = 0; + } + } + + if (breaks) + if (!yaml_emitter_write_indent(emitter)) return 0; + + if (!yaml_emitter_write_indicator(emitter, "'", 0, 0, 0)) + return 0; + + emitter->whitespace = 0; + emitter->indention = 0; + + return 1; +} + +static int +yaml_emitter_write_double_quoted_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length, int allow_breaks) +{ + yaml_string_t string; + int spaces = 0; + + STRING_ASSIGN(string, value, length); + + if (!yaml_emitter_write_indicator(emitter, "\"", 1, 0, 0)) + return 0; + + while (string.pointer != string.end) + { + if (!IS_PRINTABLE(string) || (!emitter->unicode && !IS_ASCII(string)) + || IS_BOM(string) || IS_BREAK(string) + || CHECK(string, '"') || CHECK(string, '\\')) + { + unsigned char octet; + unsigned int width; + unsigned int value; + int k; + + octet = string.pointer[0]; + width = (octet & 0x80) == 0x00 ? 1 : + (octet & 0xE0) == 0xC0 ? 2 : + (octet & 0xF0) == 0xE0 ? 3 : + (octet & 0xF8) == 0xF0 ? 4 : 0; + value = (octet & 0x80) == 0x00 ? octet & 0x7F : + (octet & 0xE0) == 0xC0 ? octet & 0x1F : + (octet & 0xF0) == 0xE0 ? octet & 0x0F : + (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; + for (k = 1; k < (int)width; k ++) { + octet = string.pointer[k]; + value = (value << 6) + (octet & 0x3F); + } + string.pointer += width; + + if (!PUT(emitter, '\\')) return 0; + + switch (value) + { + case 0x00: + if (!PUT(emitter, '0')) return 0; + break; + + case 0x07: + if (!PUT(emitter, 'a')) return 0; + break; + + case 0x08: + if (!PUT(emitter, 'b')) return 0; + break; + + case 0x09: + if (!PUT(emitter, 't')) return 0; + break; + + case 0x0A: + if (!PUT(emitter, 'n')) return 0; + break; + + case 0x0B: + if (!PUT(emitter, 'v')) return 0; + break; + + case 0x0C: + if (!PUT(emitter, 'f')) return 0; + break; + + case 0x0D: + if (!PUT(emitter, 'r')) return 0; + break; + + case 0x1B: + if (!PUT(emitter, 'e')) return 0; + break; + + case 0x22: + if (!PUT(emitter, '\"')) return 0; + break; + + case 0x5C: + if (!PUT(emitter, '\\')) return 0; + break; + + case 0x85: + if (!PUT(emitter, 'N')) return 0; + break; + + case 0xA0: + if (!PUT(emitter, '_')) return 0; + break; + + case 0x2028: + if (!PUT(emitter, 'L')) return 0; + break; + + case 0x2029: + if (!PUT(emitter, 'P')) return 0; + break; + + default: + if (value <= 0xFF) { + if (!PUT(emitter, 'x')) return 0; + width = 2; + } + else if (value <= 0xFFFF) { + if (!PUT(emitter, 'u')) return 0; + width = 4; + } + else { + if (!PUT(emitter, 'U')) return 0; + width = 8; + } + for (k = (width-1)*4; k >= 0; k -= 4) { + int digit = (value >> k) & 0x0F; + if (!PUT(emitter, digit + (digit < 10 ? '0' : 'A'-10))) + return 0; + } + } + spaces = 0; + } + else if (IS_SPACE(string)) + { + if (allow_breaks && !spaces + && emitter->column > emitter->best_width + && string.pointer != string.start + && string.pointer != string.end - 1) { + if (!yaml_emitter_write_indent(emitter)) return 0; + if (IS_SPACE_AT(string, 1)) { + if (!PUT(emitter, '\\')) return 0; + } + MOVE(string); + } + else { + if (!WRITE(emitter, string)) return 0; + } + spaces = 1; + } + else + { + if (!WRITE(emitter, string)) return 0; + spaces = 0; + } + } + + if (!yaml_emitter_write_indicator(emitter, "\"", 0, 0, 0)) + return 0; + + emitter->whitespace = 0; + emitter->indention = 0; + + return 1; +} + +static int +yaml_emitter_write_block_scalar_hints(yaml_emitter_t *emitter, + yaml_string_t string) +{ + char indent_hint[2]; + const char *chomp_hint = NULL; + + if (IS_SPACE(string) || IS_BREAK(string)) + { + indent_hint[0] = '0' + (char)emitter->best_indent; + indent_hint[1] = '\0'; + if (!yaml_emitter_write_indicator(emitter, indent_hint, 0, 0, 0)) + return 0; + } + + emitter->open_ended = 0; + + string.pointer = string.end; + if (string.start == string.pointer) + { + chomp_hint = "-"; + } + else + { + do { + string.pointer --; + } while ((*string.pointer & 0xC0) == 0x80); + if (!IS_BREAK(string)) + { + chomp_hint = "-"; + } + else if (string.start == string.pointer) + { + chomp_hint = "+"; + emitter->open_ended = 2; + } + else + { + do { + string.pointer --; + } while ((*string.pointer & 0xC0) == 0x80); + if (IS_BREAK(string)) + { + chomp_hint = "+"; + emitter->open_ended = 2; + } + } + } + + if (chomp_hint) + { + if (!yaml_emitter_write_indicator(emitter, chomp_hint, 0, 0, 0)) + return 0; + } + + return 1; +} + +static int +yaml_emitter_write_literal_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length) +{ + yaml_string_t string; + int breaks = 1; + + STRING_ASSIGN(string, value, length); + + if (!yaml_emitter_write_indicator(emitter, "|", 1, 0, 0)) + return 0; + if (!yaml_emitter_write_block_scalar_hints(emitter, string)) + return 0; + if (!PUT_BREAK(emitter)) return 0; + emitter->indention = 1; + emitter->whitespace = 1; + + while (string.pointer != string.end) + { + if (IS_BREAK(string)) + { + if (!WRITE_BREAK(emitter, string)) return 0; + emitter->indention = 1; + breaks = 1; + } + else + { + if (breaks) { + if (!yaml_emitter_write_indent(emitter)) return 0; + } + if (!WRITE(emitter, string)) return 0; + emitter->indention = 0; + breaks = 0; + } + } + + return 1; +} + +static int +yaml_emitter_write_folded_scalar(yaml_emitter_t *emitter, + yaml_char_t *value, size_t length) +{ + yaml_string_t string; + int breaks = 1; + int leading_spaces = 1; + + STRING_ASSIGN(string, value, length); + + if (!yaml_emitter_write_indicator(emitter, ">", 1, 0, 0)) + return 0; + if (!yaml_emitter_write_block_scalar_hints(emitter, string)) + return 0; + if (!PUT_BREAK(emitter)) return 0; + emitter->indention = 1; + emitter->whitespace = 1; + + while (string.pointer != string.end) + { + if (IS_BREAK(string)) + { + if (!breaks && !leading_spaces && CHECK(string, '\n')) { + int k = 0; + while (IS_BREAK_AT(string, k)) { + k += WIDTH_AT(string, k); + } + if (!IS_BLANKZ_AT(string, k)) { + if (!PUT_BREAK(emitter)) return 0; + } + } + if (!WRITE_BREAK(emitter, string)) return 0; + emitter->indention = 1; + breaks = 1; + } + else + { + if (breaks) { + if (!yaml_emitter_write_indent(emitter)) return 0; + leading_spaces = IS_BLANK(string); + } + if (!breaks && IS_SPACE(string) && !IS_SPACE_AT(string, 1) + && emitter->column > emitter->best_width) { + if (!yaml_emitter_write_indent(emitter)) return 0; + MOVE(string); + } + else { + if (!WRITE(emitter, string)) return 0; + } + emitter->indention = 0; + breaks = 0; + } + } + + return 1; +} diff --git a/src/vendors/libyaml/loader.c b/src/vendors/libyaml/loader.c new file mode 100644 index 0000000..3446e2a --- /dev/null +++ b/src/vendors/libyaml/loader.c @@ -0,0 +1,544 @@ + +#include "yaml_private.h" + +/* + * API functions. + */ + +YAML_DECLARE(int) +yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document); + +/* + * Error handling. + */ + +static int +yaml_parser_set_composer_error(yaml_parser_t *parser, + const char *problem, yaml_mark_t problem_mark); + +static int +yaml_parser_set_composer_error_context(yaml_parser_t *parser, + const char *context, yaml_mark_t context_mark, + const char *problem, yaml_mark_t problem_mark); + + +/* + * Alias handling. + */ + +static int +yaml_parser_register_anchor(yaml_parser_t *parser, + int index, yaml_char_t *anchor); + +/* + * Clean up functions. + */ + +static void +yaml_parser_delete_aliases(yaml_parser_t *parser); + +/* + * Document loading context. + */ +struct loader_ctx { + int *start; + int *end; + int *top; +}; + +/* + * Composer functions. + */ +static int +yaml_parser_load_nodes(yaml_parser_t *parser, struct loader_ctx *ctx); + +static int +yaml_parser_load_document(yaml_parser_t *parser, yaml_event_t *event); + +static int +yaml_parser_load_alias(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx); + +static int +yaml_parser_load_scalar(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx); + +static int +yaml_parser_load_sequence(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx); + +static int +yaml_parser_load_mapping(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx); + +static int +yaml_parser_load_sequence_end(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx); + +static int +yaml_parser_load_mapping_end(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx); + +/* + * Load the next document of the stream. + */ + +YAML_DECLARE(int) +yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document) +{ + yaml_event_t event; + + assert(parser); /* Non-NULL parser object is expected. */ + assert(document); /* Non-NULL document object is expected. */ + + memset(document, 0, sizeof(yaml_document_t)); + if (!STACK_INIT(parser, document->nodes, yaml_node_t*)) + goto error; + + if (!parser->stream_start_produced) { + if (!yaml_parser_parse(parser, &event)) goto error; + assert(event.type == YAML_STREAM_START_EVENT); + /* STREAM-START is expected. */ + } + + if (parser->stream_end_produced) { + return 1; + } + + if (!yaml_parser_parse(parser, &event)) goto error; + if (event.type == YAML_STREAM_END_EVENT) { + return 1; + } + + if (!STACK_INIT(parser, parser->aliases, yaml_alias_data_t*)) + goto error; + + parser->document = document; + + if (!yaml_parser_load_document(parser, &event)) goto error; + + yaml_parser_delete_aliases(parser); + parser->document = NULL; + + return 1; + +error: + + yaml_parser_delete_aliases(parser); + yaml_document_delete(document); + parser->document = NULL; + + return 0; +} + +/* + * Set composer error. + */ + +static int +yaml_parser_set_composer_error(yaml_parser_t *parser, + const char *problem, yaml_mark_t problem_mark) +{ + parser->error = YAML_COMPOSER_ERROR; + parser->problem = problem; + parser->problem_mark = problem_mark; + + return 0; +} + +/* + * Set composer error with context. + */ + +static int +yaml_parser_set_composer_error_context(yaml_parser_t *parser, + const char *context, yaml_mark_t context_mark, + const char *problem, yaml_mark_t problem_mark) +{ + parser->error = YAML_COMPOSER_ERROR; + parser->context = context; + parser->context_mark = context_mark; + parser->problem = problem; + parser->problem_mark = problem_mark; + + return 0; +} + +/* + * Delete the stack of aliases. + */ + +static void +yaml_parser_delete_aliases(yaml_parser_t *parser) +{ + while (!STACK_EMPTY(parser, parser->aliases)) { + yaml_free(POP(parser, parser->aliases).anchor); + } + STACK_DEL(parser, parser->aliases); +} + +/* + * Compose a document object. + */ + +static int +yaml_parser_load_document(yaml_parser_t *parser, yaml_event_t *event) +{ + struct loader_ctx ctx = { NULL, NULL, NULL }; + + assert(event->type == YAML_DOCUMENT_START_EVENT); + /* DOCUMENT-START is expected. */ + + parser->document->version_directive + = event->data.document_start.version_directive; + parser->document->tag_directives.start + = event->data.document_start.tag_directives.start; + parser->document->tag_directives.end + = event->data.document_start.tag_directives.end; + parser->document->start_implicit + = event->data.document_start.implicit; + parser->document->start_mark = event->start_mark; + + if (!STACK_INIT(parser, ctx, int*)) return 0; + if (!yaml_parser_load_nodes(parser, &ctx)) { + STACK_DEL(parser, ctx); + return 0; + } + STACK_DEL(parser, ctx); + + return 1; +} + +/* + * Compose a node tree. + */ + +static int +yaml_parser_load_nodes(yaml_parser_t *parser, struct loader_ctx *ctx) +{ + yaml_event_t event; + + do { + if (!yaml_parser_parse(parser, &event)) return 0; + + switch (event.type) { + case YAML_ALIAS_EVENT: + if (!yaml_parser_load_alias(parser, &event, ctx)) return 0; + break; + case YAML_SCALAR_EVENT: + if (!yaml_parser_load_scalar(parser, &event, ctx)) return 0; + break; + case YAML_SEQUENCE_START_EVENT: + if (!yaml_parser_load_sequence(parser, &event, ctx)) return 0; + break; + case YAML_SEQUENCE_END_EVENT: + if (!yaml_parser_load_sequence_end(parser, &event, ctx)) + return 0; + break; + case YAML_MAPPING_START_EVENT: + if (!yaml_parser_load_mapping(parser, &event, ctx)) return 0; + break; + case YAML_MAPPING_END_EVENT: + if (!yaml_parser_load_mapping_end(parser, &event, ctx)) + return 0; + break; + default: + assert(0); /* Could not happen. */ + return 0; + case YAML_DOCUMENT_END_EVENT: + break; + } + } while (event.type != YAML_DOCUMENT_END_EVENT); + + parser->document->end_implicit = event.data.document_end.implicit; + parser->document->end_mark = event.end_mark; + + return 1; +} + +/* + * Add an anchor. + */ + +static int +yaml_parser_register_anchor(yaml_parser_t *parser, + int index, yaml_char_t *anchor) +{ + yaml_alias_data_t data; + yaml_alias_data_t *alias_data; + + if (!anchor) return 1; + + data.anchor = anchor; + data.index = index; + data.mark = parser->document->nodes.start[index-1].start_mark; + + for (alias_data = parser->aliases.start; + alias_data != parser->aliases.top; alias_data ++) { + if (strcmp((char *)alias_data->anchor, (char *)anchor) == 0) { + yaml_free(anchor); + return yaml_parser_set_composer_error_context(parser, + "found duplicate anchor; first occurrence", + alias_data->mark, "second occurrence", data.mark); + } + } + + if (!PUSH(parser, parser->aliases, data)) { + yaml_free(anchor); + return 0; + } + + return 1; +} + +/* + * Compose node into its parent in the stree. + */ + +static int +yaml_parser_load_node_add(yaml_parser_t *parser, struct loader_ctx *ctx, + int index) +{ + struct yaml_node_s *parent; + int parent_index; + + if (STACK_EMPTY(parser, *ctx)) { + /* This is the root node, there's no tree to add it to. */ + return 1; + } + + parent_index = *((*ctx).top - 1); + parent = &parser->document->nodes.start[parent_index-1]; + + switch (parent->type) { + case YAML_SEQUENCE_NODE: + if (!STACK_LIMIT(parser, parent->data.sequence.items, INT_MAX-1)) + return 0; + if (!PUSH(parser, parent->data.sequence.items, index)) + return 0; + break; + case YAML_MAPPING_NODE: { + yaml_node_pair_t pair; + if (!STACK_EMPTY(parser, parent->data.mapping.pairs)) { + yaml_node_pair_t *p = parent->data.mapping.pairs.top - 1; + if (p->key != 0 && p->value == 0) { + p->value = index; + break; + } + } + + pair.key = index; + pair.value = 0; + if (!STACK_LIMIT(parser, parent->data.mapping.pairs, INT_MAX-1)) + return 0; + if (!PUSH(parser, parent->data.mapping.pairs, pair)) + return 0; + + break; + } + default: + assert(0); /* Could not happen. */ + return 0; + } + return 1; +} + +/* + * Compose a node corresponding to an alias. + */ + +static int +yaml_parser_load_alias(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx) +{ + yaml_char_t *anchor = event->data.alias.anchor; + yaml_alias_data_t *alias_data; + + for (alias_data = parser->aliases.start; + alias_data != parser->aliases.top; alias_data ++) { + if (strcmp((char *)alias_data->anchor, (char *)anchor) == 0) { + yaml_free(anchor); + return yaml_parser_load_node_add(parser, ctx, alias_data->index); + } + } + + yaml_free(anchor); + return yaml_parser_set_composer_error(parser, "found undefined alias", + event->start_mark); +} + +/* + * Compose a scalar node. + */ + +static int +yaml_parser_load_scalar(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx) +{ + yaml_node_t node; + int index; + yaml_char_t *tag = event->data.scalar.tag; + + if (!STACK_LIMIT(parser, parser->document->nodes, INT_MAX-1)) goto error; + + if (!tag || strcmp((char *)tag, "!") == 0) { + yaml_free(tag); + tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_SCALAR_TAG); + if (!tag) goto error; + } + + SCALAR_NODE_INIT(node, tag, event->data.scalar.value, + event->data.scalar.length, event->data.scalar.style, + event->start_mark, event->end_mark); + + if (!PUSH(parser, parser->document->nodes, node)) goto error; + + index = parser->document->nodes.top - parser->document->nodes.start; + + if (!yaml_parser_register_anchor(parser, index, + event->data.scalar.anchor)) return 0; + + return yaml_parser_load_node_add(parser, ctx, index); + +error: + yaml_free(tag); + yaml_free(event->data.scalar.anchor); + yaml_free(event->data.scalar.value); + return 0; +} + +/* + * Compose a sequence node. + */ + +static int +yaml_parser_load_sequence(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx) +{ + yaml_node_t node; + struct { + yaml_node_item_t *start; + yaml_node_item_t *end; + yaml_node_item_t *top; + } items = { NULL, NULL, NULL }; + int index; + yaml_char_t *tag = event->data.sequence_start.tag; + + if (!STACK_LIMIT(parser, parser->document->nodes, INT_MAX-1)) goto error; + + if (!tag || strcmp((char *)tag, "!") == 0) { + yaml_free(tag); + tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG); + if (!tag) goto error; + } + + if (!STACK_INIT(parser, items, yaml_node_item_t*)) goto error; + + SEQUENCE_NODE_INIT(node, tag, items.start, items.end, + event->data.sequence_start.style, + event->start_mark, event->end_mark); + + if (!PUSH(parser, parser->document->nodes, node)) goto error; + + index = parser->document->nodes.top - parser->document->nodes.start; + + if (!yaml_parser_register_anchor(parser, index, + event->data.sequence_start.anchor)) return 0; + + if (!yaml_parser_load_node_add(parser, ctx, index)) return 0; + + if (!STACK_LIMIT(parser, *ctx, INT_MAX-1)) return 0; + if (!PUSH(parser, *ctx, index)) return 0; + + return 1; + +error: + yaml_free(tag); + yaml_free(event->data.sequence_start.anchor); + return 0; +} + +static int +yaml_parser_load_sequence_end(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx) +{ + int index; + + assert(((*ctx).top - (*ctx).start) > 0); + + index = *((*ctx).top - 1); + assert(parser->document->nodes.start[index-1].type == YAML_SEQUENCE_NODE); + parser->document->nodes.start[index-1].end_mark = event->end_mark; + + (void)POP(parser, *ctx); + + return 1; +} + +/* + * Compose a mapping node. + */ + +static int +yaml_parser_load_mapping(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx) +{ + yaml_node_t node; + struct { + yaml_node_pair_t *start; + yaml_node_pair_t *end; + yaml_node_pair_t *top; + } pairs = { NULL, NULL, NULL }; + int index; + yaml_char_t *tag = event->data.mapping_start.tag; + + if (!STACK_LIMIT(parser, parser->document->nodes, INT_MAX-1)) goto error; + + if (!tag || strcmp((char *)tag, "!") == 0) { + yaml_free(tag); + tag = yaml_strdup((yaml_char_t *)YAML_DEFAULT_MAPPING_TAG); + if (!tag) goto error; + } + + if (!STACK_INIT(parser, pairs, yaml_node_pair_t*)) goto error; + + MAPPING_NODE_INIT(node, tag, pairs.start, pairs.end, + event->data.mapping_start.style, + event->start_mark, event->end_mark); + + if (!PUSH(parser, parser->document->nodes, node)) goto error; + + index = parser->document->nodes.top - parser->document->nodes.start; + + if (!yaml_parser_register_anchor(parser, index, + event->data.mapping_start.anchor)) return 0; + + if (!yaml_parser_load_node_add(parser, ctx, index)) return 0; + + if (!STACK_LIMIT(parser, *ctx, INT_MAX-1)) return 0; + if (!PUSH(parser, *ctx, index)) return 0; + + return 1; + +error: + yaml_free(tag); + yaml_free(event->data.mapping_start.anchor); + return 0; +} + +static int +yaml_parser_load_mapping_end(yaml_parser_t *parser, yaml_event_t *event, + struct loader_ctx *ctx) +{ + int index; + + assert(((*ctx).top - (*ctx).start) > 0); + + index = *((*ctx).top - 1); + assert(parser->document->nodes.start[index-1].type == YAML_MAPPING_NODE); + parser->document->nodes.start[index-1].end_mark = event->end_mark; + + (void)POP(parser, *ctx); + + return 1; +} \ No newline at end of file diff --git a/src/vendors/libyaml/parser.c b/src/vendors/libyaml/parser.c new file mode 100644 index 0000000..2b3d3e3 --- /dev/null +++ b/src/vendors/libyaml/parser.c @@ -0,0 +1,1375 @@ + +/* + * The parser implements the following grammar: + * + * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + * implicit_document ::= block_node DOCUMENT-END* + * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + * block_node_or_indentless_sequence ::= + * ALIAS + * | properties (block_content | indentless_block_sequence)? + * | block_content + * | indentless_block_sequence + * block_node ::= ALIAS + * | properties block_content? + * | block_content + * flow_node ::= ALIAS + * | properties flow_content? + * | flow_content + * properties ::= TAG ANCHOR? | ANCHOR TAG? + * block_content ::= block_collection | flow_collection | SCALAR + * flow_content ::= flow_collection | SCALAR + * block_collection ::= block_sequence | block_mapping + * flow_collection ::= flow_sequence | flow_mapping + * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + * indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + * block_mapping ::= BLOCK-MAPPING_START + * ((KEY block_node_or_indentless_sequence?)? + * (VALUE block_node_or_indentless_sequence?)?)* + * BLOCK-END + * flow_sequence ::= FLOW-SEQUENCE-START + * (flow_sequence_entry FLOW-ENTRY)* + * flow_sequence_entry? + * FLOW-SEQUENCE-END + * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * flow_mapping ::= FLOW-MAPPING-START + * (flow_mapping_entry FLOW-ENTRY)* + * flow_mapping_entry? + * FLOW-MAPPING-END + * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + */ + +#include "yaml_private.h" + +/* + * Peek the next token in the token queue. + */ + +#define PEEK_TOKEN(parser) \ + ((parser->token_available || yaml_parser_fetch_more_tokens(parser)) ? \ + parser->tokens.head : NULL) + +/* + * Remove the next token from the queue (must be called after PEEK_TOKEN). + */ + +#define SKIP_TOKEN(parser) \ + (parser->token_available = 0, \ + parser->tokens_parsed ++, \ + parser->stream_end_produced = \ + (parser->tokens.head->type == YAML_STREAM_END_TOKEN), \ + parser->tokens.head ++) + +/* + * Public API declarations. + */ + +YAML_DECLARE(int) +yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event); + +/* + * Error handling. + */ + +static int +yaml_parser_set_parser_error(yaml_parser_t *parser, + const char *problem, yaml_mark_t problem_mark); + +static int +yaml_parser_set_parser_error_context(yaml_parser_t *parser, + const char *context, yaml_mark_t context_mark, + const char *problem, yaml_mark_t problem_mark); + +/* + * State functions. + */ + +static int +yaml_parser_state_machine(yaml_parser_t *parser, yaml_event_t *event); + +static int +yaml_parser_parse_stream_start(yaml_parser_t *parser, yaml_event_t *event); + +static int +yaml_parser_parse_document_start(yaml_parser_t *parser, yaml_event_t *event, + int implicit); + +static int +yaml_parser_parse_document_content(yaml_parser_t *parser, yaml_event_t *event); + +static int +yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event); + +static int +yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, + int block, int indentless_sequence); + +static int +yaml_parser_parse_block_sequence_entry(yaml_parser_t *parser, + yaml_event_t *event, int first); + +static int +yaml_parser_parse_indentless_sequence_entry(yaml_parser_t *parser, + yaml_event_t *event); + +static int +yaml_parser_parse_block_mapping_key(yaml_parser_t *parser, + yaml_event_t *event, int first); + +static int +yaml_parser_parse_block_mapping_value(yaml_parser_t *parser, + yaml_event_t *event); + +static int +yaml_parser_parse_flow_sequence_entry(yaml_parser_t *parser, + yaml_event_t *event, int first); + +static int +yaml_parser_parse_flow_sequence_entry_mapping_key(yaml_parser_t *parser, + yaml_event_t *event); + +static int +yaml_parser_parse_flow_sequence_entry_mapping_value(yaml_parser_t *parser, + yaml_event_t *event); + +static int +yaml_parser_parse_flow_sequence_entry_mapping_end(yaml_parser_t *parser, + yaml_event_t *event); + +static int +yaml_parser_parse_flow_mapping_key(yaml_parser_t *parser, + yaml_event_t *event, int first); + +static int +yaml_parser_parse_flow_mapping_value(yaml_parser_t *parser, + yaml_event_t *event, int empty); + +/* + * Utility functions. + */ + +static int +yaml_parser_process_empty_scalar(yaml_parser_t *parser, + yaml_event_t *event, yaml_mark_t mark); + +static int +yaml_parser_process_directives(yaml_parser_t *parser, + yaml_version_directive_t **version_directive_ref, + yaml_tag_directive_t **tag_directives_start_ref, + yaml_tag_directive_t **tag_directives_end_ref); + +static int +yaml_parser_append_tag_directive(yaml_parser_t *parser, + yaml_tag_directive_t value, int allow_duplicates, yaml_mark_t mark); + +/* + * Get the next event. + */ + +YAML_DECLARE(int) +yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event) +{ + assert(parser); /* Non-NULL parser object is expected. */ + assert(event); /* Non-NULL event object is expected. */ + + /* Erase the event object. */ + + memset(event, 0, sizeof(yaml_event_t)); + + /* No events after the end of the stream or error. */ + + if (parser->stream_end_produced || parser->error || + parser->state == YAML_PARSE_END_STATE) { + return 1; + } + + /* Generate the next event. */ + + return yaml_parser_state_machine(parser, event); +} + +/* + * Set parser error. + */ + +static int +yaml_parser_set_parser_error(yaml_parser_t *parser, + const char *problem, yaml_mark_t problem_mark) +{ + parser->error = YAML_PARSER_ERROR; + parser->problem = problem; + parser->problem_mark = problem_mark; + + return 0; +} + +static int +yaml_parser_set_parser_error_context(yaml_parser_t *parser, + const char *context, yaml_mark_t context_mark, + const char *problem, yaml_mark_t problem_mark) +{ + parser->error = YAML_PARSER_ERROR; + parser->context = context; + parser->context_mark = context_mark; + parser->problem = problem; + parser->problem_mark = problem_mark; + + return 0; +} + + +/* + * State dispatcher. + */ + +static int +yaml_parser_state_machine(yaml_parser_t *parser, yaml_event_t *event) +{ + switch (parser->state) + { + case YAML_PARSE_STREAM_START_STATE: + return yaml_parser_parse_stream_start(parser, event); + + case YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, 1); + + case YAML_PARSE_DOCUMENT_START_STATE: + return yaml_parser_parse_document_start(parser, event, 0); + + case YAML_PARSE_DOCUMENT_CONTENT_STATE: + return yaml_parser_parse_document_content(parser, event); + + case YAML_PARSE_DOCUMENT_END_STATE: + return yaml_parser_parse_document_end(parser, event); + + case YAML_PARSE_BLOCK_NODE_STATE: + return yaml_parser_parse_node(parser, event, 1, 0); + + case YAML_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE: + return yaml_parser_parse_node(parser, event, 1, 1); + + case YAML_PARSE_FLOW_NODE_STATE: + return yaml_parser_parse_node(parser, event, 0, 0); + + case YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, 1); + + case YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_block_sequence_entry(parser, event, 0); + + case YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_indentless_sequence_entry(parser, event); + + case YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, 1); + + case YAML_PARSE_BLOCK_MAPPING_KEY_STATE: + return yaml_parser_parse_block_mapping_key(parser, event, 0); + + case YAML_PARSE_BLOCK_MAPPING_VALUE_STATE: + return yaml_parser_parse_block_mapping_value(parser, event); + + case YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, 1); + + case YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE: + return yaml_parser_parse_flow_sequence_entry(parser, event, 0); + + case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_key(parser, event); + + case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_value(parser, event); + + case YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE: + return yaml_parser_parse_flow_sequence_entry_mapping_end(parser, event); + + case YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, 1); + + case YAML_PARSE_FLOW_MAPPING_KEY_STATE: + return yaml_parser_parse_flow_mapping_key(parser, event, 0); + + case YAML_PARSE_FLOW_MAPPING_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, 0); + + case YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE: + return yaml_parser_parse_flow_mapping_value(parser, event, 1); + + default: + assert(1); /* Invalid state. */ + } + + return 0; +} + +/* + * Parse the production: + * stream ::= STREAM-START implicit_document? explicit_document* STREAM-END + * ************ + */ + +static int +yaml_parser_parse_stream_start(yaml_parser_t *parser, yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type != YAML_STREAM_START_TOKEN) { + return yaml_parser_set_parser_error(parser, + "did not find expected ", token->start_mark); + } + + parser->state = YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE; + STREAM_START_EVENT_INIT(*event, token->data.stream_start.encoding, + token->start_mark, token->start_mark); + SKIP_TOKEN(parser); + + return 1; +} + +/* + * Parse the productions: + * implicit_document ::= block_node DOCUMENT-END* + * * + * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + * ************************* + */ + +static int +yaml_parser_parse_document_start(yaml_parser_t *parser, yaml_event_t *event, + int implicit) +{ + yaml_token_t *token; + yaml_version_directive_t *version_directive = NULL; + struct { + yaml_tag_directive_t *start; + yaml_tag_directive_t *end; + } tag_directives = { NULL, NULL }; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + /* Parse extra document end indicators. */ + + if (!implicit) + { + while (token->type == YAML_DOCUMENT_END_TOKEN) { + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + } + } + + /* Parse an implicit document. */ + + if (implicit && token->type != YAML_VERSION_DIRECTIVE_TOKEN && + token->type != YAML_TAG_DIRECTIVE_TOKEN && + token->type != YAML_DOCUMENT_START_TOKEN && + token->type != YAML_STREAM_END_TOKEN) + { + if (!yaml_parser_process_directives(parser, NULL, NULL, NULL)) + return 0; + if (!PUSH(parser, parser->states, YAML_PARSE_DOCUMENT_END_STATE)) + return 0; + parser->state = YAML_PARSE_BLOCK_NODE_STATE; + DOCUMENT_START_EVENT_INIT(*event, NULL, NULL, NULL, 1, + token->start_mark, token->start_mark); + return 1; + } + + /* Parse an explicit document. */ + + else if (token->type != YAML_STREAM_END_TOKEN) + { + yaml_mark_t start_mark, end_mark; + start_mark = token->start_mark; + if (!yaml_parser_process_directives(parser, &version_directive, + &tag_directives.start, &tag_directives.end)) + return 0; + token = PEEK_TOKEN(parser); + if (!token) goto error; + if (token->type != YAML_DOCUMENT_START_TOKEN) { + yaml_parser_set_parser_error(parser, + "did not find expected ", token->start_mark); + goto error; + } + if (!PUSH(parser, parser->states, YAML_PARSE_DOCUMENT_END_STATE)) + goto error; + parser->state = YAML_PARSE_DOCUMENT_CONTENT_STATE; + end_mark = token->end_mark; + DOCUMENT_START_EVENT_INIT(*event, version_directive, + tag_directives.start, tag_directives.end, 0, + start_mark, end_mark); + SKIP_TOKEN(parser); + version_directive = NULL; + tag_directives.start = tag_directives.end = NULL; + return 1; + } + + /* Parse the stream end. */ + + else + { + parser->state = YAML_PARSE_END_STATE; + STREAM_END_EVENT_INIT(*event, token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; + } + +error: + yaml_free(version_directive); + while (tag_directives.start != tag_directives.end) { + yaml_free(tag_directives.end[-1].handle); + yaml_free(tag_directives.end[-1].prefix); + tag_directives.end --; + } + yaml_free(tag_directives.start); + return 0; +} + +/* + * Parse the productions: + * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + * *********** + */ + +static int +yaml_parser_parse_document_content(yaml_parser_t *parser, yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_VERSION_DIRECTIVE_TOKEN || + token->type == YAML_TAG_DIRECTIVE_TOKEN || + token->type == YAML_DOCUMENT_START_TOKEN || + token->type == YAML_DOCUMENT_END_TOKEN || + token->type == YAML_STREAM_END_TOKEN) { + parser->state = POP(parser, parser->states); + return yaml_parser_process_empty_scalar(parser, event, + token->start_mark); + } + else { + return yaml_parser_parse_node(parser, event, 1, 0); + } +} + +/* + * Parse the productions: + * implicit_document ::= block_node DOCUMENT-END* + * ************* + * explicit_document ::= DIRECTIVE* DOCUMENT-START block_node? DOCUMENT-END* + * ************* + */ + +static int +yaml_parser_parse_document_end(yaml_parser_t *parser, yaml_event_t *event) +{ + yaml_token_t *token; + yaml_mark_t start_mark, end_mark; + int implicit = 1; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + start_mark = end_mark = token->start_mark; + + if (token->type == YAML_DOCUMENT_END_TOKEN) { + end_mark = token->end_mark; + SKIP_TOKEN(parser); + implicit = 0; + } + + while (!STACK_EMPTY(parser, parser->tag_directives)) { + yaml_tag_directive_t tag_directive = POP(parser, parser->tag_directives); + yaml_free(tag_directive.handle); + yaml_free(tag_directive.prefix); + } + + parser->state = YAML_PARSE_DOCUMENT_START_STATE; + DOCUMENT_END_EVENT_INIT(*event, implicit, start_mark, end_mark); + + return 1; +} + +/* + * Parse the productions: + * block_node_or_indentless_sequence ::= + * ALIAS + * ***** + * | properties (block_content | indentless_block_sequence)? + * ********** * + * | block_content | indentless_block_sequence + * * + * block_node ::= ALIAS + * ***** + * | properties block_content? + * ********** * + * | block_content + * * + * flow_node ::= ALIAS + * ***** + * | properties flow_content? + * ********** * + * | flow_content + * * + * properties ::= TAG ANCHOR? | ANCHOR TAG? + * ************************* + * block_content ::= block_collection | flow_collection | SCALAR + * ****** + * flow_content ::= flow_collection | SCALAR + * ****** + */ + +static int +yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, + int block, int indentless_sequence) +{ + yaml_token_t *token; + yaml_char_t *anchor = NULL; + yaml_char_t *tag_handle = NULL; + yaml_char_t *tag_suffix = NULL; + yaml_char_t *tag = NULL; + yaml_mark_t start_mark, end_mark, tag_mark; + int implicit; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_ALIAS_TOKEN) + { + parser->state = POP(parser, parser->states); + ALIAS_EVENT_INIT(*event, token->data.alias.value, + token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; + } + + else + { + start_mark = end_mark = token->start_mark; + + if (token->type == YAML_ANCHOR_TOKEN) + { + anchor = token->data.anchor.value; + start_mark = token->start_mark; + end_mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) goto error; + if (token->type == YAML_TAG_TOKEN) + { + tag_handle = token->data.tag.handle; + tag_suffix = token->data.tag.suffix; + tag_mark = token->start_mark; + end_mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) goto error; + } + } + else if (token->type == YAML_TAG_TOKEN) + { + tag_handle = token->data.tag.handle; + tag_suffix = token->data.tag.suffix; + start_mark = tag_mark = token->start_mark; + end_mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) goto error; + if (token->type == YAML_ANCHOR_TOKEN) + { + anchor = token->data.anchor.value; + end_mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) goto error; + } + } + + if (tag_handle) { + if (!*tag_handle) { + tag = tag_suffix; + yaml_free(tag_handle); + tag_handle = tag_suffix = NULL; + } + else { + yaml_tag_directive_t *tag_directive; + for (tag_directive = parser->tag_directives.start; + tag_directive != parser->tag_directives.top; + tag_directive ++) { + if (strcmp((char *)tag_directive->handle, (char *)tag_handle) == 0) { + size_t prefix_len = strlen((char *)tag_directive->prefix); + size_t suffix_len = strlen((char *)tag_suffix); + tag = YAML_MALLOC(prefix_len+suffix_len+1); + if (!tag) { + parser->error = YAML_MEMORY_ERROR; + goto error; + } + memcpy(tag, tag_directive->prefix, prefix_len); + memcpy(tag+prefix_len, tag_suffix, suffix_len); + tag[prefix_len+suffix_len] = '\0'; + yaml_free(tag_handle); + yaml_free(tag_suffix); + tag_handle = tag_suffix = NULL; + break; + } + } + if (!tag) { + yaml_parser_set_parser_error_context(parser, + "while parsing a node", start_mark, + "found undefined tag handle", tag_mark); + goto error; + } + } + } + + implicit = (!tag || !*tag); + if (indentless_sequence && token->type == YAML_BLOCK_ENTRY_TOKEN) { + end_mark = token->end_mark; + parser->state = YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE; + SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, + YAML_BLOCK_SEQUENCE_STYLE, start_mark, end_mark); + return 1; + } + else { + if (token->type == YAML_SCALAR_TOKEN) { + int plain_implicit = 0; + int quoted_implicit = 0; + end_mark = token->end_mark; + if ((token->data.scalar.style == YAML_PLAIN_SCALAR_STYLE && !tag) + || (tag && strcmp((char *)tag, "!") == 0)) { + plain_implicit = 1; + } + else if (!tag) { + quoted_implicit = 1; + } + parser->state = POP(parser, parser->states); + SCALAR_EVENT_INIT(*event, anchor, tag, + token->data.scalar.value, token->data.scalar.length, + plain_implicit, quoted_implicit, + token->data.scalar.style, start_mark, end_mark); + SKIP_TOKEN(parser); + return 1; + } + else if (token->type == YAML_FLOW_SEQUENCE_START_TOKEN) { + end_mark = token->end_mark; + parser->state = YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE; + SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, + YAML_FLOW_SEQUENCE_STYLE, start_mark, end_mark); + return 1; + } + else if (token->type == YAML_FLOW_MAPPING_START_TOKEN) { + end_mark = token->end_mark; + parser->state = YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE; + MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, + YAML_FLOW_MAPPING_STYLE, start_mark, end_mark); + return 1; + } + else if (block && token->type == YAML_BLOCK_SEQUENCE_START_TOKEN) { + end_mark = token->end_mark; + parser->state = YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE; + SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, + YAML_BLOCK_SEQUENCE_STYLE, start_mark, end_mark); + return 1; + } + else if (block && token->type == YAML_BLOCK_MAPPING_START_TOKEN) { + end_mark = token->end_mark; + parser->state = YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE; + MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, + YAML_BLOCK_MAPPING_STYLE, start_mark, end_mark); + return 1; + } + else if (anchor || tag) { + yaml_char_t *value = YAML_MALLOC(1); + if (!value) { + parser->error = YAML_MEMORY_ERROR; + goto error; + } + value[0] = '\0'; + parser->state = POP(parser, parser->states); + SCALAR_EVENT_INIT(*event, anchor, tag, value, 0, + implicit, 0, YAML_PLAIN_SCALAR_STYLE, + start_mark, end_mark); + return 1; + } + else { + yaml_parser_set_parser_error_context(parser, + (block ? "while parsing a block node" + : "while parsing a flow node"), start_mark, + "did not find expected node content", token->start_mark); + goto error; + } + } + } + +error: + yaml_free(anchor); + yaml_free(tag_handle); + yaml_free(tag_suffix); + yaml_free(tag); + + return 0; +} + +/* + * Parse the productions: + * block_sequence ::= BLOCK-SEQUENCE-START (BLOCK-ENTRY block_node?)* BLOCK-END + * ******************** *********** * ********* + */ + +static int +yaml_parser_parse_block_sequence_entry(yaml_parser_t *parser, + yaml_event_t *event, int first) +{ + yaml_token_t *token; + + if (first) { + token = PEEK_TOKEN(parser); + if (!PUSH(parser, parser->marks, token->start_mark)) + return 0; + SKIP_TOKEN(parser); + } + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_BLOCK_ENTRY_TOKEN) + { + yaml_mark_t mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_BLOCK_ENTRY_TOKEN && + token->type != YAML_BLOCK_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 1, 0); + } + else { + parser->state = YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE; + return yaml_parser_process_empty_scalar(parser, event, mark); + } + } + + else if (token->type == YAML_BLOCK_END_TOKEN) + { + parser->state = POP(parser, parser->states); + (void)POP(parser, parser->marks); + SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; + } + + else + { + return yaml_parser_set_parser_error_context(parser, + "while parsing a block collection", POP(parser, parser->marks), + "did not find expected '-' indicator", token->start_mark); + } +} + +/* + * Parse the productions: + * indentless_sequence ::= (BLOCK-ENTRY block_node?)+ + * *********** * + */ + +static int +yaml_parser_parse_indentless_sequence_entry(yaml_parser_t *parser, + yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_BLOCK_ENTRY_TOKEN) + { + yaml_mark_t mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_BLOCK_ENTRY_TOKEN && + token->type != YAML_KEY_TOKEN && + token->type != YAML_VALUE_TOKEN && + token->type != YAML_BLOCK_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 1, 0); + } + else { + parser->state = YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE; + return yaml_parser_process_empty_scalar(parser, event, mark); + } + } + + else + { + parser->state = POP(parser, parser->states); + SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->start_mark); + return 1; + } +} + +/* + * Parse the productions: + * block_mapping ::= BLOCK-MAPPING_START + * ******************* + * ((KEY block_node_or_indentless_sequence?)? + * *** * + * (VALUE block_node_or_indentless_sequence?)?)* + * + * BLOCK-END + * ********* + */ + +static int +yaml_parser_parse_block_mapping_key(yaml_parser_t *parser, + yaml_event_t *event, int first) +{ + yaml_token_t *token; + + if (first) { + token = PEEK_TOKEN(parser); + if (!PUSH(parser, parser->marks, token->start_mark)) + return 0; + SKIP_TOKEN(parser); + } + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_KEY_TOKEN) + { + yaml_mark_t mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_KEY_TOKEN && + token->type != YAML_VALUE_TOKEN && + token->type != YAML_BLOCK_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_BLOCK_MAPPING_VALUE_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 1, 1); + } + else { + parser->state = YAML_PARSE_BLOCK_MAPPING_VALUE_STATE; + return yaml_parser_process_empty_scalar(parser, event, mark); + } + } + + else if (token->type == YAML_BLOCK_END_TOKEN) + { + parser->state = POP(parser, parser->states); + (void)POP(parser, parser->marks); + MAPPING_END_EVENT_INIT(*event, token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; + } + + else + { + return yaml_parser_set_parser_error_context(parser, + "while parsing a block mapping", POP(parser, parser->marks), + "did not find expected key", token->start_mark); + } +} + +/* + * Parse the productions: + * block_mapping ::= BLOCK-MAPPING_START + * + * ((KEY block_node_or_indentless_sequence?)? + * + * (VALUE block_node_or_indentless_sequence?)?)* + * ***** * + * BLOCK-END + * + */ + +static int +yaml_parser_parse_block_mapping_value(yaml_parser_t *parser, + yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_VALUE_TOKEN) + { + yaml_mark_t mark = token->end_mark; + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_KEY_TOKEN && + token->type != YAML_VALUE_TOKEN && + token->type != YAML_BLOCK_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_BLOCK_MAPPING_KEY_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 1, 1); + } + else { + parser->state = YAML_PARSE_BLOCK_MAPPING_KEY_STATE; + return yaml_parser_process_empty_scalar(parser, event, mark); + } + } + + else + { + parser->state = YAML_PARSE_BLOCK_MAPPING_KEY_STATE; + return yaml_parser_process_empty_scalar(parser, event, token->start_mark); + } +} + +/* + * Parse the productions: + * flow_sequence ::= FLOW-SEQUENCE-START + * ******************* + * (flow_sequence_entry FLOW-ENTRY)* + * * ********** + * flow_sequence_entry? + * * + * FLOW-SEQUENCE-END + * ***************** + * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * * + */ + +static int +yaml_parser_parse_flow_sequence_entry(yaml_parser_t *parser, + yaml_event_t *event, int first) +{ + yaml_token_t *token; + + if (first) { + token = PEEK_TOKEN(parser); + if (!PUSH(parser, parser->marks, token->start_mark)) + return 0; + SKIP_TOKEN(parser); + } + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type != YAML_FLOW_SEQUENCE_END_TOKEN) + { + if (!first) { + if (token->type == YAML_FLOW_ENTRY_TOKEN) { + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + } + else { + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow sequence", POP(parser, parser->marks), + "did not find expected ',' or ']'", token->start_mark); + } + } + + if (token->type == YAML_KEY_TOKEN) { + parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE; + MAPPING_START_EVENT_INIT(*event, NULL, NULL, + 1, YAML_FLOW_MAPPING_STYLE, + token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; + } + + else if (token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 0, 0); + } + } + + parser->state = POP(parser, parser->states); + (void)POP(parser, parser->marks); + SEQUENCE_END_EVENT_INIT(*event, token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; +} + +/* + * Parse the productions: + * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * *** * + */ + +static int +yaml_parser_parse_flow_sequence_entry_mapping_key(yaml_parser_t *parser, + yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type != YAML_VALUE_TOKEN && token->type != YAML_FLOW_ENTRY_TOKEN + && token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 0, 0); + } + else { + yaml_mark_t mark = token->end_mark; + SKIP_TOKEN(parser); + parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE; + return yaml_parser_process_empty_scalar(parser, event, mark); + } +} + +/* + * Parse the productions: + * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * ***** * + */ + +static int +yaml_parser_parse_flow_sequence_entry_mapping_value(yaml_parser_t *parser, + yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type == YAML_VALUE_TOKEN) { + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_FLOW_ENTRY_TOKEN + && token->type != YAML_FLOW_SEQUENCE_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 0, 0); + } + } + parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE; + return yaml_parser_process_empty_scalar(parser, event, token->start_mark); +} + +/* + * Parse the productions: + * flow_sequence_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * * + */ + +static int +yaml_parser_parse_flow_sequence_entry_mapping_end(yaml_parser_t *parser, + yaml_event_t *event) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + parser->state = YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE; + + MAPPING_END_EVENT_INIT(*event, token->start_mark, token->start_mark); + return 1; +} + +/* + * Parse the productions: + * flow_mapping ::= FLOW-MAPPING-START + * ****************** + * (flow_mapping_entry FLOW-ENTRY)* + * * ********** + * flow_mapping_entry? + * ****************** + * FLOW-MAPPING-END + * **************** + * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * * *** * + */ + +static int +yaml_parser_parse_flow_mapping_key(yaml_parser_t *parser, + yaml_event_t *event, int first) +{ + yaml_token_t *token; + + if (first) { + token = PEEK_TOKEN(parser); + if (!PUSH(parser, parser->marks, token->start_mark)) + return 0; + SKIP_TOKEN(parser); + } + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (token->type != YAML_FLOW_MAPPING_END_TOKEN) + { + if (!first) { + if (token->type == YAML_FLOW_ENTRY_TOKEN) { + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + } + else { + return yaml_parser_set_parser_error_context(parser, + "while parsing a flow mapping", POP(parser, parser->marks), + "did not find expected ',' or '}'", token->start_mark); + } + } + + if (token->type == YAML_KEY_TOKEN) { + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_VALUE_TOKEN + && token->type != YAML_FLOW_ENTRY_TOKEN + && token->type != YAML_FLOW_MAPPING_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_FLOW_MAPPING_VALUE_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 0, 0); + } + else { + parser->state = YAML_PARSE_FLOW_MAPPING_VALUE_STATE; + return yaml_parser_process_empty_scalar(parser, event, + token->start_mark); + } + } + else if (token->type != YAML_FLOW_MAPPING_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 0, 0); + } + } + + parser->state = POP(parser, parser->states); + (void)POP(parser, parser->marks); + MAPPING_END_EVENT_INIT(*event, token->start_mark, token->end_mark); + SKIP_TOKEN(parser); + return 1; +} + +/* + * Parse the productions: + * flow_mapping_entry ::= flow_node | KEY flow_node? (VALUE flow_node?)? + * * ***** * + */ + +static int +yaml_parser_parse_flow_mapping_value(yaml_parser_t *parser, + yaml_event_t *event, int empty) +{ + yaml_token_t *token; + + token = PEEK_TOKEN(parser); + if (!token) return 0; + + if (empty) { + parser->state = YAML_PARSE_FLOW_MAPPING_KEY_STATE; + return yaml_parser_process_empty_scalar(parser, event, + token->start_mark); + } + + if (token->type == YAML_VALUE_TOKEN) { + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) return 0; + if (token->type != YAML_FLOW_ENTRY_TOKEN + && token->type != YAML_FLOW_MAPPING_END_TOKEN) { + if (!PUSH(parser, parser->states, + YAML_PARSE_FLOW_MAPPING_KEY_STATE)) + return 0; + return yaml_parser_parse_node(parser, event, 0, 0); + } + } + + parser->state = YAML_PARSE_FLOW_MAPPING_KEY_STATE; + return yaml_parser_process_empty_scalar(parser, event, token->start_mark); +} + +/* + * Generate an empty scalar event. + */ + +static int +yaml_parser_process_empty_scalar(yaml_parser_t *parser, yaml_event_t *event, + yaml_mark_t mark) +{ + yaml_char_t *value; + + value = YAML_MALLOC(1); + if (!value) { + parser->error = YAML_MEMORY_ERROR; + return 0; + } + value[0] = '\0'; + + SCALAR_EVENT_INIT(*event, NULL, NULL, value, 0, + 1, 0, YAML_PLAIN_SCALAR_STYLE, mark, mark); + + return 1; +} + +/* + * Parse directives. + */ + +static int +yaml_parser_process_directives(yaml_parser_t *parser, + yaml_version_directive_t **version_directive_ref, + yaml_tag_directive_t **tag_directives_start_ref, + yaml_tag_directive_t **tag_directives_end_ref) +{ + yaml_tag_directive_t default_tag_directives[] = { + {(yaml_char_t *)"!", (yaml_char_t *)"!"}, + {(yaml_char_t *)"!!", (yaml_char_t *)"tag:yaml.org,2002:"}, + {NULL, NULL} + }; + yaml_tag_directive_t *default_tag_directive; + yaml_version_directive_t *version_directive = NULL; + struct { + yaml_tag_directive_t *start; + yaml_tag_directive_t *end; + yaml_tag_directive_t *top; + } tag_directives = { NULL, NULL, NULL }; + yaml_token_t *token; + + if (!STACK_INIT(parser, tag_directives, yaml_tag_directive_t*)) + goto error; + + token = PEEK_TOKEN(parser); + if (!token) goto error; + + while (token->type == YAML_VERSION_DIRECTIVE_TOKEN || + token->type == YAML_TAG_DIRECTIVE_TOKEN) + { + if (token->type == YAML_VERSION_DIRECTIVE_TOKEN) { + if (version_directive) { + yaml_parser_set_parser_error(parser, + "found duplicate %YAML directive", token->start_mark); + goto error; + } + if (token->data.version_directive.major != 1 + || ( + token->data.version_directive.minor != 1 + && token->data.version_directive.minor != 2 + )) { + yaml_parser_set_parser_error(parser, + "found incompatible YAML document", token->start_mark); + goto error; + } + version_directive = YAML_MALLOC_STATIC(yaml_version_directive_t); + if (!version_directive) { + parser->error = YAML_MEMORY_ERROR; + goto error; + } + version_directive->major = token->data.version_directive.major; + version_directive->minor = token->data.version_directive.minor; + } + + else if (token->type == YAML_TAG_DIRECTIVE_TOKEN) { + yaml_tag_directive_t value; + value.handle = token->data.tag_directive.handle; + value.prefix = token->data.tag_directive.prefix; + + if (!yaml_parser_append_tag_directive(parser, value, 0, + token->start_mark)) + goto error; + if (!PUSH(parser, tag_directives, value)) + goto error; + } + + SKIP_TOKEN(parser); + token = PEEK_TOKEN(parser); + if (!token) goto error; + } + + for (default_tag_directive = default_tag_directives; + default_tag_directive->handle; default_tag_directive++) { + if (!yaml_parser_append_tag_directive(parser, *default_tag_directive, 1, + token->start_mark)) + goto error; + } + + if (version_directive_ref) { + *version_directive_ref = version_directive; + } + if (tag_directives_start_ref) { + if (STACK_EMPTY(parser, tag_directives)) { + *tag_directives_start_ref = *tag_directives_end_ref = NULL; + STACK_DEL(parser, tag_directives); + } + else { + *tag_directives_start_ref = tag_directives.start; + *tag_directives_end_ref = tag_directives.top; + } + } + else { + STACK_DEL(parser, tag_directives); + } + + if (!version_directive_ref) + yaml_free(version_directive); + return 1; + +error: + yaml_free(version_directive); + while (!STACK_EMPTY(parser, tag_directives)) { + yaml_tag_directive_t tag_directive = POP(parser, tag_directives); + yaml_free(tag_directive.handle); + yaml_free(tag_directive.prefix); + } + STACK_DEL(parser, tag_directives); + return 0; +} + +/* + * Append a tag directive to the directives stack. + */ + +static int +yaml_parser_append_tag_directive(yaml_parser_t *parser, + yaml_tag_directive_t value, int allow_duplicates, yaml_mark_t mark) +{ + yaml_tag_directive_t *tag_directive; + yaml_tag_directive_t copy = { NULL, NULL }; + + for (tag_directive = parser->tag_directives.start; + tag_directive != parser->tag_directives.top; tag_directive ++) { + if (strcmp((char *)value.handle, (char *)tag_directive->handle) == 0) { + if (allow_duplicates) + return 1; + return yaml_parser_set_parser_error(parser, + "found duplicate %TAG directive", mark); + } + } + + copy.handle = yaml_strdup(value.handle); + copy.prefix = yaml_strdup(value.prefix); + if (!copy.handle || !copy.prefix) { + parser->error = YAML_MEMORY_ERROR; + goto error; + } + + if (!PUSH(parser, parser->tag_directives, copy)) + goto error; + + return 1; + +error: + yaml_free(copy.handle); + yaml_free(copy.prefix); + return 0; +} + diff --git a/src/vendors/libyaml/reader.c b/src/vendors/libyaml/reader.c new file mode 100644 index 0000000..ce1b087 --- /dev/null +++ b/src/vendors/libyaml/reader.c @@ -0,0 +1,469 @@ + +#include "yaml_private.h" + +/* + * Declarations. + */ + +static int +yaml_parser_set_reader_error(yaml_parser_t *parser, const char *problem, + size_t offset, int value); + +static int +yaml_parser_update_raw_buffer(yaml_parser_t *parser); + +static int +yaml_parser_determine_encoding(yaml_parser_t *parser); + +YAML_DECLARE(int) +yaml_parser_update_buffer(yaml_parser_t *parser, size_t length); + +/* + * Set the reader error and return 0. + */ + +static int +yaml_parser_set_reader_error(yaml_parser_t *parser, const char *problem, + size_t offset, int value) +{ + parser->error = YAML_READER_ERROR; + parser->problem = problem; + parser->problem_offset = offset; + parser->problem_value = value; + + return 0; +} + +/* + * Byte order marks. + */ + +#define BOM_UTF8 "\xef\xbb\xbf" +#define BOM_UTF16LE "\xff\xfe" +#define BOM_UTF16BE "\xfe\xff" + +/* + * Determine the input stream encoding by checking the BOM symbol. If no BOM is + * found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure. + */ + +static int +yaml_parser_determine_encoding(yaml_parser_t *parser) +{ + /* Ensure that we had enough bytes in the raw buffer. */ + + while (!parser->eof + && parser->raw_buffer.last - parser->raw_buffer.pointer < 3) { + if (!yaml_parser_update_raw_buffer(parser)) { + return 0; + } + } + + /* Determine the encoding. */ + + if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 2 + && !memcmp(parser->raw_buffer.pointer, BOM_UTF16LE, 2)) { + parser->encoding = YAML_UTF16LE_ENCODING; + parser->raw_buffer.pointer += 2; + parser->offset += 2; + } + else if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 2 + && !memcmp(parser->raw_buffer.pointer, BOM_UTF16BE, 2)) { + parser->encoding = YAML_UTF16BE_ENCODING; + parser->raw_buffer.pointer += 2; + parser->offset += 2; + } + else if (parser->raw_buffer.last - parser->raw_buffer.pointer >= 3 + && !memcmp(parser->raw_buffer.pointer, BOM_UTF8, 3)) { + parser->encoding = YAML_UTF8_ENCODING; + parser->raw_buffer.pointer += 3; + parser->offset += 3; + } + else { + parser->encoding = YAML_UTF8_ENCODING; + } + + return 1; +} + +/* + * Update the raw buffer. + */ + +static int +yaml_parser_update_raw_buffer(yaml_parser_t *parser) +{ + size_t size_read = 0; + + /* Return if the raw buffer is full. */ + + if (parser->raw_buffer.start == parser->raw_buffer.pointer + && parser->raw_buffer.last == parser->raw_buffer.end) + return 1; + + /* Return on EOF. */ + + if (parser->eof) return 1; + + /* Move the remaining bytes in the raw buffer to the beginning. */ + + if (parser->raw_buffer.start < parser->raw_buffer.pointer + && parser->raw_buffer.pointer < parser->raw_buffer.last) { + memmove(parser->raw_buffer.start, parser->raw_buffer.pointer, + parser->raw_buffer.last - parser->raw_buffer.pointer); + } + parser->raw_buffer.last -= + parser->raw_buffer.pointer - parser->raw_buffer.start; + parser->raw_buffer.pointer = parser->raw_buffer.start; + + /* Call the read handler to fill the buffer. */ + + if (!parser->read_handler(parser->read_handler_data, parser->raw_buffer.last, + parser->raw_buffer.end - parser->raw_buffer.last, &size_read)) { + return yaml_parser_set_reader_error(parser, "input error", + parser->offset, -1); + } + parser->raw_buffer.last += size_read; + if (!size_read) { + parser->eof = 1; + } + + return 1; +} + +/* + * Ensure that the buffer contains at least `length` characters. + * Return 1 on success, 0 on failure. + * + * The length is supposed to be significantly less that the buffer size. + */ + +YAML_DECLARE(int) +yaml_parser_update_buffer(yaml_parser_t *parser, size_t length) +{ + int first = 1; + + assert(parser->read_handler); /* Read handler must be set. */ + + /* If the EOF flag is set and the raw buffer is empty, do nothing. */ + + if (parser->eof && parser->raw_buffer.pointer == parser->raw_buffer.last) + return 1; + + /* Return if the buffer contains enough characters. */ + + if (parser->unread >= length) + return 1; + + /* Determine the input encoding if it is not known yet. */ + + if (!parser->encoding) { + if (!yaml_parser_determine_encoding(parser)) + return 0; + } + + /* Move the unread characters to the beginning of the buffer. */ + + if (parser->buffer.start < parser->buffer.pointer + && parser->buffer.pointer < parser->buffer.last) { + size_t size = parser->buffer.last - parser->buffer.pointer; + memmove(parser->buffer.start, parser->buffer.pointer, size); + parser->buffer.pointer = parser->buffer.start; + parser->buffer.last = parser->buffer.start + size; + } + else if (parser->buffer.pointer == parser->buffer.last) { + parser->buffer.pointer = parser->buffer.start; + parser->buffer.last = parser->buffer.start; + } + + /* Fill the buffer until it has enough characters. */ + + while (parser->unread < length) + { + /* Fill the raw buffer if necessary. */ + + if (!first || parser->raw_buffer.pointer == parser->raw_buffer.last) { + if (!yaml_parser_update_raw_buffer(parser)) return 0; + } + first = 0; + + /* Decode the raw buffer. */ + + while (parser->raw_buffer.pointer != parser->raw_buffer.last) + { + unsigned int value = 0, value2 = 0; + int incomplete = 0; + unsigned char octet; + unsigned int width = 0; + int low, high; + size_t k; + size_t raw_unread = parser->raw_buffer.last - parser->raw_buffer.pointer; + + /* Decode the next character. */ + + switch (parser->encoding) + { + case YAML_UTF8_ENCODING: + + /* + * Decode a UTF-8 character. Check RFC 3629 + * (http://www.ietf.org/rfc/rfc3629.txt) for more details. + * + * The following table (taken from the RFC) is used for + * decoding. + * + * Char. number range | UTF-8 octet sequence + * (hexadecimal) | (binary) + * --------------------+------------------------------------ + * 0000 0000-0000 007F | 0xxxxxxx + * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + * + * Additionally, the characters in the range 0xD800-0xDFFF + * are prohibited as they are reserved for use with UTF-16 + * surrogate pairs. + */ + + /* Determine the length of the UTF-8 sequence. */ + + octet = parser->raw_buffer.pointer[0]; + width = (octet & 0x80) == 0x00 ? 1 : + (octet & 0xE0) == 0xC0 ? 2 : + (octet & 0xF0) == 0xE0 ? 3 : + (octet & 0xF8) == 0xF0 ? 4 : 0; + + /* Check if the leading octet is valid. */ + + if (!width) + return yaml_parser_set_reader_error(parser, + "invalid leading UTF-8 octet", + parser->offset, octet); + + /* Check if the raw buffer contains an incomplete character. */ + + if (width > raw_unread) { + if (parser->eof) { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-8 octet sequence", + parser->offset, -1); + } + incomplete = 1; + break; + } + + /* Decode the leading octet. */ + + value = (octet & 0x80) == 0x00 ? octet & 0x7F : + (octet & 0xE0) == 0xC0 ? octet & 0x1F : + (octet & 0xF0) == 0xE0 ? octet & 0x0F : + (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; + + /* Check and decode the trailing octets. */ + + for (k = 1; k < width; k ++) + { + octet = parser->raw_buffer.pointer[k]; + + /* Check if the octet is valid. */ + + if ((octet & 0xC0) != 0x80) + return yaml_parser_set_reader_error(parser, + "invalid trailing UTF-8 octet", + parser->offset+k, octet); + + /* Decode the octet. */ + + value = (value << 6) + (octet & 0x3F); + } + + /* Check the length of the sequence against the value. */ + + if (!((width == 1) || + (width == 2 && value >= 0x80) || + (width == 3 && value >= 0x800) || + (width == 4 && value >= 0x10000))) + return yaml_parser_set_reader_error(parser, + "invalid length of a UTF-8 sequence", + parser->offset, -1); + + /* Check the range of the value. */ + + if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) + return yaml_parser_set_reader_error(parser, + "invalid Unicode character", + parser->offset, value); + + break; + + case YAML_UTF16LE_ENCODING: + case YAML_UTF16BE_ENCODING: + + low = (parser->encoding == YAML_UTF16LE_ENCODING ? 0 : 1); + high = (parser->encoding == YAML_UTF16LE_ENCODING ? 1 : 0); + + /* + * The UTF-16 encoding is not as simple as one might + * naively think. Check RFC 2781 + * (http://www.ietf.org/rfc/rfc2781.txt). + * + * Normally, two subsequent bytes describe a Unicode + * character. However a special technique (called a + * surrogate pair) is used for specifying character + * values larger than 0xFFFF. + * + * A surrogate pair consists of two pseudo-characters: + * high surrogate area (0xD800-0xDBFF) + * low surrogate area (0xDC00-0xDFFF) + * + * The following formulas are used for decoding + * and encoding characters using surrogate pairs: + * + * U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF) + * U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF) + * W1 = 110110yyyyyyyyyy + * W2 = 110111xxxxxxxxxx + * + * where U is the character value, W1 is the high surrogate + * area, W2 is the low surrogate area. + */ + + /* Check for incomplete UTF-16 character. */ + + if (raw_unread < 2) { + if (parser->eof) { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 character", + parser->offset, -1); + } + incomplete = 1; + break; + } + + /* Get the character. */ + + value = parser->raw_buffer.pointer[low] + + (parser->raw_buffer.pointer[high] << 8); + + /* Check for unexpected low surrogate area. */ + + if ((value & 0xFC00) == 0xDC00) + return yaml_parser_set_reader_error(parser, + "unexpected low surrogate area", + parser->offset, value); + + /* Check for a high surrogate area. */ + + if ((value & 0xFC00) == 0xD800) { + + width = 4; + + /* Check for incomplete surrogate pair. */ + + if (raw_unread < 4) { + if (parser->eof) { + return yaml_parser_set_reader_error(parser, + "incomplete UTF-16 surrogate pair", + parser->offset, -1); + } + incomplete = 1; + break; + } + + /* Get the next character. */ + + value2 = parser->raw_buffer.pointer[low+2] + + (parser->raw_buffer.pointer[high+2] << 8); + + /* Check for a low surrogate area. */ + + if ((value2 & 0xFC00) != 0xDC00) + return yaml_parser_set_reader_error(parser, + "expected low surrogate area", + parser->offset+2, value2); + + /* Generate the value of the surrogate pair. */ + + value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF); + } + + else { + width = 2; + } + + break; + + default: + assert(1); /* Impossible. */ + } + + /* Check if the raw buffer contains enough bytes to form a character. */ + + if (incomplete) break; + + /* + * Check if the character is in the allowed range: + * #x9 | #xA | #xD | [#x20-#x7E] (8 bit) + * | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit) + * | [#x10000-#x10FFFF] (32 bit) + */ + + if (! (value == 0x09 || value == 0x0A || value == 0x0D + || (value >= 0x20 && value <= 0x7E) + || (value == 0x85) || (value >= 0xA0 && value <= 0xD7FF) + || (value >= 0xE000 && value <= 0xFFFD) + || (value >= 0x10000 && value <= 0x10FFFF))) + return yaml_parser_set_reader_error(parser, + "control characters are not allowed", + parser->offset, value); + + /* Move the raw pointers. */ + + parser->raw_buffer.pointer += width; + parser->offset += width; + + /* Finally put the character into the buffer. */ + + /* 0000 0000-0000 007F -> 0xxxxxxx */ + if (value <= 0x7F) { + *(parser->buffer.last++) = value; + } + /* 0000 0080-0000 07FF -> 110xxxxx 10xxxxxx */ + else if (value <= 0x7FF) { + *(parser->buffer.last++) = 0xC0 + (value >> 6); + *(parser->buffer.last++) = 0x80 + (value & 0x3F); + } + /* 0000 0800-0000 FFFF -> 1110xxxx 10xxxxxx 10xxxxxx */ + else if (value <= 0xFFFF) { + *(parser->buffer.last++) = 0xE0 + (value >> 12); + *(parser->buffer.last++) = 0x80 + ((value >> 6) & 0x3F); + *(parser->buffer.last++) = 0x80 + (value & 0x3F); + } + /* 0001 0000-0010 FFFF -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + else { + *(parser->buffer.last++) = 0xF0 + (value >> 18); + *(parser->buffer.last++) = 0x80 + ((value >> 12) & 0x3F); + *(parser->buffer.last++) = 0x80 + ((value >> 6) & 0x3F); + *(parser->buffer.last++) = 0x80 + (value & 0x3F); + } + + parser->unread ++; + } + + /* On EOF, put NUL into the buffer and return. */ + + if (parser->eof) { + *(parser->buffer.last++) = '\0'; + parser->unread ++; + return 1; + } + + } + + if (parser->offset >= MAX_FILE_SIZE) { + return yaml_parser_set_reader_error(parser, "input is too long", + parser->offset, -1); + } + + return 1; +} diff --git a/src/vendors/libyaml/scanner.c b/src/vendors/libyaml/scanner.c new file mode 100644 index 0000000..ff2c076 --- /dev/null +++ b/src/vendors/libyaml/scanner.c @@ -0,0 +1,3598 @@ + +/* + * Introduction + * ************ + * + * The following notes assume that you are familiar with the YAML specification + * (http://yaml.org/spec/cvs/current.html). We mostly follow it, although in + * some cases we are less restrictive that it requires. + * + * The process of transforming a YAML stream into a sequence of events is + * divided on two steps: Scanning and Parsing. + * + * The Scanner transforms the input stream into a sequence of tokens, while the + * parser transform the sequence of tokens produced by the Scanner into a + * sequence of parsing events. + * + * The Scanner is rather clever and complicated. The Parser, on the contrary, + * is a straightforward implementation of a recursive-descendant parser (or, + * LL(1) parser, as it is usually called). + * + * Actually there are two issues of Scanning that might be called "clever", the + * rest is quite straightforward. The issues are "block collection start" and + * "simple keys". Both issues are explained below in details. + * + * Here the Scanning step is explained and implemented. We start with the list + * of all the tokens produced by the Scanner together with short descriptions. + * + * Now, tokens: + * + * STREAM-START(encoding) # The stream start. + * STREAM-END # The stream end. + * VERSION-DIRECTIVE(major,minor) # The '%YAML' directive. + * TAG-DIRECTIVE(handle,prefix) # The '%TAG' directive. + * DOCUMENT-START # '---' + * DOCUMENT-END # '...' + * BLOCK-SEQUENCE-START # Indentation increase denoting a block + * BLOCK-MAPPING-START # sequence or a block mapping. + * BLOCK-END # Indentation decrease. + * FLOW-SEQUENCE-START # '[' + * FLOW-SEQUENCE-END # ']' + * FLOW-MAPPING-START # '{' + * FLOW-MAPPING-END # '}' + * BLOCK-ENTRY # '-' + * FLOW-ENTRY # ',' + * KEY # '?' or nothing (simple keys). + * VALUE # ':' + * ALIAS(anchor) # '*anchor' + * ANCHOR(anchor) # '&anchor' + * TAG(handle,suffix) # '!handle!suffix' + * SCALAR(value,style) # A scalar. + * + * The following two tokens are "virtual" tokens denoting the beginning and the + * end of the stream: + * + * STREAM-START(encoding) + * STREAM-END + * + * We pass the information about the input stream encoding with the + * STREAM-START token. + * + * The next two tokens are responsible for tags: + * + * VERSION-DIRECTIVE(major,minor) + * TAG-DIRECTIVE(handle,prefix) + * + * Example: + * + * %YAML 1.1 + * %TAG ! !foo + * %TAG !yaml! tag:yaml.org,2002: + * --- + * + * The corresponding sequence of tokens: + * + * STREAM-START(utf-8) + * VERSION-DIRECTIVE(1,1) + * TAG-DIRECTIVE("!","!foo") + * TAG-DIRECTIVE("!yaml","tag:yaml.org,2002:") + * DOCUMENT-START + * STREAM-END + * + * Note that the VERSION-DIRECTIVE and TAG-DIRECTIVE tokens occupy a whole + * line. + * + * The document start and end indicators are represented by: + * + * DOCUMENT-START + * DOCUMENT-END + * + * Note that if a YAML stream contains an implicit document (without '---' + * and '...' indicators), no DOCUMENT-START and DOCUMENT-END tokens will be + * produced. + * + * In the following examples, we present whole documents together with the + * produced tokens. + * + * 1. An implicit document: + * + * 'a scalar' + * + * Tokens: + * + * STREAM-START(utf-8) + * SCALAR("a scalar",single-quoted) + * STREAM-END + * + * 2. An explicit document: + * + * --- + * 'a scalar' + * ... + * + * Tokens: + * + * STREAM-START(utf-8) + * DOCUMENT-START + * SCALAR("a scalar",single-quoted) + * DOCUMENT-END + * STREAM-END + * + * 3. Several documents in a stream: + * + * 'a scalar' + * --- + * 'another scalar' + * --- + * 'yet another scalar' + * + * Tokens: + * + * STREAM-START(utf-8) + * SCALAR("a scalar",single-quoted) + * DOCUMENT-START + * SCALAR("another scalar",single-quoted) + * DOCUMENT-START + * SCALAR("yet another scalar",single-quoted) + * STREAM-END + * + * We have already introduced the SCALAR token above. The following tokens are + * used to describe aliases, anchors, tag, and scalars: + * + * ALIAS(anchor) + * ANCHOR(anchor) + * TAG(handle,suffix) + * SCALAR(value,style) + * + * The following series of examples illustrate the usage of these tokens: + * + * 1. A recursive sequence: + * + * &A [ *A ] + * + * Tokens: + * + * STREAM-START(utf-8) + * ANCHOR("A") + * FLOW-SEQUENCE-START + * ALIAS("A") + * FLOW-SEQUENCE-END + * STREAM-END + * + * 2. A tagged scalar: + * + * !!float "3.14" # A good approximation. + * + * Tokens: + * + * STREAM-START(utf-8) + * TAG("!!","float") + * SCALAR("3.14",double-quoted) + * STREAM-END + * + * 3. Various scalar styles: + * + * --- # Implicit empty plain scalars do not produce tokens. + * --- a plain scalar + * --- 'a single-quoted scalar' + * --- "a double-quoted scalar" + * --- |- + * a literal scalar + * --- >- + * a folded + * scalar + * + * Tokens: + * + * STREAM-START(utf-8) + * DOCUMENT-START + * DOCUMENT-START + * SCALAR("a plain scalar",plain) + * DOCUMENT-START + * SCALAR("a single-quoted scalar",single-quoted) + * DOCUMENT-START + * SCALAR("a double-quoted scalar",double-quoted) + * DOCUMENT-START + * SCALAR("a literal scalar",literal) + * DOCUMENT-START + * SCALAR("a folded scalar",folded) + * STREAM-END + * + * Now it's time to review collection-related tokens. We will start with + * flow collections: + * + * FLOW-SEQUENCE-START + * FLOW-SEQUENCE-END + * FLOW-MAPPING-START + * FLOW-MAPPING-END + * FLOW-ENTRY + * KEY + * VALUE + * + * The tokens FLOW-SEQUENCE-START, FLOW-SEQUENCE-END, FLOW-MAPPING-START, and + * FLOW-MAPPING-END represent the indicators '[', ']', '{', and '}' + * correspondingly. FLOW-ENTRY represent the ',' indicator. Finally the + * indicators '?' and ':', which are used for denoting mapping keys and values, + * are represented by the KEY and VALUE tokens. + * + * The following examples show flow collections: + * + * 1. A flow sequence: + * + * [item 1, item 2, item 3] + * + * Tokens: + * + * STREAM-START(utf-8) + * FLOW-SEQUENCE-START + * SCALAR("item 1",plain) + * FLOW-ENTRY + * SCALAR("item 2",plain) + * FLOW-ENTRY + * SCALAR("item 3",plain) + * FLOW-SEQUENCE-END + * STREAM-END + * + * 2. A flow mapping: + * + * { + * a simple key: a value, # Note that the KEY token is produced. + * ? a complex key: another value, + * } + * + * Tokens: + * + * STREAM-START(utf-8) + * FLOW-MAPPING-START + * KEY + * SCALAR("a simple key",plain) + * VALUE + * SCALAR("a value",plain) + * FLOW-ENTRY + * KEY + * SCALAR("a complex key",plain) + * VALUE + * SCALAR("another value",plain) + * FLOW-ENTRY + * FLOW-MAPPING-END + * STREAM-END + * + * A simple key is a key which is not denoted by the '?' indicator. Note that + * the Scanner still produce the KEY token whenever it encounters a simple key. + * + * For scanning block collections, the following tokens are used (note that we + * repeat KEY and VALUE here): + * + * BLOCK-SEQUENCE-START + * BLOCK-MAPPING-START + * BLOCK-END + * BLOCK-ENTRY + * KEY + * VALUE + * + * The tokens BLOCK-SEQUENCE-START and BLOCK-MAPPING-START denote indentation + * increase that precedes a block collection (cf. the INDENT token in Python). + * The token BLOCK-END denote indentation decrease that ends a block collection + * (cf. the DEDENT token in Python). However YAML has some syntax pecularities + * that makes detections of these tokens more complex. + * + * The tokens BLOCK-ENTRY, KEY, and VALUE are used to represent the indicators + * '-', '?', and ':' correspondingly. + * + * The following examples show how the tokens BLOCK-SEQUENCE-START, + * BLOCK-MAPPING-START, and BLOCK-END are emitted by the Scanner: + * + * 1. Block sequences: + * + * - item 1 + * - item 2 + * - + * - item 3.1 + * - item 3.2 + * - + * key 1: value 1 + * key 2: value 2 + * + * Tokens: + * + * STREAM-START(utf-8) + * BLOCK-SEQUENCE-START + * BLOCK-ENTRY + * SCALAR("item 1",plain) + * BLOCK-ENTRY + * SCALAR("item 2",plain) + * BLOCK-ENTRY + * BLOCK-SEQUENCE-START + * BLOCK-ENTRY + * SCALAR("item 3.1",plain) + * BLOCK-ENTRY + * SCALAR("item 3.2",plain) + * BLOCK-END + * BLOCK-ENTRY + * BLOCK-MAPPING-START + * KEY + * SCALAR("key 1",plain) + * VALUE + * SCALAR("value 1",plain) + * KEY + * SCALAR("key 2",plain) + * VALUE + * SCALAR("value 2",plain) + * BLOCK-END + * BLOCK-END + * STREAM-END + * + * 2. Block mappings: + * + * a simple key: a value # The KEY token is produced here. + * ? a complex key + * : another value + * a mapping: + * key 1: value 1 + * key 2: value 2 + * a sequence: + * - item 1 + * - item 2 + * + * Tokens: + * + * STREAM-START(utf-8) + * BLOCK-MAPPING-START + * KEY + * SCALAR("a simple key",plain) + * VALUE + * SCALAR("a value",plain) + * KEY + * SCALAR("a complex key",plain) + * VALUE + * SCALAR("another value",plain) + * KEY + * SCALAR("a mapping",plain) + * VALUE + * BLOCK-MAPPING-START + * KEY + * SCALAR("key 1",plain) + * VALUE + * SCALAR("value 1",plain) + * KEY + * SCALAR("key 2",plain) + * VALUE + * SCALAR("value 2",plain) + * BLOCK-END + * KEY + * SCALAR("a sequence",plain) + * VALUE + * BLOCK-SEQUENCE-START + * BLOCK-ENTRY + * SCALAR("item 1",plain) + * BLOCK-ENTRY + * SCALAR("item 2",plain) + * BLOCK-END + * BLOCK-END + * STREAM-END + * + * YAML does not always require to start a new block collection from a new + * line. If the current line contains only '-', '?', and ':' indicators, a new + * block collection may start at the current line. The following examples + * illustrate this case: + * + * 1. Collections in a sequence: + * + * - - item 1 + * - item 2 + * - key 1: value 1 + * key 2: value 2 + * - ? complex key + * : complex value + * + * Tokens: + * + * STREAM-START(utf-8) + * BLOCK-SEQUENCE-START + * BLOCK-ENTRY + * BLOCK-SEQUENCE-START + * BLOCK-ENTRY + * SCALAR("item 1",plain) + * BLOCK-ENTRY + * SCALAR("item 2",plain) + * BLOCK-END + * BLOCK-ENTRY + * BLOCK-MAPPING-START + * KEY + * SCALAR("key 1",plain) + * VALUE + * SCALAR("value 1",plain) + * KEY + * SCALAR("key 2",plain) + * VALUE + * SCALAR("value 2",plain) + * BLOCK-END + * BLOCK-ENTRY + * BLOCK-MAPPING-START + * KEY + * SCALAR("complex key") + * VALUE + * SCALAR("complex value") + * BLOCK-END + * BLOCK-END + * STREAM-END + * + * 2. Collections in a mapping: + * + * ? a sequence + * : - item 1 + * - item 2 + * ? a mapping + * : key 1: value 1 + * key 2: value 2 + * + * Tokens: + * + * STREAM-START(utf-8) + * BLOCK-MAPPING-START + * KEY + * SCALAR("a sequence",plain) + * VALUE + * BLOCK-SEQUENCE-START + * BLOCK-ENTRY + * SCALAR("item 1",plain) + * BLOCK-ENTRY + * SCALAR("item 2",plain) + * BLOCK-END + * KEY + * SCALAR("a mapping",plain) + * VALUE + * BLOCK-MAPPING-START + * KEY + * SCALAR("key 1",plain) + * VALUE + * SCALAR("value 1",plain) + * KEY + * SCALAR("key 2",plain) + * VALUE + * SCALAR("value 2",plain) + * BLOCK-END + * BLOCK-END + * STREAM-END + * + * YAML also permits non-indented sequences if they are included into a block + * mapping. In this case, the token BLOCK-SEQUENCE-START is not produced: + * + * key: + * - item 1 # BLOCK-SEQUENCE-START is NOT produced here. + * - item 2 + * + * Tokens: + * + * STREAM-START(utf-8) + * BLOCK-MAPPING-START + * KEY + * SCALAR("key",plain) + * VALUE + * BLOCK-ENTRY + * SCALAR("item 1",plain) + * BLOCK-ENTRY + * SCALAR("item 2",plain) + * BLOCK-END + */ + +#include "yaml_private.h" + +/* + * Ensure that the buffer contains the required number of characters. + * Return 1 on success, 0 on failure (reader error or memory error). + */ + +#define CACHE(parser,length) \ + (parser->unread >= (length) \ + ? 1 \ + : yaml_parser_update_buffer(parser, (length))) + +/* + * Advance the buffer pointer. + */ + +#define SKIP(parser) \ + (parser->mark.index ++, \ + parser->mark.column ++, \ + parser->unread --, \ + parser->buffer.pointer += WIDTH(parser->buffer)) + +#define SKIP_LINE(parser) \ + (IS_CRLF(parser->buffer) ? \ + (parser->mark.index += 2, \ + parser->mark.column = 0, \ + parser->mark.line ++, \ + parser->unread -= 2, \ + parser->buffer.pointer += 2) : \ + IS_BREAK(parser->buffer) ? \ + (parser->mark.index ++, \ + parser->mark.column = 0, \ + parser->mark.line ++, \ + parser->unread --, \ + parser->buffer.pointer += WIDTH(parser->buffer)) : 0) + +/* + * Copy a character to a string buffer and advance pointers. + */ + +#define READ(parser,string) \ + (STRING_EXTEND(parser,string) ? \ + (COPY(string,parser->buffer), \ + parser->mark.index ++, \ + parser->mark.column ++, \ + parser->unread --, \ + 1) : 0) + +/* + * Copy a line break character to a string buffer and advance pointers. + */ + +#define READ_LINE(parser,string) \ + (STRING_EXTEND(parser,string) ? \ + (((CHECK_AT(parser->buffer,'\r',0) \ + && CHECK_AT(parser->buffer,'\n',1)) ? /* CR LF -> LF */ \ + (*((string).pointer++) = (yaml_char_t) '\n', \ + parser->buffer.pointer += 2, \ + parser->mark.index += 2, \ + parser->mark.column = 0, \ + parser->mark.line ++, \ + parser->unread -= 2) : \ + (CHECK_AT(parser->buffer,'\r',0) \ + || CHECK_AT(parser->buffer,'\n',0)) ? /* CR|LF -> LF */ \ + (*((string).pointer++) = (yaml_char_t) '\n', \ + parser->buffer.pointer ++, \ + parser->mark.index ++, \ + parser->mark.column = 0, \ + parser->mark.line ++, \ + parser->unread --) : \ + (CHECK_AT(parser->buffer,'\xC2',0) \ + && CHECK_AT(parser->buffer,'\x85',1)) ? /* NEL -> LF */ \ + (*((string).pointer++) = (yaml_char_t) '\n', \ + parser->buffer.pointer += 2, \ + parser->mark.index ++, \ + parser->mark.column = 0, \ + parser->mark.line ++, \ + parser->unread --) : \ + (CHECK_AT(parser->buffer,'\xE2',0) && \ + CHECK_AT(parser->buffer,'\x80',1) && \ + (CHECK_AT(parser->buffer,'\xA8',2) || \ + CHECK_AT(parser->buffer,'\xA9',2))) ? /* LS|PS -> LS|PS */ \ + (*((string).pointer++) = *(parser->buffer.pointer++), \ + *((string).pointer++) = *(parser->buffer.pointer++), \ + *((string).pointer++) = *(parser->buffer.pointer++), \ + parser->mark.index ++, \ + parser->mark.column = 0, \ + parser->mark.line ++, \ + parser->unread --) : 0), \ + 1) : 0) + +/* + * Public API declarations. + */ + +YAML_DECLARE(int) +yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token); + +/* + * Error handling. + */ + +static int +yaml_parser_set_scanner_error(yaml_parser_t *parser, const char *context, + yaml_mark_t context_mark, const char *problem); + +/* + * High-level token API. + */ + +YAML_DECLARE(int) +yaml_parser_fetch_more_tokens(yaml_parser_t *parser); + +static int +yaml_parser_fetch_next_token(yaml_parser_t *parser); + +/* + * Potential simple keys. + */ + +static int +yaml_parser_stale_simple_keys(yaml_parser_t *parser); + +static int +yaml_parser_save_simple_key(yaml_parser_t *parser); + +static int +yaml_parser_remove_simple_key(yaml_parser_t *parser); + +static int +yaml_parser_increase_flow_level(yaml_parser_t *parser); + +static int +yaml_parser_decrease_flow_level(yaml_parser_t *parser); + +/* + * Indentation treatment. + */ + +static int +yaml_parser_roll_indent(yaml_parser_t *parser, ptrdiff_t column, + ptrdiff_t number, yaml_token_type_t type, yaml_mark_t mark); + +static int +yaml_parser_unroll_indent(yaml_parser_t *parser, ptrdiff_t column); + +/* + * Token fetchers. + */ + +static int +yaml_parser_fetch_stream_start(yaml_parser_t *parser); + +static int +yaml_parser_fetch_stream_end(yaml_parser_t *parser); + +static int +yaml_parser_fetch_directive(yaml_parser_t *parser); + +static int +yaml_parser_fetch_document_indicator(yaml_parser_t *parser, + yaml_token_type_t type); + +static int +yaml_parser_fetch_flow_collection_start(yaml_parser_t *parser, + yaml_token_type_t type); + +static int +yaml_parser_fetch_flow_collection_end(yaml_parser_t *parser, + yaml_token_type_t type); + +static int +yaml_parser_fetch_flow_entry(yaml_parser_t *parser); + +static int +yaml_parser_fetch_block_entry(yaml_parser_t *parser); + +static int +yaml_parser_fetch_key(yaml_parser_t *parser); + +static int +yaml_parser_fetch_value(yaml_parser_t *parser); + +static int +yaml_parser_fetch_anchor(yaml_parser_t *parser, yaml_token_type_t type); + +static int +yaml_parser_fetch_tag(yaml_parser_t *parser); + +static int +yaml_parser_fetch_block_scalar(yaml_parser_t *parser, int literal); + +static int +yaml_parser_fetch_flow_scalar(yaml_parser_t *parser, int single); + +static int +yaml_parser_fetch_plain_scalar(yaml_parser_t *parser); + +/* + * Token scanners. + */ + +static int +yaml_parser_scan_to_next_token(yaml_parser_t *parser); + +static int +yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token); + +static int +yaml_parser_scan_directive_name(yaml_parser_t *parser, + yaml_mark_t start_mark, yaml_char_t **name); + +static int +yaml_parser_scan_version_directive_value(yaml_parser_t *parser, + yaml_mark_t start_mark, int *major, int *minor); + +static int +yaml_parser_scan_version_directive_number(yaml_parser_t *parser, + yaml_mark_t start_mark, int *number); + +static int +yaml_parser_scan_tag_directive_value(yaml_parser_t *parser, + yaml_mark_t mark, yaml_char_t **handle, yaml_char_t **prefix); + +static int +yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token, + yaml_token_type_t type); + +static int +yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token); + +static int +yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive, + yaml_mark_t start_mark, yaml_char_t **handle); + +static int +yaml_parser_scan_tag_uri(yaml_parser_t *parser, int uri_char, int directive, + yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri); + +static int +yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive, + yaml_mark_t start_mark, yaml_string_t *string); + +static int +yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token, + int literal); + +static int +yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser, + int *indent, yaml_string_t *breaks, + yaml_mark_t start_mark, yaml_mark_t *end_mark); + +static int +yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, + int single); + +static int +yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token); + +/* + * Get the next token. + */ + +YAML_DECLARE(int) +yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token) +{ + assert(parser); /* Non-NULL parser object is expected. */ + assert(token); /* Non-NULL token object is expected. */ + + /* Erase the token object. */ + + memset(token, 0, sizeof(yaml_token_t)); + + /* No tokens after STREAM-END or error. */ + + if (parser->stream_end_produced || parser->error) { + return 1; + } + + /* Ensure that the tokens queue contains enough tokens. */ + + if (!parser->token_available) { + if (!yaml_parser_fetch_more_tokens(parser)) + return 0; + } + + /* Fetch the next token from the queue. */ + + *token = DEQUEUE(parser, parser->tokens); + parser->token_available = 0; + parser->tokens_parsed ++; + + if (token->type == YAML_STREAM_END_TOKEN) { + parser->stream_end_produced = 1; + } + + return 1; +} + +/* + * Set the scanner error and return 0. + */ + +static int +yaml_parser_set_scanner_error(yaml_parser_t *parser, const char *context, + yaml_mark_t context_mark, const char *problem) +{ + parser->error = YAML_SCANNER_ERROR; + parser->context = context; + parser->context_mark = context_mark; + parser->problem = problem; + parser->problem_mark = parser->mark; + + return 0; +} + +/* + * Ensure that the tokens queue contains at least one token which can be + * returned to the Parser. + */ + +YAML_DECLARE(int) +yaml_parser_fetch_more_tokens(yaml_parser_t *parser) +{ + int need_more_tokens; + + /* While we need more tokens to fetch, do it. */ + + while (1) + { + /* + * Check if we really need to fetch more tokens. + */ + + need_more_tokens = 0; + + if (parser->tokens.head == parser->tokens.tail) + { + /* Queue is empty. */ + + need_more_tokens = 1; + } + else + { + yaml_simple_key_t *simple_key; + + /* Check if any potential simple key may occupy the head position. */ + + if (!yaml_parser_stale_simple_keys(parser)) + return 0; + + for (simple_key = parser->simple_keys.start; + simple_key != parser->simple_keys.top; simple_key++) { + if (simple_key->possible + && simple_key->token_number == parser->tokens_parsed) { + need_more_tokens = 1; + break; + } + } + } + + /* We are finished. */ + + if (!need_more_tokens) + break; + + /* Fetch the next token. */ + + if (!yaml_parser_fetch_next_token(parser)) + return 0; + } + + parser->token_available = 1; + + return 1; +} + +/* + * The dispatcher for token fetchers. + */ + +static int +yaml_parser_fetch_next_token(yaml_parser_t *parser) +{ + /* Ensure that the buffer is initialized. */ + + if (!CACHE(parser, 1)) + return 0; + + /* Check if we just started scanning. Fetch STREAM-START then. */ + + if (!parser->stream_start_produced) + return yaml_parser_fetch_stream_start(parser); + + /* Eat whitespaces and comments until we reach the next token. */ + + if (!yaml_parser_scan_to_next_token(parser)) + return 0; + + /* Remove obsolete potential simple keys. */ + + if (!yaml_parser_stale_simple_keys(parser)) + return 0; + + /* Check the indentation level against the current column. */ + + if (!yaml_parser_unroll_indent(parser, parser->mark.column)) + return 0; + + /* + * Ensure that the buffer contains at least 4 characters. 4 is the length + * of the longest indicators ('--- ' and '... '). + */ + + if (!CACHE(parser, 4)) + return 0; + + /* Is it the end of the stream? */ + + if (IS_Z(parser->buffer)) + return yaml_parser_fetch_stream_end(parser); + + /* Is it a directive? */ + + if (parser->mark.column == 0 && CHECK(parser->buffer, '%')) + return yaml_parser_fetch_directive(parser); + + /* Is it the document start indicator? */ + + if (parser->mark.column == 0 + && CHECK_AT(parser->buffer, '-', 0) + && CHECK_AT(parser->buffer, '-', 1) + && CHECK_AT(parser->buffer, '-', 2) + && IS_BLANKZ_AT(parser->buffer, 3)) + return yaml_parser_fetch_document_indicator(parser, + YAML_DOCUMENT_START_TOKEN); + + /* Is it the document end indicator? */ + + if (parser->mark.column == 0 + && CHECK_AT(parser->buffer, '.', 0) + && CHECK_AT(parser->buffer, '.', 1) + && CHECK_AT(parser->buffer, '.', 2) + && IS_BLANKZ_AT(parser->buffer, 3)) + return yaml_parser_fetch_document_indicator(parser, + YAML_DOCUMENT_END_TOKEN); + + /* Is it the flow sequence start indicator? */ + + if (CHECK(parser->buffer, '[')) + return yaml_parser_fetch_flow_collection_start(parser, + YAML_FLOW_SEQUENCE_START_TOKEN); + + /* Is it the flow mapping start indicator? */ + + if (CHECK(parser->buffer, '{')) + return yaml_parser_fetch_flow_collection_start(parser, + YAML_FLOW_MAPPING_START_TOKEN); + + /* Is it the flow sequence end indicator? */ + + if (CHECK(parser->buffer, ']')) + return yaml_parser_fetch_flow_collection_end(parser, + YAML_FLOW_SEQUENCE_END_TOKEN); + + /* Is it the flow mapping end indicator? */ + + if (CHECK(parser->buffer, '}')) + return yaml_parser_fetch_flow_collection_end(parser, + YAML_FLOW_MAPPING_END_TOKEN); + + /* Is it the flow entry indicator? */ + + if (CHECK(parser->buffer, ',')) + return yaml_parser_fetch_flow_entry(parser); + + /* Is it the block entry indicator? */ + + if (CHECK(parser->buffer, '-') && IS_BLANKZ_AT(parser->buffer, 1)) + return yaml_parser_fetch_block_entry(parser); + + /* Is it the key indicator? */ + + if (CHECK(parser->buffer, '?') + && (parser->flow_level || IS_BLANKZ_AT(parser->buffer, 1))) + return yaml_parser_fetch_key(parser); + + /* Is it the value indicator? */ + + if (CHECK(parser->buffer, ':') + && (parser->flow_level || IS_BLANKZ_AT(parser->buffer, 1))) + return yaml_parser_fetch_value(parser); + + /* Is it an alias? */ + + if (CHECK(parser->buffer, '*')) + return yaml_parser_fetch_anchor(parser, YAML_ALIAS_TOKEN); + + /* Is it an anchor? */ + + if (CHECK(parser->buffer, '&')) + return yaml_parser_fetch_anchor(parser, YAML_ANCHOR_TOKEN); + + /* Is it a tag? */ + + if (CHECK(parser->buffer, '!')) + return yaml_parser_fetch_tag(parser); + + /* Is it a literal scalar? */ + + if (CHECK(parser->buffer, '|') && !parser->flow_level) + return yaml_parser_fetch_block_scalar(parser, 1); + + /* Is it a folded scalar? */ + + if (CHECK(parser->buffer, '>') && !parser->flow_level) + return yaml_parser_fetch_block_scalar(parser, 0); + + /* Is it a single-quoted scalar? */ + + if (CHECK(parser->buffer, '\'')) + return yaml_parser_fetch_flow_scalar(parser, 1); + + /* Is it a double-quoted scalar? */ + + if (CHECK(parser->buffer, '"')) + return yaml_parser_fetch_flow_scalar(parser, 0); + + /* + * Is it a plain scalar? + * + * A plain scalar may start with any non-blank characters except + * + * '-', '?', ':', ',', '[', ']', '{', '}', + * '#', '&', '*', '!', '|', '>', '\'', '\"', + * '%', '@', '`'. + * + * In the block context (and, for the '-' indicator, in the flow context + * too), it may also start with the characters + * + * '-', '?', ':' + * + * if it is followed by a non-space character. + * + * The last rule is more restrictive than the specification requires. + */ + + if (!(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '-') + || CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':') + || CHECK(parser->buffer, ',') || CHECK(parser->buffer, '[') + || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{') + || CHECK(parser->buffer, '}') || CHECK(parser->buffer, '#') + || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '*') + || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '|') + || CHECK(parser->buffer, '>') || CHECK(parser->buffer, '\'') + || CHECK(parser->buffer, '"') || CHECK(parser->buffer, '%') + || CHECK(parser->buffer, '@') || CHECK(parser->buffer, '`')) || + (CHECK(parser->buffer, '-') && !IS_BLANK_AT(parser->buffer, 1)) || + (!parser->flow_level && + (CHECK(parser->buffer, '?') || CHECK(parser->buffer, ':')) + && !IS_BLANKZ_AT(parser->buffer, 1))) + return yaml_parser_fetch_plain_scalar(parser); + + /* + * If we don't determine the token type so far, it is an error. + */ + + return yaml_parser_set_scanner_error(parser, + "while scanning for the next token", parser->mark, + "found character that cannot start any token"); +} + +/* + * Check the list of potential simple keys and remove the positions that + * cannot contain simple keys anymore. + */ + +static int +yaml_parser_stale_simple_keys(yaml_parser_t *parser) +{ + yaml_simple_key_t *simple_key; + + /* Check for a potential simple key for each flow level. */ + + for (simple_key = parser->simple_keys.start; + simple_key != parser->simple_keys.top; simple_key ++) + { + /* + * The specification requires that a simple key + * + * - is limited to a single line, + * - is shorter than 1024 characters. + */ + + if (simple_key->possible + && (simple_key->mark.line < parser->mark.line + || simple_key->mark.index+1024 < parser->mark.index)) { + + /* Check if the potential simple key to be removed is required. */ + + if (simple_key->required) { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key->mark, + "could not find expected ':'"); + } + + simple_key->possible = 0; + } + } + + return 1; +} + +/* + * Check if a simple key may start at the current position and add it if + * needed. + */ + +static int +yaml_parser_save_simple_key(yaml_parser_t *parser) +{ + /* + * A simple key is required at the current position if the scanner is in + * the block context and the current column coincides with the indentation + * level. + */ + + int required = (!parser->flow_level + && parser->indent == (ptrdiff_t)parser->mark.column); + + /* + * If the current position may start a simple key, save it. + */ + + if (parser->simple_key_allowed) + { + yaml_simple_key_t simple_key; + simple_key.possible = 1; + simple_key.required = required; + simple_key.token_number = + parser->tokens_parsed + (parser->tokens.tail - parser->tokens.head); + simple_key.mark = parser->mark; + + if (!yaml_parser_remove_simple_key(parser)) return 0; + + *(parser->simple_keys.top-1) = simple_key; + } + + return 1; +} + +/* + * Remove a potential simple key at the current flow level. + */ + +static int +yaml_parser_remove_simple_key(yaml_parser_t *parser) +{ + yaml_simple_key_t *simple_key = parser->simple_keys.top-1; + + if (simple_key->possible) + { + /* If the key is required, it is an error. */ + + if (simple_key->required) { + return yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key->mark, + "could not find expected ':'"); + } + } + + /* Remove the key from the stack. */ + + simple_key->possible = 0; + + return 1; +} + +/* + * Increase the flow level and resize the simple key list if needed. + */ + +static int +yaml_parser_increase_flow_level(yaml_parser_t *parser) +{ + yaml_simple_key_t empty_simple_key = { 0, 0, 0, { 0, 0, 0 } }; + + /* Reset the simple key on the next level. */ + + if (!PUSH(parser, parser->simple_keys, empty_simple_key)) + return 0; + + /* Increase the flow level. */ + + if (parser->flow_level == INT_MAX) { + parser->error = YAML_MEMORY_ERROR; + return 0; + } + + parser->flow_level++; + + return 1; +} + +/* + * Decrease the flow level. + */ + +static int +yaml_parser_decrease_flow_level(yaml_parser_t *parser) +{ + if (parser->flow_level) { + parser->flow_level --; + (void)POP(parser, parser->simple_keys); + } + + return 1; +} + +/* + * Push the current indentation level to the stack and set the new level + * the current column is greater than the indentation level. In this case, + * append or insert the specified token into the token queue. + * + */ + +static int +yaml_parser_roll_indent(yaml_parser_t *parser, ptrdiff_t column, + ptrdiff_t number, yaml_token_type_t type, yaml_mark_t mark) +{ + yaml_token_t token; + + /* In the flow context, do nothing. */ + + if (parser->flow_level) + return 1; + + if (parser->indent < column) + { + /* + * Push the current indentation level to the stack and set the new + * indentation level. + */ + + if (!PUSH(parser, parser->indents, parser->indent)) + return 0; + + if (column > INT_MAX) { + parser->error = YAML_MEMORY_ERROR; + return 0; + } + + parser->indent = column; + + /* Create a token and insert it into the queue. */ + + TOKEN_INIT(token, type, mark, mark); + + if (number == -1) { + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + } + else { + if (!QUEUE_INSERT(parser, + parser->tokens, number - parser->tokens_parsed, token)) + return 0; + } + } + + return 1; +} + +/* + * Pop indentation levels from the indents stack until the current level + * becomes less or equal to the column. For each indentation level, append + * the BLOCK-END token. + */ + + +static int +yaml_parser_unroll_indent(yaml_parser_t *parser, ptrdiff_t column) +{ + yaml_token_t token; + + /* In the flow context, do nothing. */ + + if (parser->flow_level) + return 1; + + /* Loop through the indentation levels in the stack. */ + + while (parser->indent > column) + { + /* Create a token and append it to the queue. */ + + TOKEN_INIT(token, YAML_BLOCK_END_TOKEN, parser->mark, parser->mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + /* Pop the indentation level. */ + + parser->indent = POP(parser, parser->indents); + } + + return 1; +} + +/* + * Initialize the scanner and produce the STREAM-START token. + */ + +static int +yaml_parser_fetch_stream_start(yaml_parser_t *parser) +{ + yaml_simple_key_t simple_key = { 0, 0, 0, { 0, 0, 0 } }; + yaml_token_t token; + + /* Set the initial indentation. */ + + parser->indent = -1; + + /* Initialize the simple key stack. */ + + if (!PUSH(parser, parser->simple_keys, simple_key)) + return 0; + + /* A simple key is allowed at the beginning of the stream. */ + + parser->simple_key_allowed = 1; + + /* We have started. */ + + parser->stream_start_produced = 1; + + /* Create the STREAM-START token and append it to the queue. */ + + STREAM_START_TOKEN_INIT(token, parser->encoding, + parser->mark, parser->mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the STREAM-END token and shut down the scanner. + */ + +static int +yaml_parser_fetch_stream_end(yaml_parser_t *parser) +{ + yaml_token_t token; + + /* Force new line. */ + + if (parser->mark.column != 0) { + parser->mark.column = 0; + parser->mark.line ++; + } + + /* Reset the indentation level. */ + + if (!yaml_parser_unroll_indent(parser, -1)) + return 0; + + /* Reset simple keys. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + parser->simple_key_allowed = 0; + + /* Create the STREAM-END token and append it to the queue. */ + + STREAM_END_TOKEN_INIT(token, parser->mark, parser->mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token. + */ + +static int +yaml_parser_fetch_directive(yaml_parser_t *parser) +{ + yaml_token_t token; + + /* Reset the indentation level. */ + + if (!yaml_parser_unroll_indent(parser, -1)) + return 0; + + /* Reset simple keys. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + parser->simple_key_allowed = 0; + + /* Create the YAML-DIRECTIVE or TAG-DIRECTIVE token. */ + + if (!yaml_parser_scan_directive(parser, &token)) + return 0; + + /* Append the token to the queue. */ + + if (!ENQUEUE(parser, parser->tokens, token)) { + yaml_token_delete(&token); + return 0; + } + + return 1; +} + +/* + * Produce the DOCUMENT-START or DOCUMENT-END token. + */ + +static int +yaml_parser_fetch_document_indicator(yaml_parser_t *parser, + yaml_token_type_t type) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + + /* Reset the indentation level. */ + + if (!yaml_parser_unroll_indent(parser, -1)) + return 0; + + /* Reset simple keys. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + parser->simple_key_allowed = 0; + + /* Consume the token. */ + + start_mark = parser->mark; + + SKIP(parser); + SKIP(parser); + SKIP(parser); + + end_mark = parser->mark; + + /* Create the DOCUMENT-START or DOCUMENT-END token. */ + + TOKEN_INIT(token, type, start_mark, end_mark); + + /* Append the token to the queue. */ + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token. + */ + +static int +yaml_parser_fetch_flow_collection_start(yaml_parser_t *parser, + yaml_token_type_t type) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + + /* The indicators '[' and '{' may start a simple key. */ + + if (!yaml_parser_save_simple_key(parser)) + return 0; + + /* Increase the flow level. */ + + if (!yaml_parser_increase_flow_level(parser)) + return 0; + + /* A simple key may follow the indicators '[' and '{'. */ + + parser->simple_key_allowed = 1; + + /* Consume the token. */ + + start_mark = parser->mark; + SKIP(parser); + end_mark = parser->mark; + + /* Create the FLOW-SEQUENCE-START of FLOW-MAPPING-START token. */ + + TOKEN_INIT(token, type, start_mark, end_mark); + + /* Append the token to the queue. */ + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the FLOW-SEQUENCE-END or FLOW-MAPPING-END token. + */ + +static int +yaml_parser_fetch_flow_collection_end(yaml_parser_t *parser, + yaml_token_type_t type) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + + /* Reset any potential simple key on the current flow level. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + /* Decrease the flow level. */ + + if (!yaml_parser_decrease_flow_level(parser)) + return 0; + + /* No simple keys after the indicators ']' and '}'. */ + + parser->simple_key_allowed = 0; + + /* Consume the token. */ + + start_mark = parser->mark; + SKIP(parser); + end_mark = parser->mark; + + /* Create the FLOW-SEQUENCE-END of FLOW-MAPPING-END token. */ + + TOKEN_INIT(token, type, start_mark, end_mark); + + /* Append the token to the queue. */ + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the FLOW-ENTRY token. + */ + +static int +yaml_parser_fetch_flow_entry(yaml_parser_t *parser) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + + /* Reset any potential simple keys on the current flow level. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + /* Simple keys are allowed after ','. */ + + parser->simple_key_allowed = 1; + + /* Consume the token. */ + + start_mark = parser->mark; + SKIP(parser); + end_mark = parser->mark; + + /* Create the FLOW-ENTRY token and append it to the queue. */ + + TOKEN_INIT(token, YAML_FLOW_ENTRY_TOKEN, start_mark, end_mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the BLOCK-ENTRY token. + */ + +static int +yaml_parser_fetch_block_entry(yaml_parser_t *parser) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + + /* Check if the scanner is in the block context. */ + + if (!parser->flow_level) + { + /* Check if we are allowed to start a new entry. */ + + if (!parser->simple_key_allowed) { + return yaml_parser_set_scanner_error(parser, NULL, parser->mark, + "block sequence entries are not allowed in this context"); + } + + /* Add the BLOCK-SEQUENCE-START token if needed. */ + + if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, + YAML_BLOCK_SEQUENCE_START_TOKEN, parser->mark)) + return 0; + } + else + { + /* + * It is an error for the '-' indicator to occur in the flow context, + * but we let the Parser detect and report about it because the Parser + * is able to point to the context. + */ + } + + /* Reset any potential simple keys on the current flow level. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + /* Simple keys are allowed after '-'. */ + + parser->simple_key_allowed = 1; + + /* Consume the token. */ + + start_mark = parser->mark; + SKIP(parser); + end_mark = parser->mark; + + /* Create the BLOCK-ENTRY token and append it to the queue. */ + + TOKEN_INIT(token, YAML_BLOCK_ENTRY_TOKEN, start_mark, end_mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the KEY token. + */ + +static int +yaml_parser_fetch_key(yaml_parser_t *parser) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + + /* In the block context, additional checks are required. */ + + if (!parser->flow_level) + { + /* Check if we are allowed to start a new key (not necessary simple). */ + + if (!parser->simple_key_allowed) { + return yaml_parser_set_scanner_error(parser, NULL, parser->mark, + "mapping keys are not allowed in this context"); + } + + /* Add the BLOCK-MAPPING-START token if needed. */ + + if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, + YAML_BLOCK_MAPPING_START_TOKEN, parser->mark)) + return 0; + } + + /* Reset any potential simple keys on the current flow level. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + /* Simple keys are allowed after '?' in the block context. */ + + parser->simple_key_allowed = (!parser->flow_level); + + /* Consume the token. */ + + start_mark = parser->mark; + SKIP(parser); + end_mark = parser->mark; + + /* Create the KEY token and append it to the queue. */ + + TOKEN_INIT(token, YAML_KEY_TOKEN, start_mark, end_mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the VALUE token. + */ + +static int +yaml_parser_fetch_value(yaml_parser_t *parser) +{ + yaml_mark_t start_mark, end_mark; + yaml_token_t token; + yaml_simple_key_t *simple_key = parser->simple_keys.top-1; + + /* Have we found a simple key? */ + + if (simple_key->possible) + { + + /* Create the KEY token and insert it into the queue. */ + + TOKEN_INIT(token, YAML_KEY_TOKEN, simple_key->mark, simple_key->mark); + + if (!QUEUE_INSERT(parser, parser->tokens, + simple_key->token_number - parser->tokens_parsed, token)) + return 0; + + /* In the block context, we may need to add the BLOCK-MAPPING-START token. */ + + if (!yaml_parser_roll_indent(parser, simple_key->mark.column, + simple_key->token_number, + YAML_BLOCK_MAPPING_START_TOKEN, simple_key->mark)) + return 0; + + /* Remove the simple key. */ + + simple_key->possible = 0; + + /* A simple key cannot follow another simple key. */ + + parser->simple_key_allowed = 0; + } + else + { + /* The ':' indicator follows a complex key. */ + + /* In the block context, extra checks are required. */ + + if (!parser->flow_level) + { + /* Check if we are allowed to start a complex value. */ + + if (!parser->simple_key_allowed) { + return yaml_parser_set_scanner_error(parser, NULL, parser->mark, + "mapping values are not allowed in this context"); + } + + /* Add the BLOCK-MAPPING-START token if needed. */ + + if (!yaml_parser_roll_indent(parser, parser->mark.column, -1, + YAML_BLOCK_MAPPING_START_TOKEN, parser->mark)) + return 0; + } + + /* Simple keys after ':' are allowed in the block context. */ + + parser->simple_key_allowed = (!parser->flow_level); + } + + /* Consume the token. */ + + start_mark = parser->mark; + SKIP(parser); + end_mark = parser->mark; + + /* Create the VALUE token and append it to the queue. */ + + TOKEN_INIT(token, YAML_VALUE_TOKEN, start_mark, end_mark); + + if (!ENQUEUE(parser, parser->tokens, token)) + return 0; + + return 1; +} + +/* + * Produce the ALIAS or ANCHOR token. + */ + +static int +yaml_parser_fetch_anchor(yaml_parser_t *parser, yaml_token_type_t type) +{ + yaml_token_t token; + + /* An anchor or an alias could be a simple key. */ + + if (!yaml_parser_save_simple_key(parser)) + return 0; + + /* A simple key cannot follow an anchor or an alias. */ + + parser->simple_key_allowed = 0; + + /* Create the ALIAS or ANCHOR token and append it to the queue. */ + + if (!yaml_parser_scan_anchor(parser, &token, type)) + return 0; + + if (!ENQUEUE(parser, parser->tokens, token)) { + yaml_token_delete(&token); + return 0; + } + return 1; +} + +/* + * Produce the TAG token. + */ + +static int +yaml_parser_fetch_tag(yaml_parser_t *parser) +{ + yaml_token_t token; + + /* A tag could be a simple key. */ + + if (!yaml_parser_save_simple_key(parser)) + return 0; + + /* A simple key cannot follow a tag. */ + + parser->simple_key_allowed = 0; + + /* Create the TAG token and append it to the queue. */ + + if (!yaml_parser_scan_tag(parser, &token)) + return 0; + + if (!ENQUEUE(parser, parser->tokens, token)) { + yaml_token_delete(&token); + return 0; + } + + return 1; +} + +/* + * Produce the SCALAR(...,literal) or SCALAR(...,folded) tokens. + */ + +static int +yaml_parser_fetch_block_scalar(yaml_parser_t *parser, int literal) +{ + yaml_token_t token; + + /* Remove any potential simple keys. */ + + if (!yaml_parser_remove_simple_key(parser)) + return 0; + + /* A simple key may follow a block scalar. */ + + parser->simple_key_allowed = 1; + + /* Create the SCALAR token and append it to the queue. */ + + if (!yaml_parser_scan_block_scalar(parser, &token, literal)) + return 0; + + if (!ENQUEUE(parser, parser->tokens, token)) { + yaml_token_delete(&token); + return 0; + } + + return 1; +} + +/* + * Produce the SCALAR(...,single-quoted) or SCALAR(...,double-quoted) tokens. + */ + +static int +yaml_parser_fetch_flow_scalar(yaml_parser_t *parser, int single) +{ + yaml_token_t token; + + /* A plain scalar could be a simple key. */ + + if (!yaml_parser_save_simple_key(parser)) + return 0; + + /* A simple key cannot follow a flow scalar. */ + + parser->simple_key_allowed = 0; + + /* Create the SCALAR token and append it to the queue. */ + + if (!yaml_parser_scan_flow_scalar(parser, &token, single)) + return 0; + + if (!ENQUEUE(parser, parser->tokens, token)) { + yaml_token_delete(&token); + return 0; + } + + return 1; +} + +/* + * Produce the SCALAR(...,plain) token. + */ + +static int +yaml_parser_fetch_plain_scalar(yaml_parser_t *parser) +{ + yaml_token_t token; + + /* A plain scalar could be a simple key. */ + + if (!yaml_parser_save_simple_key(parser)) + return 0; + + /* A simple key cannot follow a flow scalar. */ + + parser->simple_key_allowed = 0; + + /* Create the SCALAR token and append it to the queue. */ + + if (!yaml_parser_scan_plain_scalar(parser, &token)) + return 0; + + if (!ENQUEUE(parser, parser->tokens, token)) { + yaml_token_delete(&token); + return 0; + } + + return 1; +} + +/* + * Eat whitespaces and comments until the next token is found. + */ + +static int +yaml_parser_scan_to_next_token(yaml_parser_t *parser) +{ + /* Until the next token is not found. */ + + while (1) + { + /* Allow the BOM mark to start a line. */ + + if (!CACHE(parser, 1)) return 0; + + if (parser->mark.column == 0 && IS_BOM(parser->buffer)) + SKIP(parser); + + /* + * Eat whitespaces. + * + * Tabs are allowed: + * + * - in the flow context; + * - in the block context, but not at the beginning of the line or + * after '-', '?', or ':' (complex value). + */ + + if (!CACHE(parser, 1)) return 0; + + while (CHECK(parser->buffer,' ') || + ((parser->flow_level || !parser->simple_key_allowed) && + CHECK(parser->buffer, '\t'))) { + SKIP(parser); + if (!CACHE(parser, 1)) return 0; + } + + /* Eat a comment until a line break. */ + + if (CHECK(parser->buffer, '#')) { + while (!IS_BREAKZ(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) return 0; + } + } + + /* If it is a line break, eat it. */ + + if (IS_BREAK(parser->buffer)) + { + if (!CACHE(parser, 2)) return 0; + SKIP_LINE(parser); + + /* In the block context, a new line may start a simple key. */ + + if (!parser->flow_level) { + parser->simple_key_allowed = 1; + } + } + else + { + /* We have found a token. */ + + break; + } + } + + return 1; +} + +/* + * Scan a YAML-DIRECTIVE or TAG-DIRECTIVE token. + * + * Scope: + * %YAML 1.1 # a comment \n + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + * %TAG !yaml! tag:yaml.org,2002: \n + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + */ + +int +yaml_parser_scan_directive(yaml_parser_t *parser, yaml_token_t *token) +{ + yaml_mark_t start_mark, end_mark; + yaml_char_t *name = NULL; + int major, minor; + yaml_char_t *handle = NULL, *prefix = NULL; + + /* Eat '%'. */ + + start_mark = parser->mark; + + SKIP(parser); + + /* Scan the directive name. */ + + if (!yaml_parser_scan_directive_name(parser, start_mark, &name)) + goto error; + + /* Is it a YAML directive? */ + + if (strcmp((char *)name, "YAML") == 0) + { + /* Scan the VERSION directive value. */ + + if (!yaml_parser_scan_version_directive_value(parser, start_mark, + &major, &minor)) + goto error; + + end_mark = parser->mark; + + /* Create a VERSION-DIRECTIVE token. */ + + VERSION_DIRECTIVE_TOKEN_INIT(*token, major, minor, + start_mark, end_mark); + } + + /* Is it a TAG directive? */ + + else if (strcmp((char *)name, "TAG") == 0) + { + /* Scan the TAG directive value. */ + + if (!yaml_parser_scan_tag_directive_value(parser, start_mark, + &handle, &prefix)) + goto error; + + end_mark = parser->mark; + + /* Create a TAG-DIRECTIVE token. */ + + TAG_DIRECTIVE_TOKEN_INIT(*token, handle, prefix, + start_mark, end_mark); + } + + /* Unknown directive. */ + + else + { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unknown directive name"); + goto error; + } + + /* Eat the rest of the line including any comments. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_BLANK(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) goto error; + } + + if (CHECK(parser->buffer, '#')) { + while (!IS_BREAKZ(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) goto error; + } + } + + /* Check if we are at the end of the line. */ + + if (!IS_BREAKZ(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "did not find expected comment or line break"); + goto error; + } + + /* Eat a line break. */ + + if (IS_BREAK(parser->buffer)) { + if (!CACHE(parser, 2)) goto error; + SKIP_LINE(parser); + } + + yaml_free(name); + + return 1; + +error: + yaml_free(prefix); + yaml_free(handle); + yaml_free(name); + return 0; +} + +/* + * Scan the directive name. + * + * Scope: + * %YAML 1.1 # a comment \n + * ^^^^ + * %TAG !yaml! tag:yaml.org,2002: \n + * ^^^ + */ + +static int +yaml_parser_scan_directive_name(yaml_parser_t *parser, + yaml_mark_t start_mark, yaml_char_t **name) +{ + yaml_string_t string = NULL_STRING; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + + /* Consume the directive name. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_ALPHA(parser->buffer)) + { + if (!READ(parser, string)) goto error; + if (!CACHE(parser, 1)) goto error; + } + + /* Check if the name is empty. */ + + if (string.start == string.pointer) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "could not find expected directive name"); + goto error; + } + + /* Check for an blank character after the name. */ + + if (!IS_BLANKZ(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a directive", + start_mark, "found unexpected non-alphabetical character"); + goto error; + } + + *name = string.start; + + return 1; + +error: + STRING_DEL(parser, string); + return 0; +} + +/* + * Scan the value of VERSION-DIRECTIVE. + * + * Scope: + * %YAML 1.1 # a comment \n + * ^^^^^^ + */ + +static int +yaml_parser_scan_version_directive_value(yaml_parser_t *parser, + yaml_mark_t start_mark, int *major, int *minor) +{ + /* Eat whitespaces. */ + + if (!CACHE(parser, 1)) return 0; + + while (IS_BLANK(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) return 0; + } + + /* Consume the major version number. */ + + if (!yaml_parser_scan_version_directive_number(parser, start_mark, major)) + return 0; + + /* Eat '.'. */ + + if (!CHECK(parser->buffer, '.')) { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected digit or '.' character"); + } + + SKIP(parser); + + /* Consume the minor version number. */ + + if (!yaml_parser_scan_version_directive_number(parser, start_mark, minor)) + return 0; + + return 1; +} + +#define MAX_NUMBER_LENGTH 9 + +/* + * Scan the version number of VERSION-DIRECTIVE. + * + * Scope: + * %YAML 1.1 # a comment \n + * ^ + * %YAML 1.1 # a comment \n + * ^ + */ + +static int +yaml_parser_scan_version_directive_number(yaml_parser_t *parser, + yaml_mark_t start_mark, int *number) +{ + int value = 0; + size_t length = 0; + + /* Repeat while the next character is digit. */ + + if (!CACHE(parser, 1)) return 0; + + while (IS_DIGIT(parser->buffer)) + { + /* Check if the number is too long. */ + + if (++length > MAX_NUMBER_LENGTH) { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "found extremely long version number"); + } + + value = value*10 + AS_DIGIT(parser->buffer); + + SKIP(parser); + + if (!CACHE(parser, 1)) return 0; + } + + /* Check if the number was present. */ + + if (!length) { + return yaml_parser_set_scanner_error(parser, "while scanning a %YAML directive", + start_mark, "did not find expected version number"); + } + + *number = value; + + return 1; +} + +/* + * Scan the value of a TAG-DIRECTIVE token. + * + * Scope: + * %TAG !yaml! tag:yaml.org,2002: \n + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + */ + +static int +yaml_parser_scan_tag_directive_value(yaml_parser_t *parser, + yaml_mark_t start_mark, yaml_char_t **handle, yaml_char_t **prefix) +{ + yaml_char_t *handle_value = NULL; + yaml_char_t *prefix_value = NULL; + + /* Eat whitespaces. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_BLANK(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) goto error; + } + + /* Scan a handle. */ + + if (!yaml_parser_scan_tag_handle(parser, 1, start_mark, &handle_value)) + goto error; + + /* Expect a whitespace. */ + + if (!CACHE(parser, 1)) goto error; + + if (!IS_BLANK(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace"); + goto error; + } + + /* Eat whitespaces. */ + + while (IS_BLANK(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) goto error; + } + + /* Scan a prefix. */ + + if (!yaml_parser_scan_tag_uri(parser, 1, 1, NULL, start_mark, &prefix_value)) + goto error; + + /* Expect a whitespace or line break. */ + + if (!CACHE(parser, 1)) goto error; + + if (!IS_BLANKZ(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a %TAG directive", + start_mark, "did not find expected whitespace or line break"); + goto error; + } + + *handle = handle_value; + *prefix = prefix_value; + + return 1; + +error: + yaml_free(handle_value); + yaml_free(prefix_value); + return 0; +} + +static int +yaml_parser_scan_anchor(yaml_parser_t *parser, yaml_token_t *token, + yaml_token_type_t type) +{ + int length = 0; + yaml_mark_t start_mark, end_mark; + yaml_string_t string = NULL_STRING; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + + /* Eat the indicator character. */ + + start_mark = parser->mark; + + SKIP(parser); + + /* Consume the value. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_ALPHA(parser->buffer)) { + if (!READ(parser, string)) goto error; + if (!CACHE(parser, 1)) goto error; + length ++; + } + + end_mark = parser->mark; + + /* + * Check if length of the anchor is greater than 0 and it is followed by + * a whitespace character or one of the indicators: + * + * '?', ':', ',', ']', '}', '%', '@', '`'. + */ + + if (!length || !(IS_BLANKZ(parser->buffer) || CHECK(parser->buffer, '?') + || CHECK(parser->buffer, ':') || CHECK(parser->buffer, ',') + || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '}') + || CHECK(parser->buffer, '%') || CHECK(parser->buffer, '@') + || CHECK(parser->buffer, '`'))) { + yaml_parser_set_scanner_error(parser, type == YAML_ANCHOR_TOKEN ? + "while scanning an anchor" : "while scanning an alias", start_mark, + "did not find expected alphabetic or numeric character"); + goto error; + } + + /* Create a token. */ + + if (type == YAML_ANCHOR_TOKEN) { + ANCHOR_TOKEN_INIT(*token, string.start, start_mark, end_mark); + } + else { + ALIAS_TOKEN_INIT(*token, string.start, start_mark, end_mark); + } + + return 1; + +error: + STRING_DEL(parser, string); + return 0; +} + +/* + * Scan a TAG token. + */ + +static int +yaml_parser_scan_tag(yaml_parser_t *parser, yaml_token_t *token) +{ + yaml_char_t *handle = NULL; + yaml_char_t *suffix = NULL; + yaml_mark_t start_mark, end_mark; + + start_mark = parser->mark; + + /* Check if the tag is in the canonical form. */ + + if (!CACHE(parser, 2)) goto error; + + if (CHECK_AT(parser->buffer, '<', 1)) + { + /* Set the handle to '' */ + + handle = YAML_MALLOC(1); + if (!handle) goto error; + handle[0] = '\0'; + + /* Eat '!<' */ + + SKIP(parser); + SKIP(parser); + + /* Consume the tag value. */ + + if (!yaml_parser_scan_tag_uri(parser, 1, 0, NULL, start_mark, &suffix)) + goto error; + + /* Check for '>' and eat it. */ + + if (!CHECK(parser->buffer, '>')) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find the expected '>'"); + goto error; + } + + SKIP(parser); + } + else + { + /* The tag has either the '!suffix' or the '!handle!suffix' form. */ + + /* First, try to scan a handle. */ + + if (!yaml_parser_scan_tag_handle(parser, 0, start_mark, &handle)) + goto error; + + /* Check if it is, indeed, handle. */ + + if (handle[0] == '!' && handle[1] != '\0' && handle[strlen((char *)handle)-1] == '!') + { + /* Scan the suffix now. */ + + if (!yaml_parser_scan_tag_uri(parser, 0, 0, NULL, start_mark, &suffix)) + goto error; + } + else + { + /* It wasn't a handle after all. Scan the rest of the tag. */ + + if (!yaml_parser_scan_tag_uri(parser, 0, 0, handle, start_mark, &suffix)) + goto error; + + /* Set the handle to '!'. */ + + yaml_free(handle); + handle = YAML_MALLOC(2); + if (!handle) goto error; + handle[0] = '!'; + handle[1] = '\0'; + + /* + * A special case: the '!' tag. Set the handle to '' and the + * suffix to '!'. + */ + + if (suffix[0] == '\0') { + yaml_char_t *tmp = handle; + handle = suffix; + suffix = tmp; + } + } + } + + /* Check the character which ends the tag. */ + + if (!CACHE(parser, 1)) goto error; + + if (!IS_BLANKZ(parser->buffer)) { + if (!parser->flow_level || !CHECK(parser->buffer, ',') ) { + yaml_parser_set_scanner_error(parser, "while scanning a tag", + start_mark, "did not find expected whitespace or line break"); + goto error; + } + } + + end_mark = parser->mark; + + /* Create a token. */ + + TAG_TOKEN_INIT(*token, handle, suffix, start_mark, end_mark); + + return 1; + +error: + yaml_free(handle); + yaml_free(suffix); + return 0; +} + +/* + * Scan a tag handle. + */ + +static int +yaml_parser_scan_tag_handle(yaml_parser_t *parser, int directive, + yaml_mark_t start_mark, yaml_char_t **handle) +{ + yaml_string_t string = NULL_STRING; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + + /* Check the initial '!' character. */ + + if (!CACHE(parser, 1)) goto error; + + if (!CHECK(parser->buffer, '!')) { + yaml_parser_set_scanner_error(parser, directive ? + "while scanning a tag directive" : "while scanning a tag", + start_mark, "did not find expected '!'"); + goto error; + } + + /* Copy the '!' character. */ + + if (!READ(parser, string)) goto error; + + /* Copy all subsequent alphabetical and numerical characters. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_ALPHA(parser->buffer)) + { + if (!READ(parser, string)) goto error; + if (!CACHE(parser, 1)) goto error; + } + + /* Check if the trailing character is '!' and copy it. */ + + if (CHECK(parser->buffer, '!')) + { + if (!READ(parser, string)) goto error; + } + else + { + /* + * It's either the '!' tag or not really a tag handle. If it's a %TAG + * directive, it's an error. If it's a tag token, it must be a part of + * URI. + */ + + if (directive && !(string.start[0] == '!' && string.start[1] == '\0')) { + yaml_parser_set_scanner_error(parser, "while parsing a tag directive", + start_mark, "did not find expected '!'"); + goto error; + } + } + + *handle = string.start; + + return 1; + +error: + STRING_DEL(parser, string); + return 0; +} + +/* + * Scan a tag. + */ + +static int +yaml_parser_scan_tag_uri(yaml_parser_t *parser, int uri_char, int directive, + yaml_char_t *head, yaml_mark_t start_mark, yaml_char_t **uri) +{ + size_t length = head ? strlen((char *)head) : 0; + yaml_string_t string = NULL_STRING; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + + /* Resize the string to include the head. */ + + while ((size_t)(string.end - string.start) <= length) { + if (!yaml_string_extend(&string.start, &string.pointer, &string.end)) { + parser->error = YAML_MEMORY_ERROR; + goto error; + } + } + + /* + * Copy the head if needed. + * + * Note that we don't copy the leading '!' character. + */ + + if (length > 1) { + memcpy(string.start, head+1, length-1); + string.pointer += length-1; + } + + /* Scan the tag. */ + + if (!CACHE(parser, 1)) goto error; + + /* + * The set of characters that may appear in URI is as follows: + * + * '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&', + * '=', '+', '$', '.', '!', '~', '*', '\'', '(', ')', '%'. + * + * If we are inside a verbatim tag <...> (parameter uri_char is true) + * then also the following flow indicators are allowed: + * ',', '[', ']' + */ + + while (IS_ALPHA(parser->buffer) || CHECK(parser->buffer, ';') + || CHECK(parser->buffer, '/') || CHECK(parser->buffer, '?') + || CHECK(parser->buffer, ':') || CHECK(parser->buffer, '@') + || CHECK(parser->buffer, '&') || CHECK(parser->buffer, '=') + || CHECK(parser->buffer, '+') || CHECK(parser->buffer, '$') + || CHECK(parser->buffer, '.') || CHECK(parser->buffer, '%') + || CHECK(parser->buffer, '!') || CHECK(parser->buffer, '~') + || CHECK(parser->buffer, '*') || CHECK(parser->buffer, '\'') + || CHECK(parser->buffer, '(') || CHECK(parser->buffer, ')') + || (uri_char && ( + CHECK(parser->buffer, ',') + || CHECK(parser->buffer, '[') || CHECK(parser->buffer, ']') + ) + )) + { + /* Check if it is a URI-escape sequence. */ + + if (CHECK(parser->buffer, '%')) { + if (!STRING_EXTEND(parser, string)) + goto error; + + if (!yaml_parser_scan_uri_escapes(parser, + directive, start_mark, &string)) goto error; + } + else { + if (!READ(parser, string)) goto error; + } + + length ++; + if (!CACHE(parser, 1)) goto error; + } + + /* Check if the tag is non-empty. */ + + if (!length) { + if (!STRING_EXTEND(parser, string)) + goto error; + + yaml_parser_set_scanner_error(parser, directive ? + "while parsing a %TAG directive" : "while parsing a tag", + start_mark, "did not find expected tag URI"); + goto error; + } + + *uri = string.start; + + return 1; + +error: + STRING_DEL(parser, string); + return 0; +} + +/* + * Decode an URI-escape sequence corresponding to a single UTF-8 character. + */ + +static int +yaml_parser_scan_uri_escapes(yaml_parser_t *parser, int directive, + yaml_mark_t start_mark, yaml_string_t *string) +{ + int width = 0; + + /* Decode the required number of characters. */ + + do { + + unsigned char octet = 0; + + /* Check for a URI-escaped octet. */ + + if (!CACHE(parser, 3)) return 0; + + if (!(CHECK(parser->buffer, '%') + && IS_HEX_AT(parser->buffer, 1) + && IS_HEX_AT(parser->buffer, 2))) { + return yaml_parser_set_scanner_error(parser, directive ? + "while parsing a %TAG directive" : "while parsing a tag", + start_mark, "did not find URI escaped octet"); + } + + /* Get the octet. */ + + octet = (AS_HEX_AT(parser->buffer, 1) << 4) + AS_HEX_AT(parser->buffer, 2); + + /* If it is the leading octet, determine the length of the UTF-8 sequence. */ + + if (!width) + { + width = (octet & 0x80) == 0x00 ? 1 : + (octet & 0xE0) == 0xC0 ? 2 : + (octet & 0xF0) == 0xE0 ? 3 : + (octet & 0xF8) == 0xF0 ? 4 : 0; + if (!width) { + return yaml_parser_set_scanner_error(parser, directive ? + "while parsing a %TAG directive" : "while parsing a tag", + start_mark, "found an incorrect leading UTF-8 octet"); + } + } + else + { + /* Check if the trailing octet is correct. */ + + if ((octet & 0xC0) != 0x80) { + return yaml_parser_set_scanner_error(parser, directive ? + "while parsing a %TAG directive" : "while parsing a tag", + start_mark, "found an incorrect trailing UTF-8 octet"); + } + } + + /* Copy the octet and move the pointers. */ + + *(string->pointer++) = octet; + SKIP(parser); + SKIP(parser); + SKIP(parser); + + } while (--width); + + return 1; +} + +/* + * Scan a block scalar. + */ + +static int +yaml_parser_scan_block_scalar(yaml_parser_t *parser, yaml_token_t *token, + int literal) +{ + yaml_mark_t start_mark; + yaml_mark_t end_mark; + yaml_string_t string = NULL_STRING; + yaml_string_t leading_break = NULL_STRING; + yaml_string_t trailing_breaks = NULL_STRING; + int chomping = 0; + int increment = 0; + int indent = 0; + int leading_blank = 0; + int trailing_blank = 0; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; + + /* Eat the indicator '|' or '>'. */ + + start_mark = parser->mark; + + SKIP(parser); + + /* Scan the additional block scalar indicators. */ + + if (!CACHE(parser, 1)) goto error; + + /* Check for a chomping indicator. */ + + if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) + { + /* Set the chomping method and eat the indicator. */ + + chomping = CHECK(parser->buffer, '+') ? +1 : -1; + + SKIP(parser); + + /* Check for an indentation indicator. */ + + if (!CACHE(parser, 1)) goto error; + + if (IS_DIGIT(parser->buffer)) + { + /* Check that the indentation is greater than 0. */ + + if (CHECK(parser->buffer, '0')) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0"); + goto error; + } + + /* Get the indentation level and eat the indicator. */ + + increment = AS_DIGIT(parser->buffer); + + SKIP(parser); + } + } + + /* Do the same as above, but in the opposite order. */ + + else if (IS_DIGIT(parser->buffer)) + { + if (CHECK(parser->buffer, '0')) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found an indentation indicator equal to 0"); + goto error; + } + + increment = AS_DIGIT(parser->buffer); + + SKIP(parser); + + if (!CACHE(parser, 1)) goto error; + + if (CHECK(parser->buffer, '+') || CHECK(parser->buffer, '-')) { + chomping = CHECK(parser->buffer, '+') ? +1 : -1; + + SKIP(parser); + } + } + + /* Eat whitespaces and comments to the end of the line. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_BLANK(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) goto error; + } + + if (CHECK(parser->buffer, '#')) { + while (!IS_BREAKZ(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) goto error; + } + } + + /* Check if we are at the end of the line. */ + + if (!IS_BREAKZ(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "did not find expected comment or line break"); + goto error; + } + + /* Eat a line break. */ + + if (IS_BREAK(parser->buffer)) { + if (!CACHE(parser, 2)) goto error; + SKIP_LINE(parser); + } + + end_mark = parser->mark; + + /* Set the indentation level if it was specified. */ + + if (increment) { + indent = parser->indent >= 0 ? parser->indent+increment : increment; + } + + /* Scan the leading line breaks and determine the indentation level if needed. */ + + if (!yaml_parser_scan_block_scalar_breaks(parser, &indent, &trailing_breaks, + start_mark, &end_mark)) goto error; + + /* Scan the block scalar content. */ + + if (!CACHE(parser, 1)) goto error; + + while ((int)parser->mark.column == indent && !(IS_Z(parser->buffer))) + { + /* + * We are at the beginning of a non-empty line. + */ + + /* Is it a trailing whitespace? */ + + trailing_blank = IS_BLANK(parser->buffer); + + /* Check if we need to fold the leading line break. */ + + if (!literal && (*leading_break.start == '\n') + && !leading_blank && !trailing_blank) + { + /* Do we need to join the lines by space? */ + + if (*trailing_breaks.start == '\0') { + if (!STRING_EXTEND(parser, string)) goto error; + *(string.pointer ++) = ' '; + } + + CLEAR(parser, leading_break); + } + else { + if (!JOIN(parser, string, leading_break)) goto error; + CLEAR(parser, leading_break); + } + + /* Append the remaining line breaks. */ + + if (!JOIN(parser, string, trailing_breaks)) goto error; + CLEAR(parser, trailing_breaks); + + /* Is it a leading whitespace? */ + + leading_blank = IS_BLANK(parser->buffer); + + /* Consume the current line. */ + + while (!IS_BREAKZ(parser->buffer)) { + if (!READ(parser, string)) goto error; + if (!CACHE(parser, 1)) goto error; + } + + /* Consume the line break. */ + + if (!CACHE(parser, 2)) goto error; + + if (!READ_LINE(parser, leading_break)) goto error; + + /* Eat the following indentation spaces and line breaks. */ + + if (!yaml_parser_scan_block_scalar_breaks(parser, + &indent, &trailing_breaks, start_mark, &end_mark)) goto error; + } + + /* Chomp the tail. */ + + if (chomping != -1) { + if (!JOIN(parser, string, leading_break)) goto error; + } + if (chomping == 1) { + if (!JOIN(parser, string, trailing_breaks)) goto error; + } + + /* Create a token. */ + + SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, + literal ? YAML_LITERAL_SCALAR_STYLE : YAML_FOLDED_SCALAR_STYLE, + start_mark, end_mark); + + STRING_DEL(parser, leading_break); + STRING_DEL(parser, trailing_breaks); + + return 1; + +error: + STRING_DEL(parser, string); + STRING_DEL(parser, leading_break); + STRING_DEL(parser, trailing_breaks); + + return 0; +} + +/* + * Scan indentation spaces and line breaks for a block scalar. Determine the + * indentation level if needed. + */ + +static int +yaml_parser_scan_block_scalar_breaks(yaml_parser_t *parser, + int *indent, yaml_string_t *breaks, + yaml_mark_t start_mark, yaml_mark_t *end_mark) +{ + int max_indent = 0; + + *end_mark = parser->mark; + + /* Eat the indentation spaces and line breaks. */ + + while (1) + { + /* Eat the indentation spaces. */ + + if (!CACHE(parser, 1)) return 0; + + while ((!*indent || (int)parser->mark.column < *indent) + && IS_SPACE(parser->buffer)) { + SKIP(parser); + if (!CACHE(parser, 1)) return 0; + } + + if ((int)parser->mark.column > max_indent) + max_indent = (int)parser->mark.column; + + /* Check for a tab character messing the indentation. */ + + if ((!*indent || (int)parser->mark.column < *indent) + && IS_TAB(parser->buffer)) { + return yaml_parser_set_scanner_error(parser, "while scanning a block scalar", + start_mark, "found a tab character where an indentation space is expected"); + } + + /* Have we found a non-empty line? */ + + if (!IS_BREAK(parser->buffer)) break; + + /* Consume the line break. */ + + if (!CACHE(parser, 2)) return 0; + if (!READ_LINE(parser, *breaks)) return 0; + *end_mark = parser->mark; + } + + /* Determine the indentation level if needed. */ + + if (!*indent) { + *indent = max_indent; + if (*indent < parser->indent + 1) + *indent = parser->indent + 1; + if (*indent < 1) + *indent = 1; + } + + return 1; +} + +/* + * Scan a quoted scalar. + */ + +static int +yaml_parser_scan_flow_scalar(yaml_parser_t *parser, yaml_token_t *token, + int single) +{ + yaml_mark_t start_mark; + yaml_mark_t end_mark; + yaml_string_t string = NULL_STRING; + yaml_string_t leading_break = NULL_STRING; + yaml_string_t trailing_breaks = NULL_STRING; + yaml_string_t whitespaces = NULL_STRING; + int leading_blanks; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error; + + /* Eat the left quote. */ + + start_mark = parser->mark; + + SKIP(parser); + + /* Consume the content of the quoted scalar. */ + + while (1) + { + /* Check that there are no document indicators at the beginning of the line. */ + + if (!CACHE(parser, 4)) goto error; + + if (parser->mark.column == 0 && + ((CHECK_AT(parser->buffer, '-', 0) && + CHECK_AT(parser->buffer, '-', 1) && + CHECK_AT(parser->buffer, '-', 2)) || + (CHECK_AT(parser->buffer, '.', 0) && + CHECK_AT(parser->buffer, '.', 1) && + CHECK_AT(parser->buffer, '.', 2))) && + IS_BLANKZ_AT(parser->buffer, 3)) + { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected document indicator"); + goto error; + } + + /* Check for EOF. */ + + if (IS_Z(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a quoted scalar", + start_mark, "found unexpected end of stream"); + goto error; + } + + /* Consume non-blank characters. */ + + if (!CACHE(parser, 2)) goto error; + + leading_blanks = 0; + + while (!IS_BLANKZ(parser->buffer)) + { + /* Check for an escaped single quote. */ + + if (single && CHECK_AT(parser->buffer, '\'', 0) + && CHECK_AT(parser->buffer, '\'', 1)) + { + if (!STRING_EXTEND(parser, string)) goto error; + *(string.pointer++) = '\''; + SKIP(parser); + SKIP(parser); + } + + /* Check for the right quote. */ + + else if (CHECK(parser->buffer, single ? '\'' : '"')) + { + break; + } + + /* Check for an escaped line break. */ + + else if (!single && CHECK(parser->buffer, '\\') + && IS_BREAK_AT(parser->buffer, 1)) + { + if (!CACHE(parser, 3)) goto error; + SKIP(parser); + SKIP_LINE(parser); + leading_blanks = 1; + break; + } + + /* Check for an escape sequence. */ + + else if (!single && CHECK(parser->buffer, '\\')) + { + size_t code_length = 0; + + if (!STRING_EXTEND(parser, string)) goto error; + + /* Check the escape character. */ + + switch (parser->buffer.pointer[1]) + { + case '0': + *(string.pointer++) = '\0'; + break; + + case 'a': + *(string.pointer++) = '\x07'; + break; + + case 'b': + *(string.pointer++) = '\x08'; + break; + + case 't': + case '\t': + *(string.pointer++) = '\x09'; + break; + + case 'n': + *(string.pointer++) = '\x0A'; + break; + + case 'v': + *(string.pointer++) = '\x0B'; + break; + + case 'f': + *(string.pointer++) = '\x0C'; + break; + + case 'r': + *(string.pointer++) = '\x0D'; + break; + + case 'e': + *(string.pointer++) = '\x1B'; + break; + + case ' ': + *(string.pointer++) = '\x20'; + break; + + case '"': + *(string.pointer++) = '"'; + break; + + case '/': + *(string.pointer++) = '/'; + break; + + case '\\': + *(string.pointer++) = '\\'; + break; + + case 'N': /* NEL (#x85) */ + *(string.pointer++) = '\xC2'; + *(string.pointer++) = '\x85'; + break; + + case '_': /* #xA0 */ + *(string.pointer++) = '\xC2'; + *(string.pointer++) = '\xA0'; + break; + + case 'L': /* LS (#x2028) */ + *(string.pointer++) = '\xE2'; + *(string.pointer++) = '\x80'; + *(string.pointer++) = '\xA8'; + break; + + case 'P': /* PS (#x2029) */ + *(string.pointer++) = '\xE2'; + *(string.pointer++) = '\x80'; + *(string.pointer++) = '\xA9'; + break; + + case 'x': + code_length = 2; + break; + + case 'u': + code_length = 4; + break; + + case 'U': + code_length = 8; + break; + + default: + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found unknown escape character"); + goto error; + } + + SKIP(parser); + SKIP(parser); + + /* Consume an arbitrary escape code. */ + + if (code_length) + { + unsigned int value = 0; + size_t k; + + /* Scan the character value. */ + + if (!CACHE(parser, code_length)) goto error; + + for (k = 0; k < code_length; k ++) { + if (!IS_HEX_AT(parser->buffer, k)) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "did not find expected hexdecimal number"); + goto error; + } + value = (value << 4) + AS_HEX_AT(parser->buffer, k); + } + + /* Check the value and write the character. */ + + if ((value >= 0xD800 && value <= 0xDFFF) || value > 0x10FFFF) { + yaml_parser_set_scanner_error(parser, "while parsing a quoted scalar", + start_mark, "found invalid Unicode character escape code"); + goto error; + } + + if (value <= 0x7F) { + *(string.pointer++) = value; + } + else if (value <= 0x7FF) { + *(string.pointer++) = 0xC0 + (value >> 6); + *(string.pointer++) = 0x80 + (value & 0x3F); + } + else if (value <= 0xFFFF) { + *(string.pointer++) = 0xE0 + (value >> 12); + *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F); + *(string.pointer++) = 0x80 + (value & 0x3F); + } + else { + *(string.pointer++) = 0xF0 + (value >> 18); + *(string.pointer++) = 0x80 + ((value >> 12) & 0x3F); + *(string.pointer++) = 0x80 + ((value >> 6) & 0x3F); + *(string.pointer++) = 0x80 + (value & 0x3F); + } + + /* Advance the pointer. */ + + for (k = 0; k < code_length; k ++) { + SKIP(parser); + } + } + } + + else + { + /* It is a non-escaped non-blank character. */ + + if (!READ(parser, string)) goto error; + } + + if (!CACHE(parser, 2)) goto error; + } + + /* Check if we are at the end of the scalar. */ + + /* Fix for crash unitialized value crash + * Credit for the bug and input is to OSS Fuzz + * Credit for the fix to Alex Gaynor + */ + if (!CACHE(parser, 1)) goto error; + if (CHECK(parser->buffer, single ? '\'' : '"')) + break; + + /* Consume blank characters. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)) + { + if (IS_BLANK(parser->buffer)) + { + /* Consume a space or a tab character. */ + + if (!leading_blanks) { + if (!READ(parser, whitespaces)) goto error; + } + else { + SKIP(parser); + } + } + else + { + if (!CACHE(parser, 2)) goto error; + + /* Check if it is a first line break. */ + + if (!leading_blanks) + { + CLEAR(parser, whitespaces); + if (!READ_LINE(parser, leading_break)) goto error; + leading_blanks = 1; + } + else + { + if (!READ_LINE(parser, trailing_breaks)) goto error; + } + } + if (!CACHE(parser, 1)) goto error; + } + + /* Join the whitespaces or fold line breaks. */ + + if (leading_blanks) + { + /* Do we need to fold line breaks? */ + + if (leading_break.start[0] == '\n') { + if (trailing_breaks.start[0] == '\0') { + if (!STRING_EXTEND(parser, string)) goto error; + *(string.pointer++) = ' '; + } + else { + if (!JOIN(parser, string, trailing_breaks)) goto error; + CLEAR(parser, trailing_breaks); + } + CLEAR(parser, leading_break); + } + else { + if (!JOIN(parser, string, leading_break)) goto error; + if (!JOIN(parser, string, trailing_breaks)) goto error; + CLEAR(parser, leading_break); + CLEAR(parser, trailing_breaks); + } + } + else + { + if (!JOIN(parser, string, whitespaces)) goto error; + CLEAR(parser, whitespaces); + } + } + + /* Eat the right quote. */ + + SKIP(parser); + + end_mark = parser->mark; + + /* Create a token. */ + + SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, + single ? YAML_SINGLE_QUOTED_SCALAR_STYLE : YAML_DOUBLE_QUOTED_SCALAR_STYLE, + start_mark, end_mark); + + STRING_DEL(parser, leading_break); + STRING_DEL(parser, trailing_breaks); + STRING_DEL(parser, whitespaces); + + return 1; + +error: + STRING_DEL(parser, string); + STRING_DEL(parser, leading_break); + STRING_DEL(parser, trailing_breaks); + STRING_DEL(parser, whitespaces); + + return 0; +} + +/* + * Scan a plain scalar. + */ + +static int +yaml_parser_scan_plain_scalar(yaml_parser_t *parser, yaml_token_t *token) +{ + yaml_mark_t start_mark; + yaml_mark_t end_mark; + yaml_string_t string = NULL_STRING; + yaml_string_t leading_break = NULL_STRING; + yaml_string_t trailing_breaks = NULL_STRING; + yaml_string_t whitespaces = NULL_STRING; + int leading_blanks = 0; + int indent = parser->indent+1; + + if (!STRING_INIT(parser, string, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, leading_break, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, trailing_breaks, INITIAL_STRING_SIZE)) goto error; + if (!STRING_INIT(parser, whitespaces, INITIAL_STRING_SIZE)) goto error; + + start_mark = end_mark = parser->mark; + + /* Consume the content of the plain scalar. */ + + while (1) + { + /* Check for a document indicator. */ + + if (!CACHE(parser, 4)) goto error; + + if (parser->mark.column == 0 && + ((CHECK_AT(parser->buffer, '-', 0) && + CHECK_AT(parser->buffer, '-', 1) && + CHECK_AT(parser->buffer, '-', 2)) || + (CHECK_AT(parser->buffer, '.', 0) && + CHECK_AT(parser->buffer, '.', 1) && + CHECK_AT(parser->buffer, '.', 2))) && + IS_BLANKZ_AT(parser->buffer, 3)) break; + + /* Check for a comment. */ + + if (CHECK(parser->buffer, '#')) + break; + + /* Consume non-blank characters. */ + + while (!IS_BLANKZ(parser->buffer)) + { + /* Check for "x:" + one of ',?[]{}' in the flow context. TODO: Fix the test "spec-08-13". + * This is not completely according to the spec + * See http://yaml.org/spec/1.1/#id907281 9.1.3. Plain + */ + + if (parser->flow_level + && CHECK(parser->buffer, ':') + && ( + CHECK_AT(parser->buffer, ',', 1) + || CHECK_AT(parser->buffer, '?', 1) + || CHECK_AT(parser->buffer, '[', 1) + || CHECK_AT(parser->buffer, ']', 1) + || CHECK_AT(parser->buffer, '{', 1) + || CHECK_AT(parser->buffer, '}', 1) + ) + ) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found unexpected ':'"); + goto error; + } + + /* Check for indicators that may end a plain scalar. */ + + if ((CHECK(parser->buffer, ':') && IS_BLANKZ_AT(parser->buffer, 1)) + || (parser->flow_level && + (CHECK(parser->buffer, ',') + || CHECK(parser->buffer, '[') + || CHECK(parser->buffer, ']') || CHECK(parser->buffer, '{') + || CHECK(parser->buffer, '}')))) + break; + + /* Check if we need to join whitespaces and breaks. */ + + if (leading_blanks || whitespaces.start != whitespaces.pointer) + { + if (leading_blanks) + { + /* Do we need to fold line breaks? */ + + if (leading_break.start[0] == '\n') { + if (trailing_breaks.start[0] == '\0') { + if (!STRING_EXTEND(parser, string)) goto error; + *(string.pointer++) = ' '; + } + else { + if (!JOIN(parser, string, trailing_breaks)) goto error; + CLEAR(parser, trailing_breaks); + } + CLEAR(parser, leading_break); + } + else { + if (!JOIN(parser, string, leading_break)) goto error; + if (!JOIN(parser, string, trailing_breaks)) goto error; + CLEAR(parser, leading_break); + CLEAR(parser, trailing_breaks); + } + + leading_blanks = 0; + } + else + { + if (!JOIN(parser, string, whitespaces)) goto error; + CLEAR(parser, whitespaces); + } + } + + /* Copy the character. */ + + if (!READ(parser, string)) goto error; + + end_mark = parser->mark; + + if (!CACHE(parser, 2)) goto error; + } + + /* Is it the end? */ + + if (!(IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer))) + break; + + /* Consume blank characters. */ + + if (!CACHE(parser, 1)) goto error; + + while (IS_BLANK(parser->buffer) || IS_BREAK(parser->buffer)) + { + if (IS_BLANK(parser->buffer)) + { + /* Check for tab characters that abuse indentation. */ + + if (leading_blanks && (int)parser->mark.column < indent + && IS_TAB(parser->buffer)) { + yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", + start_mark, "found a tab character that violates indentation"); + goto error; + } + + /* Consume a space or a tab character. */ + + if (!leading_blanks) { + if (!READ(parser, whitespaces)) goto error; + } + else { + SKIP(parser); + } + } + else + { + if (!CACHE(parser, 2)) goto error; + + /* Check if it is a first line break. */ + + if (!leading_blanks) + { + CLEAR(parser, whitespaces); + if (!READ_LINE(parser, leading_break)) goto error; + leading_blanks = 1; + } + else + { + if (!READ_LINE(parser, trailing_breaks)) goto error; + } + } + if (!CACHE(parser, 1)) goto error; + } + + /* Check indentation level. */ + + if (!parser->flow_level && (int)parser->mark.column < indent) + break; + } + + /* Create a token. */ + + SCALAR_TOKEN_INIT(*token, string.start, string.pointer-string.start, + YAML_PLAIN_SCALAR_STYLE, start_mark, end_mark); + + /* Note that we change the 'simple_key_allowed' flag. */ + + if (leading_blanks) { + parser->simple_key_allowed = 1; + } + + STRING_DEL(parser, leading_break); + STRING_DEL(parser, trailing_breaks); + STRING_DEL(parser, whitespaces); + + return 1; + +error: + STRING_DEL(parser, string); + STRING_DEL(parser, leading_break); + STRING_DEL(parser, trailing_breaks); + STRING_DEL(parser, whitespaces); + + return 0; +} diff --git a/src/vendors/libyaml/writer.c b/src/vendors/libyaml/writer.c new file mode 100644 index 0000000..894ef9d --- /dev/null +++ b/src/vendors/libyaml/writer.c @@ -0,0 +1,141 @@ + +#include "yaml_private.h" + +/* + * Declarations. + */ + +static int +yaml_emitter_set_writer_error(yaml_emitter_t *emitter, const char *problem); + +YAML_DECLARE(int) +yaml_emitter_flush(yaml_emitter_t *emitter); + +/* + * Set the writer error and return 0. + */ + +static int +yaml_emitter_set_writer_error(yaml_emitter_t *emitter, const char *problem) +{ + emitter->error = YAML_WRITER_ERROR; + emitter->problem = problem; + + return 0; +} + +/* + * Flush the output buffer. + */ + +YAML_DECLARE(int) +yaml_emitter_flush(yaml_emitter_t *emitter) +{ + int low, high; + + assert(emitter); /* Non-NULL emitter object is expected. */ + assert(emitter->write_handler); /* Write handler must be set. */ + assert(emitter->encoding); /* Output encoding must be set. */ + + emitter->buffer.last = emitter->buffer.pointer; + emitter->buffer.pointer = emitter->buffer.start; + + /* Check if the buffer is empty. */ + + if (emitter->buffer.start == emitter->buffer.last) { + return 1; + } + + /* If the output encoding is UTF-8, we don't need to recode the buffer. */ + + if (emitter->encoding == YAML_UTF8_ENCODING) + { + if (emitter->write_handler(emitter->write_handler_data, + emitter->buffer.start, + emitter->buffer.last - emitter->buffer.start)) { + emitter->buffer.last = emitter->buffer.start; + emitter->buffer.pointer = emitter->buffer.start; + return 1; + } + else { + return yaml_emitter_set_writer_error(emitter, "write error"); + } + } + + /* Recode the buffer into the raw buffer. */ + + low = (emitter->encoding == YAML_UTF16LE_ENCODING ? 0 : 1); + high = (emitter->encoding == YAML_UTF16LE_ENCODING ? 1 : 0); + + while (emitter->buffer.pointer != emitter->buffer.last) + { + unsigned char octet; + unsigned int width; + unsigned int value; + size_t k; + + /* + * See the "reader.c" code for more details on UTF-8 encoding. Note + * that we assume that the buffer contains a valid UTF-8 sequence. + */ + + /* Read the next UTF-8 character. */ + + octet = emitter->buffer.pointer[0]; + + width = (octet & 0x80) == 0x00 ? 1 : + (octet & 0xE0) == 0xC0 ? 2 : + (octet & 0xF0) == 0xE0 ? 3 : + (octet & 0xF8) == 0xF0 ? 4 : 0; + + value = (octet & 0x80) == 0x00 ? octet & 0x7F : + (octet & 0xE0) == 0xC0 ? octet & 0x1F : + (octet & 0xF0) == 0xE0 ? octet & 0x0F : + (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0; + + for (k = 1; k < width; k ++) { + octet = emitter->buffer.pointer[k]; + value = (value << 6) + (octet & 0x3F); + } + + emitter->buffer.pointer += width; + + /* Write the character. */ + + if (value < 0x10000) + { + emitter->raw_buffer.last[high] = value >> 8; + emitter->raw_buffer.last[low] = value & 0xFF; + + emitter->raw_buffer.last += 2; + } + else + { + /* Write the character using a surrogate pair (check "reader.c"). */ + + value -= 0x10000; + emitter->raw_buffer.last[high] = 0xD8 + (value >> 18); + emitter->raw_buffer.last[low] = (value >> 10) & 0xFF; + emitter->raw_buffer.last[high+2] = 0xDC + ((value >> 8) & 0xFF); + emitter->raw_buffer.last[low+2] = value & 0xFF; + + emitter->raw_buffer.last += 4; + } + } + + /* Write the raw buffer. */ + + if (emitter->write_handler(emitter->write_handler_data, + emitter->raw_buffer.start, + emitter->raw_buffer.last - emitter->raw_buffer.start)) { + emitter->buffer.last = emitter->buffer.start; + emitter->buffer.pointer = emitter->buffer.start; + emitter->raw_buffer.last = emitter->raw_buffer.start; + emitter->raw_buffer.pointer = emitter->raw_buffer.start; + return 1; + } + else { + return yaml_emitter_set_writer_error(emitter, "write error"); + } +} + diff --git a/src/vendors/libyaml/yaml.h b/src/vendors/libyaml/yaml.h new file mode 100644 index 0000000..70024d8 --- /dev/null +++ b/src/vendors/libyaml/yaml.h @@ -0,0 +1,1985 @@ +/** + * @file yaml.h + * @brief Public interface for libyaml. + * + * Include the header file with the code: + * @code + * #include + * @endcode + */ + +#ifndef YAML_H +#define YAML_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * @defgroup export Export Definitions + * @{ + */ + +/** The public API declaration. */ + +#if defined(__MINGW32__) +# define YAML_DECLARE(type) type +#elif defined(_WIN32) +# if defined(YAML_DECLARE_STATIC) +# define YAML_DECLARE(type) type +# elif defined(YAML_DECLARE_EXPORT) +# define YAML_DECLARE(type) __declspec(dllexport) type +# else +# define YAML_DECLARE(type) __declspec(dllimport) type +# endif +#else +# define YAML_DECLARE(type) type +#endif + +/** @} */ + +/** + * @defgroup version Version Information + * @{ + */ + +/** + * Get the library version as a string. + * + * @returns The function returns the pointer to a static string of the form + * @c "X.Y.Z", where @c X is the major version number, @c Y is a minor version + * number, and @c Z is the patch version number. + */ + +YAML_DECLARE(const char *) +yaml_get_version_string(void); + +/** + * Get the library version numbers. + * + * @param[out] major Major version number. + * @param[out] minor Minor version number. + * @param[out] patch Patch version number. + */ + +YAML_DECLARE(void) +yaml_get_version(int *major, int *minor, int *patch); + +/** @} */ + +/** + * @defgroup basic Basic Types + * @{ + */ + +/** The character type (UTF-8 octet). */ +typedef unsigned char yaml_char_t; + +/** The version directive data. */ +typedef struct yaml_version_directive_s { + /** The major version number. */ + int major; + /** The minor version number. */ + int minor; +} yaml_version_directive_t; + +/** The tag directive data. */ +typedef struct yaml_tag_directive_s { + /** The tag handle. */ + yaml_char_t *handle; + /** The tag prefix. */ + yaml_char_t *prefix; +} yaml_tag_directive_t; + +/** The stream encoding. */ +typedef enum yaml_encoding_e { + /** Let the parser choose the encoding. */ + YAML_ANY_ENCODING, + /** The default UTF-8 encoding. */ + YAML_UTF8_ENCODING, + /** The UTF-16-LE encoding with BOM. */ + YAML_UTF16LE_ENCODING, + /** The UTF-16-BE encoding with BOM. */ + YAML_UTF16BE_ENCODING +} yaml_encoding_t; + +/** Line break types. */ + +typedef enum yaml_break_e { + /** Let the parser choose the break type. */ + YAML_ANY_BREAK, + /** Use CR for line breaks (Mac style). */ + YAML_CR_BREAK, + /** Use LN for line breaks (Unix style). */ + YAML_LN_BREAK, + /** Use CR LN for line breaks (DOS style). */ + YAML_CRLN_BREAK +} yaml_break_t; + +/** Many bad things could happen with the parser and emitter. */ +typedef enum yaml_error_type_e { + /** No error is produced. */ + YAML_NO_ERROR, + + /** Cannot allocate or reallocate a block of memory. */ + YAML_MEMORY_ERROR, + + /** Cannot read or decode the input stream. */ + YAML_READER_ERROR, + /** Cannot scan the input stream. */ + YAML_SCANNER_ERROR, + /** Cannot parse the input stream. */ + YAML_PARSER_ERROR, + /** Cannot compose a YAML document. */ + YAML_COMPOSER_ERROR, + + /** Cannot write to the output stream. */ + YAML_WRITER_ERROR, + /** Cannot emit a YAML stream. */ + YAML_EMITTER_ERROR +} yaml_error_type_t; + +/** The pointer position. */ +typedef struct yaml_mark_s { + /** The position index. */ + size_t index; + + /** The position line. */ + size_t line; + + /** The position column. */ + size_t column; +} yaml_mark_t; + +/** @} */ + +/** + * @defgroup styles Node Styles + * @{ + */ + +/** Scalar styles. */ +typedef enum yaml_scalar_style_e { + /** Let the emitter choose the style. */ + YAML_ANY_SCALAR_STYLE, + + /** The plain scalar style. */ + YAML_PLAIN_SCALAR_STYLE, + + /** The single-quoted scalar style. */ + YAML_SINGLE_QUOTED_SCALAR_STYLE, + /** The double-quoted scalar style. */ + YAML_DOUBLE_QUOTED_SCALAR_STYLE, + + /** The literal scalar style. */ + YAML_LITERAL_SCALAR_STYLE, + /** The folded scalar style. */ + YAML_FOLDED_SCALAR_STYLE +} yaml_scalar_style_t; + +/** Sequence styles. */ +typedef enum yaml_sequence_style_e { + /** Let the emitter choose the style. */ + YAML_ANY_SEQUENCE_STYLE, + + /** The block sequence style. */ + YAML_BLOCK_SEQUENCE_STYLE, + /** The flow sequence style. */ + YAML_FLOW_SEQUENCE_STYLE +} yaml_sequence_style_t; + +/** Mapping styles. */ +typedef enum yaml_mapping_style_e { + /** Let the emitter choose the style. */ + YAML_ANY_MAPPING_STYLE, + + /** The block mapping style. */ + YAML_BLOCK_MAPPING_STYLE, + /** The flow mapping style. */ + YAML_FLOW_MAPPING_STYLE +/* YAML_FLOW_SET_MAPPING_STYLE */ +} yaml_mapping_style_t; + +/** @} */ + +/** + * @defgroup tokens Tokens + * @{ + */ + +/** Token types. */ +typedef enum yaml_token_type_e { + /** An empty token. */ + YAML_NO_TOKEN, + + /** A STREAM-START token. */ + YAML_STREAM_START_TOKEN, + /** A STREAM-END token. */ + YAML_STREAM_END_TOKEN, + + /** A VERSION-DIRECTIVE token. */ + YAML_VERSION_DIRECTIVE_TOKEN, + /** A TAG-DIRECTIVE token. */ + YAML_TAG_DIRECTIVE_TOKEN, + /** A DOCUMENT-START token. */ + YAML_DOCUMENT_START_TOKEN, + /** A DOCUMENT-END token. */ + YAML_DOCUMENT_END_TOKEN, + + /** A BLOCK-SEQUENCE-START token. */ + YAML_BLOCK_SEQUENCE_START_TOKEN, + /** A BLOCK-MAPPING-START token. */ + YAML_BLOCK_MAPPING_START_TOKEN, + /** A BLOCK-END token. */ + YAML_BLOCK_END_TOKEN, + + /** A FLOW-SEQUENCE-START token. */ + YAML_FLOW_SEQUENCE_START_TOKEN, + /** A FLOW-SEQUENCE-END token. */ + YAML_FLOW_SEQUENCE_END_TOKEN, + /** A FLOW-MAPPING-START token. */ + YAML_FLOW_MAPPING_START_TOKEN, + /** A FLOW-MAPPING-END token. */ + YAML_FLOW_MAPPING_END_TOKEN, + + /** A BLOCK-ENTRY token. */ + YAML_BLOCK_ENTRY_TOKEN, + /** A FLOW-ENTRY token. */ + YAML_FLOW_ENTRY_TOKEN, + /** A KEY token. */ + YAML_KEY_TOKEN, + /** A VALUE token. */ + YAML_VALUE_TOKEN, + + /** An ALIAS token. */ + YAML_ALIAS_TOKEN, + /** An ANCHOR token. */ + YAML_ANCHOR_TOKEN, + /** A TAG token. */ + YAML_TAG_TOKEN, + /** A SCALAR token. */ + YAML_SCALAR_TOKEN +} yaml_token_type_t; + +/** The token structure. */ +typedef struct yaml_token_s { + + /** The token type. */ + yaml_token_type_t type; + + /** The token data. */ + union { + + /** The stream start (for @c YAML_STREAM_START_TOKEN). */ + struct { + /** The stream encoding. */ + yaml_encoding_t encoding; + } stream_start; + + /** The alias (for @c YAML_ALIAS_TOKEN). */ + struct { + /** The alias value. */ + yaml_char_t *value; + } alias; + + /** The anchor (for @c YAML_ANCHOR_TOKEN). */ + struct { + /** The anchor value. */ + yaml_char_t *value; + } anchor; + + /** The tag (for @c YAML_TAG_TOKEN). */ + struct { + /** The tag handle. */ + yaml_char_t *handle; + /** The tag suffix. */ + yaml_char_t *suffix; + } tag; + + /** The scalar value (for @c YAML_SCALAR_TOKEN). */ + struct { + /** The scalar value. */ + yaml_char_t *value; + /** The length of the scalar value. */ + size_t length; + /** The scalar style. */ + yaml_scalar_style_t style; + } scalar; + + /** The version directive (for @c YAML_VERSION_DIRECTIVE_TOKEN). */ + struct { + /** The major version number. */ + int major; + /** The minor version number. */ + int minor; + } version_directive; + + /** The tag directive (for @c YAML_TAG_DIRECTIVE_TOKEN). */ + struct { + /** The tag handle. */ + yaml_char_t *handle; + /** The tag prefix. */ + yaml_char_t *prefix; + } tag_directive; + + } data; + + /** The beginning of the token. */ + yaml_mark_t start_mark; + /** The end of the token. */ + yaml_mark_t end_mark; + +} yaml_token_t; + +/** + * Free any memory allocated for a token object. + * + * @param[in,out] token A token object. + */ + +YAML_DECLARE(void) +yaml_token_delete(yaml_token_t *token); + +/** @} */ + +/** + * @defgroup events Events + * @{ + */ + +/** Event types. */ +typedef enum yaml_event_type_e { + /** An empty event. */ + YAML_NO_EVENT, + + /** A STREAM-START event. */ + YAML_STREAM_START_EVENT, + /** A STREAM-END event. */ + YAML_STREAM_END_EVENT, + + /** A DOCUMENT-START event. */ + YAML_DOCUMENT_START_EVENT, + /** A DOCUMENT-END event. */ + YAML_DOCUMENT_END_EVENT, + + /** An ALIAS event. */ + YAML_ALIAS_EVENT, + /** A SCALAR event. */ + YAML_SCALAR_EVENT, + + /** A SEQUENCE-START event. */ + YAML_SEQUENCE_START_EVENT, + /** A SEQUENCE-END event. */ + YAML_SEQUENCE_END_EVENT, + + /** A MAPPING-START event. */ + YAML_MAPPING_START_EVENT, + /** A MAPPING-END event. */ + YAML_MAPPING_END_EVENT +} yaml_event_type_t; + +/** The event structure. */ +typedef struct yaml_event_s { + + /** The event type. */ + yaml_event_type_t type; + + /** The event data. */ + union { + + /** The stream parameters (for @c YAML_STREAM_START_EVENT). */ + struct { + /** The document encoding. */ + yaml_encoding_t encoding; + } stream_start; + + /** The document parameters (for @c YAML_DOCUMENT_START_EVENT). */ + struct { + /** The version directive. */ + yaml_version_directive_t *version_directive; + + /** The list of tag directives. */ + struct { + /** The beginning of the tag directives list. */ + yaml_tag_directive_t *start; + /** The end of the tag directives list. */ + yaml_tag_directive_t *end; + } tag_directives; + + /** Is the document indicator implicit? */ + int implicit; + } document_start; + + /** The document end parameters (for @c YAML_DOCUMENT_END_EVENT). */ + struct { + /** Is the document end indicator implicit? */ + int implicit; + } document_end; + + /** The alias parameters (for @c YAML_ALIAS_EVENT). */ + struct { + /** The anchor. */ + yaml_char_t *anchor; + } alias; + + /** The scalar parameters (for @c YAML_SCALAR_EVENT). */ + struct { + /** The anchor. */ + yaml_char_t *anchor; + /** The tag. */ + yaml_char_t *tag; + /** The scalar value. */ + yaml_char_t *value; + /** The length of the scalar value. */ + size_t length; + /** Is the tag optional for the plain style? */ + int plain_implicit; + /** Is the tag optional for any non-plain style? */ + int quoted_implicit; + /** The scalar style. */ + yaml_scalar_style_t style; + } scalar; + + /** The sequence parameters (for @c YAML_SEQUENCE_START_EVENT). */ + struct { + /** The anchor. */ + yaml_char_t *anchor; + /** The tag. */ + yaml_char_t *tag; + /** Is the tag optional? */ + int implicit; + /** The sequence style. */ + yaml_sequence_style_t style; + } sequence_start; + + /** The mapping parameters (for @c YAML_MAPPING_START_EVENT). */ + struct { + /** The anchor. */ + yaml_char_t *anchor; + /** The tag. */ + yaml_char_t *tag; + /** Is the tag optional? */ + int implicit; + /** The mapping style. */ + yaml_mapping_style_t style; + } mapping_start; + + } data; + + /** The beginning of the event. */ + yaml_mark_t start_mark; + /** The end of the event. */ + yaml_mark_t end_mark; + +} yaml_event_t; + +/** + * Create the STREAM-START event. + * + * @param[out] event An empty event object. + * @param[in] encoding The stream encoding. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_stream_start_event_initialize(yaml_event_t *event, + yaml_encoding_t encoding); + +/** + * Create the STREAM-END event. + * + * @param[out] event An empty event object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_stream_end_event_initialize(yaml_event_t *event); + +/** + * Create the DOCUMENT-START event. + * + * The @a implicit argument is considered as a stylistic parameter and may be + * ignored by the emitter. + * + * @param[out] event An empty event object. + * @param[in] version_directive The %YAML directive value or + * @c NULL. + * @param[in] tag_directives_start The beginning of the %TAG + * directives list. + * @param[in] tag_directives_end The end of the %TAG directives + * list. + * @param[in] implicit If the document start indicator is + * implicit. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_start_event_initialize(yaml_event_t *event, + yaml_version_directive_t *version_directive, + yaml_tag_directive_t *tag_directives_start, + yaml_tag_directive_t *tag_directives_end, + int implicit); + +/** + * Create the DOCUMENT-END event. + * + * The @a implicit argument is considered as a stylistic parameter and may be + * ignored by the emitter. + * + * @param[out] event An empty event object. + * @param[in] implicit If the document end indicator is implicit. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_end_event_initialize(yaml_event_t *event, int implicit); + +/** + * Create an ALIAS event. + * + * @param[out] event An empty event object. + * @param[in] anchor The anchor value. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_alias_event_initialize(yaml_event_t *event, const yaml_char_t *anchor); + +/** + * Create a SCALAR event. + * + * The @a style argument may be ignored by the emitter. + * + * Either the @a tag attribute or one of the @a plain_implicit and + * @a quoted_implicit flags must be set. + * + * @param[out] event An empty event object. + * @param[in] anchor The scalar anchor or @c NULL. + * @param[in] tag The scalar tag or @c NULL. + * @param[in] value The scalar value. + * @param[in] length The length of the scalar value. + * @param[in] plain_implicit If the tag may be omitted for the plain + * style. + * @param[in] quoted_implicit If the tag may be omitted for any + * non-plain style. + * @param[in] style The scalar style. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_scalar_event_initialize(yaml_event_t *event, + const yaml_char_t *anchor, const yaml_char_t *tag, + const yaml_char_t *value, int length, + int plain_implicit, int quoted_implicit, + yaml_scalar_style_t style); + +/** + * Create a SEQUENCE-START event. + * + * The @a style argument may be ignored by the emitter. + * + * Either the @a tag attribute or the @a implicit flag must be set. + * + * @param[out] event An empty event object. + * @param[in] anchor The sequence anchor or @c NULL. + * @param[in] tag The sequence tag or @c NULL. + * @param[in] implicit If the tag may be omitted. + * @param[in] style The sequence style. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_sequence_start_event_initialize(yaml_event_t *event, + const yaml_char_t *anchor, const yaml_char_t *tag, int implicit, + yaml_sequence_style_t style); + +/** + * Create a SEQUENCE-END event. + * + * @param[out] event An empty event object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_sequence_end_event_initialize(yaml_event_t *event); + +/** + * Create a MAPPING-START event. + * + * The @a style argument may be ignored by the emitter. + * + * Either the @a tag attribute or the @a implicit flag must be set. + * + * @param[out] event An empty event object. + * @param[in] anchor The mapping anchor or @c NULL. + * @param[in] tag The mapping tag or @c NULL. + * @param[in] implicit If the tag may be omitted. + * @param[in] style The mapping style. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_mapping_start_event_initialize(yaml_event_t *event, + const yaml_char_t *anchor, const yaml_char_t *tag, int implicit, + yaml_mapping_style_t style); + +/** + * Create a MAPPING-END event. + * + * @param[out] event An empty event object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_mapping_end_event_initialize(yaml_event_t *event); + +/** + * Free any memory allocated for an event object. + * + * @param[in,out] event An event object. + */ + +YAML_DECLARE(void) +yaml_event_delete(yaml_event_t *event); + +/** @} */ + +/** + * @defgroup nodes Nodes + * @{ + */ + +/** The tag @c !!null with the only possible value: @c null. */ +#define YAML_NULL_TAG "tag:yaml.org,2002:null" +/** The tag @c !!bool with the values: @c true and @c false. */ +#define YAML_BOOL_TAG "tag:yaml.org,2002:bool" +/** The tag @c !!str for string values. */ +#define YAML_STR_TAG "tag:yaml.org,2002:str" +/** The tag @c !!int for integer values. */ +#define YAML_INT_TAG "tag:yaml.org,2002:int" +/** The tag @c !!float for float values. */ +#define YAML_FLOAT_TAG "tag:yaml.org,2002:float" +/** The tag @c !!timestamp for date and time values. */ +#define YAML_TIMESTAMP_TAG "tag:yaml.org,2002:timestamp" + +/** The tag @c !!seq is used to denote sequences. */ +#define YAML_SEQ_TAG "tag:yaml.org,2002:seq" +/** The tag @c !!map is used to denote mapping. */ +#define YAML_MAP_TAG "tag:yaml.org,2002:map" + +/** The default scalar tag is @c !!str. */ +#define YAML_DEFAULT_SCALAR_TAG YAML_STR_TAG +/** The default sequence tag is @c !!seq. */ +#define YAML_DEFAULT_SEQUENCE_TAG YAML_SEQ_TAG +/** The default mapping tag is @c !!map. */ +#define YAML_DEFAULT_MAPPING_TAG YAML_MAP_TAG + +/** Node types. */ +typedef enum yaml_node_type_e { + /** An empty node. */ + YAML_NO_NODE, + + /** A scalar node. */ + YAML_SCALAR_NODE, + /** A sequence node. */ + YAML_SEQUENCE_NODE, + /** A mapping node. */ + YAML_MAPPING_NODE +} yaml_node_type_t; + +/** The forward definition of a document node structure. */ +typedef struct yaml_node_s yaml_node_t; + +/** An element of a sequence node. */ +typedef int yaml_node_item_t; + +/** An element of a mapping node. */ +typedef struct yaml_node_pair_s { + /** The key of the element. */ + int key; + /** The value of the element. */ + int value; +} yaml_node_pair_t; + +/** The node structure. */ +struct yaml_node_s { + + /** The node type. */ + yaml_node_type_t type; + + /** The node tag. */ + yaml_char_t *tag; + + /** The node data. */ + union { + + /** The scalar parameters (for @c YAML_SCALAR_NODE). */ + struct { + /** The scalar value. */ + yaml_char_t *value; + /** The length of the scalar value. */ + size_t length; + /** The scalar style. */ + yaml_scalar_style_t style; + } scalar; + + /** The sequence parameters (for @c YAML_SEQUENCE_NODE). */ + struct { + /** The stack of sequence items. */ + struct { + /** The beginning of the stack. */ + yaml_node_item_t *start; + /** The end of the stack. */ + yaml_node_item_t *end; + /** The top of the stack. */ + yaml_node_item_t *top; + } items; + /** The sequence style. */ + yaml_sequence_style_t style; + } sequence; + + /** The mapping parameters (for @c YAML_MAPPING_NODE). */ + struct { + /** The stack of mapping pairs (key, value). */ + struct { + /** The beginning of the stack. */ + yaml_node_pair_t *start; + /** The end of the stack. */ + yaml_node_pair_t *end; + /** The top of the stack. */ + yaml_node_pair_t *top; + } pairs; + /** The mapping style. */ + yaml_mapping_style_t style; + } mapping; + + } data; + + /** The beginning of the node. */ + yaml_mark_t start_mark; + /** The end of the node. */ + yaml_mark_t end_mark; + +}; + +/** The document structure. */ +typedef struct yaml_document_s { + + /** The document nodes. */ + struct { + /** The beginning of the stack. */ + yaml_node_t *start; + /** The end of the stack. */ + yaml_node_t *end; + /** The top of the stack. */ + yaml_node_t *top; + } nodes; + + /** The version directive. */ + yaml_version_directive_t *version_directive; + + /** The list of tag directives. */ + struct { + /** The beginning of the tag directives list. */ + yaml_tag_directive_t *start; + /** The end of the tag directives list. */ + yaml_tag_directive_t *end; + } tag_directives; + + /** Is the document start indicator implicit? */ + int start_implicit; + /** Is the document end indicator implicit? */ + int end_implicit; + + /** The beginning of the document. */ + yaml_mark_t start_mark; + /** The end of the document. */ + yaml_mark_t end_mark; + +} yaml_document_t; + +/** + * Create a YAML document. + * + * @param[out] document An empty document object. + * @param[in] version_directive The %YAML directive value or + * @c NULL. + * @param[in] tag_directives_start The beginning of the %TAG + * directives list. + * @param[in] tag_directives_end The end of the %TAG directives + * list. + * @param[in] start_implicit If the document start indicator is + * implicit. + * @param[in] end_implicit If the document end indicator is + * implicit. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_initialize(yaml_document_t *document, + yaml_version_directive_t *version_directive, + yaml_tag_directive_t *tag_directives_start, + yaml_tag_directive_t *tag_directives_end, + int start_implicit, int end_implicit); + +/** + * Delete a YAML document and all its nodes. + * + * @param[in,out] document A document object. + */ + +YAML_DECLARE(void) +yaml_document_delete(yaml_document_t *document); + +/** + * Get a node of a YAML document. + * + * The pointer returned by this function is valid until any of the functions + * modifying the documents are called. + * + * @param[in] document A document object. + * @param[in] index The node id. + * + * @returns the node objct or @c NULL if @c node_id is out of range. + */ + +YAML_DECLARE(yaml_node_t *) +yaml_document_get_node(yaml_document_t *document, int index); + +/** + * Get the root of a YAML document node. + * + * The root object is the first object added to the document. + * + * The pointer returned by this function is valid until any of the functions + * modifying the documents are called. + * + * An empty document produced by the parser signifies the end of a YAML + * stream. + * + * @param[in] document A document object. + * + * @returns the node object or @c NULL if the document is empty. + */ + +YAML_DECLARE(yaml_node_t *) +yaml_document_get_root_node(yaml_document_t *document); + +/** + * Create a SCALAR node and attach it to the document. + * + * The @a style argument may be ignored by the emitter. + * + * @param[in,out] document A document object. + * @param[in] tag The scalar tag. + * @param[in] value The scalar value. + * @param[in] length The length of the scalar value. + * @param[in] style The scalar style. + * + * @returns the node id or @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_add_scalar(yaml_document_t *document, + const yaml_char_t *tag, const yaml_char_t *value, int length, + yaml_scalar_style_t style); + +/** + * Create a SEQUENCE node and attach it to the document. + * + * The @a style argument may be ignored by the emitter. + * + * @param[in,out] document A document object. + * @param[in] tag The sequence tag. + * @param[in] style The sequence style. + * + * @returns the node id or @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_add_sequence(yaml_document_t *document, + const yaml_char_t *tag, yaml_sequence_style_t style); + +/** + * Create a MAPPING node and attach it to the document. + * + * The @a style argument may be ignored by the emitter. + * + * @param[in,out] document A document object. + * @param[in] tag The sequence tag. + * @param[in] style The sequence style. + * + * @returns the node id or @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_add_mapping(yaml_document_t *document, + const yaml_char_t *tag, yaml_mapping_style_t style); + +/** + * Add an item to a SEQUENCE node. + * + * @param[in,out] document A document object. + * @param[in] sequence The sequence node id. + * @param[in] item The item node id. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_append_sequence_item(yaml_document_t *document, + int sequence, int item); + +/** + * Add a pair of a key and a value to a MAPPING node. + * + * @param[in,out] document A document object. + * @param[in] mapping The mapping node id. + * @param[in] key The key node id. + * @param[in] value The value node id. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_document_append_mapping_pair(yaml_document_t *document, + int mapping, int key, int value); + +/** @} */ + +/** + * @defgroup parser Parser Definitions + * @{ + */ + +/** + * The prototype of a read handler. + * + * The read handler is called when the parser needs to read more bytes from the + * source. The handler should write not more than @a size bytes to the @a + * buffer. The number of written bytes should be set to the @a length variable. + * + * @param[in,out] data A pointer to an application data specified by + * yaml_parser_set_input(). + * @param[out] buffer The buffer to write the data from the source. + * @param[in] size The size of the buffer. + * @param[out] size_read The actual number of bytes read from the source. + * + * @returns On success, the handler should return @c 1. If the handler failed, + * the returned value should be @c 0. On EOF, the handler should set the + * @a size_read to @c 0 and return @c 1. + */ + +typedef int yaml_read_handler_t(void *data, unsigned char *buffer, size_t size, + size_t *size_read); + +/** + * This structure holds information about a potential simple key. + */ + +typedef struct yaml_simple_key_s { + /** Is a simple key possible? */ + int possible; + + /** Is a simple key required? */ + int required; + + /** The number of the token. */ + size_t token_number; + + /** The position mark. */ + yaml_mark_t mark; +} yaml_simple_key_t; + +/** + * The states of the parser. + */ +typedef enum yaml_parser_state_e { + /** Expect STREAM-START. */ + YAML_PARSE_STREAM_START_STATE, + /** Expect the beginning of an implicit document. */ + YAML_PARSE_IMPLICIT_DOCUMENT_START_STATE, + /** Expect DOCUMENT-START. */ + YAML_PARSE_DOCUMENT_START_STATE, + /** Expect the content of a document. */ + YAML_PARSE_DOCUMENT_CONTENT_STATE, + /** Expect DOCUMENT-END. */ + YAML_PARSE_DOCUMENT_END_STATE, + + /** Expect a block node. */ + YAML_PARSE_BLOCK_NODE_STATE, + /** Expect a block node or indentless sequence. */ + YAML_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE, + /** Expect a flow node. */ + YAML_PARSE_FLOW_NODE_STATE, + /** Expect the first entry of a block sequence. */ + YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE, + /** Expect an entry of a block sequence. */ + YAML_PARSE_BLOCK_SEQUENCE_ENTRY_STATE, + + /** Expect an entry of an indentless sequence. */ + YAML_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE, + /** Expect the first key of a block mapping. */ + YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE, + /** Expect a block mapping key. */ + YAML_PARSE_BLOCK_MAPPING_KEY_STATE, + /** Expect a block mapping value. */ + YAML_PARSE_BLOCK_MAPPING_VALUE_STATE, + /** Expect the first entry of a flow sequence. */ + YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE, + + /** Expect an entry of a flow sequence. */ + YAML_PARSE_FLOW_SEQUENCE_ENTRY_STATE, + /** Expect a key of an ordered mapping. */ + YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE, + /** Expect a value of an ordered mapping. */ + YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE, + /** Expect the and of an ordered mapping entry. */ + YAML_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE, + /** Expect the first key of a flow mapping. */ + YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE, + /** Expect a key of a flow mapping. */ + + YAML_PARSE_FLOW_MAPPING_KEY_STATE, + /** Expect a value of a flow mapping. */ + YAML_PARSE_FLOW_MAPPING_VALUE_STATE, + /** Expect an empty value of a flow mapping. */ + YAML_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE, + /** Expect nothing. */ + YAML_PARSE_END_STATE +} yaml_parser_state_t; + +/** + * This structure holds aliases data. + */ + +typedef struct yaml_alias_data_s { + /** The anchor. */ + yaml_char_t *anchor; + /** The node id. */ + int index; + /** The anchor mark. */ + yaml_mark_t mark; +} yaml_alias_data_t; + +/** + * The parser structure. + * + * All members are internal. Manage the structure using the @c yaml_parser_ + * family of functions. + */ + +typedef struct yaml_parser_s { + + /** + * @name Error handling + * @{ + */ + + /** Error type. */ + yaml_error_type_t error; + /** Error description. */ + const char *problem; + /** The byte about which the problem occured. */ + size_t problem_offset; + /** The problematic value (@c -1 is none). */ + int problem_value; + /** The problem position. */ + yaml_mark_t problem_mark; + /** The error context. */ + const char *context; + /** The context position. */ + yaml_mark_t context_mark; + + /** + * @} + */ + + /** + * @name Reader stuff + * @{ + */ + + /** Read handler. */ + yaml_read_handler_t *read_handler; + + /** A pointer for passing to the read handler. */ + void *read_handler_data; + + /** Standard (string or file) input data. */ + union { + /** String input data. */ + struct { + /** The string start pointer. */ + const unsigned char *start; + /** The string end pointer. */ + const unsigned char *end; + /** The string current position. */ + const unsigned char *current; + } string; + + /** File input data. */ + FILE *file; + } input; + + /** EOF flag */ + int eof; + + /** The working buffer. */ + struct { + /** The beginning of the buffer. */ + yaml_char_t *start; + /** The end of the buffer. */ + yaml_char_t *end; + /** The current position of the buffer. */ + yaml_char_t *pointer; + /** The last filled position of the buffer. */ + yaml_char_t *last; + } buffer; + + /* The number of unread characters in the buffer. */ + size_t unread; + + /** The raw buffer. */ + struct { + /** The beginning of the buffer. */ + unsigned char *start; + /** The end of the buffer. */ + unsigned char *end; + /** The current position of the buffer. */ + unsigned char *pointer; + /** The last filled position of the buffer. */ + unsigned char *last; + } raw_buffer; + + /** The input encoding. */ + yaml_encoding_t encoding; + + /** The offset of the current position (in bytes). */ + size_t offset; + + /** The mark of the current position. */ + yaml_mark_t mark; + + /** + * @} + */ + + /** + * @name Scanner stuff + * @{ + */ + + /** Have we started to scan the input stream? */ + int stream_start_produced; + + /** Have we reached the end of the input stream? */ + int stream_end_produced; + + /** The number of unclosed '[' and '{' indicators. */ + int flow_level; + + /** The tokens queue. */ + struct { + /** The beginning of the tokens queue. */ + yaml_token_t *start; + /** The end of the tokens queue. */ + yaml_token_t *end; + /** The head of the tokens queue. */ + yaml_token_t *head; + /** The tail of the tokens queue. */ + yaml_token_t *tail; + } tokens; + + /** The number of tokens fetched from the queue. */ + size_t tokens_parsed; + + /** Does the tokens queue contain a token ready for dequeueing. */ + int token_available; + + /** The indentation levels stack. */ + struct { + /** The beginning of the stack. */ + int *start; + /** The end of the stack. */ + int *end; + /** The top of the stack. */ + int *top; + } indents; + + /** The current indentation level. */ + int indent; + + /** May a simple key occur at the current position? */ + int simple_key_allowed; + + /** The stack of simple keys. */ + struct { + /** The beginning of the stack. */ + yaml_simple_key_t *start; + /** The end of the stack. */ + yaml_simple_key_t *end; + /** The top of the stack. */ + yaml_simple_key_t *top; + } simple_keys; + + /** + * @} + */ + + /** + * @name Parser stuff + * @{ + */ + + /** The parser states stack. */ + struct { + /** The beginning of the stack. */ + yaml_parser_state_t *start; + /** The end of the stack. */ + yaml_parser_state_t *end; + /** The top of the stack. */ + yaml_parser_state_t *top; + } states; + + /** The current parser state. */ + yaml_parser_state_t state; + + /** The stack of marks. */ + struct { + /** The beginning of the stack. */ + yaml_mark_t *start; + /** The end of the stack. */ + yaml_mark_t *end; + /** The top of the stack. */ + yaml_mark_t *top; + } marks; + + /** The list of TAG directives. */ + struct { + /** The beginning of the list. */ + yaml_tag_directive_t *start; + /** The end of the list. */ + yaml_tag_directive_t *end; + /** The top of the list. */ + yaml_tag_directive_t *top; + } tag_directives; + + /** + * @} + */ + + /** + * @name Dumper stuff + * @{ + */ + + /** The alias data. */ + struct { + /** The beginning of the list. */ + yaml_alias_data_t *start; + /** The end of the list. */ + yaml_alias_data_t *end; + /** The top of the list. */ + yaml_alias_data_t *top; + } aliases; + + /** The currently parsed document. */ + yaml_document_t *document; + + /** + * @} + */ + +} yaml_parser_t; + +/** + * Initialize a parser. + * + * This function creates a new parser object. An application is responsible + * for destroying the object using the yaml_parser_delete() function. + * + * @param[out] parser An empty parser object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_parser_initialize(yaml_parser_t *parser); + +/** + * Destroy a parser. + * + * @param[in,out] parser A parser object. + */ + +YAML_DECLARE(void) +yaml_parser_delete(yaml_parser_t *parser); + +/** + * Set a string input. + * + * Note that the @a input pointer must be valid while the @a parser object + * exists. The application is responsible for destroing @a input after + * destroying the @a parser. + * + * @param[in,out] parser A parser object. + * @param[in] input A source data. + * @param[in] size The length of the source data in bytes. + */ + +YAML_DECLARE(void) +yaml_parser_set_input_string(yaml_parser_t *parser, + const unsigned char *input, size_t size); + +/** + * Set a file input. + * + * @a file should be a file object open for reading. The application is + * responsible for closing the @a file. + * + * @param[in,out] parser A parser object. + * @param[in] file An open file. + */ + +YAML_DECLARE(void) +yaml_parser_set_input_file(yaml_parser_t *parser, FILE *file); + +/** + * Set a generic input handler. + * + * @param[in,out] parser A parser object. + * @param[in] handler A read handler. + * @param[in] data Any application data for passing to the read + * handler. + */ + +YAML_DECLARE(void) +yaml_parser_set_input(yaml_parser_t *parser, + yaml_read_handler_t *handler, void *data); + +/** + * Set the source encoding. + * + * @param[in,out] parser A parser object. + * @param[in] encoding The source encoding. + */ + +YAML_DECLARE(void) +yaml_parser_set_encoding(yaml_parser_t *parser, yaml_encoding_t encoding); + +/** + * Scan the input stream and produce the next token. + * + * Call the function subsequently to produce a sequence of tokens corresponding + * to the input stream. The initial token has the type + * @c YAML_STREAM_START_TOKEN while the ending token has the type + * @c YAML_STREAM_END_TOKEN. + * + * An application is responsible for freeing any buffers associated with the + * produced token object using the @c yaml_token_delete function. + * + * An application must not alternate the calls of yaml_parser_scan() with the + * calls of yaml_parser_parse() or yaml_parser_load(). Doing this will break + * the parser. + * + * @param[in,out] parser A parser object. + * @param[out] token An empty token object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_parser_scan(yaml_parser_t *parser, yaml_token_t *token); + +/** + * Parse the input stream and produce the next parsing event. + * + * Call the function subsequently to produce a sequence of events corresponding + * to the input stream. The initial event has the type + * @c YAML_STREAM_START_EVENT while the ending event has the type + * @c YAML_STREAM_END_EVENT. + * + * An application is responsible for freeing any buffers associated with the + * produced event object using the yaml_event_delete() function. + * + * An application must not alternate the calls of yaml_parser_parse() with the + * calls of yaml_parser_scan() or yaml_parser_load(). Doing this will break the + * parser. + * + * @param[in,out] parser A parser object. + * @param[out] event An empty event object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_parser_parse(yaml_parser_t *parser, yaml_event_t *event); + +/** + * Parse the input stream and produce the next YAML document. + * + * Call this function subsequently to produce a sequence of documents + * constituting the input stream. + * + * If the produced document has no root node, it means that the document + * end has been reached. + * + * An application is responsible for freeing any data associated with the + * produced document object using the yaml_document_delete() function. + * + * An application must not alternate the calls of yaml_parser_load() with the + * calls of yaml_parser_scan() or yaml_parser_parse(). Doing this will break + * the parser. + * + * @param[in,out] parser A parser object. + * @param[out] document An empty document object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_parser_load(yaml_parser_t *parser, yaml_document_t *document); + +/** @} */ + +/** + * @defgroup emitter Emitter Definitions + * @{ + */ + +/** + * The prototype of a write handler. + * + * The write handler is called when the emitter needs to flush the accumulated + * characters to the output. The handler should write @a size bytes of the + * @a buffer to the output. + * + * @param[in,out] data A pointer to an application data specified by + * yaml_emitter_set_output(). + * @param[in] buffer The buffer with bytes to be written. + * @param[in] size The size of the buffer. + * + * @returns On success, the handler should return @c 1. If the handler failed, + * the returned value should be @c 0. + */ + +typedef int yaml_write_handler_t(void *data, unsigned char *buffer, size_t size); + +/** The emitter states. */ +typedef enum yaml_emitter_state_e { + /** Expect STREAM-START. */ + YAML_EMIT_STREAM_START_STATE, + /** Expect the first DOCUMENT-START or STREAM-END. */ + YAML_EMIT_FIRST_DOCUMENT_START_STATE, + /** Expect DOCUMENT-START or STREAM-END. */ + YAML_EMIT_DOCUMENT_START_STATE, + /** Expect the content of a document. */ + YAML_EMIT_DOCUMENT_CONTENT_STATE, + /** Expect DOCUMENT-END. */ + YAML_EMIT_DOCUMENT_END_STATE, + + /** Expect the first item of a flow sequence. */ + YAML_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE, + /** Expect an item of a flow sequence. */ + YAML_EMIT_FLOW_SEQUENCE_ITEM_STATE, + /** Expect the first key of a flow mapping. */ + YAML_EMIT_FLOW_MAPPING_FIRST_KEY_STATE, + /** Expect a key of a flow mapping. */ + YAML_EMIT_FLOW_MAPPING_KEY_STATE, + /** Expect a value for a simple key of a flow mapping. */ + YAML_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE, + + /** Expect a value of a flow mapping. */ + YAML_EMIT_FLOW_MAPPING_VALUE_STATE, + /** Expect the first item of a block sequence. */ + YAML_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE, + /** Expect an item of a block sequence. */ + YAML_EMIT_BLOCK_SEQUENCE_ITEM_STATE, + /** Expect the first key of a block mapping. */ + YAML_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE, + /** Expect the key of a block mapping. */ + YAML_EMIT_BLOCK_MAPPING_KEY_STATE, + + /** Expect a value for a simple key of a block mapping. */ + YAML_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE, + /** Expect a value of a block mapping. */ + YAML_EMIT_BLOCK_MAPPING_VALUE_STATE, + /** Expect nothing. */ + YAML_EMIT_END_STATE +} yaml_emitter_state_t; + + +/* This is needed for C++ */ + +typedef struct yaml_anchors_s { + /** The number of references. */ + int references; + /** The anchor id. */ + int anchor; + /** If the node has been emitted? */ + int serialized; +} yaml_anchors_t; + +/** + * The emitter structure. + * + * All members are internal. Manage the structure using the @c yaml_emitter_ + * family of functions. + */ + +typedef struct yaml_emitter_s { + + /** + * @name Error handling + * @{ + */ + + /** Error type. */ + yaml_error_type_t error; + /** Error description. */ + const char *problem; + + /** + * @} + */ + + /** + * @name Writer stuff + * @{ + */ + + /** Write handler. */ + yaml_write_handler_t *write_handler; + + /** A pointer for passing to the write handler. */ + void *write_handler_data; + + /** Standard (string or file) output data. */ + union { + /** String output data. */ + struct { + /** The buffer pointer. */ + unsigned char *buffer; + /** The buffer size. */ + size_t size; + /** The number of written bytes. */ + size_t *size_written; + } string; + + /** File output data. */ + FILE *file; + } output; + + /** The working buffer. */ + struct { + /** The beginning of the buffer. */ + yaml_char_t *start; + /** The end of the buffer. */ + yaml_char_t *end; + /** The current position of the buffer. */ + yaml_char_t *pointer; + /** The last filled position of the buffer. */ + yaml_char_t *last; + } buffer; + + /** The raw buffer. */ + struct { + /** The beginning of the buffer. */ + unsigned char *start; + /** The end of the buffer. */ + unsigned char *end; + /** The current position of the buffer. */ + unsigned char *pointer; + /** The last filled position of the buffer. */ + unsigned char *last; + } raw_buffer; + + /** The stream encoding. */ + yaml_encoding_t encoding; + + /** + * @} + */ + + /** + * @name Emitter stuff + * @{ + */ + + /** If the output is in the canonical style? */ + int canonical; + /** The number of indentation spaces. */ + int best_indent; + /** The preferred width of the output lines. */ + int best_width; + /** Allow unescaped non-ASCII characters? */ + int unicode; + /** The preferred line break. */ + yaml_break_t line_break; + + /** The stack of states. */ + struct { + /** The beginning of the stack. */ + yaml_emitter_state_t *start; + /** The end of the stack. */ + yaml_emitter_state_t *end; + /** The top of the stack. */ + yaml_emitter_state_t *top; + } states; + + /** The current emitter state. */ + yaml_emitter_state_t state; + + /** The event queue. */ + struct { + /** The beginning of the event queue. */ + yaml_event_t *start; + /** The end of the event queue. */ + yaml_event_t *end; + /** The head of the event queue. */ + yaml_event_t *head; + /** The tail of the event queue. */ + yaml_event_t *tail; + } events; + + /** The stack of indentation levels. */ + struct { + /** The beginning of the stack. */ + int *start; + /** The end of the stack. */ + int *end; + /** The top of the stack. */ + int *top; + } indents; + + /** The list of tag directives. */ + struct { + /** The beginning of the list. */ + yaml_tag_directive_t *start; + /** The end of the list. */ + yaml_tag_directive_t *end; + /** The top of the list. */ + yaml_tag_directive_t *top; + } tag_directives; + + /** The current indentation level. */ + int indent; + + /** The current flow level. */ + int flow_level; + + /** Is it the document root context? */ + int root_context; + /** Is it a sequence context? */ + int sequence_context; + /** Is it a mapping context? */ + int mapping_context; + /** Is it a simple mapping key context? */ + int simple_key_context; + + /** The current line. */ + int line; + /** The current column. */ + int column; + /** If the last character was a whitespace? */ + int whitespace; + /** If the last character was an indentation character (' ', '-', '?', ':')? */ + int indention; + /** If an explicit document end is required? */ + int open_ended; + + /** Anchor analysis. */ + struct { + /** The anchor value. */ + yaml_char_t *anchor; + /** The anchor length. */ + size_t anchor_length; + /** Is it an alias? */ + int alias; + } anchor_data; + + /** Tag analysis. */ + struct { + /** The tag handle. */ + yaml_char_t *handle; + /** The tag handle length. */ + size_t handle_length; + /** The tag suffix. */ + yaml_char_t *suffix; + /** The tag suffix length. */ + size_t suffix_length; + } tag_data; + + /** Scalar analysis. */ + struct { + /** The scalar value. */ + yaml_char_t *value; + /** The scalar length. */ + size_t length; + /** Does the scalar contain line breaks? */ + int multiline; + /** Can the scalar be expessed in the flow plain style? */ + int flow_plain_allowed; + /** Can the scalar be expressed in the block plain style? */ + int block_plain_allowed; + /** Can the scalar be expressed in the single quoted style? */ + int single_quoted_allowed; + /** Can the scalar be expressed in the literal or folded styles? */ + int block_allowed; + /** The output style. */ + yaml_scalar_style_t style; + } scalar_data; + + /** + * @} + */ + + /** + * @name Dumper stuff + * @{ + */ + + /** If the stream was already opened? */ + int opened; + /** If the stream was already closed? */ + int closed; + + /** The information associated with the document nodes. */ + yaml_anchors_t *anchors; + + /** The last assigned anchor id. */ + int last_anchor_id; + + /** The currently emitted document. */ + yaml_document_t *document; + + /** + * @} + */ + +} yaml_emitter_t; + +/** + * Initialize an emitter. + * + * This function creates a new emitter object. An application is responsible + * for destroying the object using the yaml_emitter_delete() function. + * + * @param[out] emitter An empty parser object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_emitter_initialize(yaml_emitter_t *emitter); + +/** + * Destroy an emitter. + * + * @param[in,out] emitter An emitter object. + */ + +YAML_DECLARE(void) +yaml_emitter_delete(yaml_emitter_t *emitter); + +/** + * Set a string output. + * + * The emitter will write the output characters to the @a output buffer of the + * size @a size. The emitter will set @a size_written to the number of written + * bytes. If the buffer is smaller than required, the emitter produces the + * YAML_WRITE_ERROR error. + * + * @param[in,out] emitter An emitter object. + * @param[in] output An output buffer. + * @param[in] size The buffer size. + * @param[in] size_written The pointer to save the number of written + * bytes. + */ + +YAML_DECLARE(void) +yaml_emitter_set_output_string(yaml_emitter_t *emitter, + unsigned char *output, size_t size, size_t *size_written); + +/** + * Set a file output. + * + * @a file should be a file object open for writing. The application is + * responsible for closing the @a file. + * + * @param[in,out] emitter An emitter object. + * @param[in] file An open file. + */ + +YAML_DECLARE(void) +yaml_emitter_set_output_file(yaml_emitter_t *emitter, FILE *file); + +/** + * Set a generic output handler. + * + * @param[in,out] emitter An emitter object. + * @param[in] handler A write handler. + * @param[in] data Any application data for passing to the write + * handler. + */ + +YAML_DECLARE(void) +yaml_emitter_set_output(yaml_emitter_t *emitter, + yaml_write_handler_t *handler, void *data); + +/** + * Set the output encoding. + * + * @param[in,out] emitter An emitter object. + * @param[in] encoding The output encoding. + */ + +YAML_DECLARE(void) +yaml_emitter_set_encoding(yaml_emitter_t *emitter, yaml_encoding_t encoding); + +/** + * Set if the output should be in the "canonical" format as in the YAML + * specification. + * + * @param[in,out] emitter An emitter object. + * @param[in] canonical If the output is canonical. + */ + +YAML_DECLARE(void) +yaml_emitter_set_canonical(yaml_emitter_t *emitter, int canonical); + +/** + * Set the indentation increment. + * + * @param[in,out] emitter An emitter object. + * @param[in] indent The indentation increment (1 < . < 10). + */ + +YAML_DECLARE(void) +yaml_emitter_set_indent(yaml_emitter_t *emitter, int indent); + +/** + * Set the preferred line width. @c -1 means unlimited. + * + * @param[in,out] emitter An emitter object. + * @param[in] width The preferred line width. + */ + +YAML_DECLARE(void) +yaml_emitter_set_width(yaml_emitter_t *emitter, int width); + +/** + * Set if unescaped non-ASCII characters are allowed. + * + * @param[in,out] emitter An emitter object. + * @param[in] unicode If unescaped Unicode characters are allowed. + */ + +YAML_DECLARE(void) +yaml_emitter_set_unicode(yaml_emitter_t *emitter, int unicode); + +/** + * Set the preferred line break. + * + * @param[in,out] emitter An emitter object. + * @param[in] line_break The preferred line break. + */ + +YAML_DECLARE(void) +yaml_emitter_set_break(yaml_emitter_t *emitter, yaml_break_t line_break); + +/** + * Emit an event. + * + * The event object may be generated using the yaml_parser_parse() function. + * The emitter takes the responsibility for the event object and destroys its + * content after it is emitted. The event object is destroyed even if the + * function fails. + * + * @param[in,out] emitter An emitter object. + * @param[in,out] event An event object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_emitter_emit(yaml_emitter_t *emitter, yaml_event_t *event); + +/** + * Start a YAML stream. + * + * This function should be used before yaml_emitter_dump() is called. + * + * @param[in,out] emitter An emitter object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_emitter_open(yaml_emitter_t *emitter); + +/** + * Finish a YAML stream. + * + * This function should be used after yaml_emitter_dump() is called. + * + * @param[in,out] emitter An emitter object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_emitter_close(yaml_emitter_t *emitter); + +/** + * Emit a YAML document. + * + * The documen object may be generated using the yaml_parser_load() function + * or the yaml_document_initialize() function. The emitter takes the + * responsibility for the document object and destroys its content after + * it is emitted. The document object is destroyed even if the function fails. + * + * @param[in,out] emitter An emitter object. + * @param[in,out] document A document object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_emitter_dump(yaml_emitter_t *emitter, yaml_document_t *document); + +/** + * Flush the accumulated characters to the output. + * + * @param[in,out] emitter An emitter object. + * + * @returns @c 1 if the function succeeded, @c 0 on error. + */ + +YAML_DECLARE(int) +yaml_emitter_flush(yaml_emitter_t *emitter); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef YAML_H */ + diff --git a/src/vendors/libyaml/yaml_private.h b/src/vendors/libyaml/yaml_private.h new file mode 100644 index 0000000..ebf3d97 --- /dev/null +++ b/src/vendors/libyaml/yaml_private.h @@ -0,0 +1,684 @@ +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +/* + * Memory management. + */ + +YAML_DECLARE(void *) +yaml_malloc(size_t size); + +YAML_DECLARE(void *) +yaml_realloc(void *ptr, size_t size); + +YAML_DECLARE(void) +yaml_free(void *ptr); + +YAML_DECLARE(yaml_char_t *) +yaml_strdup(const yaml_char_t *); + +/* + * Reader: Ensure that the buffer contains at least `length` characters. + */ + +YAML_DECLARE(int) +yaml_parser_update_buffer(yaml_parser_t *parser, size_t length); + +/* + * Scanner: Ensure that the token stack contains at least one token ready. + */ + +YAML_DECLARE(int) +yaml_parser_fetch_more_tokens(yaml_parser_t *parser); + +/* + * The size of the input raw buffer. + */ + +#define INPUT_RAW_BUFFER_SIZE 16384 + +/* + * The size of the input buffer. + * + * It should be possible to decode the whole raw buffer. + */ + +#define INPUT_BUFFER_SIZE (INPUT_RAW_BUFFER_SIZE*3) + +/* + * The size of the output buffer. + */ + +#define OUTPUT_BUFFER_SIZE 16384 + +/* + * The size of the output raw buffer. + * + * It should be possible to encode the whole output buffer. + */ + +#define OUTPUT_RAW_BUFFER_SIZE (OUTPUT_BUFFER_SIZE*2+2) + +/* + * The maximum size of a YAML input file. + * This used to be PTRDIFF_MAX, but that's not entirely portable + * because stdint.h isn't available on all platforms. + * It is not entirely clear why this isn't the maximum value + * that can fit into the parser->offset field. + */ + +#define MAX_FILE_SIZE (~(size_t)0 / 2) + + +/* + * The size of other stacks and queues. + */ + +#define INITIAL_STACK_SIZE 16 +#define INITIAL_QUEUE_SIZE 16 +#define INITIAL_STRING_SIZE 16 + +/* + * Buffer management. + */ + +#define BUFFER_INIT(context,buffer,size) \ + (((buffer).start = (yaml_char_t *)yaml_malloc(size)) ? \ + ((buffer).last = (buffer).pointer = (buffer).start, \ + (buffer).end = (buffer).start+(size), \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define BUFFER_DEL(context,buffer) \ + (yaml_free((buffer).start), \ + (buffer).start = (buffer).pointer = (buffer).end = 0) + +/* + * String management. + */ + +typedef struct { + yaml_char_t *start; + yaml_char_t *end; + yaml_char_t *pointer; +} yaml_string_t; + +YAML_DECLARE(int) +yaml_string_extend(yaml_char_t **start, + yaml_char_t **pointer, yaml_char_t **end); + +YAML_DECLARE(int) +yaml_string_join( + yaml_char_t **a_start, yaml_char_t **a_pointer, yaml_char_t **a_end, + yaml_char_t **b_start, yaml_char_t **b_pointer, yaml_char_t **b_end); + +#define NULL_STRING { NULL, NULL, NULL } + +#define STRING(string,length) { (string), (string)+(length), (string) } + +#define STRING_ASSIGN(value,string,length) \ + ((value).start = (string), \ + (value).end = (string)+(length), \ + (value).pointer = (string)) + +#define STRING_INIT(context,string,size) \ + (((string).start = YAML_MALLOC(size)) ? \ + ((string).pointer = (string).start, \ + (string).end = (string).start+(size), \ + memset((string).start, 0, (size)), \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define STRING_DEL(context,string) \ + (yaml_free((string).start), \ + (string).start = (string).pointer = (string).end = 0) + +#define STRING_EXTEND(context,string) \ + ((((string).pointer+5 < (string).end) \ + || yaml_string_extend(&(string).start, \ + &(string).pointer, &(string).end)) ? \ + 1 : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define CLEAR(context,string) \ + ((string).pointer = (string).start, \ + memset((string).start, 0, (string).end-(string).start)) + +#define JOIN(context,string_a,string_b) \ + ((yaml_string_join(&(string_a).start, &(string_a).pointer, \ + &(string_a).end, &(string_b).start, \ + &(string_b).pointer, &(string_b).end)) ? \ + ((string_b).pointer = (string_b).start, \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +/* + * String check operations. + */ + +/* + * Check the octet at the specified position. + */ + +#define CHECK_AT(string,octet,offset) \ + ((string).pointer[offset] == (yaml_char_t)(octet)) + +/* + * Check the current octet in the buffer. + */ + +#define CHECK(string,octet) (CHECK_AT((string),(octet),0)) + +/* + * Check if the character at the specified position is an alphabetical + * character, a digit, '_', or '-'. + */ + +#define IS_ALPHA_AT(string,offset) \ + (((string).pointer[offset] >= (yaml_char_t) '0' && \ + (string).pointer[offset] <= (yaml_char_t) '9') || \ + ((string).pointer[offset] >= (yaml_char_t) 'A' && \ + (string).pointer[offset] <= (yaml_char_t) 'Z') || \ + ((string).pointer[offset] >= (yaml_char_t) 'a' && \ + (string).pointer[offset] <= (yaml_char_t) 'z') || \ + (string).pointer[offset] == '_' || \ + (string).pointer[offset] == '-') + +#define IS_ALPHA(string) IS_ALPHA_AT((string),0) + +/* + * Check if the character at the specified position is a digit. + */ + +#define IS_DIGIT_AT(string,offset) \ + (((string).pointer[offset] >= (yaml_char_t) '0' && \ + (string).pointer[offset] <= (yaml_char_t) '9')) + +#define IS_DIGIT(string) IS_DIGIT_AT((string),0) + +/* + * Get the value of a digit. + */ + +#define AS_DIGIT_AT(string,offset) \ + ((string).pointer[offset] - (yaml_char_t) '0') + +#define AS_DIGIT(string) AS_DIGIT_AT((string),0) + +/* + * Check if the character at the specified position is a hex-digit. + */ + +#define IS_HEX_AT(string,offset) \ + (((string).pointer[offset] >= (yaml_char_t) '0' && \ + (string).pointer[offset] <= (yaml_char_t) '9') || \ + ((string).pointer[offset] >= (yaml_char_t) 'A' && \ + (string).pointer[offset] <= (yaml_char_t) 'F') || \ + ((string).pointer[offset] >= (yaml_char_t) 'a' && \ + (string).pointer[offset] <= (yaml_char_t) 'f')) + +#define IS_HEX(string) IS_HEX_AT((string),0) + +/* + * Get the value of a hex-digit. + */ + +#define AS_HEX_AT(string,offset) \ + (((string).pointer[offset] >= (yaml_char_t) 'A' && \ + (string).pointer[offset] <= (yaml_char_t) 'F') ? \ + ((string).pointer[offset] - (yaml_char_t) 'A' + 10) : \ + ((string).pointer[offset] >= (yaml_char_t) 'a' && \ + (string).pointer[offset] <= (yaml_char_t) 'f') ? \ + ((string).pointer[offset] - (yaml_char_t) 'a' + 10) : \ + ((string).pointer[offset] - (yaml_char_t) '0')) + +#define AS_HEX(string) AS_HEX_AT((string),0) + +/* + * Check if the character is ASCII. + */ + +#define IS_ASCII_AT(string,offset) \ + ((string).pointer[offset] <= (yaml_char_t) '\x7F') + +#define IS_ASCII(string) IS_ASCII_AT((string),0) + +/* + * Check if the character can be printed unescaped. + */ + +#define IS_PRINTABLE_AT(string,offset) \ + (((string).pointer[offset] == 0x0A) /* . == #x0A */ \ + || ((string).pointer[offset] >= 0x20 /* #x20 <= . <= #x7E */ \ + && (string).pointer[offset] <= 0x7E) \ + || ((string).pointer[offset] == 0xC2 /* #0xA0 <= . <= #xD7FF */ \ + && (string).pointer[offset+1] >= 0xA0) \ + || ((string).pointer[offset] > 0xC2 \ + && (string).pointer[offset] < 0xED) \ + || ((string).pointer[offset] == 0xED \ + && (string).pointer[offset+1] < 0xA0) \ + || ((string).pointer[offset] == 0xEE) \ + || ((string).pointer[offset] == 0xEF /* #xE000 <= . <= #xFFFD */ \ + && !((string).pointer[offset+1] == 0xBB /* && . != #xFEFF */ \ + && (string).pointer[offset+2] == 0xBF) \ + && !((string).pointer[offset+1] == 0xBF \ + && ((string).pointer[offset+2] == 0xBE \ + || (string).pointer[offset+2] == 0xBF)))) + +#define IS_PRINTABLE(string) IS_PRINTABLE_AT((string),0) + +/* + * Check if the character at the specified position is NUL. + */ + +#define IS_Z_AT(string,offset) CHECK_AT((string),'\0',(offset)) + +#define IS_Z(string) IS_Z_AT((string),0) + +/* + * Check if the character at the specified position is BOM. + */ + +#define IS_BOM_AT(string,offset) \ + (CHECK_AT((string),'\xEF',(offset)) \ + && CHECK_AT((string),'\xBB',(offset)+1) \ + && CHECK_AT((string),'\xBF',(offset)+2)) /* BOM (#xFEFF) */ + +#define IS_BOM(string) IS_BOM_AT(string,0) + +/* + * Check if the character at the specified position is space. + */ + +#define IS_SPACE_AT(string,offset) CHECK_AT((string),' ',(offset)) + +#define IS_SPACE(string) IS_SPACE_AT((string),0) + +/* + * Check if the character at the specified position is tab. + */ + +#define IS_TAB_AT(string,offset) CHECK_AT((string),'\t',(offset)) + +#define IS_TAB(string) IS_TAB_AT((string),0) + +/* + * Check if the character at the specified position is blank (space or tab). + */ + +#define IS_BLANK_AT(string,offset) \ + (IS_SPACE_AT((string),(offset)) || IS_TAB_AT((string),(offset))) + +#define IS_BLANK(string) IS_BLANK_AT((string),0) + +/* + * Check if the character at the specified position is a line break. + */ + +#define IS_BREAK_AT(string,offset) \ + (CHECK_AT((string),'\r',(offset)) /* CR (#xD)*/ \ + || CHECK_AT((string),'\n',(offset)) /* LF (#xA) */ \ + || (CHECK_AT((string),'\xC2',(offset)) \ + && CHECK_AT((string),'\x85',(offset)+1)) /* NEL (#x85) */ \ + || (CHECK_AT((string),'\xE2',(offset)) \ + && CHECK_AT((string),'\x80',(offset)+1) \ + && CHECK_AT((string),'\xA8',(offset)+2)) /* LS (#x2028) */ \ + || (CHECK_AT((string),'\xE2',(offset)) \ + && CHECK_AT((string),'\x80',(offset)+1) \ + && CHECK_AT((string),'\xA9',(offset)+2))) /* PS (#x2029) */ + +#define IS_BREAK(string) IS_BREAK_AT((string),0) + +#define IS_CRLF_AT(string,offset) \ + (CHECK_AT((string),'\r',(offset)) && CHECK_AT((string),'\n',(offset)+1)) + +#define IS_CRLF(string) IS_CRLF_AT((string),0) + +/* + * Check if the character is a line break or NUL. + */ + +#define IS_BREAKZ_AT(string,offset) \ + (IS_BREAK_AT((string),(offset)) || IS_Z_AT((string),(offset))) + +#define IS_BREAKZ(string) IS_BREAKZ_AT((string),0) + +/* + * Check if the character is a line break, space, or NUL. + */ + +#define IS_SPACEZ_AT(string,offset) \ + (IS_SPACE_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset))) + +#define IS_SPACEZ(string) IS_SPACEZ_AT((string),0) + +/* + * Check if the character is a line break, space, tab, or NUL. + */ + +#define IS_BLANKZ_AT(string,offset) \ + (IS_BLANK_AT((string),(offset)) || IS_BREAKZ_AT((string),(offset))) + +#define IS_BLANKZ(string) IS_BLANKZ_AT((string),0) + +/* + * Determine the width of the character. + */ + +#define WIDTH_AT(string,offset) \ + (((string).pointer[offset] & 0x80) == 0x00 ? 1 : \ + ((string).pointer[offset] & 0xE0) == 0xC0 ? 2 : \ + ((string).pointer[offset] & 0xF0) == 0xE0 ? 3 : \ + ((string).pointer[offset] & 0xF8) == 0xF0 ? 4 : 0) + +#define WIDTH(string) WIDTH_AT((string),0) + +/* + * Move the string pointer to the next character. + */ + +#define MOVE(string) ((string).pointer += WIDTH((string))) + +/* + * Copy a character and move the pointers of both strings. + */ + +#define COPY(string_a,string_b) \ + ((*(string_b).pointer & 0x80) == 0x00 ? \ + (*((string_a).pointer++) = *((string_b).pointer++)) : \ + (*(string_b).pointer & 0xE0) == 0xC0 ? \ + (*((string_a).pointer++) = *((string_b).pointer++), \ + *((string_a).pointer++) = *((string_b).pointer++)) : \ + (*(string_b).pointer & 0xF0) == 0xE0 ? \ + (*((string_a).pointer++) = *((string_b).pointer++), \ + *((string_a).pointer++) = *((string_b).pointer++), \ + *((string_a).pointer++) = *((string_b).pointer++)) : \ + (*(string_b).pointer & 0xF8) == 0xF0 ? \ + (*((string_a).pointer++) = *((string_b).pointer++), \ + *((string_a).pointer++) = *((string_b).pointer++), \ + *((string_a).pointer++) = *((string_b).pointer++), \ + *((string_a).pointer++) = *((string_b).pointer++)) : 0) + +/* + * Stack and queue management. + */ + +YAML_DECLARE(int) +yaml_stack_extend(void **start, void **top, void **end); + +YAML_DECLARE(int) +yaml_queue_extend(void **start, void **head, void **tail, void **end); + +#define STACK_INIT(context,stack,type) \ + (((stack).start = (type)yaml_malloc(INITIAL_STACK_SIZE*sizeof(*(stack).start))) ? \ + ((stack).top = (stack).start, \ + (stack).end = (stack).start+INITIAL_STACK_SIZE, \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define STACK_DEL(context,stack) \ + (yaml_free((stack).start), \ + (stack).start = (stack).top = (stack).end = 0) + +#define STACK_EMPTY(context,stack) \ + ((stack).start == (stack).top) + +#define STACK_LIMIT(context,stack,size) \ + ((stack).top - (stack).start < (size) ? \ + 1 : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define PUSH(context,stack,value) \ + (((stack).top != (stack).end \ + || yaml_stack_extend((void **)&(stack).start, \ + (void **)&(stack).top, (void **)&(stack).end)) ? \ + (*((stack).top++) = value, \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define POP(context,stack) \ + (*(--(stack).top)) + +#define QUEUE_INIT(context,queue,size,type) \ + (((queue).start = (type)yaml_malloc((size)*sizeof(*(queue).start))) ? \ + ((queue).head = (queue).tail = (queue).start, \ + (queue).end = (queue).start+(size), \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define QUEUE_DEL(context,queue) \ + (yaml_free((queue).start), \ + (queue).start = (queue).head = (queue).tail = (queue).end = 0) + +#define QUEUE_EMPTY(context,queue) \ + ((queue).head == (queue).tail) + +#define ENQUEUE(context,queue,value) \ + (((queue).tail != (queue).end \ + || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \ + (void **)&(queue).tail, (void **)&(queue).end)) ? \ + (*((queue).tail++) = value, \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +#define DEQUEUE(context,queue) \ + (*((queue).head++)) + +#define QUEUE_INSERT(context,queue,index,value) \ + (((queue).tail != (queue).end \ + || yaml_queue_extend((void **)&(queue).start, (void **)&(queue).head, \ + (void **)&(queue).tail, (void **)&(queue).end)) ? \ + (memmove((queue).head+(index)+1,(queue).head+(index), \ + ((queue).tail-(queue).head-(index))*sizeof(*(queue).start)), \ + *((queue).head+(index)) = value, \ + (queue).tail++, \ + 1) : \ + ((context)->error = YAML_MEMORY_ERROR, \ + 0)) + +/* + * Token initializers. + */ + +#define TOKEN_INIT(token,token_type,token_start_mark,token_end_mark) \ + (memset(&(token), 0, sizeof(yaml_token_t)), \ + (token).type = (token_type), \ + (token).start_mark = (token_start_mark), \ + (token).end_mark = (token_end_mark)) + +#define STREAM_START_TOKEN_INIT(token,token_encoding,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_STREAM_START_TOKEN,(start_mark),(end_mark)), \ + (token).data.stream_start.encoding = (token_encoding)) + +#define STREAM_END_TOKEN_INIT(token,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_STREAM_END_TOKEN,(start_mark),(end_mark))) + +#define ALIAS_TOKEN_INIT(token,token_value,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_ALIAS_TOKEN,(start_mark),(end_mark)), \ + (token).data.alias.value = (token_value)) + +#define ANCHOR_TOKEN_INIT(token,token_value,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_ANCHOR_TOKEN,(start_mark),(end_mark)), \ + (token).data.anchor.value = (token_value)) + +#define TAG_TOKEN_INIT(token,token_handle,token_suffix,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_TAG_TOKEN,(start_mark),(end_mark)), \ + (token).data.tag.handle = (token_handle), \ + (token).data.tag.suffix = (token_suffix)) + +#define SCALAR_TOKEN_INIT(token,token_value,token_length,token_style,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_SCALAR_TOKEN,(start_mark),(end_mark)), \ + (token).data.scalar.value = (token_value), \ + (token).data.scalar.length = (token_length), \ + (token).data.scalar.style = (token_style)) + +#define VERSION_DIRECTIVE_TOKEN_INIT(token,token_major,token_minor,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_VERSION_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \ + (token).data.version_directive.major = (token_major), \ + (token).data.version_directive.minor = (token_minor)) + +#define TAG_DIRECTIVE_TOKEN_INIT(token,token_handle,token_prefix,start_mark,end_mark) \ + (TOKEN_INIT((token),YAML_TAG_DIRECTIVE_TOKEN,(start_mark),(end_mark)), \ + (token).data.tag_directive.handle = (token_handle), \ + (token).data.tag_directive.prefix = (token_prefix)) + +/* + * Event initializers. + */ + +#define EVENT_INIT(event,event_type,event_start_mark,event_end_mark) \ + (memset(&(event), 0, sizeof(yaml_event_t)), \ + (event).type = (event_type), \ + (event).start_mark = (event_start_mark), \ + (event).end_mark = (event_end_mark)) + +#define STREAM_START_EVENT_INIT(event,event_encoding,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_STREAM_START_EVENT,(start_mark),(end_mark)), \ + (event).data.stream_start.encoding = (event_encoding)) + +#define STREAM_END_EVENT_INIT(event,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_STREAM_END_EVENT,(start_mark),(end_mark))) + +#define DOCUMENT_START_EVENT_INIT(event,event_version_directive, \ + event_tag_directives_start,event_tag_directives_end,event_implicit,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_DOCUMENT_START_EVENT,(start_mark),(end_mark)), \ + (event).data.document_start.version_directive = (event_version_directive), \ + (event).data.document_start.tag_directives.start = (event_tag_directives_start), \ + (event).data.document_start.tag_directives.end = (event_tag_directives_end), \ + (event).data.document_start.implicit = (event_implicit)) + +#define DOCUMENT_END_EVENT_INIT(event,event_implicit,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_DOCUMENT_END_EVENT,(start_mark),(end_mark)), \ + (event).data.document_end.implicit = (event_implicit)) + +#define ALIAS_EVENT_INIT(event,event_anchor,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_ALIAS_EVENT,(start_mark),(end_mark)), \ + (event).data.alias.anchor = (event_anchor)) + +#define SCALAR_EVENT_INIT(event,event_anchor,event_tag,event_value,event_length, \ + event_plain_implicit, event_quoted_implicit,event_style,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_SCALAR_EVENT,(start_mark),(end_mark)), \ + (event).data.scalar.anchor = (event_anchor), \ + (event).data.scalar.tag = (event_tag), \ + (event).data.scalar.value = (event_value), \ + (event).data.scalar.length = (event_length), \ + (event).data.scalar.plain_implicit = (event_plain_implicit), \ + (event).data.scalar.quoted_implicit = (event_quoted_implicit), \ + (event).data.scalar.style = (event_style)) + +#define SEQUENCE_START_EVENT_INIT(event,event_anchor,event_tag, \ + event_implicit,event_style,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_SEQUENCE_START_EVENT,(start_mark),(end_mark)), \ + (event).data.sequence_start.anchor = (event_anchor), \ + (event).data.sequence_start.tag = (event_tag), \ + (event).data.sequence_start.implicit = (event_implicit), \ + (event).data.sequence_start.style = (event_style)) + +#define SEQUENCE_END_EVENT_INIT(event,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_SEQUENCE_END_EVENT,(start_mark),(end_mark))) + +#define MAPPING_START_EVENT_INIT(event,event_anchor,event_tag, \ + event_implicit,event_style,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_MAPPING_START_EVENT,(start_mark),(end_mark)), \ + (event).data.mapping_start.anchor = (event_anchor), \ + (event).data.mapping_start.tag = (event_tag), \ + (event).data.mapping_start.implicit = (event_implicit), \ + (event).data.mapping_start.style = (event_style)) + +#define MAPPING_END_EVENT_INIT(event,start_mark,end_mark) \ + (EVENT_INIT((event),YAML_MAPPING_END_EVENT,(start_mark),(end_mark))) + +/* + * Document initializer. + */ + +#define DOCUMENT_INIT(document,document_nodes_start,document_nodes_end, \ + document_version_directive,document_tag_directives_start, \ + document_tag_directives_end,document_start_implicit, \ + document_end_implicit,document_start_mark,document_end_mark) \ + (memset(&(document), 0, sizeof(yaml_document_t)), \ + (document).nodes.start = (document_nodes_start), \ + (document).nodes.end = (document_nodes_end), \ + (document).nodes.top = (document_nodes_start), \ + (document).version_directive = (document_version_directive), \ + (document).tag_directives.start = (document_tag_directives_start), \ + (document).tag_directives.end = (document_tag_directives_end), \ + (document).start_implicit = (document_start_implicit), \ + (document).end_implicit = (document_end_implicit), \ + (document).start_mark = (document_start_mark), \ + (document).end_mark = (document_end_mark)) + +/* + * Node initializers. + */ + +#define NODE_INIT(node,node_type,node_tag,node_start_mark,node_end_mark) \ + (memset(&(node), 0, sizeof(yaml_node_t)), \ + (node).type = (node_type), \ + (node).tag = (node_tag), \ + (node).start_mark = (node_start_mark), \ + (node).end_mark = (node_end_mark)) + +#define SCALAR_NODE_INIT(node,node_tag,node_value,node_length, \ + node_style,start_mark,end_mark) \ + (NODE_INIT((node),YAML_SCALAR_NODE,(node_tag),(start_mark),(end_mark)), \ + (node).data.scalar.value = (node_value), \ + (node).data.scalar.length = (node_length), \ + (node).data.scalar.style = (node_style)) + +#define SEQUENCE_NODE_INIT(node,node_tag,node_items_start,node_items_end, \ + node_style,start_mark,end_mark) \ + (NODE_INIT((node),YAML_SEQUENCE_NODE,(node_tag),(start_mark),(end_mark)), \ + (node).data.sequence.items.start = (node_items_start), \ + (node).data.sequence.items.end = (node_items_end), \ + (node).data.sequence.items.top = (node_items_start), \ + (node).data.sequence.style = (node_style)) + +#define MAPPING_NODE_INIT(node,node_tag,node_pairs_start,node_pairs_end, \ + node_style,start_mark,end_mark) \ + (NODE_INIT((node),YAML_MAPPING_NODE,(node_tag),(start_mark),(end_mark)), \ + (node).data.mapping.pairs.start = (node_pairs_start), \ + (node).data.mapping.pairs.end = (node_pairs_end), \ + (node).data.mapping.pairs.top = (node_pairs_start), \ + (node).data.mapping.style = (node_style)) + +/* Strict C compiler warning helpers */ + +#if defined(__clang__) || defined(__GNUC__) +# define HASATTRIBUTE_UNUSED +#endif +#ifdef HASATTRIBUTE_UNUSED +# define __attribute__unused__ __attribute__((__unused__)) +#else +# define __attribute__unused__ +#endif + +/* Shim arguments are arguments that must be included in your function, + * but serve no purpose inside. Silence compiler warnings. */ +#define SHIM(a) /*@unused@*/ a __attribute__unused__ + +/* UNUSED_PARAM() marks a shim argument in the body to silence compiler warnings */ +#ifdef __clang__ +# define UNUSED_PARAM(a) (void)(a); +#else +# define UNUSED_PARAM(a) /*@-noeffect*/if (0) (void)(a)/*@=noeffect*/; +#endif + +#define YAML_MALLOC_STATIC(type) (type*)yaml_malloc(sizeof(type)) +#define YAML_MALLOC(size) (yaml_char_t *)yaml_malloc(size) diff --git a/src/yaml_config.c b/src/yaml_config.c new file mode 100644 index 0000000..e161983 --- /dev/null +++ b/src/yaml_config.c @@ -0,0 +1,868 @@ +/* + * yaml_config.c + * + * YAML loader for the talker's main config file. + * + * Parses files/datafiles/config.yaml using libyaml's event-stream API and + * populates the global SYS_OBJECT amsys (and optionally creates netlink + * entries for sites). + * + * The structure of the YAML mirrors the legacy "INIT" / "SITES" sections + * of the flat config; see parse_init_section() and parse_sites_section() + * in src/amnuts.c for the source-of-truth validation rules that this + * loader mirrors. + */ + +#include "defines.h" +#include "globals.h" +#include "prototypes.h" +#include "yaml.h" + +#include +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Forward declarations */ +/* ------------------------------------------------------------------ */ + +static void load_server(const char *path, yaml_parser_t *p); +static void load_ports(const char *path, yaml_parser_t *p); +static void load_timeouts(const char *path, yaml_parser_t *p); +static void load_defaults(const char *path, yaml_parser_t *p); +static void load_moderation(const char *path, yaml_parser_t *p); +static void load_system(const char *path, yaml_parser_t *p); +static void load_users(const char *path, yaml_parser_t *p); +static void load_messages(const char *path, yaml_parser_t *p); +#ifdef NETLINKS +static void load_sites(const char *path, yaml_parser_t *p); +#endif + +static void skip_value(const char *path, yaml_parser_t *p); + +static int parse_crash_action(const char *path, yaml_event_t *ev); +static int parse_resolve_ip(const char *path, yaml_event_t *ev); +static int parse_ban_swearing(const char *path, yaml_event_t *ev); +#ifdef NETLINKS +static enum nl_allow parse_nl_allow(const char *path, yaml_event_t *ev); +#endif + +static int level_required(const char *path, yaml_event_t *ev, + const char *field); + +/* ------------------------------------------------------------------ */ +/* Tiny helpers */ +/* ------------------------------------------------------------------ */ + +static char * +scalar_str(yaml_event_t *ev) +{ + return (char *)ev->data.scalar.value; +} + +/* + * Consume one value: scalar, sequence (recursively), or mapping + * (recursively). Used as a generic skip but currently only invoked on + * unknown-key error paths if we ever want to soften the parser. + */ +static void +skip_value(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev; + int depth = 0; + + do { + yaml_next(path, p, &ev); + switch (ev.type) { + case YAML_SEQUENCE_START_EVENT: + case YAML_MAPPING_START_EVENT: + ++depth; + break; + case YAML_SEQUENCE_END_EVENT: + case YAML_MAPPING_END_EVENT: + --depth; + break; + default: + break; + } + yaml_event_delete(&ev); + } while (depth > 0); +} + +static int +level_required(const char *path, yaml_event_t *ev, const char *field) +{ + int lvl = yaml_scalar_to_level(path, ev); + if (lvl == NUM_LEVELS) { + yaml_die(path, NULL, + "%s does not allow NONE; expected a level name", field); + } + return lvl; +} + +static int +parse_crash_action(const char *path, yaml_event_t *ev) +{ + const char *v = scalar_str(ev); + if (!strcasecmp(v, "NONE")) return 0; + if (!strcasecmp(v, "SHUTDOWN")) return 1; + if (!strcasecmp(v, "REBOOT")) return 2; + if (!strcasecmp(v, "SEAMLESS")) return 3; + yaml_die(path, NULL, + "system.crash_action must be NONE, SHUTDOWN, REBOOT or SEAMLESS" + " (got '%s')", v); +} + +static int +parse_resolve_ip(const char *path, yaml_event_t *ev) +{ + const char *v = scalar_str(ev); + if (!strcasecmp(v, "OFF")) return 0; + if (!strcasecmp(v, "AUTO")) return 1; + if (!strcasecmp(v, "MANUAL")) return 2; + if (!strcasecmp(v, "IDENTD")) return 3; + yaml_die(path, NULL, + "system.resolve_ip must be OFF, AUTO, MANUAL or IDENTD" + " (got '%s')", v); +} + +static int +parse_ban_swearing(const char *path, yaml_event_t *ev) +{ + const char *v = scalar_str(ev); + if (!strcasecmp(v, "OFF")) return SBOFF; + if (!strcasecmp(v, "MIN")) return SBMIN; + if (!strcasecmp(v, "MAX")) return SBMAX; + yaml_die(path, NULL, + "moderation.ban_swearing must be OFF, MIN or MAX (got '%s')", v); +} + +#ifdef NETLINKS +static enum nl_allow +parse_nl_allow(const char *path, yaml_event_t *ev) +{ + const char *v = scalar_str(ev); + if (!strcasecmp(v, "ALL")) return ALL; + if (!strcasecmp(v, "IN")) return IN; + if (!strcasecmp(v, "OUT")) return OUT; + yaml_die(path, NULL, + "site.allow must be ALL, IN or OUT (got '%s')", v); +} +#endif + +/* ------------------------------------------------------------------ */ +/* server: section */ +/* ------------------------------------------------------------------ */ + +static void +load_ports(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "server.ports mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in server.ports"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "server.ports value"); + + const char *v = scalar_str(&val); + if (strlen(v) >= MAXSERV) { + yaml_die(path, NULL, + "server.ports.%s: port string too long", k); + } + + if (!strcmp(k, "main")) { + strcpy(amsys->mport_port, v); + } else if (!strcmp(k, "wiz")) { +#ifdef WIZPORT + strcpy(amsys->wport_port, v); +#endif + } else if (!strcmp(k, "link")) { +#ifdef NETLINKS + strcpy(amsys->nlink_port, v); +#endif + } else { + yaml_die(path, NULL, + "unknown key 'server.ports.%s'", k); + } + yaml_event_delete(&val); + } +} + +static void +load_server(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "server mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in server mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + if (!strcmp(k, "ports")) { + load_ports(path, p); + continue; + } + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "server.* value"); + + if (!strcmp(k, "verification")) { + const char *v = scalar_str(&val); + if (strlen(v) > VERIFY_LEN) { + yaml_die(path, NULL, + "server.verification too long (max %d chars)", + VERIFY_LEN); + } +#ifdef NETLINKS + strcpy(amsys->verification, v); +#endif + } else if (!strcmp(k, "max_users")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 1) { + yaml_die(path, NULL, + "server.max_users must be >= 1 (got %d)", n); + } + amsys->max_users = n; + } else if (!strcmp(k, "max_clones")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 0) { + yaml_die(path, NULL, + "server.max_clones must be >= 0 (got %d)", n); + } + amsys->max_clones = n; + } else if (!strcmp(k, "heartbeat")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 1) { + yaml_die(path, NULL, + "server.heartbeat must be >= 1 (got %d)", n); + } + amsys->heartbeat = n; + } else { + yaml_die(path, NULL, "unknown key 'server.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* timeouts: section */ +/* ------------------------------------------------------------------ */ + +static void +load_timeouts(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "timeouts mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in timeouts mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "timeouts.* value"); + + if (!strcmp(k, "login_idle")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 10) { + yaml_die(path, NULL, + "timeouts.login_idle must be >= 10 (got %d)", n); + } + amsys->login_idle_time = n; + } else if (!strcmp(k, "user_idle")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 10) { + yaml_die(path, NULL, + "timeouts.user_idle must be >= 10 (got %d)", n); + } + amsys->user_idle_time = n; + } else if (!strcmp(k, "timeout_afks")) { + amsys->time_out_afks = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "timeout_maxlevel")) { + amsys->time_out_maxlevel = + level_required(path, &val, "timeouts.timeout_maxlevel"); + } else { + yaml_die(path, NULL, "unknown key 'timeouts.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* defaults: section */ +/* ------------------------------------------------------------------ */ + +static void +load_defaults(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "defaults mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in defaults mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "defaults.* value"); + + if (!strcmp(k, "colour")) { + amsys->colour_def = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "prompt")) { + amsys->prompt_def = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "charecho")) { + amsys->charecho_def = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "passwordecho")) { + amsys->passwordecho_def = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "warp_room")) { + const char *v = scalar_str(&val); + if (strlen(v) > ROOM_NAME_LEN) { + yaml_die(path, NULL, + "defaults.warp_room: room name too long (max %d)", + ROOM_NAME_LEN); + } + strcpy(amsys->default_warp, v); + } else if (!strcmp(k, "jail_room")) { + const char *v = scalar_str(&val); + if (strlen(v) > ROOM_NAME_LEN) { + yaml_die(path, NULL, + "defaults.jail_room: room name too long (max %d)", + ROOM_NAME_LEN); + } + strcpy(amsys->default_jail, v); + } else if (!strcmp(k, "bank_room")) { + const char *v = scalar_str(&val); + if (strlen(v) > ROOM_NAME_LEN) { + yaml_die(path, NULL, + "defaults.bank_room: room name too long (max %d)", + ROOM_NAME_LEN); + } +#ifdef GAMES + strcpy(amsys->default_bank, v); +#endif + } else if (!strcmp(k, "shoot_room")) { + const char *v = scalar_str(&val); + if (strlen(v) > ROOM_NAME_LEN) { + yaml_die(path, NULL, + "defaults.shoot_room: room name too long (max %d)", + ROOM_NAME_LEN); + } +#ifdef GAMES + strcpy(amsys->default_shoot, v); +#endif + } else { + yaml_die(path, NULL, "unknown key 'defaults.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* moderation: section */ +/* ------------------------------------------------------------------ */ + +static void +load_moderation(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "moderation mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in moderation mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "moderation.* value"); + + if (!strcmp(k, "ban_swearing")) { + amsys->ban_swearing = parse_ban_swearing(path, &val); + } else if (!strcmp(k, "minlogin_level")) { + /* NONE is allowed here — sentinel NUM_LEVELS */ + amsys->minlogin_level = yaml_scalar_to_level(path, &val); + } else if (!strcmp(k, "min_private")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 1) { + yaml_die(path, NULL, + "moderation.min_private must be >= 1 (got %d)", n); + } + amsys->min_private_users = n; + } else if (!strcmp(k, "ignore_mp_level")) { + amsys->ignore_mp_level = + level_required(path, &val, "moderation.ignore_mp_level"); + } else if (!strcmp(k, "gatecrash_level")) { + amsys->gatecrash_level = + level_required(path, &val, "moderation.gatecrash_level"); + } else if (!strcmp(k, "boot_off_min")) { + amsys->boot_off_min = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "flood_protect")) { + amsys->flood_protect = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else { + yaml_die(path, NULL, "unknown key 'moderation.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* system: section */ +/* ------------------------------------------------------------------ */ + +static void +load_system(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "system mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in system mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "system.* value"); + + if (!strcmp(k, "logging")) { + bool b = yaml_scalar_to_bool(path, &val); + amsys->logging = b ? (SYSLOG | REQLOG | NETLOG | ERRLOG) : 0; + } else if (!strcmp(k, "ignore_sigterm")) { + amsys->ignore_sigterm = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "auto_connect")) { + int b = yaml_scalar_to_bool(path, &val) ? 1 : 0; +#ifdef NETLINKS + amsys->auto_connect = b; +#else + (void)b; +#endif + } else if (!strcmp(k, "crash_action")) { + amsys->crash_action = parse_crash_action(path, &val); + } else if (!strcmp(k, "resolve_ip")) { + amsys->resolve_ip = parse_resolve_ip(path, &val); + } else if (!strcmp(k, "random_motds")) { + amsys->random_motds = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else { + yaml_die(path, NULL, "unknown key 'system.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* users: section */ +/* ------------------------------------------------------------------ */ + +static void +load_users(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "users mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in users mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "users.* value"); + + if (!strcmp(k, "auto_purge")) { + bool b = yaml_scalar_to_bool(path, &val); + if (!b) { + amsys->auto_purge_date = -1; + } else { + amsys->auto_purge_date = time(0) + 86400 - 1; + amsys->auto_purge_date -= amsys->auto_purge_date % 86400; + } + } else if (!strcmp(k, "allow_recaps")) { + amsys->allow_recaps = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "auto_promote")) { + amsys->auto_promote = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "personal_rooms")) { + amsys->personal_rooms = yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "startup_room_parse")) { + amsys->startup_room_parse = + yaml_scalar_to_bool(path, &val) ? 1 : 0; + } else if (!strcmp(k, "rem_user_maxlevel")) { + int lvl = level_required(path, &val, "users.rem_user_maxlevel"); +#ifdef NETLINKS + amsys->rem_user_maxlevel = lvl; +#else + (void)lvl; +#endif + } else if (!strcmp(k, "rem_user_deflevel")) { + int lvl = level_required(path, &val, "users.rem_user_deflevel"); +#ifdef NETLINKS + amsys->rem_user_deflevel = lvl; +#else + (void)lvl; +#endif + } else if (!strcmp(k, "wizport_level")) { + int lvl = level_required(path, &val, "users.wizport_level"); +#ifdef WIZPORT + amsys->wizport_level = lvl; +#else + (void)lvl; +#endif + } else { + yaml_die(path, NULL, "unknown key 'users.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* messages: section */ +/* ------------------------------------------------------------------ */ + +static void +load_messages(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "messages mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in messages mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "messages.* value"); + + if (!strcmp(k, "lifetime_days")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 1) { + yaml_die(path, NULL, + "messages.lifetime_days must be >= 1 (got %d)", n); + } + amsys->mesg_life = n; + } else if (!strcmp(k, "check_time")) { + const char *v = scalar_str(&val); + int hh = -1, mm = -1; + /* sscanf %d:%d works whether v is "01:00" or "1:0". + * We never see YAML-parsed sexagesimal here because libyaml + * gives us the raw scalar string. */ + if (sscanf(v, "%d:%d", &hh, &mm) != 2 || + hh < 0 || hh > 23 || + mm < 0 || mm > 59) { + yaml_die(path, NULL, + "messages.check_time must be HH:MM (got '%s')", v); + } + amsys->mesg_check_hour = hh; + amsys->mesg_check_min = mm; + amsys->mesg_check_done = time(0) + 86400 - 1; + amsys->mesg_check_done -= amsys->mesg_check_done % 86400; + amsys->mesg_check_done += + 3600 * amsys->mesg_check_hour + 60 * amsys->mesg_check_min; + } else { + yaml_die(path, NULL, "unknown key 'messages.%s'", k); + } + yaml_event_delete(&val); + } +} + +/* ------------------------------------------------------------------ */ +/* sites: section (NETLINKS only) */ +/* ------------------------------------------------------------------ */ + +#ifdef NETLINKS +static void +load_one_site(const char *path, yaml_parser_t *p, const char *service) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "site mapping"); + yaml_event_delete(&ev); + + NL_OBJECT nl = create_netlink(); + if (!nl) { + yaml_die(path, NULL, + "sites.%s: memory allocation failure creating netlink", + service); + } + + if (strlen(service) > SERV_NAME_LEN) { + yaml_die(path, NULL, + "sites.%s: link name too long (max %d)", + service, SERV_NAME_LEN); + } + strcpy(nl->service, service); + + /* default if 'allow' is omitted */ + nl->allow = ALL; + + bool got_address = false, got_port = false, got_verification = false; + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + break; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar key in sites.%s", service); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + yaml_event_t val = yaml_expect(path, p, YAML_SCALAR_EVENT, + "sites..* value"); + + if (!strcmp(k, "address")) { + const char *v = scalar_str(&val); + if (strlen(v) >= MAXHOST) { + yaml_die(path, NULL, + "sites.%s.address too long (max %d)", + service, MAXHOST - 1); + } + strcpy(nl->site, v); + strtolower(nl->site); + got_address = true; + } else if (!strcmp(k, "port")) { + int n = yaml_scalar_to_int(path, &val); + if (n < 0 || n > 65535) { + yaml_die(path, NULL, + "sites.%s.port out of range (got %d)", service, n); + } + int written = snprintf(nl->port, sizeof nl->port, "%d", n); + if (written < 0 || (size_t)written >= sizeof nl->port) { + yaml_die(path, NULL, + "sites.%s.port stringification overflow", + service); + } + got_port = true; + } else if (!strcmp(k, "verification")) { + const char *v = scalar_str(&val); + if (strlen(v) > VERIFY_LEN) { + yaml_die(path, NULL, + "sites.%s.verification too long (max %d)", + service, VERIFY_LEN); + } + strcpy(nl->verification, v); + got_verification = true; + } else if (!strcmp(k, "allow")) { + nl->allow = parse_nl_allow(path, &val); + } else { + yaml_die(path, NULL, + "unknown key 'sites.%s.%s'", service, k); + } + yaml_event_delete(&val); + } + + if (!got_address) { + yaml_die(path, NULL, "sites.%s missing required 'address'", service); + } + if (!got_port) { + yaml_die(path, NULL, "sites.%s missing required 'port'", service); + } + if (!got_verification) { + yaml_die(path, NULL, + "sites.%s missing required 'verification'", service); + } +} + +static void +load_sites(const char *path, yaml_parser_t *p) +{ + yaml_event_t ev = yaml_expect(path, p, YAML_MAPPING_START_EVENT, + "sites mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + return; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar service name in sites mapping"); + } + + char service[SERV_NAME_LEN + 1]; + snprintf(service, sizeof service, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + load_one_site(path, p, service); + } +} +#endif /* NETLINKS */ + +/* ------------------------------------------------------------------ */ +/* Top-level entry point */ +/* ------------------------------------------------------------------ */ + +void +yaml_load_config(const char *path) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Amnuts: Cannot open config file '%s'\n", path); + boot_exit(1); + } + + yaml_parser_t parser; + if (!yaml_parser_initialize(&parser)) { + fprintf(stderr, + "Amnuts: Failed to initialise YAML parser for '%s'\n", path); + fclose(fp); + boot_exit(1); + } + yaml_parser_set_input_file(&parser, fp); + + yaml_event_t ev; + + ev = yaml_expect(path, &parser, YAML_STREAM_START_EVENT, "stream"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_DOCUMENT_START_EVENT, "document"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_MAPPING_START_EVENT, + "top-level mapping"); + yaml_event_delete(&ev); + + for (;;) { + yaml_event_t key; + yaml_next(path, &parser, &key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + break; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, &parser, + "expected scalar key in top-level mapping"); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + if (!strcmp(k, "server")) { + load_server(path, &parser); + } else if (!strcmp(k, "timeouts")) { + load_timeouts(path, &parser); + } else if (!strcmp(k, "defaults")) { + load_defaults(path, &parser); + } else if (!strcmp(k, "moderation")) { + load_moderation(path, &parser); + } else if (!strcmp(k, "system")) { + load_system(path, &parser); + } else if (!strcmp(k, "users")) { + load_users(path, &parser); + } else if (!strcmp(k, "messages")) { + load_messages(path, &parser); +#ifdef NETLINKS + } else if (!strcmp(k, "sites")) { + load_sites(path, &parser); +#endif + } else { + /* Unknown top-level group: be strict. skip_value() exists + * if we ever want to soften this. */ + (void)skip_value; + yaml_die(path, &parser, "unknown top-level key '%s'", k); + } + } + + ev = yaml_expect(path, &parser, YAML_DOCUMENT_END_EVENT, "document end"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_STREAM_END_EVENT, "stream end"); + yaml_event_delete(&ev); + + yaml_parser_delete(&parser); + fclose(fp); +} diff --git a/src/yaml_gate.c b/src/yaml_gate.c new file mode 100644 index 0000000..7743900 --- /dev/null +++ b/src/yaml_gate.c @@ -0,0 +1,96 @@ +#include "defines.h" +#include "globals.h" +#include "prototypes.h" +#include +#include +#include +#include + +static int +file_exists(const char *path) +{ + struct stat st; + return stat(path, &st) == 0; +} + +static int +legacy_R_files_present(void) +{ + DIR *d = opendir(DATAFILES); + if (!d) { + return 0; + } + struct dirent *e; + int found = 0; + while ((e = readdir(d))) { + size_t len = strlen(e->d_name); + if (len > 2 && !strcmp(e->d_name + len - 2, ".R")) { + found = 1; + break; + } + } + closedir(d); + return found; +} + +static int +helpfiles_dir_nonempty(void) +{ + DIR *d = opendir(HELPFILES); + if (!d) { + return 0; + } + struct dirent *e; + int found = 0; + while ((e = readdir(d))) { + if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) { + found = 1; + break; + } + } + closedir(d); + return found; +} + +void +yaml_check_migration_gate(void) +{ + int has_config = file_exists(DATAFILES "/config.yaml"); + int has_rooms = file_exists(DATAFILES "/rooms.yaml"); + int has_help = file_exists(DATAFILES "/help.yaml"); + int yaml_complete = has_config && has_rooms && has_help; + int yaml_partial = (has_config || has_rooms || has_help) && !yaml_complete; + + if (yaml_complete) { + return; + } + + int legacy_config_present = file_exists(DATAFILES "/config"); + int legacy_R_present = legacy_R_files_present(); + int legacy_help_present = helpfiles_dir_nonempty(); + int any_legacy = legacy_config_present || legacy_R_present || legacy_help_present; + + if (yaml_partial) { + fprintf(stderr, + "Amnuts: incomplete YAML configuration in %s.\n config.yaml: %s\n rooms.yaml: %s\n help.yaml: %s\nAll three are required.\n", + DATAFILES, + has_config ? "found" : "MISSING", + has_rooms ? "found" : "MISSING", + has_help ? "found" : "MISSING" + ); + boot_exit(1); + } + + if (any_legacy) { + fprintf(stderr, + "Amnuts: legacy data files detected but no YAML configuration.\nBack up your data, then run the converter:\n python utils/convert_to_yaml.py\nOnce it completes, start the talker again.\n" + ); + boot_exit(1); + } + + fprintf(stderr, + "Amnuts: no configuration found in %s.\nOn a fresh install, copy the samples to active names:\n cp %s/config.yaml.sample %s/config.yaml\n cp %s/rooms.yaml.sample %s/rooms.yaml\n cp %s/help.yaml.sample %s/help.yaml\n", + DATAFILES, DATAFILES, DATAFILES, DATAFILES, DATAFILES, DATAFILES, DATAFILES + ); + boot_exit(1); +} diff --git a/src/yaml_help.c b/src/yaml_help.c new file mode 100644 index 0000000..8066d09 --- /dev/null +++ b/src/yaml_help.c @@ -0,0 +1,223 @@ +/* + * yaml_help.c + * + * YAML loader for the talker's help text file. + * + * Parses files/datafiles/help.yaml using libyaml's event-stream API + * and builds a sorted in-memory lookup table (help_table) keyed by + * command name. help_lookup() does a binary search over this table. + * + * Each entry can have an optional `usage` (scalar OR sequence of + * strings), an optional `aliases` (sequence), and an optional + * `description` (multi-line scalar). + */ + +#include "defines.h" +#include "globals.h" +#include "prototypes.h" +#include "yaml.h" + +#include +#include +#include + +static char * +scalar_str(yaml_event_t *ev) +{ + return (char *)ev->data.scalar.value; +} + +/* + * Read a YAML sequence of scalar strings, returning a heap array of + * strdup'd strings. *out_count receives the count. The caller has + * already consumed the SEQUENCE_START event; we consume up to and + * including SEQUENCE_END. + */ +static char ** +read_string_seq(const char *path, yaml_parser_t *p, int *out_count) +{ + char **out = NULL; + int cap = 0; + int n = 0; + + for (;;) { + yaml_event_t ev; + yaml_next(path, p, &ev); + + if (ev.type == YAML_SEQUENCE_END_EVENT) { + yaml_event_delete(&ev); + break; + } + if (ev.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar in sequence"); + } + if (n >= cap) { + cap = cap ? cap * 2 : 4; + out = realloc(out, cap * sizeof *out); + if (!out) { + yaml_die(path, p, "out of memory reading sequence"); + } + } + out[n++] = strdup(scalar_str(&ev)); + yaml_event_delete(&ev); + } + *out_count = n; + return out; +} + +/* + * Parse one help entry's body mapping. The MAPPING_START for the body + * has already been consumed by the caller. Populates the fields of `e`. + */ +static void +load_one_entry(const char *path, yaml_parser_t *p, struct help_entry *e) +{ + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + break; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, + "expected scalar key in help entry '%s'", e->command); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + if (!strcmp(k, "usage")) { + yaml_event_t v; + yaml_next(path, p, &v); + if (v.type == YAML_SCALAR_EVENT) { + e->usage = malloc(sizeof *e->usage); + if (!e->usage) { + yaml_die(path, p, + "out of memory for usage in '%s'", e->command); + } + e->usage[0] = strdup(scalar_str(&v)); + e->usage_count = 1; + yaml_event_delete(&v); + } else if (v.type == YAML_SEQUENCE_START_EVENT) { + yaml_event_delete(&v); + e->usage = read_string_seq(path, p, &e->usage_count); + } else { + yaml_die(path, p, + "expected scalar or sequence for 'usage' in '%s'", + e->command); + } + } else if (!strcmp(k, "aliases")) { + yaml_event_t v = yaml_expect(path, p, YAML_SEQUENCE_START_EVENT, + "aliases sequence"); + yaml_event_delete(&v); + e->aliases = read_string_seq(path, p, &e->alias_count); + } else if (!strcmp(k, "description")) { + yaml_event_t v = yaml_expect(path, p, YAML_SCALAR_EVENT, + "description value"); + e->description = strdup(scalar_str(&v)); + yaml_event_delete(&v); + } else { + yaml_die(path, p, + "unknown help entry key '%s' in '%s'", k, e->command); + } + } +} + +static int +help_cmp(const void *a, const void *b) +{ + const struct help_entry *ha = a; + const struct help_entry *hb = b; + return strcmp(ha->command, hb->command); +} + +void +yaml_load_help(const char *path) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Amnuts: Cannot open help file '%s'\n", path); + boot_exit(1); + } + + yaml_parser_t parser; + if (!yaml_parser_initialize(&parser)) { + fprintf(stderr, + "Amnuts: Failed to initialise YAML parser for '%s'\n", path); + fclose(fp); + boot_exit(1); + } + yaml_parser_set_input_file(&parser, fp); + + yaml_event_t ev; + + ev = yaml_expect(path, &parser, YAML_STREAM_START_EVENT, "stream"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_DOCUMENT_START_EVENT, "document"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_MAPPING_START_EVENT, + "top-level mapping"); + yaml_event_delete(&ev); + + int cap = 0; + help_table = NULL; + help_table_count = 0; + + for (;;) { + yaml_event_t name_ev; + yaml_next(path, &parser, &name_ev); + + if (name_ev.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&name_ev); + break; + } + if (name_ev.type != YAML_SCALAR_EVENT) { + yaml_die(path, &parser, "expected command name (scalar)"); + } + + if (help_table_count >= cap) { + cap = cap ? cap * 2 : 64; + help_table = realloc(help_table, cap * sizeof *help_table); + if (!help_table) { + yaml_die(path, &parser, + "out of memory growing help_table"); + } + } + struct help_entry *e = &help_table[help_table_count++]; + memset(e, 0, sizeof *e); + e->command = strdup(scalar_str(&name_ev)); + yaml_event_delete(&name_ev); + + ev = yaml_expect(path, &parser, YAML_MAPPING_START_EVENT, + "help entry body"); + yaml_event_delete(&ev); + + load_one_entry(path, &parser, e); + } + + ev = yaml_expect(path, &parser, YAML_DOCUMENT_END_EVENT, "document end"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_STREAM_END_EVENT, "stream end"); + yaml_event_delete(&ev); + + yaml_parser_delete(&parser); + fclose(fp); + + qsort(help_table, help_table_count, sizeof *help_table, help_cmp); +} + +const struct help_entry * +help_lookup(const char *command) +{ + if (!help_table || !help_table_count) { + return NULL; + } + struct help_entry key; + memset(&key, 0, sizeof key); + key.command = (char *)command; + return bsearch(&key, help_table, help_table_count, + sizeof *help_table, help_cmp); +} diff --git a/src/yaml_rooms.c b/src/yaml_rooms.c new file mode 100644 index 0000000..d534e0b --- /dev/null +++ b/src/yaml_rooms.c @@ -0,0 +1,663 @@ +/* + * yaml_rooms.c + * + * YAML loader for the talker's rooms file. + * + * Parses files/datafiles/rooms.yaml using libyaml's event-stream API + * and builds the global room linked list. Performs the same validation + * the legacy parse_rooms_section() in src/amnuts.c performs (length + * checks, duplicate name/label, link self-reference). + * + * Two-pass design: pass 1 creates each room and stashes its link names + * in a temporary heap-allocated array; pass 2 resolves those names to + * RM_OBJECT pointers once every room has been created. + */ + +#include "defines.h" +#include "globals.h" +#include "prototypes.h" +#include "yaml.h" + +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Pending-link bookkeeping for pass 2 */ +/* ------------------------------------------------------------------ */ + +struct pending_links { + RM_OBJECT room; + char **names; + int count; +}; + +/* ------------------------------------------------------------------ */ +/* Tiny helpers */ +/* ------------------------------------------------------------------ */ + +static char * +scalar_str(yaml_event_t *ev) +{ + return (char *)ev->data.scalar.value; +} + +static int +parse_access(const char *path, yaml_event_t *ev) +{ + const char *v = scalar_str(ev); + if (!strcmp(v, "BOTH")) { + return 0; + } + if (!strcmp(v, "PUB")) { + return FIXED; + } + if (!strcmp(v, "PRIV")) { + return FIXED | PRIVATE; + } + yaml_die(path, NULL, + "unknown access '%s' (expected BOTH, PUB or PRIV)", v); +} + +/* + * Read a YAML sequence of scalar strings into a heap array. Caller + * frees both the array and each entry. Sets *out_count. We expect the + * caller to already have consumed the SEQUENCE_START event; we consume + * up to and including SEQUENCE_END. + */ +static char ** +read_string_sequence(const char *path, yaml_parser_t *p, int *out_count, + int max) +{ + char **out = NULL; + int cap = 0; + int n = 0; + + for (;;) { + yaml_event_t ev; + yaml_next(path, p, &ev); + + if (ev.type == YAML_SEQUENCE_END_EVENT) { + yaml_event_delete(&ev); + break; + } + if (ev.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, "expected scalar in sequence"); + } + if (n >= max) { + yaml_die(path, p, "too many entries in sequence (max %d)", max); + } + if (n >= cap) { + cap = cap ? cap * 2 : 4; + out = realloc(out, cap * sizeof *out); + if (!out) { + yaml_die(path, p, "out of memory reading sequence"); + } + } + out[n++] = strdup(scalar_str(&ev)); + yaml_event_delete(&ev); + } + *out_count = n; + return out; +} + +/* + * Skip a netlink value when NETLINKS is disabled. v has already been + * read by the caller. Consumes a trailing MAPPING_END if v was a + * MAPPING_START. + */ +#ifndef NETLINKS +static void +skip_netlink_value(const char *path, yaml_parser_t *p, yaml_event_t *v) +{ + if (v->type == YAML_SCALAR_EVENT) { + return; + } + if (v->type == YAML_MAPPING_START_EVENT) { + int depth = 1; + while (depth > 0) { + yaml_event_t e; + yaml_next(path, p, &e); + if (e.type == YAML_MAPPING_START_EVENT) { + ++depth; + } else if (e.type == YAML_MAPPING_END_EVENT) { + --depth; + } + yaml_event_delete(&e); + } + return; + } + yaml_die(path, p, "expected scalar or mapping for netlink"); +} +#endif + +/* ------------------------------------------------------------------ */ +/* Parse one room body */ +/* ------------------------------------------------------------------ */ + +/* + * Parse the body of a single room. The MAPPING_START for the body has + * already been consumed by the caller. Returns a pending_links record + * whose `names` array is a heap-allocated list of link names; the + * caller is responsible for resolving those into pointers and freeing + * the array (in pass 2). + */ +static struct pending_links +load_one_room(const char *path, yaml_parser_t *p, const char *room_name) +{ + /* Length and duplicate-name checks BEFORE we allocate the room. */ + if (strlen(room_name) > ROOM_NAME_LEN) { + yaml_die(path, p, + "room name '%s' too long (max %d)", room_name, ROOM_NAME_LEN); + } + for (RM_OBJECT existing = room_first; existing; existing = existing->next) { + if (!strcmp(existing->name, room_name)) { + yaml_die(path, p, "duplicate room name '%s'", room_name); + } + } + + RM_OBJECT rm = create_room(); + if (!rm) { + yaml_die(path, p, "failed to allocate room '%s'", room_name); + } + strcpy(rm->name, room_name); + strcpy(rm->show_name, rm->name); + + struct pending_links pl = { .room = rm, .names = NULL, .count = 0 }; + int saw_links = 0; + int saw_map = 0; + int saw_label = 0; + int saw_desc = 0; + + for (;;) { + yaml_event_t key; + yaml_next(path, p, &key); + + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + break; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, + "expected scalar key in room '%s'", room_name); + } + + char k[64]; + snprintf(k, sizeof k, "%s", scalar_str(&key)); + yaml_event_delete(&key); + + if (!strcmp(k, "map")) { + yaml_event_t v = yaml_expect(path, p, YAML_SCALAR_EVENT, + "map value"); + const char *vs = scalar_str(&v); + if (strlen(vs) > ROOM_NAME_LEN) { + yaml_die(path, p, + "room map name '%s' too long (max %d)", + vs, ROOM_NAME_LEN); + } + strcpy(rm->map, vs); + saw_map = 1; + yaml_event_delete(&v); + } else if (!strcmp(k, "label")) { + yaml_event_t v = yaml_expect(path, p, YAML_SCALAR_EVENT, + "label value"); + const char *vs = scalar_str(&v); + if (strlen(vs) > ROOM_LABEL_LEN) { + yaml_die(path, p, + "room label '%s' too long (max %d)", + vs, ROOM_LABEL_LEN); + } + for (RM_OBJECT existing = room_first; existing; + existing = existing->next) { + if (existing != rm && !strcmp(existing->label, vs)) { + yaml_die(path, p, "duplicate room label '%s'", vs); + } + } + strcpy(rm->label, vs); + saw_label = 1; + yaml_event_delete(&v); + } else if (!strcmp(k, "links")) { + yaml_event_t v = yaml_expect(path, p, YAML_SEQUENCE_START_EVENT, + "links sequence"); + yaml_event_delete(&v); + pl.names = read_string_sequence(path, p, &pl.count, MAX_LINKS); + saw_links = 1; + } else if (!strcmp(k, "access")) { + yaml_event_t v = yaml_expect(path, p, YAML_SCALAR_EVENT, + "access value"); + rm->access = parse_access(path, &v); + yaml_event_delete(&v); + } else if (!strcmp(k, "topic")) { + yaml_event_t v = yaml_expect(path, p, YAML_SCALAR_EVENT, + "topic value"); + const char *vs = scalar_str(&v); + if (strlen(vs) > TOPIC_LEN) { + yaml_die(path, p, + "topic too long for room '%s' (max %d)", + room_name, TOPIC_LEN); + } + strcpy(rm->topic, vs); + yaml_event_delete(&v); + } else if (!strcmp(k, "description")) { + yaml_event_t v = yaml_expect(path, p, YAML_SCALAR_EVENT, + "description value"); + const char *d = scalar_str(&v); + size_t dlen = strlen(d); + if (dlen > ROOM_DESC_LEN) { + yaml_die(path, p, + "description too long for room '%s' (%zu > %d)", + room_name, dlen, ROOM_DESC_LEN); + } + memcpy(rm->desc, d, dlen); + rm->desc[dlen] = '\0'; + saw_desc = 1; + yaml_event_delete(&v); + } else if (!strcmp(k, "netlink")) { +#ifdef NETLINKS + yaml_event_t v; + yaml_next(path, p, &v); + if (v.type == YAML_SCALAR_EVENT) { + const char *vs = scalar_str(&v); + if (!strcmp(vs, "ACCEPT")) { + rm->inlink = 1; + } else { + yaml_die(path, p, + "netlink scalar must be 'ACCEPT' (got '%s')", vs); + } + yaml_event_delete(&v); + } else if (v.type == YAML_MAPPING_START_EVENT) { + yaml_event_delete(&v); + char type_buf[32] = ""; + char name_buf[SERV_NAME_LEN + 1] = ""; + for (;;) { + yaml_event_t ik; + yaml_next(path, p, &ik); + if (ik.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&ik); + break; + } + if (ik.type != YAML_SCALAR_EVENT) { + yaml_die(path, p, + "expected scalar key in netlink mapping" + " for room '%s'", room_name); + } + char ikey[32]; + snprintf(ikey, sizeof ikey, "%s", scalar_str(&ik)); + yaml_event_delete(&ik); + + yaml_event_t iv = yaml_expect(path, p, YAML_SCALAR_EVENT, + "netlink mapping value"); + const char *ivs = scalar_str(&iv); + if (!strcmp(ikey, "type")) { + snprintf(type_buf, sizeof type_buf, "%s", ivs); + } else if (!strcmp(ikey, "name")) { + if (strlen(ivs) > SERV_NAME_LEN) { + yaml_die(path, p, + "netlink name too long for room '%s'" + " (max %d)", room_name, SERV_NAME_LEN); + } + snprintf(name_buf, sizeof name_buf, "%s", ivs); + } else { + yaml_die(path, p, + "unknown netlink key '%s' for room '%s'", + ikey, room_name); + } + yaml_event_delete(&iv); + } + if (strcmp(type_buf, "CONNECT")) { + yaml_die(path, p, + "netlink mapping for room '%s' must have" + " type=CONNECT (got '%s')", + room_name, type_buf); + } + if (!*name_buf) { + yaml_die(path, p, + "netlink CONNECT for room '%s' missing 'name'", + room_name); + } + strcpy(rm->netlink_name, name_buf); + } else { + yaml_die(path, p, + "expected scalar or mapping for netlink in room '%s'", + room_name); + } +#else + yaml_event_t v; + yaml_next(path, p, &v); + skip_netlink_value(path, p, &v); + yaml_event_delete(&v); +#endif + } else { + yaml_die(path, p, + "unknown room key '%s' for room '%s'", k, room_name); + } + } + + if (!saw_map || !saw_label || !saw_links || !saw_desc) { + yaml_die(path, p, + "room '%s' missing required field(s):%s%s%s%s", + room_name, + saw_map ? "" : " map", + saw_label ? "" : " label", + saw_links ? "" : " links", + saw_desc ? "" : " description"); + } + + /* Self-link check: walk the names we collected, compare against + * the room's own name (the legacy parser compared link labels to + * the room's label; in YAML the 'links' sequence is by name). */ + for (int i = 0; i < pl.count; ++i) { + if (!strcmp(pl.names[i], rm->name)) { + yaml_die(path, p, + "room '%s' has a link to itself", rm->name); + } + } + + return pl; +} + +/* ------------------------------------------------------------------ */ +/* Top-level entry point */ +/* ------------------------------------------------------------------ */ + +void +yaml_load_rooms(const char *path) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Amnuts: Cannot open rooms file '%s'\n", path); + boot_exit(1); + } + + yaml_parser_t parser; + if (!yaml_parser_initialize(&parser)) { + fprintf(stderr, + "Amnuts: Failed to initialise YAML parser for '%s'\n", path); + fclose(fp); + boot_exit(1); + } + yaml_parser_set_input_file(&parser, fp); + + yaml_event_t ev; + + ev = yaml_expect(path, &parser, YAML_STREAM_START_EVENT, "stream"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_DOCUMENT_START_EVENT, "document"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_MAPPING_START_EVENT, + "top-level mapping"); + yaml_event_delete(&ev); + + /* Top-level mapping has exactly one key: 'rooms'. */ + yaml_event_t k = yaml_expect(path, &parser, YAML_SCALAR_EVENT, + "top-level key"); + if (strcmp(scalar_str(&k), "rooms")) { + yaml_die(path, &parser, + "expected top-level key 'rooms' (got '%s')", + scalar_str(&k)); + } + yaml_event_delete(&k); + + ev = yaml_expect(path, &parser, YAML_MAPPING_START_EVENT, + "rooms mapping"); + yaml_event_delete(&ev); + + /* Pass 1: parse every room. */ + struct pending_links *pending = NULL; + int pcap = 0; + int pn = 0; + + for (;;) { + yaml_event_t name_ev; + yaml_next(path, &parser, &name_ev); + + if (name_ev.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&name_ev); + break; + } + if (name_ev.type != YAML_SCALAR_EVENT) { + yaml_die(path, &parser, "expected room name (scalar)"); + } + + char rname[ROOM_NAME_LEN + 2]; /* +2 to detect over-length without truncating */ + snprintf(rname, sizeof rname, "%s", scalar_str(&name_ev)); + yaml_event_delete(&name_ev); + + ev = yaml_expect(path, &parser, YAML_MAPPING_START_EVENT, + "room body"); + yaml_event_delete(&ev); + + if (pn >= pcap) { + pcap = pcap ? pcap * 2 : 16; + pending = realloc(pending, pcap * sizeof *pending); + if (!pending) { + yaml_die(path, &parser, + "out of memory tracking pending room links"); + } + } + pending[pn++] = load_one_room(path, &parser, rname); + } + + /* Top-level mapping end + document/stream end */ + ev = yaml_expect(path, &parser, YAML_MAPPING_END_EVENT, + "top-level mapping end"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_DOCUMENT_END_EVENT, + "document end"); + yaml_event_delete(&ev); + ev = yaml_expect(path, &parser, YAML_STREAM_END_EVENT, "stream end"); + yaml_event_delete(&ev); + + yaml_parser_delete(&parser); + fclose(fp); + + /* Pass 2: resolve link names to room pointers. */ + for (int i = 0; i < pn; ++i) { + struct pending_links *pl = &pending[i]; + for (int j = 0; j < pl->count; ++j) { + RM_OBJECT target = NULL; + for (RM_OBJECT cand = room_first; cand; cand = cand->next) { + if (!strcmp(cand->name, pl->names[j])) { + target = cand; + break; + } + } + if (!target) { + fprintf(stderr, + "Amnuts: Room %s has undefined link '%s'.\n", + pl->room->name, pl->names[j]); + boot_exit(1); + } + if (target == pl->room) { + fprintf(stderr, + "Amnuts: Room %s cannot link to itself.\n", + pl->room->name); + boot_exit(1); + } + pl->room->link[j] = target; + free(pl->names[j]); + } + free(pl->names); + } + free(pending); +} + + +/* + * Reload descriptions for one or all non-personal rooms from rooms.yaml. + * + * Unlike the loaders called at boot, this function never calls boot_exit: + * a malformed rooms.yaml at runtime should not take down the talker. + * Errors are reported via errbuf (if non-NULL); the function returns the + * number of in-memory rooms whose descriptions were updated, or -1 on + * any parse/IO error. + * + * If `target` is NULL or empty, every room in the YAML is considered; + * otherwise only the named room is touched. Personal rooms are always + * skipped (their descriptions live in files/userfiles/rooms/, not here). + * + * Only the description field is refreshed. Topic, access, links, and + * netlink wiring are preserved as they are in memory — runtime + * mutations to those (via .topic, .private, etc.) are not clobbered. + */ +int +yaml_reload_descriptions(const char *path, const char *target, + char *errbuf, size_t errbuf_size) +{ + FILE *fp = NULL; + yaml_parser_t parser; + int parser_initialized = 0; + char *new_desc = NULL; + int updated = 0; + yaml_event_t ev; + +#define R_FAIL(...) do { \ + if (errbuf) snprintf(errbuf, errbuf_size, __VA_ARGS__); \ + goto fail; \ + } while (0) + +#define R_NEXT(EV) do { \ + if (!yaml_parser_parse(&parser, EV)) { \ + R_FAIL("%s:%lu: parse failure: %s", path, \ + (unsigned long)parser.problem_mark.line + 1, \ + parser.problem ? parser.problem : "?"); \ + } \ + } while (0) + +#define R_EXPECT(EV, TYPE, CTX) do { \ + R_NEXT(EV); \ + if ((EV)->type != (TYPE)) { \ + yaml_event_delete(EV); \ + R_FAIL("%s: expected %s while parsing %s", path, #TYPE, CTX); \ + } \ + } while (0) + + fp = fopen(path, "r"); + if (!fp) { + R_FAIL("Cannot open %s", path); + } + + yaml_parser_initialize(&parser); + parser_initialized = 1; + yaml_parser_set_input_file(&parser, fp); + + R_EXPECT(&ev, YAML_STREAM_START_EVENT, "stream"); + yaml_event_delete(&ev); + R_EXPECT(&ev, YAML_DOCUMENT_START_EVENT, "document"); + yaml_event_delete(&ev); + R_EXPECT(&ev, YAML_MAPPING_START_EVENT, "top-level mapping"); + yaml_event_delete(&ev); + + R_EXPECT(&ev, YAML_SCALAR_EVENT, "top-level key"); + if (strcmp((const char *)ev.data.scalar.value, "rooms")) { + yaml_event_delete(&ev); + R_FAIL("%s: expected top-level key 'rooms'", path); + } + yaml_event_delete(&ev); + + R_EXPECT(&ev, YAML_MAPPING_START_EVENT, "rooms mapping"); + yaml_event_delete(&ev); + + /* Walk each room mapping. */ + for (;;) { + R_NEXT(&ev); + if (ev.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&ev); + break; + } + if (ev.type != YAML_SCALAR_EVENT) { + yaml_event_delete(&ev); + R_FAIL("%s: expected scalar room name", path); + } + char rname[ROOM_NAME_LEN + 1]; + snprintf(rname, sizeof rname, "%s", + (const char *)ev.data.scalar.value); + yaml_event_delete(&ev); + + R_EXPECT(&ev, YAML_MAPPING_START_EVENT, "room body"); + yaml_event_delete(&ev); + + int match = !target || !*target || !strcmp(target, rname); + free(new_desc); + new_desc = NULL; + + for (;;) { + yaml_event_t key; + R_NEXT(&key); + if (key.type == YAML_MAPPING_END_EVENT) { + yaml_event_delete(&key); + break; + } + if (key.type != YAML_SCALAR_EVENT) { + yaml_event_delete(&key); + R_FAIL("%s: expected scalar key in room body", path); + } + int wants_desc = match && new_desc == NULL + && !strcmp((const char *)key.data.scalar.value, + "description"); + yaml_event_delete(&key); + + if (wants_desc) { + yaml_event_t v; + R_EXPECT(&v, YAML_SCALAR_EVENT, "description value"); + new_desc = strdup((const char *)v.data.scalar.value); + yaml_event_delete(&v); + } else { + /* Skip the value, handling nested mappings/sequences. */ + int depth = 0; + yaml_event_t v; + R_NEXT(&v); + if (v.type == YAML_SEQUENCE_START_EVENT + || v.type == YAML_MAPPING_START_EVENT) { + depth = 1; + } + yaml_event_delete(&v); + while (depth > 0) { + R_NEXT(&v); + if (v.type == YAML_SEQUENCE_START_EVENT + || v.type == YAML_MAPPING_START_EVENT) { + ++depth; + } else if (v.type == YAML_SEQUENCE_END_EVENT + || v.type == YAML_MAPPING_END_EVENT) { + --depth; + } + yaml_event_delete(&v); + } + } + } + + if (match && new_desc) { + for (RM_OBJECT rm = room_first; rm; rm = rm->next) { + if (!strcmp(rm->name, rname) && !is_personal_room(rm)) { + size_t len = strlen(new_desc); + if (len > ROOM_DESC_LEN) { + len = ROOM_DESC_LEN; + } + memcpy(rm->desc, new_desc, len); + rm->desc[len] = '\0'; + ++updated; + break; + } + } + } + } + + yaml_parser_delete(&parser); + fclose(fp); + free(new_desc); + return updated; + +fail: + if (parser_initialized) { + yaml_parser_delete(&parser); + } + if (fp) { + fclose(fp); + } + free(new_desc); + return -1; + +#undef R_FAIL +#undef R_NEXT +#undef R_EXPECT +} diff --git a/src/yaml_util.c b/src/yaml_util.c new file mode 100644 index 0000000..6233d33 --- /dev/null +++ b/src/yaml_util.c @@ -0,0 +1,112 @@ +#include "defines.h" +#include "globals.h" +#include "prototypes.h" +#include "yaml.h" +#include +#include +#include +#include + +static const char * +event_type_name(yaml_event_type_t t) +{ + switch (t) { + case YAML_NO_EVENT: return "NO_EVENT"; + case YAML_STREAM_START_EVENT: return "STREAM_START"; + case YAML_STREAM_END_EVENT: return "STREAM_END"; + case YAML_DOCUMENT_START_EVENT: return "DOCUMENT_START"; + case YAML_DOCUMENT_END_EVENT: return "DOCUMENT_END"; + case YAML_ALIAS_EVENT: return "ALIAS"; + case YAML_SCALAR_EVENT: return "SCALAR"; + case YAML_SEQUENCE_START_EVENT: return "SEQUENCE_START"; + case YAML_SEQUENCE_END_EVENT: return "SEQUENCE_END"; + case YAML_MAPPING_START_EVENT: return "MAPPING_START"; + case YAML_MAPPING_END_EVENT: return "MAPPING_END"; + } + return "UNKNOWN"; +} + +void +yaml_die(const char *path, yaml_parser_t *parser, const char *fmt, ...) +{ + va_list ap; + fprintf(stderr, "%s", path); + if (parser) { + fprintf(stderr, ":%lu:%lu", + (unsigned long)parser->problem_mark.line + 1, + (unsigned long)parser->problem_mark.column + 1); + } + fprintf(stderr, ": "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + if (parser && parser->problem) { + fprintf(stderr, " libyaml: %s\n", parser->problem); + } + boot_exit(1); + /* unreachable, but silences noreturn analysis */ + abort(); +} + +void +yaml_next(const char *path, yaml_parser_t *parser, yaml_event_t *event) +{ + if (!yaml_parser_parse(parser, event)) { + yaml_die(path, parser, "parse failure"); + } +} + +yaml_event_t +yaml_expect(const char *path, yaml_parser_t *parser, + yaml_event_type_t type, const char *context) +{ + yaml_event_t ev; + yaml_next(path, parser, &ev); + if (ev.type != type) { + yaml_die(path, parser, + "expected %s while parsing %s, got %s", + event_type_name(type), context, event_type_name(ev.type)); + } + return ev; +} + +bool +yaml_scalar_to_bool(const char *path, yaml_event_t *ev) +{ + const char *v = (const char *)ev->data.scalar.value; + if (!strcasecmp(v, "true") || !strcasecmp(v, "yes") || !strcmp(v, "1")) { + return true; + } + if (!strcasecmp(v, "false") || !strcasecmp(v, "no") || !strcmp(v, "0")) { + return false; + } + yaml_die(path, NULL, "expected boolean, got '%s'", v); +} + +int +yaml_scalar_to_int(const char *path, yaml_event_t *ev) +{ + char *end; + const char *v = (const char *)ev->data.scalar.value; + long n = strtol(v, &end, 10); + if (*end || end == v) { + yaml_die(path, NULL, "expected integer, got '%s'", v); + } + return (int)n; +} + +int +yaml_scalar_to_level(const char *path, yaml_event_t *ev) +{ + const char *v = (const char *)ev->data.scalar.value; + for (int i = 0; i < NUM_LEVELS; ++i) { + if (!strcasecmp(v, user_level[i].name)) { + return i; + } + } + if (!strcasecmp(v, "NONE")) { + return NUM_LEVELS; + } + yaml_die(path, NULL, "unknown level '%s'", v); +} diff --git a/supervisord.conf b/supervisord.conf index b3e61ce..ccd6fe9 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -7,6 +7,14 @@ command=/amnuts/build/amnutsTalker directory=/amnuts autostart=true autorestart=unexpected +; Treat clean exit (0) and deliberate boot_exit failure (1) as expected. +; The migration gate, malformed-config errors, and shutdown command all +; exit 1 on purpose -- supervisord must not restart in those cases or it +; will spin in a respawn loop. Real crashes (SIGSEGV etc.) come back as +; 128+signum and stay "unexpected", so genuine failures still trigger +; a restart. +exitcodes=0,1 +startretries=3 stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr diff --git a/utils/convert_to_yaml.py b/utils/convert_to_yaml.py new file mode 100644 index 0000000..4fbbb57 --- /dev/null +++ b/utils/convert_to_yaml.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +""" +Convert legacy Amnuts data files (config, *.R, helpfiles/*) to the +three YAML files used by the new loader. Destructive: removes legacy +files after a successful run, with auto-backup beforehand. + +Run from the repo root: + python utils/convert_to_yaml.py +""" +import argparse +import datetime +import glob +import os +import re +import shutil +import sys +import textwrap + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +DATAFILES = os.path.join(REPO_ROOT, "files", "datafiles") +HELPFILES = os.path.join(REPO_ROOT, "files", "helpfiles") + +CONFIG_YAML = os.path.join(DATAFILES, "config.yaml") +ROOMS_YAML = os.path.join(DATAFILES, "rooms.yaml") +HELP_YAML = os.path.join(DATAFILES, "help.yaml") + +LEGACY_CONFIG = os.path.join(DATAFILES, "config") + + +INIT_GROUPS = { + "server": [ + ("verification", "verification"), + ("mainport", "ports.main"), + ("wizport", "ports.wiz"), + ("linkport", "ports.link"), + ("max_users", "max_users"), + ("max_clones", "max_clones"), + ("heartbeat", "heartbeat"), + ], + "timeouts": [ + ("login_idle_time", "login_idle"), + ("user_idle_time", "user_idle"), + ("time_out_afks", "timeout_afks"), + ("time_out_maxlevel", "timeout_maxlevel"), + ], + "defaults": [ + ("colour_def", "colour"), + ("prompt_def", "prompt"), + ("charecho_def", "charecho"), + ("passwordecho_def", "passwordecho"), + ("default_warp", "warp_room"), + ("default_jail", "jail_room"), + ("default_bank", "bank_room"), + ("default_shoot", "shoot_room"), + ], + "moderation": [ + ("ban_swearing", "ban_swearing"), + ("minlogin_level", "minlogin_level"), + ("min_private", "min_private"), + ("ignore_mp_level", "ignore_mp_level"), + ("gatecrash_level", "gatecrash_level"), + ("boot_off_min", "boot_off_min"), + ("flood_protect", "flood_protect"), + ], + "system": [ + ("system_logging", "logging"), + ("ignore_sigterm", "ignore_sigterm"), + ("auto_connect", "auto_connect"), + ("crash_action", "crash_action"), + ("resolve_ip", "resolve_ip"), + ("random_motds", "random_motds"), + ], + "users": [ + ("auto_purge", "auto_purge"), + ("allow_recaps", "allow_recaps"), + ("auto_promote", "auto_promote"), + ("personal_rooms", "personal_rooms"), + ("startup_room_parse", "startup_room_parse"), + ("rem_user_maxlevel", "rem_user_maxlevel"), + ("rem_user_deflevel", "rem_user_deflevel"), + ("wizport_level", "wizport_level"), + ], + "messages": [ + ("mesg_life", "lifetime_days"), + ("mesg_check_time", "check_time"), + ], +} + +# YAML leaf keys whose values should be ints (after coercion). +INT_KEYS = { + "main", "wiz", "link", "max_users", "max_clones", "heartbeat", + "login_idle", "user_idle", "min_private", "lifetime_days", +} + +# Legacy keys whose values are ON/OFF or YES/NO and should become YAML bool. +BOOL_LEGACY_KEYS = { + "system_logging", "prompt_def", "passwordecho_def", "ignore_sigterm", + "auto_connect", "colour_def", "time_out_afks", "charecho_def", + "auto_purge", "allow_recaps", "auto_promote", "personal_rooms", + "random_motds", "startup_room_parse", "flood_protect", "boot_off_min", +} + +# Legacy keys whose values are level names. +LEVEL_LEGACY_KEYS = { + "minlogin_level", "wizport_level", "gatecrash_level", "ignore_mp_level", + "rem_user_maxlevel", "rem_user_deflevel", "time_out_maxlevel", +} + +# Patterns for parsing help file headers. +USAGE_LINE = re.compile(r"^~OLUsage\s*:~RS\s*(.*)$") +USAGE_CONT = re.compile(r"^~OL\s*:~RS\s*(.*)$") +ALIASES_LINE = re.compile(r"^~OLAliases\s*:~RS\s*(.*)$") +COMMAND_LINE = re.compile(r"^~OLCommand\s*:~RS\s*(.*)$") + + +def yaml_already_present(): + return any(os.path.exists(p) for p in (CONFIG_YAML, ROOMS_YAML, HELP_YAML)) + + +def legacy_present(): + if os.path.exists(LEGACY_CONFIG): + return True + if glob.glob(os.path.join(DATAFILES, "*.R")): + return True + if os.path.isdir(HELPFILES) and os.listdir(HELPFILES): + return True + return False + + +def confirm(): + print(textwrap.dedent(""" + WARNING: this script is DESTRUCTIVE. + + It will delete the following once it has written the new YAML files: + - files/datafiles/config + - files/datafiles/*.R + - files/helpfiles/ (entire directory) + + It WILL automatically copy these files into a backup folder before + touching anything, but you should ALSO take your own backup right now, + just in case. + """).strip()) + answer = input("\nProceed with conversion? [y/N] ").strip().lower() + return answer == "y" + + +def make_backup(): + stamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") + backup_dir = os.path.join(REPO_ROOT, f"data-backup-{stamp}") + os.makedirs(backup_dir, exist_ok=False) + if os.path.exists(LEGACY_CONFIG): + shutil.copy2(LEGACY_CONFIG, backup_dir) + for r in glob.glob(os.path.join(DATAFILES, "*.R")): + shutil.copy2(r, backup_dir) + if os.path.isdir(HELPFILES): + shutil.copytree(HELPFILES, os.path.join(backup_dir, "helpfiles")) + print(f"Backup written to {backup_dir}") + return backup_dir + + +def cleanup_legacy(): + if os.path.exists(LEGACY_CONFIG): + os.remove(LEGACY_CONFIG) + for r in glob.glob(os.path.join(DATAFILES, "*.R")): + os.remove(r) + if os.path.isdir(HELPFILES): + shutil.rmtree(HELPFILES) + + +def parse_legacy_config(): + """Return (init_dict, sites_dict, rooms_lines, topics_lines). + + Reads files/datafiles/config and splits it into its four sections. + Lines starting with '#' or blank lines are ignored. INIT/ROOMS/TOPICS/SITES + section headers end with ':' and switch the active section. + """ + section = None + init = {} + sites = {} + rooms_lines = [] + topics_lines = [] + with open(LEGACY_CONFIG) as f: + for raw in f: + line = raw.split("#", 1)[0].rstrip("\n").rstrip() + if not line.strip(): + continue + if line.endswith(":") and not " " in line.rstrip(":"): + section = line.rstrip(":").strip() + continue + if section == "INIT": + parts = line.split(None, 1) + if len(parts) == 2: + init[parts[0]] = parts[1].strip() + elif section == "SITES": + parts = line.split() + if len(parts) >= 4: + sites[parts[0]] = { + "address": parts[1], + "port": int(parts[2]), + "verification": parts[3], + "allow": parts[4] if len(parts) > 4 else "ALL", + } + elif section == "ROOMS": + rooms_lines.append(line) + elif section == "TOPICS": + topics_lines.append(line) + return init, sites, rooms_lines, topics_lines + + +def _set_path(d, dotted_path, value): + parts = dotted_path.split(".") + for p in parts[:-1]: + d = d.setdefault(p, {}) + d[parts[-1]] = value + + +def _coerce(legacy_key, yaml_path, raw): + leaf = yaml_path.rsplit(".", 1)[-1] + if leaf in INT_KEYS: + return int(raw) + if legacy_key in BOOL_LEGACY_KEYS: + return raw.upper() in ("YES", "ON", "1", "TRUE") + # ban_swearing, crash_action, resolve_ip stay as strings (their values + # are uppercase enum-like names, not booleans). + return raw + + +def convert_config(): + import yaml as pyyaml # imported lazily so the skeleton ran without yaml installed + init, sites, _rooms_lines, _topics_lines = parse_legacy_config() + out = {} + for group, mappings in INIT_GROUPS.items(): + for legacy_key, yaml_path in mappings: + full_path = f"{group}.{yaml_path}" + if legacy_key in init: + _set_path(out, full_path, _coerce(legacy_key, yaml_path, init[legacy_key])) + if sites: + out["sites"] = sites + with open(CONFIG_YAML, "w") as f: + pyyaml.safe_dump(out, f, sort_keys=False, default_flow_style=False) + print(f"Wrote {CONFIG_YAML}") + + +def convert_rooms(): + import yaml as pyyaml + + # Force multi-line strings to be emitted as literal block scalars + # (description: |) rather than the default folded/quoted form. + def _str_representer(dumper, data): + if "\n" in data: + return dumper.represent_scalar( + "tag:yaml.org,2002:str", data, style="|" + ) + return dumper.represent_scalar("tag:yaml.org,2002:str", data) + + pyyaml.SafeDumper.add_representer(str, _str_representer) + + _init, _sites, room_lines, topic_lines = parse_legacy_config() + rooms = {} + label_to_name = {} + + # Pass 1: parse structure, build label-to-name map. + parsed = [] + for line in room_lines: + parts = line.split() + if len(parts) < 4: + raise ValueError(f"Malformed ROOMS line: {line!r}") + map_, label, name, links_csv = parts[:4] + access = parts[4] if len(parts) > 4 else None + netlink_kw = parts[5] if len(parts) > 5 else None + netlink_name = parts[6] if len(parts) > 6 else None + label_to_name[label] = name + parsed.append((name, map_, label, links_csv, access, netlink_kw, netlink_name)) + + # Pass 2: build entries, resolving links from labels. + for name, map_, label, links_csv, access, netlink_kw, netlink_name in parsed: + link_names = [] + for ll in links_csv.split(","): + ll = ll.strip() + if not ll: + continue + if ll not in label_to_name: + raise ValueError( + f"Room '{name}' links to unknown label '{ll}'" + ) + link_names.append(label_to_name[ll]) + + entry = { + "map": map_, + "label": label, + "links": link_names, + } + if access and access != "BOTH": + entry["access"] = access + if netlink_kw == "ACCEPT": + entry["netlink"] = "ACCEPT" + elif netlink_kw == "CONNECT": + if not netlink_name: + raise ValueError( + f"Room '{name}' has CONNECT without a netlink name" + ) + entry["netlink"] = {"type": "CONNECT", "name": netlink_name} + elif netlink_kw is not None: + raise ValueError( + f"Room '{name}' has unknown netlink keyword '{netlink_kw}'" + ) + + # Description from .R, if present. + desc_path = os.path.join(DATAFILES, f"{name}.R") + if os.path.exists(desc_path): + with open(desc_path) as f: + entry["description"] = f.read() + + rooms[name] = entry + + # Pass 3: attach topics by room name. + for line in topic_lines: + # Topic format: + parts = line.split(None, 1) + if len(parts) < 2: + continue + room_name, topic = parts + if room_name in rooms: + rooms[room_name]["topic"] = topic.strip() + # If a topic references a non-existent room, skip silently -- the + # legacy parser would error, but for one-shot migration we'd rather + # produce output and let the operator notice on review. + + with open(ROOMS_YAML, "w") as f: + pyyaml.safe_dump( + {"rooms": rooms}, + f, + sort_keys=False, + default_flow_style=False, + allow_unicode=True, + ) + print(f"Wrote {ROOMS_YAML}") + + +def parse_help_file(path): + """Parse a single legacy helpfile, returning a dict suitable for YAML.""" + entry = {} + usage = [] + aliases = [] + desc_lines = [] + in_desc = False + + with open(path) as f: + for raw in f: + line = raw.rstrip("\n") + + if in_desc: + desc_lines.append(line) + continue + + if not line.strip(): + in_desc = True + continue + + if COMMAND_LINE.match(line): + continue + + m = USAGE_LINE.match(line) + if m: + usage.append(m.group(1).strip()) + continue + + m = USAGE_CONT.match(line) + if m: + usage.append(m.group(1).strip()) + continue + + m = ALIASES_LINE.match(line) + if m: + raw_aliases = m.group(1).strip() + # Separators: ", " or " or " + for part in re.split(r",\s*|\s+or\s+", raw_aliases): + part = part.strip() + if part: + aliases.append(part) + continue + + # Unrecognised header line -- fold into description. + desc_lines.append(line) + in_desc = True + + if usage: + # Single-line usage stays a scalar; multi-line becomes a sequence. + entry["usage"] = usage[0] if len(usage) == 1 else usage + if aliases: + entry["aliases"] = aliases + desc = "\n".join(desc_lines).strip("\n") + if desc: + entry["description"] = desc + return entry + + +def convert_help(): + import yaml as pyyaml + out = {} + flagged = [] + if not os.path.isdir(HELPFILES): + with open(HELP_YAML, "w") as f: + pyyaml.safe_dump({}, f) + print(f"Wrote {HELP_YAML} (empty -- no helpfiles directory)") + return + # Build entries in alphabetical order at the top level. parse_help_file + # constructs each entry with keys in the desired order: usage, aliases, + # description. We rely on Python 3.7+ dict insertion-order preservation + # and emit with sort_keys=False so entry-level keys keep that order + # while top-level command names stay alphabetical (because we iterate + # sorted listdir below). + for fname in sorted(os.listdir(HELPFILES)): + path = os.path.join(HELPFILES, fname) + if not os.path.isfile(path): + continue + try: + out[fname] = parse_help_file(path) + except Exception as e: + flagged.append((fname, str(e))) + with open(HELP_YAML, "w") as f: + pyyaml.safe_dump(out, f, sort_keys=False, default_flow_style=False, + allow_unicode=True, width=10000) + print(f"Wrote {HELP_YAML} ({len(out)} entries)") + if flagged: + print("WARNING: the following help files did not parse cleanly and " + "should be reviewed manually:") + for fname, err in flagged: + print(f" {fname}: {err}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--keep-legacy", action="store_true", + help="Don't delete legacy files after conversion (development use only).", + ) + args = parser.parse_args() + + if yaml_already_present(): + print("Already migrated -- YAML files exist. Aborting.") + return 0 + + if not legacy_present(): + print("Nothing to convert.") + return 0 + + if not confirm(): + print("Aborted.") + return 1 + + backup_dir = make_backup() + try: + convert_config() + convert_rooms() + convert_help() + except Exception: + for p in (CONFIG_YAML, ROOMS_YAML, HELP_YAML): + if os.path.exists(p): + os.remove(p) + print( + f"Conversion failed; legacy files left in place. " + f"Backup at {backup_dir}." + ) + raise + + if not args.keep_legacy: + cleanup_legacy() + print("Legacy files removed.") + else: + print("--keep-legacy: legacy files left in place.") + + print("Done. Three YAML files written to files/datafiles/.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From e5591c40c91a482a0365366f05ca42bf78cf5682 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sat, 9 May 2026 21:31:57 +0100 Subject: [PATCH 2/2] Updated git ignore --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c7600df..c6741b6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,4 @@ amnutsIdent amnutsTalker supervisord.pid supervisord.log -files/datafiles/config.yml -files/datafiles/rooms.yml -files/datafiles/.yml +files/datafiles/*.yaml