code-node-graph 0.1.0__tar.gz → 0.2.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.
Files changed (186) hide show
  1. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/CHANGELOG.md +11 -1
  2. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/PKG-INFO +1 -1
  3. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/__init__.py +1 -1
  4. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/graph.py +153 -4
  5. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/style.py +34 -6
  6. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/tests/test_code_node.py +14 -0
  7. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/.gitignore +0 -0
  8. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/LICENSE +0 -0
  9. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/README.md +0 -0
  10. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/examples/books-api/README.md +0 -0
  11. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/examples/cart-service/README.md +0 -0
  12. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/examples/orders-service/README.md +0 -0
  13. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/pyproject.toml +0 -0
  14. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/__main__.py +0 -0
  15. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/__init__.py +0 -0
  16. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/aws_model.py +0 -0
  17. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/findings.py +0 -0
  18. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/graph.py +0 -0
  19. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/mapping.py +0 -0
  20. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/model.py +0 -0
  21. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/reader.py +0 -0
  22. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/syntax.py +0 -0
  23. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/analysis/terms.py +0 -0
  24. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/cli.py +0 -0
  25. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/deploys/__init__.py +0 -0
  26. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/deploys/sam.py +0 -0
  27. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/deploys/serverless.py +0 -0
  28. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/kit/__init__.py +0 -0
  29. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/kit/blocks.py +0 -0
  30. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/kit/fmt.py +0 -0
  31. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/kit/highlight.py +0 -0
  32. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/kit/render.py +0 -0
  33. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/kit/text.py +0 -0
  34. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/libraries/__init__.py +0 -0
  35. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/libraries/llm.py +0 -0
  36. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/libraries/mongo.py +0 -0
  37. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/libraries/redis.py +0 -0
  38. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/libraries/sql.py +0 -0
  39. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/pages.py +0 -0
  40. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/platforms/__init__.py +0 -0
  41. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/platforms/aws_lambda.py +0 -0
  42. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/platforms/web.py +0 -0
  43. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/project.py +0 -0
  44. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/__init__.py +0 -0
  45. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/brands.py +0 -0
  46. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/build.py +0 -0
  47. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/editor.py +0 -0
  48. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/README.md +0 -0
  49. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/anthropic-icon.svg +0 -0
  50. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-amplify.svg +0 -0
  51. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-api-gateway.svg +0 -0
  52. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-appflow.svg +0 -0
  53. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-appsync.svg +0 -0
  54. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-athena.svg +0 -0
  55. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-backup.svg +0 -0
  56. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-batch.svg +0 -0
  57. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-certificate-manager.svg +0 -0
  58. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-cloudformation.svg +0 -0
  59. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-cloudfront.svg +0 -0
  60. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-cloudtrail.svg +0 -0
  61. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-cloudwatch.svg +0 -0
  62. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-codebuild.svg +0 -0
  63. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-codecommit.svg +0 -0
  64. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-codedeploy.svg +0 -0
  65. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-codepipeline.svg +0 -0
  66. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-cognito.svg +0 -0
  67. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-config.svg +0 -0
  68. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-documentdb.svg +0 -0
  69. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-dynamodb.svg +0 -0
  70. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-ec2.svg +0 -0
  71. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-ecs.svg +0 -0
  72. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-eks.svg +0 -0
  73. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-elasticache.svg +0 -0
  74. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-elb.svg +0 -0
  75. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-eventbridge.svg +0 -0
  76. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-glacier.svg +0 -0
  77. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-glue.svg +0 -0
  78. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-iam.svg +0 -0
  79. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-keyspaces.svg +0 -0
  80. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-kinesis.svg +0 -0
  81. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-kms.svg +0 -0
  82. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-lake-formation.svg +0 -0
  83. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-lambda.svg +0 -0
  84. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-lightsail.svg +0 -0
  85. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-mq.svg +0 -0
  86. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-msk.svg +0 -0
  87. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-neptune.svg +0 -0
  88. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-open-search.svg +0 -0
  89. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-quicksight.svg +0 -0
  90. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-rds.svg +0 -0
  91. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-redshift.svg +0 -0
  92. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-route53.svg +0 -0
  93. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-s3.svg +0 -0
  94. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-secrets-manager.svg +0 -0
  95. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-ses.svg +0 -0
  96. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-shield.svg +0 -0
  97. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-sns.svg +0 -0
  98. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-sqs.svg +0 -0
  99. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-step-functions.svg +0 -0
  100. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-systems-manager.svg +0 -0
  101. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-timestream.svg +0 -0
  102. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-waf.svg +0 -0
  103. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws-xray.svg +0 -0
  104. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/aws.svg +0 -0
  105. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/azure-icon.svg +0 -0
  106. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/django-icon.svg +0 -0
  107. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/elasticsearch.svg +0 -0
  108. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/fastapi-icon.svg +0 -0
  109. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/firebase-icon.svg +0 -0
  110. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/flask.svg +0 -0
  111. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/google-cloud.svg +0 -0
  112. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/kafka-icon.svg +0 -0
  113. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/mariadb-icon.svg +0 -0
  114. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/mongodb-icon.svg +0 -0
  115. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/mysql-icon.svg +0 -0
  116. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/openai-icon.svg +0 -0
  117. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/postgresql.svg +0 -0
  118. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/python.svg +0 -0
  119. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/rabbitmq-icon.svg +0 -0
  120. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/redis.svg +0 -0
  121. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/serverless.svg +0 -0
  122. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/slack-icon.svg +0 -0
  123. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-api-gateway.svg +0 -0
  124. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-appflow.svg +0 -0
  125. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-appsync.svg +0 -0
  126. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-athena.svg +0 -0
  127. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-backup.svg +0 -0
  128. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-batch.svg +0 -0
  129. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-certificate-manager.svg +0 -0
  130. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-cloudformation.svg +0 -0
  131. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-cloudfront.svg +0 -0
  132. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-cloudtrail.svg +0 -0
  133. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-cloudwatch.svg +0 -0
  134. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-codebuild.svg +0 -0
  135. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-codecommit.svg +0 -0
  136. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-codedeploy.svg +0 -0
  137. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-codepipeline.svg +0 -0
  138. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-cognito.svg +0 -0
  139. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-config.svg +0 -0
  140. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-documentdb.svg +0 -0
  141. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-dynamodb.svg +0 -0
  142. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-ec2.svg +0 -0
  143. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-ecs.svg +0 -0
  144. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-eks.svg +0 -0
  145. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-elasticache.svg +0 -0
  146. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-elb.svg +0 -0
  147. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-eventbridge.svg +0 -0
  148. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-glacier.svg +0 -0
  149. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-glue.svg +0 -0
  150. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-iam.svg +0 -0
  151. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-keyspaces.svg +0 -0
  152. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-kinesis.svg +0 -0
  153. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-kms.svg +0 -0
  154. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-lake-formation.svg +0 -0
  155. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-lambda.svg +0 -0
  156. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-mq.svg +0 -0
  157. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-msk.svg +0 -0
  158. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-neptune.svg +0 -0
  159. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-open-search.svg +0 -0
  160. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-quicksight.svg +0 -0
  161. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-rds.svg +0 -0
  162. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-redshift.svg +0 -0
  163. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-route53.svg +0 -0
  164. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-s3.svg +0 -0
  165. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-secrets-manager.svg +0 -0
  166. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-ses.svg +0 -0
  167. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-shield.svg +0 -0
  168. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-sns.svg +0 -0
  169. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-sqs.svg +0 -0
  170. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-step-functions.svg +0 -0
  171. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-systems-manager.svg +0 -0
  172. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-timestream.svg +0 -0
  173. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-waf.svg +0 -0
  174. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/small/aws-xray.svg +0 -0
  175. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/snowflake-icon.svg +0 -0
  176. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/sqlite.svg +0 -0
  177. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/stripe.svg +0 -0
  178. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/supabase-icon.svg +0 -0
  179. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/logos/twilio-icon.svg +0 -0
  180. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/outline.py +0 -0
  181. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/tabs.py +0 -0
  182. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/report/walk.py +0 -0
  183. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/src/code_node/serve.py +0 -0
  184. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/tests/snapshots/books-api.txt +0 -0
  185. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/tests/snapshots/cart-service.txt +0 -0
  186. {code_node_graph-0.1.0 → code_node_graph-0.2.0}/tests/snapshots/orders-service.txt +0 -0
