Created
June 21, 2015 10:52
-
-
Save slowli/639031fb5fee06d4c742 to your computer and use it in GitHub Desktop.
Embedding declarations in runnable Python scripts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
Example of an embeddable module. | |
''' | |
#region get_input | |
def get_input(): | |
''' Scans and returns input until EOF symbol. ''' | |
input_text = '' | |
try: | |
while True: input_text += raw_input() + '\n' | |
except EOFError: | |
pass | |
return input_text[:-1] | |
#endregion | |
#region cached | |
def cached(fn): | |
''' Decorator for functions that allows caching their values. ''' | |
_cache = dict() | |
def cached_fn(*args): | |
mapped_args = args | |
if not mapped_args in _cache: | |
_cache[mapped_args] = fn(*args) | |
return _cache[mapped_args] | |
return cached_fn | |
#endregion | |
#region cat depends on cached | |
@cached | |
def cat(n): | |
''' Calculates Catalan numbers using the recurrent formula. ''' | |
if n == 0: return 1 | |
sum = 0 | |
for i in range(n): | |
sum += cat(i) * cat(n - 1 - i) | |
return sum | |
#endregion |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
''' | |
A simple util to automatically embed declarations from modules into | |
runnable scripts. | |
Can be used, e.g., in competetive programming, where it is usually required | |
to submit a solution as a single file. | |
''' | |
import sys, os, re | |
def usage(): | |
print '''\ | |
Usage: {0} [-n module.name] path/to/module.py path/to/script.py | |
{0} | |
Embeds declarations from a module into a runnable script. | |
The modified script is output to the standard output. | |
If a module name is not specified, it is assumed the filename | |
of the module without the `.py' extension. | |
Called without arguments, the command prints this help message. | |
Preparing modules | |
Declarations in the module file must be marked with | |
#region / #endregion comments, e.g. | |
#region foo | |
def foo(): | |
pass | |
#endregion | |
Regions may be dependent on other regions, e.g. | |
#region bar depends on foo | |
def bar(): | |
foo() | |
#endregion | |
Imports in the runnable script must be in the form | |
from <module> import <declaration1>, <declaration2>, ... | |
Example: | |
{0} -n common test/common.py test/1.py | |
'''.format(sys.argv[0]) | |
def wrong_usage(): | |
print '''\ | |
Invalid command usage. Type `{0}' for help. | |
'''.format(sys.argv[0]) | |
class Module(dict): | |
# RegExp for region starting comment | |
# Groups: | |
# 1 - name of the region | |
# 3 - dependencies | |
_R_REGEX = re.compile('^#region (\w+)?(\s+depends on\s+(\w+(\s*,\s*\w+)*))?') | |
# RegExp for region ending comment | |
_E_REGEX = re.compile('^#endregion') | |
# RegExp for splitting comma-separated lists | |
_COMMA = re.compile('\s*,\s*') | |
# Template for RegExp of the import declaration | |
_I_REGEX_T = '^from {0} import (\w+(\s*,\s*\w+)*)' | |
def __init__(self, lines, name): | |
region_name = '' | |
region = '' | |
self.dependencies = dict() | |
self.name = name | |
for line in lines: | |
match = self._R_REGEX.match(line) | |
if match: | |
region_name = match.group(1) | |
region = '' | |
dependencies = match.group(3) | |
if dependencies: | |
dependencies = self._COMMA.split(dependencies) | |
self.dependencies[region_name] = dependencies | |
elif self._E_REGEX.match(line): | |
self[region_name] = region[:-1] | |
region = '' | |
else: | |
region += line | |
def inline_imports(self, imports, start_msg='', end_msg=''): | |
''' Creates the embedded code for the given imported declarations. ''' | |
all_imports = imports | |
ptr = len(all_imports) - 1 | |
while ptr >= 0: | |
im = all_imports[ptr] | |
if self.dependencies[im] is not None: | |
for dp in self.dependencies[im]: | |
if dp not in all_imports: | |
all_imports[0:0] = [ dp ] | |
ptr -= 1 | |
code = '\n\n'.join([self[im] for im in all_imports]) | |
code = start_msg + '\n' + code + '\n' + end_msg | |
return code | |
def embed(self, lines, start_msg='', end_msg=''): | |
''' Replaces import declarations from this module in the script | |
with fragments of the module. ''' | |
import_re = re.compile(self._I_REGEX_T.format(self.name)) | |
code = '' | |
for line in lines: | |
match = import_re.match(line) | |
if match: | |
imports = self._COMMA.split(match.group(1)) | |
code += self.inline_imports(imports) | |
else: | |
code += line | |
return code | |
if __name__ == '__main__': | |
if len(sys.argv) == 1: | |
usage(); sys.exit(0) | |
module_name = None | |
if sys.argv[1] == '-n': | |
if len(sys.argv) < 3: | |
wrong_usage(); sys.exit(2) | |
module_name = sys.argv[2] | |
del sys.argv[1:3] | |
if module_name is None: | |
module_name = os.path.basename(sys.argv[1]) | |
if module_name.endswith('.py'): | |
module_name = module_name[:-3] | |
if len(sys.argv) < 3: | |
wrong_usage(); sys.exit(2) | |
with open(sys.argv[1], 'r') as fh: | |
module = Module(fh, name=module_name) | |
with open(sys.argv[2], 'r') as fh: | |
print module.embed(fh) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
''' | |
Runnable script illustrating automatical code embedding. | |
The script calculates Catalan numbers | |
(https://en.wikipedia.org/wiki/Catalan_number). | |
Indices of numbers are read from the standard input, one index at a line. | |
To test code embedding, use the command | |
python embed.py common.py example.py > example_e.py | |
and then run example_e.py. | |
''' | |
import unittest, sys | |
from common import get_input, cat | |
if __name__ == '__main__': | |
for line in get_input().splitlines(): | |
i = int(line) | |
print 'Cat({0}) = {1}'.format(i, cat(i)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment