code-node-graph 0.4.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/CHANGELOG.md +35 -1
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/PKG-INFO +2 -1
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/README.md +1 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/__init__.py +1 -1
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/graph.py +3 -1
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/model.py +57 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/cli.py +9 -1
- code_node_graph-0.6.0/src/code_node/history.py +156 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/pages.py +6 -2
- code_node_graph-0.6.0/src/code_node/report/changes.py +116 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/graph.py +197 -40
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/style.py +79 -18
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/walk.py +20 -5
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/tests/test_code_node.py +65 -2
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/.gitignore +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/LICENSE +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/examples/books-api/README.md +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/examples/cart-service/README.md +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/examples/orders-service/README.md +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/pyproject.toml +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/__main__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/__init__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/aws_model.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/findings.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/mapping.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/reader.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/syntax.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/analysis/terms.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/deploys/__init__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/deploys/sam.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/deploys/serverless.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/kit/__init__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/kit/blocks.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/kit/fmt.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/kit/highlight.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/kit/render.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/kit/text.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/libraries/__init__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/libraries/llm.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/libraries/mongo.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/libraries/redis.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/libraries/sql.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/platforms/__init__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/platforms/aws_lambda.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/platforms/web.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/project.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/__init__.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/brands.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/build.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/editor.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/README.md +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/anthropic-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-amplify.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-api-gateway.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-appflow.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-appsync.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-athena.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-backup.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-batch.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-certificate-manager.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-cloudformation.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-cloudfront.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-cloudtrail.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-cloudwatch.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-codebuild.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-codecommit.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-codedeploy.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-codepipeline.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-cognito.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-config.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-documentdb.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-dynamodb.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-ec2.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-ecs.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-eks.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-elasticache.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-elb.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-eventbridge.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-glacier.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-glue.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-iam.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-keyspaces.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-kinesis.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-kms.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-lake-formation.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-lambda.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-lightsail.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-mq.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-msk.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-neptune.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-open-search.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-quicksight.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-rds.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-redshift.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-route53.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-s3.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-secrets-manager.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-ses.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-shield.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-sns.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-sqs.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-step-functions.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-systems-manager.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-timestream.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-waf.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws-xray.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/aws.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/azure-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/django-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/elasticsearch.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/fastapi-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/firebase-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/flask.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/google-cloud.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/kafka-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/mariadb-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/mongodb-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/mysql-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/openai-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/postgresql.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/python.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/rabbitmq-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/redis.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/serverless.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/slack-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-api-gateway.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-appflow.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-appsync.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-athena.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-backup.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-batch.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-certificate-manager.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-cloudformation.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-cloudfront.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-cloudtrail.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-cloudwatch.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-codebuild.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-codecommit.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-codedeploy.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-codepipeline.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-cognito.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-config.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-documentdb.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-dynamodb.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-ec2.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-ecs.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-eks.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-elasticache.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-elb.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-eventbridge.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-glacier.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-glue.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-iam.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-keyspaces.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-kinesis.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-kms.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-lake-formation.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-lambda.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-mq.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-msk.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-neptune.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-open-search.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-quicksight.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-rds.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-redshift.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-route53.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-s3.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-secrets-manager.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-ses.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-shield.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-sns.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-sqs.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-step-functions.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-systems-manager.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-timestream.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-waf.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/small/aws-xray.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/snowflake-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/sqlite.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/stripe.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/supabase-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/logos/twilio-icon.svg +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/outline.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/report/tabs.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/src/code_node/serve.py +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/tests/snapshots/books-api.txt +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/tests/snapshots/cart-service.txt +0 -0
- {code_node_graph-0.4.0 → code_node_graph-0.6.0}/tests/snapshots/orders-service.txt +0 -0
|
@@ -4,6 +4,38 @@ Every change a user would notice, newest first ([Keep a Changelog](https://keepa
|
|
|
4
4
|
follow [Semantic Versioning](https://semver.org/); before 1.0, a minor version may change what a command does.
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.0] - 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- A function's code panel says beside each line who changed it last and how long ago ("Asha · 3d"), with a bar
|
|
12
|
+
that's darker the newer the line and amber for a line not committed yet. Its last row says when the function was
|
|
13
|
+
last changed, by whom, in which commit, and how many commits and people it comes from; point at a line and it says
|
|
14
|
+
that line's commit instead. The gear by × opens the panel's settings, where the history can be turned off; the
|
|
15
|
+
page remembers that. The panel's head is plainer: the name, then what it is, where, and a link to open it in your
|
|
16
|
+
editor, without the def repeated over the code.
|
|
17
|
+
- Code on the page is set in Cascadia Code when it's installed, before Cascadia Mono and the system's monospace
|
|
18
|
+
font, and the docs site's code is in Cascadia Code instead of IBM Plex Mono.
|
|
19
|
+
|
|
20
|
+
## [0.5.0] - 2026-10-20
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- The graph moves like a canvas: drag it to pan in any direction, hold Ctrl (⌘ on a Mac) and scroll, or pinch a
|
|
25
|
+
trackpad, to zoom around the pointer, and `+`, `-` and `0` when it has focus. It stays in a box as wide as the page and
|
|
26
|
+
about the window's height, however far it's zoomed, so the scroll bar under a tall graph is no longer the only way across;
|
|
27
|
+
**Fit** puts all of it in that box, and − and + buttons in its corner say how big it's drawn. This needs JavaScript;
|
|
28
|
+
without it the graph scrolls as before.
|
|
29
|
+
|
|
30
|
+
- Each function says what git says about it, when the folder is in git: who changed it last, how long ago and in
|
|
31
|
+
which commit (a link to the commit on GitHub or GitLab), how many commits and people its lines come from, and how
|
|
32
|
+
many of its lines changed since the last commit. It's in a box's hover card and code panel and in the step that
|
|
33
|
+
goes into it. It's read with `git blame`, once per file; `--no-git` leaves it out.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- A box's code opens in a panel over the graph, rather than pushing the graph aside on a wide screen.
|
|
38
|
+
|
|
7
39
|
## [0.4.0] - 2026-10-20
|
|
8
40
|
|
|
9
41
|
### Fixed
|
|
@@ -193,7 +225,9 @@ follow [Semantic Versioning](https://semver.org/); before 1.0, a minor version m
|
|
|
193
225
|
- `--platform NAME` runs a platform's checks whatever the entry point is; `--lambda` is short for
|
|
194
226
|
`--platform lambda`. `EntryPoint.is_lambda` and `PageOptions.is_lambda` are now `platform` (`"lambda"` or `""`).
|
|
195
227
|
|
|
196
|
-
[Unreleased]: https://github.com/utkarsh5026/code-node/compare/v0.
|
|
228
|
+
[Unreleased]: https://github.com/utkarsh5026/code-node/compare/v0.6.0...HEAD
|
|
229
|
+
[0.6.0]: https://github.com/utkarsh5026/code-node/compare/v0.5.0...v0.6.0
|
|
230
|
+
[0.5.0]: https://github.com/utkarsh5026/code-node/compare/v0.4.0...v0.5.0
|
|
197
231
|
[0.4.0]: https://github.com/utkarsh5026/code-node/compare/v0.3.0...v0.4.0
|
|
198
232
|
[0.3.0]: https://github.com/utkarsh5026/code-node/compare/v0.2.0...v0.3.0
|
|
199
233
|
[0.2.0]: https://github.com/utkarsh5026/code-node/compare/v0.1.0...v0.2.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: code-node-graph
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: See how Python code works without running it: a graph of who calls whom from an entry point, and what each path reaches.
|
|
5
5
|
Project-URL: Homepage, https://github.com/utkarsh5026/code-node
|
|
6
6
|
Project-URL: Documentation, https://utkarsh5026.github.io/code-node/
|
|
@@ -117,6 +117,7 @@ string is flagged.
|
|
|
117
117
|
| `-o`, `--out`, `--no-open` | Where to save (default `~/.cache/code-node/`), and whether to open it |
|
|
118
118
|
| `--platform NAME`, `--lambda` | Run a platform's checks on any entry point |
|
|
119
119
|
| `--editor vscode\|cursor\|none` | Editor the line links open (default `vscode`, WSL-aware) |
|
|
120
|
+
| `--no-git` | Leave out what git says about each function (who changed it last, when, in which commit) |
|
|
120
121
|
|
|
121
122
|
</details>
|
|
122
123
|
|
|
@@ -87,6 +87,7 @@ string is flagged.
|
|
|
87
87
|
| `-o`, `--out`, `--no-open` | Where to save (default `~/.cache/code-node/`), and whether to open it |
|
|
88
88
|
| `--platform NAME`, `--lambda` | Run a platform's checks on any entry point |
|
|
89
89
|
| `--editor vscode\|cursor\|none` | Editor the line links open (default `vscode`, WSL-aware) |
|
|
90
|
+
| `--no-git` | Leave out what git says about each function (who changed it last, when, in which commit) |
|
|
90
91
|
|
|
91
92
|
</details>
|
|
92
93
|
|
|
@@ -13,7 +13,7 @@ from .libraries import Library, register_library
|
|
|
13
13
|
from .platforms import Platform, get_platform, register_platform
|
|
14
14
|
from .project import EntryPoint, Project, entry_points, read_project
|
|
15
15
|
|
|
16
|
-
__version__ = "0.
|
|
16
|
+
__version__ = "0.6.0"
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"CodeGraph",
|
|
@@ -15,7 +15,7 @@ from ..kit.text import _clip
|
|
|
15
15
|
|
|
16
16
|
from .syntax import _a
|
|
17
17
|
from .aws_model import _service_name
|
|
18
|
-
from .model import AwsCall, CodeFunction, CodeMap, CodeSpot, CodeStep, LibraryCall
|
|
18
|
+
from .model import AwsCall, CodeFunction, CodeMap, CodeSpot, CodeStep, History, LibraryCall
|
|
19
19
|
from .findings import _listed, _short_name
|
|
20
20
|
from ..platforms import get_platform
|
|
21
21
|
|
|
@@ -56,6 +56,7 @@ class GraphNode:
|
|
|
56
56
|
icon: str = "" # a service's icon, when its plugin names one: 'database', 'queue', 'spark'; or the entry
|
|
57
57
|
# point's logo, when its platform names one: 'fastapi-icon'
|
|
58
58
|
name: str = "" # a function's box: the function, as module.name ('orders.repo.save')
|
|
59
|
+
history: History | None = None # a function's box: what git says about its lines
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
@dataclass
|
|
@@ -834,6 +835,7 @@ def code_graph(
|
|
|
834
835
|
when=_when(f, f.called_from),
|
|
835
836
|
icon=platform.entry_icon(cmap) if is_entry and not focus else "",
|
|
836
837
|
name=f.name,
|
|
838
|
+
history=f.history,
|
|
837
839
|
)
|
|
838
840
|
)
|
|
839
841
|
out_column = 1 + max([n.column for n in graph.nodes if n.kind in ("handler", "function", "start")] + [1])
|
|
@@ -173,6 +173,62 @@ class EventField:
|
|
|
173
173
|
return _event_label(self.path)
|
|
174
174
|
|
|
175
175
|
|
|
176
|
+
@dataclass
|
|
177
|
+
class Commit:
|
|
178
|
+
"""
|
|
179
|
+
A commit, as git blame tells it.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
sha: str
|
|
183
|
+
author: str
|
|
184
|
+
time: int # when it was made, in seconds since 1970
|
|
185
|
+
subject: str # its message's first line
|
|
186
|
+
url: str = "" # its page on the repository's host, when that's GitHub or GitLab
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def short(self) -> str:
|
|
190
|
+
return self.sha[:7]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@dataclass
|
|
194
|
+
class History:
|
|
195
|
+
"""
|
|
196
|
+
What git says about a function's lines as they are now: the commit that last changed one of them, how many commits
|
|
197
|
+
and people they come from, and how many have changed since the last commit. Read by history.py; None on a
|
|
198
|
+
function when the folder isn't in git.
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
last: Commit | None # the newest commit among its lines; None when none of them is committed yet
|
|
202
|
+
commits: int # the commits its lines come from
|
|
203
|
+
authors: list[str] # who wrote them, most lines first
|
|
204
|
+
first: int # when the oldest of them was made (about when the function was written), in seconds since 1970
|
|
205
|
+
uncommitted: int # its lines changed since the last commit
|
|
206
|
+
now: int # when this was read, for 'three days ago'
|
|
207
|
+
lines: dict[int, Commit | None] = field(default_factory=dict) # each of its lines -> the commit it comes from,
|
|
208
|
+
# None for one changed since the last commit
|
|
209
|
+
|
|
210
|
+
def ago(self, time: int) -> str:
|
|
211
|
+
"""
|
|
212
|
+
'today', '3 days ago', '2 months ago', '1 year ago'.
|
|
213
|
+
"""
|
|
214
|
+
days = max(0, self.now - time) // 86_400
|
|
215
|
+
for size, unit in ((365, "year"), (30, "month"), (7, "week"), (1, "day")):
|
|
216
|
+
if days >= size:
|
|
217
|
+
count = days // size
|
|
218
|
+
return f"{count} {unit}{'' if count == 1 else 's'} ago"
|
|
219
|
+
return "today"
|
|
220
|
+
|
|
221
|
+
def since(self, time: int) -> str:
|
|
222
|
+
"""
|
|
223
|
+
The same, short, for beside a line: 'today', '3d', '2w', '5mo', '1y'.
|
|
224
|
+
"""
|
|
225
|
+
days = max(0, self.now - time) // 86_400
|
|
226
|
+
for size, unit in ((365, "y"), (30, "mo"), (7, "w"), (1, "d")):
|
|
227
|
+
if days >= size:
|
|
228
|
+
return f"{days // size}{unit}"
|
|
229
|
+
return "today"
|
|
230
|
+
|
|
231
|
+
|
|
176
232
|
@dataclass
|
|
177
233
|
class CodeFunction:
|
|
178
234
|
"""
|
|
@@ -190,6 +246,7 @@ class CodeFunction:
|
|
|
190
246
|
# nearest around the call, else the nearest `if x: return` above it ('args.serve', not every check before it)
|
|
191
247
|
depth: int = 0 # calls from the entry point (the entry point is 0)
|
|
192
248
|
is_async: bool = False # async def: a call that blocks in it holds up everything else on its event loop
|
|
249
|
+
history: History | None = None # what git says about its lines, when the folder is in git (history.py)
|
|
193
250
|
|
|
194
251
|
|
|
195
252
|
@dataclass
|
|
@@ -99,6 +99,9 @@ def _parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|
|
99
99
|
default="vscode",
|
|
100
100
|
help="the editor the page's line links open (default vscode; none for no links)",
|
|
101
101
|
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--no-git", action="store_true", help="leave out what git says about each function (who changed it, when)"
|
|
104
|
+
)
|
|
102
105
|
return parser.parse_args(argv)
|
|
103
106
|
|
|
104
107
|
|
|
@@ -286,7 +289,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
286
289
|
if entry is None:
|
|
287
290
|
return 1
|
|
288
291
|
options = PageOptions(
|
|
289
|
-
draw_all=args.all,
|
|
292
|
+
draw_all=args.all,
|
|
293
|
+
functions=args.functions,
|
|
294
|
+
depth=args.depth,
|
|
295
|
+
platform=args.platform,
|
|
296
|
+
editor=args.editor,
|
|
297
|
+
git=not args.no_git,
|
|
290
298
|
)
|
|
291
299
|
if args.mermaid:
|
|
292
300
|
print(make_mermaid(project.root, entry, options))
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""
|
|
2
|
+
What git says about the functions a map shows: `git blame` on the lines of each one, once per file, for the commit
|
|
3
|
+
that last changed it, how many commits and people its lines come from, the lines changed since the last commit, and
|
|
4
|
+
the commit each line comes from.
|
|
5
|
+
|
|
6
|
+
git is run on the folder, never the code in it, and only to read: blame with no text conversion and no file system
|
|
7
|
+
monitor. A folder whose own git config names a filter command (one git would run on the files) isn't read at all.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import time
|
|
16
|
+
from collections import Counter
|
|
17
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from .analysis.model import CodeFunction, Commit, History
|
|
21
|
+
|
|
22
|
+
_TIMEOUT = 20 # seconds a git command may take before its file goes without history
|
|
23
|
+
|
|
24
|
+
_WORKERS = 8 # files blamed at once
|
|
25
|
+
|
|
26
|
+
_UNCOMMITTED = "0" * 40 # the commit blame gives lines changed since the last one
|
|
27
|
+
|
|
28
|
+
_HOSTS = {"github.com": "{}/commit/{}", "gitlab.com": "{}/-/commit/{}"} # a commit's page, by host
|
|
29
|
+
|
|
30
|
+
_SAFE = ["-c", "core.fsmonitor=false", "--no-pager"] # no program of the folder's config runs
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _git(root: Path, *args: str) -> str | None:
|
|
34
|
+
"""
|
|
35
|
+
What a git command prints, run in root; None when git isn't there or fails (not a repository, a file it doesn't
|
|
36
|
+
track, a folder it doesn't trust).
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
done = subprocess.run(
|
|
40
|
+
["git", *_SAFE, "-C", str(root), *args],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
encoding="utf-8",
|
|
44
|
+
errors="replace",
|
|
45
|
+
timeout=_TIMEOUT,
|
|
46
|
+
env={**os.environ, "GIT_TERMINAL_PROMPT": "0", "GIT_OPTIONAL_LOCKS": "0"},
|
|
47
|
+
)
|
|
48
|
+
except (OSError, subprocess.SubprocessError):
|
|
49
|
+
return None
|
|
50
|
+
return done.stdout if done.returncode == 0 else None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _runs_filters(root: Path) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Whether the folder's own git config (not the user's) names a filter command, which blame would run on its files.
|
|
56
|
+
"""
|
|
57
|
+
found = _git(root, "config", "--show-scope", "--get-regexp", r"^filter\..*\.(clean|process)$")
|
|
58
|
+
return any(line.split("\t", 1)[0] in ("local", "worktree") for line in (found or "").splitlines())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _commit_page(remote: str) -> str:
|
|
62
|
+
"""
|
|
63
|
+
The repository's web address with a {} for the sha, from its origin: 'https://github.com/o/r/commit/{}'.
|
|
64
|
+
"""
|
|
65
|
+
found = re.fullmatch(r"(?:https?://(?:[^@/]+@)?|ssh://(?:[^@/]+@)?|[^@/]+@)([^/:]+)[:/](.+?)(?:\.git)?/?", remote)
|
|
66
|
+
if not found or found.group(1) not in _HOSTS:
|
|
67
|
+
return ""
|
|
68
|
+
return _HOSTS[found.group(1)].format(f"https://{found.group(1)}/{found.group(2)}", "{}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _blame(root: Path, file: str, start: int, end: int) -> dict[int, tuple[str, dict[str, str]]]:
|
|
72
|
+
"""
|
|
73
|
+
Each line from start to end of the file as it is now: line -> (the commit it comes from, that commit's headers).
|
|
74
|
+
"""
|
|
75
|
+
out = _git(root, "blame", "--porcelain", "--no-textconv", "-L", f"{start},{end}", "--", file)
|
|
76
|
+
lines: dict[int, tuple[str, dict[str, str]]] = {}
|
|
77
|
+
if not out:
|
|
78
|
+
return lines
|
|
79
|
+
headers: dict[str, dict[str, str]] = {}
|
|
80
|
+
sha = ""
|
|
81
|
+
for row in out.splitlines():
|
|
82
|
+
if row.startswith("\t"):
|
|
83
|
+
continue
|
|
84
|
+
parts = row.split(" ")
|
|
85
|
+
if len(parts) >= 3 and len(parts[0]) == 40 and parts[1].isdigit() and parts[2].isdigit():
|
|
86
|
+
sha = parts[0]
|
|
87
|
+
headers.setdefault(sha, {})
|
|
88
|
+
lines[int(parts[2])] = (sha, headers[sha])
|
|
89
|
+
elif sha:
|
|
90
|
+
key, _, value = row.partition(" ")
|
|
91
|
+
headers[sha].setdefault(key, value)
|
|
92
|
+
return lines
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _history(blamed: dict[int, tuple[str, dict[str, str]]], start: int, end: int, page: str, now: int) -> History:
|
|
96
|
+
"""
|
|
97
|
+
A function's history from its lines' blame.
|
|
98
|
+
"""
|
|
99
|
+
commits: dict[str, Commit] = {}
|
|
100
|
+
by_author: Counter[str] = Counter()
|
|
101
|
+
uncommitted = 0
|
|
102
|
+
lines: dict[int, Commit | None] = {}
|
|
103
|
+
for k in range(start, end + 1):
|
|
104
|
+
if k not in blamed:
|
|
105
|
+
continue
|
|
106
|
+
sha, said = blamed[k]
|
|
107
|
+
if sha == _UNCOMMITTED:
|
|
108
|
+
uncommitted += 1
|
|
109
|
+
lines[k] = None
|
|
110
|
+
continue
|
|
111
|
+
if sha not in commits:
|
|
112
|
+
commits[sha] = Commit(
|
|
113
|
+
sha,
|
|
114
|
+
said.get("author", "?"),
|
|
115
|
+
int(said.get("author-time", "0") or 0),
|
|
116
|
+
said.get("summary", ""),
|
|
117
|
+
page.format(sha) if page else "",
|
|
118
|
+
)
|
|
119
|
+
by_author[commits[sha].author] += 1
|
|
120
|
+
lines[k] = commits[sha]
|
|
121
|
+
last = max(commits.values(), key=lambda c: c.time, default=None)
|
|
122
|
+
return History(
|
|
123
|
+
last=last,
|
|
124
|
+
commits=len(commits),
|
|
125
|
+
authors=[name for name, _ in by_author.most_common()],
|
|
126
|
+
first=min((c.time for c in commits.values()), default=0),
|
|
127
|
+
uncommitted=uncommitted,
|
|
128
|
+
now=now,
|
|
129
|
+
lines=lines,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def read_history(root: Path, functions: list[CodeFunction]) -> int:
|
|
134
|
+
"""
|
|
135
|
+
Fills in each function's history from git, blaming each file once over the lines its functions take up. How many
|
|
136
|
+
functions it found history for: 0 when the folder isn't in git, git isn't installed, or the folder's config would
|
|
137
|
+
have git run a program of its own.
|
|
138
|
+
"""
|
|
139
|
+
spans: dict[str, tuple[int, int]] = {}
|
|
140
|
+
for f in functions:
|
|
141
|
+
if f.file and f.line and f.lines:
|
|
142
|
+
start, end = spans.get(f.file, (f.line, f.line + f.lines - 1))
|
|
143
|
+
spans[f.file] = (min(start, f.line), max(end, f.line + f.lines - 1))
|
|
144
|
+
if not spans or _git(root, "rev-parse", "--is-inside-work-tree") is None or _runs_filters(root):
|
|
145
|
+
return 0
|
|
146
|
+
page = _commit_page((_git(root, "remote", "get-url", "origin") or "").strip())
|
|
147
|
+
now = int(time.time())
|
|
148
|
+
with ThreadPoolExecutor(max_workers=min(_WORKERS, len(spans))) as pool:
|
|
149
|
+
blamed = dict(zip(spans, pool.map(lambda file: _blame(root, file, *spans[file]), spans)))
|
|
150
|
+
found = 0
|
|
151
|
+
for f in functions:
|
|
152
|
+
lines = blamed.get(f.file)
|
|
153
|
+
if lines:
|
|
154
|
+
f.history = _history(lines, f.line, f.line + f.lines - 1, page, now)
|
|
155
|
+
found += 1
|
|
156
|
+
return found
|
|
@@ -6,13 +6,14 @@ once, and the server makes it again on every change.
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
-
from dataclasses import dataclass
|
|
9
|
+
from dataclasses import dataclass, replace
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
12
|
from .analysis.graph import code_graph
|
|
13
13
|
from .analysis.mapping import entry_file, map_code
|
|
14
14
|
from .analysis.model import CodeMap
|
|
15
15
|
from .deploys import find_deployment
|
|
16
|
+
from .history import read_history
|
|
16
17
|
from .project import EntryPoint, Project, read_project
|
|
17
18
|
from .report.build import page_blocks
|
|
18
19
|
from .report.editor import editor_link
|
|
@@ -38,6 +39,7 @@ class PageOptions:
|
|
|
38
39
|
depth: int = GRAPH_DEPTH
|
|
39
40
|
platform: str = "" # --platform: its checks run whatever the entry point is ('' leaves it to the entry point)
|
|
40
41
|
editor: str = "vscode" # --editor: the editor its line links open ('vscode', 'cursor'; 'none' for no links)
|
|
42
|
+
git: bool = True # what git says about each function (--no-git leaves it out)
|
|
41
43
|
|
|
42
44
|
@property
|
|
43
45
|
def limits(self) -> tuple[int, int]:
|
|
@@ -63,6 +65,8 @@ def map_entry(root: Path, entry: EntryPoint, options: PageOptions) -> tuple[Code
|
|
|
63
65
|
platform=options.platform or entry.platform,
|
|
64
66
|
deployment=deployment,
|
|
65
67
|
)
|
|
68
|
+
if options.git:
|
|
69
|
+
read_history(project.root, cmap.functions)
|
|
66
70
|
return cmap, project
|
|
67
71
|
|
|
68
72
|
|
|
@@ -87,6 +91,6 @@ def make_mermaid(root: Path, entry: EntryPoint, options: PageOptions) -> str:
|
|
|
87
91
|
"""
|
|
88
92
|
The graph as Mermaid.
|
|
89
93
|
"""
|
|
90
|
-
cmap, _ = map_entry(root, entry, options)
|
|
94
|
+
cmap, _ = map_entry(root, entry, replace(options, git=False)) # Mermaid has nowhere to say it
|
|
91
95
|
max_functions, max_depth = options.limits
|
|
92
96
|
return code_graph(cmap, max_functions=max_functions, max_depth=max_depth).mermaid()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
What git says about a function, as HTML: who changed it last and when, with a link to the commit when the repository
|
|
3
|
+
is on GitHub or GitLab, how many commits and people its lines come from, and its lines not committed yet. The same
|
|
4
|
+
in the graph's hover card and the walk's card, and in the graph's panel line by line, beside its code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
|
|
11
|
+
from ..kit.text import _esc
|
|
12
|
+
|
|
13
|
+
from ..analysis.model import Commit, History
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def history_html(history: History | None, short: bool = False) -> str:
|
|
17
|
+
"""
|
|
18
|
+
The function's history as a few lines, or '' without one. short: the last change alone, for a hover card.
|
|
19
|
+
"""
|
|
20
|
+
if history is None or not (history.last or history.uncommitted):
|
|
21
|
+
return ""
|
|
22
|
+
said = []
|
|
23
|
+
last = history.last
|
|
24
|
+
if last:
|
|
25
|
+
day = datetime.fromtimestamp(last.time, timezone.utc).strftime("%Y-%m-%d")
|
|
26
|
+
sha = f"<code>{_esc(last.short)}</code>"
|
|
27
|
+
if last.url and not short:
|
|
28
|
+
sha = f'<a href="{_esc(last.url)}" target="_blank" rel="noopener" title="The commit">{sha} ↗</a>'
|
|
29
|
+
said.append(
|
|
30
|
+
f'Last changed <b title="{day}">{_esc(history.ago(last.time))}</b> by {_esc(last.author)} in {sha}'
|
|
31
|
+
+ (f": <q>{_esc(last.subject)}</q>" if last.subject else "")
|
|
32
|
+
)
|
|
33
|
+
if history.commits > 1 and not short:
|
|
34
|
+
people = f"{len(history.authors)} people" if len(history.authors) > 1 else _esc(history.authors[0])
|
|
35
|
+
said.append(
|
|
36
|
+
f"Its lines come from {history.commits} commits by {people}, the oldest "
|
|
37
|
+
f"{_esc(history.ago(history.first))}"
|
|
38
|
+
)
|
|
39
|
+
if history.uncommitted:
|
|
40
|
+
changed = f"{history.uncommitted} line{'' if history.uncommitted == 1 else 's'} changed since the last commit"
|
|
41
|
+
said.append(f'<span class="cnhu">{changed if last else "Not committed yet"}</span>')
|
|
42
|
+
return '<div class="cnh">' + "".join(f"<p>{line}</p>" for line in said) + "</div>"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_AGES = 4 # shades of a line's bar, oldest to newest
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def blame_keys(history: History) -> dict[str, int]:
|
|
49
|
+
"""
|
|
50
|
+
The commits its lines come from, oldest first, each with its place in that order: what a line's mark names to
|
|
51
|
+
find its commit in the panel's last row.
|
|
52
|
+
"""
|
|
53
|
+
commits = {c.sha: c for c in history.lines.values() if c}
|
|
54
|
+
return {sha: k for k, sha in enumerate(sorted(commits, key=lambda sha: (commits[sha].time, sha)))}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def blame_mark(history: History, line: int, keys: dict[str, int], same: bool) -> str:
|
|
58
|
+
"""
|
|
59
|
+
What goes beside a line of the panel's code: who changed it last and how long ago (left out when the line above
|
|
60
|
+
says the same, same), over a bar darker the newer the line, amber for one not committed yet. Pointing at it says
|
|
61
|
+
the commit; '' for a line blame didn't cover, so the code stays in line.
|
|
62
|
+
"""
|
|
63
|
+
if line not in history.lines:
|
|
64
|
+
return '<span class="sb"></span>'
|
|
65
|
+
commit = history.lines[line]
|
|
66
|
+
if commit is None:
|
|
67
|
+
return (
|
|
68
|
+
f'<span class="sb au{" same" if same else ""}" data-k="u" title="Changed since the last commit">'
|
|
69
|
+
"<span>Uncommitted</span></span>"
|
|
70
|
+
)
|
|
71
|
+
k = keys[commit.sha]
|
|
72
|
+
shade = _AGES - 1 if len(keys) == 1 else round(k * (_AGES - 1) / (len(keys) - 1))
|
|
73
|
+
said = f"{commit.subject} · {commit.author}, {history.ago(commit.time)} · {commit.short}"
|
|
74
|
+
return (
|
|
75
|
+
f'<span class="sb a{shade}{" same" if same else ""}" data-k="{k}" title="{_esc(said)}">'
|
|
76
|
+
f"<span>{_esc(commit.author.split(' ')[0] or commit.author)} · {_esc(history.since(commit.time))}</span></span>"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def blame_row(history: History, keys: dict[str, int]) -> str:
|
|
81
|
+
"""
|
|
82
|
+
The panel's last row: when its code was last changed and by whom, then hidden, a row for each commit its lines come
|
|
83
|
+
from, which a script shows in its place while a line from that commit is pointed at.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def sha(commit: Commit) -> str:
|
|
87
|
+
short = f"<code>{_esc(commit.short)}</code>"
|
|
88
|
+
if not commit.url:
|
|
89
|
+
return f'<span class="csfh">{short}</span>'
|
|
90
|
+
return (
|
|
91
|
+
f'<a class="csfh" href="{_esc(commit.url)}" target="_blank" rel="noopener" title="The commit">{short}</a>'
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
last = history.last
|
|
95
|
+
if last:
|
|
96
|
+
day = datetime.fromtimestamp(last.time, timezone.utc).strftime("%Y-%m-%d")
|
|
97
|
+
said = f'Changed <b title="{day}">{_esc(history.ago(last.time))}</b> by {_esc(last.author)}'
|
|
98
|
+
if history.commits > 1:
|
|
99
|
+
people = len(history.authors)
|
|
100
|
+
said += f" · {history.commits} commits" + (f" by {people} people" if people > 1 else "")
|
|
101
|
+
else:
|
|
102
|
+
said = "Not committed yet"
|
|
103
|
+
if history.uncommitted and last:
|
|
104
|
+
lines = f"{history.uncommitted} line{'' if history.uncommitted == 1 else 's'}"
|
|
105
|
+
said += f' · <span class="cnhu">{lines} not committed</span>'
|
|
106
|
+
rows = [f'<p class="csfs"><span>{said}</span>{sha(last) if last else ""}</p>']
|
|
107
|
+
commits = {c.sha: c for c in history.lines.values() if c}
|
|
108
|
+
for found, k in keys.items():
|
|
109
|
+
commit = commits[found]
|
|
110
|
+
rows.append(
|
|
111
|
+
f'<p data-k="{k}" hidden><span><b>{_esc(commit.subject or commit.short)}</b> · {_esc(commit.author)}, '
|
|
112
|
+
f"{_esc(history.ago(commit.time))}</span>{sha(commit)}</p>"
|
|
113
|
+
)
|
|
114
|
+
if history.uncommitted:
|
|
115
|
+
rows.append('<p data-k="u" hidden><span class="cnhu">Changed since the last commit</span></p>')
|
|
116
|
+
return f'<div class="csf">{"".join(rows)}</div>'
|