<commit_before>use YAMLish;

unit module Uzu::Config;

sub valid-project-folder-structure(
    @template_dirs
    --> Bool()
) {
    @template_dirs.grep({ !$_.IO.e }).&{
        unless elems $_ > 0 {
            note "Project directory missing: \n * {$_.join: "\n * "}";
            exit 1;
        }
    }();
}

sub parse-config(
    IO::Path :$config_file
    --> Map()
) {
    unless $config_file.IO.f {
        note "Config file [{$config_file}] not found. Please run uzu init to generate.";
        exit 1;
    }

    try {

        CATCH {
            default {
                note "Invalid config yaml file [{$config_file}]";
                note .Str;
                exit 1;
            }
        }

        my %config      = load-yaml slurp($config_file) when $config_file.IO.f;
        # Normalize themes
        %config<themes> = %config<themes>.List.hyper.map(-> $theme {
            $theme ~~ Iterable ?? $theme.head !! $theme;
        }).List;

        # Collect non-core variables into :site
        %config<site> = flat(grep({ 
            # Exclude core vars
            # from site hash
            !['exclude',
              'exclude_pages',
              'extended',
              'host',
              'language',
              'port',
              'project_root',
              'template_engine',
              'theme',
              'themes',
              'pre_command',
              'post_command'].contains: .key
        }, %config)).Hash;

        return %config;
    }
}

sub build-dir-exists(@seen, $dir) {
    if so @seen (cont) $dir {
        note "Cannot render multiple themes to the same build directory [{$dir}]" when so @seen (cont) $dir;
        exit 1;
    }
}

sub safe-build-dir-check($dir, :$project_root) {
    if $dir.IO ~~ $*HOME.IO|$project_root.IO {
        note "Build directory [{$dir.IO.path}] cannot be {$*HOME} or project root [{$project_root}].";
        exit 1;
    }
}

sub themes-config(
    Str      :$single_theme,   # optional, only config this theme
    IO::Path :$themes_dir,     # project themes dir
    Str      :$theme,          # yaml theme:
             :@themes,         # yaml themes:
    IO::Path :$build_dir,      # default build dir
    Int      :$port,           # default port
    List     :$exclude_pages,  # default exclude pages
    IO::Path :$project_root    # project root
) {
    # Always use single theme if yaml `theme:` variable set
    return %{
        "{$theme||'default'}" => %{
            theme_dir      => $themes_dir.IO.child("$theme"||'default'),
            build_dir      => $build_dir,
            port           => $port,
            exclude_pages  => $exclude_pages},
    } when defined $theme and $theme !~~ "";

    # Keep track of build dirs to avoid
    # reusing the same build dir for
    # multiple themes.
    my IO::Path @seen_build_dirs;

    # We can to keep incrementing the 
    # port to assign to the next theme
    # if a port is not defined for the
    # theme or the defined port is 
    # already used.
    my Int $working_port = $port;
    my %themes;

    # ... otherwise use hash
    for hyper @themes -> $theme {
        
        given $theme {

            # Single theme, $theme = theme name
            when Str {

                # When user passes --theme to cli only configure that theme or skip
                next when $single_theme.defined && $single_theme !~~ $theme;

                my Int $theme_port = $working_port;
                # Increment next port
                ++$working_port;

                %themes{"{$theme||'default'}"} = %{
                    theme_dir      => $themes_dir.IO.child($theme||'default'),
                    build_dir      => $build_dir.IO.child($theme||'default'),
                    port           => $theme_port,
                    exclude_pages  => $exclude_pages
                }
            } 
            
            when Pair {

                my Str  $theme_name    = $theme.key;
                my Hash $theme_config  = $theme.value;
                my IO::Path $theme_dir = $themes_dir.IO.child($theme_name);

                # When user passes --theme to cli only configure that theme or skip
                next when $single_theme.defined && $single_theme !~~ $theme_name;

                do {
                    note "Theme directory [{$theme_name}] does not exist. Skipping.";
                    next;
                } unless $theme_dir.IO.e;

                # Single theme build dir
                my IO::Path $theme_build_dir = do if @themes.elems eq 1 {
                    $theme_config<build_dir>:exists ?? $theme_config<build_dir>.IO !! $build_dir;
                } else { # Mutiple themes build dir
                    my $theme_build_dir = $theme_config<build_dir> ?? $theme_config<build_dir> !! $build_dir.IO.child($theme_name);
                    build-dir-exists @seen_build_dirs, $theme_build_dir;
                    push @seen_build_dirs, $theme_build_dir.IO;
                    safe-build-dir-check $theme_build_dir.IO, :$project_root;
                    $theme_build_dir.IO;
                }

                # Single theme port
                my Int $theme_port = do if @themes.elems eq 1 {
                    $theme_config<port>||$working_port;
                } else {
                    # Multiple themes port
                    $working_port = $theme_config<port> when $theme_config<port> && $theme_config<port> > $working_port;
                    $theme_config<port>||$working_port;
                }

                # Increment next port
                ++$working_port;

                %themes{"{$theme_name}"} = %{
                    theme_dir      => $theme_dir,
                    build_dir      => $theme_build_dir,
                    port           => $theme_port,
                    exclude_pages  => $theme_config<exclude_pages>
                }
            }
        }
    }
    return %themes;
}<commit_msg>working port for multiple themes<commit_after>use YAMLish;

unit module Uzu::Config;

