project-stats

keep track of your projects
git clone https://git.ce9e.org/project-stats.git

commit
12222b9d2dab2e0c430350d779a8c5c0ffc6e6d2
parent
0d31a6e062ed54029e9976a52a7d3af4028d0eb5
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2025-08-24 06:52
lint

Diffstat

M project_stats.py 57 +++++++++++++++++++++++++++++++--------------------------

1 files changed, 31 insertions, 26 deletions


diff --git a/project_stats.py b/project_stats.py

@@ -1,4 +1,3 @@
    1    -1 from functools import total_ordering
    2     1 import argparse
    3     2 import asyncio
    4     3 import logging
@@ -6,10 +5,11 @@ import os
    6     5 import re
    7     6 import subprocess
    8     7 import sys
   -1     8 from functools import total_ordering
    9     9 
   10    -1 from dateutil import parser as dt
   11    10 import aiohttp
   12    11 import yaml
   -1    12 from dateutil import parser as dt
   13    13 
   14    14 try:
   15    15     from cheesecake.cheesecake_index import Cheesecake
@@ -71,13 +71,13 @@ def r_get(d, *keys):
   71    71 
   72    72 
   73    73 @total_ordering
   74    -1 class Claims(object):
   -1    74 class Claims:
   75    75     def __init__(self):
   76    76         self._list = []
   77    77 
   78    78     def _index(self, key):
   79    79         i = 0
   80    -1         for k, value in self._list:
   -1    80         for k, _ in self._list:
   81    81             if key == k:
   82    82                 return i
   83    83             i += 1
@@ -96,11 +96,12 @@ class Claims(object):
   96    96     def values(self):
   97    97         return [value for value, sources in self._list]
   98    98 
   99    -1     def format(self, show_sources=True):
   -1    99     def format(self, *, show_sources=True):
  100   100         def _format_claim(value, sources):
  101   101             s = str(value)
  102   102             if show_sources:
  103    -1                 s += ' (%s)' % ', '.join(sources)
   -1   103                 joined = ', '.join(sources)
   -1   104                 s += f' ({joined})'
  104   105             return s
  105   106         return '; '.join([_format_claim(v, srcs) for v, srcs in self._list])
  106   107 
@@ -108,7 +109,7 @@ class Claims(object):
  108   109         return self.values() < other.values()
  109   110 
  110   111 
  111    -1 class ClaimsDict(object):
   -1   112 class ClaimsDict:
  112   113     def __init__(self, keys, short=9):
  113   114         self._keys = keys
  114   115         self._short = short
@@ -137,7 +138,7 @@ class ClaimsDict(object):
  137   138         except KeyError:
  138   139             return default
  139   140 
  140    -1     def format(self, short=False, indent=0, show_sources=True):
   -1   141     def format(self, *, short=False, indent=0, show_sources=True):
  141   142         keys = self._keys[:self._short] if short else self._keys
  142   143         lines = []
  143   144         for key in keys:
@@ -145,7 +146,7 @@ class ClaimsDict(object):
  145   146             formated_value = value.format(show_sources=show_sources)
  146   147             if not formated_value:
  147   148                 continue
  148    -1             lines.append(' ' * indent + '%s: %s' % (key, formated_value))
   -1   149             lines.append(' ' * indent + f'{key}: {formated_value}')
  149   150         return '\n'.join(lines)
  150   151 
  151   152 
@@ -167,7 +168,7 @@ async def get_json(url, user=None, token=None):
  167   168     if user is not None:
  168   169         # FIXME: not very robust
  169   170         url += '&' if '?' in url else '?'
  170    -1         url += 'login=%s&token=%s' % (user, token)
   -1   171         url += f'login={user}&token={token}'
  171   172 
  172   173     async with aiohttp.ClientSession() as session:
  173   174         async with session.get(url) as resp:
@@ -231,7 +232,8 @@ async def get_gitlab(_id, token=None):
  231   232     data, issues, pulls = await asyncio.gather(
  232   233         _get_json(''),
  233   234         _get_json('/issues?state=opened'),
  234    -1         _get_json('/merge_requests?state=opened'))
   -1   235         _get_json('/merge_requests?state=opened'),
   -1   236     )
  235   237 
  236   238     return {
  237   239         'name': data['name'],
@@ -248,7 +250,7 @@ async def get_gitlab(_id, token=None):
  248   250 
  249   251 async def get_local(path):
  250   252     def git(cmd, *args):
  251    -1         _cmd = ['git', '-C', path, cmd] + list(args)
   -1   253         _cmd = ['git', '-C', path, cmd, *args]
  252   254         return subprocess.check_output(_cmd).decode('utf8')
  253   255 
  254   256     def get_latest_tag():
@@ -276,7 +278,7 @@ async def get_local(path):
  276   278 
  277   279 
  278   280 async def get_pypi(name):
  279    -1     data = await get_json('https://pypi.org/pypi/{}/json'.format(name))
   -1   281     data = await get_json(f'https://pypi.org/pypi/{name}/json')
  280   282     return {
  281   283         'version': data['info']['version'],
  282   284         'description': data['info']['summary'],
@@ -299,7 +301,8 @@ async def get_npm(name):
  299   301         'time.created',
  300   302         'time.modified',
  301   303         stdout=asyncio.subprocess.PIPE,
  302    -1         stderr=asyncio.subprocess.PIPE)
   -1   304         stderr=asyncio.subprocess.PIPE,
   -1   305     )
  303   306     stdout, stderr = await process.communicate()
  304   307     if process.returncode != 0:
  305   308         return
@@ -382,7 +385,8 @@ async def get_project(key, project, config):
  382   385 def get_projects(projects_config, config):
  383   386     projects_list = aiorun(asyncio.gather(*[
  384   387         get_project(key, project, config)
  385    -1         for key, project in projects_config.items()]))
   -1   388         for key, project in projects_config.items()
   -1   389     ]))
  386   390 
  387   391     projects = {}
  388   392     for key, project in zip(projects_config.keys(), projects_list):
@@ -406,7 +410,7 @@ def select_config(args):
  406   410             if os.path.exists(path):
  407   411                 return path
  408   412 
  409    -1         print('No config file available. Tried %s.' % ', '.join(choices))
   -1   413         print(f'No config file available. Tried {", ".join(choices)}.')
  410   414         sys.exit(1)
  411   415 
  412   416 
@@ -422,20 +426,20 @@ def parse_args():
  422   426     parser.add_argument(
  423   427         '-l', '--list',
  424   428         action='store_true',
  425    -1         help='only list projects; do not show any stats')
   -1   429         help='only list projects; do not show any stats',
   -1   430     )
  426   431     parser.add_argument(
  427   432         '-s', '--short',
  428   433         action='store_true',
  429    -1         help='show only basic stats')
   -1   434         help='show only basic stats',
   -1   435     )
  430   436     parser.add_argument('-c', '--config')
  431    -1     parser.add_argument(
  432    -1         '-z', '--sort',
  433    -1         metavar='KEY',
  434    -1         help='sort by key')
   -1   437     parser.add_argument('-z', '--sort', metavar='KEY', help='sort by key')
  435   438     parser.add_argument(
  436   439         '-S', '--show-sources',
  437   440         action='store_true',
  438    -1         help='show a source for each claim')
   -1   441         help='show a source for each claim',
   -1   442     )
  439   443 
  440   444     return parser.parse_args()
  441   445 
@@ -464,11 +468,12 @@ def main():
  464   468                 claim = projects[key][args.sort]
  465   469                 print(key, claim.format(show_sources=False))
  466   470             else:
  467    -1                 claims = projects[key]
  468    -1                 print('%s\n%s\n' % (key, claims.format(
   -1   471                 print(key)
   -1   472                 print(projects[key].format(
  469   473                     indent=2,
  470   474                     short=args.short,
  471    -1                     show_sources=args.show_sources)))
   -1   475                     show_sources=args.show_sources,
   -1   476                 ))
  472   477 
  473   478 
  474   479 if __name__ == '__main__':