stagit

static git page generator  https://git.ce9e.org
git clone https://git.ce9e.org/stagit.git

commit
ca93ddb84b07a99c75efcf88cc3035544124d3b5
parent
b0b3e29be7cc75d64781d0797fdc5fee97a45eaa
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2024-09-05 09:52
python: lint

Diffstat

M gitolite/shell.py 64 ++++++++++++++++++++++++++++++------------------------------
M gitolite/stagit.py 199 ++++++++++++++++++++++++++++++-------------------------------
M src/ctpl.py 98 ++++++++++++++++++++++++++++++------------------------------

3 files changed, 180 insertions, 181 deletions


diff --git a/gitolite/shell.py b/gitolite/shell.py

@@ -2,48 +2,48 @@
    2     2 # see man git-shell
    3     3 
    4     4 import os
    5    -1 import sys
    6     5 import re
    7     6 import subprocess
   -1     7 import sys
    8     8 
    9     9 import stagit
   10    10 
   11    11 SOC_RE = (
   12    -1 	r"^(git-receive-pack|git-upload-pack|git-upload-archive) "
   13    -1 	r"'?/?([a-z0-9_:-]+)(\.git)?/?'?$"
   -1    12     r"^(git-receive-pack|git-upload-pack|git-upload-archive) "
   -1    13     r"'?/?([a-z0-9_:-]+)(\.git)?/?'?$"
   14    14 )
   15    15 
   16    16 
   17    17 def parse_soc():
   18    -1 	soc = re.match(SOC_RE, os.environ['SSH_ORIGINAL_COMMAND'])
   19    -1 	if not soc:
   20    -1 		sys.stderr.write('invalid command\n')
   21    -1 		sys.exit(1)
   22    -1 	cmd, repo, _ = soc.groups()
   23    -1 	return cmd, repo
   -1    18     soc = re.match(SOC_RE, os.environ['SSH_ORIGINAL_COMMAND'])
   -1    19     if not soc:
   -1    20         sys.stderr.write('invalid command\n')
   -1    21         sys.exit(1)
   -1    22     cmd, repo, _ = soc.groups()
   -1    23     return cmd, repo
   24    24 
   25    25 
   26    26 if __name__ == '__main__':
   27    -1 	user = sys.argv[1]
   28    -1 
   29    -1 	if 'SSH_ORIGINAL_COMMAND' not in os.environ:
   30    -1 		for repo in stagit.config.iter_repos():
   31    -1 			if stagit.config.can_ssh(repo, user):
   32    -1 				print(repo)
   33    -1 		sys.exit(0)
   34    -1 
   35    -1 	cmd, repo = parse_soc()
   36    -1 
   37    -1 	if not stagit.config.can_ssh(repo, user):
   38    -1 		sys.stderr.write('access denied\n')
   39    -1 		sys.exit(1)
   40    -1 
   41    -1 	sys.exit(subprocess.call(
   42    -1 		[*cmd.split('-', 1), repo],
   43    -1 		cwd=stagit.REPO_DIR,
   44    -1 		env={
   45    -1 			**os.environ,
   46    -1 			'STAGIT_USER': user,
   47    -1 			'STAGIT_REPO': repo,
   48    -1 		},
   49    -1 	))
   -1    27     user = sys.argv[1]
   -1    28 
   -1    29     if 'SSH_ORIGINAL_COMMAND' not in os.environ:
   -1    30         for repo in stagit.config.iter_repos():
   -1    31             if stagit.config.can_ssh(repo, user):
   -1    32                 print(repo)
   -1    33         sys.exit(0)
   -1    34 
   -1    35     cmd, repo = parse_soc()
   -1    36 
   -1    37     if not stagit.config.can_ssh(repo, user):
   -1    38         sys.stderr.write('access denied\n')
   -1    39         sys.exit(1)
   -1    40 
   -1    41     sys.exit(subprocess.call(
   -1    42         [*cmd.split('-', 1), repo],
   -1    43         cwd=stagit.REPO_DIR,
   -1    44         env={
   -1    45             **os.environ,
   -1    46             'STAGIT_USER': user,
   -1    47             'STAGIT_REPO': repo,
   -1    48         },
   -1    49     ))