sub valid-project-folder-structure(
    @template_dirs
    --> Bool()
) {
    @template_dirs.grep({ !$_.IO.e }).&{
        unless elems $_ > 0 {
            note "Project directory missing: \n * {$_.join: "\n * "}";
            exit 1;
        }
    }();
}

sub parse-config(
    IO::Path :$config_file
    --> Map()
) {
    unless $config_file.IO.f {
        note "Config file [{$config_file}] not found. Please run uzu init to generate.";
        exit 1;
    }

    try {

        CATCH {
            default {
                note "Invalid config yaml file [{$config_file}]";
                note .Str;
                exit 1;
            }
        }

        my %config      = load-yaml slurp($config_file) when $config_file.IO.f;
        # Normalize themes
        %config<themes> = %config<themes>.List.hyper.map(-> $theme {
            $theme ~~ Iterable ?? $theme.head !! $theme;
        }).List;

        # Collect non-core variables into :site
        %config<site> = flat(grep({ 
            # Exclude core vars
            # from site hash
            !['exclude',
              'exclude_pages',
              'extended',
              'host',
              'language',
              'port',
              'project_root',
              'template_engine',
              'theme',
              'themes',
              'pre_command',
              'post_command'].contains: .key
        }, %config)).Hash;

        return %config;
    }
}

sub build-dir-exists(@seen, $dir) {
    if so @seen (cont) $dir {
        note "Cannot render multiple themes to the same build directory [{$dir}]" when so @seen (cont) $dir;
        exit 1;
    }
}

sub safe-build-dir-check($dir, :$project_root) {
    if $dir.IO ~~ $*HOME.IO|$project_root.IO {
        note "Build directory [{$dir.IO.path}] cannot be {$*HOME} or project root [{$project_root}].";
        exit 1;
    }
}

sub themes-config(
    Str      :$single_theme,   # optional, only config this theme
    IO::Path :$themes_dir,     # project themes dir
    Str      :$theme,          # yaml theme:
             :@themes,         # yaml themes:
    IO::Path :$build_dir,      # default build dir
    Int      :$port,           # default port
    List     :$exclude_pages,  # default exclude pages
    IO::Path :$project_root    # project root
) {
    # Always use single theme if yaml `theme:` variable set
    return %{
        "{$theme||'default'}" => %{
            theme_dir      => $themes_dir.IO.child("$theme"||'default'),
            build_dir      => $build_dir,
            port           => ($port||3000),
            exclude_pages  => $exclude_pages},
    } when defined $theme and $theme !~~ "";

    # Keep track of build dirs to avoid
    # reusing the same build dir for
    # multiple themes.
    my IO::Path @seen_build_dirs;

    # We can to keep incrementing the 
    # port to assign to the next theme
    # if a port is not defined for the
    # theme or the defined port is 
    # already used.
    my Int $working_port = $port;
    my %themes;

    # ... otherwise use hash
    for hyper @themes -> $theme {
        
        given $theme {

            # Single theme, $theme = theme name
            when Str {

                # When user passes --theme to cli only configure that theme or skip
                next when $single_theme.defined && $single_theme !~~ $theme;

                my Int $theme_port = $working_port;
                # Increment next port
                ++$working_port;

                %themes{"{$theme||'default'}"} = %{
                    theme_dir      => $themes_dir.IO.child($theme||'default'),
                    build_dir      => $build_dir.IO.child($theme||'default'),
                    port           => $theme_port,
                    exclude_pages  => $exclude_pages
                }
            } 
            
            when Pair {

                my Str  $theme_name    = $theme.key;
                my Hash $theme_config  = $theme.value;
                my IO::Path $theme_dir = $themes_dir.IO.child($theme_name);

                # When user passes --theme to cli only configure that theme or skip
                next when $single_theme.defined && $single_theme !~~ $theme_name;

                do {
                    note "Theme directory [{$theme_name}] does not exist. Skipping.";
                    next;
                } unless $theme_dir.IO.e;

                # Single theme build dir
                my IO::Path $theme_build_dir = do if @themes.elems eq 1 {
                    $theme_config<build_dir>:exists ?? $theme_config<build_dir>.IO !! $build_dir;
                } else { # Mutiple themes build dir
                    my $theme_build_dir = $theme_config<build_dir> ?? $theme_config<build_dir> !! $build_dir.IO.child($theme_name);
                    build-dir-exists @seen_build_dirs, $theme_build_dir;
                    push @seen_build_dirs, $theme_build_dir.IO;
                    safe-build-dir-check $theme_build_dir.IO, :$project_root;
                    $theme_build_dir.IO;
                }

                # Single theme port
                my Int $theme_port = do if @themes.elems eq 1 {
                    $theme_config<port>:exists ?? $theme_config<port>.Int !! $working_port;
                } else {
                    # Multiple themes port
                    if $theme_config<port>:exists && $theme_config<port> > $working_port {
                        $working_port = $theme_config<port>.Int;
                    }
                    $theme_config<port>:exists ?? $theme_config<port>.Int !! $working_port;
                }

                # Increment next port
                ++$working_port;

                %themes{"{$theme_name}"} = %{
                    theme_dir      => $theme_dir,
                    build_dir      => $theme_build_dir,
                    port           => $theme_port,
                    exclude_pages  => $theme_config<exclude_pages>
                }
            }
        }
    }
    return %themes;
}
{'fraction_non_alphanumeric': 0.1157353672691096, 'fraction_numerical': 0.00165809981760902, 'mean_word_length': 1.485166872682324, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6770174', 'n_tokens_mistral': 3544, 'n_tokens_neox': 3307, 'n_words': 870}