<commit_before>#!/usr/bin/env python3

import re
import os
import argparse
import shutil
import json
from subprocess import call

# TODO cli args
# scanning:
#   search_root
#   paths to ignore

# Template paths
human_template = os.path.expanduser("~/Templates/Human Character Sheet.nwod")
changeling_template = os.path.expanduser("~/Templates/Changeling Character Sheet.nwod")
session_template = os.path.expanduser("~/Templates/Session Log.md")

# Helper files
changeling_bonuses = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'support/seeming-kith.json')

# Regexes for parsing important elements
name_re = re.compile('([\w\s]+)(?: - )?.*')
section_re = re.compile('^--.+--\s*$')
tag_re = re.compile('^@(?P<tag>\w+)\s+(?P<value>.*)$')
plot_regex = '^plot (\d+)$'
session_regex = '^session (\d+)$'
comment_re = re.compile(
    '(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
    re.DOTALL | re.MULTILINE
)
seeming_re = re.compile(
    '^(\s+)seeming(\s+)\w+$',
    re.MULTILINE | re.IGNORECASE
)
kith_re = re.compile(
    '^(\s+)kith(\s+)\w+$',
    re.MULTILINE | re.IGNORECASE
)

# Group-like tags. These all accept an accompanying `rank` tag.
group_tags = ['group', 'court', 'motley']

# Recognized extensions
valid_exts = ('.nwod')

class Result:
    """Holds data about the result of a subcommand"""
    def __init__(self, success, openable = None, errcode = 0, errmsg = ''):
        super(Result, self).__init__()
        self.success = success
        self.openable = openable
        self.errcode = errcode
        # Error codes:
        # 0. Everything's fine
        # 1. Tried to create a file that already exists
        # 2. Latest plot and session files have different numbers
        # 3. Feature is not yet implemented
        # 4. Filesystem error
        self.errmsg = errmsg

def _load_json(filename):
    """ Parse a JSON file
        First remove all comments, then use the standard json package

        Comments look like :
            // ...
        or
            /*
            ...
            */
    """
    with open(filename) as f:
        content = ''.join(f.readlines())

        ## Looking for comments
        match = comment_re.search(content)
        while match:
            # single line comment
            content = content[:match.start()] + content[match.end():]
            match = comment_re.search(content)

        # Return parsed json
        return json.loads(content)

class Settings:
    """Stores settings for later use"""
    path_default = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'support/settings-default.json')

    def __init__(self, path = path_default):
        self.settings_files = [_load_json(path)]

    def get(self, key):
        """Get the value of a settings key"""
        key_parts = key.split('.')
        arr = self.settings_files[0]
        for k in key_parts:
            try:
                arr = arr[k]
            except KeyError:
                return None
        return arr

def main():
    parser = argparse.ArgumentParser(description = 'GM helper script to manage game files')
    parser.add_argument('-b', '--batch', action='store_true', default=False, help="Do not open any newly created files")
    subparsers = parser.add_subparsers(title='Subcommands', description="Commands that can be run on the current campaign", metavar="changeling, human, session, update, webpage, lint")

    parser_changeling = subparsers.add_parser('changeling', help="Create a new changeling character")
    parser_changeling.set_defaults(func=create_changeling)
    parser_changeling.add_argument('name', help="character's name", metavar='name')
    parser_changeling.add_argument('seeming', help="character's Seeming", metavar='seeming')
    parser_changeling.add_argument('kith', help="character's Kith", metavar='kith')
    parser_changeling.add_argument('-c', '--court', help="the character's Court", metavar='court')
    parser_changeling.add_argument('-m', '--motley', help="the character's Motley", metavar='motley')
    parser_changeling.add_argument('-g', '--group', default=[], nargs="*", help='name of a group that counts the character as a member', metavar='group')

    parser_human = subparsers.add_parser('human', help="Create a new human character")
    parser_human.set_defaults(func=create_human)
    parser_human.add_argument('name', help="character's name", metavar='name')
    parser_human.add_argument('-g', '--group', default=[], nargs="*", help='name of a group that counts the character as a member', metavar='group')

    parser_session = subparsers.add_parser('session', help="Create files for a new game session")
    parser_session.set_defaults(func=create_session)

    parser_update = subparsers.add_parser('update', aliases=['u'], help="Update various support files (motleys, etc.) using the content of the character files")
    parser_update.set_defaults(funct=update_dependencies)

    parser_webpage = subparsers.add_parser('webpage', aliases=['web', 'w'], help="Generate the NPC Listing webpage")
    parser_webpage.set_defaults(funct=make_webpage)

    parser_lint = subparsers.add_parser('lint', help="Check the character files for minimum completeness.")
    parser_lint.set_defaults(func=lint)

    args = parser.parse_args()

    prefs = Settings()

    result = args.func(args, prefs)

    if not result.success:
        print(result.errmsg)
        return result.errcode

    if result.openable and not args.batch:
        call([prefs.get("editor")] + result.openable)

def create_changeling(args, prefs):
    target_path = _add_path_if_exists(prefs.get('paths.characters'), 'Changelings')
    if args.court is not None:
        target_path = _add_path_if_exists(target_path, args.court.title())
    else:
        target_path = _add_path_if_exists(target_path, 'Courtless')

    for group_raw in args.group:
        group_name = group_raw.title()
        target_path = _add_path_if_exists(target_path, group_name)

    filename = args.name + '.nwod'
    target_path = os.path.join(target_path, filename)
    if os.path.exists(target_path):
        return Result(False, errmsg="Character '%s' already exists!" % args.name, errcode = 1)

    seeming_name = args.seeming.title()<commit_msg>Store install path in prefs<commit_after>#!/usr/bin/env python3

import re
import os
import argparse
import shutil
import json
from subprocess import call

# TODO cli args
# scanning:
#   search_root
#   paths to ignore

# Template paths
human_template = os.path.expanduser("~/Templates/Human Character Sheet.nwod")
changeling_template = os.path.expanduser("~/Templates/Changeling Character Sheet.nwod")
session_template = os.path.expanduser("~/Templates/Session Log.md")

# Regexes for parsing important elements
name_re = re.compile('([\w\s]+)(?: - )?.*')
section_re = re.compile('^--.+--\s*$')
tag_re = re.compile('^@(?P<tag>\w+)\s+(?P<value>.*)$')
plot_regex = '^plot (\d+)$'
session_regex = '^session (\d+)$'
comment_re = re.compile(
    '(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?',
    re.DOTALL | re.MULTILINE
)
seeming_re = re.compile(
    '^(\s+)seeming(\s+)\w+$',
    re.MULTILINE | re.IGNORECASE
)
kith_re = re.compile(
    '^(\s+)kith(\s+)\w+$',
    re.MULTILINE | re.IGNORECASE
)

# Group-like tags. These all accept an accompanying `rank` tag.
group_tags = ['group', 'court', 'motley']

# Recognized extensions
valid_exts = ('.nwod')

class Result:
    """Holds data about the result of a subcommand"""
    def __init__(self, success, openable = None, errcode = 0, errmsg = ''):
        super(Result, self).__init__()
        self.success = success
        self.openable = openable
        self.errcode = errcode
        # Error codes:
        # 0. Everything's fine
        # 1. Tried to create a file that already exists
        # 2. Latest plot and session files have different numbers
        # 3. Feature is not yet implemented
        # 4. Filesystem error
        self.errmsg = errmsg

def _load_json(filename):
    """ Parse a JSON file
        First remove all comments, then use the standard json package

        Comments look like :
            // ...
        or
            /*
            ...
            */
    """
    with open(filename) as f:
        content = ''.join(f.readlines())

        ## Looking for comments
        match = comment_re.search(content)
        while match:
            # single line comment
            content = content[:match.start()] + content[match.end():]
            match = comment_re.search(content)

        # Return parsed json
        return json.loads(content)

class Settings:
    """Stores settings for later use"""
    install_base = os.path.dirname(os.path.realpath(__file__))
    path_default = os.path.join(install_base, 'support/settings-default.json')

    def __init__(self, path = path_default):
        self.settings_files = [_load_json(path)]

    def get(self, key):
        """Get the value of a settings key"""
        key_parts = key.split('.')
        arr = self.settings_files[0]
        for k in key_parts:
            try:
                arr = arr[k]
            except KeyError:
                return None
        return arr

def main():
    parser = argparse.ArgumentParser(description = 'GM helper script to manage game files')
    parser.add_argument('-b', '--batch', action='store_true', default=False, help="Do not open any newly created files")
    subparsers = parser.add_subparsers(title='Subcommands', description="Commands that can be run on the current campaign", metavar="changeling, human, session, update, webpage, lint")

    parser_changeling = subparsers.add_parser('changeling', help="Create a new changeling character")
    parser_changeling.set_defaults(func=create_changeling)
    parser_changeling.add_argument('name', help="character's name", metavar='name')
    parser_changeling.add_argument('seeming', help="character's Seeming", metavar='seeming')
    parser_changeling.add_argument('kith', help="character's Kith", metavar='kith')
    parser_changeling.add_argument('-c', '--court', help="the character's Court", metavar='court')
    parser_changeling.add_argument('-m', '--motley', help="the character's Motley", metavar='motley')
    parser_changeling.add_argument('-g', '--group', default=[], nargs="*", help='name of a group that counts the character as a member', metavar='group')

    parser_human = subparsers.add_parser('human', help="Create a new human character")
    parser_human.set_defaults(func=create_human)
    parser_human.add_argument('name', help="character's name", metavar='name')
    parser_human.add_argument('-g', '--group', default=[], nargs="*", help='name of a group that counts the character as a member', metavar='group')

    parser_session = subparsers.add_parser('session', help="Create files for a new game session")
    parser_session.set_defaults(func=create_session)

    parser_update = subparsers.add_parser('update', aliases=['u'], help="Update various support files (motleys, etc.) using the content of the character files")
    parser_update.set_defaults(funct=update_dependencies)

    parser_webpage = subparsers.add_parser('webpage', aliases=['web', 'w'], help="Generate the NPC Listing webpage")
    parser_webpage.set_defaults(funct=make_webpage)

    parser_lint = subparsers.add_parser('lint', help="Check the character files for minimum completeness.")
    parser_lint.set_defaults(func=lint)

    args = parser.parse_args()

    prefs = Settings()

    result = args.func(args, prefs)

    if not result.success:
        print(result.errmsg)
        return result.errcode

    if result.openable and not args.batch:
        call([prefs.get("editor")] + result.openable)

def create_changeling(args, prefs):
    changeling_bonuses = os.path.join(prefs.install_base, 'support/seeming-kith.json')

    target_path = _add_path_if_exists(prefs.get('paths.characters'), 'Changelings')
    if args.court is not None:
        target_path = _add_path_if_exists(target_path, args.court.title())
    else:
        target_path = _add_path_if_exists(target_path, 'Courtless')

    for group_raw in args.group:
        group_name = group_raw.title()
        target_path = _add_path_if_exists(target_path, group_name)

    filename = args.name + '.nwod'
    target_path = os.path.join(target_path, filename)
    if os.path.exists(target_path):
        return Result(False, errmsg="Character '%s' already exists!" % args.name, errcode = 1)

    seeming_name = args.seeming.title()
{'fraction_non_alphanumeric': 0.13759627727856225, 'fraction_numerical': 0.0014441591784338896, 'mean_word_length': 3.4709469153515062, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, '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': '266821', 'n_tokens_mistral': 4008, 'n_tokens_neox': 3877, 'n_words': 939}