From e67a2b502876b9604a12799df9537cbad82e647f Mon Sep 17 00:00:00 2001 From: nickpandolfi Date: Wed, 13 Apr 2016 17:25:32 -0400 Subject: [PATCH] Added the new function run that will automatically extract code and run as a script --- TODO.txt | 17 +++---- cyther/cyther.py | 121 +++++++++++++++++++++++++++++------------------ test_cyther.py | 14 +++++- 3 files changed, 96 insertions(+), 56 deletions(-) diff --git a/TODO.txt b/TODO.txt index 5354e24..ecbd243 100644 --- a/TODO.txt +++ b/TODO.txt @@ -10,18 +10,16 @@ MAINTAIN Make sure that you constantly search for the assumptions cyther makes - Make my getDirs() function is up to date with build_ext in distutils + Make my getIncludeAndRuntime() function is up to date with build_ext in distutils Any name cool badges to add? Make sure you got all the recent and useful ones Use http://shields.io/ to generate non changing badges -EASY FIX - - Make sure that all the message passoffs make sense. Update 'ERROR_PASSOFF' value? + Make sure that all the message passoffs make sense - Rename the getDirs() function to make more sense +EASY FIX - Is it wise to take out the checking procedures with the getDirs() function. Why are they necessary?? + Is it wise to take out the checking procedures with the getIncludeAndRuntime() function. Why are they necessary?? Reformat the README to include all info and more examples @@ -30,8 +28,6 @@ EASY FIX MEDIUM FIX - For the love of god, reorganize everything to be encapsulated, then seperate it with blocks of '#' - Where is the best spot to put the README and all the other files? Get Travis to deploy automatically. But before that, run a script to deploy to the test >> include in build process @@ -62,6 +58,9 @@ EASY NEW FEATURES Specify how to give pass off args and 3rd party modules to include in the epilog section of '-h' Do this also with the cython and gcc pass-off arguments + Write a test script that actually tests the whole deal. Have it run every scenario and then + USE the actual extension that it makes + HARD NEW FEATURES Function to clean a directory of everything 'locally build' related, or move it to a cythercache @@ -95,6 +94,8 @@ BEFORE DEPLOY: QUESTIONS: + Would it be possible to automatically send me their __cytherinfo__ variable? + Will sift ever have to return multiple results for the lib directories?? What about lines like this platform.platform().strip().lower().startswith('windows')?? diff --git a/cyther/cyther.py b/cyther/cyther.py index 7ad0c42..a392878 100644 --- a/cyther/cyther.py +++ b/cyther/cyther.py @@ -3,8 +3,8 @@ from distutils import msvccompiler import os, sys, subprocess, platform -import re, collections -import time, argparse +import re, collections, argparse +import time, timeit class CytherError(Exception): @@ -24,7 +24,7 @@ def dealWithLibA(message): raise CytherError(message) -def getDirs(): +def getIncludeAndRuntime(): include_dirs, library_dirs = [], [] py_include = sysconfig.get_python_inc() @@ -114,18 +114,32 @@ def getDirs(): CYTHONIZABLE_FILE_EXTS = ('.pyx', '.py') -PYTHON_DIRECTORY = sys.exec_prefix -DRIVE_AND_NAME = os.path.splitdrive(PYTHON_DIRECTORY) -PYTHON_NAME = 'python' + MAJOR + '.' + MINOR -PYTHON_NAME_2 = os.path.basename(DRIVE_AND_NAME[1]).lower() -DRIVE = DRIVE_AND_NAME[0] - +DRIVE, _ = os.path.splitdrive(sys.exec_prefix) if not DRIVE: DRIVE = os.path.normpath('/') CYTHER_CONFIG_FILE = os.path.join(os.path.expanduser('~'), '.cyther') -INCLUDE_DIRS, RUNTIME_DIRS = getDirs() + +def sift(obj): + string = [str(item) for name in obj for item in os.listdir(name)] + s = set(re.findall('(?<=lib)(.+?)(?=\.so|\.a)', '\n'.join(string))) + result = max(list(s), key=len) + return result + + +INCLUDE_DIRS, RUNTIME_DIRS = getIncludeAndRuntime() + +L_OPTION = '-l' + sift(RUNTIME_DIRS) + +INCLUDE_STRING = '' +for directory in INCLUDE_DIRS: + INCLUDE_STRING += '-I' + directory + + +RUNTIME_STRING = '' +for directory in RUNTIME_DIRS: + RUNTIME_STRING += '-L' + directory EXPRESSIONS = collections.defaultdict() @@ -141,17 +155,42 @@ def getDirs(): NOT_NEEDED_MESSAGE = "Module '{}' does not have to be included, or has no .get_include() method" - if not INCLUDE_DIRS: raise CytherError(MISSING_INCLUDE_DIRS) if not RUNTIME_DIRS: dealWithLibA(MISSING_RUNTIME_DIRS) +pound_extract = re.compile(r"(?:#\s*@\s?[Cc]yther\s+)(?P.+?)(?:\s*)(?:\n|$)") +tripple_extract = re.compile(r"(?:(?:''')(?:(?:.|\n)+?)@[C|c]yther\s+)(?P(?:.|\n)+?)(?:\s*)(?:''')") + + ######################################################################################################################## ######################################################################################################################## +def run(filename, *, timer=False, repeat=3, number=10000, precision=2): + with open(filename, 'r') as file: + string = file.read() + + obj = re.findall(pound_extract, string) + re.findall(tripple_extract, string) + if not obj: + print("There was no '@cyther' code collected from the file '{}'".format(filename)) + + code = ''.join([item + '\n' for item in obj]) + + if timer: + timer_obj = timeit.Timer(code) + try: + result = min(timer_obj.repeat(repeat, number)) / number + rounded = format(result, '.{}e'.format(precision)) + print("{} loops, best of {}: ({}) sec per loop".format(number, repeat, rounded)) + except: + timer_obj.print_exc() + else: + exec(code) + + def crawl(*to_find, source=DRIVE): """This function will wrap the shutil.which function to return the abspath every time, or a empty string""" found = {} @@ -205,23 +244,15 @@ def getFullPath(filename): def processFiles(args): """Generates and error checks each file's information before the compilation actually starts""" to_process = [] - i, l = getDirs() - if args['include']: - i.append(args['include']) - - include_directories = '' - for d in i: - include_directories += '-I' + d - - libs_directories = '' - for d in l: - libs_directories += '-L' + d for filename in args['filenames']: file = dict() - file['include'] = include_directories - file['libs'] = libs_directories - file['l_option'] = '-l' + sift(getDirs()[1]) + + if args['include']: + file['include'] = INCLUDE_STRING + ''.join(['-I' + item for item in args['include']]) + else: + file['include'] = INCLUDE_STRING + file['file_path'] = getFullPath(filename) file['file_base_name'] = os.path.splitext(os.path.basename(file['file_path']))[0] file['no_extension'], file['extension'] = os.path.splitext(file['file_path']) @@ -271,13 +302,6 @@ def isOutDated(file): return False -def sift(obj): - string = [str(item) for name in obj for item in os.listdir(name)] - s = set(re.findall('(?<=lib)(.+?)(?=\.so|\.a)', '\n'.join(string))) - result = max(list(s), key=len) - return result - - def makeCommands(preset, file): """Given a high level preset, it will construct the basic args to pass over. 'ninja', 'beast', and 'minimal'""" @@ -286,18 +310,20 @@ def makeCommands(preset, file): if preset == 'ninja': cython_command = ['cython', '-a', '-p', '-o', file['c_name'], file['file_path']] - gcc_command = ['gcc', '-fPIC', '-shared', '-w', '-O3', file['include'], file['libs'], '-o', - file['output_name'], file['c_name'], file['l_option']] + gcc_command = ['gcc', '-fPIC', '-shared', '-w', '-O3', file['include'], RUNTIME_STRING, '-o', + file['output_name'], file['c_name'], L_OPTION] elif preset == 'beast': cython_command = ['cython', '-a', '-l', '-p', '-o', file['c_name'], file['file_path']] - gcc_command = ['gcc', '-fPIC', '-shared', '-Wall', '-O3', file['include'], file['libs'], '-o', - file['output_name'], file['c_name'], file['l_option']] + gcc_command = ['gcc', '-fPIC', '-shared', '-Wall', '-O3', file['include'], RUNTIME_STRING, '-o', + file['output_name'], file['c_name'], L_OPTION] elif preset == 'minimal': cython_command = ['cython', '-o', file['c_name'], file['file_path']] - gcc_command = ['gcc', '-fPIC', '-shared', file['include'], file['libs'], '-o', file['output_name'], - file['c_name'], file['l_option']] + gcc_command = ['gcc', '-fPIC', '-shared', file['include'], RUNTIME_STRING, '-o', file['output_name'], + file['c_name'], L_OPTION] elif preset == 'swift': - pass + cython_command = ['cython', '-o', file['c_name'], file['file_path']] + gcc_command = ['gcc', '-fPIC', '-shared', '-Os', file['include'], RUNTIME_STRING, '-o', file['output_name'], + file['c_name'], L_OPTION] else: raise CytherError("The preset '{}' is not supported".format(preset)) return cython_command, gcc_command @@ -445,6 +471,8 @@ def core(args): else: time.sleep(interval) +######################################################################################################################## +######################################################################################################################## OPERATING_SYSTEM = platform.platform() @@ -465,7 +493,7 @@ def core(args): INFO += "\n\t\tOperating System: {}".format(OPERATING_SYSTEM) INFO += "\n\t\t\tOS is Windows: {}".format(IS_WINDOWS) INFO += "\n\t\tDefault Output Extension: {}".format(DEFAULT_OUTPUT_EXTENSION) -INFO += "\n\t\tInstallation Directory: {}".format(PYTHON_DIRECTORY) +INFO += "\n\t\tInstallation Directory: {}".format(sys.exec_prefix) INFO += "\n\tCython ({}):".format(CYTHON_EXECUTABLE) INFO += "\n\t\tNothing Here Yet" @@ -487,15 +515,16 @@ def core(args): help_local = 'When not flagged, builds in __cythercache__, when flagged, it builds locally in the same directory' help_watch = "When given, cyther will watch the directory with the 't' option implied and compile," \ "when necessary, the files given" -help_cython = "Arguments to pass to Cython (use '_' or '__' instead of '-' or '--'" -help_gcc = "Arguments to pass to gcc (use '_' or '__' instead of '-' or '--'" +help_cython = "Arguments to pass to Cython" +help_gcc = "Arguments to pass to gcc" -description_text = 'Auto compile and build .pyx files in place.\n{}' -description = description_text.format(__cytherinfo__) +description_text = 'Auto compile and build .pyx or .py files in place.' +description = description_text +epilog_text = "{}\n(Use '_' or '__' instead of '-' or '--' when passing args to gcc or Cython)" +epilog = epilog_text.format(__cytherinfo__) formatter = argparse.RawDescriptionHelpFormatter -usage = 'cyther [options] input_file' -parser = argparse.ArgumentParser(description=description, formatter_class=formatter, usage=usage) +parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=formatter) parser.add_argument('filenames', action='store', nargs='+', help=help_filenames) parser.add_argument('-p', '--preset', action='store', default='', help=help_preset) parser.add_argument('-t', '--timestamp', action='store_true', default=False, help=help_timestamp) diff --git a/test_cyther.py b/test_cyther.py index f4b1d84..9e6dea4 100644 --- a/test_cyther.py +++ b/test_cyther.py @@ -1,7 +1,17 @@ + from cyther import * from subprocess import call +''' +@cyther + +a = ''.join([str(x) for x in range(10)]) +if a != '0123456789': + raise CytherError("The @ code doesn't work correctly") +''' core('example_file.pyx') -call(['python', 'cytherize.py', 'example_file.pyx', '-t']) -core('-h') +call(['python', 'cytherize.py', 'example_file.pyx', '-t']) + +run(__file__) +run(__file__, timer=True) \ No newline at end of file