diff --git a/gitolite/stagit.py b/gitolite/stagit.py

@@ -1,9 +1,8 @@
   -1     1 import configparser
    1     2 import os
    2    -1 import sys
    3    -1 import stat
    4     3 import shutil
    5     4 import subprocess
    6    -1 import configparser
   -1     5 import sys
    7     6 from xml.sax.saxutils import escape
    8     7 
    9     8 USER = 'git'
@@ -14,137 +13,137 @@ CONF_PATH = os.path.expanduser('~/stagit.conf')
   14    13 
   15    14 
   16    15 if os.environ.get('USER') != USER:
   17    -1 	sys.stderr.write('wrong user\n')
   18    -1 	sys.exit(1)
   -1    16     sys.stderr.write('wrong user\n')
   -1    17     sys.exit(1)
   19    18 
   20    19 
   21    20 def create_sh_script(path, lines):
   22    -1 	if lines:
   23    -1 		with open(path, 'w') as fh:
   24    -1 			fh.write('#!/bin/sh\n')
   25    -1 			for line in lines:
   26    -1 				fh.write(line + '\n')
   27    -1 		os.chmod(path, 0o775)
   28    -1 	elif os.path.exists(path):
   29    -1 		os.unlink(path)
   -1    21     if lines:
   -1    22         with open(path, 'w') as fh:
   -1    23             fh.write('#!/bin/sh\n')
   -1    24             for line in lines:
   -1    25                 fh.write(line + '\n')
   -1    26         os.chmod(path, 0o775)
   -1    27     elif os.path.exists(path):
   -1    28         os.unlink(path)
   30    29 
   31    30 
   32    31 def touch(path):
   33    -1 	with open(path, 'a'):
   34    -1 		os.utime(path, None)
   -1    32     with open(path, 'a'):
   -1    33         os.utime(path, None)
   35    34 
   36    35 
   37    36 def get_repo_dir(repo):
   38    -1 	return os.path.join(REPO_DIR, repo + '.git')
   -1    37     return os.path.join(REPO_DIR, repo + '.git')
   39    38 
   40    39 
   41    40 class Config:
   42    -1 	def __init__(self):
   43    -1 		self.config = configparser.ConfigParser()
   44    -1 		self.config.read(CONF_PATH)
   -1    41     def __init__(self):
   -1    42         self.config = configparser.ConfigParser()
   -1    43         self.config.read(CONF_PATH)
   45    44 
   46    -1 	def get(self, section, key):
   47    -1 		return self.config.get(section, key, fallback='')
   -1    45     def get(self, section, key):
   -1    46         return self.config.get(section, key, fallback='')
   48    47 
   49    -1 	def getboolean(self, section, key):
   50    -1 		return self.config.getboolean(section, key, fallback=False)
   -1    48     def getboolean(self, section, key):
   -1    49         return self.config.getboolean(section, key, fallback=False)
   51    50 
   52    -1 	def can_ssh(self, repo, user):
   53    -1 		allowed = self.get(repo, 'ssh').split()
   54    -1 		return user in allowed or '@all' in allowed
   -1    51     def can_ssh(self, repo, user):
   -1    52         allowed = self.get(repo, 'ssh').split()
   -1    53         return user in allowed or '@all' in allowed
   55    54 
   56    -1 	def iter_repos(self):
   57    -1 		for key in self.config:
   58    -1 			if key != 'DEFAULT':
   59    -1 				yield key
   -1    55     def iter_repos(self):
   -1    56         for key in self.config:
   -1    57             if key != 'DEFAULT':
   -1    58                 yield key
   60    59 
   61    60 
   62    61 config = Config()
   63    62 
   64    63 
   65    64 def update_repo(repo):
   66    -1 	repodir = get_repo_dir(repo)
   67    -1 	if not os.path.exists(repodir):
   68    -1 		os.makedirs(repodir, exist_ok=True)
   69    -1 		subprocess.check_call(['git', 'init', '--bare'], cwd=repodir)
   70    -1 
   71    -1 	export_ok = os.path.join(repodir, 'git-daemon-export-ok')
   72    -1 	post_update = []
   73    -1 	if config.getboolean(repo, 'http'):
   74    -1 		post_update.append(
   75    -1 			'python3 -c \'import os, stagit; '
   76    -1 			'stagit.render_repo(os.environ["STAGIT_REPO"])\''
   77    -1 		)
   78    -1 		touch(export_ok)
   79    -1 	else:
   80    -1 		if os.path.exists(export_ok):
   81    -1 			os.unlink(export_ok)
   82    -1 
   83    -1 	if config.get(repo, 'post-update'):
   84    -1 		post_update.append(config.get(repo, 'post-update'))
   85    -1 	create_sh_script(os.path.join(repodir, 'hooks', 'post-update'), post_update)
   86    -1 
   87    -1 	with open(os.path.join(repodir, 'description'), 'w') as fh:
   88    -1 		fh.write(config.get(repo, 'desc'))
   89    -1 	with open(os.path.join(repodir, 'url'), 'w') as fh:
   90    -1 		fh.write(config.get(repo, 'url'))
   -1    65     repodir = get_repo_dir(repo)
   -1    66     if not os.path.exists(repodir):
   -1    67         os.makedirs(repodir, exist_ok=True)
   -1    68         subprocess.check_call(['git', 'init', '--bare'], cwd=repodir)
   -1    69 
   -1    70     export_ok = os.path.join(repodir, 'git-daemon-export-ok')
   -1    71     post_update = []
   -1    72     if config.getboolean(repo, 'http'):
   -1    73         post_update.append(
   -1    74             "python3 -c 'import os, stagit; "
   -1    75             'stagit.render_repo(os.environ["STAGIT_REPO"])\''
   -1    76         )
   -1    77         touch(export_ok)
   -1    78     else:
   -1    79         if os.path.exists(export_ok):
   -1    80             os.unlink(export_ok)
   -1    81 
   -1    82     if config.get(repo, 'post-update'):
   -1    83         post_update.append(config.get(repo, 'post-update'))
   -1    84     create_sh_script(os.path.join(repodir, 'hooks', 'post-update'), post_update)
   -1    85 
   -1    86     with open(os.path.join(repodir, 'description'), 'w') as fh:
   -1    87         fh.write(config.get(repo, 'desc'))
   -1    88     with open(os.path.join(repodir, 'url'), 'w') as fh:
   -1    89         fh.write(config.get(repo, 'url'))
   91    90 
   92    91 
   93    92 def render_index():
   94    -1 	fh = open(os.path.join(WWW_DIR, 'index.html'), 'w')
   95    -1 	fh.write('<!DOCTYPE html>\n')
   96    -1 	fh.write('<html>\n<head>\n')
   97    -1 	fh.write('<meta charset="UTF-8">')
   98    -1 	fh.write('<meta name="viewport" content="width=device-width">\n')
   99    -1 	fh.write('<title>Repositories</title>\n')
  100    -1 	fh.write('<link rel="stylesheet" type="text/css" href="style.css" />\n')
  101    -1 	fh.write('</head>\n<body>\n')
  102    -1 	fh.write('<h1>Repositories</h1>')
  103    -1 	fh.write('<hr/>\n')
  104    -1 	fh.write('<main id="content">\n')
  105    -1 	fh.write('<table>\n<thead>\n')
  106    -1 	fh.write('<tr><th>Name</th><th>Description</th></tr>\n')
  107    -1 	fh.write('</thead>\n<tbody>\n')
  108    -1 	for repo in config.iter_repos():
  109    -1 		if config.getboolean(repo, 'http'):
  110    -1 			fh.write('<tr><td><a href="%s/">%s</a></td><td>%s</td></tr>\n' % (
  111    -1 				escape(repo), escape(repo), escape(config.get(repo, 'desc'))
  112    -1 			))
  113    -1 	fh.write('</tbody>\n</table>\n')
  114    -1 	fh.write('</main>\n</body>\n</html>\n')
  115    -1 	fh.close()
   -1    93     fh = open(os.path.join(WWW_DIR, 'index.html'), 'w')
   -1    94     fh.write('<!DOCTYPE html>\n')
   -1    95     fh.write('<html>\n<head>\n')
   -1    96     fh.write('<meta charset="UTF-8">')
   -1    97     fh.write('<meta name="viewport" content="width=device-width">\n')
   -1    98     fh.write('<title>Repositories</title>\n')
   -1    99     fh.write('<link rel="stylesheet" type="text/css" href="style.css" />\n')
   -1   100     fh.write('</head>\n<body>\n')
   -1   101     fh.write('<h1>Repositories</h1>')
   -1   102     fh.write('<hr/>\n')
   -1   103     fh.write('<main id="content">\n')
   -1   104     fh.write('<table>\n<thead>\n')
   -1   105     fh.write('<tr><th>Name</th><th>Description</th></tr>\n')
   -1   106     fh.write('</thead>\n<tbody>\n')
   -1   107     for repo in config.iter_repos():
   -1   108         if config.getboolean(repo, 'http'):
   -1   109             fh.write('<tr><td><a href="{}/">{}</a></td><td>{}</td></tr>\n'.format(
   -1   110                 escape(repo), escape(repo), escape(config.get(repo, 'desc'))
   -1   111             ))
   -1   112     fh.write('</tbody>\n</table>\n')
   -1   113     fh.write('</main>\n</body>\n</html>\n')
   -1   114     fh.close()
  116   115 
  117   116 
  118   117 def render_repo(repo):
  119    -1 	print('Generating HTML for %s…' % repo)
  120    -1 	target_dir = os.path.join(WWW_DIR, repo)
  121    -1 	if os.path.isdir(target_dir):
  122    -1 		shutil.rmtree(target_dir)
  123    -1 	os.makedirs(target_dir)
  124    -1 	subprocess.check_call(['stagit', get_repo_dir(repo)], cwd=target_dir)
   -1   118     print(f'Generating HTML for {repo}…')
   -1   119     target_dir = os.path.join(WWW_DIR, repo)
   -1   120     if os.path.isdir(target_dir):
   -1   121         shutil.rmtree(target_dir)
   -1   122     os.makedirs(target_dir)
   -1   123     subprocess.check_call(['stagit', get_repo_dir(repo)], cwd=target_dir)
  125   124 
  126   125 
  127   126 def render_all():
  128    -1 	for repo in os.listdir(WWW_DIR):
  129    -1 		path = os.path.join(WWW_DIR, repo)
  130    -1 		if os.path.isdir(path):
  131    -1 			shutil.rmtree(path)
  132    -1 	for repo in config.iter_repos():
  133    -1 		if config.getboolean(repo, 'http'):
  134    -1 			render_repo(repo)
  135    -1 	render_index()
   -1   127     for repo in os.listdir(WWW_DIR):
   -1   128         path = os.path.join(WWW_DIR, repo)
   -1   129         if os.path.isdir(path):
   -1   130             shutil.rmtree(path)
   -1   131     for repo in config.iter_repos():
   -1   132         if config.getboolean(repo, 'http'):
   -1   133             render_repo(repo)
   -1   134     render_index()
  136   135 
  137   136 
  138   137 if __name__ == '__main__':
  139    -1 	all_repos = set(config.iter_repos())
  140    -1 	phy_repos = set(fn[:-4] for fn in os.listdir(REPO_DIR) if fn.endswith('.git'))
  141    -1 	stale_repos = phy_repos - all_repos
   -1   138     all_repos = set(config.iter_repos())
   -1   139     phy_repos = set(fn[:-4] for fn in os.listdir(REPO_DIR) if fn.endswith('.git'))
   -1   140     stale_repos = phy_repos - all_repos
  142   141 
  143    -1 	print('Updating repos …')
  144    -1 	for repo in all_repos:
  145    -1 		update_repo(repo)
   -1   142     print('Updating repos …')
   -1   143     for repo in all_repos:
   -1   144         update_repo(repo)
  146   145 
  147    -1 	if stale_repos:
  148    -1 		print('Warning: stale files for deleted repos:', ', '.join(stale_repos))
   -1   146     if stale_repos:
   -1   147         print('Warning: stale files for deleted repos:', ', '.join(stale_repos))
  149   148 
  150    -1 	render_all()
   -1   149     render_all()

