From 77338050a0c3030dd678ade0fe8a6514f45431f4 Mon Sep 17 00:00:00 2001 From: Andrea Gavana Date: Thu, 5 Apr 2012 18:28:47 +0000 Subject: [PATCH] Phoenix: - First stub at integrating `wx.lib` (116 warnings); - Added `internationalization.rst` file from Werner (with a cool sample app as well); - Added new samples for AffineMatrix2D. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@71099 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- build.py | 21 +- docs/sphinx/_downloads/i18nwxapp/app_base.py | 107 +++ .../_downloads/i18nwxapp/i18ntools/geni18n.py | 62 ++ .../i18nwxapp/i18ntools/i18nwxapp.pot | 73 ++ .../sphinx/_downloads/i18nwxapp/i18nwxapp.zip | Bin 0 -> 8916 bytes .../locale/de/LC_MESSAGES/i18nwxapp.mo | Bin 0 -> 1267 bytes .../locale/de/LC_MESSAGES/i18nwxapp.po | 74 ++ .../locale/fr/LC_MESSAGES/i18nwxapp.mo | Bin 0 -> 1284 bytes .../locale/fr/LC_MESSAGES/i18nwxapp.po | 74 ++ docs/sphinx/_downloads/i18nwxapp/sampleapp.py | 111 +++ .../images/sphinxdocs/function_summary.png | Bin 0 -> 1545 bytes .../images/sphinxdocs/module_summary.png | Bin 0 -> 1674 bytes .../images/sphinxdocs/package_summary.png | Bin 0 -> 2322 bytes .../rest_substitutions/overviews/index.rst | 38 +- .../overviews/internationalization.rst | 94 ++- .../converted/AffineMatrix2D.Concat.1.py | 4 + .../converted/AffineMatrix2D.Invert.1.py | 4 + .../converted/AffineMatrix2DBase.Concat.1.py | 4 + .../converted/AffineMatrix2DBase.Invert.1.py | 4 + etgtools/sphinx_generator.py | 34 +- sphinxtools/constants.py | 62 +- sphinxtools/inheritance.py | 432 +++++----- sphinxtools/librarydescription.py | 735 ++++++++++++++++++ sphinxtools/modulehunter.py | 632 +++++++++++++++ sphinxtools/postprocess.py | 42 +- sphinxtools/templates.py | 66 +- sphinxtools/utilities.py | 26 +- 27 files changed, 2477 insertions(+), 222 deletions(-) create mode 100644 docs/sphinx/_downloads/i18nwxapp/app_base.py create mode 100644 docs/sphinx/_downloads/i18nwxapp/i18ntools/geni18n.py create mode 100644 docs/sphinx/_downloads/i18nwxapp/i18ntools/i18nwxapp.pot create mode 100644 docs/sphinx/_downloads/i18nwxapp/i18nwxapp.zip create mode 100644 docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.mo create mode 100644 docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.po create mode 100644 docs/sphinx/_downloads/i18nwxapp/locale/fr/LC_MESSAGES/i18nwxapp.mo create mode 100644 docs/sphinx/_downloads/i18nwxapp/locale/fr/LC_MESSAGES/i18nwxapp.po create mode 100644 docs/sphinx/_downloads/i18nwxapp/sampleapp.py create mode 100644 docs/sphinx/_static/images/sphinxdocs/function_summary.png create mode 100644 docs/sphinx/_static/images/sphinxdocs/module_summary.png create mode 100644 docs/sphinx/_static/images/sphinxdocs/package_summary.png create mode 100644 docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Concat.1.py create mode 100644 docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Invert.1.py create mode 100644 docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Concat.1.py create mode 100644 docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Invert.1.py create mode 100644 sphinxtools/librarydescription.py create mode 100644 sphinxtools/modulehunter.py diff --git a/build.py b/build.py index 6253f6fa..5e8ccd46 100755 --- a/build.py +++ b/build.py @@ -68,6 +68,8 @@ Usage: ./build.py [command(s)] [options] SIP files sphinx Run the documentation building process using Sphinx (this needs to be done after dox and etg) + wxlib Run the documentation building process using Sphinx for + wx.lib (and possibly other pure-Python modules/packages) sip Run sip test Run the unit test suite test_* Run just one test module @@ -123,7 +125,7 @@ def main(args): elif cmd in ['dox', 'doxhtml', 'etg', 'sip', 'touch', 'test', 'build_wx', 'build_py', 'setup_py', 'waf_py', 'build', 'bdist', 'clean', 'clean_wx', 'clean_py', 'cleanall', 'clean_sphinx', - 'sphinx']: + 'sphinx', 'wxlib']: function = globals()[cmd] function(options, args) else: @@ -592,6 +594,23 @@ def sphinx(options, args): msg('Postprocesing sphinx output...') PostProcess(htmlDir) + +def wxlib(options, args): + from sphinxtools.modulehunter import ModuleHunter + + cmdTimer = CommandTimer('wxlib') + pwd = pushDir(phoenixDir()) + + libDir = os.path.join(phoenixDir(), 'wx', 'lib') + + if not os.path.isdir(libDir): + raise Exception('Missing wx.lib folder in the distribution') + + init_name = os.path.join(libDir, '__init__.py') + import_name = 'lib' + version = version3 + + ModuleHunter(init_name, import_name, version) def sip(options, args): diff --git a/docs/sphinx/_downloads/i18nwxapp/app_base.py b/docs/sphinx/_downloads/i18nwxapp/app_base.py new file mode 100644 index 00000000..d68fb740 --- /dev/null +++ b/docs/sphinx/_downloads/i18nwxapp/app_base.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*-# +# +# Author: Werner F. Bruhin +# Purpose: how to I18N enable an application +# +# Inspired by the I18N wxPython demo and the Internationalization page on +# the wxPython wiki. +# + +import sys +import os + +import wx + +# Install a custom displayhook to keep Python from setting the global +# _ (underscore) to the value of the last evaluated expression. If +# we don't do this, our mapping of _ to gettext can get overwritten. +# This is useful/needed in interactive debugging with PyShell. + +def _displayHook(obj): + if obj is not None: + print repr(obj) + +# add translation macro to builtin similar to what gettext does +import __builtin__ +__builtin__.__dict__['_'] = wx.GetTranslation + +from wx.lib.mixins.inspection import InspectionMixin + +class BaseApp(wx.App, InspectionMixin): + def OnInit(self): + self.Init() # InspectionMixin + # work around for Python stealing "_" + sys.displayhook = _displayHook + + self.appName = "I18N sample application" + # define the translation domain, the name has to match your .mo files + self.langDomain = "I18Nwxapp" + # languages you want to support + self.supLang = {u"en": wx.LANGUAGE_ENGLISH, + u"fr": wx.LANGUAGE_FRENCH, + u"de": wx.LANGUAGE_GERMAN, + } + + self.doConfig() + + self.locale = None + wx.Locale.AddCatalogLookupPathPrefix('locale') + self.updateLanguage(self.appConfig.Read(u"Language")) + + return True + + def doConfig(self): + """Setup an application configuration file""" + # configuration folder + sp = wx.StandardPaths.Get() + self.configLoc = sp.GetUserConfigDir() + self.configLoc = os.path.join(self.configLoc, self.appName) + # win: C:\Users\userid\AppData\Roaming\appName + # nix: \home\userid\appName + + if not os.path.exists(self.configLoc): + os.mkdir(self.configLoc) + + # AppConfig stuff is here + self.appConfig = wx.FileConfig(appName=self.appName, + vendorName=u'who you wish', + localFilename=os.path.join( + self.configLoc, "AppConfig")) + + if not self.appConfig.HasEntry(u'Language'): + # on first run we default to German + self.appConfig.Write(key=u'Language', value=u'de') + + self.appConfig.Flush() + + def updateLanguage(self, lang): + """ + Update the language to the requested one. + + Make *sure* any existing locale is deleted before the new + one is created. The old C++ object needs to be deleted + before the new one is created, and if we just assign a new + instance to the old Python variable, the old C++ locale will + not be destroyed soon enough, likely causing a crash. + + :param string `lang`: one of the supported language codes + + """ + # if an unsupported language is requested default to English + if lang in self.supLang: + selLang = self.supLang[lang] + else: + selLang = wx.LANGUAGE_ENGLISH + + if self.locale: + assert sys.getrefcount(self.locale) <= 2 + del self.locale + + # create a locale object for this language + self.locale = wx.Locale(selLang) + if self.locale.IsOk(): + self.locale.AddCatalog(self.langDomain) + else: + self.locale = None + diff --git a/docs/sphinx/_downloads/i18nwxapp/i18ntools/geni18n.py b/docs/sphinx/_downloads/i18nwxapp/i18ntools/geni18n.py new file mode 100644 index 00000000..650184d3 --- /dev/null +++ b/docs/sphinx/_downloads/i18nwxapp/i18ntools/geni18n.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*-# +#!/usr/bin/env python +""" +This will generate the .pot and .mo files for the application domain and languages defined below. + +The .po and .mo files are placed as per convention in + +"appfolder/locale/lang/LC_MESSAGES" + +The .pot file is placed in the current folder. + +This script or something similar should be added to your build process. + +The actual translation work is normally done using a tool like poEdit or +similar, it allows you to generate a particular language catalog from the .pot +file or to use the .pot to merge new translations into an existing language +catalog. + +""" + +domainName = 'i18nwxapp' +# define the languages you are supporting and need translation, so if texts +# in your source are English then you don't need 'en' here +supLang = ['fr', 'de'] + +import os +import sys +import subprocess + +appFolder, loc = os.path.split(os.getcwd()) + +# setup some stuff to get at Python I18N tools/utilities + +pyExe = sys.executable +pyFolder = os.path.split(pyExe)[0] +pyToolsFolder = os.path.join(pyFolder, 'Tools') +pyI18nFolder = os.path.join(pyToolsFolder, 'i18n') +pyGettext = os.path.join(pyI18nFolder, 'pygettext.py') +pyMsgfmt = os.path.join(pyI18nFolder, 'msgfmt.py') +outFolder = os.getcwd() + +# build command for pygettext +gtOptions = '-a -d %s -o %s.pot -p %s %s' +tCmd = pyExe + ' ' + pyGettext + ' ' + (gtOptions % (domainName, + domainName, + outFolder, + appFolder)) +print "Generating the .pot file" +print "cmd: %s" % tCmd +rCode = subprocess.call(tCmd) +print "return code: %s\n\n" % rCode + +for tLang in supLang: + # build command for msgfmt + langDir = os.path.join(appFolder, ('locale\%s\LC_MESSAGES' % tLang)) + poFile = os.path.join(langDir, domainName + '.po') + tCmd = pyExe + ' ' + pyMsgfmt + ' ' + poFile + + print "Generating the .mo file" + print "cmd: %s" % tCmd + rCode = subprocess.call(tCmd) + print "return code: %s\n\n" % rCode diff --git a/docs/sphinx/_downloads/i18nwxapp/i18ntools/i18nwxapp.pot b/docs/sphinx/_downloads/i18nwxapp/i18ntools/i18nwxapp.pot new file mode 100644 index 00000000..14cb9a8c --- /dev/null +++ b/docs/sphinx/_downloads/i18nwxapp/i18ntools/i18nwxapp.pot @@ -0,0 +1,73 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2012-04-05 12:12+Paris, Madrid (heure d’été)\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:17 +msgid "The I18N sample application" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:28 +msgid "Close" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:29 +msgid "Close the application" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:31 +msgid "&File" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:36 +msgid "Edit something" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:37 +msgid "Edit an entry of something" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:40 +msgid "&Edit" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:45 +msgid "&About" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:46 +msgid "About the program" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:48 +msgid "&Help" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:58 +msgid "A nice label for the TextCtrl" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:62 +msgid "a search control" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:68 +msgid "Open a file dialog" +msgstr "" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:80 +msgid "Choose a file" +msgstr "" + diff --git a/docs/sphinx/_downloads/i18nwxapp/i18nwxapp.zip b/docs/sphinx/_downloads/i18nwxapp/i18nwxapp.zip new file mode 100644 index 0000000000000000000000000000000000000000..3522adc7d00d0808c05914961b59e8bcb278cc4f GIT binary patch literal 8916 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

