#! /usr/bin/env python import functools import glob import os import subprocess import sys from distutils.cmd import Command as BaseCommand from distutils.command.build import build as _build from distutils.command.sdist import sdist from distutils.errors import CompileError from distutils.spawn import find_executable from multiprocessing.pool import ThreadPool from setuptools import find_packages, setup from setuptools.command.install_lib import install_lib as _install_lib inkscape = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src', 'inkscape_wrapper.py')) class Command(BaseCommand): user_options = [] def initialize_options(self): pass def finalize_options(self): pass class eo_sdist(sdist): def run(self): print('creating VERSION file') if os.path.exists('VERSION'): os.remove('VERSION') version = get_version() version_file = open('VERSION', 'w') version_file.write(version) version_file.close() sdist.run(self) print('removing VERSION file') if os.path.exists('VERSION'): os.remove('VERSION') def local_findall(self, dir=os.curdir): # override default file search to allow for duplicates (as some themes have # multiple variants that are just symlinks) files = [ os.path.join(base, file) for base, dirs, files in os.walk(dir, followlinks=True) for file in files ] if dir == os.curdir: make_rel = functools.partial(os.path.relpath, start=dir) files = map(make_rel, files) self.filelist.allfiles = list(filter(os.path.isfile, files)) def get_file_list(self): self.filelist.findall = self.local_findall super().get_file_list() def copy_file(self, *args, **kwargs): # do not allow hardlinking as hardlinks would get inserted as is in # the tarball, and would not extract properly. kwargs.pop('link', None) super().copy_file(*args, **kwargs) def get_version(): '''Use the VERSION, if absent generates a version with git describe, if not tag exists, take 0.0- and add the length of the commit log. ''' if os.path.exists('VERSION'): with open('VERSION') as v: return v.read() if os.path.exists('.git'): p = subprocess.Popen( ['git', 'describe', '--dirty=.dirty', '--match=v*'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) result = p.communicate()[0] if p.returncode == 0: result = result.decode('ascii').strip()[1:] # strip spaces/newlines and initial v if '-' in result: # not a tagged version real_number, commit_count, commit_hash = result.split('-', 2) version = '%s.post%s+%s' % (real_number, commit_count, commit_hash) else: version = result.replace('.dirty', '+dirty') return version else: return '0.0.post%s' % len(subprocess.check_output(['git', 'rev-list', 'HEAD']).splitlines()) return '0.0' class compile_translations(Command): description = 'compile message catalogs to MO files via django compilemessages' user_options = [] def run(self): curdir = os.getcwd() try: from django.core.management import call_command for path, dirs, files in os.walk('publik_base_theme'): if 'locale' not in dirs: continue os.chdir(os.path.realpath(path)) call_command('compilemessages') os.chdir(curdir) except ImportError: sys.stderr.write('!!! Please install Django >= 1.4 to build translations\n') finally: os.chdir(curdir) class build_themes_json(Command): def run(self): subprocess.run(['python3', 'create_themes_json.py']) def get_css_files(include_map_files=False): for dirname in sorted(os.listdir('static')): config = os.path.join('static', dirname, 'config.json') if not os.path.exists(config): continue yield 'static/%s/style.css' % dirname if include_map_files: yield 'static/%s/style.css.map' % dirname if os.path.exists(os.path.join('static', dirname, 'backoffice.scss')): yield 'static/%s/backoffice.css' % dirname if include_map_files: yield 'static/%s/backoffice.css.map' % dirname yield 'static/portal-agent/css/agent-portal.css' yield 'static/includes/gadjo-extra.css' def subprocess_run(command): kwargs = { 'env': {'LC_ALL': 'C.UTF-8'}, } if isinstance(command, list): print(' '.join(command)) else: print(command) kwargs['shell'] = True return bool(subprocess.run(command, **kwargs).returncode == 0) class compile_scss(Command): description = 'compile scss files into css files' def run(self): sass_bin = find_executable('sassc') if not sass_bin: raise CompileError( 'A sass compiler is required but none was found. See sass-lang.com for choices.' ) for dirname in ('includes', 'toodego', 'lille', 'lomme', 'hellemmes', 'toulouse-2022'): subprocess.run(['python3', 'make_data_uris.py', os.path.join('static', dirname)], check=True) def get_build_commands(): for css_filename in get_css_files(): yield ['sassc', '--sourcemap', css_filename.replace('.css', '.scss'), css_filename] with ThreadPool() as pool: for result in pool.imap_unordered(subprocess_run, get_build_commands()): if not result: raise CompileError('error compiling css') class build_icons(Command): def run(self): cmds = [ 'python3 src/render-imgs-dashboard.py static/chateauroux/img/ --normal 333333 ' '--selected 0779B7 --title FFFFFF --title-width 80', 'python3 src/render-imgs-categories.py static/orleans/img/ --primary f05923 --secondary 34697D', 'python3 src/render-imgs-dashboard.py static/orleans/img/ --normal FFFFFF ' '--normal-width 30 --selected f05923 --selected-width 30 --title FFFFFF --title-width 80', 'python3 src/render-imgs-categories.py static/publik/img/', 'python3 src/render-imgs-dashboard.py static/publik/img/ --normal 4D4D4D ' '--selected DF017A --title FFFFFF --title-width 80', 'python3 src/render-imgs-categories.py static/somme-cd80/img/ --primary A8002B ' '--secondary A8002B', 'python3 src/render-imgs-dashboard.py static/somme-cd80/img/ --normal 4D4D4D ' '--selected 87A738 --title FFFFFF --title-width 80', ] if self.distribution.commands == ['install']: cmds = [f'{x} --skip-existing' for x in cmds] if not os.path.exists('static/tournai/img'): os.mkdir('static/tournai/img') for image in glob.glob('static/tournai/assets/*.svg'): target_filename = 'static/tournai/img/' + os.path.basename(image).replace('.svg', '.png') if ( os.path.exists(target_filename) and os.stat(image).st_mtime < os.stat(target_filename).st_mtime ): continue cmds.append( [ inkscape, '--without-gui', '--file', image, '--export-area-drawing', '--export-area-snap', '--export-png', target_filename, '--export-width', '40', ] ) with ThreadPool() as pool: for result in pool.imap_unordered(subprocess_run, cmds): if not result: raise CompileError('error building icons') class build(_build): sub_commands = [ ('build_themes_json', None), ('build_icons', None), ('compile_scss', None), ('compile_translations', None), ] + _build.sub_commands class install_lib(_install_lib): def run(self): self.run_command('compile_translations') super().run() def data_tree(destdir, sourcedir): extensions = [ '.html', '.txt', '.scss', '.css', '.js', '.ico', '.gif', '.png', '.jpg', '.jpeg', '.woff', '.woff2', '.ttf', '.css', '.map', '.svg', '.eot', '.otf', '.py', # prepare-template.py ] r = [] for root, dirs, files in os.walk(sourcedir): l = [os.path.join(root, x) for x in files if os.path.splitext(x)[1] in extensions] r.append((root.replace(sourcedir, destdir, 1), l)) return r setup( name='publik_base_theme', version=get_version(), license='AGPLv3 or later', description='Themes for Publik', url='https://dev.entrouvert.org/projects/publik-base-theme/', author='Entr’ouvert', author_email='info@entrouvert.com', packages=find_packages(), include_package_data=True, data_files=data_tree('share/publik/themes/publik-base/static', 'static') + data_tree('share/publik/themes/publik-base/templates', 'templates') + [('share/publik/themes/publik-base', ['themes.json'])], setup_requires=[ 'django>=2.2', ], classifiers=[ 'Development Status :: 5 - Stable', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', ], zip_safe=False, cmdclass={ 'build': build, 'build_icons': build_icons, 'build_themes_json': build_themes_json, 'compile_scss': compile_scss, 'compile_translations': compile_translations, 'install_lib': install_lib, 'sdist': eo_sdist, }, )