Module: init

There is a really primitive program that will interactively ask you some questions

It will spit out a basic ini file that you can then modify.


Code Reference

Interactive initialization for mastoscore configuration using prompt_toolkit

confirm_abandon()

Confirm if the user wants to abandon changes

Source code in mastoscore/init.py
def confirm_abandon() -> bool:
    """Confirm if the user wants to abandon changes"""
    try:
        response = get_input(
            PromptSession(), "Are you sure you want to abandon? (yes/no)", "no"
        )
        return response.lower() in ["y", "yes"]
    except (KeyboardInterrupt, EOFError):
        return False

display_config(config)

Display the current configuration in a scrollable window

Source code in mastoscore/init.py
def display_config(config: ConfigParser) -> None:
    """Display the current configuration in a scrollable window"""
    from io import StringIO

    # Convert config to string
    output = StringIO()
    config.write(output)
    content = output.getvalue()

    # Create text area with content
    text_area = TextArea(
        text=content, read_only=True, scrollbar=True, wrap_lines=True, focusable=True
    )

    # Create key bindings
    kb = KeyBindings()

    @kb.add("q")
    def _(event):
        event.app.exit()

    # Create window with content
    window = HSplit(
        [
            text_area,
            Window(
                height=1,
                content=FormattedTextControl(
                    "Use up/down/pageup/pagedown to scroll, q to continue"
                ),
            ),
        ]
    )

    # Create and run application
    app = Application(
        layout=Layout(window), key_bindings=kb, full_screen=True, mouse_support=True
    )
    app.run()

display_section_values(title, values)

Display the current section's values in yellow

Source code in mastoscore/init.py
def display_section_values(title, values):
    """Display the current section's values in yellow"""
    print(f"\n=== {title} ===")
    for key, value in values.items():
        if not key.startswith(("event_", "episode_")):
            print(f"  {key}: \033[33m{value}\033[0m")

edit_section(config, section, session)

Re-prompt for all values in a section

Source code in mastoscore/init.py
def edit_section(config: ConfigParser, section: str, session: PromptSession) -> bool:
    """Re-prompt for all values in a section"""
    if section not in config:
        print(f"\nError: Section {section} not found")
        return False

    print(f"\n=== Editing {section} section ===")

    try:
        if section == "mastoscore":
            config[section]["cred_file"] = get_input(
                session,
                "Path to credentials file (e.g., .env/mybot.env)",
                config[section].get("cred_file"),
            )
            config[section]["hashtag"] = get_input(
                session,
                "Hashtag to track (without the # symbol)",
                config[section].get("hashtag"),
            )
            config[section]["api_base_url"] = get_input(
                session, "Mastodon server URL", config[section].get("api_base_url")
            )
            config[section]["botusername"] = get_input(
                session,
                "Bot's username (without @)",
                config[section].get("botusername"),
            )
            config[section]["timezone"] = get_input(
                session, "Timezone", config[section].get("timezone", "UTC")
            )
            config[section]["top_n"] = get_input(
                session,
                "Number of top posts to show",
                config[section].get("top_n", "3"),
            )
            config[section]["lookback"] = get_input(
                session,
                "Days to look back for posts",
                config[section].get("lookback", "2"),
            )
            config[section]["tag_users"] = get_input(
                session,
                "Tag users in posts? (true/false)",
                config[section].get("tag_users", "false"),
            )

        elif section == "fetch":
            config[section]["max"] = get_input(
                session, "Maximum toots to fetch", config[section].get("max", "3000")
            )
            config[section]["dry_run"] = get_input(
                session,
                "Enable dry run mode for fetching? (true/false)",
                config[section].get("dry_run", "false"),
            )

        elif section == "analyse":
            config[section]["hours_margin"] = get_input(
                session,
                "Hours margin for analysis",
                config[section].get("hours_margin", "1"),
            )

        display_section_values(f"{section} Section Updated", config[section])
        return True

    except (KeyboardInterrupt, EOFError):
        print(f"\nSection {section} edit cancelled.")
        return False

get_input(session, prompt, default=None, required=True)

Get input with light blue prompt and yellow response

Source code in mastoscore/init.py
def get_input(session, prompt, default=None, required=True) -> str:
    """Get input with light blue prompt and yellow response"""
    while True:
        try:
            if default:
                result = session.prompt(
                    HTML(
                        f"<ansicyan>{prompt}</ansicyan> [<ansiyellow>{default}</ansiyellow>]: "
                    )
                )
                if not result and default:
                    return str(default)
            else:
                result = session.prompt(HTML(f"<ansicyan>{prompt}</ansicyan>: "))

            if result or not required:
                return str(result) if result else ""
            print("This field is required. Please provide a value.")

        except (KeyboardInterrupt, EOFError):
            print("\nInput cancelled.")
            raise

init()

Interactive configuration setup for mastoscore using prompt_toolkit

