Skip to content

Instantly share code, notes, and snippets.

@ktnyt
Last active January 30, 2023 09:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ktnyt/4d4aa798784777b876f3df67182380c6 to your computer and use it in GitHub Desktop.
Save ktnyt/4d4aa798784777b876f3df67182380c6 to your computer and use it in GitHub Desktop.
C++ boilerplate code generator
#!/usr/bin/env python
################################################################################
#
# Copyright (c) 2016 Kotone Itaya
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
################################################################################
from os.path import isfile
from datetime import date
import argparse
def apache(filename, author):
year = date.today().year
return '\n'.join(map(lambda s: s.rstrip(), [
'/******************************************************************************',
' * ',
' * {0} ',
' * ',
' * Copyright (C) {2} {1} ',
' * ',
' * Licensed to the Apache Software Foundation (ASF) under one ',
' * or more contributor license agreements. See the NOTICE file ',
' * distributed with this work for additional information ',
' * regarding copyright ownership. The ASF licenses this file ',
' * to you under the Apache License, Version 2.0 (the ',
' * "License"); you may not use this file except in compliance ',
' * with the License. You may obtain a copy of the License at ',
' * ',
' * http://www.apache.org/licenses/LICENSE-2.0 ',
' * ',
' * Unless required by applicable law or agreed to in writing, ',
' * software distributed under the License is distributed on an ',
' * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ',
' * KIND, either express or implied. See the License for the ',
' * specific language governing permissions and limitations ',
' * under the License. ',
' * ',
' *****************************************************************************/'
])).format(filename, author, year)
def mit(filename, author):
year = date.today().year
return '\n'.join(map(lambda s: s.rstrip(), [
'/******************************************************************************',
' * ',
' * {0} ',
' * ',
' * MIT License ',
' * ',
' * Copyright (c) {2} {1} ',
' * ',
' * Permission is hereby granted, free of charge, to any person obtaining a copy',
' * of this software and associated documentation files (the "Software"), to ',
' * deal in the Software without restriction, including without limitation the ',
' * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ',
' * sell copies of the Software, and to permit persons to whom the Software is ',
' * furnished to do so, subject to the following conditions: ',
' * ',
' * The above copyright notice and this permission notice shall be included in ',
' * all copies or substantial portions of the Software. ',
' * ',
' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ',
' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ',
' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ',
' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ',
' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ',
' * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS',
' * IN THE SOFTWARE. ',
' * ',
' *****************************************************************************/'
])).format(filename, author, year)
license_generators = {
'apache': apache,
'mit': mit,
}
class Formatter:
def __init__(self, step=2):
self.step = step
self.code = ''
self.indents = 0
def add(self, code=''):
lines = code.split('\n')
for line in lines:
self.code += ''.join([' '] * self.indents) + line + '\n'
def indent(self):
self.indents += self.step
def unindent(self):
self.indents -= self.step
def dump(self, fullpath, overwrite):
if isfile(fullpath):
if not overwrite:
print 'File {0} exists but overwrite is NOT enabled. Will not overwrite.'.format(fullpath)
return
else:
print 'File {0} exists. Overwriting file.'.format(fullpath)
with open(fullpath, 'w') as f:
f.write(self.code + '\n')
print 'Generated {0}.'.format(fullpath)
def __str__(self):
return self.code.rstrip()
def include_format(includes):
clibraries = ['cassert', 'cctype', 'cerrno', 'cfenv', 'cfloat', 'cinttypes', 'ciso646', 'climits', 'clocale', 'cmath', 'csetjmp', 'csignal',
'stdarg', 'cstdbool', 'cstddef', 'cstdint', 'cstdio', 'cstdlib', 'cstring', 'ctgmath', 'ctime', 'cuchar', 'cwchar', 'cwctype']
containers = ['array', 'bitset', 'deque', 'forward_list', 'list', 'map', 'queue', 'set', 'stack', 'unordered_map', 'unordered_set', 'vector']
iolibraries = ['ios', 'istream', 'ostream', 'streambuf', 'iostream', 'fstream', 'sstream']
concurrency = ['atomic', 'condition_variable', 'future', 'mutex', 'thread']
miscellaneous = ['algorithm', 'chrono', 'codecvt', 'complex', 'exception', 'functional', 'initializer_list', 'iterator', 'limits', 'locale',
'memory', 'new', 'numeric', 'random', 'ratio', 'regex', 'stdexcept', 'string', 'system_error', 'tuple', 'typeindex', 'typeinfo',
'type_traits', 'utility', 'valarray']
f = Formatter()
for include in clibraries:
if include in includes:
f.add('#include <{0}>'.format(includes.pop(includes.index(include))))
for include in containers:
if include in includes:
f.add('#include <{0}>'.format(includes.pop(includes.index(include))))
for include in iolibraries:
if include in includes:
f.add('#include <{0}>'.format(includes.pop(includes.index(include))))
for include in concurrency:
if include in includes:
f.add('#include <{0}>'.format(includes.pop(includes.index(include))))
for include in miscellaneous:
if include in includes:
f.add('#include <{0}>'.format(includes.pop(includes.index(include))))
for include in includes:
f.add('#include "{0}"'.format(include))
return str(f)
def makepath(namespace, path, base, ext):
if len(namespace):
filename = '{0}/{1}.{2}'.format('/'.join(namespace), base, ext)
else:
filename = '{0}.{1}'.format(base, ext)
if not len(path):
fullpath = filename
elif path[0] == '.':
fullpath = '{0}/{1}.{2}'.format(path, base, ext)
else:
fullpath = '{0}/{1}'.format(path, filename)
return fullpath
def generate_header(namespace, indent, overwrite, author, oslicense, args, content='', extra=''):
base = args.base
ext = args.ext
path = args.path
include = args.include
if len(namespace):
filename = '{0}/{1}.{2}'.format('/'.join(namespace), base, ext)
else:
filename = '{0}.{1}'.format(base, ext)
preamble = license_generators[oslicense](filename, author)
if len(namespace):
guard = '__{0}__'.format('_'.join(namespace + [base, ext]).upper())
else:
guard = '__{0}_{1}__'.format(base.upper(), ext.upper())
f = Formatter()
f.add(preamble)
f.add()
f.add('#ifndef ' + guard)
f.add('#define ' + guard)
f.add()
if len(include):
f.add(include_format(include))
f.add()
for name in namespace:
f.add('namespace {0} {{'.format(name))
if indent:
f.indent()
if len(namespace) and not indent:
f.add()
f.add(content)
if len(namespace) and not indent:
f.add()
for name in reversed(namespace):
if indent:
f.unindent()
f.add('}} // namespace {}'.format(name))
f.add()
if len(extra):
f.add(extra)
f.add()
f.add('#endif // {}'.format(guard))
fullpath = makepath(namespace, path, base, ext)
f.dump(fullpath, overwrite)
def generate_source(namespace, indent, overwrite, author, oslicense, args, content=''):
base = args.base
ext = args.ext
path = args.path
header = args.header
include = args.include
if len(namespace):
filename = '{0}/{1}.{2}'.format('/'.join(namespace), base, ext)
else:
filename = '{0}.{1}'.format(base, ext)
preamble = license_generators[oslicense](filename, author)
f = Formatter()
f.add(preamble)
f.add()
if len(header):
f.add('#include "{0}"'.format(header))
f.add()
if len(include):
f.add(include_format(include))
f.add()
for name in namespace:
f.add('namespace {0} {{'.format(name))
if indent:
f.indent()
if len(namespace) and not indent:
f.add()
f.add(content)
if len(namespace) and not indent:
f.add()
for name in reversed(namespace):
if indent:
f.unindent()
f.add('}} // namespace {}'.format(name))
f.add()
fullpath = makepath(namespace, path, base, ext)
f.dump(fullpath, overwrite)
def generate_module(namespace, indent, overwrite, author, oslicense, args, content=''):
module = args.module
base = args.base
header_ext = args.header_ext
source_ext = args.source_ext
header_path = args.header_path
source_path = args.source_path
header_include = args.header_include
source_include = args.source_include
header_only = args.header_only
ro5 = args.ro5
dptr = args.dptr
if not len(base):
base = module
# Create header contents
header_f = Formatter()
header_f.add('class {0} {{'.format(module))
header_f.add('public:')
header_f.indent()
# Public section
if ro5:
if header_only:
if dptr:
header_f.add('{0}() : self(std::make_shared<impl>()) {{}}'.format(module))
header_f.add()
header_f.add('{0}(const {0}& other) : self(other.self) {{}}'.format(module))
header_f.add()
header_f.add('{0}({0}&& other) noexcept : self(other.self) {{ other.self = nullptr; }}'.format(module))
header_f.add()
header_f.add('{0}& operator=(const {0}& other)'.format(module))
header_f.add('{')
header_f.indent()
header_f.add('{0} another(other);'.format(module))
header_f.add('*this = std::move(another);')
header_f.add('return *this;')
header_f.unindent()
header_f.add('}')
header_f.add()
header_f.add('{0}& operator=({0}&& other) noexcept'.format(module))
header_f.add('{')
header_f.indent()
header_f.add('swap(*this, other);')
header_f.add('return *this;')
header_f.unindent()
header_f.add('}')
header_f.add()
header_f.add('friend void swap({0}& a, {0}& b) {{ std::swap(a.self, b.self); }}'.format(module))
else:
header_f.add('{0}() {{}}'.format(module))
header_f.add('{0}(const {0}&) {{}}'.format(module))
header_f.add('{0}({0}&&) noexcept {{}}'.format(module))
header_f.add('{0}& operator=(const {0}&) {{}}'.format(module))
header_f.add('{0}& operator=({0}&&) noexcept {{}}'.format(module))
header_f.add('friend void swap({0}& a, {0}& b) {{}}'.format(module))
else:
header_f.add('{0}();'.format(module))
header_f.add('{0}(const {0}&);'.format(module))
header_f.add('{0}({0}&&) noexcept;'.format(module))
header_f.add('{0}& operator=(const {0}&);'.format(module))
header_f.add('{0}& operator=({0}&&) noexcept;'.format(module))
header_f.add('friend void swap({0}& a, {0}& b);'.format(module))
else:
header_f.add()
header_f.unindent()
header_f.add('private:')
header_f.indent()
# Private section
if dptr:
if header_only:
header_f.add('struct impl {')
header_f.indent()
header_f.add()
header_f.unindent()
header_f.add('}; std::shared_ptr<impl> self;')
else:
header_f.add('struct impl; std::shared_ptr<impl> self;')
else:
header_f.add()
header_f.unindent()
header_f.add('};')
args.base = base
args.ext = header_ext
args.path = header_path
args.include = header_include
if dptr:
args.include.append('memory')
generate_header(namespace, indent, overwrite, author, oslicense, args, content=str(header_f))
if header_only:
return
# Create source contents
source_f = Formatter()
if ro5:
if dptr:
source_f.add('struct {0}::impl {{}};'.format(module))
source_f.add()
source_f.add('{0}::{0}() : self(std::make_shared<impl>()) {{}}'.format(module))
source_f.add()
source_f.add('{0}::{0}(const {0}& other) : self(other.self) {{}}'.format(module))
source_f.add()
source_f.add('{0}::{0}({0}&& other) noexcept : self(other.self) {{ other.self = nullptr; }}'.format(module))
source_f.add()
source_f.add('{0}& {0}::operator=(const {0}& other)'.format(module))
source_f.add('{')
source_f.indent()
source_f.add('{0} another(other);'.format(module))
source_f.add('*this = std::move(another);')
source_f.add('return *this;')
source_f.unindent()
source_f.add('}')
source_f.add()
source_f.add('{0}& {0}::operator=({0}&& other) noexcept'.format(module))
source_f.add('{')
source_f.indent()
source_f.add('swap(*this, other);')
source_f.add('return *this;')
source_f.unindent()
source_f.add('}')
source_f.add()
source_f.add('void swap({0}& a, {0}& b) {{ std::swap(a.self, b.self); }}'.format(module))
else:
source_f.add('{0}::{0}() {{}}'.format(module))
source_f.add()
source_f.add('{0}::{0}(const {0}& other) : self(other.self) {{}}'.format(module))
source_f.add()
source_f.add('{0}::{0}({0}&& other) noexcept {{}}'.format(module))
source_f.add()
source_f.add('{0}& {0}::operator=(const {0}& other) {{}}'.format(module))
source_f.add()
source_f.add('{0}& {0}::operator=({0}&& other) noexcept {{}}'.format(module))
args.ext = args.source_ext
args.path = source_path
args.include = source_include
if len(namespace):
args.header = '{0}/{1}.{2}'.format('/'.join(namespace), base, header_ext)
else:
args.header = '{0}.{1}'.format(base, header_ext)
generate_source(namespace, indent, overwrite, author, oslicense, args, content=str(source_f))
def generate_template(namespace, indent, overwrite, author, oslicense, args, content=''):
module = args.module
base = args.base
header_ext = args.header_ext
source_ext = args.source_ext
header_path = args.header_path
source_path = args.source_path
header_include = args.header_include
source_include = args.source_include
header_only = args.header_only
ro5 = args.ro5
dptr = args.dptr
if not len(base):
base = module
modname = '{0}<T>'.format(module)
# Create header contents
header_f = Formatter()
header_f.add('template<typename T>')
header_f.add('class {0} {{'.format(module))
header_f.add('public:')
header_f.indent()
# Public section
if ro5:
if header_only:
if dptr:
header_f.add('{0}() : self(std::make_shared<impl>()) {{}}'.format(module))
header_f.add()
header_f.add('{0}(const {0}& other) : self(other.self) {{}}'.format(module))
header_f.add()
header_f.add('{0}({0}&& other) noexcept : self(other.self) {{ other.self = nullptr; }}'.format(module))
header_f.add()
header_f.add('{0}& operator=(const {0}& other)'.format(module))
header_f.add('{')
header_f.indent()
header_f.add('{0} another(other);'.format(module))
header_f.add('*this = std::move(another);')
header_f.add('return *this;')
header_f.unindent()
header_f.add('}')
header_f.add()
header_f.add('{0}& operator=({0}&& other) noexcept'.format(module))
header_f.add('{')
header_f.indent()
header_f.add('swap(*this, other);')
header_f.add('return *this;')
header_f.unindent()
header_f.add('}')
header_f.add()
header_f.add('friend void swap({0}& a, {0}& b) {{ std::swap(a, b); }}'.format(module))
else:
header_f.add('{0}() {{}}'.format(module))
header_f.add('{0}(const {0}&) {{}}'.format(module))
header_f.add('{0}({0}&&) noexcept {{}}'.format(module))
header_f.add('{0}& operator=(const {0}&) {{}}'.format(module))
header_f.add('{0}& operator=({0}&&) noexcept {{}}'.format(module))
header_f.add('friend void swap({0}& a, {0}& b) {{}}'.format(module))
else:
header_f.add('{0}();'.format(module))
header_f.add('{0}(const {0}&);'.format(module))
header_f.add('{0}({0}&&) noexcept;'.format(module))
header_f.add('{0}& operator=(const {0}&);'.format(module))
header_f.add('{0}& operator=({0}&&) noexcept;'.format(module))
header_f.add('friend void swap({0}& a, {0}& b);'.format(module))
else:
header_f.add()
header_f.unindent()
header_f.add('private:')
header_f.indent()
# Private section
if dptr:
if header_only:
header_f.add('struct impl {')
header_f.indent()
header_f.add()
header_f.unindent()
header_f.add('}; std::shared_ptr<impl> self;')
else:
header_f.add('struct impl; std::shared_ptr<impl> self;')
else:
header_f.add()
header_f.unindent()
header_f.add('};')
args.base = base
args.ext = header_ext
args.path = header_path
args.include = header_include
if len(namespace):
implname = '{0}/{1}_impl.{2}'.format('/'.join(namespace), base, source_ext)
else:
implname = '{0}_impl.{1}'.format(base, source_ext)
if header_only:
generate_header(namespace, indent, overwrite, author, oslicense, args, content=str(header_f))
return
else:
generate_header(namespace, indent, overwrite, author, oslicense, args, content=str(header_f), extra='#include "{0}"'.format(implname))
# Create source contents
source_f = Formatter()
if ro5:
if dptr:
source_f.add('template<typename T>')
source_f.add('struct {0}::impl {{}};'.format(modname))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}() : self(std::make_shared<impl>()) {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}(const {0}& other) : self(other.self) {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}& operator=(const {0}& other) noexcept : self(other.self) {{ other.self = nullptr; }}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}& operator=({0}&& other) noexcept {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('void swap({0}::{1}& a, {0}::{1}& b) {{}}'.format(modname, module))
else:
source_f.add('template<typename T>')
source_f.add('{0}::{1}() {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}(const {0}& other) : self(other.self) {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}({0}&& other) noexcept {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}& operator=(const {0}& other) {{}}'.format(modname, module))
source_f.add()
source_f.add('template<typename T>')
source_f.add('{0}::{1}& operator=({0}&& other) noexcept {{}}'.format(modname, module))
args.base = '{0}_impl'.format(base)
args.ext = args.source_ext
args.path = source_path
args.include = source_include
args.header = ''
generate_source(namespace, indent, overwrite, author, oslicense, args, content=str(source_f))
def add_common(parser):
parser.add_argument('--namespace', default=[], help='List of namespaces', nargs='*')
parser.add_argument('--indent', default=False, help='Indent namespace', action='store_true')
parser.add_argument('--overwrite', default=False, help='Overwrite existing files', action='store_true')
parser.add_argument('--author', default='', help='Name of author')
parser.add_argument('--oslicense', default='mit', help='Open source license preamble')
return parser
def main():
parser = argparse.ArgumentParser(description='C++ boilerplate code generator')
subparsers = parser.add_subparsers(title='Subcommands')
header_parser = add_common(subparsers.add_parser('header', description='Generate a header file', help='Generate a header file'))
header_parser.set_defaults(func=generate_header)
header_parser.add_argument('base', help='Basename of generated file')
header_parser.add_argument('--ext', default='hpp', help='File extension to use')
header_parser.add_argument('--path', default='include', help='Path to generate file')
header_parser.add_argument('--include', default=[], help='List of includes', nargs='*')
source_parser = add_common(subparsers.add_parser('source', description='Generate a source file', help='Generate a source file'))
source_parser.set_defaults(func=generate_source)
source_parser.add_argument('base', help='Basename of generated file')
source_parser.add_argument('--ext', default='cpp', help='File extension to use')
source_parser.add_argument('--path', default='src', help='Path to generate file')
source_parser.add_argument('--header', default='', help='Header file to refer to')
source_parser.add_argument('--include', default=[], help='List of includes', nargs='*')
module_parser = add_common(subparsers.add_parser('module', description='Generate a class', help='Generate a class'))
module_parser.set_defaults(func=generate_module)
module_parser.add_argument('module', help='Name of class to generate')
module_parser.add_argument('--base', default='', help='Basename of generated file(s)')
module_parser.add_argument('--header-ext', default='hpp', help='File extension for header file')
module_parser.add_argument('--source-ext', default='cpp', help='File extension for source file')
module_parser.add_argument('--header-path', default='include', help='Path to generate header')
module_parser.add_argument('--source-path', default='src', help='Path to generate source')
module_parser.add_argument('--header-include', default=[], help='List of includes for header', nargs='*')
module_parser.add_argument('--source-include', default=[], help='List of includes for source', nargs='*')
module_parser.add_argument('--header-only', default=False, help='Generate headers only', action='store_true')
module_parser.add_argument('--ro5', default=False, help='Generate rule of five template', action='store_true')
module_parser.add_argument('--dptr', default=False, help='Use data pointer idiom', action='store_true')
template_parser = add_common(subparsers.add_parser('template', description='Generate a template class', help='Generate a template class'))
template_parser.set_defaults(func=generate_template)
template_parser.add_argument('module', help='Name of class to generate')
template_parser.add_argument('--base', default='', help='Basename of generated file(s)')
template_parser.add_argument('--header-ext', default='hpp', help='File extension for header file')
template_parser.add_argument('--source-ext', default='hpp', help='File extension for source file')
template_parser.add_argument('--header-path', default='include', help='Path to generate header')
template_parser.add_argument('--source-path', default='include', help='Path to generate source')
template_parser.add_argument('--header-include', default=[], help='List of includes for header', nargs='*')
template_parser.add_argument('--source-include', default=[], help='List of includes for source', nargs='*')
template_parser.add_argument('--header-only', default=False, help='Generate headers only', action='store_true')
template_parser.add_argument('--ro5', default=False, help='Generate rule of five template', action='store_true')
template_parser.add_argument('--dptr', default=False, help='Use data pointer idiom', action='store_true')
args = parser.parse_args()
namespace = args.namespace
indent = args.indent
overwrite = args.overwrite
author = args.author
oslicense = args.oslicense
args.func(namespace, indent, overwrite, author, oslicense, args)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment