From 5ed98618a9d10d3589bba423f93140d532b4d5eb Mon Sep 17 00:00:00 2001 From: Jeremy Stanley Date: Tue, 10 Jun 2008 22:42:56 +0000 Subject: [PATCH] Imported from archive. * (all): Updated copyright notices for 2008, and added references to the included LICENSE file. Added similar copyright notices to all data/document files. * LICENSE: Switched the project from modified 2-clause BSD license to the simpler and equivalent ISC license. * banner.txt, login.txt, menu (menu:entering_account_name) (menu:main_utility): Implemented text file inclusion using a file replacement macro, and relocated the ASCII/ANSI art from the login/lobby menu descriptions into separate login.txt and banner.txt files. * command (command:show): Renamed parameter to option in the help. * menu (menu:delete_avatar): Corrected a misleading typo in the desription. * mudpy.conf (internal:time), mudpy.py (User.__init__) (User.check_idle, User.pulse, handle_user_input): Added idle and linkdead dict facets, indicating how long users can idle in various states before they're warned and ultimately disconnected. * mudpy.py (replace_macros): Performance enhancement, moving unnecessary declarations outside the processing loop. (wrap_ansi_text): Refactored the word-wrapping routines to solve a bug where lines explicitly terminated at the wrap column got wrapped early. --- LICENSE | 36 ++++------ archetype | 4 ++ banner.txt | 5 ++ command | 6 +- katarsis/index | 4 ++ katarsis/pride_square/index | 4 ++ katarsis/pride_square/location | 6 +- katarsis/pride_square/prop | 4 ++ login.txt | 21 ++++++ menu | 10 ++- mudpy | 6 +- mudpy.conf | 6 ++ mudpy.py | 150 +++++++++++++++++++++++++++++------------ 13 files changed, 189 insertions(+), 73 deletions(-) create mode 100644 banner.txt create mode 100644 login.txt diff --git a/LICENSE b/LICENSE index c1ce24f..902f8d5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,23 +1,13 @@ -Copyright (c) 2005, 2006 Jeremy Stanley . All rights -reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - +Copyright (c) 2004-2008 Jeremy Stanley + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/archetype b/archetype index ad43eba..fa370b7 100644 --- a/archetype +++ b/archetype @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [__control__] read_only = yes diff --git a/banner.txt b/banner.txt new file mode 100644 index 0000000..5b66ddc --- /dev/null +++ b/banner.txt @@ -0,0 +1,5 @@ + O OOO O O O OOOO O O O O OO OOO OO OOO OOO OOO OOO + OOOOO OO O O O O O O O O O O O O O O O O O O O O O + O O OOO O O O O O O OO OOOO O OOOO OOO OO O OO + O O O O O O O O O O O O O O O O O O O O O O O + O OO OO O OO O O O O O O O O O O O O O O OOO OOO OOO diff --git a/command b/command index 18205d0..8998afc 100644 --- a/command +++ b/command @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [__control__] read_only = yes @@ -73,5 +77,5 @@ help = Invoke it like this:$(eol)$(eol) set actor:dominique description You se action = command_show(actor, parameters) administrative = yes description = Show various data. -help = Here are the possible incantations ( is required, [parameter] is optional, (note) is a note):$(eol)$(eol) show categories (list all element category names)$(eol) show category (list the elements in a category)$(eol) show element (list facet definitions for an element)$(eol) show file (list elements in a file)$(eol) show files (list all element data files)$(eol) show log [level [start [stop]]] (list logs above level from start to stop)$(eol) show result (evaluates a python expression)$(eol) show time (returns several current timer values) +help = Here are the possible incantations ( is required, [option] is optional, (note) is a note):$(eol)$(eol) show categories (list all element category names)$(eol) show category (list the elements in a category)$(eol) show element (list facet definitions for an element)$(eol) show file (list elements in a file)$(eol) show files (list all element data files)$(eol) show log [level [start [stop]]] (list logs above level from start to stop)$(eol) show result (evaluates a python expression)$(eol) show time (returns several current timer values) diff --git a/katarsis/index b/katarsis/index index 943b88a..7f14392 100644 --- a/katarsis/index +++ b/katarsis/index @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [__control__] include_files = pride_square/index read_only = yes diff --git a/katarsis/pride_square/index b/katarsis/pride_square/index index 2836fe9..0997a26 100644 --- a/katarsis/pride_square/index +++ b/katarsis/pride_square/index @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [__control__] include_files = [ "location", "prop" ] read_only = yes diff --git a/katarsis/pride_square/location b/katarsis/pride_square/location index 8e2bb43..462f6ad 100644 --- a/katarsis/pride_square/location +++ b/katarsis/pride_square/location @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [location:-1,-1,0] description = This booth sells cloth garments of every description. Fine silks and linens line the walls, draped from every protrusion and stacked on every surface. gridlinks = ['north'] @@ -196,7 +200,7 @@ name = Pride Square terrain = city [location:0,0,1] -description = The stand is currently not in use for a performance, but shoppers gather here to rest and chat with one another. A pleasantly cool breeze coupled with a nice view of the market make this a good place to relax. +description = The stand is currently not in use for a performance, but shoppers gather here to rest and chat with one another. A pleasantly cool breeze coupled with a nice view of the market makes this a good place to relax. gridlinks = ['down'] name = The Bandstand terrain = inside diff --git a/katarsis/pride_square/prop b/katarsis/pride_square/prop index 64d1fd8..e7fb24a 100644 --- a/katarsis/pride_square/prop +++ b/katarsis/pride_square/prop @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [prop:fountain] impression = An inviting public fountain bubbles here, tempting you with thirst. keywords = fountain water diff --git a/login.txt b/login.txt new file mode 100644 index 0000000..86ce3a9 --- /dev/null +++ b/login.txt @@ -0,0 +1,21 @@ + 888 888 888 888 `788b, 8888888888b + 888 888888888888 888 888 `788b, 88888888888 + 8888888888888b 888888888888 888 888 `788b, d88P d88P + 88888888888888b d888, 888 888 888 `788b, d88P d88P + 888 `888 d88A88b, 888 888 888 d88P d88P d88P + 888 888 d88P `788b,d88P 888 888 d88P d888 d888b, + 888`88b 888 `788o8P d88P 888d88P ,d88P' d88P788b, + d88P `88b 888 ,d88P' d88P 88888P ,d88P' d88P `788b + d88P `88888P ,d88P' d88P 8888P ,d88P' d88P `88b + d88P `888P ,d88P' d88P 888P ,d88P' d88P `88b + 88888 d8888P db 8888888888 db 888888b d888b 88 d888b + 88888 d8888P d88b 88 d88b 88 `8b d8P `8b 88 d8P `8b + 88888 d8888P d8P`8b 88 d8P`8b 88 d8P `8b 88 `8b + 88888 d8888P d8P `8b 88 d8P `8b 888888P `78b, 88 `78b, + 88888 d8888P d88888888b 88 d88888888b 88 `8b `78b, 88 `78b, + 88888888888 88 88 88 88 88 88 `8b `8b d8P 88 `8b d8P + 88888 `8888b 88 88 88 88 88 88 88 `88888P 88 `88888P + 88888 `8888b + 88888 `8888b Aeons ago, in a time long since forgotten, this land was fair + 88888 `8888b and peaceful, governed by harmony and untouched by despair... + 88888 `8888b But then the evil came. diff --git a/menu b/menu index cd4e614..b1b0252 100644 --- a/menu +++ b/menu @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [__control__] read_only = yes @@ -73,7 +77,7 @@ branch_a = main_utility choice_a = abort selection create = dict([(str(x+1),y) for x,y in enumerate(user.list_avatar_names())]) default = a -description = This is the list of avatars available for you to awaken. +description = This is the list of avatars available for you to delete. prompt = Whom would you like to delete? [menu:disconnecting] @@ -83,7 +87,7 @@ description = $(red)Disconnecting...$(nrm) prompt = $(red)Closing your previous connection...$(nrm)$(eol) [menu:entering_account_name] -description = $(red) :;$(bld)%$(nrm)$(red). $(bld)%S%$(nrm)$(red); $(bld)%$(eol)$(nrm)$(red)t$(bld)S@@@@@ G@S$(nrm)$(red); $(bld)G@@$(nrm)$(red)t .$(bld)G@$(nrm)$(red)t$(eol)$(bld)%@@@@@@ $(nrm)$(red);$(bld)@@@@G @@@$(nrm)$(red). .t$(bld)SG@% $(nrm)$(red);$(bld)S$(nrm)$(red)t;.$(eol);$(bld)@@@@@S G@@@G @@G $(nrm)$(red): :$(bld)@@@@$(nrm)$(red): t$(bld)@ $(nrm)$(red):;$(eol).$(bld)@@@@@% $(nrm)$(red):$(bld)@@@G @@@@@$(nrm)$(red). :$(bld)@@@@ S@@$(nrm)$(red): . .$(bld)%G@@$(nrm)$(red); .$(eol) $(bld)@@@@@$(nrm)$(red); $(bld)G@@G $(nrm)$(red):;; .;$(bld)S@@S%%$(nrm)$(red): . .$(bld)%SS $(nrm)$(red).$(bld)@@@%$(nrm)$(red).$(bld)G@@S$(nrm)$(red): ;$(bld)@S $(nrm)$(red)t$(bld)@@@@ $(nrm)$(red);$(bld)@G$(eol) G@@@@$(nrm)$(red)::$(bld)@@S $(nrm)$(red):$(bld)@$(nrm)$(red)t.$(bld)@@G $(nrm)$(red).$(bld)@G $(nrm)$(red);$(bld)@S$(nrm)$(red);$(bld)G@G @@@S%$(nrm)$(red); $(bld)S@@@S $(nrm)$(red);$(bld)@@@S S@@@$(eol) %@@@@ S@S G@@S$(nrm)$(red)t$(bld)@G $(nrm)$(red):$(bld)@$(nrm)$(red)t $(bld)@@@$(nrm)$(red);:$(bld)@G @@@ $(nrm)$(red)t$(bld)GGG%$(nrm)$(red);. :$(bld)@@@$(nrm)$(red): ;$(bld)@GS$(nrm)$(red)t:$(eol) ;$(bld)@@@@$(nrm)$(red).$(bld)@S $(nrm)$(red). .$(bld)@@$(nrm)$(red)t $(bld)GG $(nrm)$(red);$(bld)@$(nrm)$(red). ;$(bld)@G$(nrm)$(red). $(bld)SG @@% @@@$(nrm)$(red)t .$(bld)@@@ @@@S$(eol) $(nrm)$(red).$(bld)@@@G%@S@G SG$(nrm)$(red). .$(bld)G $(nrm)$(red)t$(bld)@ GS @$(nrm)$(red):: $(bld)@@$(nrm)$(red): ;$(bld)%S@SSS @@% $(nrm)$(red):t$(bld)%GGSG$(eol) @@@GS$(nrm)$(red):$(bld)@@@$(nrm)$(red). .$(bld)S $(nrm)$(red):t$(bld)S@@% tt $(nrm)$(red):t .;$(bld)%G@@@% G@ %@@@S @@$(nrm)$(red). $(bld)%@@@$(eol) G@@S $(nrm)$(red)t$(bld)@@% $(nrm)$(red)::t$(bld)%SSGGG@% %$(nrm)$(red). .::;;;tt$(bld)%%$(nrm)$(red); $(bld)GS $(nrm)$(red);$(bld)S%$(nrm)$(red);: $(bld)@G $(nrm)$(red);$(bld)S$(nrm)$(red)t;.$(eol) $(bld)%@@$(nrm)$(red): $(bld)S@@ S S$(nrm)$(red): $(bld)G$(nrm)$(red);$(eol) ;$(bld)@@$(nrm)$(red). $(bld)G@$(nrm)$(red); t $(bld)t %$(eol) GS $(nrm)$(red).$(bld)S% $(nrm)$(red). . :$(eol) $(bld)%$(nrm)$(red)t :$(bld)t$(eol) $(nrm)$(red);: :. $(bld)$(grn)Aeons ago, in a time long since forgotten, this land was fair$(eol) $(nrm)$(red).. ; $(bld)$(grn)and peaceful, governed by harmony and untouched by despair...$(eol)$(eol) $(red)But then the evil came.$(eol) $(blk)[ http://katarsis.mudpy.org/ ]$(nrm) +description = $(inc:login.txt) error_bad_name = Your account name needs to contain only digits (0-9) and letters (a-z). prompt = Identify yourself: @@ -110,7 +114,7 @@ choice_p = permanently remove your account demand_a = user.account.getlist("avatars") demand_c = len(user.account.getlist("avatars")) < universe.categories["internal"]["limits"].getint("max_avatars") demand_d = user.account.getlist("avatars") -description = From here you can awaken, create and delete avatars. An avatar is your persona in the world of Katarsis. You can also leave or permanently delete your account. +description = $(red)$(inc:banner.txt)$(nrm)$(eol)$(eol)From here you can awaken, create and delete avatars. An avatar is your persona in the world of Katarsis. You can also leave or permanently delete your account. prompt = What would you like to do? [menu:verifying_new_password] diff --git a/mudpy b/mudpy index 519f07b..8e115bd 100755 --- a/mudpy +++ b/mudpy @@ -1,9 +1,9 @@ #!/usr/bin/python """Skeletal executable for the mudpy engine.""" -# Copyright (c) 2005, 2006 Jeremy Stanley . All rights -# reserved. Licensed per terms in the LICENSE file distributed with this -# software. +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. # core objects for the mudpy engine import mudpy diff --git a/mudpy.conf b/mudpy.conf index 6074cdf..02fd0b0 100644 --- a/mudpy.conf +++ b/mudpy.conf @@ -1,3 +1,7 @@ +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. + [__control__] default_files = { "account": "account", "actor": "actor", "command": "command", "internal": "internal", "location": "location", "menu": "menu", "other": "other", "prop": "prop" } include_files = [ "archetype", "katarsis/index" ] @@ -39,6 +43,8 @@ definition_w = 7d definition_y = 12mo frequency_log = 6000 frequency_save = 600 +linkdead = { "default": 6000, "entering_account_name": 600, "active": 6048000 } +idle = { "default": 5000, "entering_account_name": 500, "active": 5040000 } increment = 0.1 [internal:directions] diff --git a/mudpy.py b/mudpy.py index 01b9142..699eae1 100644 --- a/mudpy.py +++ b/mudpy.py @@ -1,8 +1,8 @@ """Core objects for the mudpy engine.""" -# Copyright (c) 2005, 2006 Jeremy Stanley . All rights -# reserved. Licensed per terms in the LICENSE file distributed with this -# software. +# Copyright (c) 2004-2008 Jeremy Stanley . Permission +# to use, copy, modify, and distribute this software is granted under +# terms provided in the LICENSE file distributed with this software. # import some things we need from ConfigParser import RawConfigParser @@ -508,6 +508,7 @@ class User: self.error = "" self.input_queue = [] self.last_address = "" + self.last_input = universe.get_time() self.menu_choices = {} self.menu_seen = False self.negotiation_pause = 0 @@ -530,6 +531,27 @@ class User: self.connection.close() self.remove() + def check_idle(self): + """Warn or disconnect idle users as appropriate.""" + idletime = universe.get_time() - self.last_input + linkdead_dict = universe.categories["internal"]["time"].getdict("linkdead") + if self.state in linkdead_dict: linkdead_state = self.state + else: linkdead_state = "default" + if idletime > linkdead_dict[linkdead_state]: + self.send("$(eol)$(red)You've done nothing for far too long... goodbye!$(nrm)$(eol)", flush=True, add_prompt=False) + logline = "Disconnecting " + if self.account and self.account.get("name"): logline += self.account.get("name") + else: logline += "an unknown user" + logline += " after idling too long in a " + self.state + " state." + log(logline, 2) + self.state = "disconnecting" + self.menu_seen = False + idle_dict = universe.categories["internal"]["time"].getdict("idle") + if self.state in idle_dict: idle_state = self.state + else: idle_state = "default" + if idletime == idle_dict[idle_state]: + self.send("$(eol)$(red)If you continue to be unproductive, you'll be shown the door...$(nrm)$(eol)") + def reload(self): """Save, load a new user and relocate the connection.""" @@ -677,6 +699,9 @@ class User: self.state = "disconnecting" self.menu_seen = False + # check for an idle connection and act appropriately + else: self.check_idle() + # if output is paused, decrement the counter if self.state == "initial": if self.negotiation_pause: self.negotiation_pause -= 1 @@ -992,21 +1017,40 @@ def wrap_ansi_text(text, width): # ignoring color escape sequences relative_position = 0 + # whether the current character is part of a telnet IAC sequence + iac_counter = 0 + # whether the current character is part of a color escape sequence escape = False # iterate over each character from the begining of the text for each_character in text: + # the current character is the telnet IAC character + if each_character == IAC and not iac_counter: + iac_counter = 2 + + # the current character is within an IAC sequence + elif iac_counter: + + # the current character is another IAC, + # terminating the sequence + if each_character == IAC: + iac_counter = 0 + + # otherwise, decrement the IAC counter + else: + iac_counter -= 1 + # the current character is the escape character - if each_character == chr(27): + elif each_character == chr(27) and not escape: escape = True # the current character is within an escape sequence elif escape: # the current character is m, which terminates the - # current escape sequence + # escape sequence if each_character == "m": escape = False @@ -1017,7 +1061,7 @@ def wrap_ansi_text(text, width): # the current character meets the requested maximum line width, # so we need to backtrack and find a space at which to wrap - elif relative_position == width: + elif relative_position == width and not each_character == "\r": # distance of the current character examined from the # relative position @@ -1092,56 +1136,75 @@ def random_name(): def replace_macros(user, text, is_input=False): """Replaces macros in text output.""" + # third person pronouns + pronouns = { + "female": { "obj": "her", "pos": "hers", "sub": "she" }, + "male": { "obj": "him", "pos": "his", "sub": "he" }, + "neuter": { "obj": "it", "pos": "its", "sub": "it" } + } + + # a dict of replacement macros + macros = { + "eol": "\r\n", + "bld": chr(27) + "[1m", + "nrm": chr(27) + "[0m", + "blk": chr(27) + "[30m", + "blu": chr(27) + "[34m", + "cyn": chr(27) + "[36m", + "grn": chr(27) + "[32m", + "mgt": chr(27) + "[35m", + "red": chr(27) + "[31m", + "yel": chr(27) + "[33m", + } + + # add dynamic macros where possible + if user.account: + account_name = user.account.get("name") + if account_name: + macros["account"] = account_name + if user.avatar: + avatar_gender = user.avatar.get("gender") + if avatar_gender: + macros["tpop"] = pronouns[avatar_gender]["obj"] + macros["tppp"] = pronouns[avatar_gender]["pos"] + macros["tpsp"] = pronouns[avatar_gender]["sub"] + # loop until broken while True: - # third person pronouns - pronouns = { - "female": { "obj": "her", "pos": "hers", "sub": "she" }, - "male": { "obj": "him", "pos": "his", "sub": "he" }, - "neuter": { "obj": "it", "pos": "its", "sub": "it" } - } - - # a dict of replacement macros - macros = { - "$(eol)": "\r\n", - "$(bld)": chr(27) + "[1m", - "$(nrm)": chr(27) + "[0m", - "$(blk)": chr(27) + "[30m", - "$(blu)": chr(27) + "[34m", - "$(cyn)": chr(27) + "[36m", - "$(grn)": chr(27) + "[32m", - "$(mgt)": chr(27) + "[35m", - "$(red)": chr(27) + "[31m", - "$(yel)": chr(27) + "[33m", - } - - # add dynamic macros where possible - if user.account: - account_name = user.account.get("name") - if account_name: - macros["$(account)"] = account_name - if user.avatar: - avatar_gender = user.avatar.get("gender") - if avatar_gender: - macros["$(tpop)"] = pronouns[avatar_gender]["obj"] - macros["$(tppp)"] = pronouns[avatar_gender]["pos"] - macros["$(tpsp)"] = pronouns[avatar_gender]["sub"] - # find and replace per the macros dict macro_start = text.find("$(") if macro_start == -1: break macro_end = text.find(")", macro_start) + 1 - macro = text[macro_start:macro_end] + macro = text[macro_start+2:macro_end-1] if macro in macros.keys(): - text = text.replace(macro, macros[macro]) + replacement = macros[macro] + + # this is how we handle local file inclusion (dangerous!) + elif macro.startswith("inc:"): + incfile = path_join(universe.startdir, macro[4:]) + if exists(incfile): + incfd = file(incfile) + replacement = "" + for line in incfd: + if line.endswith("\n") and not line.endswith("\r\n"): + line = line.replace("\n", "\r\n") + replacement += line + # lose the trailing eol + replacement = replacement[:-2] + else: + replacement = "" + log("Couldn't read included " + incfile + " file.", 6) # if we get here, log and replace it with null else: - text = text.replace(macro, "") + replacement = "" if not is_input: log("Unexpected replacement macro " + macro + " encountered.", 6) + # and now we act on the replacement + text = text.replace("$(" + macro + ")", replacement) + # replace the look-like-a-macro sequence text = text.replace("$_(", "$(") @@ -1410,6 +1473,9 @@ def handle_user_input(user): # since we got input, flag that the menu/prompt needs to be redisplayed user.menu_seen = False + # update the last_input timestamp while we're at it + user.last_input = universe.get_time() + def generic_menu_handler(user): """A generic menu choice handler.""" -- 2.11.0