~ruther/qmk_firmware

c66930445f7d5941eb847568288046d51f853786 — skullydazed 5 years ago 58724f8
Use pathlib everywhere we can (#7872)

* Use pathlib everywhere we can

* Update lib/python/qmk/path.py

Co-Authored-By: Erovia <Erovia@users.noreply.github.com>

* Update lib/python/qmk/path.py

Co-Authored-By: Erovia <Erovia@users.noreply.github.com>

* Improvements based on @erovia's feedback

* rework qmk compile and qmk flash to use pathlib

* style

* Remove the subcommand_name argument from find_keyboard_keymap()

Co-authored-by: Erovia <Erovia@users.noreply.github.com>
M lib/python/milc.py => lib/python/milc.py +5 -1
@@ 273,7 273,7 @@ class MILC(object):
        self._inside_context_manager = False
        self.ansi = ansi_colors
        self.arg_only = []
        self.config = None
        self.config = self.config_source = None
        self.config_file = None
        self.default_arguments = {}
        self.version = 'unknown'


@@ 473,6 473,7 @@ class MILC(object):
        """
        self.acquire_lock()
        self.config = Configuration()
        self.config_source = Configuration()
        self.config_file = self.find_config_file()

        if self.config_file and self.config_file.exists():


@@ 498,6 499,7 @@ class MILC(object):
                            value = int(value)

                    self.config[section][option] = value
                    self.config_source[section][option] = 'config_file'

        self.release_lock()



@@ 530,12 532,14 @@ class MILC(object):
                    arg_value = getattr(self.args, argument)
                    if arg_value is not None:
                        self.config[section][argument] = arg_value
                        self.config_source[section][argument] = 'argument'
                else:
                    if argument not in self.config[entrypoint_name]:
                        # Check if the argument exist for this section
                        arg = getattr(self.args, argument)
                        if arg is not None:
                            self.config[section][argument] = arg
                            self.config_source[section][argument] = 'argument'

        self.release_lock()


M lib/python/qmk/cli/compile.py => lib/python/qmk/cli/compile.py +12 -87
@@ 3,16 3,12 @@
You can compile a keymap already in the repo or using a QMK Configurator export.
"""
import subprocess
import os
from argparse import FileType

from milc import cli
from qmk.commands import create_make_command
from qmk.commands import parse_configurator_json
from qmk.commands import compile_configurator_json

import qmk.keymap
import qmk.path
from qmk.commands import compile_configurator_json, create_make_command, find_keyboard_keymap, parse_configurator_json


@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), help='The configurator export to compile')


@@ 24,99 20,28 @@ def compile(cli):

    If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists.

    FIXME(skullydazed): add code to check and warn if the keymap already exists

    If --keyboard and --keymap are provided this command will build a firmware based on that.

    If a keyboard and keymap are provided this command will build a firmware based on that.
    """
    # Set CWD as directory command was issued from
    cwd = os.environ['ORIG_CWD']
    qmk_path = os.getcwd()
    current_folder = os.path.basename(cwd)
    # Initialize boolean to check for being in a keyboard directory and initialize keyboard string
    in_keyboard = False
    in_layout = False
    keyboard = ""
    keymap = ""
    user_keymap = ""
    user_keyboard = ""

    # Set path for '/keyboards/' directory
    keyboards_path = os.path.join(qmk_path, "keyboards")
    layouts_path = os.path.join(qmk_path, "layouts")

    # If below 'keyboards' and not in 'keyboards' or 'keymaps', get current keyboard name
    if cwd.startswith(keyboards_path):
        if current_folder != "keyboards" and current_folder != "keymaps":
            if os.path.basename(os.path.abspath(os.path.join(cwd, ".."))) == "keymaps":
                # If in a keymap folder, set relative path, get everything before /keymaps, and the keymap name
                relative_path = cwd[len(keyboards_path):][1:]
                keyboard = str(relative_path).split("/keymaps", 1)[0]
                keymap = str(relative_path.rsplit("/", 1)[-1])
            else:
                keyboard = str(cwd[len(keyboards_path):])[1:]

            in_keyboard = True

    # If in layouts dir
    if cwd.startswith(layouts_path):
        if current_folder != "layouts":
            in_layout = True

    # If user keyboard/keymap or compile keyboard/keymap are supplied, assign those
    if cli.config.compile.keyboard:
        user_keyboard = cli.config.compile.keyboard
    if cli.config.compile.keymap and not in_layout:
        user_keymap = cli.config.compile.keymap

    if cli.args.filename:
        # Parse the configurator json
        # If a configurator JSON was provided skip straight to compiling it
        # FIXME(skullydazed): add code to check and warn if the keymap already exists when compiling a json keymap.
        user_keymap = parse_configurator_json(cli.args.filename)

        # Generate the keymap
        keymap_path = qmk.path.keymap(user_keymap['keyboard'])
        cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path)

        # Compile the keymap
        command = compile_configurator_json(user_keymap)

        cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])

    elif user_keyboard and user_keymap:
        # Generate the make command for a specific keyboard/keymap.
        command = create_make_command(user_keyboard, user_keymap)

    elif in_keyboard:
        keyboard = user_keyboard if user_keyboard else keyboard
        keymap = user_keymap if user_keymap else keymap

        if not os.path.exists(os.path.join(keyboards_path, keyboard, "rules.mk")):
            cli.log.error('This directory does not contain a rules.mk file. Change directory or supply --keyboard with optional --keymap')
            return False

        # Get path for keyboard directory
        keymap_path = qmk.path.keymap(keyboard)

        # Check for global keymap config first
        if keymap:
            command = create_make_command(keyboard, keymap)

        else:
            # If no default keymap exists and none provided
            cli.log.error('This directory does not contain a keymap. Set one with `qmk config` or supply `--keymap` ')
            return False
    else:
        # Perform the action the user specified
        user_keyboard, user_keymap = find_keyboard_keymap()
        if user_keyboard and user_keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(user_keyboard, user_keymap)

    elif in_layout:
        if user_keyboard:
            keymap = current_folder
            command = create_make_command(user_keyboard, keymap)
        else:
            cli.log.error('You must supply a keyboard to compile a layout keymap. Set one with `qmk config` or supply `--keyboard` ')
            cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
            cli.echo('usage: qmk compile [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [filename]')
            return False

    else:
        cli.log.error('You must supply a configurator export, both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.')
        return False

    cli.log.info('Compiling keymap with {fg_cyan}%s\n\n', ' '.join(command))
    subprocess.run(command)

M lib/python/qmk/cli/flash.py => lib/python/qmk/cli/flash.py +13 -21
@@ 8,7 8,7 @@ from argparse import FileType

import qmk.path
from milc import cli
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
from qmk.commands import compile_configurator_json, create_make_command, find_keyboard_keymap, parse_configurator_json


def print_bootloader_help():


@@ 45,39 45,31 @@ def flash(cli):
    If bootloader is omitted, the one according to the rules.mk will be used.

    """
    command = []
    if cli.args.bootloaders:
        # Provide usage and list bootloaders
        cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
        print_bootloader_help()
        return False

    elif cli.config.flash.keymap and not cli.config.flash.keyboard:
        # If only a keymap was given but no keyboard, suggest listing keyboards
        cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
        cli.log.error('run \'qmk list_keyboards\' to find out the supported keyboards')
        return False

    elif cli.args.filename:
        # Get keymap path to log info
    if cli.args.filename:
        # Handle compiling a configurator JSON
        user_keymap = parse_configurator_json(cli.args.filename)
        keymap_path = qmk.path.keymap(user_keymap['keyboard'])

        cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path)

        # Convert the JSON into a C file and write it to disk.
        command = compile_configurator_json(user_keymap, cli.args.bootloader)

        cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap'])

    elif cli.config.flash.keyboard and cli.config.flash.keymap:
        # Generate the make command for a specific keyboard/keymap.
        command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader)

    else:
        cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
        cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`. You can also specify a bootloader with --bootloader. Use --bootloaders to list the available bootloaders.')
        return False
        # Perform the action the user specified
        user_keyboard, user_keymap = find_keyboard_keymap()
        if user_keyboard and user_keymap:
            # Generate the make command for a specific keyboard/keymap.
            command = create_make_command(user_keyboard, user_keymap, cli.args.bootloader)

        else:
            cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`.')
            cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]')
            return False

    cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command))
    subprocess.run(command)

M lib/python/qmk/cli/json/keymap.py => lib/python/qmk/cli/json/keymap.py +16 -15
@@ 1,14 1,15 @@
"""Generate a keymap.c from a configurator export.
"""
import json
import os
from pathlib import Path

from milc import cli

import qmk.keymap
import qmk.path


