├── BUGS.txt ├── CONTRIBUTORS.txt ├── LICENSE.txt ├── MANIFEST.in ├── Makefile.py ├── README.md ├── TODO.txt ├── bin ├── preprocess └── preprocess.exe ├── lib └── preprocess.py ├── setup.py ├── src ├── exe │ ├── Makefile.win │ └── launcher.cpp └── trentm.com │ ├── index.markdown │ ├── logo.gif │ └── project-info.xml └── test ├── inputs ├── define_types.py ├── define_undef.py ├── defined.py ├── elif.py ├── else.py ├── else_expr.py ├── empty.py ├── error.py ├── exc1.py ├── exc2.py ├── exc3.py ├── exc4.py ├── exc5.py ├── file_and_line.py ├── if.py ├── ifdef.py ├── ifndef.py ├── ignore_error.py ├── keep_lines_bugs.py ├── keep_lines_bugs.py.opts ├── nested.py ├── nested_bug1.py ├── recursive_include_a.py ├── recursive_include_b.py ├── sample.tex ├── sphere.f ├── subst_bug2.py ├── subst_bug2.py.opts ├── subst_bug2.py.tags ├── substitution.py ├── substitution.py.opts └── undefined.py ├── outputs ├── define_types.py ├── define_undef.py ├── defined.py ├── elif.py ├── else.py ├── else_expr.py ├── empty.py ├── error.py.err ├── exc1.py.err ├── exc2.py.err ├── exc3.py.err ├── exc4.py.err ├── exc5.py.err ├── file_and_line.py ├── if.py ├── ifdef.py ├── ifndef.py ├── ignore_error.py ├── keep_lines_bugs.py ├── nested.py ├── nested_bug1.py ├── recursive_include_a.py.err ├── recursive_include_b.py.err ├── sample.tex ├── sphere.f ├── subst_bug2.py ├── substitution.py └── undefined.py.err ├── test.py ├── test_preprocess.py ├── test_preprocess_inputs.py ├── testlib.py └── testsupport.py /BUGS.txt: -------------------------------------------------------------------------------- 1 | - Id: 1 2 | Status: Fixed 3 | Title: Nested #if-blocks will not be properly handled. 4 | Test-Case: test_nested_bug1.py 5 | Description: 6 | In the following code: 7 | #if 0 8 | #if 1 9 | foo 10 | #endif 11 | #endif 12 | the "foo" line _will_ get printed, even though it should not. 13 | 14 | - Id: 2 15 | Status: Open 16 | Title: Substitution (when turned on via -s) will substitute into 17 | program strings 18 | Test-Case: test_subst_bug2.py 19 | Description: 20 | That is not the ideal behaviour (ideal being defined by (1) what I 21 | would expect in my program, and (2) what the C preprocessor does). 22 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | Trent Mick (primary author) 2 | Hans Petter Langtangen 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002-2005 ActiveState Corp. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.in Makefile* *.txt MANIFEST 2 | recursive-include bin preprocess* 3 | recursive-include test/inputs * 4 | recursive-include test/outputs * 5 | recursive-include src/exe * 6 | exclude *~ 7 | -------------------------------------------------------------------------------- /Makefile.py: -------------------------------------------------------------------------------- 1 | 2 | """Makefile for the preprocess project. 3 | 4 | ${common_task_list} 5 | 6 | See `mk -h' for options. 7 | """ 8 | 9 | import sys 10 | import os 11 | from os.path import join, dirname, normpath, abspath, exists, basename 12 | import re 13 | import webbrowser 14 | 15 | from mklib.common import MkError 16 | from mklib import Task 17 | from mklib import sh 18 | 19 | 20 | 21 | class bugs(Task): 22 | """Open bug database page.""" 23 | def make(self): 24 | webbrowser.open("http://code.google.com/p/preprocess/issues/list") 25 | 26 | class site(Task): 27 | """Open the Google Code project page.""" 28 | def make(self): 29 | webbrowser.open("http://code.google.com/p/preprocess/") 30 | 31 | class play(Task): 32 | def make(self): 33 | sh.run("python setup.py install") 34 | sh.run("/Library/Frameworks/Python.framework/Versions/2.5/bin/preprocess foo.txt") 35 | 36 | 37 | class clean(Task): 38 | """Clean generated files and dirs.""" 39 | def make(self): 40 | patterns = [ 41 | "dist", 42 | "build", 43 | "MANIFEST", 44 | "*.pyc", 45 | "lib/*.pyc", 46 | ] 47 | for pattern in patterns: 48 | p = join(self.dir, pattern) 49 | for path in glob(p): 50 | sh.rm(path, log=self.log) 51 | 52 | class test(Task): 53 | """Run all tests (except known failures).""" 54 | def make(self): 55 | for ver, python in self._gen_pythons(): 56 | if ver < (2,3): 57 | # Don't support Python < 2.3. 58 | continue 59 | elif ver >= (3,0): 60 | # Don't (yet) support Python 3. 61 | continue 62 | ver_str = "%s.%s" % ver 63 | print "-- test with Python %s (%s)" % (ver_str, python) 64 | assert ' ' not in python 65 | sh.run_in_dir("%s test.py" % python, 66 | join(self.dir, "test")) 67 | 68 | def _python_ver_from_python(self, python): 69 | assert ' ' not in python 70 | o = os.popen('''%s -c "import sys; print(sys.version)"''' % python) 71 | ver_str = o.read().strip() 72 | ver_bits = re.split("\.|[^\d]", ver_str, 2)[:2] 73 | ver = tuple(map(int, ver_bits)) 74 | return ver 75 | 76 | def _gen_python_names(self): 77 | yield "python" 78 | for ver in [(2,3), (2,4), (2,5), (2,6), (2,7), (3,0), (3,1)]: 79 | yield "python%d.%d" % ver 80 | 81 | def _gen_pythons(self): 82 | sys.path.insert(0, join(self.dir, "externals", "which")) 83 | import which 84 | python_from_ver = {} 85 | for name in self._gen_python_names(): 86 | for python in which.whichall(name): 87 | ver = self._python_ver_from_python(python) 88 | if ver not in python_from_ver: 89 | python_from_ver[ver] = python 90 | for ver, python in sorted(python_from_ver.items()): 91 | yield ver, python 92 | 93 | 94 | class sdist(Task): 95 | """python setup.py sdist""" 96 | def make(self): 97 | sh.run_in_dir("%spython setup.py sdist -f --formats zip" 98 | % _setup_command_prefix(), 99 | self.dir, self.log.debug) 100 | 101 | class webdist(Task): 102 | """Build a web dist package for trentm.com/projects/ 103 | 104 | "Web dist" packages are zip files with '.web' extension. All files in 105 | the zip must be under a dir named after the project. There must be a 106 | webinfo.xml file at /webinfo.xml. This file is "defined" 107 | by the parsing in trentm.com/build.py. 108 | """ 109 | deps = ["docs"] 110 | 111 | def results(self): 112 | yield join(self.dir, "dist", "preprocess-%s.web" % _get_version()) 113 | 114 | def make(self): 115 | assert sys.platform != "win32", "'webdist' not implemented for win32" 116 | build_dir = join(self.dir, "build", "webdist") 117 | zip_dir = join(build_dir, "preprocess") 118 | if exists(build_dir): 119 | sh.rm(build_dir) 120 | os.makedirs(zip_dir) 121 | 122 | # Copy the webdist bits to the build tree. 123 | manifest = [ 124 | "src/trentm.com/project-info.xml", 125 | "src/trentm.com/index.markdown", 126 | "LICENSE.txt", 127 | "lib/preprocess.py", 128 | "src/trentm.com/logo.gif", 129 | ] 130 | for src in manifest: 131 | sh.cp(src, dstdir=zip_dir, log=self.log.info) 132 | 133 | # Zip up the webdist contents. 134 | dist_dir = join(self.dir, "dist") 135 | bit = abspath(join(dist_dir, "preprocess-%s.web" % _get_version())) 136 | if exists(bit): 137 | os.remove(bit) 138 | if not exists(dist_dir): 139 | os.makedirs(dist_dir) 140 | sh.run_in_dir("zip -r %s preprocess" % bit, build_dir, self.log.info) 141 | 142 | class pypi_upload(Task): 143 | """Upload release to pypi.""" 144 | def make(self): 145 | tasks = (sys.platform == "win32" 146 | and "sdist --formats zip bdist_wininst upload" 147 | or "sdist --formats zip upload") 148 | sh.run_in_dir("%spython setup.py %s" % (_setup_command_prefix(), tasks), 149 | self.dir, self.log.debug) 150 | 151 | sys.path.insert(0, join(self.dir, "lib")) 152 | url = "http://pypi.python.org/pypi/preprocess/" 153 | import webbrowser 154 | webbrowser.open_new(url) 155 | 156 | class googlecode_upload(Task): 157 | """Upload sdist to Google Code project site.""" 158 | deps = ["sdist"] 159 | def make(self): 160 | helper_in_cwd = exists(join(self.dir, "googlecode_upload.py")) 161 | if helper_in_cwd: 162 | sys.path.insert(0, self.dir) 163 | try: 164 | import googlecode_upload 165 | except ImportError: 166 | raise MkError("couldn't import `googlecode_upload` (get it from http://support.googlecode.com/svn/trunk/scripts/googlecode_upload.py)") 167 | if helper_in_cwd: 168 | del sys.path[0] 169 | 170 | ver = _get_version() 171 | sdist_path = join(self.dir, "dist", "preprocess-%s.zip" % ver) 172 | status, reason, url = googlecode_upload.upload_find_auth( 173 | sdist_path, 174 | "preprocess", # project_name 175 | "preprocess %s source package" % ver, # summary 176 | ["Featured", "Type-Archive"]) # labels 177 | if not url: 178 | raise MkError("couldn't upload sdist to Google Code: %s (%s)" 179 | % (reason, status)) 180 | self.log.info("uploaded sdist to `%s'", url) 181 | 182 | project_url = "http://code.google.com/p/preprocess/" 183 | import webbrowser 184 | webbrowser.open_new(project_url) 185 | 186 | class trentm_com_upload(Task): 187 | """Upload webdist to trentm.com bits (in prep for updating trentm.com).""" 188 | deps = ["webdist"] 189 | def make(self): 190 | ver = _get_version() 191 | dist_dir = join(self.dir, "dist") 192 | 193 | paths = [join(dist_dir, "preprocess-%s%s" % (ver, ext)) 194 | for ext in [".web"]] 195 | 196 | # Upload the bits. 197 | user = "trentm" 198 | host = "trentm.com" 199 | remote_dir = "~/data/bits/preprocess/%s" % _get_version() 200 | if sys.platform == "win32": 201 | ssh = "plink" 202 | scp = "pscp -unsafe" 203 | else: 204 | ssh = "ssh" 205 | scp = "scp" 206 | sh.run("%s %s@%s 'mkdir -p %s'" % (ssh, user, host, remote_dir), 207 | self.log.info) 208 | for path in paths: 209 | sh.run("%s %s %s@%s:%s" % (scp, path, user, host, remote_dir), 210 | self.log.info) 211 | 212 | class todo(Task): 213 | """Print out todo's and xxx's in the docs area.""" 214 | def make(self): 215 | for path in _paths_from_path_patterns(['.'], 216 | excludes=[".svn", "*.pyc", "TO""DO.txt", "Makefile.py", 217 | "*.png", "*.gif", "*.pprint", "*.prof", 218 | "tmp-*"]): 219 | self._dump_pattern_in_path("TO\DO\\|XX\X", path) 220 | 221 | path = join(self.dir, "TO""DO.txt") 222 | todos = re.compile("^- ", re.M).findall(open(path, 'r').read()) 223 | print "(plus %d TODOs from TO""DO.txt)" % len(todos) 224 | 225 | def _dump_pattern_in_path(self, pattern, path): 226 | os.system("grep -nH '%s' '%s'" % (pattern, path)) 227 | 228 | 229 | class gow(Task): 230 | """Build the Windows 'gow.exe' launcher exe for DQSD integration.""" 231 | def make(self): 232 | assert sys.platform == "win32", "can only build `gow.exe' on Windows" 233 | sh.run_in_dir("nmake -f Makefile.win", join("src", "dqsd")) 234 | 235 | class docs(Task): 236 | """Regenerate some doc bits from project-info.xml.""" 237 | deps = ["src/trentm.com/project-info.xml"] 238 | results = [ 239 | "README.txt", 240 | "src/trentm.com/index.markdown" 241 | ] 242 | def make(self): 243 | project_info_xml = join("src", "trentm.com", "project-info.xml") 244 | index_markdown = join("src", "trentm.com", "index.markdown") 245 | sh.run_in_dir("projinfo -f %s -R -o README.txt --force" 246 | % project_info_xml, self.dir) 247 | sh.run_in_dir("projinfo -f %s --index-markdown -o %s --force" 248 | % (project_info_xml, index_markdown), self.dir) 249 | 250 | class check_version(Task): 251 | """grep for version strings in source code 252 | 253 | List all things that look like version strings in the source code. 254 | Used for checking that versioning is updated across the board. 255 | """ 256 | sources = [ 257 | "lib/preprocess.py", 258 | "src/trentm.com/project-info.xml", 259 | ] 260 | def make(self): 261 | pattern = r'[0-9]\+\(\.\|, \)[0-9]\+\(\.\|, \)[0-9]\+' 262 | sh.run_in_dir('grep -n "%s" %s' % (pattern, ' '.join(self.sources)), 263 | self.dir) 264 | 265 | 266 | #---- internal support stuff 267 | 268 | # Recipe: paths_from_path_patterns (0.3.7) 269 | def _should_include_path(path, includes, excludes): 270 | """Return True iff the given path should be included.""" 271 | from os.path import basename 272 | from fnmatch import fnmatch 273 | 274 | base = basename(path) 275 | if includes: 276 | for include in includes: 277 | if fnmatch(base, include): 278 | try: 279 | log.debug("include `%s' (matches `%s')", path, include) 280 | except (NameError, AttributeError): 281 | pass 282 | break 283 | else: 284 | try: 285 | log.debug("exclude `%s' (matches no includes)", path) 286 | except (NameError, AttributeError): 287 | pass 288 | return False 289 | for exclude in excludes: 290 | if fnmatch(base, exclude): 291 | try: 292 | log.debug("exclude `%s' (matches `%s')", path, exclude) 293 | except (NameError, AttributeError): 294 | pass 295 | return False 296 | return True 297 | 298 | _NOT_SPECIFIED = ("NOT", "SPECIFIED") 299 | def _paths_from_path_patterns(path_patterns, files=True, dirs="never", 300 | recursive=True, includes=[], excludes=[], 301 | on_error=_NOT_SPECIFIED): 302 | """_paths_from_path_patterns([, ...]) -> file paths 303 | 304 | Generate a list of paths (files and/or dirs) represented by the given path 305 | patterns. 306 | 307 | "path_patterns" is a list of paths optionally using the '*', '?' and 308 | '[seq]' glob patterns. 309 | "files" is boolean (default True) indicating if file paths 310 | should be yielded 311 | "dirs" is string indicating under what conditions dirs are 312 | yielded. It must be one of: 313 | never (default) never yield dirs 314 | always yield all dirs matching given patterns 315 | if-not-recursive only yield dirs for invocations when 316 | recursive=False 317 | See use cases below for more details. 318 | "recursive" is boolean (default True) indicating if paths should 319 | be recursively yielded under given dirs. 320 | "includes" is a list of file patterns to include in recursive 321 | searches. 322 | "excludes" is a list of file and dir patterns to exclude. 323 | (Note: This is slightly different than GNU grep's --exclude 324 | option which only excludes *files*. I.e. you cannot exclude 325 | a ".svn" dir.) 326 | "on_error" is an error callback called when a given path pattern 327 | matches nothing: 328 | on_error(PATH_PATTERN) 329 | If not specified, the default is look for a "log" global and 330 | call: 331 | log.error("`%s': No such file or directory") 332 | Specify None to do nothing. 333 | 334 | Typically this is useful for a command-line tool that takes a list 335 | of paths as arguments. (For Unix-heads: the shell on Windows does 336 | NOT expand glob chars, that is left to the app.) 337 | 338 | Use case #1: like `grep -r` 339 | {files=True, dirs='never', recursive=(if '-r' in opts)} 340 | script FILE # yield FILE, else call on_error(FILE) 341 | script DIR # yield nothing 342 | script PATH* # yield all files matching PATH*; if none, 343 | # call on_error(PATH*) callback 344 | script -r DIR # yield files (not dirs) recursively under DIR 345 | script -r PATH* # yield files matching PATH* and files recursively 346 | # under dirs matching PATH*; if none, call 347 | # on_error(PATH*) callback 348 | 349 | Use case #2: like `file -r` (if it had a recursive option) 350 | {files=True, dirs='if-not-recursive', recursive=(if '-r' in opts)} 351 | script FILE # yield FILE, else call on_error(FILE) 352 | script DIR # yield DIR, else call on_error(DIR) 353 | script PATH* # yield all files and dirs matching PATH*; if none, 354 | # call on_error(PATH*) callback 355 | script -r DIR # yield files (not dirs) recursively under DIR 356 | script -r PATH* # yield files matching PATH* and files recursively 357 | # under dirs matching PATH*; if none, call 358 | # on_error(PATH*) callback 359 | 360 | Use case #3: kind of like `find .` 361 | {files=True, dirs='always', recursive=(if '-r' in opts)} 362 | script FILE # yield FILE, else call on_error(FILE) 363 | script DIR # yield DIR, else call on_error(DIR) 364 | script PATH* # yield all files and dirs matching PATH*; if none, 365 | # call on_error(PATH*) callback 366 | script -r DIR # yield files and dirs recursively under DIR 367 | # (including DIR) 368 | script -r PATH* # yield files and dirs matching PATH* and recursively 369 | # under dirs; if none, call on_error(PATH*) 370 | # callback 371 | """ 372 | from os.path import basename, exists, isdir, join 373 | from glob import glob 374 | 375 | assert not isinstance(path_patterns, basestring), \ 376 | "'path_patterns' must be a sequence, not a string: %r" % path_patterns 377 | GLOB_CHARS = '*?[' 378 | 379 | for path_pattern in path_patterns: 380 | # Determine the set of paths matching this path_pattern. 381 | for glob_char in GLOB_CHARS: 382 | if glob_char in path_pattern: 383 | paths = glob(path_pattern) 384 | break 385 | else: 386 | paths = exists(path_pattern) and [path_pattern] or [] 387 | if not paths: 388 | if on_error is None: 389 | pass 390 | elif on_error is _NOT_SPECIFIED: 391 | try: 392 | log.error("`%s': No such file or directory", path_pattern) 393 | except (NameError, AttributeError): 394 | pass 395 | else: 396 | on_error(path_pattern) 397 | 398 | for path in paths: 399 | if isdir(path): 400 | # 'includes' SHOULD affect whether a dir is yielded. 401 | if (dirs == "always" 402 | or (dirs == "if-not-recursive" and not recursive) 403 | ) and _should_include_path(path, includes, excludes): 404 | yield path 405 | 406 | # However, if recursive, 'includes' should NOT affect 407 | # whether a dir is recursed into. Otherwise you could 408 | # not: 409 | # script -r --include="*.py" DIR 410 | if recursive and _should_include_path(path, [], excludes): 411 | for dirpath, dirnames, filenames in os.walk(path): 412 | dir_indeces_to_remove = [] 413 | for i, dirname in enumerate(dirnames): 414 | d = join(dirpath, dirname) 415 | if dirs == "always" \ 416 | and _should_include_path(d, includes, excludes): 417 | yield d 418 | if not _should_include_path(d, [], excludes): 419 | dir_indeces_to_remove.append(i) 420 | for i in reversed(dir_indeces_to_remove): 421 | del dirnames[i] 422 | if files: 423 | for filename in sorted(filenames): 424 | f = join(dirpath, filename) 425 | if _should_include_path(f, includes, excludes): 426 | yield f 427 | 428 | elif files and _should_include_path(path, includes, excludes): 429 | yield path 430 | 431 | _g_version = None 432 | def _get_version(): 433 | global _g_version 434 | if _g_version is None: 435 | sys.path.insert(0, join(dirname(__file__), "lib")) 436 | try: 437 | import preprocess 438 | _g_version = preprocess.__version__ 439 | finally: 440 | del sys.path[0] 441 | return _g_version 442 | 443 | def _setup_command_prefix(): 444 | prefix = "" 445 | if sys.platform == "darwin": 446 | # http://forums.macosxhints.com/archive/index.php/t-43243.html 447 | # This is an Apple customization to `tar` to avoid creating 448 | # '._foo' files for extended-attributes for archived files. 449 | prefix = "COPY_EXTENDED_ATTRIBUTES_DISABLE=1 " 450 | return prefix 451 | 452 | 453 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # preprocess.py: a portable multi-language file preprocessor 2 | 3 | Download the latest preprocess.py packages from 4 | https://github.com/hplgit/preprocess.git. 5 | 6 | | | | 7 | | --------------- | ---------------------------------------- | 8 | | Home | https://github.com/hplgit/preprocess.git | 9 | | License | MIT (see `LICENSE.txt`) | 10 | | Platforms | Windows, Linux, Mac OS X, Unix | 11 | | Current Version | 1.2 | 12 | | Dev Status | Fairly mature | 13 | | Requirements | Python >= 2.7 or Python >= 3.4 | 14 | | Dependencies | [python-future](http://python-future.org) | 15 | | Author | Trent Mick | 16 | 17 | ## Why preprocess.py? 18 | 19 | There are millions of templating systems out there (most of them 20 | developed for the web). This isn't one of those, though it does share 21 | some basics: a markup syntax for templates that are processed to give 22 | resultant text output. The main difference with `preprocess.py` is 23 | that its syntax is hidden in comments (whatever the syntax for comments 24 | maybe in the target filetype) so that the file can still have valid 25 | syntax. A comparison with the C preprocessor is more apt. 26 | 27 | `preprocess.py` is targetted at build systems that deal with many 28 | types of files. Languages for which it works include: C++, Python, 29 | Perl, Tcl, XML, JavaScript, CSS, IDL, TeX, Fortran, PHP, Java, Shell 30 | scripts (Bash, CSH, etc.) and C#. Preprocess is usable both as a 31 | command line app and as a Python module. 32 | 33 | Here is how is works: All preprocessor statements are on their own 34 | line. A preprocessor statement is a comment (as appropriate for the 35 | language of the file being preprocessed). This way the preprocessor 36 | statements do not make an unpreprocessed file syntactically incorrect. 37 | 38 | Here is a simple example from Python code in some file `myapp1.p.py`: 39 | 40 | ``` 41 | def myfunc(): 42 | """ 43 | Function for computing fundamental arithmetics. 44 | # #if EXTENSIVE_DOC 45 | # #include "../myfunc_doc.txt" 46 | # #endif 47 | """ 48 | return 1 + 1 49 | ``` 50 | Running 51 | 52 | ``` 53 | preprocess myapp1.p.py > myapp1.py 54 | ``` 55 | yields 56 | 57 | ``` 58 | def myfunc(): 59 | """ 60 | Function for computing fundamental arithmetics. 61 | """ 62 | return 1 + 1 63 | ``` 64 | in `myapp1.py` since `EXTENSIVE_DOC` is not defined, but 65 | 66 | ``` 67 | preprocess -DEXTENSIVE_DOC myapp1.p.py > myapp1.py 68 | ``` 69 | defines `EXTENSIVE_DOC` and the file `../myfunc_doc.txt` is copied 70 | by the `#include` statement, resulting in the output 71 | 72 | ``` 73 | def myfunc(): 74 | """ 75 | Function for computing fundamental arithmetics. 76 | This function takes 77 | the expression "1 + 1" 78 | and returns its result. 79 | """ 80 | return 1 + 1 81 | ``` 82 | if the file `../myfunc_doc.txt` contains the text 83 | 84 | ``` 85 | This function takes 86 | the expression "1 + 1" 87 | and returns its result. 88 | ``` 89 | 90 | Here is another example where a variable defined on the command 91 | line is a list: 92 | 93 | ``` 94 | preprocess -DFEATURES=macros,scc myapp2.p.py 95 | ``` 96 | The code to the left is transformed to the code to the right: 97 | 98 | ``` 99 | ... ... 100 | # #if "macros" in FEATURES 101 | def do_work_with_macros(): def do_work_with_macros(): 102 | pass pass 103 | # #else 104 | def do_work_without_macros(): 105 | pass 106 | # #endif 107 | ... ... 108 | ``` 109 | Or, with a JavaScript file, 110 | 111 | ``` 112 | ... ... 113 | // #if "macros" in FEATURES 114 | function do_work_with_macros() { function do_work_with_macros() { 115 | } } 116 | // #else 117 | function do_work_without_macros() { 118 | } 119 | // #endif 120 | ... ... 121 | ``` 122 | 123 | Preprocess has proved useful for 124 | build-time code differentiation in the 125 | [Komodo](http://www.activestate.com/Komodo) build system -- which 126 | includes source code in Python, JavaScript, XML, CSS, Perl, and C/C++. 127 | Preprocess is also a key tool in the [DocOnce](https://github.com/hplgit/doconce) documentation system. 128 | 129 | The `#if` expression (`"macros" in FEATURES` in the example) is Python 130 | code, so it has Python's full comparison richness. A number of 131 | preprocessor statements are implemented: 132 | 133 | ``` 134 | #define VAR [VALUE] 135 | #undef VAR 136 | #ifdef VAR 137 | #ifndef VAR 138 | #if EXPRESSION 139 | #elif EXPRESSION 140 | #else 141 | #endif 142 | #error ERROR_STRING 143 | #include "FILE" 144 | #include "FILE" fromto: from-regex@to-regex 145 | #include "FILE" fromto_: from-regex@to-regex 146 | ``` 147 | Run `pydoc preprocess` to see more explanation. 148 | 149 | As well, preprocess can do in-line substitution of defined variables. 150 | Although this is currently off by default because substitution will occur 151 | in program strings (and actually anywhere), which is not ideal. 152 | 153 | Please send any feedback to the original author 154 | [Trent Mick](mailto:trentm@google.com) or the current maintainer 155 | [Hans Petter Langtangen](mailto:hpl@simula.no). 156 | 157 | 158 | 159 | ## Install Notes 160 | 161 | Download the latest `preprocess` zip source package, unzip it, and run `python 162 | setup.py install`: 163 | ``` 164 | unzip master.zip 165 | cd preprocess 166 | python setup.py install 167 | ``` 168 | Note that `preprocess.py` version 1.2 depends on [python-future](http://python-future.org). If you have `pip` installed, `python-future` is easily installed by 169 | 170 | ``` 171 | pip install future 172 | ``` 173 | 174 | The `setup.py` command will install `preprocess.py` into your Python 175 | `site-packages` and also into your Python bin directory. If you can 176 | now run `preprocess` and get a response then you are good to go, 177 | otherwise read on. 178 | 179 | The *problem* is that the Python `bin` directory is not always on your 180 | `PATH` on some operating systems - notably Mac OS X. To finish the 181 | install on OS X either manually move 'preprocess' to somewhere on your 182 | `PATH`: 183 | ``` 184 | cp preprocess.py /usr/local/bin/preprocess 185 | ``` 186 | or create a symlink to it (try one of these depending on your Python 187 | version): 188 | ``` 189 | ln -s /System/Library/Frameworks/Python.framework/Versions/2.3/bin/preprocess /usr/local/bin/preprocess 190 | ln -s /Library/Frameworks/Python.framework/Versions/2.4/bin/preprocess /usr/local/bin/preprocess 191 | ``` 192 | (Note: You'll probably need to prefix those commands with `sudo` and 193 | the exact paths may differ on your system.) 194 | 195 | 196 | ## Getting Started 197 | 198 | Once you have the package installed, run `preprocess --help` for full usage 199 | information: 200 | ``` 201 | $ preprocess --help 202 | Preprocess a file. 203 | 204 | Command Line Usage: 205 | preprocess [...] 206 | 207 | Options: 208 | -h, --help Print this help and exit. 209 | -V, --version Print the version info and exit. 210 | -v, --verbose Give verbose output for errors. 211 | 212 | -o Write output to the given file instead of to stdout. 213 | -f, --force Overwrite given output file. (Otherwise an IOError 214 | will be raised if already exists. 215 | -D Define a variable for preprocessing. 216 | can simply be a variable name (in which case it 217 | will be true) or it can be of the form 218 | =. An attempt will be made to convert 219 | to an integer so "-D FOO=0" will create a 220 | false value. 221 | -I Add an directory to the include path for 222 | #include directives. 223 | 224 | -k, --keep-lines Emit empty lines for preprocessor statement 225 | lines and skipped output lines. This allows line 226 | numbers to stay constant. 227 | -s, --substitute Substitute defines into emitted lines. By 228 | default substitution is NOT done because it 229 | currently will substitute into program strings. 230 | 231 | Module Usage: 232 | from preprocess import preprocess 233 | preprocess(infile, outfile=sys.stdout, defines={}, force=0, 234 | keepLines=0, includePath=[], substitute=0) 235 | 236 | The can be marked up with special preprocessor statement lines 237 | of the form: 238 | 239 | where the are the native comment delimiters for 240 | that file type. 241 | 242 | 243 | Examples 244 | -------- 245 | 246 | HTML (*.htm, *.html) or XML (*.xml, *.kpf, *.xul) files: 247 | 248 | 249 | ... 250 | 251 | 252 | Python (*.py), Perl (*.pl), Tcl (*.tcl), Ruby (*.rb), Bash (*.sh), 253 | or make ([Mm]akefile*) files: 254 | 255 | # #if defined('FAV_COLOR') and FAV_COLOR == "blue" 256 | ... 257 | # #elif FAV_COLOR == "red" 258 | ... 259 | # #else 260 | ... 261 | # #endif 262 | 263 | C (*.c, *.h), C++ (*.cpp, *.cxx, *.cc, *.h, *.hpp, *.hxx, *.hh), 264 | Java (*.java), PHP (*.php) or C# (*.cs) files: 265 | 266 | // #define FAV_COLOR 'blue' 267 | ... 268 | /* #ifndef FAV_COLOR */ 269 | ... 270 | // #endif 271 | 272 | Fortran 77 (*.f) or 90/95 (*.f90) files: 273 | 274 | C #if COEFF == 'var' 275 | ... 276 | C #endif 277 | 278 | 279 | Preprocessor Syntax 280 | ------------------- 281 | 282 | - Valid statements: 283 | #define [] 284 | #undef 285 | #ifdef 286 | #ifndef 287 | #if 288 | #elif 289 | #else 290 | #endif 291 | #error 292 | #include "" 293 | where is any valid Python expression. 294 | - The expression after #if/elif may be a Python statement. It is an 295 | error to refer to a variable that has not been defined by a -D 296 | option or by an in-content #define. 297 | - Special built-in methods for expressions: 298 | defined(varName) Return true if given variable is defined. 299 | 300 | Tips 301 | ---- 302 | 303 | A suggested file naming convention is to let input files to 304 | preprocess be of the form .p. and direct the output 305 | of preprocess to ., e.g.: 306 | preprocess -o foo.py foo.p.py 307 | The advantage is that other tools (esp. editors) will still 308 | recognize the unpreprocessed file as the original language. 309 | ``` 310 | 311 | And, for module usage, read the preprocess.preprocess() docstring: 312 | ``` 313 | pydoc preprocess.preprocess 314 | ``` 315 | 316 | ## Change Log 317 | 318 | ### v1.2.2 319 | 320 | Added `--substitute_include` and `-i` options for substitutions of defined variables (`-DVAR=value`) in `#include` statements (makes it possible to have variables in included filenames: `# #include "myfile_VAR.do.txt"`). 321 | (By Hans Petter Langtangen.) 322 | 323 | ### v1.2.1 324 | 325 | Added support for including just parts of a file (from a regex to a regex). 326 | (By Hans Petter Langtangen.) 327 | 328 | ### v1.2.0 329 | 330 | Used python-future and the futurize script to port the code to a common 331 | Python 2/3 base. Preprocess now depends on python-future. 332 | Moved code base to GitHub. 333 | (By Hans Petter Langtangen.) 334 | 335 | Note that v1.1.0 has moved from code.google.com to github.com: https://github.com/trentm/preprocess, but this version is does not (yet) feature the enhancements in v1.2.0 and later. 336 | 337 | ### v1.1.0 338 | 339 | - Move to code.google.com/p/preprocess for code hosting. 340 | - Re-org directory structure to assist with deployment to pypi and 341 | better installation with `setup.py`. 342 | - Pulled the `content.types` file that assists with filetype 343 | determination into `preprocess.py`. This makes `preprocess.py` fully 344 | independent and also makes the `setup.py` script simpler. The 345 | `-c|--content-types-path` option can be used to specify 346 | addition content types information. 347 | 348 | ### v1.0.9 349 | 350 | - Fix the 'contentType' optional arg for `#include`'s. 351 | - Add cheap XML content sniffing. 352 | 353 | ### v1.0.8 354 | 355 | - Allow for JS and CSS-style comment delims in XML/HTML. Ideally this 356 | would deal with full lexing but that isn't going to happen soon. 357 | 358 | ### v1.0.7 359 | 360 | - Allow explicit specification of content type. 361 | 362 | ### v1.0.6 363 | 364 | - Add ability to include a filename mentioned in a define: `#include VAR`. 365 | (Note: v1.2.2 has `-i` option for substituting into *parts* of the filename.) 366 | 367 | 368 | ### v1.0.5 369 | 370 | - Make sure to use the *longest* define names first when doing 371 | substitutions. This ensure that substitution in this line: 372 | `FOO and BAR are FOOBAR` 373 | will do the right thing if there are "FOO" and "FOOBAR" defines. 374 | 375 | ### v1.0.4 376 | 377 | - Add WiX XML file extensions. 378 | - Add XSLT file extensions. 379 | 380 | ### v1.0.3 381 | 382 | - TeX support (from Hans Petter Langtangen) 383 | 384 | ### v1.0.2 385 | 386 | - Fix a bug with `-k|--keep-lines` and preprocessor some directives in 387 | ignored if blocks (undef, define, error, include): those lines were 388 | not kept. (bug noted by Eric Promislow) 389 | 390 | ### v1.0.1 391 | 392 | - Fix documentation error for `#define` statement. The correct syntax 393 | is `#define VAR [VALUE]` while the docs used to say 394 | `#define VAR[=VALUE]`. (from Hans Petter Langtangen) 395 | - Correct '! ...' comment-style for Fortran -- the '!' can be on any 396 | column in Fortran 90. (from Hans Petter Langtangen) 397 | - Return a non-zero exit code on error. 398 | 399 | ### v1.0.0 400 | 401 | - Update the test suite (it had been broken for quite a while) and add 402 | a Fortran test case. 403 | - Improve Fortran support to support any char in the first column to 404 | indicate a comment. (Idea from Hans Petter Langtangen) 405 | - Recognize '.f90' files as Fortran. (from Hans Petter Langtangen) 406 | - Add Java, C#, Shell script and PHP support. (from Hans Petter 407 | Langtangen) 408 | 409 | ### v0.9.2 410 | 411 | - Add Fortran support (from Hans Petter Langtangen) 412 | - Ensure content.types gets written to "bindir" next to preprocess.py 413 | there so it can be picked up (from Hans Petter Langtangen). 414 | 415 | ### v0.9.1 416 | 417 | - Fully read in the input file before processing. This allows 418 | preprocessing of a file onto itself. 419 | 420 | ### v0.9.0 421 | 422 | - Change version attributes and semantics. Before: had a `_version_` 423 | tuple. After: `__version__` is a string, `__version_info__` is a tuple. 424 | 425 | ### v0.8.1 426 | 427 | - Mentioned `#ifdef` and `#ifndef` in documentation (these have been there 428 | for a while). 429 | - Add preprocess.exe to source package (should fix installation on 430 | Windows). 431 | - Incorporate Komodo changes: 432 | - change 171050: add Ruby support 433 | - change 160914: Only attempt to convert define strings from the 434 | command-line to *int* instead of eval'ing as any Python 435 | expression: which could surprise with strings that work as 436 | floats. 437 | - change 67962: Fix `#include` directives in preprocessed files. 438 | 439 | ### v0.8.0 440 | 441 | - Move hosting to trentm.com. Improve the starter docs a little bit. 442 | 443 | ### 0.7.0: 444 | 445 | - Fix bug 1: Nested #if-blocks will not be properly handled. 446 | - Add 'Text' type for .txt files and default (with a warn) unknown 447 | filetypes to 'Text'. Text files are defined to use to `#...`-style 448 | comments to allow if/else/.../endif directives as in 449 | Perl/Python/Tcl files. 450 | 451 | ### 0.6.1: 452 | 453 | - Fix a bug where preprocessor statements were not ignored when not 454 | emitting. For example the following should _not_ cause an error: 455 | ``` 456 | # #if 0 457 | # #error womba womba womba 458 | # #endif 459 | ``` 460 | - Fix a bug where multiple uses of preprocess.preprocess() in the 461 | same interpreter would erroneously re-use the same list of 462 | __preprocessedFiles. This could cause false detection of recursive 463 | `#include`'s. 464 | - Fix `#include`, broken in 0.6.0. 465 | 466 | ### 0.6.0: 467 | 468 | - substitution: Variables can now replaced with their defined value 469 | in preprocessed file content. This is turned OFF by default 470 | because, IMO, substitution should not be done in program strings. 471 | I need to add lexing for all supported languages before I can do 472 | *that* properly. Substitution can be turned on with the 473 | `--substitute` command-line option or the `subst=1` module interface 474 | option. 475 | - Add support for preprocessing HTML files. 476 | 477 | ### 0.5.0: 478 | 479 | - Add `#error`, `#define`, `#undef`, `#ifdef` and `#ifndef` statements. 480 | - `#include` statement, `-I` command line option and `includePath` 481 | module interface option to specify an include path 482 | - Add `__FILE__` and `__LINE__` default defines. 483 | - More strict and more helpful error messages: 484 | - Lines of the form `#else ` and `#endif ` no longer 485 | match. 486 | - error messages for illegal `#if`-block constructs 487 | - error messages for use of `defined(BAR)` instead of 488 | `defined('BAR')` in expressions 489 | - New "keep lines" option to output blank lines for skipped content 490 | lines and preprocessor statement lines (to preserve line numbers 491 | in the processed file). 492 | 493 | ### 0.4.0: 494 | 495 | - Add `#elif` preprocessor statement. 496 | - Add `defined()` built-in, e.g. `#if defined('FOO')` 497 | 498 | ### 0.3.2: 499 | 500 | - Make `#if` expressions Python code. 501 | - Change "defines" attribute of `preprocess.preprocess()`. 502 | - Add `-f|--force` option to overwrite given output file. 503 | 504 | ### 0.2.0: 505 | 506 | - Add content types for C/C++. 507 | - Better module documentation. 508 | - You can define *false* vars on the command line now. 509 | - `python setup.py install` works. 510 | 511 | ### 0.1.0: 512 | 513 | - First release. 514 | 515 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | # High Priority 2 | 3 | - move BUGS.txt to issues on code site 4 | - bug: bin/preprocess.exe doesn't work now (works when installed on Windows 5 | with setup.py because it copies preprocess.py to Scripts as well (don't 6 | know why) -- at least for Python 2.5 7 | - add issues for everything important here 8 | - test/testsupport.py:64: DeprecationWarning: The popen2 module is deprecated. Use the subprocess module. 9 | import popen2 10 | - add issue from Mike Bauer: 11 | Yes, that is a problem that I occassionally ran into, but mostly just hacked around because I didn't think it would be that common. 12 | 13 | To "fix" this, I think I want to get strict about the preprocess.py directive by requiring no space between the second "#" and the directive name: 14 | 15 | # #if ... (this works) 16 | ## if ... (this is not treated as a preprocess.py directive) 17 | 18 | - add logging_patch_from_alfred.patch (Alfred Lorber) 19 | - use path_from_path_patterns recipe to provide a simple facility for 20 | recursively preprocessing a bunch of files, a dir, etc. As suggested by 21 | Hans. 22 | - convert to logging package 23 | 24 | 25 | # Medium Priority 26 | 27 | - test 'includePath' optional argument 28 | - test cases for the module interface (do I have any?) 29 | - test cases for -k|--keep-lines 30 | - patchtemplate functionality: look at how the preprocessor.pl below does 31 | this, perhaps this is an acceptible poorman's version 32 | (Somewhat have this, see --substitute added in v0.6.0.) 33 | - stealing from http://software.hixie.ch/utilities/unix/preprocessor/ 34 | - Should I really add #elifdef and #elifndef? 35 | - Would #filter/#endfilter be useful? 36 | - be more strict: 37 | - perhaps add -W option to allow levels of strictness 38 | - make #undef of undefined vars an error 39 | - perhaps add #pragma to enable turning on of options: e.g. 40 | # #pragma substitution 41 | 42 | 43 | # Lower Priority 44 | 45 | - preprocess 2: if can justify the change for: 46 | - string inter syntax that doesn't suffer from accidents (though that 47 | creates a prob for maintain syntax validity in the document) 48 | 49 | -------------------------------------------------------------------------------- /bin/preprocess: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | from os.path import join, dirname, exists 5 | 6 | # Use the local preprocess.py if we are in the source tree. 7 | source_tree_script = join(dirname(__file__), "..", "lib", "preprocess.py") 8 | if exists(source_tree_script): 9 | sys.path.insert(0, dirname(source_tree_script)) 10 | try: 11 | from preprocess import main 12 | finally: 13 | del sys.path[0] 14 | else: 15 | from preprocess import main 16 | 17 | if __name__ == "__main__": 18 | sys.exit( main(sys.argv) ) 19 | -------------------------------------------------------------------------------- /bin/preprocess.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hplgit/preprocess/d5ab9a0544cd2b15b0e8ae4f0b143b9047d1a54b/bin/preprocess.exe -------------------------------------------------------------------------------- /lib/preprocess.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2002-2008 ActiveState Software Inc. 3 | # License: MIT License (http://www.opensource.org/licenses/mit-license.php) 4 | 5 | """ 6 | Preprocess a file. 7 | 8 | Command Line Usage: 9 | preprocess [...] 10 | 11 | Options: 12 | -h, --help Print this help and exit. 13 | -V, --version Print the version info and exit. 14 | -v, --verbose Give verbose output for errors. 15 | 16 | -o Write output to the given file instead of to stdout. 17 | -f, --force Overwrite given output file. (Otherwise an IOError 18 | will be raised if already exists. 19 | -D Define a variable for preprocessing. 20 | can simply be a variable name (in which case it 21 | will be true) or it can be of the form 22 | =. An attempt will be made to convert 23 | to an integer so "-D FOO=0" will create a 24 | false value. 25 | -I Add an directory to the include path for 26 | #include directives. 27 | 28 | -k, --keep-lines Emit empty lines for preprocessor statement 29 | lines and skipped output lines. This allows line 30 | numbers to stay constant. 31 | -s, --substitute Substitute defines into emitted lines. By 32 | default substitution is NOT done because it 33 | currently will substitute into everything, e.g., 34 | program strings. 35 | -c, --content-types-path 36 | Specify a path to a content.types file to assist 37 | with filetype determination. See the 38 | `_gDefaultContentTypes` string in this file for 39 | details on its format. 40 | 41 | Module Usage: 42 | from preprocess import preprocess 43 | preprocess(infile, outfile=sys.stdout, defines={}, force=0, 44 | keepLines=0, includePath=[], substitute=0, 45 | contentType=None) 46 | 47 | The can be marked up with special preprocessor statement lines 48 | of the form: 49 | 50 | where the are the native comment delimiters for 51 | that file type. 52 | 53 | 54 | Examples 55 | -------- 56 | 57 | HTML (*.htm, *.html) or XML (*.xml, *.kpf, *.xul) files: 58 | 59 | 60 | ... 61 | 62 | 63 | Python (*.py), Perl (*.pl), Tcl (*.tcl), Ruby (*.rb), Bash (*.sh), 64 | or make ([Mm]akefile*) files: 65 | 66 | # #if defined('FAV_COLOR') and FAV_COLOR == "blue" 67 | ... 68 | # #elif FAV_COLOR == "red" 69 | ... 70 | # #else 71 | ... 72 | # #endif 73 | 74 | C (*.c, *.h), C++ (*.cpp, *.cxx, *.cc, *.h, *.hpp, *.hxx, *.hh), 75 | Java (*.java), PHP (*.php) or C# (*.cs) files: 76 | 77 | // #define FAV_COLOR 'blue' 78 | ... 79 | /* #ifndef FAV_COLOR */ 80 | ... 81 | // #endif 82 | 83 | Fortran 77 (*.f) or 90/95 (*.f90) files: 84 | 85 | C #if COEFF == 'var' 86 | ... 87 | C #endif 88 | 89 | And other languages. 90 | 91 | 92 | Preprocessor Syntax 93 | ------------------- 94 | 95 | - Valid statements: 96 | #define [] 97 | #undef 98 | #ifdef 99 | #ifndef 100 | #if 101 | #elif 102 | #else 103 | #endif 104 | #error 105 | #include "" 106 | #include "" fromto: from-regex@to-regex 107 | #include "" fromto_: from-regex@to-regex 108 | #include 109 | where is any valid Python expression. 110 | 111 | - About #include with from/to regex: fromto: means that the line 112 | with to-regex is not inclued, while fromto_: means that it is 113 | included. Note that from-regex and to-regex must be valid 114 | regular expressions. The to-regex can be left out (but the 115 | @ delimiter must be kept), meaning that everything from from-regex 116 | to the end of the file is included. 117 | 118 | - The expression after #if/elif may be a Python statement. It is an 119 | error to refer to a variable that has not been defined by a -D 120 | option or by an in-content #define. 121 | 122 | - Special built-in methods for expressions: 123 | defined(varName) Return true if given variable is defined. 124 | 125 | 126 | Tips 127 | ---- 128 | 129 | A suggested file naming convention is to let input files to 130 | preprocess be of the form .p. and direct the output 131 | of preprocess to ., e.g.: 132 | preprocess -o foo.py foo.p.py 133 | The advantage is that other tools (esp. editors) will still 134 | recognize the unpreprocessed file as the original language. 135 | """ 136 | from past.builtins import cmp 137 | from builtins import map 138 | from builtins import str 139 | from builtins import range 140 | from builtins import object 141 | 142 | __version_info__ = (1, 2, 2) 143 | __version__ = '.'.join(map(str, __version_info__)) 144 | 145 | import os 146 | import sys 147 | import getopt 148 | import types 149 | import re 150 | import pprint 151 | 152 | 153 | 154 | #---- exceptions 155 | 156 | class PreprocessError(Exception): 157 | def __init__(self, errmsg, file=None, lineno=None, line=None): 158 | self.errmsg = str(errmsg) 159 | self.file = file 160 | self.lineno = lineno 161 | self.line = line 162 | Exception.__init__(self, errmsg, file, lineno, line) 163 | def __str__(self): 164 | s = "" 165 | if self.file is not None: 166 | s += self.file + ":" 167 | if self.lineno is not None: 168 | s += str(self.lineno) + ":" 169 | if self.file is not None or self.lineno is not None: 170 | s += " " 171 | s += self.errmsg 172 | #if self.line is not None: 173 | # s += ": " + self.line 174 | return s 175 | 176 | 177 | 178 | #---- global data 179 | 180 | # Comment delimiter info. 181 | # A mapping of content type to a list of 2-tuples defining the line 182 | # prefix and suffix for a comment. Each prefix or suffix can either 183 | # be a string (in which case it is transformed into a pattern allowing 184 | # whitespace on either side) or a compiled regex. 185 | _commentGroups = { 186 | "Python": [ ('#', '') ], 187 | "Perl": [ ('#', '') ], 188 | "PHP": [ ('/*', '*/'), ('//', ''), ('#', '') ], 189 | "Ruby": [ ('#', '') ], 190 | "Tcl": [ ('#', '') ], 191 | "Shell": [ ('#', '') ], 192 | # Allowing for CSS and JavaScript comments in XML/HTML. 193 | "XML": [ (''), ('/*', '*/'), ('//', '') ], 194 | "HTML": [ (''), ('/*', '*/'), ('//', '') ], 195 | "Makefile": [ ('#', '') ], 196 | "JavaScript": [ ('/*', '*/'), ('//', '') ], 197 | "CSS": [ ('/*', '*/') ], 198 | "C": [ ('/*', '*/') ], 199 | "C++": [ ('/*', '*/'), ('//', '') ], 200 | "Java": [ ('/*', '*/'), ('//', '') ], 201 | "C#": [ ('/*', '*/'), ('//', '') ], 202 | "IDL": [ ('/*', '*/'), ('//', '') ], 203 | "Text": [ ('#', '') ], 204 | "Fortran": [ (re.compile(r'^[a-zA-Z*$]\s*'), ''), ('!', '') ], 205 | "TeX": [ ('%', '') ], 206 | } 207 | 208 | 209 | 210 | #---- internal logging facility 211 | 212 | class _Logger(object): 213 | DEBUG, INFO, WARN, ERROR, CRITICAL = range(5) 214 | def __init__(self, name, level=None, streamOrFileName=sys.stderr): 215 | self._name = name 216 | if level is None: 217 | self.level = self.WARN 218 | else: 219 | self.level = level 220 | if type(streamOrFileName) == bytes: 221 | self.stream = open(streamOrFileName, 'w') 222 | self._opennedStream = 1 223 | else: 224 | self.stream = streamOrFileName 225 | self._opennedStream = 0 226 | def __del__(self): 227 | if self._opennedStream: 228 | self.stream.close() 229 | def getLevel(self): 230 | return self.level 231 | def setLevel(self, level): 232 | self.level = level 233 | def _getLevelName(self, level): 234 | levelNameMap = { 235 | self.DEBUG: "DEBUG", 236 | self.INFO: "INFO", 237 | self.WARN: "WARN", 238 | self.ERROR: "ERROR", 239 | self.CRITICAL: "CRITICAL", 240 | } 241 | return levelNameMap[level] 242 | def isEnabled(self, level): 243 | return level >= self.level 244 | def isDebugEnabled(self): return self.isEnabled(self.DEBUG) 245 | def isInfoEnabled(self): return self.isEnabled(self.INFO) 246 | def isWarnEnabled(self): return self.isEnabled(self.WARN) 247 | def isErrorEnabled(self): return self.isEnabled(self.ERROR) 248 | def isFatalEnabled(self): return self.isEnabled(self.FATAL) 249 | def log(self, level, msg, *args): 250 | if level < self.level: 251 | return 252 | message = "%s: %s: " % (self._name, self._getLevelName(level).lower()) 253 | message = message + (msg % args) + "\n" 254 | self.stream.write(message) 255 | self.stream.flush() 256 | def debug(self, msg, *args): 257 | self.log(self.DEBUG, msg, *args) 258 | def info(self, msg, *args): 259 | self.log(self.INFO, msg, *args) 260 | def warn(self, msg, *args): 261 | self.log(self.WARN, msg, *args) 262 | def error(self, msg, *args): 263 | self.log(self.ERROR, msg, *args) 264 | def fatal(self, msg, *args): 265 | self.log(self.CRITICAL, msg, *args) 266 | 267 | log = _Logger("preprocess", _Logger.WARN) 268 | #log = _Logger("preprocess", _Logger.DEBUG) 269 | 270 | 271 | 272 | #---- internal support stuff 273 | 274 | def _evaluate(expr, defines): 275 | """Evaluate the given expression string with the given context. 276 | 277 | WARNING: This runs eval() on a user string. This is unsafe. 278 | """ 279 | #interpolated = _interpolate(s, defines) 280 | try: 281 | rv = eval(expr, {'defined':lambda v: v in defines}, defines) 282 | except Exception as ex: 283 | msg = str(ex) 284 | if msg.startswith("name '") and msg.endswith("' is not defined"): 285 | # A common error (at least this is presumed:) is to have 286 | # defined(FOO) instead of defined('FOO') 287 | # We should give a little as to what might be wrong. 288 | # msg == "name 'FOO' is not defined" --> varName == "FOO" 289 | varName = msg[len("name '"):-len("' is not defined")] 290 | if expr.find("defined(%s)" % varName) != -1: 291 | # "defined(FOO)" in expr instead of "defined('FOO')" 292 | msg += " (perhaps you want \"defined('%s')\" instead of "\ 293 | "\"defined(%s)\")" % (varName, varName) 294 | elif msg.startswith("invalid syntax"): 295 | msg = "invalid syntax: '%s'" % expr 296 | raise PreprocessError(msg, defines['__FILE__'], defines['__LINE__']) 297 | log.debug("evaluate %r -> %s (defines=%r)", expr, rv, defines) 298 | return rv 299 | 300 | 301 | #---- module API 302 | 303 | def preprocess(infile, outfile=sys.stdout, defines={}, 304 | force=0, keepLines=0, includePath=[], 305 | substitute=0, include_substitute=0, 306 | contentType=None, contentTypesRegistry=None, 307 | __preprocessedFiles=None): 308 | """Preprocess the given file. 309 | 310 | "infile" is the input path, either a string (the whole file), 311 | or a tuple (filename, from_, to_, last_to_line) that defines 312 | the lines in filename from from_ to to_, but not include to_ 313 | if last_to_line is False. from_ and to_ are treated as 314 | regular expressions. 315 | "outfile" is the output path or stream (default is sys.stdout). 316 | "defines" is a dictionary of defined variables that will be 317 | understood in preprocessor statements. Keys must be strings and, 318 | currently, only the truth value of any key's value matters. 319 | "force" will overwrite the given outfile if it already exists. Otherwise 320 | an IOError will be raise if the outfile already exists. 321 | "keepLines" will cause blank lines to be emitted for preprocessor lines 322 | and content lines that would otherwise be skipped. 323 | "includePath" is a list of directories to search for given #include 324 | directives. The directory of the file being processed is presumed. 325 | "substitute", if true, will allow substitution of defines into emitted 326 | lines. (NOTE: This substitution will happen to anything, e.g., 327 | within program strings, and this may not be what you expect!) 328 | "include_substitute", if true, will allow substitution of defines into 329 | #include statements. 330 | "contentType" can be used to specify the content type of the input 331 | file. It not given, it will be guessed. 332 | "contentTypesRegistry" is an instance of ContentTypesRegistry. If not specified 333 | a default registry will be created. 334 | "__preprocessedFiles" (for internal use only) is used to ensure files 335 | are not recusively preprocessed. 336 | 337 | Returns the modified dictionary of defines or raises PreprocessError if 338 | there was some problem. 339 | """ 340 | if isinstance(infile, tuple): 341 | infile, from_, to_, last_to_line = infile 342 | else: 343 | from_, to_, last_to_line = None, None, None 344 | 345 | if __preprocessedFiles is None: 346 | __preprocessedFiles = [] 347 | log.info("preprocess(infile=%r, outfile=%r, defines=%r, force=%r, "\ 348 | "keepLines=%r, includePath=%r, contentType=%r, "\ 349 | "__preprocessedFiles=%r)", infile, outfile, defines, force, 350 | keepLines, includePath, contentType, __preprocessedFiles) 351 | absInfile = os.path.normpath(os.path.abspath(infile)) 352 | if absInfile in __preprocessedFiles: 353 | pass 354 | # Allow the same file to be included multiple times, it is not 355 | # necessarily recursive include! 356 | #raise PreprocessError("detected recursive #include of '%s'"\ 357 | # % infile) 358 | __preprocessedFiles.append(os.path.abspath(infile)) 359 | 360 | # Determine the content type and comment info for the input file. 361 | if contentType is None: 362 | registry = contentTypesRegistry or getDefaultContentTypesRegistry() 363 | contentType = registry.getContentType(infile) 364 | if contentType is None: 365 | contentType = "Text" 366 | log.warn("defaulting content type for '%s' to '%s'", 367 | infile, contentType) 368 | try: 369 | cgs = _commentGroups[contentType] 370 | except KeyError: 371 | raise PreprocessError("don't know comment delimiters for content "\ 372 | "type '%s' (file '%s')"\ 373 | % (contentType, infile)) 374 | 375 | # Generate statement parsing regexes. Basic format: 376 | # 377 | # Examples: 378 | # 379 | # ... 380 | # 381 | # 382 | # # #if BAR 383 | # ... 384 | # # #else 385 | # ... 386 | # # #endif 387 | stmts = ['#\s*(?Pif|elif|ifdef|ifndef)\s+(?P.*?)', 388 | '#\s*(?Pelse|endif)', 389 | '#\s*(?Perror)\s+(?P.*?)', 390 | '#\s*(?Pdefine)\s+(?P[^\s]*?)(\s+(?P.+?))?', 391 | '#\s*(?Pundef)\s+(?P[^\s]*?)', 392 | '#\s*(?Pinclude) +"(?P.*?)" +(?Pfromto_?):\s+(?P.+\n)', 393 | '#\s*(?Pinclude)\s+"(?P.*?)"', 394 | r'#\s*(?Pinclude)\s+(?P[^\s]+?)', 395 | ] 396 | patterns = [] 397 | for stmt in stmts: 398 | # The comment group prefix and suffix can either be just a 399 | # string or a compiled regex. 400 | for cprefix, csuffix in cgs: 401 | if hasattr(cprefix, "pattern"): 402 | pattern = cprefix.pattern 403 | else: 404 | pattern = r"^\s*%s\s*" % re.escape(cprefix) 405 | pattern += stmt 406 | if hasattr(csuffix, "pattern"): 407 | pattern += csuffix.pattern 408 | else: 409 | pattern += r"\s*%s\s*$" % re.escape(csuffix) 410 | patterns.append(pattern) 411 | stmtRes = [re.compile(p) for p in patterns] 412 | 413 | # Process the input file. 414 | # (Would be helpful if I knew anything about lexing and parsing 415 | # simple grammars.) 416 | fin = open(infile, 'r') 417 | lines = fin.readlines() 418 | if from_ is not None: 419 | from_line = -1 420 | to_line = -1 421 | for i in range(len(lines)): 422 | if re.search(from_, lines[i]): 423 | from_line = i 424 | if to_ != '' and from_line != -1: 425 | # not to end of file and has found from_ line 426 | if re.search(to_, lines[i]): 427 | to_line = i if last_to_line else i-1 428 | break 429 | if to_line == -1: 430 | lines = lines[from_line:] 431 | else: 432 | lines = lines[from_line:to_line+1] 433 | 434 | fin.close() 435 | if isinstance(outfile, (str, bytes)): 436 | if force and os.path.exists(outfile): 437 | os.chmod(outfile, 0o777) 438 | os.remove(outfile) 439 | fout = open(outfile, 'w') 440 | else: 441 | fout = outfile 442 | 443 | defines['__FILE__'] = infile 444 | SKIP, EMIT = range(2) # states 445 | states = [(EMIT, # a state is (, 446 | 0, # , 447 | 0)] # ) 448 | lineNum = 0 449 | for line in lines: 450 | lineNum += 1 451 | log.debug("line %d: %r", lineNum, line) 452 | defines['__LINE__'] = lineNum 453 | 454 | # Is this line a preprocessor stmt line? 455 | #XXX Could probably speed this up by optimizing common case of 456 | # line NOT being a preprocessor stmt line. 457 | for stmtRe in stmtRes: 458 | match = stmtRe.match(line) 459 | if match: 460 | break 461 | else: 462 | match = None 463 | 464 | if match: 465 | op = match.group("op") 466 | log.debug("%r stmt (states: %r)", op, states) 467 | if op == "define": 468 | if not (states and states[-1][0] == SKIP): 469 | var, val = match.group("var", "val") 470 | if val is None: 471 | val = None 472 | else: 473 | try: 474 | val = eval(val, {}, {}) 475 | except: 476 | pass 477 | defines[var] = val 478 | elif op == "undef": 479 | if not (states and states[-1][0] == SKIP): 480 | var = match.group("var") 481 | try: 482 | del defines[var] 483 | except KeyError: 484 | pass 485 | elif op == "include": 486 | if not (states and states[-1][0] == SKIP): 487 | if "var" in match.groupdict(): 488 | # This is the second include form: #include VAR 489 | var = match.group("var") 490 | f = defines[var] 491 | else: 492 | # This is the first include form: #include "path" 493 | f = match.group("fname") 494 | fromto = part = None 495 | if "fromto" in match.groupdict(): 496 | fromto, part = match.group("fromto", "part") 497 | if fromto == 'fromto_': 498 | last_to_line = True 499 | else: 500 | last_to_line = False 501 | try: 502 | from_, to_ = part.split('@') 503 | except ValueError: 504 | raise PreprocessError('Wrong syntax, need #include %s: from-regex@to-regex' % fromto) 505 | 506 | 507 | # HPL modification: 508 | # Perform substitutions here such that #include statements 509 | # can use defines. 510 | if include_substitute: 511 | for name in reversed(sorted(defines, key=len)): 512 | value = defines[name] 513 | f = f.replace(name, str(value)) 514 | 515 | for d in [os.path.dirname(infile)] + includePath: 516 | fname = os.path.normpath(os.path.join(d, f)) 517 | if os.path.exists(fname): 518 | break 519 | else: 520 | raise PreprocessError("could not find #include'd file "\ 521 | "\"%s\" on include path: %r"\ 522 | % (f, includePath)) 523 | if fromto is not None: 524 | fname = (fname, from_, to_, last_to_line) 525 | defines = preprocess(fname, fout, defines, force, 526 | keepLines, includePath, 527 | substitute, include_substitute, 528 | contentTypesRegistry=contentTypesRegistry, 529 | __preprocessedFiles=__preprocessedFiles) 530 | elif op in ("if", "ifdef", "ifndef"): 531 | if op == "if": 532 | expr = match.group("expr") 533 | elif op == "ifdef": 534 | expr = "defined('%s')" % match.group("expr") 535 | elif op == "ifndef": 536 | expr = "not defined('%s')" % match.group("expr") 537 | try: 538 | if states and states[-1][0] == SKIP: 539 | # Were are nested in a SKIP-portion of an if-block. 540 | states.append((SKIP, 0, 0)) 541 | elif _evaluate(expr, defines): 542 | states.append((EMIT, 1, 0)) 543 | else: 544 | states.append((SKIP, 0, 0)) 545 | except KeyError: 546 | raise PreprocessError("use of undefined variable in "\ 547 | "#%s stmt" % op, defines['__FILE__'], 548 | defines['__LINE__'], line) 549 | elif op == "elif": 550 | expr = match.group("expr") 551 | try: 552 | if states[-1][2]: # already had #else in this if-block 553 | raise PreprocessError("illegal #elif after #else in "\ 554 | "same #if block", defines['__FILE__'], 555 | defines['__LINE__'], line) 556 | elif states[-1][1]: # if have emitted in this if-block 557 | states[-1] = (SKIP, 1, 0) 558 | elif states[:-1] and states[-2][0] == SKIP: 559 | # Were are nested in a SKIP-portion of an if-block. 560 | states[-1] = (SKIP, 0, 0) 561 | elif _evaluate(expr, defines): 562 | states[-1] = (EMIT, 1, 0) 563 | else: 564 | states[-1] = (SKIP, 0, 0) 565 | except IndexError: 566 | raise PreprocessError("#elif stmt without leading #if "\ 567 | "stmt", defines['__FILE__'], 568 | defines['__LINE__'], line) 569 | elif op == "else": 570 | try: 571 | if states[-1][2]: # already had #else in this if-block 572 | raise PreprocessError("illegal #else after #else in "\ 573 | "same #if block", defines['__FILE__'], 574 | defines['__LINE__'], line) 575 | elif states[-1][1]: # if have emitted in this if-block 576 | states[-1] = (SKIP, 1, 1) 577 | elif states[:-1] and states[-2][0] == SKIP: 578 | # Were are nested in a SKIP-portion of an if-block. 579 | states[-1] = (SKIP, 0, 1) 580 | else: 581 | states[-1] = (EMIT, 1, 1) 582 | except IndexError: 583 | raise PreprocessError("#else stmt without leading #if "\ 584 | "stmt", defines['__FILE__'], 585 | defines['__LINE__'], line) 586 | elif op == "endif": 587 | try: 588 | states.pop() 589 | except IndexError: 590 | raise PreprocessError("#endif stmt without leading #if"\ 591 | "stmt", defines['__FILE__'], 592 | defines['__LINE__'], line) 593 | elif op == "error": 594 | if not (states and states[-1][0] == SKIP): 595 | error = match.group("error") 596 | raise PreprocessError("#error: "+error, defines['__FILE__'], 597 | defines['__LINE__'], line) 598 | log.debug("states: %r", states) 599 | if keepLines: 600 | fout.write("\n") 601 | else: 602 | try: 603 | if states[-1][0] == EMIT: 604 | log.debug("emit line (%s)" % states[-1][1]) 605 | # Substitute all defines into line. 606 | # XXX Should avoid recursive substitutions. But that 607 | # would be a pain right now. 608 | sline = line 609 | if substitute: 610 | for name in reversed(sorted(defines, key=len)): 611 | value = defines[name] 612 | sline = sline.replace(name, str(value)) 613 | fout.write(sline) 614 | elif keepLines: 615 | log.debug("keep blank line (%s)" % states[-1][1]) 616 | fout.write("\n") 617 | else: 618 | log.debug("skip line (%s)" % states[-1][1]) 619 | except IndexError: 620 | raise PreprocessError("superfluous #endif before this line", 621 | defines['__FILE__'], 622 | defines['__LINE__']) 623 | if len(states) > 1: 624 | raise PreprocessError("unterminated #if block", defines['__FILE__'], 625 | defines['__LINE__']) 626 | elif len(states) < 1: 627 | raise PreprocessError("superfluous #endif on or before this line", 628 | defines['__FILE__'], defines['__LINE__']) 629 | 630 | if fout != outfile: 631 | fout.close() 632 | 633 | return defines 634 | 635 | 636 | #---- content-type handling 637 | 638 | _gDefaultContentTypes = """ 639 | # Default file types understood by "preprocess.py". 640 | # 641 | # Format is an extension of 'mime.types' file syntax. 642 | # - '#' indicates a comment to the end of the line. 643 | # - a line is: 644 | # [...] 645 | # where, 646 | # 's are equivalent in spirit to the names used in the Windows 647 | # registry in HKCR, but some of those names suck or are inconsistent; 648 | # and 649 | # is a suffix (pattern starts with a '.'), a regular expression 650 | # (pattern is enclosed in '/' characters), a full filename (anything 651 | # else). 652 | # 653 | # Notes on case-sensitivity: 654 | # 655 | # A suffix pattern is case-insensitive on Windows and case-sensitive 656 | # elsewhere. A filename pattern is case-sensitive everywhere. A regex 657 | # pattern's case-sensitivity is defined by the regex. This means it is by 658 | # default case-sensitive, but this can be changed using Python's inline 659 | # regex option syntax. E.g.: 660 | # Makefile /^(?i)makefile.*$/ # case-INsensitive regex 661 | 662 | Python .py 663 | Python .pyw 664 | Perl .pl 665 | Ruby .rb 666 | Tcl .tcl 667 | XML .xml 668 | XML .kpf 669 | XML .xul 670 | XML .rdf 671 | XML .xslt 672 | XML .xsl 673 | XML .wxs 674 | XML .wxi 675 | HTML .htm 676 | HTML .html 677 | XML .xhtml 678 | Makefile /^[Mm]akefile.*$/ 679 | PHP .php 680 | JavaScript .js 681 | CSS .css 682 | C++ .c # C++ because then we can use //-style comments 683 | C++ .cpp 684 | C++ .cxx 685 | C++ .cc 686 | C++ .h 687 | C++ .hpp 688 | C++ .hxx 689 | C++ .hh 690 | IDL .idl 691 | Text .txt 692 | Fortran .f 693 | Fortran .f90 694 | Shell .sh 695 | Shell .csh 696 | Shell .ksh 697 | Shell .zsh 698 | Java .java 699 | C# .cs 700 | TeX .tex 701 | 702 | # Some Komodo-specific file extensions 703 | Python .ksf # Fonts & Colors scheme files 704 | Text .kkf # Keybinding schemes files 705 | """ 706 | 707 | class ContentTypesRegistry(object): 708 | """A class that handles determining the filetype of a given path. 709 | 710 | Usage: 711 | >>> registry = ContentTypesRegistry() 712 | >>> registry.getContentType("foo.py") 713 | "Python" 714 | """ 715 | 716 | def __init__(self, contentTypesPaths=None): 717 | self.contentTypesPaths = contentTypesPaths 718 | self._load() 719 | 720 | def _load(self): 721 | from os.path import dirname, join, exists 722 | 723 | self.suffixMap = {} 724 | self.regexMap = {} 725 | self.filenameMap = {} 726 | 727 | self._loadContentType(_gDefaultContentTypes) 728 | localContentTypesPath = join(dirname(__file__), "content.types") 729 | if exists(localContentTypesPath): 730 | log.debug("load content types file: `%r'" % localContentTypesPath) 731 | self._loadContentType(open(localContentTypesPath, 'r').read()) 732 | for path in (self.contentTypesPaths or []): 733 | log.debug("load content types file: `%r'" % path) 734 | self._loadContentType(open(path, 'r').read()) 735 | 736 | def _loadContentType(self, content, path=None): 737 | """Return the registry for the given content.types file. 738 | 739 | The registry is three mappings: 740 | -> 741 | -> 742 | -> 743 | """ 744 | for line in content.splitlines(0): 745 | words = line.strip().split() 746 | for i in range(len(words)): 747 | if words[i][0] == '#': 748 | del words[i:] 749 | break 750 | if not words: continue 751 | contentType, patterns = words[0], words[1:] 752 | if not patterns: 753 | if line[-1] == '\n': line = line[:-1] 754 | raise PreprocessError("bogus content.types line, there must "\ 755 | "be one or more patterns: '%s'" % line) 756 | for pattern in patterns: 757 | if pattern.startswith('.'): 758 | if sys.platform.startswith("win"): 759 | # Suffix patterns are case-insensitive on Windows. 760 | pattern = pattern.lower() 761 | self.suffixMap[pattern] = contentType 762 | elif pattern.startswith('/') and pattern.endswith('/'): 763 | self.regexMap[re.compile(pattern[1:-1])] = contentType 764 | else: 765 | self.filenameMap[pattern] = contentType 766 | 767 | def getContentType(self, path): 768 | """Return a content type for the given path. 769 | 770 | @param path {str} The path of file for which to guess the 771 | content type. 772 | @returns {str|None} Returns None if could not determine the 773 | content type. 774 | """ 775 | basename = os.path.basename(path) 776 | contentType = None 777 | # Try to determine from the path. 778 | if not contentType and basename in self.filenameMap: 779 | contentType = self.filenameMap[basename] 780 | log.debug("Content type of '%s' is '%s' (determined from full "\ 781 | "path).", path, contentType) 782 | # Try to determine from the suffix. 783 | if not contentType and '.' in basename: 784 | suffix = "." + basename.split(".")[-1] 785 | if sys.platform.startswith("win"): 786 | # Suffix patterns are case-insensitive on Windows. 787 | suffix = suffix.lower() 788 | if suffix in self.suffixMap: 789 | contentType = self.suffixMap[suffix] 790 | log.debug("Content type of '%s' is '%s' (determined from "\ 791 | "suffix '%s').", path, contentType, suffix) 792 | # Try to determine from the registered set of regex patterns. 793 | if not contentType: 794 | for regex, ctype in self.regexMap.items(): 795 | if regex.search(basename): 796 | contentType = ctype 797 | log.debug("Content type of '%s' is '%s' (matches regex '%s')", 798 | path, contentType, regex.pattern) 799 | break 800 | # Try to determine from the file contents. 801 | content = open(path, 'rb').read() 802 | if content.startswith(b" foo.exe 2 | 3 | APPNAME=preprocess 4 | 5 | # for release: 6 | CFLAGS=-D_CONSOLE -D_MBCS -DWIN32 -W3 -Ox -DNDEBUG -D_NDEBUG -MD 7 | LDFLAGS=/subsystem:console kernel32.lib user32.lib gdi32.lib advapi32.lib shlwapi.lib 8 | # for debug: 9 | # CFLAGS = -D_CONSOLE -D_MBCS /DWIN32 /Zi /Od /DDEBUG /D_DEBUG /MDd 10 | # LDFLAGS += /DEBUG 11 | 12 | $(APPNAME).exe: launcher.cpp 13 | cl -nologo $(CFLAGS) -c launcher.cpp 14 | link -nologo $(LDFLAGS) launcher.obj -out:$(APPNAME).exe 15 | 16 | clean: 17 | if exist launcher.obj; del launcher.obj 18 | if exist $(APPNAME).exe; del $(APPNAME).exe 19 | -------------------------------------------------------------------------------- /src/exe/launcher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2002 Trent Mick 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a 5 | * copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included 13 | * in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 20 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | /* Console launch executable. 25 | * 26 | * This program exists solely to launch: 27 | * python /.py 28 | * on Windows. ".py" must be in the same directory. 29 | * 30 | * Rationale: 31 | * - On some Windows flavours .py *can* be put on the PATHEXT to be 32 | * able to find ".py" if it is on the PATH. This is fine 33 | * until you need shell redirection to work. It does NOT for 34 | * extensions to PATHEXT. Redirection *does* work for "python 35 | *