From 3a3fb4cb26e70a5b56180181d61068d0456c0e69 Mon Sep 17 00:00:00 2001 From: Johan Cwiklinski Date: Sun, 25 Aug 2024 14:36:59 +0200 Subject: [PATCH] Add scripts --- bin/release | 661 +++++++++++++++++++++++++++++++++++++++++++++++++ bin/twig-cache | 108 ++++++++ lang/Makefile | 49 ++++ 3 files changed, 818 insertions(+) create mode 100755 bin/release create mode 100755 bin/twig-cache create mode 100644 lang/Makefile diff --git a/bin/release b/bin/release new file mode 100755 index 0000000..caac1e0 --- /dev/null +++ b/bin/release @@ -0,0 +1,661 @@ +#!/usr/bin/python + +import os, sys, argparse, re, git, http.client, subprocess +import urlgrabber.progress, tarfile, shutil, gitdb, time, fnmatch +from datetime import datetime +from termcolor import colored +from urllib.parse import urlparse + +# on Tuxfamily +ssh_path = 'galette/galette-repository/plugins/' +ssh_host = 'ssh.tuxfamily.org' +galette_dl_repo = 'http://download.tuxfamily.org/galette/plugins/' +is_local = False + +local_dl_repo = os.path.join( + os.path.dirname( + os.path.dirname(os.path.abspath(__file__)) + ), + 'dist' +) +verbose = False +tagrefs = None +force = False +commit = None +extra = None +sign = True +assume_yes = False +nightly = False +ssh_key = False +tag_commit = None + + +def print_err(msg): + """ + Display colored error message + """ + print(colored(msg, 'red', attrs=['bold'])) + + +def get_numeric_version(ver): + """ + Returns all numeric version + """ + return re.findall(r'\d+', ver) + + +def valid_version(ver): + """ + Check if provided version is valid. + + Takes all digits in passed version, then reassemble them with dots + to check if it is the same as original given one. + """ + return '.'.join(get_numeric_version(ver)) == ver + + +def incr_version(ver): + """ + Increment version number + """ + version = get_numeric_version(ver) + version[-1] = str(int(version[-1]) + 1) + return version + + +def propose_version(): + """ + Propose new minor and major versions, + according to existing git tags + """ + last_major = '0' + last_minor = '0' + + for tagref in tagrefs: + tag = tagref.tag + if valid_version(tag.tag): + # last minor version is always the last one :) + if tag.tag > last_minor: + last_minor = tag.tag + + # last major version + if len(tag.tag) == 5 and tag.tag > last_major: + last_major = tag.tag + + if verbose: + print('last minor: %s | last major %s' % (last_minor, last_major)) + + # no version provided. propose one + new_minor = None + new_major = None + + if len(last_minor) == 5: + # if the latest is a major version + new_minor = last_minor + '.1' + else: + new_minor = '.'.join(incr_version(last_minor)) + + new_major = '.'.join(incr_version(last_major)) + + print("""Proposed versions: + minor: %s + major: %s + """ % (new_minor, new_major)) + + +def get_latest_version(): + """ + Look for latest version + """ + global tag_commit + + last = None + for tagref in tagrefs: + tag = tagref.tag + if tag is not None and valid_version(tag.tag): + # last minor version is always the last one :) + if last is None or tag.tag > last.tag: + last = tag + + tag_commit = last.hexsha + return last.tag + + +def is_existing_version(ver): + """ + Look specified version exists + """ + for tagref in tagrefs: + tag = tagref.tag + if valid_version(tag.tag): + if tag.tag == ver: + return True + return False + +def ask_user_confirm(msg): + """ + Ask user his confirmation + """ + if assume_yes: + return True + else: + while True: + sys.stdout.write(msg) + choice = input().lower() + if choice == 'y' or choice == 'yes': + return True + elif choice == 'n' or choice == 'no': + return False + else: + print_err( + "Invalid input. Please enter 'yes' or 'no' (or 'y' or 'n')." + ) + + +def get_rel_name(buildver): + """ + Build archive name from command line parameters + That would be used for git archiving prefix and archive name + """ + archive_name = None + + if commit and extra: + now = datetime.now() + archive_name = 'galette-plugin-activities-%s-%s-%s-%s' % ( + buildver, + extra, + now.strftime('%Y%m%d'), + commit + ) + elif nightly: + archive_name = 'galette-plugin-activities-dev' + else: + archive_name = 'galette-plugin-activities-%s' % buildver + + return archive_name + + +def _do_build(ver): + """ + Proceed build + """ + global is_local + + exists = False + ascexists = False + rel_name = get_rel_name(ver) + archive_name = rel_name + '.tar.bz2' + galette_archive = os.path.join( + local_dl_repo, + archive_name + ) + + if not force: + # first check if a version + local = False + ascLocal = False + + url = galette_dl_repo + '/' + archive_name + urlasc = '%s.asc' % url + + if is_local: + exists = os.path.isfile(url) + else: + parsed = urlparse(url) + + connection = http.client.HTTPConnection(parsed[1], 80) + connection.request('HEAD', parsed[2]) + response = connection.getresponse() + exists = response.status == 200 + + if not exists: + # also check from local repo + exists = os.path.exists(galette_archive) + if exists: + local = True + + if is_local: + ascexists = os.path.isfile(urlasc) + else: + ascparsed = urlparse(urlasc) + connection = http.client.HTTPConnection(ascparsed[1], 80) + connection.request('HEAD', ascparsed[2]) + response = connection.getresponse() + ascexists = response.status == 200 + + if not ascexists: + # also check from local repo + ascexists = os.path.exists( + os.path.join( + local_dl_repo, + archive_name + '.asc' + ) + ) + if ascexists: + ascLocal = True + + if exists or ascexists: + msg = None + if exists: + loctxt = '' + if local: + loctxt = 'locally ' + msg = 'Release %s already %sexists' % (rel_name, loctxt) + + if ascexists: + loctxt = '' + if ascLocal: + loctxt = ' locally' + if msg is not None: + msg += ' and has been %ssigned!' % loctxt + else: + msg += 'Release has been %ssigned!' % loctxt + + msg += '\n\nYou will *NOT* build another one :)' + print_err(msg) + else: + print('Building %s...' % rel_name) + + archive_cmd_pattern = 'git archive --prefix=%s/ %s | bzip2 > %s' + if commit and extra or nightly: + archive_cmd = archive_cmd_pattern % ( + rel_name, + commit, + galette_archive + ) + else: + archive_cmd = archive_cmd_pattern % ( + rel_name, + ver, + galette_archive + ) + + if verbose: + typestr = 'Tag' + typever = ver + + if commit and extra: + typestr = 'Commit' + typever = commit + + print('Release name: %s, %s: %s, Dest: %s' % ( + rel_name, + typestr, + typever, + galette_archive + )) + print('Archive command: %s' % archive_cmd) + + if commit and extra: + print('Archiving GIT commit %s' % commit) + else: + print('Archiving GIT tag %s' % ver) + + p1 = subprocess.Popen(archive_cmd, shell=True) + p1.communicate() + + print('Adding vendor libraries') + add_libs(rel_name, galette_archive) + + if sign: + do_sign(galette_archive) + + upload = ask_user_confirm( + 'Do you want to upload archive %s? [yes/No] ' % galette_archive + ) + + if upload: + do_upload(galette_archive) + + +def do_sign(archive): + sign_cmd = 'gpg --detach-sign --armor %s' % archive + p1 = subprocess.Popen(sign_cmd, shell=True) + p1.communicate() + + +def do_upload(galette_archive): + """ + proceed file upload + :param galette_archive: + :return: + """ + global is_local + + if is_local: + do_cp(galette_archive) + else: + do_scp(galette_archive) + + +def do_scp(archive): + global ssh_key, ssh_host, ssh_path + + path = ssh_path + if extra: + path += 'dev/' + + if ssh_key: + scp_cmd = 'scp -i %s %s* %s:%s' % (ssh_key, archive, ssh_host, path) + else: + scp_cmd = 'scp -r %s* %s:%s' % (archive, ssh_host, path) + print(scp_cmd) + p1 = subprocess.Popen(scp_cmd, shell=True) + p1.communicate() + + +def do_cp(archive): + global galette_dl_repo + + path = galette_dl_repo + if extra: + path = os.path.join(path, 'dev') + + shutil.copyfile( + archive, + os.path.join(path, os.path.basename(archive)) + ) + + +def add_libs(rel_name, galette_archive): + """ + Add external libraries to the archive + """ + galette = tarfile.open(galette_archive, 'r|bz2', format=tarfile.GNU_FORMAT) + src_dir = os.path.join(local_dl_repo, 'src') + if not os.path.exists(src_dir): + os.makedirs(src_dir) + galette.extractall(path=src_dir) + galette.close() + + npm_dir = os.path.join(src_dir, rel_name) + npm_cmd = 'npm install --prefix %s' % npm_dir + print(npm_cmd) + p1 = subprocess.Popen(npm_cmd, shell=True, cwd=npm_dir) + p1.wait() + + wp_cmd = 'npm run-script build-dist' + p1 = subprocess.Popen(wp_cmd, shell=True, cwd=npm_dir) + p1.wait() + + shutil.rmtree(os.path.join(npm_dir, 'node_modules')) + shutil.rmtree(os.path.join(npm_dir, 'bin')) + os.remove(os.path.join(npm_dir, '.gitignore')) + os.remove(os.path.join(npm_dir, 'webpack.config.js')) + os.remove(os.path.join(npm_dir, 'package.json')) + os.remove(os.path.join(npm_dir, 'package-lock.json')) + os.remove(os.path.join(npm_dir, 'calendar.js')) + + composer_dir = os.path.join(src_dir, rel_name) + has_composer = os.path.exists( + os.path.join( + composer_dir, + 'composer.json' + ) + ) + + if has_composer: + composer_cmd = 'composer install --no-dev' + p1 = subprocess.Popen(composer_cmd, shell=True, cwd=composer_dir) + p1.wait() + + #cleanup vendors + for root, dirnames, filenames in os.walk(os.path.join(composer_dir, 'vendor')): + #remove git directories + for dirname in fnmatch.filter(dirnames, '.git*'): + remove_dir = os.path.join(composer_dir, root, dirname) + shutil.rmtree(remove_dir) + #remove test directories + for dirname in fnmatch.filter(dirnames, 'test?'): + remove_dir = os.path.join(composer_dir, root, dirname) + shutil.rmtree(remove_dir) + #remove examples directories + for dirname in fnmatch.filter(dirnames, 'example?'): + remove_dir = os.path.join(composer_dir, root, dirname) + shutil.rmtree(remove_dir) + #remove doc directories + for dirname in fnmatch.filter(dirnames, 'doc?'): + remove_dir = os.path.join(composer_dir, root, dirname) + shutil.rmtree(remove_dir) + #remove composer stuff + for filename in fnmatch.filter(filenames, 'composer*'): + remove_file = os.path.join(composer_dir, root, filename) + os.remove(remove_file) + + for dirname in dirnames: + #remove Faker useless languages + if root.endswith('src/Faker/Provider'): + if dirname not in ['en_US', 'fr_FR', 'de_DE']: + shutil.rmtree(os.path.join(root, dirname)) + #begin to remove tcpdf not used fonts + if root.endswith('tcpdf/fonts'): + if dirname != 'dejavu-fonts-ttf-2.34': + shutil.rmtree(os.path.join(root, dirname)) + + for filename in filenames: + if os.path.islink(os.path.join(root, filename)): + os.remove(os.path.join(root, filename)) + #remove tcpdf not used fonts + if root.endswith('tcpdf/fonts'): + if filename not in [ + 'dejavusansbi.ctg.z', + 'dejavusansbi.z', + 'dejavusansb.z', + 'dejavusansi.ctg.z', + 'dejavusansi.z', + 'dejavusans.z', + 'zapfdingbats.php', + 'dejavusansb.ctg.z', + 'dejavusansbi.php', + 'dejavusansb.php', + 'dejavusans.ctg.z', + 'dejavusansi.php', + 'dejavusans.php', + 'helvetica.php' + ]: + os.remove(os.path.join(root, filename)) + + galette = tarfile.open(galette_archive, 'w|bz2', format=tarfile.GNU_FORMAT) + + for i in os.listdir(src_dir): + galette.add( + os.path.join(src_dir, i), + arcname=rel_name + ) + + galette.close() + shutil.rmtree(src_dir) + + +def valid_commit(repo, c): + """ + Validate commit existence in repository + """ + global commit + + try: + dformat = '%a, %d %b %Y %H:%M' + repo_commit = repo.commit(c) + + commit = repo_commit.hexsha[:10] + print(colored("""Commit information: + Hash: %s + Author: %s + Authored date: %s + Commiter: %s + Commit date: %s + Message: %s""" % ( + commit, + repo_commit.author, + time.strftime(dformat, time.gmtime(repo_commit.authored_date)), + repo_commit.committer, + time.strftime(dformat, time.gmtime(repo_commit.committed_date)), + repo_commit.message + ), None, 'on_grey', attrs=['bold'])) + return True + except gitdb.exc.BadObject: + return False + +def main(): + """ + Main method + """ + global verbose, tagrefs, force, extra, assume_yes, nightly, sign, ssh_key, repo, ssh_host, ssh_path, galette_dl_repo, is_local + + parser = argparse.ArgumentParser(description='Release Galette Activities Plugin') + group = parser.add_mutually_exclusive_group() + group.add_argument( + '-v', + '--version', + help='Version to release' + ) + group.add_argument( + '-p', + '--propose', + help='Calculate and propose next possible versions', + action='store_true' + ) + parser.add_argument( + '-c', + '--commit', + help='Specify commit to archive (-v required)' + ) + parser.add_argument( + '-e', + '--extra', + help='Extra version information (-c required)' + ) + parser.add_argument( + '-Y', + '--assume-yes', + help='Assume YES to all questions. Be sure to understand what you are doing!', + action='store_true' + ) + parser.add_argument( + '-V', + '--verbose', + help='Be more verbose', + action="store_true" + ) + parser.add_argument( + '-n', + '--nightly', + help='Build nightly', + action="store_true" + ) + parser.add_argument( + '-l', + '--local', + help='Use local copy (defined from galette_dl_repo) rather than SSH', + action='store_true' + ) + parser.add_argument( + '-k', + '--ssh-key', + help='SSH key to be used for uploading', + ) + parser.add_argument( + '-H', + '--ssh-host', + help='SSH host to upload to (default %s)' % ssh_host + ) + parser.add_argument( + '-P', + '--ssh-path', + help='Path on SSH host (default %s)' % ssh_path + ) + parser.add_argument( + '-d', + '--download-url', + help='Download URL (default %s)' % galette_dl_repo + ) + parser.add_argument('-f', action='store_true') + args = parser.parse_args() + + verbose = args.verbose + + if verbose: + print(args) + + galette_repo = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + repo = git.Repo(galette_repo) + tagrefs = repo.tags + + if args.f == True: + force = ask_user_confirm( + 'Are you *REALLY* sure you mean -f when you typed -f? [yes/No] ' + ) + assume_yes = args.assume_yes + + if args.local: + if not args.download_url: + print_err('download_url is mandatory for local builds!') + sys.exit(1) + is_local = args.local + + else: + if args.ssh_key: + ssh_key = args.ssh_key + + if args.ssh_host: + ssh_host = args.ssh_host + + if args.ssh_path: + ssh_path = args.ssh_path + + if args.download_url: + galette_dl_repo = args.download_url + + build = False + buildver = None + if args.nightly: + nightly = True + buildver = 'dev' + args.commit = repo.commit('develop') + if valid_commit(repo, args.commit): + force = True + build = True + sign = False + assume_yes = True + else: + print_err('Invalid commit ref %s' % args.commit) + elif (args.extra or args.commit) and (not args.extra or not args.commit or not args.version): + print_err('You have to specify --version --commit and --extra all together') + sys.exit(1) + elif args.commit and args.version and args.extra: + if valid_commit(repo, args.commit): + if verbose: + print('Commit is valid') + build = True + buildver = args.version + extra = args.extra + else: + print_err('Invalid commit ref %s' % args.commit) + elif args.version: + if not valid_version(args.version): + print_err('%s is not a valid version number!' % args.version) + sys.exit(1) + else: + # check if specified version exists + if not is_existing_version(args.version): + print_err('%s does not exist!' % args.version) + else: + build = True + buildver = args.version + elif args.propose: + propose_version() + else: + buildver = get_latest_version() + if force: + build = True + else: + build = ask_user_confirm( + 'Do you want to build Galette Activities Plugin version %s? [Yes/no] ' % buildver + ) + + if build: + _do_build(buildver) + + +if __name__ == "__main__": + main() diff --git a/bin/twig-cache b/bin/twig-cache new file mode 100755 index 0000000..73c9a6e --- /dev/null +++ b/bin/twig-cache @@ -0,0 +1,108 @@ +#!/bin/php +isDir() && !$fpath->isLink()) ? rmdir($fpath->getPathname()) : unlink($fpath->getPathname()); + } + + rmdir($path); + return true; + } + return false; +} + +/** + * Return a custom Twig cache handler. + * This handler is useful to be able to preserve filenames of compiled files. + * + * @param string $directory + * + * @return CacheInterface + */ +function getTwigCacheHandler(string $directory): CacheInterface +{ + return new class($directory) extends FilesystemCache { + + private string $directory; + + public function __construct(string $directory, int $options = 0) + { + $this->directory = rtrim($directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + parent::__construct($directory, $options); + } + + public function generateKey(string $name, string $className): string + { + return $this->directory . $name; + } + }; +} + +$directory = sprintf('%s/../templates/default', __DIR__); +$cache_dir = sprintf('%s/../tempcache', __DIR__); +$cache = getTwigCacheHandler($cache_dir); +if (file_exists($cache_dir)) { + rmdir_recursive($cache_dir); +} +mkdir($cache_dir); + +$iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($directory), + RecursiveIteratorIterator::LEAVES_ONLY +); + +$loader = new FilesystemLoader($directory); +$twig = new Environment( + $loader, + [ + 'cache' => $cache, + 'auto_reload' => true, + ] +); + +$twig_functions = [ + '__', + '_T', + '_Tn', + '_Tx', + '_Tnx', + 'url_for', + 'memberName', + 'callstatic', + 'is_current_url', + 'get_class', + 'base_path', + 'file_exists' +]; + +foreach ($twig_functions as $function) { + $twig->addFunction(new TwigFunction($function, $function)); +} + +/** @var SplFileInfo $file */ +foreach ($iterator as $file) { + if ($file->isFile()) { + $twig->load(str_replace($directory . '/', '', $file)); + } +} diff --git a/lang/Makefile b/lang/Makefile new file mode 100644 index 0000000..4853dcd --- /dev/null +++ b/lang/Makefile @@ -0,0 +1,49 @@ +INSTALLDIR = . +LANGUAGES = `find ./ -name "activities_*.po" -print | sed 's|^\./activities_\(.*\).po|\1|'` +DOMAINS = activities +PHP_SOURCES = $(shell find ../ -maxdepth 1 -name \*.php) \ + $(shell find ../lib/GaletteActivities/ -name \*.php) \ + $(shell find ../tempcache -name \*.twig) + +all : extract mo + +twig : + ../bin/twig-cache + +po : + @echo "Generating PO files:" + @for l in ${LANGUAGES}; do \ + for d in ${DOMAINS}; do \ + if [ -f $${d}_$${l}.po ]; then \ + echo -n " Updating $${d}_$${l}.po"; \ + msgmerge -U $${d}_$${l}.po $$d.pot >/dev/null ; \ + else \ + echo " Creating of $${d}_$${l}.po"; \ + msginit -o $${d}_$${l}.po -i $$d.pot >/dev/null ; \ + fi; \ + done \ + done + +mo : + @echo "Generating MO files:" + @for l in ${LANGUAGES}; do \ + for d in ${DOMAINS}; do \ + mkdir -p ${INSTALLDIR}/$${l}/LC_MESSAGES; \ + echo " formatting ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo."; \ + msgfmt $${d}_$${l}.po -o ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo.new; \ + if diff -qI 'PO-Revision-Date:.*' ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo.new ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo > /dev/null; then \ + echo " $${d}.mo NOT updated."; \ + rm ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo.new; \ + else \ + echo " $${d}.mo UPDATED."; \ + mv ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo.new ${INSTALLDIR}/$${l}/LC_MESSAGES/$${d}.mo; \ + fi; \ + done \ + done + +extract : twig ${PHP_SOURCES} Makefile + xgettext ${PHP_SOURCES} --keyword=_T:1,2t --keyword=__:1,2t --keyword=_Tn:1,2,4t --keyword=_Tx:1c,2,3t --keyword=_Tnx:1c,2,3,5t -L PHP --from-code=UTF-8 --add-comments=TRANS --force-po -o activities.pot; + echo "Generating en_US:" + @for d in ${DOMAINS}; do \ + LANG=C msginit --no-translator -i $$d.pot --locale=en_US.utf8 -o $${d}_en_US.po; \ + done