diff --git a/src/ctpl.py b/src/ctpl.py

@@ -1,66 +1,66 @@
    1    -1 import sys
    2     1 import re
   -1     2 import sys
    3     3 
    4     4 
    5     5 def tokenize(s):
    6    -1 	tokens = []
    7    -1 	start = 0
    8    -1 	for i, c in enumerate(s):
    9    -1 		if c == '{':
   10    -1 			if i > start:
   11    -1 				tokens.append((True, s[start:i]))
   12    -1 			start = i + 1
   13    -1 		elif c == '}':
   14    -1 			if i > start:
   15    -1 				tokens.append((False, s[start:i]))
   16    -1 			start = i + 1
   17    -1 	if i + 1 > start:
   18    -1 		tokens.append((True, s[start:i+1]))
   19    -1 	return tokens
   -1     6     tokens = []
   -1     7     start = 0
   -1     8     for i, c in enumerate(s):
   -1     9         if c == '{':
   -1    10             if i > start:
   -1    11                 tokens.append((True, s[start:i]))
   -1    12             start = i + 1
   -1    13         elif c == '}':
   -1    14             if i > start:
   -1    15                 tokens.append((False, s[start:i]))
   -1    16             start = i + 1
   -1    17     if i + 1 > start:
   -1    18         tokens.append((True, s[start:i+1]))
   -1    19     return tokens
   20    20 
   21    21 
   22    22 def print_safe(s, args):
   23    -1 	if args:
   24    -1 		return ['fprintf(fp, "%s", %s);' % (s, ', '.join(args))]
   25    -1 	elif s:
   26    -1 		return ['fputs("%s", fp);' % s]
   27    -1 	return []
   -1    23     if args:
   -1    24         return [f'fprintf(fp, "{s}", {", ".join(args)});']
   -1    25     elif s:
   -1    26         return [f'fputs("{s}", fp);']
   -1    27     return []
   28    28 
   29    29 
   30    30 def parse_line(indent, tpl):
   31    -1 	lines = []
   32    -1 	s = ''
   33    -1 	args = []
   34    -1 	for is_text, token in tokenize(tpl):
   35    -1 		if is_text:
   36    -1 			s += token
   37    -1 		elif ':%' in token:
   38    -1 			arg, placeholder = token.split(':', 1)
   39    -1 			s += placeholder
   40    -1 			args.append(arg)
   41    -1 		else:
   42    -1 			lines += print_safe(s, args)
   43    -1 			s = ''
   44    -1 			args = []
   -1    31     lines = []
   -1    32     s = ''
   -1    33     args = []
   -1    34     for is_text, token in tokenize(tpl):
   -1    35         if is_text:
   -1    36             s += token
   -1    37         elif ':%' in token:
   -1    38             arg, placeholder = token.split(':', 1)
   -1    39             s += placeholder
   -1    40             args.append(arg)
   -1    41         else:
   -1    42             lines += print_safe(s, args)
   -1    43             s = ''
   -1    44             args = []
   45    45 
   46    -1 			if ':' in token:
   47    -1 				arg, l = token.split(':', 1)
   48    -1 			else:
   49    -1 				arg = token
   50    -1 				l = 'strlen(%s)' % token
   -1    46             if ':' in token:
   -1    47                 arg, length = token.split(':', 1)
   -1    48             else:
   -1    49                 arg = token
   -1    50                 length = f'strlen({token})'
   51    51 
   52    -1 			lines.append('xmlencode(fp, %s, %s);' % (arg, l))
   53    -1 	lines += print_safe(s, args)
   -1    52             lines.append(f'xmlencode(fp, {arg}, {length});')
   -1    53     lines += print_safe(s, args)
   54    54 
   55    -1 	print(indent + ' '.join(lines))
   -1    55     print(indent + ' '.join(lines))
   56    56 
   57    57 
   58    58 if __name__ == '__main__':
   59    -1 	fh = open(sys.argv[1])
   -1    59     fh = open(sys.argv[1])
   60    60 
   61    -1 	for line in fh:
   62    -1 		match = re.match(r'(\s*)T\("(.*)"\);\n$', line)
   63    -1 		if match:
   64    -1 			parse_line(*match.groups())
   65    -1 		else:
   66    -1 			print(line.rstrip())
   -1    61     for line in fh:
   -1    62         match = re.match(r'(\s*)T\("(.*)"\);\n$', line)
   -1    63         if match:
   -1    64             parse_line(*match.groups())
   -1    65         else:
   -1    66             print(line.rstrip())