@cli.argument('-o', '--output', arg_only=True, help='File to write to')
@cli.argument('-o', '--output', arg_only=True, type=Path, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
@cli.argument('filename', arg_only=True, help='Configurator JSON file')
@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')


@@ 17,13 18,17 @@ def json_keymap(cli):

    This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.
    """
    cli.args.filename = qmk.path.normpath(cli.args.filename)

    # Error checking
    if cli.args.filename == ('-'):
        cli.log.error('Reading from STDIN is not (yet) supported.')
    if not cli.args.filename.exists():
        cli.log.error('JSON file does not exist!')
        cli.print_usage()
        exit(1)
    if not os.path.exists(qmk.path.normpath(cli.args.filename)):
        cli.log.error('JSON file does not exist!')

    if str(cli.args.filename) == '-':
        # TODO(skullydazed/anyone): Read file contents from STDIN
        cli.log.error('Reading from STDIN is not (yet) supported.')
        cli.print_usage()
        exit(1)



@@ 32,21 37,17 @@ def json_keymap(cli):
        cli.args.output = None

    # Parse the configurator json
    with open(qmk.path.normpath(cli.args.filename), 'r') as fd:
    with cli.args.filename.open('r') as fd:
        user_keymap = json.load(fd)

    # Generate the keymap
    keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers'])

    if cli.args.output:
        output_dir = os.path.dirname(cli.args.output)

        if not os.path.exists(output_dir):
            os.makedirs(output_dir)

        output_file = qmk.path.normpath(cli.args.output)
        with open(output_file, 'w') as keymap_fd:
            keymap_fd.write(keymap_c)
        cli.args.output.parent.mkdir(parents=True, exist_ok=True)
        if cli.args.output.exists():
            cli.args.output.replace(cli.args.output.name + '.bak')
        cli.args.output.write_text(keymap_c)

        if not cli.args.quiet:
            cli.log.info('Wrote keymap to %s.', cli.args.output)

M lib/python/qmk/cli/list/keyboards.py => lib/python/qmk/cli/list/keyboards.py +1 -0
@@ 1,5 1,6 @@
"""List the keyboards currently defined within QMK
"""
# We avoid pathlib here because this is performance critical code.
import os
import glob


M lib/python/qmk/cli/new/keymap.py => lib/python/qmk/cli/new/keymap.py +15 -11
@@ 1,8 1,9 @@
"""This script automates the copying of the default keymap into your own keymap.
"""
import os
import shutil
from pathlib import Path

import qmk.path
from milc import cli




@@ 17,24 18,27 @@ def new_keymap(cli):
    keymap = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else input("Keymap Name: ")

    # generate keymap paths
    kb_path = os.path.join(os.getcwd(), "keyboards", keyboard)
    keymap_path_default = os.path.join(kb_path, "keymaps/default")
    keymap_path = os.path.join(kb_path, "keymaps/%s" % keymap)
    kb_path = Path('keyboards') / keyboard
    keymap_path = qmk.path.keymap(keyboard)
    keymap_path_default = keymap_path / 'default'
    keymap_path_new = keymap_path / keymap

    # check directories
    if not os.path.exists(kb_path):
    if not kb_path.exists():
        cli.log.error('Keyboard %s does not exist!', kb_path)
        exit(1)
    if not os.path.exists(keymap_path_default):

    if not keymap_path_default.exists():
        cli.log.error('Keyboard default %s does not exist!', keymap_path_default)
        exit(1)
    if os.path.exists(keymap_path):
        cli.log.error('Keymap %s already exists!', keymap_path)

    if keymap_path_new.exists():
        cli.log.error('Keymap %s already exists!', keymap_path_new)
        exit(1)

    # create user directory with default keymap files
    shutil.copytree(keymap_path_default, keymap_path, symlinks=True)
    shutil.copytree(str(keymap_path_default), str(keymap_path_new), symlinks=True)

    # end message to user
    cli.log.info("%s keymap directory created in: %s", keymap, keymap_path)
    cli.log.info("Compile a firmware with your new keymap by typing: \n" + "qmk compile -kb %s -km %s", keyboard, keymap)
    cli.log.info("%s keymap directory created in: %s", keymap, keymap_path_new)
    cli.log.info("Compile a firmware with your new keymap by typing: \n\n\tqmk compile -kb %s -km %s\n", keyboard, keymap)

M lib/python/qmk/commands.py => lib/python/qmk/commands.py +87 -9
@@ 1,13 1,19 @@
"""Functions that build make commands
"""Helper functions for commands.
"""
import json
from pathlib import Path

from milc import cli

import qmk.keymap
from qmk.path import is_keyboard, is_keymap_dir, under_qmk_firmware


def create_make_command(keyboard, keymap, target=None):
    """Create a make compile command

    Args:

        keyboard
            The path of the keyboard, for example 'plank'



@@ 18,24 24,22 @@ def create_make_command(keyboard, keymap, target=None):
            Usually a bootloader.

    Returns:

        A command that can be run to make the specified keyboard and keymap
    """
    if target is None:
        return ['make', ':'.join((keyboard, keymap))]
    return ['make', ':'.join((keyboard, keymap, target))]
    make_args = [keyboard, keymap]

    if target:
        make_args.append(target)

def parse_configurator_json(configurator_file):
    """Open and parse a configurator json export
    """
    user_keymap = json.load(configurator_file)
    return user_keymap
    return ['make', ':'.join(make_args)]


def compile_configurator_json(user_keymap, bootloader=None):
    """Convert a configurator export JSON file into a C file

    Args:

        configurator_filename
            The configurator JSON export file



@@ 43,6 47,7 @@ def compile_configurator_json(user_keymap, bootloader=None):
            A bootloader to flash

    Returns:

        A command to run to compile and flash the C file.
    """
    # Write the keymap C file


@@ 52,3 57,76 @@ def compile_configurator_json(user_keymap, bootloader=None):
    if bootloader is None:
        return create_make_command(user_keymap['keyboard'], user_keymap['keymap'])
    return create_make_command(user_keymap['keyboard'], user_keymap['keymap'], bootloader)


def find_keyboard_keymap():
    """Returns `(keyboard_name, keymap_name)` based on the user's current environment.

    This determines the keyboard and keymap name using the following precedence order:

        * Command line flags (--keyboard and --keymap)
        * Current working directory
            * `keyboards/<keyboard_name>`
            * `keyboards/<keyboard_name>/keymaps/<keymap_name>`
            * `layouts/**/<keymap_name>`
            * `users/<keymap_name>`
        * Configuration
            * cli.config.<subcommand>.keyboard
            * cli.config.<subcommand>.keymap
    """
    # Check to make sure their copy of MILC supports config_source
    if not hasattr(cli, 'config_source'):
        cli.log.error("Your QMK CLI is out of date. Please upgrade using pip3 or your package manager.")
        exit(1)

    # State variables
    relative_cwd = under_qmk_firmware()
    keyboard_name = ""
    keymap_name = ""

    # If the keyboard or keymap are passed as arguments use that in preference to anything else
    if cli.config_source[cli._entrypoint.__name__]['keyboard'] == 'argument':
        keyboard_name = cli.config[cli._entrypoint.__name__]['keyboard']
    if cli.config_source[cli._entrypoint.__name__]['keymap'] == 'argument':
        keymap_name = cli.config[cli._entrypoint.__name__]['keymap']

    if not keyboard_name or not keymap_name:
        # If we don't have a keyboard_name and keymap_name from arguments try to derive one or both
        if relative_cwd and relative_cwd.parts and relative_cwd.parts[0] == 'keyboards':
            # Try to determine the keyboard and/or keymap name
            current_path = Path('/'.join(relative_cwd.parts[1:]))

            if current_path.parts[-2] == 'keymaps':
                if not keymap_name:
                    keymap_name = current_path.parts[-1]
                if not keyboard_name:
                    keyboard_name = '/'.join(current_path.parts[:-2])
            elif not keyboard_name and is_keyboard(current_path):
                keyboard_name = str(current_path)

        elif relative_cwd and relative_cwd.parts and relative_cwd.parts[0] == 'layouts':
            # Try to determine the keymap name from the community layout
            if is_keymap_dir(relative_cwd) and not keymap_name:
                keymap_name = relative_cwd.name

        elif relative_cwd and relative_cwd.parts and relative_cwd.parts[0] == 'users':
            # Try to determine the keymap name based on which userspace they're in
            if not keymap_name and len(relative_cwd.parts) > 1:
                keymap_name = relative_cwd.parts[1]

    # If we still don't have a keyboard and keymap check the config
    if not keyboard_name and cli.config[cli._entrypoint.__name__]['keyboard']:
        keyboard_name = cli.config[cli._entrypoint.__name__]['keyboard']

    if not keymap_name and cli.config[cli._entrypoint.__name__]['keymap']:
        keymap_name = cli.config[cli._entrypoint.__name__]['keymap']

    return (keyboard_name, keymap_name)


def parse_configurator_json(configurator_file):
    """Open and parse a configurator json export
    """
    user_keymap = json.load(configurator_file)

    return user_keymap

A lib/python/qmk/constants.py => lib/python/qmk/constants.py +9 -0
@@ 0,0 1,9 @@
"""Information that should be available to the python library.
"""
from pathlib import Path

# The root of the qmk_firmware tree.
QMK_FIRMWARE = Path.cwd()

# This is the number of directories under `qmk_firmware/keyboards` that will be traversed. This is currently a limitation of our make system.
MAX_KEYBOARD_SUBFOLDERS = 5

M lib/python/qmk/keymap.py => lib/python/qmk/keymap.py +6 -12
@@ 31,11 31,10 @@ def template(keyboard):
        keyboard
            The keyboard to return a template for.
    """
    template_name = 'keyboards/%s/templates/keymap.c' % keyboard
    template_file = Path('keyboards/%s/templates/keymap.c' % keyboard)

    if os.path.exists(template_name):
        with open(template_name, 'r') as fd:
            return fd.read()
    if template_file.exists():
        return template_file.read_text()

    return DEFAULT_KEYMAP_C



@@ 85,15 84,10 @@ def write(keyboard, keymap, layout, layers):
            An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
    """
    keymap_c = generate(keyboard, layout, layers)
    keymap_path = qmk.path.keymap(keyboard)
    keymap_dir = os.path.join(keymap_path, keymap)
    keymap_file = os.path.join(keymap_dir, 'keymap.c')
    keymap_file = qmk.path.keymap(keyboard) / keymap / 'keymap.c'

    if not os.path.exists(keymap_dir):
        os.makedirs(keymap_dir)

    with open(keymap_file, 'w') as keymap_fd:
        keymap_fd.write(keymap_c)
    keymap_file.parent.mkdir(parents=True, exist_ok=True)
    keymap_file.write_text(keymap_c)

    return keymap_file


M lib/python/qmk/path.py => lib/python/qmk/path.py +46 -11
@@ 2,34 2,69 @@
"""
import logging
import os
from pathlib import Path

from qmk.constants import QMK_FIRMWARE, MAX_KEYBOARD_SUBFOLDERS
from qmk.errors import NoSuchKeyboardError


def is_keymap_dir(keymap_path):
    """Returns True if `keymap_path` is a valid keymap directory.
    """
    keymap_path = Path(keymap_path)
    keymap_c = keymap_path / 'keymap.c'
    keymap_json = keymap_path / 'keymap.json'

    return any((keymap_c.exists(), keymap_json.exists()))


def is_keyboard(keyboard_name):
    """Returns True if `keyboard_name` is a keyboard we can compile.
    """
    keyboard_path = QMK_FIRMWARE / 'keyboards' / keyboard_name
    rules_mk = keyboard_path / 'rules.mk'
    return rules_mk.exists()


def under_qmk_firmware():
    """Returns a Path object representing the relative path under qmk_firmware, or None.
    """
    cwd = Path(os.environ['ORIG_CWD'])

    try:
        return cwd.relative_to(QMK_FIRMWARE)
    except ValueError:
        return None


def keymap(keyboard):
    """Locate the correct directory for storing a keymap.

    Args:

        keyboard
            The name of the keyboard. Example: clueboard/66/rev3
    """
    for directory in ['.', '..', '../..', '../../..', '../../../..', '../../../../..']:
        basepath = os.path.normpath(os.path.join('keyboards', keyboard, directory, 'keymaps'))
    keyboard_folder = Path('keyboards') / keyboard

    for i in range(MAX_KEYBOARD_SUBFOLDERS):
        if (keyboard_folder / 'keymaps').exists():
            return (keyboard_folder / 'keymaps').resolve()

        if os.path.exists(basepath):
            return basepath
        keyboard_folder = keyboard_folder.parent

    logging.error('Could not find keymaps directory!')
    logging.error('Could not find the keymaps directory!')
    raise NoSuchKeyboardError('Could not find keymaps directory for: %s' % keyboard)


def normpath(path):
    """Returns the fully resolved absolute path to a file.
    """Returns a `pathlib.Path()` object for a given path.

    This function will return the absolute path to a file as seen from the
    directory the script was called from.
    This will use the path to a file as seen from the directory the script was called from. You should use this to normalize filenames supplied from the command line.
    """
    if path and path[0] == '/':
        return os.path.normpath(path)
    path = Path(path)

    if path.is_absolute():
        return Path(path)

    return os.path.normpath(os.path.join(os.environ['ORIG_CWD'], path))
    return Path(os.environ['ORIG_CWD']) / path

M lib/python/qmk/tests/test_qmk_path.py => lib/python/qmk/tests/test_qmk_path.py +3 -2
@@ 1,13 1,14 @@
import os
from pathlib import Path

import qmk.path


def test_keymap_onekey_pytest():
    path = qmk.path.keymap('handwired/onekey/pytest')
    assert path == 'keyboards/handwired/onekey/keymaps'
    assert path.samefile('keyboards/handwired/onekey/keymaps')


def test_normpath():
    path = qmk.path.normpath('lib/python')
    assert path == os.path.join(os.environ['ORIG_CWD'], 'lib/python')
    assert path.samefile(Path(os.environ['ORIG_CWD']) / 'lib/python')