@@ -4,6 +4,15 @@ 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.2.0] - 2026-10-20
8
+
9
+ ### Changed
10
+
11
+ - Pointing at a box in the graph shows a card instead of the browser's plain tooltip: what kind of box it is and
12
+ its name, a function's `def` as written in colour with its docstring, a service's operations and the permissions
13
+ they need set as code, and its warnings in a block of their own. It sits under the box, over it or beside it,
14
+ wherever it fits, and zooms with the graph.
15
+
7
16
  ## [0.1.0] - 2026-10-20
8
17
 
9
18
  ### Added
@@ -153,5 +162,6 @@ follow [Semantic Versioning](https://semver.org/); before 1.0, a minor version m
153
162
  - `--platform NAME` runs a platform's checks whatever the entry point is; `--lambda` is short for
154
163
  `--platform lambda`. `EntryPoint.is_lambda` and `PageOptions.is_lambda` are now `platform` (`"lambda"` or `""`).
155
164
 
156
- [Unreleased]: https://github.com/utkarsh5026/code-node/compare/v0.1.0...HEAD
165
+ [Unreleased]: https://github.com/utkarsh5026/code-node/compare/v0.2.0...HEAD
166
+ [0.2.0]: https://github.com/utkarsh5026/code-node/compare/v0.1.0...v0.2.0
157
167
  [0.1.0]: https://github.com/utkarsh5026/code-node/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code-node-graph
3
- Version: 0.1.0
3
+ Version: 0.2.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/
@@ -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.1.0"
16
+ __version__ = "0.2.0"
17
17
 
18
18
  __all__ = [
19
19
  "CodeGraph",
@@ -13,8 +13,8 @@ from typing import Any, Callable
13
13
 
14
14
  from ..kit.blocks import _Report, _draws
15
15
  from ..kit.fmt import _plural
16
- from ..kit.highlight import _python_lines
17
- from ..kit.render import _prose
16
+ from ..kit.highlight import _python_html, _python_lines
17
+ from ..kit.render import _CALL_RE, _prose
18
18
  from ..kit.text import _esc
19
19
 
20
20
  from ..analysis.graph import CodeGraph, GraphEdge, GraphNode
@@ -1081,7 +1081,7 @@ def _node_svg(
1081
1081
  )
1082
1082
  classes = " ".join(["cn", f"k-{n.kind}", f"n-{n.id}", *(f"a-{m}" for m in neighbours)]) + (" warn" if warn else "")
1083
1083
  return (
1084
- f'<g class="{_esc(classes)}" transform="translate({x:.1f},{y:.1f})"><title>{_esc(n.tip or n.label)}</title>'
1084
+ f'<g class="{_esc(classes)}" transform="translate({x:.1f},{y:.1f})">'
1085
1085
  f'<rect class="bg" width="{w:.0f}" height="{h:.0f}" rx="11"/>'
1086
1086
  f'<rect class="fg" x=".5" y=".5" width="{w - 1:.0f}" height="{h - 1:.0f}" rx="10.5"/>{"".join(parts)}</g>'
1087
1087
  )
@@ -1175,6 +1175,153 @@ def _g_panel(n: GraphNode, lines_of: Callable[[str], list[str]], editor: str = "
1175
1175
  )
1176
1176
 
1177
1177
 
1178
+ _G_TIP_W = 420 # a box's hover card, wide
1179
+
1180
+ _G_TIP_SIGNATURE = 6 # lines of a def its hover card shows, at most
1181
+
1182
+ _G_TIP_WARNING = "A warning is about it: "
1183
+
1184
+ # a name in the card's sentences worth setting as code: a dotted or keyed path (Records[].s3.bucket.name,
1185
+ # book:{book_id}, s3:GetObject, orders/handler.py) or a variable in capitals (DB_HOST)
1186
+ _G_CODE_RE = re.compile(
1187
+ r"(?<![\w.\-/])([A-Za-z_][\w\-]*(?:[.:/]+[\w{}\-]+|\[\]|\{\w*\})+|[A-Z][A-Z0-9]*_[A-Z0-9_]*[A-Z0-9])(?![\w{])"
1188
+ )
1189
+
1190
+
1191
+ def _g_tip_text(text: str) -> str:
1192
+ """
1193
+ One of the card's sentences as HTML, its calls coloured as Python and its names set as code, every piece escaped.
1194
+ """
1195
+ out = []
1196
+ for i, piece in enumerate(_CALL_RE.split(text)):
1197
+ if i % 2:
1198
+ out.append(f'<code class="ctk">{_python_html(piece)}</code>')
1199
+ continue
1200
+ for k, part in enumerate(_G_CODE_RE.split(piece)):
1201
+ out.append(f'<code class="ctk ctn">{_esc(part)}</code>' if k % 2 else _esc(part))
1202
+ return "".join(out)
1203
+
1204
+
1205
+ def _g_tip_item(item: str) -> str:
1206
+ """
1207
+ One thing a service box lists, for its card: 'PutItem (dynamodb:PutItem)' as the operation in code and what it
1208
+ needs beside it.
1209
+ """
1210
+ m = re.fullmatch(r"(.+?) \((.+)\)", item)
1211
+ if not m:
1212
+ return f'<li><code class="ctk ctn">{_esc(item)}</code></li>'
1213
+ return (
1214
+ f'<li><code class="ctk ctn">{_esc(m.group(1))}</code> <span class="ctm">{_g_tip_text(m.group(2))}</span></li>'
1215
+ )
1216
+
1217
+
1218
+ def _g_tip_signature(n: GraphNode, lines_of: Callable[[str], list[str]]) -> list[str]:
1219
+ """
1220
+ A function's def as it's written, in colour: its decorators and the lines up to the colon that opens its body.
1221
+ [] when its file wasn't read.
1222
+ """
1223
+ if not n.code:
1224
+ return []
1225
+ span = n.code[0]
1226
+ lines = lines_of(span.file)
1227
+ if not lines or span.start > len(lines):
1228
+ return []
1229
+ start = span.start
1230
+ while start > 1 and lines[start - 2].lstrip().startswith("@"):
1231
+ start -= 1
1232
+ shown = []
1233
+ for k in range(start, min(len(lines), span.end, start + _G_TIP_SIGNATURE - 1) + 1):
1234
+ shown.append(lines[k - 1])
1235
+ if k >= span.start and re.sub(r"<[^>]*>", "", lines[k - 1]).rstrip().endswith(":"):
1236
+ break
1237
+ indent = min((len(line) - len(line.lstrip(" ")) for line in shown if line.strip()), default=0)
1238
+ return [line[indent:] for line in shown]
1239
+
1240
+
1241
+ def _g_tip(n: GraphNode, lines_of: Callable[[str], list[str]], opens: bool) -> tuple[str, float]:
1242
+ """
1243
+ What a box says on hover, as a card: what kind of box it is and its name, a function's def in colour and its
1244
+ docstring, then the rest of its words with the code in them set as code, its warnings apart, and when it has a
1245
+ panel (opens), that a click shows its code. With how tall it is likely to be, to pick whether it goes under the
1246
+ box or over it.
1247
+ """
1248
+ said = [line for line in (n.tip or n.label).split("\n") if line]
1249
+ warnings = [w for line in said if line.startswith(_G_TIP_WARNING) for w in line[len(_G_TIP_WARNING) :].split("; ")]
1250
+ said = [line for line in said if not line.startswith(_G_TIP_WARNING)]
1251
+ mono = n.kind in ("handler", "function")
1252
+ body, tall, sub = [], 80.0 + (20 if n.sub else 0), n.sub
1253
+ if mono and said and _CALL_RE.match(said[0]) and " in " in said[0]: # 'name(args) in file, 3 lines: its doc'
1254
+ head = said.pop(0)
1255
+ signature = _g_tip_signature(n, lines_of) or [_python_html(head.split(" in ", 1)[0])]
1256
+ body.append(f'<pre class="ctc">{chr(10).join(signature)}</pre>')
1257
+ tall += 21 * len(signature) + 34
1258
+ lines = re.search(r", (\d+ lines?)", head)
1259
+ sub = f"{n.sub} · {lines.group(1)}" if lines and n.sub else sub
1260
+ if n.lines and n.lines[0]:
1261
+ body.append(f'<p class="ctd">{_g_tip_text(n.lines[0])}</p>')
1262
+ tall += 23 * math.ceil(len(n.lines[0]) / 42) + 10
1263
+ for line in said:
1264
+ lead, _, rest = line.partition(": ")
1265
+ if rest and lead == n.label: # 'S3: GetObject (s3:GetObject), PutObject (s3:PutObject)': a list
1266
+ items = rest.split(", ")
1267
+ body.append(f'<ul class="ctl">{"".join(_g_tip_item(item) for item in items)}</ul>')
1268
+ tall += 30 * len(items) + 8
1269
+ else:
1270
+ body.append(f'<p class="ctp">{_g_tip_text(line)}</p>')
1271
+ tall += 23 * math.ceil(len(line) / 42) + 10
1272
+ if warnings:
1273
+ body.append(
1274
+ '<div class="ctw"><b>Warning</b><ul>'
1275
+ + "".join(f"<li>{_g_tip_text(w)}</li>" for w in warnings)
1276
+ + "</ul></div>"
1277
+ )
1278
+ tall += 50 + sum(23 * math.ceil(len(w) / 36) + 4 for w in warnings)
1279
+ kind = _G_PANEL_KINDS.get(n.kind, n.kind)
1280
+ name = _python_html(n.label) if mono else _esc(n.label)
1281
+ card = (
1282
+ f'<div class="ctt k-{_esc(n.kind)}{" warn" if warnings else ""}"><div class="cth">'
1283
+ f'<span class="ctkd">{_esc(kind)}</span><b class="ctn{" mono" if mono else ""}">{name}</b>'
1284
+ + (f'<span class="cts{" mono" if mono else ""}">{_esc(sub)}</span>' if sub else "")
1285
+ + f"</div>{''.join(body)}"
1286
+ + ('<div class="cto">Click it to read its code</div>' if opens else "")
1287
+ + "</div>"
1288
+ )
1289
+ return card, tall + (40 if opens else 0)
1290
+
1291
+
1292
+ def _g_tip_svg(
1293
+ n: GraphNode,
1294
+ box: tuple[float, float, float, float],
1295
+ width: float,
1296
+ tall: float,
1297
+ lines_of: Callable[[str], list[str]],
1298
+ opens: bool,
1299
+ ) -> str:
1300
+ """
1301
+ A box's hover card, in the graph so it zooms with it: under the box when it fits there, else over it, else beside
1302
+ it, and inside the graph from left to right. Hidden until the box is pointed at.
1303
+ """
1304
+ card, high = _g_tip(n, lines_of, opens)
1305
+ x, y, w, h = box
1306
+ card_w = min(_G_TIP_W, max(width - 16, 220))
1307
+ left = max(8.0, min(x, width - card_w - 8))
1308
+ below, above = tall - (y + h + 10) - 8, y - 10 - 8
1309
+ if high <= below:
1310
+ top, room, side = y + h + 10, below, "dn"
1311
+ elif high <= above:
1312
+ top, room, side = 8.0, above, "up"
1313
+ else: # beside the box, as high up as keeps all of it in the graph; over the box only when there's no room beside
1314
+ if width - (x + w + 12) - 8 >= card_w:
1315
+ left = x + w + 12
1316
+ elif x - 12 - 8 >= card_w:
1317
+ left = x - 12 - card_w
1318
+ top, room, side = max(8.0, min(y, tall - high - 8)), high, "dn"
1319
+ return (
1320
+ f'<foreignObject class="ctip tp-{_esc(n.id)} {side}" x="{left:.0f}" y="{top:.0f}" width="{card_w:.0f}" '
1321
+ f'height="{room:.0f}" aria-hidden="true"><div class="ctx">{card}</div></foreignObject>'
1322
+ )
1323
+
1324
+
1178
1325
  def _g_captions(graph: CodeGraph, xs: dict[int, float], widths: dict[int, float], column_of: dict[str, int]) -> str:
1179
1326
  """
1180
1327
  The words over the columns: COMES IN, HANDLER, CALLS, REACHES, each over the columns it covers.
@@ -1256,6 +1403,7 @@ def _graph_html(block: _Graph, report: _Report) -> str:
1256
1403
  for n in graph.nodes: # pointing at a box leaves only it, its arrows and the boxes they join in sight
1257
1404
  report.rules.add(f".{root} .cg:has(.n-{n.id}:hover) .ce:not(.s-{n.id}):not(.t-{n.id}){{opacity:.06}}")
1258
1405
  report.rules.add(f".{root} .cg:has(.n-{n.id}:hover) .cn:not(.n-{n.id}):not(.a-{n.id}){{opacity:.28}}")
1406
+ report.rules.add(f".{root} .cg:has(.n-{n.id}:hover) .tp-{n.id}{{display:inline}}")
1259
1407
  report.rules.add(
1260
1408
  f".{root} .cg:has(.n-{n.id}:hover) :is(.s-{n.id},.t-{n.id}) .ln{{stroke-width:2.4;stroke-opacity:1}}"
1261
1409
  )
@@ -1307,6 +1455,7 @@ def _graph_html(block: _Graph, report: _Report) -> str:
1307
1455
  for n in graph.nodes
1308
1456
  for box in [_node_svg(cards[n.id], boxes[n.id], list(dict.fromkeys(near[n.id])), access[n.id])]
1309
1457
  )
1458
+ tips = "".join(_g_tip_svg(n, boxes[n.id], width, tall, lines_of, bool(panels.get(n.id))) for n in graph.nodes)
1310
1459
  kinds = {e.kind for e in graph.edges}
1311
1460
  pills = [f'<span class="kp">{_g_swatch(k)}{_esc(word)}</span>' for k, word in _G_LEGEND if k in kinds]
1312
1461
  if any(e.guessed for e in graph.edges):
@@ -1362,7 +1511,7 @@ def _graph_html(block: _Graph, report: _Report) -> str:
1362
1511
  f'{title}<div class="cgw">{switches}<div class="cgb">{bar}<div class="cg">'
1363
1512
  f'<svg viewBox="0 0 {width:.0f} {tall:.0f}" width="{width:.0f}" height="{tall:.0f}" '
1364
1513
  f'style="--w:{width:.0f}px" role="img" aria-label="{_esc(block.title or "How the code fits together")}">'
1365
- f"{_g_captions(graph, xs, widths, column_of)}{edges}{nodes}{ports}</svg></div>"
1514
+ f"{_g_captions(graph, xs, widths, column_of)}{edges}{nodes}{ports}{tips}</svg></div>"
1366
1515
  f'<div class="cgk">{"".join(pills)}{hidden}{hint}</div></div>{"".join(panels.values())}</div>'
1367
1516
  )
1368
1517
 
@@ -95,12 +95,12 @@ _EXTRA_CSS = """\
95
95
  .cnd .flow .oq{font-style:italic}
96
96
  .cnd .flow .os{display:inline-flex;flex-wrap:wrap;gap:3px 5px}
97
97
  .cnd .flow .ob{margin-left:22px}
98
- .cnd :is(.flow,.cside,.walk) .pk{color:var(--jp-mirror-editor-keyword-color,light-dark(#7c3aed,#c792ea))}
99
- .cnd :is(.flow,.cside,.walk) .pf{color:var(--jp-mirror-editor-def-color,light-dark(#2563eb,#82aaff))}
100
- .cnd :is(.flow,.cside,.walk) :is(.js,.jk){color:var(--jp-mirror-editor-string-color,light-dark(#047857,#a5d6a7))}
101
- .cnd :is(.flow,.cside,.walk) :is(.jn,.jl){color:var(--jp-mirror-editor-number-color,light-dark(#c2410c,#f78c6c))}
102
- .cnd :is(.flow,.cside,.walk) .pa{font-style:italic}
103
- .cnd :is(.flow,.cside,.walk) .pc{opacity:.6;font-style:italic}
98
+ .cnd :is(.flow,.cside,.walk,.ctip) .pk{color:var(--jp-mirror-editor-keyword-color,light-dark(#7c3aed,#c792ea))}
99
+ .cnd :is(.flow,.cside,.walk,.ctip) .pf{color:var(--jp-mirror-editor-def-color,light-dark(#2563eb,#82aaff))}
100
+ .cnd :is(.flow,.cside,.walk,.ctip) :is(.js,.jk){color:var(--jp-mirror-editor-string-color,light-dark(#047857,#a5d6a7))}
101
+ .cnd :is(.flow,.cside,.walk,.ctip) :is(.jn,.jl){color:var(--jp-mirror-editor-number-color,light-dark(#c2410c,#f78c6c))}
102
+ .cnd :is(.flow,.cside,.walk,.ctip) .pa{font-style:italic}
103
+ .cnd :is(.flow,.cside,.walk,.ctip) .pc{opacity:.6;font-style:italic}
104
104
  .cnd .cgb{width:fit-content;max-width:min(100%,1180px)}
105
105
  .cnd .cg{border:1px solid rgba(127,127,127,.2);border-radius:14px;margin:6px 0 8px;overflow:auto;background-color:rgba(127,127,127,.035);background-attachment:local;background-size:18px 18px;
106
106
  background-image:radial-gradient(circle at 1px 1px,rgba(127,127,127,.3) 1px,transparent 0);background-position:9px 9px}
@@ -221,6 +221,34 @@ _EXTRA_CSS = """\
221
221
  .cnd .cgk .kl path{fill:none;stroke:currentColor;stroke-opacity:.8;stroke-width:2.1;stroke-linecap:round;stroke-linejoin:round}
222
222
  .cnd .cg a.cl{cursor:pointer;outline:none}
223
223
  .cnd .cg a.cl:focus-visible .fg{stroke:light-dark(#2563eb,#60a5fa);stroke-opacity:1;stroke-width:2}
224
+ .cnd .cg .ctip{display:none;overflow:visible;pointer-events:none}
225
+ .cnd .cg .ctip .ctx{display:flex;flex-direction:column;height:100%}
226
+ .cnd .cg .ctip.up .ctx{justify-content:flex-end}
227
+ .cnd .cg .ctt{position:relative;box-sizing:border-box;padding:16px 18px;border:1px solid rgba(127,127,127,.3);border-radius:12px;background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas));color:CanvasText;box-shadow:0 2px 6px rgba(15,23,42,.08),0 14px 34px rgba(15,23,42,.18);font-size:14px;line-height:1.6;text-align:left;animation:cnTip .14s ease-out .18s both}
228
+ @keyframes cnTip{from{opacity:0;transform:translateY(3px)}to{opacity:1;transform:none}}
229
+ @media (prefers-reduced-motion:reduce){.cnd .cg .ctt{animation:none}}
230
+ .cnd .cg .ctt.k-function,.cnd .cg .ctt.k-handler{--c:light-dark(#2563eb,#82aaff)}
231
+ .cnd .cg .cth{display:flex;flex-direction:column;gap:3px;margin:0 0 14px}
232
+ .cnd .cg .ctkd{font-size:10.5px;font-weight:650;letter-spacing:.08em;text-transform:uppercase;color:var(--c,currentColor);opacity:.9}
233
+ .cnd .cg .ctt b.ctn{font-size:16px;font-weight:650;overflow-wrap:anywhere}
234
+ .cnd .cg .ctt b.ctn.mono{font:650 15px var(--mono)}
235
+ .cnd .cg .cts{font-size:12.5px;opacity:.65;overflow-wrap:anywhere}
236
+ .cnd .cg .cts.mono{font-family:var(--mono)}
237
+ .cnd .cg .ctt pre.ctc{margin:0 0 12px;padding:10px 12px;border:1px solid rgba(127,127,127,.2);border-radius:8px;background:rgba(127,127,127,.06);font:13px/1.55 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere}
238
+ .cnd .cg .cto{margin:16px -18px -16px;padding:9px 18px;border-top:1px solid rgba(127,127,127,.16);border-radius:0 0 12px 12px;background:rgba(127,127,127,.05);font-size:12px;opacity:.7}
239
+ .cnd .cg .ctt p{margin:0 0 10px;overflow-wrap:anywhere}
240
+ .cnd .cg .ctt p:last-child{margin-bottom:0}
241
+ .cnd .cg .ctt p.ctd{font-style:italic;opacity:.85}
242
+ .cnd .cg .ctt p.ctp{opacity:.88}
243
+ .cnd .cg .ctt code.ctk{font:12.5px var(--mono);padding:1px 5px;border-radius:4px;background:rgba(127,127,127,.12);overflow-wrap:anywhere}
244
+ .cnd .cg .ctt code.ctn{color:light-dark(#0f766e,#7dd3c0)}
245
+ .cnd .cg .ctt ul{margin:0;padding:0;list-style:none}
246
+ .cnd .cg .ctt ul.ctl li{display:flex;flex-wrap:wrap;align-items:baseline;gap:2px 10px;padding:6px 0;border-top:1px solid rgba(127,127,127,.14)}
247
+ .cnd .cg .ctt ul.ctl li:first-child{border-top:none}
248
+ .cnd .cg .ctt .ctm{font-size:12px;opacity:.7}
249
+ .cnd .cg .ctt .ctw{margin:12px 0 0;padding:10px 14px;border-radius:8px;background:rgba(245,158,11,.12);border-left:3px solid #f59e0b}
250
+ .cnd .cg .ctt .ctw b{display:block;margin:0 0 4px;font-size:10.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:light-dark(#b45309,#fbbf24)}
251
+ .cnd .cg .ctt .ctw li{margin:4px 0 0;overflow-wrap:anywhere}
224
252
  .cnd .cside{position:fixed;top:0;right:0;bottom:0;z-index:30;box-sizing:border-box;width:min(660px,94vw);overflow:auto;padding:0 20px 28px;background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas));color:CanvasText;border-left:1px solid rgba(127,127,127,.3);box-shadow:-14px 0 36px rgba(15,23,42,.14);transform:translateX(104%);visibility:hidden;transition:transform .22s ease,visibility 0s linear .22s}
225
253
  .cnd .cside:target{transform:none;visibility:visible;transition:transform .22s ease}
226
254
  .cnd .cside .csh{display:flex;align-items:flex-start;gap:12px;position:sticky;top:0;z-index:1;margin:0 -20px 12px;padding:16px 20px 12px;background:var(--jp-layout-color0,var(--vscode-editor-background,Canvas));border-bottom:1px solid rgba(127,127,127,.2)}
@@ -5,6 +5,7 @@ code-node end to end on small folders written into tmp_path: entry points found,
5
5
  from __future__ import annotations
6
6
 
7
7
  import os
8
+ import re
8
9
  from pathlib import Path
9
10
 
10
11
  import pytest
@@ -525,6 +526,19 @@ def test_arrows_from_one_box_run_together_and_the_graph_zooms(tmp_path: Path) ->
525
526
  assert 'class="cgs cgfs" id="cn-graph0-full"' in page and ">Full screen<" in page and "<script" not in page
526
527
 
527
528
 
529
+ def test_a_box_says_what_it_is_in_a_card_on_hover(tmp_path: Path) -> None:
530
+ write(tmp_path / "code", {"app.py": HANDLER})
531
+ out = tmp_path / "page.html"
532
+ assert main([str(tmp_path / "code"), "--no-open", "-o", str(out)]) == 0
533
+ page = out.read_text()
534
+ assert '<g class="cn ' in page and not re.search(r'<g class="cn [^>]*><title>', page) # not the plain tooltip
535
+ assert '<foreignObject class="ctip tp-f0' in page and ":hover) .tp-f0{display:inline}" in page
536
+ # its def as written, in colour
537
+ assert (
538
+ '<pre class="ctc"><span class="pk">def</span> <span class="pf">lambda_handler</span>(event, context):' in page
539
+ )
540
+
541
+
528
542
  def test_line_links_open_the_editor(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
529
543
  from code_node.report.editor import editor_link, open_at
530
544
 
File without changes