aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk/cli/new/keymap.py
blob: 20a0c0198c741870a3368d3f09e5e78fcff50c4d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""This script automates the copying of the default keymap into your own keymap.
"""
import re
import json
import shutil
from pathlib import Path

from milc import cli
from milc.questions import question, choice

from qmk.constants import HAS_QMK_USERSPACE, QMK_USERSPACE
from qmk.path import is_keyboard, keymaps, keymap
from qmk.git import git_get_username
from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.keyboard import keyboard_completer, keyboard_folder
from qmk.userspace import UserspaceDefs
from qmk.json_schema import json_load
from qmk.json_encoders import KeymapJSONEncoder
from qmk.info import info_json


def _list_available_converters(kb_name):
    """Search for converters that can be applied to a given keyboard
    """
    if not is_keyboard(kb_name):
        return None

    info = info_json(kb_name)
    pin_compatible = info.get('pin_compatible')
    if not pin_compatible:
        return None

    return sorted(folder.name.split('_to_')[-1] for folder in Path('platforms').glob(f'*/converters/{pin_compatible}_to_*'))


def _set_converter(file, converter):
    """add/overwrite any existing converter specified in keymap.json
    """
    json_data = json_load(file) if file.exists() else {}

    json_data['converter'] = converter

    output = json.dumps(json_data, cls=KeymapJSONEncoder, sort_keys=True)
    file.write_text(output + '\n', encoding='utf-8')


def validate_keymap_name(name):
    """Returns True if the given keymap name contains only a-z, 0-9 and underscore characters.
    """
    regex = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9_]+$')
    return bool(regex.match(name))


def prompt_keyboard():
    prompt = """{fg_yellow}Select Keyboard{style_reset_all}
If you're unsure you can view a full list of supported keyboards with {fg_yellow}qmk list-keyboards{style_reset_all}.

Keyboard Name? """
    return question(prompt)


def prompt_user():
    prompt = """
{fg_yellow}Name Your Keymap{style_reset_all}

Keymap name? """
    return question(prompt, default=git_get_username())


def prompt_converter(kb_name):
    prompt = """
{fg_yellow}Configure Development Board{style_reset_all}
For more information, see:
https://docs.qmk.fm/feature_converters

Use converter? """

    converters = _list_available_converters(kb_name)
    if not converters:
        return None

    choices = ['No (default)', *converters]

    answer = choice(prompt, options=choices, default=0)
    return None if choices.index(answer) == 0 else answer


@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
@cli.argument('--converter', help='Specify the name of a converter to configure')
@cli.argument('--skip-converter', arg_only=True, action='store_true', help='Skip converter')
@cli.subcommand('Creates a new keymap for the keyboard of your choosing')
@automagic_keyboard
@automagic_keymap
def new_keymap(cli):
    """Creates a new keymap for the keyboard of your choosing.
    """
    cli.log.info('{style_bright}Generating a new keymap{style_normal}')
    cli.echo('')

    # ask for user input if keyboard or keymap was not provided in the command line
    kb_name = cli.config.new_keymap.keyboard if cli.config.new_keymap.keyboard else prompt_keyboard()
    user_name = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else prompt_user()
    converter = cli.config.new_keymap.converter if cli.args.skip_converter or cli.config.new_keymap.converter else prompt_converter(kb_name)

    # check directories
    try:
        kb_name = keyboard_folder(kb_name)
    except ValueError:
        cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} does not exist! Please choose a valid name.')
        return False

    # generate keymap paths
    keymaps_dirs = keymaps(kb_name)
    keymap_path_default = keymap(kb_name, 'default')
    keymap_path_new = keymaps_dirs[0] / user_name

    if not keymap_path_default.exists():
        cli.log.error(f'Default keymap {{fg_cyan}}{keymap_path_default}{{fg_reset}} does not exist!')
        return False

    if not validate_keymap_name(user_name):
        cli.log.error('Keymap names must contain only {fg_cyan}a-z{fg_reset}, {fg_cyan}0-9{fg_reset} and {fg_cyan}_{fg_reset}! Please choose a different name.')
        return False

    if keymap_path_new.exists():
        cli.log.error(f'Keymap {{fg_cyan}}{user_name}{{fg_reset}} already exists! Please choose a different name.')
        return False

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

    if converter:
        _set_converter(keymap_path_new / 'keymap.json', converter)

    # end message to user
    cli.log.info(f'{{fg_green}}Created a new keymap called {{fg_cyan}}{user_name}{{fg_green}} in: {{fg_cyan}}{keymap_path_new}.{{fg_reset}}')
    cli.log.info(f"Compile a firmware with your new keymap by typing: {{fg_yellow}}qmk compile -kb {kb_name} -km {user_name}{{fg_reset}}.")

    # Add to userspace compile if we have userspace available
    if HAS_QMK_USERSPACE:
        userspace = UserspaceDefs(QMK_USERSPACE / 'qmk.json')
        userspace.add_target(keyboard=kb_name, keymap=user_name, do_print=False)
        return userspace.save()