fC2fffRzoGfF} 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 R14DUvWWS^VDe2E|{|^-$c_07) literal 0 HcmV?d00001 diff --git a/docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.mo b/docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.mo new file mode 100644 index 0000000000000000000000000000000000000000..724b961d52e34dffc75614dd5cc047cd40f8a4fa GIT binary patch literal 1267 zcmZvbPmdcl7{(2h7DAz=2toNHy$2LgwG)ypE3Jk)0dP zvuPwwd;|nSNE|qDDMt=HaX_T%r>M$_55Ng-@OYB$ZlyZ1e`9;*d0xMM^We;}?*!T@ z^f%DIMSlhT_9UeC3u&vx(AgWDg=8mBuCwznPnRFhZVl&Uux6#w8eSPC zeaB6F$t4*$rL}HvE$P0Sbl0i3miE@p52aK70`1D&shr*}xpqwXAKxr)hsEu4kVvlb zzKSM~SGah-+v|2)pGqH+k(0)28LUg`b9ROuZFgwP)yM+9KWkP#Lc(XtaK>6oH@5a> zC$rNJAz&u8oNzK?IZdgxyR$nuJYKKUZS`?{BsUq&D;y-?&fclCROUU(O6fd@53Y^2 zla0goVl)N2WMDFz;~rAl7^~2_%m@t0$dj#kO4WSAA&PaXIrTd0tuK}e3x zO*m7r_|2gVj#ObI(=2V88JoP0C4`hwBSy0Jv&GM)hWuVuYQ^5;@e&jcUXLyhH{zH0 z5g44D(D)!a+DEBym`N`NaXhls_?e~QvP5ZdrzoJ)7{yXewpjT}v6hbpxmTl9KUtP4 H{xbgqHvCYU literal 0 HcmV?d00001 diff --git a/docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.po b/docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.po new file mode 100644 index 00000000..d2ba29f2 --- /dev/null +++ b/docs/sphinx/_downloads/i18nwxapp/locale/de/LC_MESSAGES/i18nwxapp.po @@ -0,0 +1,74 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: I18N wxPyhton sample\n" +"POT-Creation-Date: 2012-04-05 12:12+Paris, Madrid (heure d’été)\n" +"PO-Revision-Date: 2012-04-05 12:13+0100\n" +"Last-Translator: Werner F. Bruhin \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Poedit-Language: English\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:17 +msgid "The I18N sample application" +msgstr "Die I18N-Beispielanwendung" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:28 +msgid "Close" +msgstr "Schliessen" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:29 +msgid "Close the application" +msgstr "Die Anwendung schliessen" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:31 +msgid "&File" +msgstr "&Datei" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:36 +msgid "Edit something" +msgstr "Etwas bearbeiten" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:37 +msgid "Edit an entry of something" +msgstr "Einenen Eintrag bearbeiten von etwas" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:40 +msgid "&Edit" +msgstr "&Bearbeiten" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:45 +msgid "&About" +msgstr "&Über" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:46 +msgid "About the program" +msgstr "Über die Anwendung" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:48 +msgid "&Help" +msgstr "&Hilfe" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:58 +msgid "A nice label for the TextCtrl" +msgstr "Ein schönes Etikett für die TextCtrl" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:62 +msgid "a search control" +msgstr "Eine Such Kontrolle" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:68 +msgid "Open a file dialog" +msgstr "Einen Dateidialog öffnen" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:80 +msgid "Choose a file" +msgstr "Eine Datei auswählen" + diff --git a/docs/sphinx/_downloads/i18nwxapp/locale/fr/LC_MESSAGES/i18nwxapp.mo b/docs/sphinx/_downloads/i18nwxapp/locale/fr/LC_MESSAGES/i18nwxapp.mo new file mode 100644 index 0000000000000000000000000000000000000000..fec285ae3f3af7b091c05e295993577d55f1ac96 GIT binary patch literal 1284 zcmZvaNoy2A7=}yZI_~0v8+;cNMSGkH212`F95Wg)X4G-PTc!J(>54sFZFO~$0Z;w} zak+ZU#e=^<40!S0}~UQ1#UMZS{UXxaGA#+lPJ<{T2FA^xc~j4IE_BzNeHW8!2%~7*F;>bsLNDs+@?3ADQ}a5vVstuG7@I0) zF?x&RR7_E#0;X8bb4(p=$VN=D4O1gui;c_nJq_jw(lCkcJHlmoRPu`(S_R<*sHW-$a zu{1$Mfre=vi}QT&-TKRc_17 zdTk?N?WQ+5yyv3nu<@poK!^AT zpC;#(t=wb;hGK-?^}bJOzsuGctEPRbS0^fuy_RPDrMDK5Pff!~5Dic6>)Zs~7?k%)eL+-ArW6hyBuvmA{umWj&vNISQWzv`4TZRFDyLG4 zg%7kfLtW78$+o+wjDDZ|0Wv;H^cbVWfMn~nGpx1BxyFHgLnt{84 kN^u`g*IeHo<7F, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: I18N wxPyhton sample\n" +"POT-Creation-Date: 2012-04-05 12:12+Paris, Madrid (heure d’été)\n" +"PO-Revision-Date: 2012-04-05 12:14+0100\n" +"Last-Translator: Werner F. Bruhin \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: pygettext.py 1.5\n" +"X-Poedit-Language: French\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:17 +msgid "The I18N sample application" +msgstr "L'exemple d'application I18N" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:28 +msgid "Close" +msgstr "Terminer" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:29 +msgid "Close the application" +msgstr "Terminer l'application" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:31 +msgid "&File" +msgstr "&Fichier" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:36 +msgid "Edit something" +msgstr "Modifier quelque chose" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:37 +msgid "Edit an entry of something" +msgstr "Modifier une entrée de quelque chose" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:40 +msgid "&Edit" +msgstr "&Modifier" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:45 +msgid "&About" +msgstr "&Au sujet de" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:46 +msgid "About the program" +msgstr "Au sujet du programm" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:48 +msgid "&Help" +msgstr "&Aide" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:58 +msgid "A nice label for the TextCtrl" +msgstr "Une belle étiquette pour le TextCtrl" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:62 +msgid "a search control" +msgstr "Une 'control' de recherche" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:68 +msgid "Open a file dialog" +msgstr "Ouvrir un dialogue de fichier" + +#: h:\devProjectsT\PhoenixI18N\i18nwxapp\sampleapp.py:80 +msgid "Choose a file" +msgstr "Choisir un fichier" + diff --git a/docs/sphinx/_downloads/i18nwxapp/sampleapp.py b/docs/sphinx/_downloads/i18nwxapp/sampleapp.py new file mode 100644 index 00000000..970906d3 --- /dev/null +++ b/docs/sphinx/_downloads/i18nwxapp/sampleapp.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*-# +#!/usr/bin/env python + +"""The sample I18N application""" + +import os + +import wx +import wx.lib.sized_controls as sc + +class AppI18N(sc.SizedFrame): + def __init__(self, parent, **kwds): + """ + A sample application to demonstrate how to enable I18N support + """ + super(AppI18N, self).__init__(parent, **kwds) + self.SetTitle(_(u"The I18N sample application")) + + self.createMenu() + self.createOtherCtrls() + + def createMenu(self): + menubar = wx.MenuBar() + + # file menu + fileMenu = wx.Menu() + closeMenuItem = fileMenu.Append(wx.NewId(), + _(u"Close"), + _(u"Close the application")) + self.Bind(wx.EVT_MENU, self.onClose, closeMenuItem) + menubar.Append(fileMenu, _(u"&File")) + + # edit menu + manageMenu = wx.Menu() + manageSomethingMenuItem = manageMenu.Append(wx.NewId(), + _(u"Edit something"), + _(u"Edit an entry of something")) + self.Bind(wx.EVT_MENU, self.doEditSomething, manageSomethingMenuItem) + + menubar.Append(manageMenu, _(u"&Edit")) + + # help menu + helpMenu = wx.Menu() + aboutMenuItem = helpMenu.Append(wx.NewId(), + _(u"&About"), + _(u"About the program")) + self.Bind(wx.EVT_MENU, self.doAboutBox, aboutMenuItem) + menubar.Append(helpMenu, _(u"&Help")) + + self.SetMenuBar(menubar) + + def createOtherCtrls(self): + pane = self.GetContentsPane() + + cPane = sc.SizedPanel(pane) + cPane.SetSizerType("grid", options={"cols": 2}) + st = wx.StaticText(cPane, wx.ID_ANY, + _(u"A nice label for the TextCtrl")) + st.SetSizerProps(valign='center') + tc = wx.TextCtrl(cPane, wx.ID_ANY) + + searchSt = wx.StaticText(cPane, wx.ID_ANY, + _(u"a search control")) + searchSt.SetSizerProps(valign='center') + searchC = wx.SearchCtrl(cPane, wx.ID_ANY) + + sline = wx.StaticLine(pane, wx.ID_ANY) + sline.SetSizerProps(expand=True) + bPane = sc.SizedPanel(pane) + fB = wx.Button(bPane, wx.ID_ANY, _(u"Open a file dialog")) + fB.SetSizerProps(align="center") + fB.Bind(wx.EVT_BUTTON, self.onFbButton) + + def onFbButton(self, event): + wildcard = "Python source (*.py)|*.py|" \ + "Compiled Python (*.pyc)|*.pyc|" \ + "SPAM files (*.spam)|*.spam|" \ + "Egg file (*.egg)|*.egg|" \ + "All files (*.*)|*.*" + + with wx.FileDialog( + self, message=_(u"Choose a file"), + defaultDir=os.getcwd(), + defaultFile="", + wildcard=wildcard, + style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR + ) as dlg: + + # Show the dialog and retrieve the user response. If it is the + # OK response, + # process the data. + if dlg.ShowModal() == wx.ID_OK: + # This returns a Python list of files that were selected. + paths = dlg.GetPaths() + + def onClose(self, event): + event.Skip() + + def doEditSomething(self, event): + event.Skip() + + def doAboutBox(self, event): + event.Skip() + +if __name__ == '__main__': + import app_base as ab + app = ab.BaseApp(redirect=False) + + frame = AppI18N(None) + frame.Show() + app.MainLoop() diff --git a/docs/sphinx/_static/images/sphinxdocs/function_summary.png b/docs/sphinx/_static/images/sphinxdocs/function_summary.png new file mode 100644 index 0000000000000000000000000000000000000000..8db72168e2881e5679783781abb64ea234c55ca8 GIT binary patch literal 1545 zcmV+k2KM=hP)YXfdBvnqe(G}qSF#z#j-Pr2Rht>JL^lRzNw z-u(Rhh_36&VuTcpbLY+-h{a;GapT7MP$<+4pz_F(BR@AZG|;+r>jnXo;u!<{SDZb2 zwyv|Yld7w$$?y072;jN;`uflI?%hk_aF|L;N(ew0fNwP}&wmkM7)E?-Yz&&FfpdNl z0K0qlZtBjRJ9zZy5rhy|9d7rU3+U+RIH0O(w7a_-mSur+ei?w3OeW*OU{K8@UO9t*L;Hjt(W0$uv$(On`F^ zm&0tIxzs(X|VMNxJGKr)$x+wF$W=Sv_PWiHi}C#awnTr~r+SnOBM zIYOb3>%oHuHB(bln4Fx<{_OGW-n@Bp^R{iq+u_WDe0{}dYjg7Ze zRSiu}P9l@Z%$TOR1Au|0VzU8cknMniuv!g}L$2@Hvu9mxZEZ-BByi3FVA@$w2e9Bg zItr-#d=%0J8~~3*BGrsB=(-NhIf&@0Bfva#=+LK;NTkW{_q#4!xUloel`9R?)6>7l z0V2BTL@Q38KHb{V(sDTx ziS*6P%)qwog8=@Vn3(uwcz8H^?bC6m z8uxm=I~ZfzH`i?4^zdQ&&2&1g1NgzAF|fj(i!q{_WHJfH7yw*#i1PQ7BuV-solZjt zf!FKbs7j^o6S9_^D^de1Jfa{0t*x!iM1*_y?&Y>%r2_&0)~{bbW*7znfl@GLYZ+b7 zIiro`HeH+mBC4}23v+XGxfXm}fcf8r!{PEkAOM#O7#|xQLIH+UkO0nkov!P!Z5u)e z2q7-76cCHW6iw6iRaI4KLJ0hID^XF1fP#w9?RIZTr_-=33jh*~u}J`|tE=n7R4TP| ze0==-{{H@b*RNmy9Ke3J+r7)@^ECiKO-)VIw(YlPXJ?N+O;AAsTrStnTeoh(wrvnm zH=Of-WMm|kNF*>gIQWifnuD6A4OCQAZ18wIyEx|v27~d1g@sbvw%-NdMb@H=8&pr6 zI8k@>=urxX!z9Zxc|4vS0M-NWR904gS6W(1vMgVgBkN0vP`lp|K#y_ zUIGvTAmb@#&$0~k^z@t>8X9`Fv$ON-+1c410K)6_Hm6c4EG{mBh`JeLcbrJX+}zwj z0EZoN24E3@sj6xVfHEg)6H8t2MD&jZw70h(&@}Dv@bEAU!>9w$4`7jq_V@Po!m=y~ zA-;47bMqbn(2zX=1@fE`*}ivU`NZ@D9t*g1>CzTm*KzaaO<0zd01!BS{P^~fk&&pX zsvx4T7-N4sQRcjy@}kR!KoLhW0HCU>>V;U)2CxCyiFZxzlR2y;^B_C@$j&=q vDb2aPU(@^EN&zlp3n%|plV3z@e%bs7eTeBOP=+`x00000NkvXXu0mjfhTr4p literal 0 HcmV?d00001 diff --git a/docs/sphinx/_static/images/sphinxdocs/module_summary.png b/docs/sphinx/_static/images/sphinxdocs/module_summary.png new file mode 100644 index 0000000000000000000000000000000000000000..f108497f68e21773f1bd564b0142af2f6a999463 GIT binary patch literal 1674 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. :hidden: :glob: - * + MigrationGuide + TODO + DocstringsGuidelines + functions + 1classindex + app_overview + bitmap_overview + bookctrl_overview + common_dialogs_overview + config_overview + datetime_overview + dc_overview + dialog_overview + dnd_overview + events_overview + filesystem_overview + font_encodings + font_overview + internationalization + listctrl_overview + log_classes_overview + printing_framework_overview + refcount_overview + scrolling_overview + sizers_overview + splitterwindow_overview + standard_event_identifiers + stock_items + toolbar_overview + treectrl_overview + validator_overview + window_deletion_overview + window_ids_overview + window_sizing_overview + window_styles_overview + dataview.1classindex + lib Indices and tables diff --git a/docs/sphinx/rest_substitutions/overviews/internationalization.rst b/docs/sphinx/rest_substitutions/overviews/internationalization.rst index 0e8d9f76..23f7c51a 100644 --- a/docs/sphinx/rest_substitutions/overviews/internationalization.rst +++ b/docs/sphinx/rest_substitutions/overviews/internationalization.rst @@ -2,13 +2,105 @@ .. _internationalization: +.. _I18N: +.. _localization: +.. _L10N: + ================================================== |phoenix_title| **Internationalization Overview** ================================================== -.. todo:: Write this section. +"Internationalization" (often referred to as I18N) is the process to change an +application so that all user visible texts are translated to the user selected +language and that things like dates, money amounts and numbers in general are +shown in a format the user is familiar with/or used to. + +The easiest way to show what is needed is by using a little code sample. +Text translation +================ + +Prepare the source code +----------------------- + +Text translation in Python is done using gettext [1]_ , to ensure that all wxPython +labels are also translated we will use :class:`Locale` and :func:`GetTranslation` . + +How to prepare your source code to enable translation of texts:: + + aString = _(u"This is a string which will be translated") +As you can see it is very simple, you just enclose the text with the translation function "_()", +obviously there is a bit more to it, see below. + +Enabling I18N for a whole application you would do some setup in the application file along the following lines: + +.. literalinclude:: _downloads/i18nwxapp/app_base.py + :lines: 25-27 + + +Here we setup the "_" translation function and making it available application by adding it to builtin. + +The code required to change to a different language is as follows: + +.. literalinclude:: _downloads/i18nwxapp/app_base.py + :pyobject: BaseApp.updateLanguage + + +Do the actual translation work +------------------------------ + +You need to extract all the text strings marked by the "_" function, a little script `geni18n.py` is in the +:download:`downloadable zip file <_downloads/i18nwxapp/i18nwxapp.zip>`, it will extract all the strings +and generate a ``.pot`` file. The `geni18n.py` script will also generate the ``.mo`` files for defined languages. + +The ``.pot`` file is then provided to the translators and they use it to generate a ``.po`` file for +the language they translate too or they can also use the ``.pot`` file to merge new/changed text strings +to an existing ``.po`` file. + +To do the actual translation we recomment `poEdit` [2]_ , it allows you to create or update a translation +catalog (``.po`` file) from the ``.pot`` file. + + +Sample application +------------------ + +In the :download:`downloadable zip file <_downloads/i18nwxapp/i18nwxapp.zip>` we included a small +sample application showing the above in action. + +- `app_base.py` contains the initialization code +- `sampleapp.py` is the main frame/application, just run this to see things in action +- `i18ntools/geni18n.py` is the script to generate the ``.pot`` file and it also generates the ``.mo`` files. + +.. note:: + The application has a button which displays a file dialog, as wxPython uses a native widget for this the + text are shown in the operating system language and not the language which is selected in `app_base.py`. + + +Localization overview +===================== + +"Localization", often referred to as "L10N", is the process to adapt the display of dates and numbers to local custom. + +E.g. "4/5/2012" would for an American mean April 5. 2012, but for most Europeans it would be 4. May 2012. + + +Localize dates +============== + +.. todo:: to be written + + +Localize numbers +================ + +.. todo:: to be written + + +.. rubric:: Footnotes + +.. [1] gettext - http://docs.python.org/library/gettext.html +.. [2] poEdit - http://www.poedit.net/ \ No newline at end of file diff --git a/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Concat.1.py b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Concat.1.py new file mode 100644 index 00000000..edb7db4b --- /dev/null +++ b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Concat.1.py @@ -0,0 +1,4 @@ + + # | t.self.11 t.self.12 0 | | self.11 self.12 0 | + # matrix' = | t.self.21 t.self.22 0 | x | self.21 self.22 0 | + # | t.self.tx t.self.ty 1 | | self.tx self.ty 1 | diff --git a/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Invert.1.py b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Invert.1.py new file mode 100644 index 00000000..d45fc476 --- /dev/null +++ b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2D.Invert.1.py @@ -0,0 +1,4 @@ + + # | self.11 self.12 0 | + # Invert | self.21 self.22 0 | + # | self.tx self.ty 1 | diff --git a/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Concat.1.py b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Concat.1.py new file mode 100644 index 00000000..edb7db4b --- /dev/null +++ b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Concat.1.py @@ -0,0 +1,4 @@ + + # | t.self.11 t.self.12 0 | | self.11 self.12 0 | + # matrix' = | t.self.21 t.self.22 0 | x | self.21 self.22 0 | + # | t.self.tx t.self.ty 1 | | self.tx self.ty 1 | diff --git a/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Invert.1.py b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Invert.1.py new file mode 100644 index 00000000..d45fc476 --- /dev/null +++ b/docs/sphinx/rest_substitutions/snippets/python/converted/AffineMatrix2DBase.Invert.1.py @@ -0,0 +1,4 @@ + + # | self.11 self.12 0 | + # Invert | self.21 self.22 0 | + # | self.tx self.ty 1 | diff --git a/etgtools/sphinx_generator.py b/etgtools/sphinx_generator.py index 393a198d..79f6a591 100644 --- a/etgtools/sphinx_generator.py +++ b/etgtools/sphinx_generator.py @@ -2218,6 +2218,11 @@ class XMLDocString(object): klass = self.xml_item name = self.class_name dummy, fullname = Wx2Sphinx(name) + + if '.' in fullname: + parts = fullname.split('.') + module = '.'.join(parts[0:-1]) + stream.write('\n\n.. currentmodule:: %s\n\n'%module) stream.write(templates.TEMPLATE_DESCRIPTION % (fullname, name)) @@ -2250,11 +2255,11 @@ class XMLDocString(object): stream.write(snippets) if klass.method_list: - summary = MakeSummary(name, klass.method_list, templates.TEMPLATE_METHOD_SUMMARY, 'meth') + summary = MakeSummary(klass.method_list, templates.TEMPLATE_METHOD_SUMMARY, 'meth') stream.write(summary) if klass.property_list: - summary = MakeSummary(name, klass.property_list, templates.TEMPLATE_PROPERTY_SUMMARY, 'attr') + summary = MakeSummary(klass.property_list, templates.TEMPLATE_PROPERTY_SUMMARY, 'attr') stream.write(summary) stream.write(templates.TEMPLATE_API) @@ -2548,6 +2553,9 @@ class XMLDocString(object): stream = StringIO() self.output_file = self.current_module + "%s.enumeration.txt"%enum_name + + if self.current_module.strip(): + stream.write('\n\n.. currentmodule:: %s\n\n'%self.current_module[0:-1]) stream.write(templates.TEMPLATE_DESCRIPTION % (fullname, enum_name)) stream.write('\n\nThe `%s` enumeration provides the following values:\n\n'%enum_name) @@ -2856,14 +2864,14 @@ class SphinxGenerator(generators.DocsGeneratorBase): klass.module = self.current_module self.current_class = klass - name = klass.name + class_name = klass.name self.current_class.method_list = [] self.current_class.property_list = [] class_items = [i for i in klass if not i.ignored] class_items = sorted(class_items, key=operator.attrgetter('name')) - class_items = self.RemoveDuplicated(name, class_items) + class_items = self.RemoveDuplicated(class_name, class_items) init_position = -1 @@ -2872,14 +2880,14 @@ class SphinxGenerator(generators.DocsGeneratorBase): method_name, simple_docs = self.getName(item) if method_name == '__init__': init_position = index - self.current_class.method_list.insert(0, (method_name, simple_docs)) + self.current_class.method_list.insert(0, ('%s.%s'%(class_name, method_name), simple_docs)) else: - self.current_class.method_list.append((method_name, simple_docs)) + self.current_class.method_list.append(('%s.%s'%(class_name, method_name), simple_docs)) elif isinstance(item, extractors.PyPropertyDef): - simple_docs = self.createPropertyLinks(name, item) - self.current_class.property_list.append((item.name, simple_docs)) + simple_docs = self.createPropertyLinks(class_name, item) + self.current_class.property_list.append(('%s.%s'%(class_name, item.name), simple_docs)) - PickleClassInfo(self.current_module + name, self.current_class) + PickleClassInfo(self.current_module + class_name, self.current_class) if init_position >= 0: init_method = class_items.pop(init_position) @@ -2890,7 +2898,7 @@ class SphinxGenerator(generators.DocsGeneratorBase): docstring = XMLDocString(klass) docstring.kind = 'class' - filename = self.current_module + "%s.txt"%name + filename = self.current_module + "%s.txt"%class_name docstring.output_file = filename docstring.current_module = self.current_module @@ -2983,15 +2991,15 @@ class SphinxGenerator(generators.DocsGeneratorBase): for item in class_items: if isinstance(item, methods) and not self.IsFullyDeprecated(item): method_name, simple_docs = self.getName(item) - self.current_class.method_list.append((method_name, simple_docs)) + self.current_class.method_list.append(('%s.%s'%(name, method_name), simple_docs)) elif isinstance(item, properties): simple_docs = self.createPropertyLinks(name, item) - self.current_class.property_list.append((item.name, simple_docs)) + self.current_class.property_list.append(('%s.%s'%(name, item.name), simple_docs)) for item in ctors: if item.isCtor: method_name, simple_docs = self.getName(item) - self.current_class.method_list.insert(0, ('__init__', simple_docs)) + self.current_class.method_list.insert(0, ('%s.__init__'%name, simple_docs)) PickleClassInfo(self.current_module + name, self.current_class) diff --git a/sphinxtools/constants.py b/sphinxtools/constants.py index 33980b31..30ea011c 100644 --- a/sphinxtools/constants.py +++ b/sphinxtools/constants.py @@ -144,8 +144,68 @@ HTML_REPLACE = ['module', 'function', 'method', 'class', 'classmethod', 'staticm # The SVN revision of wxWidgets/Phoenix used to build the Sphinx docs. # There must be a more intelligent way to get this information automatically. -SVN_REVISION = '71086' +SVN_REVISION = '71066' # Today's date representation for the Sphinx HTML docs TODAY = datetime.date.today().strftime('%d %B %Y') +# wx.lib and other pure-Python stuff + +class Enumeration(object): + + def __init__(self, name, enumList): + + self.__doc__ = name + lookup = { } + reverseLookup = { } + i = 0 + uniqueNames = [ ] + uniqueValues = [ ] + + for item in enumList: + x = item.upper() + uniqueNames.append(x) + uniqueValues.append(i) + lookup[x] = i + reverseLookup[i] = x + i = i + 1 + + self.lookup = lookup + self.reverseLookup = reverseLookup + + def __getattr__(self, attr): + + if not self.lookup.has_key(attr): + raise AttributeError + + return self.lookup[attr] + + def whatis(self, value): + + return self.reverseLookup[value] + + +CONSTANT_RE = re.compile('^([\w\s,]+)=', re.M) + +EXCLUDED_ATTRS = ['__builtins__', '__doc__', '__name__', '__file__', '__path__', + '__module__', '__all__'] + +TYPE_DESCRIPTION = ['library', + 'package', + 'py_module', 'pyd_module', 'pyc_module', 'pyw_module', + 'klass', + 'function', + 'method', 'static_method', 'class_method', 'instance_method', + 'method_descriptor', 'builtin_method', 'builtin_function', + 'property', + 'booltype', 'classtype', 'complextype', 'dictproxytype', 'dicttype', 'filetype', + 'floattype', 'instancetype', 'inttype', 'lambdatype', 'listtype', 'longtype', + 'nonetype', 'objecttype', 'slicetype', 'strtype', 'tracebacktype', 'tupletype', + 'typetype', 'unicodetype', 'unknowntype', 'xrangetype'] + +object_types = Enumeration('Object_Types', TYPE_DESCRIPTION) + +MODULE_TO_ICON = [(".py", object_types.PY_MODULE, "Py_Module"), (".pyd", object_types.PYD_MODULE, "Pyd_Module"), + (".pyc", object_types.PYC_MODULE, "Pyc_Module"), (".pyw", object_types.PYW_MODULE, "Pyw_Module"), + (".so", object_types.PYD_MODULE, "Pyd_Module")] + diff --git a/sphinxtools/inheritance.py b/sphinxtools/inheritance.py index 3531d788..12a8d472 100644 --- a/sphinxtools/inheritance.py +++ b/sphinxtools/inheritance.py @@ -25,192 +25,246 @@ ENOENT = getattr(errno, 'ENOENT', 0) EPIPE = getattr(errno, 'EPIPE', 0) -class InheritanceDiagram(object): - """ - Given a list of classes, determines the set of classes that they inherit - from all the way to the root "object", and then is able to generate a - graphviz dot graph from them. - """ - - # ----------------------------------------------------------------------- # - - def __init__(self, class_info): - - #print class_info - #print - #print - - self.class_info, self.specials = class_info - - - # These are the default attrs for graphviz - default_graph_attrs = { - 'rankdir': 'LR', - 'size': '"8.0, 12.0"', - } - default_node_attrs = { - 'shape': 'box', - 'fontsize': 10, - 'height': 0.3, - 'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, ' - 'Arial, Helvetica, sans', - 'style': '"setlinewidth(0.5)"', - } - default_edge_attrs = { - 'arrowsize': 0.5, - 'style': '"setlinewidth(0.5)"', - } - - - # ----------------------------------------------------------------------- # - - def FormatNodeAttrs(self, attrs): - - return ','.join(['%s=%s' % x for x in attrs.items()]) - - - # ----------------------------------------------------------------------- # - - def FormatGraphAttrs(self, attrs): - - return ''.join(['%s=%s;\n' % x for x in attrs.items()]) - - - # ----------------------------------------------------------------------- # - - def GenerateDot(self, name="dummy"): - """ - Generate a graphviz dot graph from the classes that were passed in to `__init__`. - - :param string `name`: the name of the graph. - - :rtype: `string` - - :returns: A string representing the Graphviz dot diagram. - """ - - inheritance_graph_attrs = dict(fontsize=9, ratio='auto', size='""', rankdir="LR") - inheritance_node_attrs = {"align": "center", 'shape': 'box', - 'fontsize': 10, 'height': 0.3, - 'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, ' - 'Arial, Helvetica, sans', 'style': '"setlinewidth(0.5)"', - 'labelloc': 'c', 'fontcolor': 'grey45'} - - inheritance_edge_attrs = {'arrowsize': 0.5, 'style': '"setlinewidth(0.5)"', "color": "black"} - - g_attrs = self.default_graph_attrs.copy() - n_attrs = self.default_node_attrs.copy() - e_attrs = self.default_edge_attrs.copy() - g_attrs.update(inheritance_graph_attrs) - n_attrs.update(inheritance_node_attrs) - e_attrs.update(inheritance_edge_attrs) - - res = [] - res.append('digraph %s {\n' % name) - res.append(self.FormatGraphAttrs(g_attrs)) - - for name, fullname, bases in self.class_info.values(): - # Write the node - this_node_attrs = n_attrs.copy() - - if name in self.specials: - this_node_attrs['fontcolor'] = 'black' - this_node_attrs['color'] = 'blue' - this_node_attrs['style'] = 'bold' - - newname, fullname = Wx2Sphinx(name) - - this_node_attrs['URL'] = '"%s.html"'%fullname - res.append(' "%s" [%s];\n' % - (newname, self.FormatNodeAttrs(this_node_attrs))) - - # Write the edges - for base_name in bases: - - this_edge_attrs = e_attrs.copy() - if name in self.specials: - this_edge_attrs['color'] = 'red' - - base_name, dummy = Wx2Sphinx(base_name) - - res.append(' "%s" -> "%s" [%s];\n' % - (base_name, newname, - self.FormatNodeAttrs(this_edge_attrs))) - res.append('}\n') - return ''.join(res) - - - # ----------------------------------------------------------------------- # - - def MakeInheritanceDiagram(self): - """ - Actually generates the inheritance diagram as a PNG file plus the corresponding - MAP file for mouse navigation over the inheritance boxes. - - These two files are saved into the ``INHERITANCEROOT`` folder (see `sphinxtools/constants.py` - for more information). - - :rtype: `tuple` - - :returns: a tuple containing the PNG file name and a string representing the content - of the MAP file (with newlines stripped away). - - .. note:: The MAP file is deleted as soon as its content has been read. - """ - - code = self.GenerateDot() - - # graphviz expects UTF-8 by default - if isinstance(code, unicode): - code = code.encode('utf-8') - - static_root = INHERITANCEROOT - - dot_args = ['dot'] - dummy, filename = Wx2Sphinx(self.specials[0]) - outfn = os.path.join(static_root, filename + '_inheritance.png') - - if os.path.isfile(outfn): - os.remove(outfn) - if os.path.isfile(outfn + '.map'): - os.remove(outfn + '.map') - - dot_args.extend(['-Tpng', '-o' + outfn]) - dot_args.extend(['-Tcmapx', '-o%s.map' % outfn]) - - try: - - p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE) - - except OSError, err: - - if err.errno != ENOENT: # No such file or directory - raise - - print '\nERROR: Graphviz command `dot` cannot be run (needed for Graphviz output), check your ``PATH`` setting' - - try: - # Graphviz may close standard input when an error occurs, - # resulting in a broken pipe on communicate() - stdout, stderr = p.communicate(code) - - except OSError, err: - - # in this case, read the standard output and standard error streams - # directly, to get the error message(s) - stdout, stderr = p.stdout.read(), p.stderr.read() - p.wait() - - if p.returncode != 0: - print '\nERROR: Graphviz `dot` command exited with error:\n[stderr]\n%s\n[stdout]\n%s\n\n' % (stderr, stdout) - - mapfile = outfn + '.map' - - fid = open(mapfile, 'rt') - map = fid.read() - fid.close() - - os.remove(mapfile) - - return os.path.split(outfn)[1], map.replace('\n', ' ') - +class InheritanceDiagram(object): + """ + Given a list of classes, determines the set of classes that they inherit + from all the way to the root "object", and then is able to generate a + graphviz dot graph from them. + """ + + def __init__(self, classes, main_class=None): + + if main_class is None: + self.class_info, self.specials = classes + self.class_info = self.class_info.values() + else: + self.class_info, self.specials = self._class_info(classes) + + self.main_class = main_class + + + def _class_info(self, classes): + """Return name and bases for all classes that are ancestors of + *classes*. + + *parts* gives the number of dotted name parts that is removed from the + displayed node names. + """ + + all_classes = {} + specials = [] + + def recurse(cls): + nodename, fullname = self.class_name(cls) + + baselist = [] + all_classes[cls] = (nodename, fullname, baselist) + + for base in cls.__bases__: + baselist.append(self.class_name(base)[0]) + if base not in all_classes: + recurse(base) + + for cls in classes: + recurse(cls) + specials.append(self.class_name(cls)[1]) + + return all_classes.values(), specials + + + def class_name(self, cls): + """Given a class object, return a fully-qualified name. + + This works for things I've tested in matplotlib so far, but may not be + completely general. + """ + + module = cls.__module__ + + if module == '__builtin__': + fullname = cls.__name__ + else: + fullname = '%s.%s' % (module, cls.__name__) + + name_parts = fullname.split('.') + if 'wx._' in fullname: + nodename = fullname = name_parts[-1] + else: + # Just the last 2 parts + nodename = '.'.join(name_parts[-2:]) + + return nodename, fullname + + + # These are the default attrs for graphviz + default_graph_attrs = { + 'rankdir': 'LR', + 'size': '"8.0, 12.0"', + } + default_node_attrs = { + 'shape': 'box', + 'fontsize': 10, + 'height': 0.3, + 'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, ' + 'Arial, Helvetica, sans', + 'style': '"setlinewidth(0.5)"', + } + default_edge_attrs = { + 'arrowsize': 0.5, + 'style': '"setlinewidth(0.5)"', + } + + def _format_node_attrs(self, attrs): + return ','.join(['%s=%s' % x for x in attrs.items()]) + + def _format_graph_attrs(self, attrs): + return ''.join(['%s=%s;\n' % x for x in attrs.items()]) + + def generate_dot(self, name="dummy", urls={}, graph_attrs={}, node_attrs={}, edge_attrs={}): + """Generate a graphviz dot graph from the classes that were passed in + to __init__. + + *name* is the name of the graph. + + *urls* is a dictionary mapping class names to HTTP URLs. + + *graph_attrs*, *node_attrs*, *edge_attrs* are dictionaries containing + key/value pairs to pass on as graphviz properties. + """ + + inheritance_graph_attrs = dict(fontsize=9, ratio='auto', size='""', rankdir="LR") + inheritance_node_attrs = {"align": "center", 'shape': 'box', + 'fontsize': 10, 'height': 0.3, + 'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, ' + 'Arial, Helvetica, sans', 'style': '"setlinewidth(0.5)"', + 'labelloc': 'c', 'fontcolor': 'grey45'} + + inheritance_edge_attrs = {'arrowsize': 0.5, 'style': '"setlinewidth(0.5)"', "color": "black"} + + g_attrs = self.default_graph_attrs.copy() + n_attrs = self.default_node_attrs.copy() + e_attrs = self.default_edge_attrs.copy() + g_attrs.update(inheritance_graph_attrs) + n_attrs.update(inheritance_node_attrs) + e_attrs.update(inheritance_edge_attrs) + + res = [] + res.append('digraph %s {\n' % name) + res.append(self._format_graph_attrs(g_attrs)) + + for name, fullname, bases in self.class_info: + # Write the node + this_node_attrs = n_attrs.copy() + + if fullname in self.specials: + this_node_attrs['fontcolor'] = 'black' + this_node_attrs['color'] = 'blue' + this_node_attrs['style'] = 'bold' + + if self.main_class is None: + newname, fullname = Wx2Sphinx(name) + else: + newname = name + + this_node_attrs['URL'] = '"%s.html"'%fullname + res.append(' "%s" [%s];\n' % + (newname, self._format_node_attrs(this_node_attrs))) + + # Write the edges + for base_name in bases: + + this_edge_attrs = e_attrs.copy() + if fullname in self.specials: + this_edge_attrs['color'] = 'red' + + if self.main_class is None: + base_name, dummy = Wx2Sphinx(base_name) + + res.append(' "%s" -> "%s" [%s];\n' % + (base_name, newname, + self._format_node_attrs(this_edge_attrs))) + res.append('}\n') + return ''.join(res) + + + # ----------------------------------------------------------------------- # + + def MakeInheritanceDiagram(self): + """ + Actually generates the inheritance diagram as a PNG file plus the corresponding + MAP file for mouse navigation over the inheritance boxes. + + These two files are saved into the ``INHERITANCEROOT`` folder (see `sphinxtools/constants.py` + for more information). + + :rtype: `tuple` + + :returns: a tuple containing the PNG file name and a string representing the content + of the MAP file (with newlines stripped away). + + .. note:: The MAP file is deleted as soon as its content has been read. + """ + + static_root = INHERITANCEROOT + + if self.main_class is not None: + filename = self.main_class.name + else: + dummy, filename = Wx2Sphinx(self.specials[0]) + + outfn = os.path.join(static_root, filename + '_inheritance.png') + mapfile = outfn + '.map' + + if os.path.isfile(outfn) and os.path.isfile(mapfile): + fid = open(mapfile, 'rt') + map = fid.read() + fid.close() + return os.path.split(outfn)[1], map.replace('\n', ' ') + + code = self.generate_dot() + + # graphviz expects UTF-8 by default + if isinstance(code, unicode): + code = code.encode('utf-8') + + dot_args = ['dot'] + + if os.path.isfile(outfn): + os.remove(outfn) + if os.path.isfile(mapfile): + os.remove(mapfile) + + dot_args.extend(['-Tpng', '-o' + outfn]) + dot_args.extend(['-Tcmapx', '-o' + mapfile]) + + try: + + p = Popen(dot_args, stdout=PIPE, stdin=PIPE, stderr=PIPE) + + except OSError, err: + + if err.errno != ENOENT: # No such file or directory + raise + + print '\nERROR: Graphviz command `dot` cannot be run (needed for Graphviz output), check your ``PATH`` setting' + + try: + # Graphviz may close standard input when an error occurs, + # resulting in a broken pipe on communicate() + stdout, stderr = p.communicate(code) + + except OSError, err: + + # in this case, read the standard output and standard error streams + # directly, to get the error message(s) + stdout, stderr = p.stdout.read(), p.stderr.read() + p.wait() + + if p.returncode != 0: + print '\nERROR: Graphviz `dot` command exited with error:\n[stderr]\n%s\n[stdout]\n%s\n\n' % (stderr, stdout) + + fid = open(mapfile, 'rt') + map = fid.read() + fid.close() + + return os.path.split(outfn)[1], map.replace('\n', ' ') diff --git a/sphinxtools/librarydescription.py b/sphinxtools/librarydescription.py new file mode 100644 index 00000000..63f319cb --- /dev/null +++ b/sphinxtools/librarydescription.py @@ -0,0 +1,735 @@ +import sys +import os +import operator +import errno +import re + +from StringIO import StringIO +from subprocess import Popen, PIPE + +from inspect import getmro, getclasstree, getdoc, getcomments + +from utilities import MakeSummary, ChopDescription, WriteSphinxOutput, PickleClassInfo +from constants import object_types, MODULE_TO_ICON +import templates + +ENOENT = getattr(errno, 'ENOENT', 0) +EPIPE = getattr(errno, 'EPIPE', 0) + +reload(sys) +sys.setdefaultencoding("utf-8") + + +def make_class_tree(tree): + + class_tree = [] + + if isinstance(tree, list): + for node in tree: + class_tree.append(make_class_tree(node)) + else: + name = tree[0].__name__ + class_tree.append(name) + + return class_tree + + +def generic_summary(libraryItem, stream): + + write_toc = True + add_tilde = [True, True] + + if libraryItem.kind in [object_types.LIBRARY, object_types.PACKAGE]: + list1 = libraryItem.GetItemByKind(object_types.PACKAGE) + list2 = libraryItem.GetItemByKind(object_types.PY_MODULE, object_types.PYW_MODULE) + + templ = [templates.TEMPLATE_PACKAGE_SUMMARY, templates.TEMPLATE_MODULE_SUMMARY] + refs = ['mod', 'mod'] + + elif libraryItem.kind in range(object_types.PY_MODULE, object_types.PYW_MODULE+1): + list1 = libraryItem.GetItemByKind(object_types.FUNCTION) + list2 = libraryItem.GetItemByKind(object_types.KLASS, recurse=True) + + templ = [templates.TEMPLATE_STD_FUNCTION_SUMMARY, templates.TEMPLATE_STD_CLASS_SUMMARY] + refs = ['func', 'ref'] + add_tilde = [True, False] + + elif libraryItem.kind == object_types.KLASS: + write_toc = False + list1 = libraryItem.GetItemByKind(object_types.METHOD, object_types.INSTANCE_METHOD) + list2 = libraryItem.GetItemByKind(object_types.PROPERTY) + + templ = [templates.TEMPLATE_METHOD_SUMMARY, templates.TEMPLATE_PROPERTY_SUMMARY] + refs = ['meth', 'attr'] + add_tilde = [True, True] + + else: + raise Exception('Invalid library item: %s'%libraryItem.GetShortName()) + + toctree = '' + + for index, sub_list in enumerate([list1, list2]): + table = [] + for item in sub_list: + + if item.is_redundant: + continue + + docs = ChopDescription(ReplaceWxDot(item.docs)) + table.append((item.name, docs)) + + if item.kind != object_types.FUNCTION: + toctree += ' %s\n'%item.name + + if table: + summary = MakeSummary(table, templ[index], refs[index], add_tilde[index]) + stream.write(summary) + + if toctree and write_toc: + stream.write(templates.TEMPLATE_TOCTREE%toctree) + stream.write('\n\n') + + +def MakeSphinxFile(name): + + return os.path.join(os.getcwd(), 'docs', 'sphinx', '%s.txt'%name) + + +def ReplaceWxDot(text): + + # Double ticks with 'wx.' in them + text = re.sub(r'``wx\.(.*?)``', r'``\1`` ', text) + + # Signle ticks with 'wx.' in them... try and referencing them + text = re.sub(r'`wx\.(.*?)`', r'`\1` ', text) + + return text + + +class ParentBase(object): + + def __init__(self, name, kind): + + self.name = name + self.kind = kind + + self.docs = u'' + self.comments = u'' + + self.is_redundant = False + + self.children = [] + + + def Add(self, klass): + + if u'lambda' in klass.name: + return + + for child in self.children: + if child.name == klass.name: + return + + klass.parent = self + self.children.append(klass) + + + def Save(self): + + self.children = sorted(self.children, key=lambda k: (getattr(k, "order"), getattr(k, "name").lower())) + + if self.docs is None: + self.docs = u'' + + if self.comments is None or not self.comments.strip(): + self.comments = u'' + + for child in self.children: + child.Save() + + + def GetImage(self): + + return self.kind + + + def GetName(self): + + return self.name + + + def GetShortName(self): + + return self.name.split(".")[-1] + + + def GetObject(self): + + return self.obj_type + + + def GetChildren(self): + + return self.children + + + def GetChildrenCount(self, recursively=True): + """ + Gets the number of children of this item. + + :param bool `recursively`: if ``True``, returns the total number of descendants, + otherwise only one level of children is counted. + """ + + count = len(self.children) + + if not recursively: + return count + + total = count + + for n in xrange(count): + total += self.children[n].GetChildrenCount() + + return total + + + def GetKindCount(self, minObj, maxObj=None): + + if maxObj is None: + maxObj = minObj + + count = 0 + for child in self.children: + if minObj <= child.kind <= maxObj: + count += 1 + + return count + + + def GetItemByKind(self, minObj, maxObj=None, recurse=False): + + if maxObj is None: + maxObj = minObj + + items = [] + for child in self.children: + if minObj <= child.kind <= maxObj: + items.append(child) + + if recurse: + items = items + child.GetItemByKind(minObj, maxObj, recurse) + + return items + + + def ToRest(self): + + pass + + +class Library(ParentBase): + + def __init__(self, name): + + ParentBase.__init__(self, name, object_types.LIBRARY) + + self.parent = None + self.filename = u'' + self.order = 0 + self.obj_type = u"Library" + self.python_version = u'' + + self.sphinx_file = MakeSphinxFile(name) + self.base_name = name + + + def GetShortName(self): + + return self.name + + + def Walk(self, obj): + + if obj == self: + obj.ToRest() + + # must have at least root folder + children = obj.GetChildren() + + if not children: + return + + # check each name + for child in children: + + if child.is_redundant: + continue + + child.ToRest() + + # recursively scan other folders, appending results + self.Walk(child) + + + def GetPythonVersion(self): + + return self.python_version + + + def ToRest(self): + + print '\n\nReST-ifying %s...\n\n'%self.base_name + stream = StringIO() + + header = templates.TEMPLATE_DESCRIPTION%(self.base_name, self.base_name + ' Library') + stream.write(header) + + stream.write(ReplaceWxDot(self.docs) + '\n\n') + + generic_summary(self, stream) + WriteSphinxOutput(stream, self.sphinx_file) + + +class Module(ParentBase): + + def __init__(self, name, kind): + + ParentBase.__init__(self, name, kind) + + self.filename = u'' + self.sphinx_file = MakeSphinxFile(name) + + if kind == object_types.PACKAGE: + self.obj_type = u"Package" + self.order = kind + return + + self.order = object_types.PY_MODULE + + for dummy, icon, description in MODULE_TO_ICON: + if icon == kind: + self.obj_type = description + break + + self.inheritance_diagram = None + + + def ToRest(self): + + stream = StringIO() + + label = 'Module' + if self.kind == object_types.PACKAGE: + label = 'Package' + + stream.write('.. module:: %s\n\n'%self.name) + stream.write('.. currentmodule:: %s\n\n'%self.name) + + header = templates.TEMPLATE_DESCRIPTION%(self.name, '%s %s'%(self.name, label)) + + stream.write(header) + stream.write(ReplaceWxDot(self.docs) + '\n\n') + + spacer = ' '*self.name.count('.') + + if self.kind != object_types.PACKAGE: + print '%s - %s (module)'%(spacer, self.name) + if self.inheritance_diagram: + png, map = self.inheritance_diagram.MakeInheritanceDiagram() + short_name = self.GetShortName() + image_desc = templates.TEMPLATE_INHERITANCE % (short_name, png, short_name, map) + stream.write(image_desc) + else: + print '%s - %s (package)'%(spacer, self.name) + + generic_summary(self, stream) + + if self.kind != object_types.PACKAGE: + functions = self.GetItemByKind(object_types.FUNCTION) + + count = 0 + for fun in functions: + if not fun.is_redundant: + count = 1 + break + + if count > 0: + stream.write('\n\nFunctions\n===========\n\n') + + for fun in functions: + if fun.is_redundant: + continue + fun.Write(stream) + + WriteSphinxOutput(stream, self.sphinx_file) + + + +class Class(ParentBase): + + def __init__(self, name, obj): + + ParentBase.__init__(self, name, object_types.KLASS) + + try: + subs = obj.__subclasses__() + except (AttributeError, TypeError): + subs = [] + + sups = list(obj.__bases__) + + sortedSubClasses = [] + sortedSupClasses = [] + + for item in sups: + item = repr(item) + + sup = item.replace("", "").replace("= 0: + init = self.children.pop(pop) + self.children.insert(0, init) + + self.signature = self.signature.replace('wx.', '') + self.signature = self.signature.rstrip(':').lstrip('class ') + + if ' def __init__' in self.signature: + index = self.signature.index(' def __init__') + self.signature = self.signature[0:index] + + self.signature.strip() + + +class ChildrenBase(object): + + def __init__(self, name, kind): + + self.name = name + self.kind = kind + + self.order = 4 + + self.docs = u'' + self.comments = u'' + + self.is_redundant = False + +## self.id = NewId() + + + def GetImage(self): + + return self.kind + + + def GetName(self): + + return self.name + + + def GetShortName(self): + + return self.name.split(".")[-1] + + + def GetChildren(self): + + return [] + + + def GetChildrenCount(self, recursively=True): + + return 0 + + + def GetObject(self): + + return self.obj_type + + + def Save(self): + + if self.docs is None: + self.docs = u'' + + if self.comments is None or not self.comments.strip(): + self.comments = u'' + + + def ToRest(self): + + pass + + +class Method(ChildrenBase): + + def __init__(self, name, kind): + + ChildrenBase.__init__(self, name, kind) + + self.order = 5 + + self.arguments = [] + self.signature = u'' + + self.obj_type = u"Method/Function" + + + def Save(self): + + ChildrenBase.Save(self) + + newargs = [] + if self.arguments and any(self.arguments[0]): + for name, repr_val, eval_val in self.arguments: + repr_val = (repr_val is not None and [repr_val] or [""])[0] + eval_val = (eval_val is not None and [eval_val] or [""])[0] + newargs.append((name, repr_val, eval_val)) + + self.arguments = newargs + + self.signature = self.signature.replace('wx.', '') + self.signature = self.signature.rstrip(':').lstrip() + + if self.signature.startswith('def '): + self.signature = self.signature[4:] + + if not self.signature.strip(): + self.is_redundant = True + + + def Write(self, stream): + + if self.is_redundant: + return + + if self.kind == object_types.FUNCTION: + stream.write('.. function:: %s\n\n'%self.signature) + indent = 3*' ' + else: + stream.write(' .. method:: %s\n\n'%self.signature) + indent = 6*' ' + + if not self.docs.strip(): + stream.write('\n') + return + + text = '' + for line in self.docs.splitlines(): + text += indent + ReplaceWxDot(line) + '\n' + + text += '\n\n' + stream.write(text) + + + +class Property(ChildrenBase): + + def __init__(self, name, item): + + ChildrenBase.__init__(self, name, object_types.PROPERTY) + + self.getter = self.setter = self.deleter = "" + + try: + if item.fget: + self.getter = item.fget.__name__ + if item.fset: + self.setter = item.fset.__name__ + if item.fdel: + self.deleter = item.fdel.__name__ + except AttributeError: + # Thank you for screwing it up, Cython... + if item.fget: + self.getter = item.fget.__class__.__name__ + if item.fset: + self.setter = item.fset.__class__.__name__ + if item.fdel: + self.deleter = item.fdel.__class__.__name__ + + self.docs = getdoc(item) + self.comments = getcomments(item) + + self.obj_type = u"Property" + self.order = 6 + + + def Write(self, stream, class_name): + + if self.is_redundant: + return + + docs = '' + for item in [self.setter, self.getter, self.deleter]: + if item and 'lambda' not in item and not item.startswith('_'): + if docs: + docs += ', :meth:`~%s.%s` '%(class_name, item) + else: + docs += ':meth:`~%s.%s` '%(class_name, item) + + if docs: + docs = 'See %s'%docs + + stream.write(' .. attribute:: %s\n\n'%self.GetShortName()) + stream.write(' %s\n\n\n'%docs) + + +class Attribute(ChildrenBase): + + def __init__(self, name, specs, value): + + specs = unicode(specs) + start, end = specs.find("'"), specs.rfind("'") + specs = specs[start+1:end] + + strValue = repr(value) + uspecs = specs.upper() + + try: + kind = getattr(object_types, uspecs) + except AttributeError: + try: + uspecs = uspecs + u"TYPE" + kind = getattr(object_types, uspecs) + except AttributeError: + kind = object_types.UNKNOWNTYPE + + try: + reprValue = repr(value.__class__) + except (NameError, AttributeError): + reprValue = "" + + if u"class" in strValue or u"class" in reprValue: + kind = object_types.INSTANCETYPE + + ChildrenBase.__init__(self, name, kind) + + self.value = strValue + self.specs = specs + + try: + self.docs = getdoc(value) + except (NameError, AttributeError): + self.docs = u'' + + self.obj_type = u"Attribute" + self.order = 7 + + + def ToRest(self): + + pass + diff --git a/sphinxtools/modulehunter.py b/sphinxtools/modulehunter.py new file mode 100644 index 00000000..a2895eee --- /dev/null +++ b/sphinxtools/modulehunter.py @@ -0,0 +1,632 @@ +# -*- coding: utf-8 -*- + +# Describe classes, methods and functions in a module. +# Works with user-defined modules, all Python library +# modules, including built-in modules. + +import os +import sys +import glob +import types +import cPickle +import imp +import traceback +import pkgutil + +import __builtin__ + +from inspect import getargspec, ismodule, getdoc, getmodule, getcomments, isfunction +from inspect import ismethoddescriptor, getsource, ismemberdescriptor, isgetsetdescriptor +from inspect import isbuiltin, isclass, getfile, ismethod + +from librarydescription import Library, Module, Class +from librarydescription import Method, Property, Attribute + +import inheritance + +from constants import object_types, EXCLUDED_ATTRS, MODULE_TO_ICON +from constants import CONSTANT_RE + +reload(sys) +sys.setdefaultencoding("utf-8") + +try: + import wx +except ImportError: + wxPath = None + for path in sys.path: + if 'wx-' in path: + wxPath = path + break + + if wxPath is None: + raise Exception('Unable to find the wx package') + + sys.path.insert(0, wxPath) + +import wx + + +if hasattr(os.path, "relpath"): + relpath = os.path.relpath # since Python 2.6 +else: + def relpath(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + + # Work out how much of the filepath is shared by start and path. + i = len(os.path.commonprefix([start_list, path_list])) + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + + return os.path.join(*rel_list) + + +def format_traceback(): + + t, v = sys.exc_info()[:2] + message = "".join(traceback.format_exception_only(t, v)).replace("\n", " ") + return message.strip() + + +def format_method(method): + + method = method.strip() + + if "def " not in method: + return None, None + + indx1, indx2, indx3 = method.index("def "), method.index("("), method.rindex(")") + name = method[indx1+4:indx2] + signature = method[indx2+1:indx3] + + if "\n" in signature: + sig = signature.split("\n") + params = "" + for s in sig: + params += s.strip() + " " + params = params.rstrip() + else: + params = signature + + return name, params + + +def analyze_params(obj, signature): + + params = signature.split(",") + if "self" in params: + params.remove("self") + signature = ",".join(params) + + param_tuple = [] + + if not params: + return signature, param_tuple + + try: + arginfo = getargspec(obj) + except TypeError: + arginfo = None + + pevals = {} + + if arginfo: + args = arginfo[0] + argsvar = arginfo[1] + + if arginfo[3]: + + dl = len(arginfo[3]) + al = len(args) + defargs = args[al-dl:al] + info = arginfo[3] + + for d, i in zip(defargs, info): + pevals[d] = i + + for par in params: + p = par.strip() + pvalue = peval = None + + if "=" in p: + all_values = p.split("=") + pname, pvalue = all_values[0].strip(), "=".join(all_values[1:]).strip() + pvalue = pvalue.strip() + if pname in pevals: + try: + peval = unicode(pevals[pname]) + except UnicodeDecodeError: + peval = repr(pevals[pname]) + except TypeError: + peval = u'' + else: + pname = p + + param_tuple.append((pname, pvalue, peval)) + + return signature, param_tuple + + +def get_constructor(source): + + description = "" + hasComma = False + + for line in source.split("\n"): + + if '#' in line: + line = line[0:line.index('#')].strip() + + if ":" in line: + hasComma = True + commaPos = line.index(":") + + if ('"""' in line or "'''" in line) and hasComma: + break + + if hasComma and ' def ' in line: + defPos = line.index(' def ') + if defPos > commaPos: + break + + description += " " + line.strip() + + if "):" in line or ") :" in line or ") :" in line: + break + + return description + + +def inspect_source(method_class, obj, source): + + description = get_constructor(source) + name, params = format_method(description) + + if name is None: + return + + signature, param_tuple = analyze_params(obj, params) + + method_class.arguments = param_tuple + method_class.signature = description.strip() + + if "classmethod " in description or is_classmethod(obj): + method_class.kind = object_types.CLASS_METHOD + elif "staticmethod " in description: + method_class.kind = object_types.STATIC_METHOD + + +def is_classmethod(instancemethod): + " Determine if an instancemethod is a classmethod. " + + if hasattr(instancemethod, 'im_self'): + return instancemethod.im_self is not None + + return False + + +def describe_func(obj, parent_class, module_name): + """ + Describe the function object passed as argument. + If this is a method object, the second argument will + be passed as True. + """ + + try: + name = obj.__name__ + except AttributeError: + # Funny comtypes... + return + + if name.startswith("_") and "__init__" not in name: + return + + name = parent_class.name + "." + name + + docs = getdoc(obj) + comments = getcomments(obj) + + if isfunction(obj): + method = object_types.FUNCTION + elif ismethod(obj): + method = object_types.METHOD + elif ismethoddescriptor(obj): + method = object_types.METHOD_DESCRIPTOR + + if isinstance(obj, types.MethodType): + method = object_types.INSTANCE_METHOD + + try: + source_code = getsource(obj) + except (IOError, TypeError): + source_code = "" + + klass = Method(name, method) + klass.docs = docs + + klass_module = getmodule(obj) + if klass_module and klass_module.__name__ != module_name: + klass.is_redundant = True + + if source_code: + inspect_source(klass, obj, source_code) + klass.number_lines = "%d"%len(source_code.split("\n")) + + if isinstance(obj, staticmethod): + klass.method = method = object_types.STATIC_METHOD + + try: + if method in [object_types.METHOD, object_types.METHOD_DESCRIPTOR, object_types.INSTANCE_METHOD]: + code = obj.im_func.func_code + elif method == object_types.STATIC_METHOD: + code = obj.im_func.func_code + else: + code = obj.func_code + except AttributeError: + code = None + + if code is not None: + klass.firstlineno = "%d"%code.co_firstlineno + + parent_class.Add(klass) + + +def describe_class(obj, module_class, module_name, constants): + """ + Describe the class object passed as argument, + including its methods. + """ + + class_name = obj.__name__ + + if class_name == "object": + return + + class_name = module_class.name + "." + class_name + + docs = getdoc(obj) + comments = getcomments(obj) + + obj_dict = obj.__dict__ + + klass = Class(class_name, obj) + + count = 0 + + for name in obj_dict: + + if name.startswith("_") and "__init__" not in name: + continue + + if name in EXCLUDED_ATTRS: + continue + + try: + item = getattr(obj, name) + except AttributeError: + # Thanks to ReportLab for this funny exception... + continue + + if ismodule(item): + continue + + if ismemberdescriptor(item) or isgetsetdescriptor(item): + continue + + if isbuiltin(item): + count += 1 + elif ismethod(item) or isfunction(item) or ismethoddescriptor(item) or \ + isinstance(item, types.MethodType): + count += 1 + describe_func(item, klass, module_name) + elif isclass(item): + count += 1 + describe_class(item, klass, module_name, constants) + else: + name = class_name + "." + name + if isinstance(item, property): + item_class = Property(name, item) + klass.Add(item_class) + + item_module = getmodule(obj) + if item_module and item_module.__name__ != module_name: + item_class.is_redundant = True + + else: + item_class = Attribute(name, type(item), item) + klass.Add(item_class) + + if constants: + item_class.is_redundant = name not in constants + + count += 1 + + klass.docs = docs + klass.comments = comments + + klass_module = getmodule(obj) + if klass_module and klass_module.__name__ != module_name: + klass.is_redundant = True + else: + klass.inheritance_diagram = inheritance.InheritanceDiagram([obj], klass) + + module_class.Add(klass) + + try: + source_code = getsource(obj) + except (IOError, TypeError): + source_code = "" + + if source_code: + description = get_constructor(source_code) + klass.signature = description.strip() + klass.number_lines = '%d'%len(source_code.split("\n")) + + +def describe_module(module, kind, constants=[]): + """ + Describe the module object passed as argument + including its classes and functions. + """ + + module_name = module.__name__ + + if kind == object_types.LIBRARY: + klass = Library(module_name) + else: + klass = Module(module_name, kind) + + klass.docs = getdoc(module) + klass.comments = getcomments(module) + + klass.filename = module.__file__ + inheritance_diagram = [] + + count = 0 + + for name in dir(module): + + if name in EXCLUDED_ATTRS: + continue + + obj = getattr(module, name) + + if ismodule(obj): + continue + + if ismemberdescriptor(obj) or isgetsetdescriptor(obj): + continue + + if isclass(obj): + count += 1 + describe_class(obj, klass, module_name, constants) + + if obj.__module__ == module.__name__: + inheritance_diagram.append(obj) + + elif isbuiltin(obj): + count += 1 + elif ismethod(obj) or isfunction(obj) or ismethoddescriptor(obj) or \ + isinstance(obj, types.MethodType): + count +=1 + describe_func(obj, klass, module_name) + else: + attribute = Attribute(module_name + "." + name, type(obj), obj) + klass.Add(attribute) + + if constants: + attribute.is_redundant = name not in constants + + if kind not in [object_types.PACKAGE, object_types.LIBRARY]: + if inheritance_diagram and len(inheritance_diagram) < 20: + klass.inheritance_diagram = inheritance.InheritanceDiagram(inheritance_diagram, klass) + + return klass, count + + +def Import(init_name, import_name, full_process=True): + + directory, module_name = os.path.split(init_name) + dirname = os.path.dirname(directory) + + if not full_process: + path = list(sys.path) + sys.path.insert(0, dirname) + + f = None + + try: + + f, filename, description = imp.find_module(import_name, [dirname]) + mainmod = imp.load_module(import_name, f, filename, description) + + except (ImportError, NameError): + + message = format_traceback() + message += ' Please check that the "Import command" text box is correctly filled' + print "Error: %s"%message + + if not full_process: + sys.path = path[:] + + return + + if f: + f.close() + + if not full_process: + sys.path = path[:] + try: + version = mainmod.__version__ + except AttributeError: + try: + version = mainmod.__VERSION__ + except AttributeError: + print "Warning: Library '%s' has no __version__ or __VERSION__ attribute. Please specify it in the 'Import command' textbox"%import_name + return + + print version + + return mainmod + + +def PrintProgress(name, looped_names): + + looped_names.append(name) + if len(looped_names) == 5: + message = ", ".join(looped_names) + looped_names = [] + print message + + return looped_names + + + +def FindModuleType(filename): + + splitext = os.path.splitext(filename)[0] + for extension, icon, description in MODULE_TO_ICON: + if os.path.isfile(splitext + extension): + return icon + + +def SubImport(import_string, module, parent_class, ispkg): + + try: + submod = __import__(import_string, fromlist=[module]) + except: + # pubsub and Editra can be funny sometimes... + message = "Unable to import module/package '%s'.\n Exception was: %s"%(module, format_traceback()) + print "\nWARNING: %s\n"%message + return None, 0 + + if not ismodule(submod): + return None, 0 + + filename = getfile(submod) + + if ispkg: + kind = object_types.PACKAGE + else: + kind = FindModuleType(filename) + + constants = [] + + if kind in [object_types.PY_MODULE, object_types.PACKAGE]: + + contents = open(filename, "rt").read() + consts = CONSTANT_RE.findall(contents) + + for c in consts: + if "," in c: + c = c.split(",") + constants.extend([v.strip() for v in c]) + else: + constants.append(c.strip()) + + module_class, count = describe_module(submod, kind=kind, constants=constants) + parent_class.Add(module_class) + + return module_class, count + + +def ModuleHunter(init_name, import_name, version): + + pickle_file = os.path.join(os.getcwd(), 'docs', 'sphinx', 'wxlib.pkl') + + if os.path.isfile(pickle_file): + fid = open(pickle_file, 'rb') + library_class = cPickle.load(fid) + fid.close() + library_class.Walk(library_class) + return + + path = list(sys.path) + + directory, module_name = os.path.split(init_name) + path = list(sys.path) + + sys.path.insert(0, os.path.dirname(directory)) + + mainmod = Import(init_name, import_name) + + if mainmod is None: + return + + message = "Importing main library '%s'..."%import_name + print "Message: %s"%message + + module_name = os.path.splitext(getfile(mainmod))[0] + ".py" + contents = open(module_name, "rt").read() + constants = CONSTANT_RE.findall(contents) + + library_class, count = describe_module(mainmod, kind=object_types.LIBRARY, constants=constants) + library_class.name = "%s-%s"%(import_name, version) + + message = "Main library '%s' imported..."%library_class.name + print "Message: %s"%message + + message = "Importing sub-modules and sub-packages...\n" + print "Message: %s"%message + + looped_names = [] + ancestors_dict = {import_name: library_class} + + for importer, module_name, ispkg in pkgutil.walk_packages(path=[directory], + prefix=import_name+".", + onerror=lambda x: None): + + import_string = module_name + splitted = module_name.split(".") + + fromlist = splitted[-1] + parent_name = ".".join(splitted[0:-1]) + + parent_class = ancestors_dict[parent_name] + module_class, count = SubImport(import_string, fromlist, parent_class, ispkg) + + if module_class is None: + continue + + looped_names = PrintProgress(module_name, looped_names) + + if module_name not in ancestors_dict: + ancestors_dict[module_name] = module_class + + major, minor, micro, release = sys.version_info[0:-1] + pythonVersion = u"%d.%d.%d-%s"%(major, minor, micro, release) + + library_class.python_version = pythonVersion + library_class.Save() + + sys.path[:] = path # restore + + fid = open(pickle_file, 'wb') + cPickle.dump(library_class, fid) + fid.close() + + library_class.Walk(library_class) + + +if __name__ == "__main__": + + argv = sys.argv[1:] + + if len(argv) == 2: + init_name, import_name = argv + Import(init_name, import_name, full_process=False) + else: + init_name, import_name, version, save_dir = argv + ModuleHunter(init_name, import_name, version, save_dir) + + + + \ No newline at end of file diff --git a/sphinxtools/postprocess.py b/sphinxtools/postprocess.py index c4a96e7a..9adbb061 100644 --- a/sphinxtools/postprocess.py +++ b/sphinxtools/postprocess.py @@ -70,7 +70,7 @@ def SphinxIndexes(sphinxDir): if file.endswith('functions.pkl'): ReformatFunctions(file) elif 'classindex' in file: - MakeClassIndex(file) + MakeClassIndex(sphinxDir, file) BuildEnumsAndMethods(sphinxDir) @@ -122,9 +122,10 @@ def BuildEnumsAndMethods(sphinxDir): if widget_name in SECTIONS_EXCLUDE: start, end = SECTIONS_EXCLUDE[widget_name] - lindex = text.index(start) - rindex = text.index(end) - text = text[0:lindex] + text[rindex:] + if start in text and end in text: + lindex = text.index(start) + rindex = text.index(end) + text = text[0:lindex] + text[rindex:] # Replace the "Perl Note" stuff, we don't need it newtext = '' @@ -361,7 +362,7 @@ def ReformatFunctions(file): # ----------------------------------------------------------------------- # -def MakeClassIndex(file): +def MakeClassIndex(sphinxDir, file): text_file = os.path.splitext(file)[0] + '.txt' local_file = os.path.split(file)[1] @@ -376,13 +377,24 @@ def MakeClassIndex(file): if local_file.count('.') == 1: # Core functions label = 'Core' + module = '' + enumDots = 1 else: label = local_file.split('.')[0:-2][0] + module = label + enumDots = 2 + + enum_files = glob.glob(sphinxDir + '/%s*.enumeration.txt'%module) + enum_base = [os.path.split(os.path.splitext(enum)[0])[1] for enum in enum_files] names = classes.keys() names.sort() - text = templates.TEMPLATE_CLASS_INDEX % (label, label) + text = '' + if module: + text += '\n\n.. module:: %s\n\n'%module + + text += templates.TEMPLATE_CLASS_INDEX % (label, label) text += 80*'=' + ' ' + 80*'=' + '\n' text += '%-80s **Short Description**\n'%'**Class**' @@ -391,8 +403,24 @@ def MakeClassIndex(file): for cls in names: text += '%-80s %s\n'%(':ref:`%s`'%Wx2Sphinx(cls)[1], classes[cls]) - text += 80*'=' + ' ' + 80*'=' + '\n' + text += 80*'=' + ' ' + 80*'=' + '\n\n' + contents = [] + for cls in names: + contents.append(Wx2Sphinx(cls)[1]) + + for enum in enum_base: + if enum.count('.') == enumDots: + contents.append(enum) + + contents.sort() + + toctree = '' + for item in contents: + toctree += ' %s\n'%item + + text += templates.TEMPLATE_TOCTREE%toctree + writeIfChanged(text_file, text) diff --git a/sphinxtools/templates.py b/sphinxtools/templates.py index f34b0de7..c5c11aa7 100644 --- a/sphinxtools/templates.py +++ b/sphinxtools/templates.py @@ -16,9 +16,9 @@ TEMPLATE_DESCRIPTION = ''' .. _%s: -===================================================================================== +========================================================================================================================================== |phoenix_title| **%s** -===================================================================================== +========================================================================================================================================== ''' @@ -93,6 +93,18 @@ TEMPLATE_SUBCLASSES = ''' ''' +# Template for the superclasses of a class, with a string containing a list +# of comma separated class names with their ReST role as :ref: prepended +TEMPLATE_SUPERCLASSES = ''' +|super_classes| Known Superclasses +================================== + +%s + +| + +''' + # Template for the method summary of a class, containing a table made of # ``method_name`` ``method description`` @@ -247,3 +259,53 @@ TEMPLATE_HEADINGS = ''' ''' +# Templates for the summary of modules/packages, containing a table made of +# ``module name`` ``short description`` +TEMPLATE_MODULE_SUMMARY = ''' +|module_summary| Modules Summary +================================ + +%s + +| + +''' + +TEMPLATE_PACKAGE_SUMMARY = ''' +|package_summary| Packages Summary +================================== + +%s + +| + +''' + +TEMPLATE_STD_FUNCTION_SUMMARY = ''' +|function_summary| Functions Summary +==================================== + +%s + +| + +''' + +TEMPLATE_STD_CLASS_SUMMARY = ''' +|class_summary| Classes Summary +=============================== + +%s + +| + +''' + + +TEMPLATE_TOCTREE = ''' +.. toctree:: + :maxdepth: 1 + :hidden: + +%s +''' diff --git a/sphinxtools/utilities.py b/sphinxtools/utilities.py index 01893ae8..674726cd 100644 --- a/sphinxtools/utilities.py +++ b/sphinxtools/utilities.py @@ -458,26 +458,38 @@ def FindControlImages(element): # ----------------------------------------------------------------------- # -def MakeSummary(name, item_list, template, kind): +def MakeSummary(item_list, template, kind, add_tilde=True): """ This function generates a table containing a method/property name and a shortened version of its docstrings. - :param string `name`: the method/property class name. :param list `item_list`: a list of tuples like `(method/property name, short docstrings)`. :param string `template`: the template to use (from `sphinxtools/templates.py`, can be the ``TEMPLATE_METHOD_SUMMARY`` or the ``TEMPLATE_PROPERTY_SUMMARY``. - :param string `kind`: can be ":meth:" or ":attr:". + :param string `kind`: can be ":meth:" or ":attr:" or ":ref:". :rtype: `string` """ + + maxlen = 0 + for method, simple_docs in item_list: + substr = ':%s:`~%s`'%(kind, method) + maxlen = max(maxlen, len(substr)) + + maxlen = max(80, maxlen) - summary = '='*80 + ' ' + '='*80 + "\n" + summary = '='*maxlen + ' ' + '='*80 + "\n" + format = '%-' + str(maxlen) + 's %s' for method, simple_docs in item_list: - summary += '%-80s %s'%(':%s:`~%s.%s`'%(kind, name, method), simple_docs) + '\n' + if add_tilde: + substr = ':%s:`~%s`'%(kind, method) + else: + substr = ':%s:`%s`'%(kind, method) + + summary += format%(substr, simple_docs) + '\n' - summary += '='*80 + ' ' + '='*80 + "\n" + summary += '='*maxlen + ' ' + '='*80 + "\n" return template % summary @@ -585,7 +597,7 @@ def PickleClassInfo(class_name, element): for base in element.bases: bases.append(Wx2Sphinx(base)[1]) - + items[class_name] = (method_list, bases) fid = open(pickle_file, 'wb') cPickle.dump(items, fid)