diff --git a/EXAMPLE/prometheus-advanced.html b/EXAMPLE/prometheus-advanced.html index c269008..ca8e877 100644 --- a/EXAMPLE/prometheus-advanced.html +++ b/EXAMPLE/prometheus-advanced.html @@ -5,113 +5,248 @@ prometheus - helm chart documentation diff --git a/EXAMPLE/prometheus.html b/EXAMPLE/prometheus.html index 80d708b..56fe7e3 100644 --- a/EXAMPLE/prometheus.html +++ b/EXAMPLE/prometheus.html @@ -6,41 +6,247 @@ prometheus - helm chart documentation diff --git a/EXAMPLE/style.css b/EXAMPLE/style.css index cd2131f..706d3bd 100644 --- a/EXAMPLE/style.css +++ b/EXAMPLE/style.css @@ -1,36 +1,242 @@ -body { - font-family: sans-serif; - margin: auto; - padding: 10px; - max-width: 80%; +html { + --blue: #A8DADC; /* Pastel Cyan */ + --dark-blue: #457B9D; /* Steel Blue */ + --light-blue: #F1FAEE; /* Honeydew */ + --light-gray: #F8F9FA; /* Light Gray */ + --gray: #D8E2DC; /* Soft Gray */ + --dark-gray: #264653; /* Deep Green */ + --dark-bg: #343A40; /* Charcoal */ + --dark-card-bg: #495057; /* Granite */ + --dark-text: #E9ECEF; /* Light Gray Text for Dark Mode */ + --darker-text: #121212; /* Dark text for Dark Mode */ + --dark-gray-text: #264653; /* Deep Green for Dark Mode Text */ + --dark-link: #A8DADC; /* Pastel Cyan for Links */ + --dark-link-hover: #81B1BD; /* Steel Blue for Hover */ + --primary-gradient: linear-gradient(135deg, #A8DADC, #F1FAEE); /* Cyan to Honeydew */ + --secondary-gradient: linear-gradient(135deg, #457B9D, #A8DADC); /* Steel Blue to Cyan */ + --margin-top: 3em; + --border-radius: 15px; +} + +body, html { + height: 100%; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + background-color: var(--light-gray); + color: var(--dark-gray); + transition: all 0.3s; +} + +#navbar-outer { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: auto; + background: var(--secondary-gradient); + overflow-x: hidden; + margin-top: 0; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + z-index: 1000; + transition: all 0.3s; +} + +#navbar, .container { + max-width: 80%; + margin: auto; +} + +#navbar a { + padding: 1em; + text-decoration: none; + color: var(--light-gray); + display: inline-block; + transition: background-color 0.3s, transform 0.3s; + border-radius: var(--border-radius); +} + +#navbar a:first-child { + background: var(--primary-gradient); + font-weight: bold; + color: var(--dark-gray); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +#navbar a:hover { + background: var(--primary-gradient); + transform: translateY(-2px); + color: var(--dark-gray); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2); +} + +#search { + position: relative; + padding: 15px; + width: 98%; + margin: 20px auto; + display: block; + font-size: medium; + color: var(--dark-gray); + border: none; + border-radius: var(--border-radius); + background: var(--light-gray); + box-shadow: inset 6px 6px 12px #ccc, inset -6px -6px 12px #fff; + transition: all 0.3s; +} + +#search:focus { + box-shadow: inset 6px 6px 12px #bbb, inset -6px -6px 12px #eee, 0 0 8px 2px rgba(248, 241, 225, 0.5); + outline: none; +} + +.content { + padding: 25px; + margin-top: var(--margin-top); + background-color: white; + border-radius: var(--border-radius); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + transition: all 0.3s; + background: var(--light-gray); +} + +.container { + padding: 25px; + border-radius: var(--border-radius); + box-shadow: 4px 4px 8px #ccc, -4px -4px 8px #fff; + background-color: var(--light-gray); + transition: all 0.3s; +} + +.table-container { + padding: 0 1em; + overflow: auto; } table { - font-family: Arial, Helvetica, sans-serif; - border-collapse: collapse; - display: block; - overflow-x: auto; + border-collapse: collapse; + width: 100%; + background-color: white; + border-radius: var(--border-radius); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; + overflow: hidden; + transition: all 0.3s; } td, th { - border: 1px solid #ddd; - padding: 8px; + border: 1px solid var(--gray); + padding: 12px; + transition: background-color 0.3s; } -tr:nth-child(even){background-color: #f2f2f2;} +tr:nth-child(even) { + background-color: #E9ECEF; /* Soft Light Gray */ +} -tr:hover {background-color: #E0F2FF;} +tr:hover { + background-color: #D8E2DC; /* Very Soft Gray */ +} th { - padding-top: 12px; - padding-bottom: 12px; - text-align: left; - background-color: #0288d1; - color: white; + padding-top: 14px; + padding-bottom: 14px; + text-align: left; + background: var(--secondary-gradient); + color: var(--light-gray); } pre { background-color: #E4E4E4; - padding: 0.5em; - border-radius: 5px; + padding: 15px; + border-radius: var(--border-radius); + overflow-x: auto; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.3s; +} + +h1, h2, h3, h4, h5, h6 { + scroll-margin-top: var(--margin-top); + color: var(--dark-gray); + transition: color 0.3s; +} + +/* Dark Mode */ +@media (prefers-color-scheme: dark) { + body, html { + background-color: var(--dark-bg); + color: var(--dark-text); + } + + #navbar-outer { + background: var(--primary-gradient); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.5); + } + + #navbar a { + color: var(--dark-gray-text); /* Make navbar links text darker */ + } + + #navbar a:first-child { + background: var(--secondary-gradient); + color: var(--light-gray); + } + + #navbar a:hover { + background: var(--secondary-gradient); + color: var(--light-gray); + } + + #search { + background: var(--dark-card-bg); + color: var(--dark-text); + box-shadow: inset 6px 6px 12px #292d31, inset -6px -6px 12px #5a616a; + } + + #search:focus { + box-shadow: inset 6px 6px 12px #292d31, inset -6px -6px 12px #5a616a, 0 0 8px 2px rgba(168, 218, 220, 0.5); + } + + .content, .container { + background: var(--dark-card-bg); + box-shadow: 4px 4px 8px #292d31, -4px -4px 8px #5a616a; + } + + table { + background-color: var(--dark-card-bg); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.5); + } + + td, th { + border: 1px solid #555; + } + + tr:nth-child(even) { + background-color: #42474d; /* Dark Granite */ + } + + tr:hover { + background-color: #3c3f41; /* Slightly lighter dark granite */ + } + + th { + background: var(--primary-gradient); + color: var(--dark-gray-text); /* Make table header text darker */ + } + + pre { + background-color: #333; + } + + h1, h2, h3, h4, h5, h6 { + color: var(--light-blue); + } + + a { + color: var(--dark-link); + transition: color 0.3s; + } + + a:hover { + color: var(--dark-link-hover); + } } diff --git a/README.md b/README.md index 481eb92..cd2f5dc 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ helm chart, follow these links: ## Usage -### Docker Image +### 🐳 Docker Image | | Note | @@ -46,7 +46,7 @@ docker run -v ${full_path_to_host_chart_dir}:/tmp/chart ghcr.io/very-doge-wow/st ``` -### Installation +### 🛠 Installation To run it natively on your machine using pipenv: @@ -66,7 +66,7 @@ pip install pyyaml markdown python stella.py --help ``` -### General Usage +### 📚 General Usage ```text @@ -94,7 +94,7 @@ additional functionality which would break when using custom CSS. It will create a static html site with dynamic navbar and a search for the chart's values. -## Adding `stella` Docstrings to your Chart +## ⎈ Adding `stella` Docstrings to your Chart Metadata is read from the present files of your chart. Additionally, you should also document the options given @@ -155,7 +155,7 @@ Will yield the output: -## Custom Templating +## 📄 Custom Templating To specify a custom template, create a text/markdown file, then pass it to stella using the config parameter. @@ -172,14 +172,14 @@ You can use the following fields inside your template: * `{{ stella.objects }}` * `{{ stella.values }}` -## Contributing to `stella` +## 💫 Contributing to `stella` You want to contribute? Awesome! Feel free to propose changes, report bugs or request features and improvements. But first, please read the [contribution guidelines](https://github.com/very-doge-wow/stella/blob/main/CONTRIBUTING.md). -## Why `stella`? +## 💭 Why `stella`? `stella` is named after [Tilemann Stella](https://de.wikipedia.org/wiki/Tilemann_Stella), @@ -187,3 +187,57 @@ a scholar from the Renaissance era. He is most famously known for being a cartographer and for creating multiple waterways, which is fitting when considering the tool should create helm chart docs. + +## 🧑‍💻 Development + + +
+Expand for more info + +### Linting Code + +Install linters beforehand: + +* [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli) +* [ruff](https://docs.astral.sh/ruff/) + +```shell +# Markdown Linter +markdownlint './*.md' \ + --ignore './test/output.md' \ + --ignore './test/custom-template-keywords.md' \ + --ignore './EXAMPLE_OUTPUT.md' + +# Python Linter +ruff check \ + --fix \ + --config "lint.extend-select=['E','F','B','Q','S','W','DJ']" . +``` + +### Running Unit Tests + +```shell +pipenv install -d +pipenv run pytest -vv --cov --cov-report=xml +``` + +### Updating Example Outputs + +```shell +pipenv install + +pipenv run python stella.py \ + -fh \ + -css EXAMPLE/style.css \ + -hcp EXAMPLE/prometheus \ + -o EXAMPLE/prometheus.html + +pipenv run python stella.py \ + -fh \ + --advanced-html \ + -hcp EXAMPLE/prometheus \ + -o EXAMPLE/prometheus-advanced.html +``` + +
+ diff --git a/reader/chart_reader.py b/reader/chart_reader.py index c6cc9a2..e8e5860 100644 --- a/reader/chart_reader.py +++ b/reader/chart_reader.py @@ -81,7 +81,7 @@ def get_value_from_yaml(parsed_yaml: dict, full_path: str) -> dict: Returns: result (dict): Generated data structure. """ - keys = full_path.split('.') # Split the full path into individual keys + keys = full_path.split(".") # Split the full path into individual keys result = {} # Initialize an empty dictionary to store the result current = result # Set a pointer to the beginning of the result dictionary for key in keys[:-1]: # Iterate over each key except the last one @@ -98,7 +98,7 @@ def get_value_from_yaml(parsed_yaml: dict, full_path: str) -> dict: elif isinstance(value, list) and not value: current[keys[-1]] = [] # Set the value if it's an empty list elif isinstance(value, str) and not value.strip(): - current[keys[-1]] = '' # Set the value if it's an empty string or contains only whitespace + current[keys[-1]] = "" # Set the value if it's an empty string or contains only whitespace else: current[keys[-1]] = value # Otherwise, set the value as is else: @@ -124,18 +124,18 @@ def build_full_path(i: int, value_name_dirty: str, value_name_clean: str, values # first element will always be the current key's name full_path = value_name_clean # check if whitespace before key is found - match = re.search(r'^(\s+).*$', value_name_dirty) + match = re.search(r"^(\s+).*$", value_name_dirty) # if already on top-level, we are done here if not match: return full_path # save original indent to compare against it below - initial_indent = match.group(0).count(' ') + initial_indent = match.group(0).count(" ") index = i # save all indents to which a key was already added to full_path, as only one can exist for each indent matched_indents = [] while match: # count the indent - indent_num = match.group(0).count(' ') + indent_num = match.group(0).count(" ") # early exit if already on toplevel if indent_num == 0: return f"{upper_key}.{full_path}" @@ -150,14 +150,14 @@ def build_full_path(i: int, value_name_dirty: str, value_name_clean: str, values # index now points to the line with the key value_name_dirty = values_lines[index].split(":")[0] upper_key = value_name_dirty.strip() - match_new = re.search(r'^\s+', value_name_dirty) + match_new = re.search(r"^\s+", value_name_dirty) if match_new: - indent_num_new = match_new.group(0).count(' ') + indent_num_new = match_new.group(0).count(" ") # make sure the found key is actually closer to top-level than the first one by counting indent if indent_num > indent_num_new and initial_indent > indent_num_new and (indent_num_new not in matched_indents): full_path = f"{upper_key}.{full_path}" matched_indents.append(indent_num_new) - match = re.search(r'^\s*', value_name_dirty) + match = re.search(r"^\s*", value_name_dirty) return full_path @@ -185,8 +185,8 @@ def generate_values_doc(doc: dict, helm_chart_path: str) -> dict: if stella in line: # found a stella doc string # get the indent - match = re.search(r'^(\s+).*$', line) - indent_num = match.group(0).count(' ')-1 if match else 0 + match = re.search(r"^(\s+).*$", line) + indent_num = match.group(0).count(" ")-1 if match else 0 doc_string = "" i = index # check if the next line still is a comment, if so add it to docstring @@ -316,7 +316,7 @@ def generate_objects(doc: dict, helm_chart_path: str) -> dict: if obj != "" and type(obj) == str: doc["objects"].append({ "kind": obj, - "from Template": tmpl['path'] + "from Template": tmpl["path"] }) logging.debug("done generating objects doc") diff --git a/reader/chart_reader_test.py b/reader/chart_reader_test.py index fad871a..ea001b4 100644 --- a/reader/chart_reader_test.py +++ b/reader/chart_reader_test.py @@ -1,4 +1,3 @@ -import unittest import yaml @@ -25,45 +24,45 @@ def test_read(): result["objects"] = sorted(result["objects"], key=lambda item: item.get("kind")) result["templates"] = sorted(result["templates"], key=lambda item: item.get("path")) assert_that(result, has_entries( - {'name': 'test-chart', 'appVersion': '1.16.0', 'apiVersion': 'v2', 'version': '0.1.0', - 'description': 'A Helm chart for Kubernetes', 'type': 'application', 'dependencies': [ - {'name': 'postgresql', 'condition': 'postgresql.enabled', 'version': '1.2.3', - 'repository': 'https://lol.de/repo/'}, - {'name': 'mysql', 'condition': 'mysql.enabled', 'version': '1.2.3', 'repository': 'https://lol.de/repo/'}], - 'values': [ - {'name': 'affinity', - 'description': '', - 'default': {'affinity': {}}, 'example': ''},{'name': 'autoscaling', 'description': '', 'default': - {'autoscaling': {'enabled': False, 'minReplicas': 1, 'maxReplicas': 100, - 'targetCPUUtilizationPercentage': 80}}, 'example': ''}, - {'name': 'fullnameOverride', 'description': '', 'default': {'fullnameOverride': ''}, - 'example': ''}, {'name': 'image', 'description': 'which image to deploy\n', 'default': - {'image': {'repository': 'nginx', 'pullPolicy': 'IfNotPresent', 'tag': ''}}, 'example': + {"name": "test-chart", "appVersion": "1.16.0", "apiVersion": "v2", "version": "0.1.0", + "description": "A Helm chart for Kubernetes", "type": "application", "dependencies": [ + {"name": "postgresql", "condition": "postgresql.enabled", "version": "1.2.3", + "repository": "https://lol.de/repo/"}, + {"name": "mysql", "condition": "mysql.enabled", "version": "1.2.3", "repository": "https://lol.de/repo/"}], + "values": [ + {"name": "affinity", + "description": "", + "default": {"affinity": {}}, "example": ""},{"name": "autoscaling", "description": "", "default": + {"autoscaling": {"enabled": False, "minReplicas": 1, "maxReplicas": 100, + "targetCPUUtilizationPercentage": 80}}, "example": ""}, + {"name": "fullnameOverride", "description": "", "default": {"fullnameOverride": ""}, + "example": ""}, {"name": "image", "description": "which image to deploy\n", "default": + {"image": {"repository": "nginx", "pullPolicy": "IfNotPresent", "tag": ""}}, "example": '\nimage:\n repository: very-doge-wow/stella\n pullPolicy: IfNotPresent\n tag: "latest"\n'}, - {'name': 'imagePullSecrets', 'description': '', 'default': {'imagePullSecrets': []}, 'example': ''}, - {'name': 'ingress', 'description': '', 'default': {'ingress': {'enabled': False, 'className': '', - 'annotations': {}, 'hosts': [{'host': 'chart-example.local', 'paths': - [{'path': '/', 'pathType': 'ImplementationSpecific'}]}], 'tls': []}}, 'example': ''}, - {'name': 'nameOverride', 'description': '', 'default': {'nameOverride': ''}, 'example': ''}, - {'name': 'nodeSelector', 'description': '', 'default': {'nodeSelector': {}}, 'example': ''}, - {'name': 'podAnnotations', 'description': '', 'default': {'podAnnotations': {}}, 'example': ''}, - {'name': 'podSecurityContext', 'description': '', 'default': {'podSecurityContext': {}}, 'example': ''}, - {'name': 'replicaCount', 'description': 'how many replicas to deploy\n', 'default': - {'replicaCount': 1}, 'example': ''}, {'name': 'resources', 'description': '', 'default': - {'resources': {}}, 'example': ''}, {'name': 'securityContext', 'description': '', - 'default': {'securityContext': {}}, 'example': ''}, {'name': 'service', 'description': '', - 'default': {'service': {'type': 'ClusterIP', 'port': 80}}, 'example': ''}, - {'name': 'serviceAccount', 'description': '', 'default': {'serviceAccount': - {'create': True, 'annotations': {}, 'name': ''}}, 'example': ''}, - {'name': 'tolerations', 'description': '', 'default': {'tolerations': []}, 'example': ''}], - 'templates': [{'path': 'deployment.yaml'}, {'path': 'hpa.yaml'}, {'path': 'ingress.yaml'}, - {'path': 'service.yaml'}, {'path': 'serviceaccount.yaml'}], - 'objects': [{'kind': 'Deployment', 'from Template': 'deployment.yaml'}, - {'kind': 'HorizontalPodAutoscaler', 'from Template': 'hpa.yaml'}, - {'kind': 'Ingress', 'from Template': 'ingress.yaml'}, - {'kind': 'Service', 'from Template': 'service.yaml'}, - {'kind': 'ServiceAccount', 'from Template': 'serviceaccount.yaml'}], - 'commands': [{'description': '', 'command': ''}]} + {"name": "imagePullSecrets", "description": "", "default": {"imagePullSecrets": []}, "example": ""}, + {"name": "ingress", "description": "", "default": {"ingress": {"enabled": False, "className": "", + "annotations": {}, "hosts": [{"host": "chart-example.local", "paths": + [{"path": "/", "pathType": "ImplementationSpecific"}]}], "tls": []}}, "example": ""}, + {"name": "nameOverride", "description": "", "default": {"nameOverride": ""}, "example": ""}, + {"name": "nodeSelector", "description": "", "default": {"nodeSelector": {}}, "example": ""}, + {"name": "podAnnotations", "description": "", "default": {"podAnnotations": {}}, "example": ""}, + {"name": "podSecurityContext", "description": "", "default": {"podSecurityContext": {}}, "example": ""}, + {"name": "replicaCount", "description": "how many replicas to deploy\n", "default": + {"replicaCount": 1}, "example": ""}, {"name": "resources", "description": "", "default": + {"resources": {}}, "example": ""}, {"name": "securityContext", "description": "", + "default": {"securityContext": {}}, "example": ""}, {"name": "service", "description": "", + "default": {"service": {"type": "ClusterIP", "port": 80}}, "example": ""}, + {"name": "serviceAccount", "description": "", "default": {"serviceAccount": + {"create": True, "annotations": {}, "name": ""}}, "example": ""}, + {"name": "tolerations", "description": "", "default": {"tolerations": []}, "example": ""}], + "templates": [{"path": "deployment.yaml"}, {"path": "hpa.yaml"}, {"path": "ingress.yaml"}, + {"path": "service.yaml"}, {"path": "serviceaccount.yaml"}], + "objects": [{"kind": "Deployment", "from Template": "deployment.yaml"}, + {"kind": "HorizontalPodAutoscaler", "from Template": "hpa.yaml"}, + {"kind": "Ingress", "from Template": "ingress.yaml"}, + {"kind": "Service", "from Template": "service.yaml"}, + {"kind": "ServiceAccount", "from Template": "serviceaccount.yaml"}], + "commands": [{"description": "", "command": ""}]} )) @@ -71,12 +70,12 @@ def test_generate_chart_metadata_real_file(): result = chart_reader.generate_chart_metadata({}, "test/test-chart") assert_that(result, has_entries( { - 'apiVersion': 'v2', - 'appVersion': '1.16.0', - 'description': 'A Helm chart for Kubernetes', - 'name': 'test-chart', - 'type': 'application', - 'version': '0.1.0' + "apiVersion": "v2", + "appVersion": "1.16.0", + "description": "A Helm chart for Kubernetes", + "name": "test-chart", + "type": "application", + "version": "0.1.0" } )) @@ -89,12 +88,12 @@ def test_generate_chart_metadata_unknown(): chart_reader.yaml.safe_load = real_yaml_load assert_that(result, has_entries( { - 'apiVersion': 'unknown', - 'appVersion': 'unknown', - 'description': 'unknown', - 'name': 'unknown', - 'type': 'unknown', - 'version': 'unknown' + "apiVersion": "unknown", + "appVersion": "unknown", + "description": "unknown", + "name": "unknown", + "type": "unknown", + "version": "unknown" } )) @@ -116,15 +115,15 @@ def test_generate_values_doc_and_example(): result = chart_reader.generate_values_doc(doc, "test/values-stella") assert_that(result["values"], contains_inanyorder( { - 'name': 'replicaCount', - 'description': 'how many replicas to deploy\n', - 'default': {'replicaCount': 1}, 'example': '' + "name": "replicaCount", + "description": "how many replicas to deploy\n", + "default": {"replicaCount": 1}, "example": "" }, { - 'name': 'image', - 'description': 'which image to deploy\n', - 'default': {'image': {'repository': 'nginx', 'pullPolicy': 'IfNotPresent', 'tag': ''}}, - 'example': '\nimage:\n repository: very-doge-wow/stella\n pullPolicy: IfNotPresent\n tag: "latest"\n' + "name": "image", + "description": "which image to deploy\n", + "default": {"image": {"repository": "nginx", "pullPolicy": "IfNotPresent", "tag": ""}}, + "example": '\nimage:\n repository: very-doge-wow/stella\n pullPolicy: IfNotPresent\n tag: "latest"\n' } )) @@ -146,9 +145,9 @@ def test_generate_values_doc_only(): result = chart_reader.generate_values_doc(doc, "test/values-stella-only") assert_that(result["values"], contains_inanyorder( { - 'name': 'replicaCount', - 'description': 'how many replicas to deploy\n', - 'default': {'replicaCount': 1}, 'example': '' + "name": "replicaCount", + "description": "how many replicas to deploy\n", + "default": {"replicaCount": 1}, "example": "" } )) @@ -169,7 +168,7 @@ def test_generate_values_pipes_in_tables(): } result = chart_reader.generate_values_doc(doc, "test/values-pipes") assert_that(result["values"], contains_inanyorder( - {'name': 'customObjects', 'description': 'Test for using pipes in examples\n', 'default': {'customObjects': []}, 'example': '\ncustomObjects:\n - \\|\n best-string\n'} + {"name": "customObjects", "description": "Test for using pipes in examples\n", "default": {"customObjects": []}, "example": "\ncustomObjects:\n - \\|\n best-string\n"} )) @@ -190,8 +189,8 @@ def test_generate_values_comments_in_examples(): result = chart_reader.generate_values_doc(doc, "test/values-comments-examples") print(result) assert_that(result["values"], contains_inanyorder( - {'name': 'best', 'description': 'Test for comments in examples\n', 'default': {'best': []}, - 'example': '\nbest:\n # this is a comment inside an example\n - value\n'} + {"name": "best", "description": "Test for comments in examples\n", "default": {"best": []}, + "example": "\nbest:\n # this is a comment inside an example\n - value\n"} )) @@ -212,7 +211,7 @@ def test_generate_values_docs_nested(): result = chart_reader.generate_values_doc(doc, "test/values-nested-docs") print(result) assert_that(result["values"], contains_inanyorder( - {'name': 'image', 'description': 'which image to deploy\n', 'default': {'image': {'repository': 'nginx', 'pullPolicy': 'IfNotPresent', 'tag': ''}}, 'example': '\nimage:\n repository: very-doge-wow/stella\n pullPolicy: IfNotPresent\n'}, {'name': 'image.tag', 'description': 'Overrides the image tag whose default is the chart appVersion.\n', 'default': {'image': {'tag': ''}}, 'example': '\nimage:\n tag: "latest"\n'}, {'name': 'replicaCount', 'description': 'how many replicas to deploy\n', 'default': {'replicaCount': 1}, 'example': ''} + {"name": "image", "description": "which image to deploy\n", "default": {"image": {"repository": "nginx", "pullPolicy": "IfNotPresent", "tag": ""}}, "example": "\nimage:\n repository: very-doge-wow/stella\n pullPolicy: IfNotPresent\n"}, {"name": "image.tag", "description": "Overrides the image tag whose default is the chart appVersion.\n", "default": {"image": {"tag": ""}}, "example": '\nimage:\n tag: "latest"\n'}, {"name": "replicaCount", "description": "how many replicas to deploy\n", "default": {"replicaCount": 1}, "example": ""} )) @@ -405,44 +404,44 @@ def test_build_full_path(): # desc getme: true """ - result = chart_reader.build_full_path(i=3, value_name_dirty=' another', value_name_clean='another', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=3, value_name_dirty=" another", value_name_clean="another", + values_lines=test_yaml.split("\n")) assert result == "first.another" - result = chart_reader.build_full_path(i=2, value_name_dirty=' element', value_name_clean='element', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=2, value_name_dirty=" element", value_name_clean="element", + values_lines=test_yaml.split("\n")) assert result == "first.element" - result = chart_reader.build_full_path(i=5, value_name_dirty='emptydict', value_name_clean='emptydict', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=5, value_name_dirty="emptydict", value_name_clean="emptydict", + values_lines=test_yaml.split("\n")) assert result == "emptydict" - result = chart_reader.build_full_path(i=5, value_name_dirty='emptyarray', value_name_clean='emptyarray', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=5, value_name_dirty="emptyarray", value_name_clean="emptyarray", + values_lines=test_yaml.split("\n")) assert result == "emptyarray" - result = chart_reader.build_full_path(i=9, value_name_dirty='yet', value_name_clean='yet', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=9, value_name_dirty="yet", value_name_clean="yet", + values_lines=test_yaml.split("\n")) assert result == "yet" - result = chart_reader.build_full_path(i=10, value_name_dirty=' another', value_name_clean='another', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=10, value_name_dirty=" another", value_name_clean="another", + values_lines=test_yaml.split("\n")) assert result == "yet.another" - result = chart_reader.build_full_path(i=14, value_name_dirty=' drei', value_name_clean='drei', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=14, value_name_dirty=" drei", value_name_clean="drei", + values_lines=test_yaml.split("\n")) assert result == "eins.zwei.drei" - result = chart_reader.build_full_path(i=16, value_name_dirty=' fuenf', value_name_clean='fuenf', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=16, value_name_dirty=" fuenf", value_name_clean="fuenf", + values_lines=test_yaml.split("\n")) assert result == "eins.zwei.vier.fuenf" - result = chart_reader.build_full_path(i=18, value_name_dirty=' sechs', value_name_clean='sechs', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=18, value_name_dirty=" sechs", value_name_clean="sechs", + values_lines=test_yaml.split("\n")) assert result == "eins.sechs" - result = chart_reader.build_full_path(i=29, value_name_dirty=' getme', value_name_clean='getme', - values_lines=test_yaml.split('\n')) + result = chart_reader.build_full_path(i=29, value_name_dirty=" getme", value_name_clean="getme", + values_lines=test_yaml.split("\n")) assert result == "banane.test.getme" diff --git a/stella.py b/stella.py index 0c351b1..6d99983 100755 --- a/stella.py +++ b/stella.py @@ -1,12 +1,11 @@ #! /usr/local/bin/python3 import argparse import logging -import os import reader.chart_reader as chart_reader import writer.doc_writer as doc_writer -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser(description="Will create documentation for helm charts using metadata.") parser.add_argument("-hcp", "--helm-chart-path", help="Path to helm chart (default `.`).", required=False, default=".") parser.add_argument("-o", "--output", help="Output file (default `output.md`).", required=False, default="output.md") @@ -40,14 +39,14 @@ try: # read all necessary metadata result = chart_reader.read(args.helm_chart_path) - + # fix file ending for case format html if args.format_html and args.output.endswith(".md"): args.output = args.output.replace(".md", ".html") # write doc from gathered data doc_writer.write(output=args.output, doc=result, template=args.template, format_html=args.format_html, advanced_html=args.advanced_html, css=args.css) - except Exception as err: + except Exception: logging.exception("Error occurred.") exit(1) diff --git a/writer/doc_writer.py b/writer/doc_writer.py index cf309eb..d0934fa 100644 --- a/writer/doc_writer.py +++ b/writer/doc_writer.py @@ -113,113 +113,248 @@ def write(output: str, doc: dict, template: str, format_html: bool, advanced_htm REPLACE_STRING_TITLE @@ -385,7 +520,7 @@ def translate_list_of_dicts_to_md(list_of_dicts: list) -> str: def count_lines(text): # Split the text by newline characters - lines = text.split('\n') + lines = text.split("\n") # Count the number of lines return len(lines) diff --git a/writer/doc_writer_test.py b/writer/doc_writer_test.py index 3352022..d990cb7 100644 --- a/writer/doc_writer_test.py +++ b/writer/doc_writer_test.py @@ -487,113 +487,248 @@ def test_writer_advanced_html(): unittest - helm chart documentation