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='%(levelname)s\t%(message)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 ""