# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean CLI v1.0. Copyright 2021 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This program automatically updates the commands reference part of the readme
# It should be ran using `python scripts/readme.py` from the root of the project
import os
os.environ["__README__"] = "true"
import re
from pathlib import Path
from typing import List
from click import Command, Group
from click.testing import CliRunner
from pydantic import ConfigDict
from lean.commands import lean
from lean.models.pydantic import WrappedBaseModel
from lean.components.util.click_group_default_command import DefaultCommandGroup
from lean.container import container
class NamedCommand(WrappedBaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
name: str
command: Command
def get_commands(group: Group, parent_names: List[str] = []) -> List[NamedCommand]:
"""Returns all lean commands by name.
:param group: the group to get the commands from
:param parent_names: the names of the groups leading up to the current group
:return: a list containing all commands in the current group with their full names
"""
all_commands = []
for obj in group.commands.values():
if isinstance(obj, DefaultCommandGroup):
name_parts = parent_names + [group.name, obj.name]
all_commands.append(NamedCommand(name=" ".join(name_parts), command=obj))
if isinstance(obj, Group):
all_commands.extend(get_commands(obj, parent_names + [group.name]))
else:
name_parts = parent_names + [group.name, obj.name]
all_commands.append(NamedCommand(name=" ".join(name_parts), command=obj))
return all_commands
def get_header_id(name: str) -> str:
"""Returns the id of the header with the given name in a GitHub readme.
:param name: the name of the header to get the id of
:return: the id of the header with the given name when rendered on GitHub
"""
name = name.lower()
name = re.sub(r"\s", "-", name)
name = re.sub(r"[^a-zA-Z0-9-]", "", name)
return name
def get_configurations() -> List[dict]:
"""Returns all available configurations with their keys and descriptions."""
config_manager = container.cli_config_manager
configurations = []
for option in config_manager.all_options:
configurations.append({
"key": option.key,
"description": option.description
})
return configurations
def generate_configurations_table(configurations: List[dict]) -> str:
"""Generates a Markdown table for the given configurations."""
table = ["| Key | Description |", "| --- | --- |"]
for config in configurations:
table.append(f"| `{config['key']}` | {config['description']} |")
return "\n".join(table)
def main() -> None:
configurations = get_configurations()
configurations_table = generate_configurations_table(configurations)
named_commands = get_commands(lean)
named_commands = sorted(named_commands, key=lambda c: c.name)
table_of_contents = []
command_sections = []
for c in named_commands:
header = f"### `{c.name}`"
help_str = c.command.get_short_help_str(limit=120)
help_output = CliRunner().invoke(c.command, ["--help"], prog_name=c.name, terminal_width=120).output.strip()
help_output = f"```\n{help_output}\n```"
command_source = None
if not isinstance(c.command, DefaultCommandGroup):
command_source = f"lean/commands/{c.name.replace('lean ', '').replace(' ', '/').replace('-', '_')}.py"
command_source = f"_See code: [{command_source}]({command_source})_"
section_parts = [header, help_str, help_output, command_source]
table_of_contents.append(f"- [`{c.name}`](#{get_header_id(c.name)})")
command_sections.append("\n\n".join(filter(None, section_parts)))
configuration_text = "\n".join(["<!-- configuration table start -->", configurations_table, "<!-- configuration table end -->"])
commands_text = "\n".join(table_of_contents) + "\n\n" + "\n\n".join(command_sections)
commands_text = "\n".join(["<!-- commands start -->", commands_text, "<!-- commands end -->"])
readme_path = Path.cwd() / "README.md"
readme_content = readme_path.read_text(encoding="utf-8")
# CLI Configurations
readme_content = re.sub(r"<!-- configuration table start -->.*<!-- configuration table end -->",
configuration_text,
readme_content,
flags=re.DOTALL)
# Commands
readme_content = re.sub(r"<!-- commands start -->.*<!-- commands end -->",
commands_text,
readme_content,
flags=re.DOTALL)
readme_path.write_text(readme_content, encoding="utf-8")
if __name__ == "__main__":
main()