Skip to content

Instantly share code, notes, and snippets.

@slowli
Created June 21, 2015 10:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slowli/639031fb5fee06d4c742 to your computer and use it in GitHub Desktop.
Save slowli/639031fb5fee06d4c742 to your computer and use it in GitHub Desktop.
Embedding declarations in runnable Python scripts
'''
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
#!/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)
#!/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