Source code in mastoscore/init.py
def init():
    """
    Interactive configuration setup for mastoscore using prompt_toolkit
    """

    # Set up signal handlers
    def signal_handler(signum, frame):
        print("\nReceived interrupt signal Exiting...")
        exit(1)

    signal(SIGINT, signal_handler)
    signal(SIGQUIT, signal_handler)
    logger = getLogger(__name__)
    basicConfig(
        format="%(asctime)s %(levelname)-8s %(message)s",
        level=logging.ERROR,
        datefmt="%H:%M:%S",
    )
    logger.setLevel(INFO)

    # Define prompt style
    style = Style.from_dict(
        {
            "ansicyan": "#00B7EB bold",  # Light blue
            "ansiyellow": "#FFFF00",  # Yellow
        }
    )

    # Create prompt session
    session = PromptSession(style=style)

    print("\nWelcome to Mastoscore Configuration\n")
    print("This wizard will help you create a configuration file for your event.\n")

    config = ConfigParser(interpolation=ExtendedInterpolation())

    # [mastoscore] section
    config["mastoscore"] = {}
    ms = config["mastoscore"]

    print("\n=== Basic Configuration (mastoscore section) ===")

    try:
        # Core values
        ms["cred_file"] = get_input(
            session, "Path to credentials file (e.g., .env/mybot.env)"
        )
        ms["hashtag"] = get_input(session, "Hashtag to track (without the # symbol)")
        ms["api_base_url"] = get_input(session, "Mastodon server URL")
        ms["botusername"] = get_input(session, "Bot's username (without @)")
        ms["timezone"] = get_input(session, "Timezone", "UTC")
        ms["top_n"] = get_input(session, "Number of top posts to show", "3")
        ms["lookback"] = get_input(session, "Days to look back for posts", "2")
        ms["tag_users"] = get_input(
            session, "Tag users in posts? (true/false)", "false"
        )

        # Standard paths
        ms["journaldir"] = "data"
        ms["journalfile"] = "${mastoscore:hashtag}"

        # [fetch] section
        print("\n=== Fetch Configuration ===")
        config["fetch"] = {}
        fetch = config["fetch"]

        fetch["max"] = get_input(session, "Maximum toots to fetch", "3000")
        fetch["dry_run"] = get_input(
            session, "Enable dry run mode for fetching? (true/false)", "false"
        )

        # [analyse] section
        print("\n=== Analysis Configuration ===")
        config["analyse"] = {}
        analyse = config["analyse"]
        analyse["hours_margin"] = get_input(session, "Hours margin for analysis", "1")

        # Add debug level to sections
        config["fetch"]["debug"] = "20"
        config["analyse"]["debug"] = "20"
        analyse["hours_margin"] = get_input(session, "Hours margin for analysis", "1")

    except (KeyboardInterrupt, EOFError):
        print("\nConfiguration cancelled.")
        return False
    except Exception as e:
        logger.error(f"Error during configuration: {e}")
        return False

    # Clear screen and show final configuration
    clear()
    print("\nConfiguration Summary:\n")
    display_config(config)

    # Ask user what to do
    while True:
        action = get_input(session, "Choose action (save/change/abandon)", "save")

        if action.lower() == "save":
            filename = f"ini/{ms['hashtag']}.ini"
            if save_config(config, filename):
                print(f"\nConfiguration saved to {filename}")
                exit(0)
            else:
                print(
                    "\nFailed to save configuration. Please try save again or abandon to exit."
                )
                continue

        elif action.lower() == "change":
            section = select_section()
            if section:
                if edit_section(config, section, session):
                    # Show updated configuration
                    clear()
                    print("\nConfiguration Summary:\n")
                    display_config(config)
                continue

        elif action.lower() == "abandon":
            if confirm_abandon():
                print("\nConfiguration abandoned.")
                exit(1)

        else:
            print("\nInvalid choice. Please choose 'save', 'change', or 'abandon'")

save_config(config, filename)

Save configuration to file with error handling

Source code in mastoscore/init.py
def save_config(config: ConfigParser, filename: str) -> bool:
    """Save configuration to file with error handling"""
    import os

    try:
        # Create directory if it doesn't exist
        os.makedirs(os.path.dirname(filename), exist_ok=True)

        # Write configuration file
        with open(filename, "w") as f:
            config.write(f)

        return True

    except PermissionError:
        print(f"\nError: Permission denied writing to {filename}")
        return False
    except FileNotFoundError:
        print(f"\nError: Directory path not found for {filename}")
        return False
    except Exception as e:
        print(f"\nError writing configuration file: {e}")
        return False

select_section()

Display a menu to select a section to edit

Source code in mastoscore/init.py
def select_section() -> str:
    """Display a menu to select a section to edit"""
    sections = [
        ("mastoscore", "1. mastoscore - Basic Configuration"),
        ("fetch", "2. fetch - Fetch Configuration"),
        ("analyse", "3. analyse - Analysis Configuration"),
    ]

    radio_list = RadioList(sections)

    # Create application
    app = Application(
        layout=Layout(
            HSplit(
                [
                    Window(
                        content=FormattedTextControl(
                            "Select a section to edit (use arrow keys or number keys):"
                        )
                    ),
                    radio_list,
                ]
            )
        ),
        full_screen=True,
    )

    result = app.run()
    return result if result else ""