#! /usr/bin/env python3# -*- coding: iso-8859-1 -*-# Originally written by Barry Warsaw <barry@python.org>## Minimally patched to make it even more xgettext compatible# by Peter Funk <pf@artcom-gmbh.de>## 2002年11月22日 Jrgen Hermann <jh@web.de># Added checks that _() only contains string literals, and# command line args are resolved to module lists, i.e. you# can now pass a filename, a module or package name, or a# directory (including globbing chars, important for Win32).# Made docstring fit in 80 chars wide displays using pydoc.## for selftestingtry:import fintl_ = fintl.gettextexcept ImportError:_ = lambda s: s__doc__ = _("""pygettext -- Python equivalent of xgettext(1)Many systems (Solaris, Linux, Gnu) provide extensive tools that ease theinternationalization of C programs. Most of these tools are independent ofthe programming language and can be used from within Python programs.Martin von Loewis' work[1] helps considerably in this regard.There's one problem though; xgettext is the program that scans source codelooking for message strings, but it groks only C (or C++). Pythonintroduces a few wrinkles, such as dual quoting characters, triple quotedstrings, and raw strings. xgettext understands none of this.Enter pygettext, which uses Python's standard tokenize module to scanPython source code, generating .pot files identical to what GNU xgettext[2]generates for C and C++ code. From there, the standard GNU tools can beused.A word about marking Python strings as candidates for translation. GNUxgettext recognizes the following keywords: gettext, dgettext, dcgettext,and gettext_noop. But those can be a lot of text to include all over yourcode. C and C++ have a trick: they use the C preprocessor. Mostinternationalized C source includes a #define for gettext() to _() so thatwhat has to be written in the source is much less. Thus these are bothtranslatable strings:gettext("Translatable String")_("Translatable String")Python of course has no preprocessor so this doesn't work so well. Thus,pygettext searches only for _() by default, but see the -k/--keyword flagbelow for how to augment this.[1] http://www.python.org/workshops/1997-10/proceedings/loewis.html[2] http://www.gnu.org/software/gettext/gettext.htmlNOTE: pygettext attempts to be option and feature compatible with GNUxgettext where ever possible. However some options are still missing or arenot fully implemented. Also, xgettext's use of command line switches withoption arguments is broken, and in these cases, pygettext just definesadditional switches.Usage: pygettext [options] inputfile ...Options:-a--extract-allExtract all strings.-d name--default-domain=nameRename the default output file from messages.pot to name.pot.-E--escapeReplace non-ASCII characters with octal escape sequences.-D--docstringsExtract module, class, method, and function docstrings. These donot need to be wrapped in _() markers, and in fact cannot be forPython to consider them docstrings. (See also the -X option).-h--helpPrint this help message and exit.-k word--keyword=wordKeywords to look for in addition to the default set, which are:%(DEFAULTKEYWORDS)sYou can have multiple -k flags on the command line.-K--no-default-keywordsDisable the default set of keywords (see above). Any keywordsexplicitly added with the -k/--keyword option are still recognized.--no-locationDo not write filename/lineno location comments.-n--add-locationWrite filename/lineno location comments indicating where eachextracted string is found in the source. These lines appear beforeeach msgid. The style of comments is controlled by the -S/--styleoption. This is the default.-o filename--output=filenameRename the default output file from messages.pot to filename. Iffilename is `-' then the output is sent to standard out.-p dir--output-dir=dirOutput files will be placed in directory dir.-S stylename--style stylenameSpecify which style to use for location comments. Two styles aresupported:Solaris # File: filename, line: line-numberGNU #: filename:lineThe style name is case insensitive. GNU style is the default.-v--verbosePrint the names of the files being processed.-V--versionPrint the version of pygettext and exit.-w columns--width=columnsSet width of output to columns.-x filename--exclude-file=filenameSpecify a file that contains a list of strings that are not beextracted from the input files. Each string to be excluded mustappear on a line by itself in the file.-X filename--no-docstrings=filenameSpecify a file that contains a list of files (one per line) thatshould not have their docstrings extracted. This is only useful inconjunction with the -D option above.If `inputfile' is -, standard input is read.""")import osimport importlib.machineryimport importlib.utilimport sysimport globimport timeimport getoptimport tokenimport tokenize__version__ = '1.5'default_keywords = ['_']DEFAULTKEYWORDS = ', '.join(default_keywords)EMPTYSTRING = ''# The normal pot-file header. msgmerge and Emacs's po-mode work better if it's# there.pot_header = _('''\# SOME DESCRIPTIVE TITLE.# Copyright (C) YEAR ORGANIZATION# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.#msgid ""msgstr """Project-Id-Version: PACKAGE VERSION\\n""POT-Creation-Date: %(time)s\\n""PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n""Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n""Language-Team: LANGUAGE <LL@li.org>\\n""MIME-Version: 1.0\\n""Content-Type: text/plain; charset=%(charset)s\\n""Content-Transfer-Encoding: %(encoding)s\\n""Generated-By: pygettext.py %(version)s\\n"''')def usage(code, msg=''):print(__doc__ % globals(), file=sys.stderr)if msg:print(msg, file=sys.stderr)sys.exit(code)def make_escapes(pass_nonascii):global escapes, escapeif pass_nonascii:# Allow non-ascii characters to pass through so that e.g. 'msgid# "Hhe"' would result not result in 'msgid "H366円he"'. Otherwise we# escape any character outside the 32..126 range.mod = 128escape = escape_asciielse:mod = 256escape = escape_nonasciiescapes = [r"\%03o" % i for i in range(mod)]for i in range(32, 127):escapes[i] = chr(i)escapes[ord('\\')] = r'\\'escapes[ord('\t')] = r'\t'escapes[ord('\r')] = r'\r'escapes[ord('\n')] = r'\n'escapes[ord('\"')] = r'\"'def escape_ascii(s, encoding):return ''.join(escapes[ord(c)] if ord(c) < 128 else c for c in s)def escape_nonascii(s, encoding):return ''.join(escapes[b] for b in s.encode(encoding))def is_literal_string(s):return s[0] in '\'"' or (s[0] in 'rRuU' and s[1] in '\'"')def safe_eval(s):# unwrap quotes, safelyreturn eval(s, {'__builtins__':{}}, {})def normalize(s, encoding):# This converts the various Python string types into a format that is# appropriate for .po files, namely much closer to C style.lines = s.split('\n')if len(lines) == 1:s = '"' + escape(s, encoding) + '"'else:if not lines[-1]:del lines[-1]lines[-1] = lines[-1] + '\n'for i in range(len(lines)):lines[i] = escape(lines[i], encoding)lineterm = '\\n"\n"'s = '""\n"' + lineterm.join(lines) + '"'return sdef containsAny(str, set):"""Check whether 'str' contains ANY of the chars in 'set'"""return 1 in [c in str for c in set]def getFilesForName(name):"""Get a list of module files for a filename, a module or package name,or a directory."""if not os.path.exists(name):# check for glob charsif containsAny(name, "*?[]"):files = glob.glob(name)list = []for file in files:list.extend(getFilesForName(file))return list# try to find module or packagetry:spec = importlib.util.find_spec(name)name = spec.originexcept ImportError:name = Noneif not name:return []if os.path.isdir(name):# find all python files in directorylist = []# get extension for python source files_py_ext = importlib.machinery.SOURCE_SUFFIXES[0]for root, dirs, files in os.walk(name):# don't recurse into CVS directoriesif 'CVS' in dirs:dirs.remove('CVS')# add all *.py files to listlist.extend([os.path.join(root, file) for file in filesif os.path.splitext(file)[1] == _py_ext])return listelif os.path.exists(name):# a single filereturn [name]return []class TokenEater:def __init__(self, options):self.__options = optionsself.__messages = {}self.__state = self.__waitingself.__data = []self.__lineno = -1self.__freshmodule = 1self.__curfile = Noneself.__enclosurecount = 0def __call__(self, ttype, tstring, stup, etup, line):# dispatch## import token## print('ttype:', token.tok_name[ttype], 'tstring:', tstring,## file=sys.stderr)self.__state(ttype, tstring, stup[0])def __waiting(self, ttype, tstring, lineno):opts = self.__options# Do docstring extractions, if enabledif opts.docstrings and not opts.nodocstrings.get(self.__curfile):# module docstring?if self.__freshmodule:if ttype == tokenize.STRING and is_literal_string(tstring):self.__addentry(safe_eval(tstring), lineno, isdocstring=1)self.__freshmodule = 0elif ttype not in (tokenize.COMMENT, tokenize.NL):self.__freshmodule = 0return# class or func/method docstring?if ttype == tokenize.NAME and tstring in ('class', 'def'):self.__state = self.__suiteseenreturnif ttype == tokenize.NAME and tstring in opts.keywords:self.__state = self.__keywordseendef __suiteseen(self, ttype, tstring, lineno):# skip over any enclosure pairs until we see the colonif ttype == tokenize.OP:if tstring == ':' and self.__enclosurecount == 0:# we see a colon and we're not in an enclosure: end of defself.__state = self.__suitedocstringelif tstring in '([{':self.__enclosurecount += 1elif tstring in ')]}':self.__enclosurecount -= 1def __suitedocstring(self, ttype, tstring, lineno):# ignore any intervening noiseif ttype == tokenize.STRING and is_literal_string(tstring):self.__addentry(safe_eval(tstring), lineno, isdocstring=1)self.__state = self.__waitingelif ttype not in (tokenize.NEWLINE, tokenize.INDENT,tokenize.COMMENT):# there was no class docstringself.__state = self.__waitingdef __keywordseen(self, ttype, tstring, lineno):if ttype == tokenize.OP and tstring == '(':self.__data = []self.__lineno = linenoself.__state = self.__openseenelse:self.__state = self.__waitingdef __openseen(self, ttype, tstring, lineno):if ttype == tokenize.OP and tstring == ')':# We've seen the last of the translatable strings. Record the# line number of the first line of the strings and update the list# of messages seen. Reset state for the next batch. If there# were no strings inside _(), then just ignore this entry.if self.__data:self.__addentry(EMPTYSTRING.join(self.__data))self.__state = self.__waitingelif ttype == tokenize.STRING and is_literal_string(tstring):self.__data.append(safe_eval(tstring))elif ttype not in [tokenize.COMMENT, token.INDENT, token.DEDENT,token.NEWLINE, tokenize.NL]:# warn if we see anything else than STRING or whitespaceprint(_('*** %(file)s:%(lineno)s: Seen unexpected token "%(token)s"') % {'token': tstring,'file': self.__curfile,'lineno': self.__lineno}, file=sys.stderr)self.__state = self.__waitingdef __addentry(self, msg, lineno=None, isdocstring=0):if lineno is None:lineno = self.__linenoif not msg in self.__options.toexclude:entry = (self.__curfile, lineno)self.__messages.setdefault(msg, {})[entry] = isdocstringdef set_filename(self, filename):self.__curfile = filenameself.__freshmodule = 1def write(self, fp):options = self.__optionstimestamp = time.strftime('%Y-%m-%d %H:%M%z')encoding = fp.encoding if fp.encoding else 'UTF-8'print(pot_header % {'time': timestamp, 'version': __version__,'charset': encoding,'encoding': '8bit'}, file=fp)# Sort the entries. First sort each particular entry's keys, then# sort all the entries by their first item.reverse = {}for k, v in self.__messages.items():keys = sorted(v.keys())reverse.setdefault(tuple(keys), []).append((k, v))rkeys = sorted(reverse.keys())for rkey in rkeys:rentries = reverse[rkey]rentries.sort()for k, v in rentries:# If the entry was gleaned out of a docstring, then add a# comment stating so. This is to aid translators who may wish# to skip translating some unimportant docstrings.isdocstring = any(v.values())# k is the message string, v is a dictionary-set of (filename,# lineno) tuples. We want to sort the entries in v first by# file name and then by line number.v = sorted(v.keys())if not options.writelocations:pass# location comments are different b/w Solaris and GNU:elif options.locationstyle == options.SOLARIS:for filename, lineno in v:d = {'filename': filename, 'lineno': lineno}print(_('# File: %(filename)s, line: %(lineno)d') % d, file=fp)elif options.locationstyle == options.GNU:# fit as many locations on one line, as long as the# resulting line length doesn't exceed 'options.width'locline = '#:'for filename, lineno in v:d = {'filename': filename, 'lineno': lineno}s = _(' %(filename)s:%(lineno)d') % dif len(locline) + len(s) <= options.width:locline = locline + selse:print(locline, file=fp)locline = "#:" + sif len(locline) > 2:print(locline, file=fp)if isdocstring:print('#, docstring', file=fp)print('msgid', normalize(k, encoding), file=fp)print('msgstr ""\n', file=fp)def main():global default_keywordstry:opts, args = getopt.getopt(sys.argv[1:],'ad:DEhk:Kno:p:S:Vvw:x:X:',['extract-all', 'default-domain=', 'escape', 'help','keyword=', 'no-default-keywords','add-location', 'no-location', 'output=', 'output-dir=','style=', 'verbose', 'version', 'width=', 'exclude-file=','docstrings', 'no-docstrings',])except getopt.error as msg:usage(1, msg)# for holding option valuesclass Options:# constantsGNU = 1SOLARIS = 2# defaultsextractall = 0 # FIXME: currently this option has no effect at all.escape = 0keywords = []outpath = ''outfile = 'messages.pot'writelocations = 1locationstyle = GNUverbose = 0width = 78excludefilename = ''docstrings = 0nodocstrings = {}options = Options()locations = {'gnu' : options.GNU,'solaris' : options.SOLARIS,}# parse optionsfor opt, arg in opts:if opt in ('-h', '--help'):usage(0)elif opt in ('-a', '--extract-all'):options.extractall = 1elif opt in ('-d', '--default-domain'):options.outfile = arg + '.pot'elif opt in ('-E', '--escape'):options.escape = 1elif opt in ('-D', '--docstrings'):options.docstrings = 1elif opt in ('-k', '--keyword'):options.keywords.append(arg)elif opt in ('-K', '--no-default-keywords'):default_keywords = []elif opt in ('-n', '--add-location'):options.writelocations = 1elif opt in ('--no-location',):options.writelocations = 0elif opt in ('-S', '--style'):options.locationstyle = locations.get(arg.lower())if options.locationstyle is None:usage(1, _('Invalid value for --style: %s') % arg)elif opt in ('-o', '--output'):options.outfile = argelif opt in ('-p', '--output-dir'):options.outpath = argelif opt in ('-v', '--verbose'):options.verbose = 1elif opt in ('-V', '--version'):print(_('pygettext.py (xgettext for Python) %s') % __version__)sys.exit(0)elif opt in ('-w', '--width'):try:options.width = int(arg)except ValueError:usage(1, _('--width argument must be an integer: %s') % arg)elif opt in ('-x', '--exclude-file'):options.excludefilename = argelif opt in ('-X', '--no-docstrings'):fp = open(arg)try:while 1:line = fp.readline()if not line:breakoptions.nodocstrings[line[:-1]] = 1finally:fp.close()# calculate escapesmake_escapes(not options.escape)# calculate all keywordsoptions.keywords.extend(default_keywords)# initialize list of strings to excludeif options.excludefilename:try:with open(options.excludefilename) as fp:options.toexclude = fp.readlines()except IOError:print(_("Can't read --exclude-file: %s") % options.excludefilename, file=sys.stderr)sys.exit(1)else:options.toexclude = []# resolve args to module listsexpanded = []for arg in args:if arg == '-':expanded.append(arg)else:expanded.extend(getFilesForName(arg))args = expanded# slurp through all the fileseater = TokenEater(options)for filename in args:if filename == '-':if options.verbose:print(_('Reading standard input'))fp = sys.stdin.bufferclosep = 0else:if options.verbose:print(_('Working on %s') % filename)fp = open(filename, 'rb')closep = 1try:eater.set_filename(filename)try:tokens = tokenize.tokenize(fp.readline)for _token in tokens:eater(*_token)except tokenize.TokenError as e:print('%s: %s, line %d, column %d' % (e.args[0], filename, e.args[1][0], e.args[1][1]),file=sys.stderr)finally:if closep:fp.close()# write the outputif options.outfile == '-':fp = sys.stdoutclosep = 0else:if options.outpath:options.outfile = os.path.join(options.outpath, options.outfile)fp = open(options.outfile, 'w')closep = 1try:eater.write(fp)finally:if closep:fp.close()if __name__ == '__main__':main()# some more test strings# this one creates a warning_('*** Seen unexpected token "%(token)s"') % {'token': 'test'}_('more' 'than' 'one' 'string')
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。