1#!/usr/bin/env python2 2"""SeqAn code generation from templates / skeletons. 3 4This module contains code to help the creation of modules, tests, apps etc. 5It can be called directly or imported and the main() function can be called. 6 7It will perform the following replacements: 8 9 %(AUTHOR)s will be replaced by the author's name, either given on command 10 line or taken from environment variable SEQAN_AUTHOR. 11 12 %(NAME)s will be replaced by the name of the generated code. 13 %(TITLE)s will be replaced by the name of the generated, but centered in 14 74 characters, to be used in the file header comment. 15 16 %(YEAR)d will be replaced by the current year. 17 %(DATE)s will be replaced by the current date. 18 %(TIME)s will be replaced by the current time. 19 20 %(HEADER_GUARD)s will be replaced by the UPPER_CASE_PATH_H_ to the file. 21 22 %(CMAKE_PROJECT_NAME)s will be replaced by lower_case_path to the target 23 directory. 24 25 %(CMAKE_PROJECT_PATH)s will be replaced by the path to the target directory. 26 27Copyright: (c) 2010, Knut Reinert, FU Berlin 28License: 3-clause BSD (see LICENSE) 29""" 30 31from __future__ import with_statement 32 33__author__ = 'Manuel Holtgrewe <manuel.holtgrewe@fu-berlin.de>' 34 35import datetime 36import optparse 37import os 38import os.path 39import sys 40import string 41 42import paths 43 44# Add os.path.relpath if it is not already there, so we can use Python 2.5, too. 45# TODO(holtgrew): This could go into a "compatibility" module. 46if not 'relpath' in dir(os.path): 47 import posixpath 48 from posixpath import curdir, sep, pardir, join 49 50 def relpath(path, start=curdir): 51 """Return a relative version of a path""" 52 if not path: 53 raise ValueError("no path specified") 54 start_list = posixpath.abspath(start).split(sep) 55 path_list = posixpath.abspath(path).split(sep) 56 # Work out how much of the filepath is shared by start and path. 57 i = len(posixpath.commonprefix([start_list, path_list])) 58 rel_list = [pardir] * (len(start_list)-i) + path_list[i:] 59 if not rel_list: 60 return curdir 61 return join(*rel_list) 62 os.path.relpath = relpath 63 64# Length of the header comment. 65HEADER_CENTER_WIDTH = 74 66 67# Fallback for author string if neither given on command line or environment 68# Variable SEQAN_AUTHOR. 69DEFAULT_AUTHOR = 'Your Name <your.email@example.net>' 70 71# Program usage string for command line parser. 72USAGE = """ 73Usage: %prog [options] repository NAME 74 %prog [options] [module|test|app|demo|header|lheader] NAME LOCATION 75 %prog [options] app_tests LOCATION 76""".strip() 77 78# Program description, used for command line parser. Will be wrapped by, though. 79DESCRIPTION = """ 80The SeqAn code generator. 81 82The first version ("repository") is to be be called to create your new entries 83below the directory sandbox. The second version is to be called to create new 84library modules, tests, apps, app tests, and demos inside a sandbox. 85""".strip() 86#""" 87#Example: 88# 89# %prog repository sandbox/john_doe 90# 91#The second version is to be called to create new library modules, tests, apps, 92#and demos inside a sandbox. Example: 93# 94# %prog module my_module sandbox/john_doe 95# 96#This command creates a new library module in sandbox/john_doe/include/seqan. 97#It consists of the directory my_module, the files my_module.h and 98#my_module/my_module_base.h. 99# 100# %prog test my_module sandbox/john_doe 101# 102#This command creates the tests for module "my_module" in sandbox/john_doe. 103# 104# %prog app my_app sandbox/john_doe 105# 106#This command creates a new application named my_app in sandbox/john_doe/apps. 107# 108# %prog demo my_demo sandbox/john_doe 109# 110#This command creates a new demo in sandbox/john_doe/demos. 111#""".strip() 112 113def createDirectory(path, dry_run=False): 114 print 'mkdir(%s)' % path 115 print 116 if not dry_run: 117 if not os.path.exists(path): 118 os.mkdir(path) 119 120def configureFile(target_file, source_file, replacements, dry_run, options): 121 print 'Configuring file.' 122 print ' Source:', source_file 123 print ' Target:', target_file 124 print 125 if os.path.exists(target_file) and not options.force: 126 msg = 'Target file already exists. Move it away and call the script again.' 127 print >>sys.stderr, msg 128 return 1 129 130 with open(source_file, 'rb') as f: 131 contents = f.read() 132 target_contents = contents % replacements 133 if dry_run: 134 print 'The contents of the target file are:' 135 print '-' * 78 136 print target_contents 137 print '-' * 78 138 else: 139 with open(target_file, 'wb') as f: 140 f.write(target_contents) 141 return 0 142 143def _pathToIdentifier(relative_path): 144 result = relative_path.replace('/', '_') 145 result = result.replace('\\', '_') 146 result = result.replace('-', '_') 147 result = result.replace('.', '_') 148 result = result.replace(' ', '_') 149 return result 150 151def buildReplacements(type_, name, location, target_file, options): 152 result = {} 153 result['AUTHOR'] = options.author 154 result['YEAR'] = datetime.date.today().year 155 result['TIME'] = datetime.datetime.now().strftime('%H:%M') 156 result['DATE'] = datetime.date.today().strftime('%Y-%m-%d') 157 result['NAME'] = name 158 result['TITLE'] = name.center(HEADER_CENTER_WIDTH).rstrip() 159 path = os.path.relpath(target_file, paths.repositoryRoot()) 160 guard = _pathToIdentifier(path).upper() 161 result['HEADER_GUARD'] = guard + '_' 162 path = os.path.relpath(os.path.dirname(target_file), 163 paths.repositoryRoot()) 164 cmake_project_name = _pathToIdentifier(path) 165 result['CMAKE_PROJECT_NAME'] = cmake_project_name 166 result['CMAKE_PROJECT_PATH'] = path.replace('\\', '\\\\') 167 if type_ == 'repository': 168 result['REPOSITORY_PSEUDO_TARGET_NAME'] = name.replace('/', '_').replace('\\', '_').replace(' ', '_') 169 if type_ == 'app_tests': 170 result['APP_NAME'] = os.path.split(os.path.split(location)[0])[1] 171 result['APP_NAME_U'] = result['APP_NAME'].upper() 172 result['LOCATION'] = os.path.join(os.path.split(os.path.normpath(location))[0]) 173 return result 174 175def _checkTargetPaths(target_path, options): 176 """Check that the path does not exist but its parent does.""" 177 # Check that the given path does not exist yet. 178 if os.path.exists(target_path) and not options.force: 179 msg = 'The path %s already exists. Move it and call this script again.' 180 print >>sys.stderr, msg % target_path 181 return False 182 # Check that the parent path already exists. 183 if not os.path.exists(os.path.dirname(target_path)): 184 msg = 'The parent of the target path does not exist yet: %s' 185 print >>sys.stderr, msg % os.path.dirname(target_path) 186 print >>sys.stderr, 'Please create it and call this script again.' 187 return False 188 return True 189 190def createModule(name, location, options): 191 include_path = paths.pathToInclude(location) 192 seqan_path = os.path.join(include_path, 'seqan') 193 module_path = os.path.join(seqan_path, name) 194 header_path = os.path.join(seqan_path, '%s.h' % name) 195 print 'Creating module in %s' % module_path 196 if options.create_dirs and not _checkTargetPaths(module_path, options): 197 return 1 198 if options.create_dirs and not _checkTargetPaths(header_path, options): 199 return 1 200 print ' Module path is: %s' % module_path 201 print ' Module header path is: %s' % header_path 202 print '' 203 if options.create_dirs: 204 # Create directory. 205 createDirectory(module_path, options.dry_run) 206 if options.create_programs: 207 # Copy over module header. 208 source_file = paths.pathToTemplate('module_template', 'module.h') 209 target_file = header_path 210 replacements = buildReplacements('module', name, seqan_path, target_file, options) 211 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 212 if res: return res 213 # Copy over header inside module. 214 source_file = paths.pathToTemplate('module_template', 'header.h') 215 target_file = os.path.join(module_path, '%s_base.h' % name) 216 replacements = buildReplacements('module', name, seqan_path, target_file, options) 217 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 218 if res: return res 219 return 0 220 221def createTest(name, location, options): 222 target_path = paths.pathToTest(location, name) 223 print 'Creating test in %s' % target_path 224 if options.create_dirs and not _checkTargetPaths(target_path, options): 225 return 1 226 print ' Target path is: %s' % target_path 227 print '' 228 if options.create_dirs: 229 # Create directory. 230 createDirectory(target_path, options.dry_run) 231 if options.create_programs: 232 # Copy over .cpp file for test and perform replacements. 233 source_file = paths.pathToTemplate('test_template', 'test.cpp') 234 target_file = os.path.join(target_path, 'test_%s.cpp' % name) 235 replacements = buildReplacements('test', name, location, target_file, options) 236 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 237 if res: return res 238 # Copy over .h file for test and perform replacements. 239 source_file = paths.pathToTemplate('test_template', 'test.h') 240 target_file = os.path.join(target_path, 'test_%s.h' % name) 241 replacements = buildReplacements('test', name, location, target_file, options) 242 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 243 if res: return res 244 if options.create_cmakelists: 245 # Copy over CMakeLists.txt file for test and perform replacements. 246 source_file = paths.pathToTemplate('test_template', 'CMakeLists.txt') 247 target_file = os.path.join(target_path, 'CMakeLists.txt') 248 replacements = buildReplacements('test', name, location, target_file, options) 249 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 250 if res: return res 251 return 0 252 253def createApp(name, location, options): 254 target_path = paths.pathToApp(location, name) 255 print 'Creating app in %s' % target_path 256 if options.create_dirs and not _checkTargetPaths(target_path, options): 257 return 1 258 print ' Target path is: %s' % target_path 259 print '' 260 if options.create_programs: 261 # Create directory. 262 createDirectory(target_path, options.dry_run) 263 # Copy over .cpp file for app and perform replacements. 264 source_file = paths.pathToTemplate('app_template', 'app.cpp') 265 target_file = os.path.join(target_path, '%s.cpp' % name) 266 replacements = buildReplacements('app', name, location, target_file, options) 267 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 268 if res: return res 269 if options.create_cmakelists: 270 # Copy over CMakeLists.txt file for app and perform replacements. 271 source_file = paths.pathToTemplate('app_template', 'CMakeLists.txt') 272 target_file = os.path.join(target_path, 'CMakeLists.txt') 273 replacements = buildReplacements('app', name, location, target_file, options) 274 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 275 if res: return res 276 return 0 277 278def createDemo(name, location, options): 279 target_path = paths.pathToDemo(location, name) 280 print 'Creating demo in %s' % target_path 281 if options.create_dirs and not _checkTargetPaths(target_path, options): 282 return 1 283 print ' Target path is: %s' % target_path 284 print '' 285 if options.create_programs: 286 # Copy over .cpp file for app and perform replacements. 287 source_file = paths.pathToTemplate('demo_template', 'demo.cpp') 288 target_file = os.path.join(target_path) 289 replacements = buildReplacements('demo', name, location, target_file, options) 290 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 291 if res: return res 292 return 0 293 294def createHeader(name, location, options): 295 target_path = paths.pathToHeader(location, name) 296 print 'Creating (non-library) header in %s' % target_path 297 if not _checkTargetPaths(target_path, options): 298 return 1 299 print ' Target path is: %s' % target_path 300 print '' 301 # Copy over .h file for app and perform replacements. 302 source_file = paths.pathToTemplate('header_template', 'header.h') 303 target_file = os.path.join(target_path) 304 replacements = buildReplacements('header', name, location, target_file, options) 305 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 306 if res: return res 307 print 'NOTE: Do not forget to add the header to the CMakeLists.txt file!' 308 return 0 309 310def createLibraryHeader(name, location, options): 311 target_path = paths.pathToHeader(location, name) 312 print 'Creating library header in %s' % target_path 313 if not _checkTargetPaths(target_path, options): 314 return 1 315 print ' Target path is: %s' % target_path 316 print '' 317 # Copy over .h file for app and perform replacements. 318 source_file = paths.pathToTemplate('header_template', 'library_header.h') 319 target_file = os.path.join(target_path) 320 replacements = buildReplacements('library_header', name, location, target_file, options) 321 res = configureFile(target_file, source_file, replacements, options.dry_run, options) 322 if res: return res 323 return 0 324 325def createRepository(location, options): 326 print 'Creating module %s' % location 327 target_path = paths.pathToRepository(location) 328 if options.create_dirs and not _checkTargetPaths(target_path, options): 329 return 1 330 print ' Target path is: %s' % target_path 331 print '' 332 if options.create_dirs: 333 # Create directories. 334 createDirectory(target_path, options.dry_run) 335 createDirectory(os.path.join(target_path, 'apps'), options.dry_run) 336 createDirectory(os.path.join(target_path, 'demos'), options.dry_run) 337 createDirectory(os.path.join(target_path, 'include'), options.dry_run) 338 createDirectory(os.path.join(target_path, 'include', 'seqan'), options.dry_run) 339 createDirectory(os.path.join(target_path, 'tests'), options.dry_run) 340 if options.create_cmakelists: 341 # Copy over file ${REPOSITORY}/CMakeLists.txt. 342 target_file = os.path.join(target_path, 'CMakeLists.txt') 343 source_file = paths.pathToTemplate('repository_template', 'CMakeLists.txt') 344 replacements = buildReplacements('repository', location, target_path, target_file, options) 345 configureFile(target_file, source_file, replacements, options.dry_run, options) 346 # Copy over file ${REPOSITORY}/apps/CMakeLists.txt. 347 target_file = os.path.join(target_path, 'apps', 'CMakeLists.txt') 348 source_file = paths.pathToTemplate('repository_template', 'apps_CMakeLists.txt') 349 replacements = buildReplacements('repository', location, target_path, target_file, options) 350 configureFile(target_file, source_file, replacements, options.dry_run, options) 351 # Copy over file ${REPOSITORY}/tests/CMakeLists.txt. 352 target_file = os.path.join(target_path, 'tests', 'CMakeLists.txt') 353 source_file = paths.pathToTemplate('repository_template', 'tests_CMakeLists.txt') 354 replacements = buildReplacements('repository', location, target_path, target_file, options) 355 configureFile(target_file, source_file, replacements, options.dry_run, options) 356 # Copy over file ${REPOSITORY}/demos/CMakeLists.txt. 357 target_file = os.path.join(target_path, 'demos', 'CMakeLists.txt') 358 source_file = paths.pathToTemplate('repository_template', 'demos_CMakeLists.txt') 359 replacements = buildReplacements('repository', location, target_path, target_file, options) 360 configureFile(target_file, source_file, replacements, options.dry_run, options) 361 return 0 362 363def createAppTests(location, options): 364 print 'Creating app tests at %s' % location 365 tests_location = os.path.join(location, 'tests') 366 target_path = paths.pathToRepository(tests_location) 367 if options.create_dirs and not _checkTargetPaths(target_path, options): 368 return 1 369 print ' Target path is: %s' % target_path 370 print '' 371 372 # Create directories. 373 if options.create_dirs: 374 createDirectory(target_path, options.dry_run) 375 376 # Copy over file ${APP}/tests/generate_outputs.sh 377 target_file = os.path.join(target_path, 'generate_outputs.sh') 378 source_file = paths.pathToTemplate('app_tests_template', 'generate_outputs.sh') 379 replacements = buildReplacements('app_tests', location, target_path, target_file, options) 380 configureFile(target_file, source_file, replacements, options.dry_run, options) 381 # Copy over file ${APP}/tests/run_tests.py 382 target_file = os.path.join(target_path, 'run_tests.py') 383 source_file = paths.pathToTemplate('app_tests_template', 'run_tests.py') 384 replacements = buildReplacements('app_tests', location, target_path, target_file, options) 385 configureFile(target_file, source_file, replacements, options.dry_run, options) 386 387 print '=' * 80 388 print 'Do not forget to add the tests in %s:' % os.path.join(location, 'CMakeLists.txt') 389 print '' 390 print '# Add app tests if Python interpreter could be found.' 391 print 'if(PYTHONINTERP_FOUND)' 392 print ' add_test(NAME app_test_%s COMMAND ${PYTHON_EXECUTABLE}' % os.path.split(location)[-1] 393 print ' ${CMAKE_CURRENT_SOURCE_DIR}/tests/run_tests.py ${CMAKE_SOURCE_DIR}' 394 print ' ${CMAKE_BINARY_DIR})' 395 print 'endif(PYTHONINTERP_FOUND)' 396 print '=' * 80 397 398 return 0 399 400def main(): 401 # Parse arguments. 402 parser = optparse.OptionParser(usage=USAGE, description=DESCRIPTION) 403 parser.add_option('-s', '--skel-root', dest='skel_root', 404 help=('Set path to the directory where the skeletons ' 405 'live in. Taken from environment variable ' 406 'SEQAN_SKELS if available.'), 407 default=os.environ.get('SEQAN_SKELS', 408 paths.pathToSkeletons())) 409 parser.add_option('-a', '--author', dest='author', 410 help=('Set author to use. Should have the format USER ' 411 '<EMAIL>. Taken from environment variable ' 412 'SEQAN_AUTHOR if it exists.'), 413 default=os.environ.get('SEQAN_AUTHOR', DEFAULT_AUTHOR)) 414 parser.add_option('-d', '--dry-run', dest='dry_run', action='store_true', 415 help='Do not change anything, just simulate.', 416 default=False) 417 parser.add_option('-c', '--cmakelists-only', dest='cmakelists_only', 418 action='store_true', 419 help='Only create CMakeLists.txt files', 420 default=False) 421 parser.add_option('--force', dest='force', action='store_true', 422 help='Overwrite existing files and directories.', 423 default=False) 424 options, args = parser.parse_args() 425 options.create_cmakelists = True 426 options.create_infos = True 427 options.create_dirs = True 428 options.create_programs = True 429 if options.cmakelists_only: 430 options.create_dirs = False 431 options.create_programs = False 432 433 if not args: 434 parser.print_help(file=sys.stderr) 435 return 1 436 if len(args) < 2: 437 print >>sys.stderr, 'Invalid argument count!' 438 return 1 439 if args[0] not in ['module', 'test', 'app', 'demo', 'repository', 440 'header', 'lheader', 'app_tests']: 441 print >>sys.stderr, 'Invalid template "%s".' % args[0] 442 return 1 443 if args[0] in['repository', 'app_tests']: 444 if len(args) != 2: 445 print >>sys.stderr, 'Invalid argument count!' 446 return 1 447 448 if args[0] == 'repository': 449 return createRepository(args[1], options) 450 elif args[0] == 'app_tests': 451 return createAppTests(args[1], options) 452 elif len(args) != 3: 453 print >>sys.stderr, 'Invalid argument count!' 454 return 1 455 create_methods = { 456 'module' : createModule, 457 'test': createTest, 458 'app': createApp, 459 'demo': createDemo, 460 'header': createHeader, 461 'lheader': createLibraryHeader, 462 } 463 return create_methods[args[0]](args[1], args[2], options) 464 465if __name__ == '__main__': 466 sys.exit(main()) 467 468