From: Oleg Broytman Date: Sat, 30 Nov 2013 21:54:41 +0000 (+0400) Subject: Initial import X-Git-Tag: 2.1.3~38 X-Git-Url: https://git.phdru.name/?a=commitdiff_plain;h=d7c459a9f979c4978cf07ff11056512a852fd61d;p=m_lib.git Initial import Start to move m_lib to git. --- d7c459a9f979c4978cf07ff11056512a852fd61d diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..bc86232 --- /dev/null +++ b/README.txt @@ -0,0 +1,41 @@ + defenc.py - get default encoding. + + flog.py - simple file logger. + + lazy/ - lazy evaluation modules - lazy dictionary and lazy import. + + mcrypt.py - crypt module supplement function gen_salt(). + + metaclasses.py - borrowed from Michele Simionato (mis6@pitt.edu) + to solve "TypeError: metatype conflict among bases". + + md5wrapper.py - just an MD5 wrapper. + + m_path.py - simple convenient get_homedir(). + + m_shutil.py - additional shell utilities (currently only mkhier + function). + + net/ftp/ - modules related to FTP - ftpparse (pure-python parser of LIST + command output) and ftpscan - recursive scanner of FTP directories. + + net/sms.py - Send SMS to subscribers of Moscow operators (Beeline, MTS, + Megafone) using their Web or SMTP gateways. + + net/www/ - modules related to Web/HTTP/HTML/XML/DTML/etc. + + opdate.py - additional date/time manipulation routines + In this module Date is a number of days since 1/1/1600 (up to 31 Dec 3999) + I am not sure about how easy it might be to extend the modules beyond + these bounds. Time is just a number of seconds since midnight. User can + add/subtract dates and times, calculate diffs ("how many days, months + and years passed since 21 Dec 1967?") and so on. DateTime <==> UTC (GMT) + conversion routines provided, too. + This module required flognat's strptime.py. + + opstring.py - additional string manipulation routines + (character padding, encoding conversions). + + rus/ - work with russian cyrillic - convert text to/from translit. + + tty_menu.py - extremely dumb text-mode menues. diff --git a/m_lib/__init__.py b/m_lib/__init__.py new file mode 100644 index 0000000..438c49c --- /dev/null +++ b/m_lib/__init__.py @@ -0,0 +1 @@ +"Broytman Library for Python, Copyright (C) 1996-2001 PhiloSoft Design" diff --git a/m_lib/clock/__init__.py b/m_lib/clock/__init__.py new file mode 100644 index 0000000..7719ef8 --- /dev/null +++ b/m_lib/clock/__init__.py @@ -0,0 +1 @@ +"Broytman Clock Library for Python, Copyright (C) 1996-2001 PhiloSoft Design" diff --git a/m_lib/clock/mkclock.py b/m_lib/clock/mkclock.py new file mode 100755 index 0000000..f65c766 --- /dev/null +++ b/m_lib/clock/mkclock.py @@ -0,0 +1,39 @@ +#! /usr/bin/env python +""" + Test if current interpreter do not have clock() and define it as need + + Written by Broytman, Jul 1997. Copyright (C) 1997 PhiloSoft Design +""" + +import sys, time + +print "Testing...", +sys.stdout.flush() + +time.sleep(3) +print '\n' + " "*len("Testing...") + '\n', + +need_clock = time.clock() <> 3 + +outfile = open("clock.py", 'w') + +if need_clock: + print "Generaing clock.py with custom clock()" + outfile.write("""\"\"\" + Define clock() for systems that do not have it +\"\"\" + +from time import time +_clock = time() + +def clock(): + return int(time() - _clock) +""") +else: + print "Generaing clock.py with standard clock()" + outfile.write("""\"\"\" + Define clock() shim +\"\"\" + +from time import clock + """) diff --git a/m_lib/clock/tstclock.py b/m_lib/clock/tstclock.py new file mode 100755 index 0000000..827fa1d --- /dev/null +++ b/m_lib/clock/tstclock.py @@ -0,0 +1,13 @@ +#! /usr/bin/env python +""" + Define clock() for systems that do not have it + + Written by Broytman, Jul 1997. Copyright (C) 1997 PhiloSoft Design +""" + +from clock import clock +from time import sleep + +print "Testing..." +sleep(3) +print "Clock:", clock() diff --git a/m_lib/defenc.py b/m_lib/defenc.py new file mode 100755 index 0000000..01d8990 --- /dev/null +++ b/m_lib/defenc.py @@ -0,0 +1,39 @@ +#! /usr/bin/env python +"""Get default encoding + + Written by Oleg Broytman. Copyright (C) 2007, 2008 PhiloSoft Design. +""" + +__all__ = ['default_encoding'] + +import sys + +try: + import locale + use_locale = True +except ImportError: + use_locale = False + +if use_locale: + # Get the default charset. + try: + lcAll = locale.getdefaultlocale() + except locale.Error, err: + print >>sys.stderr, "WARNING:", err + lcAll = [] + + if len(lcAll) == 2: + default_encoding = lcAll[1] + else: + try: + default_encoding = locale.getpreferredencoding() + except locale.Error, err: + print >>sys.stderr, "WARNING:", err + default_encoding = sys.getdefaultencoding() +else: + default_encoding = sys.getdefaultencoding() + +default_encoding = default_encoding.lower() + +if __name__ == "__main__": + print default_encoding diff --git a/m_lib/flog.py b/m_lib/flog.py new file mode 100755 index 0000000..b46bae5 --- /dev/null +++ b/m_lib/flog.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python +"""File logger + + Written by Broytman, Dec 1997. Copyright (C) 1997 PhiloSoft Design. +""" + + +from time import * + + +class FLog: + def __init__(self, f, overwrite = 0, timeformat = "%a %d %b %Y %T"): + if type(f) == type(''): # If f is string - use it as file's name + if overwrite: + mode = 'w' + else: + mode = 'a' + self.outfile = open(f, mode) + else: + self.outfile = f # else assume it is opened file (fileobject) or + # "compatible" object (must has write() method) + self.f = f + self.timeformat = timeformat + + + def __del__(self): + self.close() + + + def close(self): + if type(self.f) == type(''): # If f was opened - close it + self.outfile.close() + + + def log(self, str): + self.outfile.write("%s %s\n" % (strftime(self.timeformat, localtime(time())), str)) + + + __call__ = log + + + def flush(self): + self.outfile.flush() + + +def makelog(f): + return FLog(f, 1) + + +def openlog(f): + return FLog(f) + + +def test(): + log = makelog("test.log") + log.log("Log opened") + log("Log closed") + log.close() + +if __name__ == "__main__": + test() diff --git a/m_lib/lazy/__init__.py b/m_lib/lazy/__init__.py new file mode 100644 index 0000000..4dedfba --- /dev/null +++ b/m_lib/lazy/__init__.py @@ -0,0 +1,6 @@ +"Broytman WWW Library for Python, Copyright (C) 1996-2003 PhiloSoft Design" + + +__all__ = [ + "dict", "imports" +] diff --git a/m_lib/lazy/dict.py b/m_lib/lazy/dict.py new file mode 100644 index 0000000..c6750e4 --- /dev/null +++ b/m_lib/lazy/dict.py @@ -0,0 +1,37 @@ +"Lazy dictionaries calculate self content upon first access" + +class LazyDict: + "Abstract parent of all lazy dictionaries" + + def _init(self): + raise NotImplementedError + + def __getattr__(self, attr): + if self.data is None: self._init() + return getattr(self.data, attr) + + def __getitem__(self, key): + if self.data is None: self._init() + return self.data[key] + + def __setitem__(self, key, value): + if self.data is None: self._init() + self.data[key] = value + + +class LazyDictInitFunc(LazyDict): + "Lazy dict that initializes itself by calling supplied init function" + + def __init__(self, init=None, *args, **kw): + self.init = init + self.data = None + self.args = args + self.kw = kw + + def _init(self): + init = self.init + if init is None: + data = {} # just a new empty dict + else: + data = init(*self.args, **self.kw) + self.data = data diff --git a/m_lib/lazy/imports.py b/m_lib/lazy/imports.py new file mode 100644 index 0000000..99eb032 --- /dev/null +++ b/m_lib/lazy/imports.py @@ -0,0 +1,23 @@ +"Lazy imoport - import the module upon first request" + + +try: + from mx.Misc import LazyModule + +except ImportError: + + class LazyModule: + def __init__(self, module_name, locals, globals=None): + self.module = None + self.module_name = module_name + self.locals = locals + if globals is None: + globals = locals + self.globals = globals + + def __getattr__(self, attr): + if self.module is None: + self.module = module = __import__(self.module_name, self.globals, self.locals) + else: + module = self.module + return getattr(module, attr) diff --git a/m_lib/m_path.py b/m_lib/m_path.py new file mode 100755 index 0000000..6d20720 --- /dev/null +++ b/m_lib/m_path.py @@ -0,0 +1,26 @@ +#! /usr/bin/env python + +# +# useful function(s) for manipulating paths +# +# Author: Oleg Broytman +# Copyright (C) 2002 PhiloSoft Design +# + + +_homedir = None + +def get_homedir(): + global _homedir + if _homedir is None: + import sys, os + _homedir = os.path.abspath(os.path.dirname(sys.argv[0])) + return _homedir + + +def test(): + print get_homedir() + + +if __name__ == "__main__": + test() diff --git a/m_lib/m_shutil.py b/m_lib/m_shutil.py new file mode 100755 index 0000000..10e5ebf --- /dev/null +++ b/m_lib/m_shutil.py @@ -0,0 +1,38 @@ +#! /usr/bin/env python +"""Broytman's shell utilities. Additional to shutil.py (standard library module) + + Written by Broytman, Oct 1997. Copyright (C) 1997 PhiloSoft Design. +""" + + +import os, string + + +mkhier_error = "m_shutil.mkhier_error" + +def mkhier(path): # Python implementation of UNIX' mkdir -p /path/to/dir + if os.path.isdir(path): + return # It's Ok to have the directory already created + + if os.path.exists(path): + raise mkhier_error, "`%s' is file" % path + + list_dirs = string.split(path, os.sep) + #print list_dirs + for i in range(0, len(list_dirs)): + new_path = string.join(list_dirs[0:i+1], os.sep) + if (new_path <> '') and (not os.path.exists(new_path)): + #print "Making", new_path + os.mkdir(new_path) + + +def mcd(dir): + os.mkdir(dir) + os.chdir(dir) + + +def test(): + mkhier("I.AM/creating/TEST/dir") + +if __name__ == "__main__": + test() diff --git a/m_lib/mcrypt.py b/m_lib/mcrypt.py new file mode 100755 index 0000000..d6ae1dc --- /dev/null +++ b/m_lib/mcrypt.py @@ -0,0 +1,51 @@ +#! /usr/bin/env python + +# +# useful function(s) for module crypt +# +# Author: Oleg Broytman +# Copyright (C) 1998-1999 PhiloSoft Design +# + + +import random, crypt + + +saltchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcedfghijklmnopqrstuvwxyz0123456789./" +len_salt = len(saltchars) + +def gen_salt(): + """ + There are some difference among modern unicies. BSD/OS, for example, + uses MD5 hash, and ignores salt completely. FreeBSD uses 3 different + versions of crypt() - with standard salt, with extended 9-byte salt, + and MD5 (again, ignoring salt at all). + This function generates salt for standard "Broken DES"-based crypt(). + """ + r1 = random.randint(0, len_salt-1) + r2 = random.randint(0, len_salt-1) + return "%s%s" % (saltchars[r1], saltchars[r2]) + + +def test(): + passwd = raw_input("Enter password: ") + salt = gen_salt() + encrypted = crypt.crypt(passwd, salt) + + pwd_file = open("test.pwd", 'w') + pwd_file.write("%s:%s" % ("user", encrypted)) + pwd_file.close() + print "Password file written" + + import string + pwd_file = open("test.pwd", 'r') + username, encrypted = string.split(pwd_file.readline()[:-1], ':') + pwd_file.close() + + if crypt.crypt(encrypted, encrypted): + print "Password verified Ok" + else: + print "BAD password" + +if __name__ == "__main__": + test() diff --git a/m_lib/md5wrapper.py b/m_lib/md5wrapper.py new file mode 100755 index 0000000..0c1a5f1 --- /dev/null +++ b/m_lib/md5wrapper.py @@ -0,0 +1,53 @@ +#! /usr/bin/env python +"""User wrapper for md5 builtin object""" + +__author__ = "Oleg Broytman " +__copyright__ = "Copyright (C) 1997-2012 PhiloSoft Design" +__license__ = "GNU GPL" + +__all__ = ['md5wrapper'] + +import sys +if sys.version < '2.5': + from md5 import md5 +else: + from hashlib import md5 + +class md5wrapper: + def __init__(self, init=None): + if init: + self._md5 = md5(init) + else: + self._md5 = md5() + + def update(self, data): + self._md5.update(data) + + def digest(self): + return self._md5.digest() + + def __repr__(self): + str = self.digest() + return "%02x"*len(str) % tuple(map(ord, str)) + # This nice was suggested by Guido + + def md5file(self, f): + if type(f) == type(''): # If f is string - use it as file's name + infile = open(f, 'r') + else: + infile = f # else assume it is file or file-like object + + try: + while 1: + buf = infile.read(16*1024) + if not buf: break + self.update(buf) + + finally: + if type(f) == type(''): # If f was opened - close it + infile.close() + +if __name__ == "__main__": + print "This must print exactly the string" + print "Test: 900150983cd24fb0d6963f7d28e17f72" + print "Test:", md5wrapper("abc") diff --git a/m_lib/metaclasses.py b/m_lib/metaclasses.py new file mode 100755 index 0000000..27525af --- /dev/null +++ b/m_lib/metaclasses.py @@ -0,0 +1,64 @@ +#! /usr/bin/env python + + +# From: Michele Simionato (mis6@pitt.edu) +# http://groups.google.com/groups?selm=2259b0e2.0304250413.4be8ee45%40posting.google.com +# To solve "TypeError: metatype conflict among bases" + + +def _generatemetaclass(bases, metas): + "Internal function called by child" + + if metas == (type,): # trivial metaclass + metabases = (); metaname = "_" + else: # non-trivial metaclass + metabases = metas + metaname = "_"+''.join([m.__name__ for m in metas]) + trivial = lambda m: m in metabases or m is type + + for b in bases: + meta_b = type(b) + if not trivial(meta_b): + metabases += (meta_b,) + metaname += meta_b.__name__ + + if not metabases: # trivial metabase + return type + elif len(metabases) == 1: # single metabase + return metabases[0] + else: # multiple metabases + return type(metaname, metabases, {}) # creates a new metaclass + #shifting the possible conflict to meta-metaclasses + + +def child(*bases, **options): + """Class factory avoiding metatype conflicts: if the base classes have + metaclasses conflicting within themselves or with the given metaclass, + it automatically generates a compatible metaclass and instantiate the + child class from it. The recognized keywords in the option dictionary + are name, dic and meta.""" + name = options.get('name', ''.join([b.__name__ for b in bases])+'_') + dic = options.get('dic', {}) + metas = options.get('metas', (type,)) + return _generatemetaclass(bases, metas)(name, bases, dic) + + + +def test(): + class M_A(type): pass + class M_B(type): pass + A = M_A('A', (), {}) + B = M_B('B', (), {}) + + try: + class C(A, B): pass + except TypeError: + pass + else: + raise RuntimeError + + C = child(A, B, name='C') + + +if __name__ == "__main__": + test() diff --git a/m_lib/net/__init__.py b/m_lib/net/__init__.py new file mode 100644 index 0000000..b557b3a --- /dev/null +++ b/m_lib/net/__init__.py @@ -0,0 +1,6 @@ +"Broytman Net Library for Python, Copyright (C) 1996-2003 PhiloSoft Design" + + +__all__ = [ + "ftp", "www", "sms" +] diff --git a/m_lib/net/ftp/__init__.py b/m_lib/net/ftp/__init__.py new file mode 100644 index 0000000..7e30fda --- /dev/null +++ b/m_lib/net/ftp/__init__.py @@ -0,0 +1,16 @@ +"Broytman WWW Library for Python, Copyright (C) 1996-2003 PhiloSoft Design" + + +__all__ = [ + "ftpparse", "ftpscan", "TelnetFTP" +] + + +from ftplib import FTP +from telnetlib import IAC + + +class TelnetFTP(FTP): + def putline(self, line): + line = line.replace(IAC, IAC+IAC) + FTP.putline(self, line) diff --git a/m_lib/net/ftp/ftpparse.py b/m_lib/net/ftp/ftpparse.py new file mode 100755 index 0000000..cb4c569 --- /dev/null +++ b/m_lib/net/ftp/ftpparse.py @@ -0,0 +1,278 @@ +#! /usr/bin/env python +"""Parse output of FTP LIST command. + Pure python implementation. + Author: Oleg Broytman . + Copyright (C) 2003-2005 PhiloSoft Design. + License: GPL. + See http://cr.yp.to/ftpparse.html, http://effbot.org/downloads#ftpparse, + http://c0re.23.nu/c0de/ftpparsemodule/ and http://www.ocgy.ubc.ca/~tang/treeftp + +Currently covered formats: + UNIX ls, with or without gid; + Windoze FTP Servers; + VMS; + WFTPD; + NetPresenz (Mac); + NetWare; + DOS; + +Definitely not covered: + EPLF (show me an FTP server first...); + Long VMS filenames, with information split across two lines; + NCSA Telnet FTP server. Has LIST = NLST (and bad NLST for directories). +""" + + +__version__ = "1.1.2" +__author__ = "Oleg Broytman " +__copyright__ = "Copyright (C) 2003-2005 PhiloSoft Design" + + +# ChangeLog: +# 2005-04-26 version 1.1.2 [phd] Changed some comments and URLs. +# 2003-07-23 version 1.1.1 [phd] Upgrade to Python 2.2: 0/1 => False/True. +# 2003-07-17 version 1.1.0 [phd] Great renaming. +# 2003-07-17 version 1.0.1 [phd] Preserve leading spaces in UNIX format. +# 2003-02-17 version 1.0.0 [phd] First public version. +# 2003-02-07 version 0.0.1 [phd] started the project. + + +try: + from mx import DateTime +except ImportError: + _parse_datetime = False +else: + _parse_datetime = True + + +class parse_error(Exception): pass + + +class entry: + def __init__(self, name=None, perm=None, nlink=None, user=None, group=None, \ + size=None, mtime=None, links_to=None, file_type=None): + + if mtime: + mtime = ' '.join(mtime) + if _parse_datetime: + try: + mtime = DateTime.DateTimeFrom(mtime) + except DateTime.Error: + pass + + self.name = name + self.perm = perm + self.nlink = nlink + self.user = user + self.group = group + self.size = size + self.mtime = mtime + self.links_to = links_to + self.file_type = file_type # f - regular file, d - directory, l - symlink + + + def __str__(self): + return """<%s: name=%s, perm=%s, nlink=%s, user=%s, group=%s, size=%s, mtime=%s, links-to=%s, type=%s at 0x%x>""" % ( + self.__class__.__name__, self.name, self.perm, self.nlink, + self.user, self.group, self.size, self.mtime, + self.links_to, self.file_type, id(self)) + + +def _parse_unix(line, parts): + name = None + perm = None + nlink = None + user = None + group = None + size = None + mtime = None + links_to = None + file_type = None + + perm = parts[0] + + if parts[1][0] == '[': # NetWare + if perm[0] == 'd': + file_type = 'd' + elif perm[0] == '-': + file_type = 'f' + else: + return None + perm = perm + ' ' + parts[1] + user = parts[2] + size = parts[3] + mtime = parts[4:7] + + parts = line.split(None, 7) # resplit the original line... + name = parts[7] # ...in case the filename contains whitespaces + + elif parts[1] == "folder": # NetPresenz for the Mac + file_type = 'd' + size = parts[2] + mtime = parts[3:6] + + parts = line.split(None, 6) + name = parts[6] + + elif parts[0][0] == 'l': # symlink + file_type = 'l' + nlink = int(parts[1]) + user = parts[2] + group = parts[3] + size = parts[4] + mtime = parts[5:8] + + parts = line.split(None, 8) + link = parts[8] + parts = link.split(" -> ") + name = parts[0] + links_to = parts[1] + + elif len(parts) > 8: + if perm[0] == 'd': + file_type = 'd' + elif perm[0] == '-': + file_type = 'f' + else: + return None + nlink = int(parts[1]) + user = parts[2] + group = parts[3] + size = parts[4] + mtime = parts[5:8] + + parts = line.split(None, 7) + name = parts[7] + parts = name.split(' ')[1:] + name = ' '.join(parts) + + else: + if parts[2].isdigit(): # NetPresenz + file_type = 'f' + size = parts[3] + mtime = parts[4:7] + else: + if perm[0] == 'd': + file_type = 'd' + elif perm[0] == '-': + file_type = 'f' + else: + return None + nlink = int(parts[1]) + user = parts[2] + size = parts[3] + mtime = parts[4:7] + + parts = line.split(None, 7) + name = parts[7] + + return entry(name, perm, nlink, user, group, size, mtime, links_to, file_type) + + +def _parse_vms(parts): + name = parts[0] + perm = parts[5] + nlink = parts[1] + user = parts[4][1:-1] + group = None + size = None + mtime = parts[2:4] + links_to = None + file_type = None + + if ',' in user: + parts = user.split(',') + user = parts[0] + group = parts[1] + + return entry(name, perm, nlink, user, group, size, mtime, links_to, file_type) + + +def _parse_dos(parts): + name = parts[3] + perm = None + nlink = None + user = None + group = None + size = None + links_to = None + + if _parse_datetime: + # Change %m-%d-%y format to %y-%m-%d + date = parts[0] + date_parts = date.split('-') + date = "%s-%s-%s" % (date_parts[2], date_parts[0], date_parts[1]) + time = parts[1] + mtime = [date, time] + else: + mtime = parts[0:2] + + if parts[2] == "": + file_type = 'd' + else: + file_type = 'f' + size = int(parts[2]) + + return entry(name, perm, nlink, user, group, size, mtime, links_to, file_type) + + +def ftpparse(line): + parts = line.split() + c = parts[0][0] + + if c == '+': # EPLF format is not supported + return None + + if c in ('b', 'c', 'd', 'l', 'p', 's', '-'): # UNIX-like + return _parse_unix(line, parts) + + if ';' in parts[0]: # VMS + return _parse_vms(parts) + + if '0' <= c <= '9': # DOS + return _parse_dos(parts) + + + #Some useless lines, safely ignored: + #"Total of 11 Files, 10966 Blocks." (VMS) + #"total 14786" (UNIX) + #"DISK$ANONFTP:[ANONYMOUS]" (VMS) + #"Directory DISK$PCSA:[ANONYM]" (VMS) + + return None + + +def test(): + #UNIX-style listing, without inum and without blocks + print ftpparse("-rw-r--r-- 1 root other 531 Jan 29 03:26 README") + print ftpparse("dr-xr-xr-x 2 root other 512 Apr 8 1994 etc") + print ftpparse("dr-xr-xr-x 2 root 512 Apr 8 1994 etc") + print ftpparse("lrwxrwxrwx 1 root other 7 Jan 25 00:17 bin -> usr/bin") + #FTP servers for Windoze: + print ftpparse("---------- 1 owner group 1803128 Jul 10 10:18 ls-lR.Z") + print ftpparse("d--------- 1 owner group 0 May 9 19:45 Softlib") + #WFTPD for DOS: + print ftpparse("-rwxrwxrwx 1 noone nogroup 322 Aug 19 1996 message.ftp") + #NetWare: + print ftpparse("d [R----F--] supervisor 512 Jan 16 18:53 login") + print ftpparse("- [R----F--] rhesus 214059 Oct 20 15:27 cx.exe") + #NetPresenz for the Mac: + print ftpparse("-------r-- 326 1391972 1392298 Nov 22 1995 MegaPhone.sit") + print ftpparse("drwxrwxr-x folder 2 May 10 1996 network") + + #MultiNet (some spaces removed from examples) + print ftpparse("00README.TXT;1 2 30-DEC-1996 17:44 [SYSTEM] (RWED,RWED,RE,RE)") + print ftpparse("CORE.DIR;1 1 8-SEP-1996 16:09 [SYSTEM] (RWE,RWE,RE,RE)") + #non-MutliNet VMS: + print ftpparse("CII-MANUAL.TEX;1 213/216 29-JAN-1996 03:33:12 [ANONYMOU,ANONYMOUS] (RWED,RWED,,)") + + #DOS format + print ftpparse("04-27-00 09:09PM licensed") + print ftpparse("07-18-00 10:16AM pub") + print ftpparse("04-14-00 03:47PM 589 readme.htm") + + print ftpparse("-rw-r--r-- 1 root other 531 Jan 29 03:26 READ ME") + print ftpparse("-rw-r--r-- 1 root other 531 Jan 29 03:26 DO NOT READ ME ") + +if __name__ == "__main__": + test() diff --git a/m_lib/net/ftp/ftpscan.py b/m_lib/net/ftp/ftpscan.py new file mode 100755 index 0000000..319660b --- /dev/null +++ b/m_lib/net/ftp/ftpscan.py @@ -0,0 +1,283 @@ +#! /usr/bin/env python +"""Recursive FTP scanners""" + + +import ftplib +from m_lib.net.ftp.ftpparse import ftpparse + + +class FtpScanError(Exception): pass +ftpscan_error_mark = object() # error marker + + +class GetFiles: + def __init__(self): + self.entries = [] + + def __call__(self, line): + entry = ftpparse(line) + if entry: + self.entries.append(entry) + + def filter(self, file_type): + return filter(lambda e, file_type=file_type: e.file_type == file_type, + self.entries) + + def files(self): + return self.filter('f') + + def directories(self): + return filter(lambda e: e.name not in (".", ".."), self.filter('d')) + + +class ReconnectingFTPCallWrapper: + retries = 10 # retries per function call + + def __init__(self, wrapper, func): + self.wrapper = wrapper + self.func = func + + def __call__(self, *params, **kw): + wrapper = self.wrapper + func = self.func + + for retry in range(self.retries): + try: + return func(*params, **kw) + except EOFError: + pass + + ftp_dir = wrapper._ftp_dir + wrapper._tree.append((ftpscan_error_mark, "Connection reset by peer at directory `%s'. Reconnecting..." % ftp_dir)) + + ftp = wrapper._ftp + ftp.close() + + ftp.connect(wrapper._ftp_server, wrapper._ftp_port) + ftp.login(wrapper._login, wrapper._password) + ftp.cwd(ftp_dir) + +class ReconnectingFTPWrapper: + ReconnectingFTPCallWrapperClass = ReconnectingFTPCallWrapper + + def __init__(self, ftp, ftp_server, ftp_port=None, login=None, password=None, ftp_dir='/', tree=None): + self._ftp = ftp + self._ftp_server = ftp_server + self._ftp_port = ftp_port + self._login = login + self._password = password + ftp_dir = [''] + [name for name in ftp_dir.split('/') if name] # remove double slashes // + self._ftp_dir = '/'.join(ftp_dir) + self._tree = tree + + def cwd(self, new_cwd, do_ftp=True): + ftp_dir = self._ftp_dir.split('/') + if new_cwd == "..": + del ftp_dir[-1] + else: + ftp_dir.append(new_cwd) + self._ftp_dir = '/'.join(ftp_dir) + if do_ftp: self._wrap(self._ftp.cwd)(new_cwd) + + def __getattr__(self, attr): + value = getattr(self._ftp, attr) + if callable(value): + return self._wrap(value) + return value + + def _wrap(self, func): + return self.ReconnectingFTPCallWrapperClass(self, func) + + +def _traverse_ftp(ftp, tree, ftp_dir): + get_files = GetFiles() + try: + ftp.dir(get_files) + except ftplib.all_errors, msg: + tree.append((ftpscan_error_mark, "Cannot list directory `%s': %s" % (ftp_dir, msg))) + return + files = get_files.files() + directories = get_files.directories() + + if ftp_dir and ftp_dir[-1] == '/': + ftp_dir = ftp_dir[:-1] # Prevent paths to contain double slashes // + + tree.append((ftp_dir, files)) + + for d in directories: + name = d.name + full_path = ftp_dir + '/' + name + try: + ftp.cwd(name) + except ftplib.error_perm, msg: + tree.append((ftpscan_error_mark, "Cannot enter directory `%s': %s" % (full_path, msg))) + if isinstance(ftp, ReconnectingFTPWrapper): + ftp.cwd("..", False) + except ftplib.all_errors, msg: + tree.append((ftpscan_error_mark, "Cannot enter directory `%s': %s" % (full_path, msg))) + else: + _traverse_ftp(ftp, tree, full_path) + ftp.cwd("..") + + +def ftpscan1(ftp_server, ftp_port=None, login=None, password=None, + ftp_dir='/', passive=None, FTPClass=ftplib.FTP, reconnect=False, + ReconnectingFTPWrapperClass=ReconnectingFTPWrapper): + """Recursive FTP scan using one-by-one directory traversing. It is slow + but robust - it works with all but very broken FTP servers. + """ + tree = [] + ftp = FTPClass() + if passive is not None: + ftp.set_pasv(passive) + if reconnect: + ftp = ReconnectingFTPWrapperClass(ftp, ftp_server, ftp_port, login, password, ftp_dir, tree) + ftp.connect(ftp_server, ftp_port) + ftp.login(login, password) + if ftp_dir <> '/': + ftp.cwd(ftp_dir) + + _traverse_ftp(ftp, tree, ftp_dir) + ftp.quit() + + return tree + + +def ftpscanrecursive(ftp_server, ftp_port=None, login=None, password=None, + ftp_dir='/', passive=None, FTPClass=ftplib.FTP, reconnect=False): + """ + Recursive FTP scan using fast LIST -R command. Not all servers supports + this, though. + """ + ftp = FTPClass() + if passive is not None: + ftp.set_pasv(passive) + ftp.connect(ftp_server, ftp_port) + ftp.login(login, password) + if ftp_dir <> '/': + ftp.cwd(ftp_dir) + + lines = [] + try: + ftp.dir("-R", lines.append) + except ftplib.error_perm: + # The server does not implement LIST -R and + # treats -R as a name of a directory (-: + ftp.quit() + raise FtpScanError, "the server does not implement recursive listing" + ftp.quit() + + tree = [] + current_dir = ftp_dir + files = [] + + for line in lines: + if line: + if line[-1] == ':' and not line.startswith("-rw-"): # directory + tree.append((current_dir, files)) + if line[:2] == "./": + line = line[1:] # remove leading dot + elif line[0] <> '/': + line = '/' + line + current_dir = line[:-1] + files = [] + else: + if not line.startswith("total "): + entry = ftpparse(line) + if entry: + if entry.file_type == 'f': + files.append(entry) + else: + tree.append((ftpscan_error_mark, "Unrecognised line: `%s'" % line)) + tree.append((current_dir, files)) + + if len(tree) == 1: + raise FtpScanError, "the server ignores -R in LIST" + + return tree + + +def ftpscan(ftp_server, ftp_port=None, login=None, password=None, + ftp_dir='/', passive=None, FTPClass=ftplib.FTP): + try: + return ftpscanrecursive(ftp_server, ftp_port, login, password, ftp_dir, passive, FTPClass) + except FtpScanError: + try: + return ftpscan1(ftp_server, ftp_port, login, password, ftp_dir, passive, FTPClass) + except EOFError: + return ftpscan1(ftp_server, ftp_port, login, password, ftp_dir, passive, FTPClass, True) + except EOFError: + return ftpscan1(ftp_server, ftp_port, login, password, ftp_dir, passive, FTPClass, True) + + +def test(ftp_server, func, passive=None, reconnect=False): + from time import time + start_time = time() + + tree = func(ftp_server, passive=passive, reconnect=reconnect) + + stop_time = time() + print stop_time - start_time + + logfname = "%s.list" % ftp_server + log = open(logfname, 'w') + + for ftp_dir, files in tree: + if ftp_dir == ftpscan_error_mark: + log.write("Error:\n") + log.write(files) + log.write('\n') + else: + log.write(ftp_dir + '\n') + for _file in files: + log.write(" ") + log.write(_file.name + '\n') + + +def usage(code=0): + sys.stderr.write("Usage: %s [-a|-p] [hostname]\n" % sys.argv[0]) + sys.exit(code) + +if __name__ == "__main__": + import sys + from getopt import getopt, GetoptError + + try: + options, arguments = getopt(sys.argv[1:], "hap", + ["help", "active", "passive"]) + except GetoptError: + usage(1) + + passive = None + + for option, value in options: + if option in ("-h", "--help"): + usage() + elif option in ("-a", "--active"): + passive = False + elif option in ("-p", "--passive"): + passive = True + else: + usage(2) + + l = len(arguments) + if (l == 0): + ftp_server = "localhost" + elif l > 1: + usage() + else: + ftp_server = arguments[0] + + print "Scanning", ftp_server + try: + test(ftp_server, ftpscanrecursive, passive) + except FtpScanError, msg: + print "Rescanning due to the error:", msg + try: + test(ftp_server, ftpscan1, passive) + except EOFError: + print "Rescanning due to the error: connection reset by peer" + test(ftp_server, ftpscan1, passive, True) + except EOFError: + print "Rescanning due to the error: connection reset by peer" + test(ftp_server, ftpscan1, passive, True) diff --git a/m_lib/net/sms.py b/m_lib/net/sms.py new file mode 100644 index 0000000..423fd4a --- /dev/null +++ b/m_lib/net/sms.py @@ -0,0 +1,569 @@ +"SMS Transports by phd, corvin, r_sabitov" +# -*- coding: koi8-r -*- + + +import socket + +from m_lib.opstring import koi2win +from m_lib.rus.rus2lat import rus2lat + + +debug_level = 0 +use_syslog = 0 + +def debug(level, message): + if level <= debug_level: + if use_syslog: + import syslog + syslog.syslog(message) + import sys + sys.stderr.write("%s\n" % message) + + +smsSize = 160 + +# +# Base transport classes +# + +class Transport(object): + def __init__(self, phone, message): + self.phone = phone + self.message = message + + def calcText(self, maxsize=smsSize, transFunction=None): + "Recode text on demand and truncate the result" + + text = self.message + if transFunction: + text = transFunction(text) + text = text[:maxsize - 1] + return text + + def calcExpiration(self): + from mx import DateTime + return DateTime.now() + DateTime.DateTimeDelta(0, 4) + +class CP1251(object): + "Mixin that converts input text from koi8 to cp1251" + + def calcText(self, maxsize=smsSize): + text = super(CP1251, self).calcText(maxsize=maxsize) + return koi2win(text) + +# +# HTTP transport +# + +class HTTPTransport(Transport): + "Base class for HTTP transport" + + HTTPHeaders = { + 'User-Agent': 'Mozilla/5.0 (X11; U; Linux 2.2.19 i686; en-US; rv:0.9) Gecko/20010507', + 'Accept': '*/*' + } + + referer = '' + + def post(self, dict): + import urllib, urlparse + _none, host, uri, _none, _none, _none = urlparse.urlparse(self.url) + postdata = urllib.urlencode(dict) + + methodFunc = getattr(self, self.method) + + try: + reply = methodFunc(postdata, host, uri) + except socket.error: + ret = 0 + else: + html = reply[2].fp.read() + debug(2, html) + + if html.find(self.ok_match) >= 0: + ret = 1 + else: + ret = 0 + + + _debug = 'msg to %s via %s (%s)' % \ + (self.phone, self.__class__.__name__, ret) + debug(1, _debug) + + return ret + + def GET(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None): + "HTTP method GET" + + if postdata: + uri = uri + "?" + postdata + + import httplib + if proxy_host: + http = httplib.HTTP(proxy_host, proxy_port) + http.set_debuglevel(debug_level) + http.putrequest("GET", 'http://%s%s' % (host, uri)) + else: + http = httplib.HTTP(host, port) + http.set_debuglevel(debug_level) + http.putrequest("GET", uri) + + http.putheader("Host", host) + if self.referer: + http.putheader("Referer", self.referer) + + for name, val in self.HTTPHeaders.items(): + http.putheader(name, val) + + http.endheaders() + + reply = http.getreply() + reply[2].fp = http.getfile() + return reply + + def POST(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None): + "HTTP method POST" + + import httplib + if proxy_host: + http = httplib.HTTP(proxy_host, proxy_port) + http.set_debuglevel(debug_level) + http.putrequest("POST", 'http://%s%s' % (host, uri)) + else: + http = httplib.HTTP(host, port) + http.set_debuglevel(debug_level) + http.putrequest("POST", uri) + + http.putheader("Host", host) + if self.referer: + http.putheader("Referer", self.referer) + http.putheader('Content-Type', 'application/x-www-form-urlencoded') + http.putheader('Content-Length', str(len(postdata))) + + for name, val in self.HTTPHeaders.items(): + http.putheader(name, val) + + http.endheaders() + http.send(postdata) + + reply = http.getreply() + reply[2].fp = http.getfile() + return reply + +class CP1251HTTPTransport(CP1251, HTTPTransport): + pass + +# +# SNPP transport +# + +class SNPPTransport(Transport): + "Base class for SNPP transport" + + host = 'localhost' + port = 444 + ok_match = '250 Message Sent Successfully' + + def post(self, dict): + # raw snpp hack w/o error checking + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('www.extel-gsm.com', 4444)) + sock_file = sock.makefile('r') + + # 220 Extel Mobile Communications. SNPP Gateway v.0.2.15; Wed May 30 00:43:56 2001 + # 220 SNPP Gateway Ready + debug(2, sock_file.readline()) + debug(2, sock_file.readline()) + + sock.send('PAGE %s\r\n' % dict['phone']) + # 250 Pager ID Accepted + debug(2, sock_file.readline()) + + sock.send('DATA\r\n') + # 354 Begin Input; End with '.' + debug(2, sock_file.readline()) + + sock.send('%s\r\n' % dict['message']) + sock.send('.\r\n') + # 250 Message OK + debug(2, sock_file.readline()) + + sock.send('SEND\r\n') + # 250 Message Sent Successfully. Msg ID = 692274 + reply = sock_file.readline() + debug(2, reply) + + sock.send('QUIT\r\n') + sock.close() + except: + ret = 0 + else: + if reply.find(self.ok_match) >= 0: + ret = 1 + else: + ret = 0 + + _debug = 'msg to %s via %s (%s)' % \ + (self.phone, self.__class__.__name__, ret) + debug(1, _debug) + return ret + +# +# E-mail trnsport +# + +class EMailTransport(Transport): + def __init__(self, phone, message, mail_from=None, gate=None): + Transport.__init__(self, phone, message) + + if not mail_from: + import getpass + try: + realuser = getpass.getuser() + except AttributeError: + # Not all systems have os.environ or getpw... + realuser = 'nobody' + thishost = socket.getfqdn() + mail_from = "%s@%s" % (realuser, thishost) + debug(1, mail_from) + self.mail_from = mail_from + + self.gate = gate + + def send(self): + message = """\ +From: %s +To: %s@%s +Subject: SMS + +%s +""" % (self.mail_from, self.phone, self.gate, self.message) + self.post(message) + debug(2, message) + return 1 + +class SendmailTransport(EMailTransport): + def __init__(self, phone, message, mail_from=None, gate=None): + EMailTransport.__init__(self, phone, message, mail_from, gate) + + sendmail = self.find_sendmail() + if not sendmail: + raise ValueError, "cannot find sendmail binary" + self.sendmail = sendmail + + def find_sendmail(self): + import os + for sendmail in ("/usr/lib/sendmail", "/usr/sbin/sendmail"): + if os.path.exists(sendmail): + return sendmail + return None + + def post(self, message): + cmd = "%s -oem -oi '%s@%s'" % (self.sendmail, self.phone, self.gate) + debug(1, cmd) + import os + sendmail = os.popen(cmd, 'w') + sendmail.write(message) + sendmail.close() + +class SMTPTransport(EMailTransport): + def __init__(self, phone, message, mail_from=None, gate=None, mail_relay="localhost"): + EMailTransport.__init__(self, phone, message, mail_from, gate) + self.mail_relay = mail_relay + + def post(self, message): + debug(1, self.mail_relay) + import smtplib + smtp = smtplib.SMTP(self.mail_relay) + smtp.sendmail(self.mail_from, "%s@%s" % (self.phone, self.gate), message) + smtp.close() + + +# +# Real transports +# + +class MTSru(HTTPTransport): + "Russian provider MTS.ru" + + method = "POST" + url = "http://www.mts.ru:5051/cgi-bin/cgi.exe" + referer = "" + ok_match = '÷ÁÛÅ ÓÏÏÂÝÅÎÉÅ ÏÔÐÒÁ×ÌÅÎÏ' + + def send(self): + dict = {} + text = self.calcText() + time = self.calcExpiration() + + dict['function'] = 'sms_send' + #dict['MMObjectType'] = '0' + #dict['MMObjectID'] = '' + phone = self.phone + if phone[:4] == "8902": + phone = "8916%s" % phone[4:] + dict['To'] = phone + dict['Msg'] = text + dict['Hour'] = time.strftime('%k').strip() + dict['Min'] = time.strftime('%M') + dict['Day'] = time.strftime('%e').strip() + dict['Mon'] = str(time.month) + dict['Year'] = time.strftime('%Y') + dict['count'] = str(len(text)) + dict['Lang'] = '2' + + return self.post(dict) + + def POST(self, postdata, host, uri, port=80, proxy_host=None, proxy_port=None): + postdata = "MMObjectType=0&MMObjectID=&" + postdata + return HTTPTransport.POST(self, postdata, host, uri, port, proxy_host, proxy_port) + + +class MTSGSMcom(HTTPTransport): + url = "http://www.mtsgsm.com/sms/sent.html" + referer = "http://www.mtsgsm.com/sms/" + method = "GET" + ok_match = '÷ÁÛÅ ÓÏÏÂÝÅÎÉÅ ÏÔÐÒÁ×ÌÅÎÏ' + + def send(self): + dict = {} + text = self.calcText() + time = self.calcExpiration() + + dict['Posted'] = '1' + dict['To'] = self.phone + dict['Msg'] = text + dict['count'] = str(len(text)) + dict['SMSHour'] = time.strftime('%k').strip() + dict['SMSMinute'] = time.strftime('%M') + dict['SMSDay'] = time.strftime('%e').strip() + dict['SMSMonth'] = str(time.month - 1) + dict['SMSYear'] = time.strftime('%Y') + + return self.post(dict) + + +class BeeOnLine(CP1251HTTPTransport): + "Russian provider BeeOnLine.ru" + + method = "POST" + url = "http://www.beeonline.ru/portal/comm/send_sms/simple_send_sms.sms" + referer = "http://www.beeonline.ru/portal/comm/send_sms/simple_send_sms.sms" + crap = 'BOL ' + ok_match = koi2win('÷ÁÛÅ ÓÏÏÂÝÅÎÉÅ ÏÔÐÒÁ×ÌÅÎÏ') + + def __init__(self, phone, message, + mode="GSM", # mode can be either GSM or DAMPS + transliterate=1, # turn transliteration of/off + reply_to=''): # send reply to this e-mail + Transport.__init__(self, phone, message) + if mode not in ('GSM', 'DAMPS'): + raise ValueError, "mode (%s) must be either 'GSM' or 'DAMPS'" % mode + self.mode = mode + self.transliterate = transliterate + self.reply_to = reply_to + + def send(self): + dict = {} + text = self.calcText(smsSize - len(self.crap)) + + # hidden + #dict['deferto'] = '' + #dict['adv_year'] = '' + dict['send'] = 'send' + dict['destination_number_from'] = 'ordinary' # number, not a BEEpost + + prf = self.phone[:4] + if self.mode == "GSM": + dict['network_code'] = '3' + elif prf == '7095': + dict['network_code'] = '2' + elif prf == '7901': + dict['network_code'] = '1' + else: + raise RuntimeError, "incorrect combination of mode (%s) and prefix (%s)" % (self.mode, prf) + + dict['phone'] = self.phone[4:] + dict['message'] = text + dict['mlength'] = str(smsSize - len(self.crap) - len(text)) + + if self.mode == "GSM" and not self.transliterate: + dict['translit'] = '1' # turn transliteration OFF! :) + + if self.reply_to: + dict['send_email'] = '1' + dict['reply_addr'] = self.reply_to + + return self.post(dict) + +class BeeOnLineSMS(BeeOnLine): + "Russian provider BeeOnLine.ru" + + url = "http://www.beeonline.ru/servlet/send/sms/" + + def send(self): + dict = {} + text = self.calcText(smsSize - len(self.crap)) + + dict['prf'] = self.phone[:4] + dict['phone'] = self.phone[4:] + dict['termtype'] = self.mode[0] + dict['message'] = text + + if self.mode == "GSM" and not self.transliterate: + dict['translit'] = '1' # turn transliteration OFF! :) + + dict['number_sms'] = "number_sms_send" + return self.post(dict) + + +class Pcom(HTTPTransport): + "Russian provider PCOM.ru" + + method = "POST" + url = "http://www.pcom.ru/online.phtml" + referer = "http://www.pcom.ru/online.phtml" + crap = '' + ok_match = koi2win('ÐÏÓÔÁ×ÌÅÎÏ × ÏÞÅÒÅÄØ') + + def calcText(self, maxsize=smsSize): + "force translitertaion" + + return HTTPTransport.calcText(self, maxsize=maxsize, + transFunction=rus2lat) + + def send(self): + dict = {} + text = self.calcText(120 - len(self.crap)) + expiration = self.calcExpiration() + from mx import DateTime + now = DateTime.now() + + dict['ACTION'] = 'SENDSMS' + + dict['SMS_START_HOUR'] = now.strftime('%H') + dict['SMS_START_MINUTE'] = now.strftime('%M') + dict['SMS_START_DAY'] = now.strftime('%d') + dict['SMS_START_MONTH'] = now.strftime('%m') + dict['SMS_START_YEAR'] = now.strftime('%Y') + + dict['SMS_STOP_HOUR'] = expiration.strftime('%H') + dict['SMS_STOP_MINUTE'] = expiration.strftime('%M') + dict['SMS_STOP_DAY'] = expiration.strftime('%d') + dict['SMS_STOP_MONTH'] = expiration.strftime('%m') + dict['SMS_STOP_YEAR'] = expiration.strftime('%Y') + + dict['prefix'] = self.phone[1:4] + dict['DN'] = self.phone[4:] + dict['MSG'] = text + + return self.post(dict) + + +class ExtelGsmCom(SNPPTransport): + "Russian provider Extel-GSM.com" + + host = 'www.extel-gsm.com' + port = 4444 + ok_match = '250 Message Sent Successfully' + crap = '' + + prefix = '0119' + + def calcText(self, maxsize=smsSize): + "force translitertaion" + + return SNPPTransport.calcText(self, maxsize=maxsize, + transFunction=rus2lat) + + def send(self): + dict = {} + text = self.calcText(smsSize - len(self.crap)) + phone = self.phone[5:] + + # 0112 (city code) must be replaced with (0119) + dict['phone'] = '+7%s%s' % (self.prefix, phone) + dict['message'] = text + + return self.post(dict) + + +class MegafoneMoscow(CP1251HTTPTransport): + "Rissian provider megafonmoscow.ru" + + method = "POST" + url = "http://www.megafonmoscow.ru/rus/sms.xpml" + referer = "http://www.megafonmoscow.ru/rus/sms.xpml" + ok_match = koi2win('óÏÏÂÝÅÎÉÅ ÕÓÐÅÛÎÏ ÏÔÐÒÁ×ÌÅÎÏ') + + def send(self): + dict = {} + text = self.calcText() + + dict['prefix'] = self.phone[:4] + dict['addr'] = self.phone[4:] + dict['message'] = text + dict['send'] = " Send " + + if hasattr(self, 'transliterate') and self.transliterate: + dict['transliterate'] = '1' + + return self.post(dict) + + +class SkyLinkMsk(CP1251HTTPTransport): + "Russian provider SkyLink.Msk.ru" + + method = "POST" + url = "http://skylink.msk.ru/inc/sendsms.php" + ok_match = koi2win('óÏÏÂÝÅÎÉÅ ÂÙÌÏ ÏÔÐÒÁ×ÌÅÎÏ') + + def send(self): + dict = {} + text = self.calcText() + + phone = self.phone + if phone[0] == '7': + phone = '8' + phone[1:] + dict['dest'] = phone + dict['smsbody'] = text + + return self.post(dict) + + +BEELINE = BeeOnLineSMS +#BEELINE = Whizdiary +MTS = MTSru +#MTS = MTSGSMcom +#MTS = Whizdiary +SONET = Pcom +EXTEL = ExtelGsmCom +Megafone = MegafoneMoscow +SkyLink = SkyLinkMsk + + +Prefix2Provider = { + "903": BEELINE, + "905": BEELINE, + "906": BEELINE, + + "910": MTS, + "916": MTS, + + "926": Megafone +} + + +class SMSError(Exception): pass + +def Phone2Provider(phone): + prefix = phone[1:4] + + if prefix in ("095", "901"): # 901 is being used by Beeline and SkyLink + raise SMSError, "unknown provider for phone %s" % phone + + if Prefix2Provider.has_key(prefix): + return Prefix2Provider[prefix] + + raise SMSError, "bad prefix for phone %s" % phone diff --git a/m_lib/net/www/__init__.py b/m_lib/net/www/__init__.py new file mode 100644 index 0000000..27e9a0b --- /dev/null +++ b/m_lib/net/www/__init__.py @@ -0,0 +1,6 @@ +"Broytman WWW Library for Python, Copyright (C) 1996-2002 PhiloSoft Design" + + +__all__ = [ + "util", "dtml", "url_lib", "html", "xml", "serverpush" +] diff --git a/m_lib/net/www/dtml.py b/m_lib/net/www/dtml.py new file mode 100644 index 0000000..f8f42ee --- /dev/null +++ b/m_lib/net/www/dtml.py @@ -0,0 +1,17 @@ +"DTML utilities" + +class standard_html: # Base class for using with ZTemplates + def __init__(self, title): + self.standard_html_header = """ + + + %s + + + +""" % title + + self.standard_html_footer = """ +""" + + self.title = title diff --git a/m_lib/net/www/html.py b/m_lib/net/www/html.py new file mode 100644 index 0000000..9a9ef6f --- /dev/null +++ b/m_lib/net/www/html.py @@ -0,0 +1,145 @@ +"HTML parsers" + + +from HTMLParser import HTMLParser as _HTMLParser +from htmlentitydefs import entitydefs + + +def join_attrs(attrs): + attr_list = [''] + for attrname, value in attrs: + if value is None: + attr_list.append('%s' % attrname) + else: + attr_list.append('%s="%s"' % (attrname, value.strip())) + + return ' '.join(attr_list) + + +class HTMLParser(_HTMLParser): + + + def __init__(self): + _HTMLParser.__init__(self) + self.accumulator = "" + + + def handle_starttag(self, tag, attrs): + try: + method = getattr(self, 'start_' + tag) + except AttributeError: + try: + method = getattr(self, 'do_' + tag) + except AttributeError: + self.unknown_starttag(tag, attrs) + else: + method(attrs) + else: + method(attrs) + + def handle_endtag(self, tag): + try: + method = getattr(self, 'end_' + tag) + except AttributeError: + self.unknown_endtag(tag) + else: + method() + + + def handle_data(self, data): + if data: + self.accumulator = "%s%s" % (self.accumulator, data) + + def handle_comment(self, data): + if data: + self.accumulator = "%s" % (self.accumulator, data) + + + def handle_charref(self, name): + self.accumulator = "%s&#%s;" % (self.accumulator, name) + + def handle_entityref(self, name): + if entitydefs.has_key(name): # If it is one of the standard SGML entities - close it with semicolon + x = ';' + else: + x = '' + self.accumulator = "%s&%s%s" % (self.accumulator, name, x) + + + # Pass other tags unmodified + def unknown_starttag(self, tag, attrs): + self.accumulator = "%s<%s%s>" % (self.accumulator, tag, join_attrs(attrs)) + + def unknown_endtag(self, tag): + self.accumulator = "%s" % (self.accumulator, tag) + + +# Additional classes for filters + +class _allowStartTag: + def __init__(self, filter, tag): + self.filter = filter + self.tag = tag + + def __call__(self, attrs): + filter = self.filter + filter.accumulator = "%s<%s%s>" % (filter.accumulator, self.tag, join_attrs(attrs)) + +class _allowEndTag: + def __init__(self, filter, tag): + self.filter = filter + self.tag = tag + + def __call__(self): + filter = self.filter + filter.accumulator = "%s" % (filter.accumulator, self.tag) + + +class HTMLFilter(HTMLParser): + allowStartTagClass = _allowStartTag + allowEndTagClass = _allowEndTag + + def handle_comment(self, data): + pass + + # Filter out all tags + def unknown_starttag(self, tag, attrs): + pass + + def unknown_endtag(self, tag): + pass + + + def allow_startTag(self, tag): + setattr(self, "start_%s" % tag, self.allowStartTagClass(self, tag)) + + def allow_endTag(self, tag): + setattr(self, "end_%s" % tag, self.allowEndTagClass(self, tag)) + + +# how to use them: + +#class DocHTMLFilter(HTMLFilter): +# def __init__(self): +# HTMLFilter.__init__(self) +# +# # allow tags , ,
+# # ... and closing tags +# +# self.allow_startTag('table') +# self.allow_endTag('table') +# +# self.allow_startTag('tr') +# self.allow_endTag('tr') +# +# self.allow_startTag('td') +# self.allow_endTag('td') + +def filter_html(str, filter=None): + "Process HTML using some HTML parser/filter" + + if filter is None: + filter = HTMLFilter() + + filter.feed(str) + return filter.accumulator diff --git a/m_lib/net/www/serverpush.py b/m_lib/net/www/serverpush.py new file mode 100644 index 0000000..11f3c65 --- /dev/null +++ b/m_lib/net/www/serverpush.py @@ -0,0 +1,28 @@ +"Server Push" + + +import sys, mimetools + +class ServerPush: + def __init__(self, out=sys.stdout): + self.out = out + self.output = out.write + self.boundary = mimetools.choose_boundary() + + + def start(self): + self.output("""\ +Content-type: multipart/x-mixed-replace;boundary=%s + +""" % self.boundary) + + + def next(self, content_type="text/html"): + self.output("""\ +--%s +Content-type: %s +""" % (self.boundary, content_type)) + + + def stop(self): + self.output("--%s--" % self.boundary) diff --git a/m_lib/net/www/url_lib.py b/m_lib/net/www/url_lib.py new file mode 100644 index 0000000..2ba2d4b --- /dev/null +++ b/m_lib/net/www/url_lib.py @@ -0,0 +1,21 @@ +"url_lib" + + +from urllib import FancyURLopener + +class NoAsk_URLopener(FancyURLopener): + "URL opener that does not ask for a password interactively" + + def __init__(self, username, password): + FancyURLopener.__init__(self) + + self.username = username + self.password = password + self._asked = 0 + + def prompt_user_passwd(self, host, realm): + if self._asked: + return None, None + + self._asked = 1 + return self.username, self.password diff --git a/m_lib/net/www/util.py b/m_lib/net/www/util.py new file mode 100644 index 0000000..28b8f09 --- /dev/null +++ b/m_lib/net/www/util.py @@ -0,0 +1,84 @@ +"Common WWW/CGI utilities" + + +import sys, os + + +def exception(str = ""): + if sys.exc_type == SystemExit: # pass exit() normally + return + + print "Content-Type: text/html" + print # Terminate HTTP headers + + import html + print html.exception() + + if str: + print str + + sys.exit(1) + + +def error(err_str): + if not err_str: + err_str = "Unknown error" + + print "Content-Type: text/html" + print # Terminate HTTP headers + + print str(err_str) + sys.exit(1) + + +def get_redirect(_str = ""): + server_name = os.environ["SERVER_NAME"] + server_port = os.environ["SERVER_PORT"] + + if server_port == "80": + server_port = "" + else: + server_port = ":" + server_port + + return "http://" + server_name + server_port + _str + + +def convert_empty(estr): + if estr: + _str = str(estr) + else: + _str = " " + return _str + + +def gen_html(title, body): + print """ + + + + %s + + + + + %s + + + """ % (title, body) + + +def mkexpires(hours=1, minutes=0, seconds=0): + from datetime import datetime, timedelta + expire = datetime.now() + timedelta(hours=hours, minutes=minutes, seconds=seconds) + return "Expires: %s" % expire.strftime("%a, %d %b %Y %H:%M:%S GMT") + + +def parse_time(t): + import time + for format in ("%a, %d %b %Y %H:%M:%S GMT", "%A, %d-%b-%y %H:%M:%S GMT", "%A, %d-%b-%Y %H:%M:%S GMT"): + try: + return time.mktime(time.strptime(t, format)) - time.timezone + except (ValueError, OverflowError): + pass + + return None diff --git a/m_lib/net/www/xml.py b/m_lib/net/www/xml.py new file mode 100644 index 0000000..532afb6 --- /dev/null +++ b/m_lib/net/www/xml.py @@ -0,0 +1,58 @@ +"XML parsers" + + +import re, xmllib +illegal = re.compile('[^\t\r\n -\377]') # illegal chars in content +xmllib.illegal = illegal # allow cyrillic characters in XML + + +def join_xml_attrs(attrs): + attr_list = [''] + for attrname, value in attrs.items(): + attr_list.append('%s="%s"' % (attrname, string.strip(value))) + + return string.join(attr_list, " ") + + +class XMLParser(xmllib.XMLParser): + def __init__(self): + xmllib.XMLParser.__init__(self) + self.accumulator = "" + + + def handle_data(self, data): + if data: + self.accumulator = "%s%s" % (self.accumulator, data) + + def handle_comment(self, data): + if data: + self.accumulator = "%s" % (self.accumulator, data) + + + # Pass other tags unmodified + def unknown_starttag(self, tag, attrs): + self.accumulator = "%s<%s%s>" % (self.accumulator, tag, join_xml_attrs(attrs)) + + def unknown_endtag(self, tag): + self.accumulator = "%s" % (self.accumulator, tag) + + +class XMLFilter(XMLParser): + def handle_comment(self, data): + pass + + # Filter out all tags + def unknown_starttag(self, tag, attrs): + pass + + def unknown_endtag(self, tag): + pass + +def filter_xml(str, filter=None): + "Process XML using some XML parser/filter" + + if filter is None: + filter = XMLFilter() + + filter.feed(str) + return filter.accumulator diff --git a/m_lib/opdate.py b/m_lib/opdate.py new file mode 100755 index 0000000..ac53c81 --- /dev/null +++ b/m_lib/opdate.py @@ -0,0 +1,480 @@ +#! /usr/bin/env python +# -*- coding: koi8-r -*- + +# +# opDate - date/time manipulation routines +# Some ideas came from Turbo Professional/Object Professional (t/o)pDate.PAS +# +# Written by Broytman, Nov 1997 - Dec 2003 +# Copyright (C) 1997-2003 PhiloSoft Design +# + + +from string import * +from time import * +from calendar import * +from opstring import * + + +MinYear = 1600 +MaxYear = 3999 +MinDate = 0x00000000 # = 01/01/1600 +MaxDate = 0x000D6025 # = 12/31/3999 +Date1900 = 0x0001AC05 # = 01/01/1900 +Date1980 = 0x00021E28 # = 01/01/1980 +Date2000 = 0x00023AB1 # = 01/01/2000 +BadDate = 0x7FFFFFFF + +Threshold2000 = 1900 + +MinTime = 0 # = 00:00:00 am +MaxTime = 86399 # = 23:59:59 pm +BadTime = 0x7FFFFFFF + +SecondsInDay = 86400 # number of seconds in a day +SecondsInHour = 3600 # number of seconds in an hour +SecondsInMinute = 60 # number of seconds in a minute +HoursInDay = 24 # number of hours in a day +MinutesInHour = 60 # number of minutes in an hour + +First2Months = 59 # 1600 was a leap year +FirstDayOfWeek = 5 # 01/01/1600 was a Saturday + + +# Errors +class opdate_error(Exception): + pass + + +# +### Date manipulation routines +# + +def IsLeapYear(Year): + if ( (Year % 4 == 0) and (Year % 4000 <> 0) and ((Year % 100 <> 0) or (Year % 400 == 0)) ): + return True + return False + + +def _setYear(Year): + # Internal function + if Year < 100: + Year = Year + 1900 + if Year < Threshold2000: + Year = Year + 100 + return Year + + +def DaysInMonth(Month, Year): + """ Return the number of days in the specified month of a given year """ + if Month in [1, 3, 5, 7, 8, 10, 12]: + return 31 + + elif Month in [4, 6, 9, 11]: + return 30 + + elif Month == 2: + return 28+IsLeapYear(_setYear(Year)) + + else: + raise opdate_error, "bad month `%s'" % str(Month) + + +def ValidDate(Day, Month, Year): + """ Verify that day, month, year is a valid date """ + Year = _setYear(Year) + + if (Day < 1) or (Year < MinYear) or (Year > MaxYear): + return False + elif (Month >= 1) and (Month <= 12): + return Day <= DaysInMonth(Month, Year) + else: + return False + + +def DMYtoDate(Day, Month, Year): + """ Convert from day, month, year to a julian date """ + Year = _setYear(Year) + + if not ValidDate(Day, Month, Year): + return BadDate + + if (Year == MinYear) and (Month < 3): + if Month == 1: + return Day-1 + else: + return Day+30 + else: + if Month > 2: + Month = Month - 3 + else: + Month = Month + 9 + Year = Year - 1 + Year = Year - MinYear + + return (((Year / 100)*146097) / 4) + (((Year % 100)*1461) / 4) + (((153*Month)+2) / 5)+Day+First2Months + + +def DateToDMY(Julian): + """ Convert from a julian date to day, month, year """ + if Julian == BadDate: + return 0, 0, 0 + + if Julian <= First2Months: + Year = MinYear + if Julian <= 30: + Month = 1 + Day = Julian + 1 + else: + Month = 2 + Day = Julian-30 + else: + I = (4*(Julian-First2Months))-1 + J = (4*((I % 146097) / 4))+3 + Year = (100*(I / 146097))+(J / 1461) + I = (5*(((J % 1461)+4) / 4))-3 + Month = I / 153 + Day = ((I % 153)+5) / 5 + if Month < 10: + Month = Month + 3 + else: + Month = Month - 9 + Year = Year + 1 + Year = Year + MinYear + + return Day, Month, Year + + +def IncDate(Julian, Days, Months, Years): + """ Add (or subtract) the number of months, days, and years to a date. + Months and years are added before days. No overflow/underflow checks are made + """ + Day, Month, Year = DateToDMY(Julian) + Day28Delta = Day-28 + if Day28Delta < 0: + Day28Delta = 0 + else: + Day = 28 + + Year = Year + Years + Year = Year + Months / 12 + Month = Month + Months % 12 + if Month < 1: + Month = Month + 12 + Year = Year - 1 + elif Month > 12: + Month = Month - 12 + Year = Year + 1 + + Julian = DMYtoDate(Day, Month, Year) + if Julian <> BadDate: + Julian = Julian + Days + Day28Delta + + return Julian + + +def IncDateTrunc(Julian, Months, Years): + """ Add (or subtract) the specified number of months and years to a date """ + Day, Month, Year = DateToDMY(Julian) + Day28Delta = Day-28 + if Day28Delta < 0: + Day28Delta = 0 + else: + Day = 28 + + Year = Year + Years + Year = Year + Months / 12 + Month = Month + Months % 12 + if Month < 1: + Month = Month + 12 + Year = Year - 1 + elif Month > 12: + Month = Month - 12 + Year = Year + 1 + + Julian = DMYtoDate(Day, Month, Year) + if Julian <> BadDate: + MaxDay = DaysInMonth(Month, Year) + if Day+Day28Delta > MaxDay: + Julian = Julian + MaxDay-Day + else: + Julian = Julian + Day28Delta + + return Julian + + +def DateDiff(Date1, Date2): + """ Return the difference in days,months,years between two valid julian dates """ + #we want Date2 > Date1 + if Date1 > Date2: + _tmp = Date1 + Date1 = Date2 + Date2 = _tmp + + #convert dates to day,month,year + Day1, Month1, Year1 = DateToDMY(Date1) + Day2, Month2, Year2 = DateToDMY(Date2) + + #days first + if Day2 < Day1: + Month2 = Month2 - 1 + if Month2 == 0: + Month2 = 12 + Year2 = Year2 - 1 + Day2 = Day2 + DaysInMonth(Month2, Year2) + Days = abs(Day2-Day1) + + #now months and years + if Month2 < Month1: + Month2 = Month2 + 12 + Year2 = Year2 - 1 + Months = Month2-Month1 + Years = Year2-Year1 + + return Days, Months, Years + + +def DayOfWeek(Julian): + """ Return the day of the week for the date. Returns DayType(7) if Julian == BadDate. """ + if Julian == BadDate: + raise opdate_error, "bad date `%s'" % str(Julian) + else: + return (Julian+FirstDayOfWeek) % 7 + + +def DayOfWeekDMY(Day, Month, Year): + """ Return the day of the week for the day, month, year """ + return DayOfWeek( DMYtoDate(Day, Month, Year) ) + + +#def MonthStringToMonth(MSt): +# """ Convert the month name in MSt to a month (1..12) or -1 on error """ +# lmn = strptime.LongMonthNames[strptime.LANGUAGE] +# smn = strptime.ShortMonthNames[strptime.LANGUAGE] +# lmna = LongMonthNamesA +# +# I = FindStr(MSt, lmn)+1 or FindStr(MSt, smn)+1 or \ +# FindStrUC(MSt, lmn)+1 or FindStrUC(MSt, smn)+1 or \ +# FindStr(MSt, lmna)+1 or FindStrUC(MSt, lmna)+1 +# +# return I-1 + + +def Today(): + """ Returns today's date as a julian """ + Year, Month, Day = localtime(time())[0:3] + return DMYtoDate(Day, Month, Year) + +# +### Time manipulation routines +# + +def TimeToHMS(T): + """ Convert a Time variable to Hours, Minutes, Seconds """ + if T == BadTime: + return 0, 0, 0 + + else: + Hours = T / SecondsInHour + T = T - Hours*SecondsInHour + Minutes = T / SecondsInMinute + T = T - Minutes*SecondsInMinute + Seconds = T + + return Hours, Minutes, Seconds + + +def HMStoTime(Hours, Minutes, Seconds): + """ Convert Hours, Minutes, Seconds to a Time variable """ + Hours = Hours % HoursInDay + T = Hours*SecondsInHour + Minutes*SecondsInMinute + Seconds + + return T % SecondsInDay + + +def ValidTime(Hours, Minutes, Seconds): + """ Return true if Hours:Minutes:Seconds is a valid time """ + return (0 <= Hours < 24) and (0 <= Minutes < 60) and (0 <= Seconds < 60) + + +def CurrentTime(): + """ Returns current time in seconds since midnight """ + Hours, Minutes, Seconds = localtime(time())[3:6] + return HMStoTime(Hours, Minutes, Seconds) + + +def TimeDiff(Time1, Time2): + """ Return the difference in hours,minutes,seconds between two times """ + if Time1 > Time2: + T = Time1-Time2 + else: + T = Time2-Time1 + + Hours, Minutes, Seconds = TimeToHMS(T) + return Hours, Minutes, Seconds + + +def IncTime(T, Hours, Minutes, Seconds): + """ Add the specified hours,minutes,seconds to T and return the result """ + T = T + HMStoTime(Hours, Minutes, Seconds) + return T % SecondsInDay + + +def DecTime(T, Hours, Minutes, Seconds): + """ Subtract the specified hours,minutes,seconds from T and return the result """ + Hours = Hours % HoursInDay + T = T - HMStoTime(Hours, Minutes, Seconds) + if T < 0: + return T+SecondsInDay + else: + return T + + +def RoundToNearestHour(T, Truncate = False): + """ Round T to the nearest hour, or Truncate minutes and seconds from T """ + Hours, Minutes, Seconds = TimeToHMS(T) + Seconds = 0 + + if not Truncate: + if Minutes >= (MinutesInHour / 2): + Hours = Hours + 1 + + Minutes = 0 + return HMStoTime(Hours, Minutes, Seconds) + + +def RoundToNearestMinute(T, Truncate = False): + """ Round T to the nearest minute, or Truncate seconds from T """ + Hours, Minutes, Seconds = TimeToHMS(T) + + if not Truncate: + if Seconds >= (SecondsInMinute / 2): + Minutes = Minutes + 1 + + Seconds = 0 + return HMStoTime(Hours, Minutes, Seconds) + + +def DateTimeDiff(DT1, DT2): + """ Return the difference in days,seconds between two points in time """ + # swap if DT1 later than DT2 + if (DT1[0] > DT2[0]) or ((DT1[0] == DT2[0]) and (DT1[1] > DT2[1])): + _tmp = DT1 + DT1 = DT2 + DT2 = _tmp + + # the difference in days is easy + Days = DT2[0]-DT1[0] + + # difference in seconds + if DT2[1] < DT1[1]: + # subtract one day, add 24 hours + Days = Days - 1 + DT2[1] = DT2[1] + SecondsInDay + + Secs = DT2[1]-DT1[1] + return Days, Secs + + +def IncDateTime(DT1, Days, Secs): + """ Increment (or decrement) DT1 by the specified number of days and seconds + and put the result in DT2 """ + DT2 = DT1[:] + + # date first + DT2[0] = DT2[0] + Days + + if Secs < 0: + # change the sign + Secs = -Secs + + # adjust the date + DT2[0] = DT2[0] - Secs / SecondsInDay + Secs = Secs % SecondsInDay + + if Secs > DT2[1]: + # subtract a day from DT2[0] and add a day's worth of seconds to DT2[1] + DT2[0] = DT2[0] - 1 + DT2[1] = DT2[1] + SecondsInDay + + # now subtract the seconds + DT2[1] = DT2[1] - Secs + + else: + # increment the seconds + DT2[1] = DT2[1] + Secs + + # adjust date if necessary + DT2[0] = DT2[0] + DT2[1] / SecondsInDay + + # force time to 0..SecondsInDay-1 range + DT2[1] = DT2[1] % SecondsInDay + + return DT2 + + +# +### UTC (GMT) stuff +# + +UTC_0Date = DMYtoDate(1, 1, 1970) + + +def DateTimeToGMT(Date, Time = False): + Date = Date - UTC_0Date + return Date*SecondsInDay + Time + + +def GMTtoDateTime(GMT): + q, r = divmod(GMT, SecondsInDay) + return q + UTC_0Date, r + + +# +### Cyrillic stuff +# + +LongMonthNamesA = ['ñÎ×ÁÒÑ', 'æÅ×ÒÁÌÑ', 'íÁÒÔÁ', 'áÐÒÅÌÑ', 'íÁÑ', 'éÀÎÑ', + 'éÀÌÑ', 'á×ÇÕÓÔÁ', 'óÅÎÔÑÂÒÑ', 'ïËÔÑÂÒÑ', 'îÏÑÂÒÑ', 'äÅËÁÂÒÑ'] + + +# +### Test stuff +# + +def test(): + print "Is 1984 leap year?", IsLeapYear(1984) + print "Is 1990 leap year?", IsLeapYear(1990) + + print "Days in month 8 year 1996:", DaysInMonth(8, 1996) + + print "Is date 8/12/1996 valid?", ValidDate(8, 12, 1996) + print "Is date 40/11/1996 valid?", ValidDate(40, 11, 1996) + print "Is date 8/14/1996 valid?", ValidDate(8, 14, 1996) + + print "Date->DMY for 138219:", DateToDMY(138219) + + diff = DateDiff(DMYtoDate(12, 10, 1996), DMYtoDate(12, 10, 1997)) + print "Date 12/10/1996 and date 12/10/1997 diff: %d years, %d months, %d days" % (diff[2], diff[1], diff[0]) + + diff = DateDiff(DMYtoDate(12, 10, 1996), DMYtoDate(12, 11, 1997)) + print "Date 12/10/1996 and date 12/11/1997 diff: %d years, %d months, %d days" % (diff[2], diff[1], diff[0]) + + diff = DateDiff(DMYtoDate(31, 1, 1996), DMYtoDate(1, 3, 1996)) + print "Date 31/01/1996 and date 01/03/1996 diff: %d years, %d months, %d days" % (diff[2], diff[1], diff[0]) + + + #print "November is %dth month" % MonthStringToMonth("November") + + print "Today is", Today() + print "Now is", CurrentTime() + + print "My birthday 21 Dec 1967 is (must be Thursday):", day_name[DayOfWeekDMY(21, 12, 67)] + + gmt = DateTimeToGMT(DMYtoDate(21, 12, 1967), HMStoTime(23, 45, 0)) + print "21 Dec 1967, 23:45:00 --", gmtime(gmt) # DOS version of gmtime has error processing dates before 1/1/1970 :( + D, T = GMTtoDateTime(gmt) + print "(gmt) --", DateToDMY(D), TimeToHMS(T) + +if __name__ == "__main__": + test() diff --git a/m_lib/opstring.py b/m_lib/opstring.py new file mode 100755 index 0000000..92d193b --- /dev/null +++ b/m_lib/opstring.py @@ -0,0 +1,158 @@ +#! /usr/bin/env python +# -*- coding: koi8-r -*- + +# +# opString - string/pathnames manipulation routines +# Some ideas came from Turbo Professional/Object Professional (t/o)pString.PAS +# +# Written by Broytman, Nov 1997. Copyright (C) 1997 PhiloSoft Design +# + + +from string import * + + +# +### Int/string conversion routines +# + + +def bin(i): + """ + Convert integer to binary string. + """ + s = '' + q = i + while (1): + q, r = divmod(q, 2) + s = digits[r] + s + if q == 0: break + + return s + + +# +### String manipulation routines +# + + +def PadCh(S, Ch, Len): + """ Return a string right-padded to length Len with Ch """ + if len(S) >= Len: + return S + else: + return S + Ch*(Len - len(S)) + + +def Pad(S, Len): + """ Return a string right-padded to length len with blanks """ + return PadCh(S, ' ', Len) + + +def LeftPadCh(S, Ch, Len): + """ Return a string left-padded to length len with ch """ + if len(S) >= Len: + return S + else: + return Ch*(Len - len(S)) + S + + +def LeftPad(S, Len): + """ Return a string left-padded to length len with blanks """ + return LeftPadCh(S, ' ', Len) + + +def CenterCh(S, Ch, Width): + """ Return a string centered in a string of Ch with specified width """ + if len(S) >= Width: + return S + else: + l = (Width - len(S)) / 2 + r = Width - len(S) - l + return Ch*l + S + Ch*r + + +def Center(S, Width): + """ Return a string centered in a blank string of specified width """ + return CenterCh(S, ' ', Width) + + +def FindStr(str, list): + """ Find given string in the list of strings """ + for i in range(len(list)): + if str == list[i]: + return i + + return -1 + + +def FindStrUC(str, list): + """ Find string ignoring case """ + str = upper(str) + for i in range(len(list)): + if str == upper(list[i]): + return i + + return -1 + + +# óËÌÏÎÅÎÉÑ + +transl_adict = { + "day" : ["ÄÅÎØ", "ÄÎÑ", "ÄÎÅÊ"], + "week" : ["ÎÅÄÅÌÑ", "ÎÅÄÅÌÉ", "ÎÅÄÅÌØ"], + "month": ["ÍÅÓÑÃ", "ÍÅÓÑÃÁ", "ÍÅÓÑÃÅ×"], + "year" : ["ÇÏÄ", "ÇÏÄÁ", "ÌÅÔ"] +} + +transl_adict["days"] = transl_adict["day"] +transl_adict["weeks"] = transl_adict["week"] +transl_adict["months"] = transl_adict["month"] +transl_adict["years"] = transl_adict["year"] + +transl_vdict = { + 1: 0, + 2: 1, 3: 1, 4: 1, + 5: 2, 6: 2, 7: 2, 8: 2, 9: 2, 0: 2 +} + +def translate_a(val, id): + if not transl_adict.has_key(id): + return '' + + if 5 <= (val % 100) <= 20: + val = 2 + else: + val = transl_vdict[val % 10] + return transl_adict[id][val] + + +# Encodings, especially cyrillic. Requires Unicode, hence Python 2.0+ + +def recode(s, from_encoding, to_encoding, errors = "strict"): + return unicode(s, from_encoding, errors).encode(to_encoding, errors) + + +def win2koi(s, errors = "strict"): + return recode(s, "cp1251", "koi8-r", errors) + +def koi2win(s, errors = "strict"): + return recode(s, "koi8-r", "cp1251", errors) + + +# +### Test stuff +# + +def test(): + print "bin(0x6) =", bin(0x6) + print "bin(0xC) =", bin(0xC) + + print "'Test' left-padded :", LeftPad("Test", 20) + print "'Test' right-padded:", PadCh("Test", '*', 20) + print "'Test' centered :", CenterCh("Test", '=', 20) + + print "'ïÌÅÇ':", koi2win(win2koi("ïÌÅÇ")) + +if __name__ == "__main__": + test() diff --git a/m_lib/rus/__init__.py b/m_lib/rus/__init__.py new file mode 100644 index 0000000..a4f13fa --- /dev/null +++ b/m_lib/rus/__init__.py @@ -0,0 +1,6 @@ +"Broytman WWW Library for Python, Copyright (C) 1996-2003 PhiloSoft Design" + + +__all__ = [ + "rus2lat", "lat2rus" +] diff --git a/m_lib/rus/lat2rus.py b/m_lib/rus/lat2rus.py new file mode 100755 index 0000000..abf8af3 --- /dev/null +++ b/m_lib/rus/lat2rus.py @@ -0,0 +1,116 @@ +#! /usr/bin/env python +# -*- coding: koi8-r -*- + +# +# Lat -> Rus translation +# Written by Broytman. Copyright (C) 2002 PhiloSoft Design +# + +lat2koi_d = { + "q": "Ê", + "w": "Ã", + "e": "Õ", + "r": "Ë", + "t": "Å", + "y": "Î", + "u": "Ç", + "i": "Û", + "o": "Ý", + "p": "Ú", + "[": "È", + "]": "ß", + "a": "Æ", + "s": "Ù", + "d": "×", + "f": "Á", + "g": "Ð", + "h": "Ò", + "j": "Ï", + "k": "Ì", + "l": "Ä", + ";": "Ö", + "'": "Ü", + "z": "Ñ", + "x": "Þ", + "c": "Ó", + "v": "Í", + "b": "É", + "n": "Ô", + "m": "Ø", + ",": "Â", + ".": "À", + "Q": "ê", + "W": "ã", + "E": "õ", + "R": "ë", + "T": "å", + "Y": "î", + "U": "ç", + "I": "û", + "O": "ý", + "P": "ú", + "{": "è", + "}": "ÿ", + "A": "æ", + "S": "ù", + "D": "÷", + "F": "á", + "G": "ð", + "H": "ò", + "J": "ï", + "K": "ì", + "L": "ä", + ":": "ö", + "\"": "ü", + "Z": "ñ", + "X": "þ", + "C": "ó", + "V": "í", + "B": "é", + "N": "ô", + "M": "ø", + "<": "â", + ">": "à", + "`": "£", + "~": "³", + "!": "!", + "@": "\"", + "#": "#", + "$": "*", + "%": ":", + "^": ",", + "&": ".", + "*": ";" +} + + +def make_lat2xxx(encoding="cp1251"): + d = {} + for k, v in lat2koi_d.items(): + v = unicode(v, "koi8-r").encode(encoding) + d[k] = v + return d + + +from m_lib.lazy.dict import LazyDictInitFunc +lat2win_d = LazyDictInitFunc(make_lat2xxx, encoding="cp1251") + + +def lat2rus(instr, lat2rus_d = lat2koi_d): + out = [] + for c in instr: + out.append(lat2rus_d.get(c, c)) + return ''.join(out) + + +lat2koi = lat2rus + +def lat2win(instr): + return lat2rus(instr, lat2win_d) + + +if __name__ == "__main__": + Test = "Ghbdtn nt,t^ ghtrhfcysq vbh!" + print "Test:", Test + print "ôÅÓÔ:", lat2koi(Test) + print "ôÅÓÔ:", unicode(lat2win(Test), "cp1251").encode("koi8-r") diff --git a/m_lib/rus/rus2lat.py b/m_lib/rus/rus2lat.py new file mode 100755 index 0000000..dda03da --- /dev/null +++ b/m_lib/rus/rus2lat.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python +# -*- coding: koi8-r -*- + +# +# Rus -> Lat transliteration (koi2lat and win2lat) +# Written by Broytman. Copyright (C) 1997-2002 PhiloSoft Design +# + +koi2lat_d = { + "á": "A", + "â": "B", + "÷": "V", + "ç": "G", + "ä": "D", + "å": "E", + "ö": "Zh", + "ú": "Z", + "é": "I", + "ê": "Y", + "ë": "K", + "ì": "L", + "í": "M", + "î": "N", + "ï": "O", + "ð": "P", + "ò": "R", + "ó": "S", + "ô": "T", + "õ": "U", + "æ": "F", + "è": "H", + "ã": "Ts", + "þ": "Ch", + "û": "Sh", + "ý": "Sh", + "ÿ": "'", + "ø": "'", + "ù": "Y", + "ü": "E", + "à": "Yu", + "ñ": "Ya", + "Á": "a", + "Â": "b", + "×": "v", + "Ç": "g", + "Ä": "d", + "Å": "e", + "Ö": "zh", + "Ú": "z", + "É": "i", + "Ê": "y", + "Ë": "k", + "Ì": "l", + "Í": "m", + "Î": "n", + "Ï": "o", + "Ð": "p", + "Ò": "r", + "Ó": "s", + "Ô": "t", + "Õ": "u", + "Æ": "f", + "È": "h", + "Ã": "ts", + "Þ": "ch", + "Û": "sh", + "Ý": "sh", + "ß": "'", + "Ø": "'", + "Ù": "y", + "Ü": "e", + "À": "yu", + "Ñ": "ya" +} + +def make_xxx2lat(encoding="cp1251"): + d = {} + for k, v in koi2lat_d.items(): + k = unicode(k, "koi8-r").encode(encoding) + d[k] = v + return d + + +from m_lib.lazy.dict import LazyDictInitFunc +win2lat_d = LazyDictInitFunc(make_xxx2lat, encoding="cp1251") + + +def rus2lat(instr, rus2lat_d = koi2lat_d): + out = [] + for c in instr: + out.append(rus2lat_d.get(c, c)) + return ''.join(out) + + +koi2lat = rus2lat + +def win2lat(instr): + return rus2lat(instr, win2lat_d) + + +if __name__ == "__main__": + Test = "ýÅÒÂÁËÏ× éÇÏÒØ çÒÉÇÏÒØÅ×ÉÞ. áâ÷ xyz ÁÂ× øøüàñ ßØÜÀÑ" + print "Test:", Test + print "ôÅÓÔ:", koi2lat(Test) + print "ôÅÓÔ:", win2lat(unicode(Test, "koi8-r").encode("cp1251")) diff --git a/m_lib/tty_menu.py b/m_lib/tty_menu.py new file mode 100755 index 0000000..35ba43d --- /dev/null +++ b/m_lib/tty_menu.py @@ -0,0 +1,56 @@ +#! /usr/bin/env python +"""tty menus + + Written by Broytman, Mar 1998. Copyright (C) 1997 PhiloSoft Design. +""" + + +import string + + +def hmenu(prompt, astring): + """ + Writes prompt and read result + until result[0] is one of allowed characters (from astring), + and returns the character + """ + while 1: + result = raw_input(prompt) + if len(result) > 0: + c = result[0] + if c in astring: + return c + + +def vmenu(item_list, prompt, format = "%d. %s"): + """ + Prints numbered list of items and allow user to select one, + returns selected number. Returns -1, if user enter non-numeric string. + """ + for i in range(len(item_list)): + print format % (i, item_list[i]) + print + + result = raw_input(prompt) + + try: + result = string.atoi(result) + except string.atoi_error: + result = -1 + + return result + + +def test(): + result = hmenu("Select: d)aily, w)eekly, m)onthly, c)ancel: ", "dwmc") + print "Answer is '%s'\n" % result + + os_list = ["DOS", "Windows", "UNIX"] + result = vmenu(os_list, "Select OS: ") + if 0 <= result < len(os_list): + print "Answer is '%s'\n" % os_list[result] + else: + print "Wrong selection" + +if __name__ == "__main__": + test() diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..857eced --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +#! /usr/bin/env python + +from distutils.core import setup + +setup(name = "m_lib", + version = "1.4", + description = "Broytman Library for Python", + long_description = "Broytman Library for Python, Copyright (C) 1996-2012 PhiloSoft Design", + author = "Oleg Broytman", + author_email = "phd@phdru.name", + url = "http://phdru.name/Software/Python/#m_lib", + license = "GPL", + platforms = "All", + packages = ["m_lib", "m_lib.clock", "m_lib.lazy", + "m_lib.net", "m_lib.net.ftp", "m_lib.net.www", "m_lib.rus"] +)