concertina_helper.layouts.layout_loader

  1from pathlib import Path
  2from collections.abc import Iterable
  3
  4import re
  5
  6from yaml import safe_load
  7
  8from ..type_defs import Pitch, PitchMatrix
  9from .bisonoric import BisonoricLayout
 10from .unisonoric import UnisonoricLayout
 11
 12
 13def _names_to_pitches(matrix: Iterable[Iterable[str]]) -> PitchMatrix:
 14    '''
 15    >>> pitch_matrix = _names_to_pitches([['C4']])
 16    >>> print(pitch_matrix[0][0])
 17    C4
 18    '''
 19    return PitchMatrix(
 20        tuple(
 21            tuple(
 22                Pitch(name) for name in row
 23            ) for row in matrix
 24        )
 25    )
 26
 27
 28def _parse_matrix(rows: Iterable[str]) -> PitchMatrix:
 29    '''
 30    >>> pitch_matrix = _parse_matrix(['C4 E4 G4'])
 31    >>> print(pitch_matrix[0][0])
 32    C4
 33    '''
 34    return _names_to_pitches([re.split(r'\s+', row.strip()) for row in rows])
 35
 36
 37def parse_unisonoric_layout(layout_spec: dict) -> UnisonoricLayout:
 38    left_matrix = _parse_matrix(layout_spec['left'])
 39    right_matrix = _parse_matrix(layout_spec['right'])
 40    return UnisonoricLayout(left_matrix, right_matrix)
 41
 42
 43def parse_bisonoric_layout(layout_spec: dict) -> BisonoricLayout:
 44    push_layout = parse_unisonoric_layout(layout_spec['push'])
 45    pull_layout = parse_unisonoric_layout(layout_spec['pull'])
 46    return BisonoricLayout(push_layout=push_layout, pull_layout=pull_layout)
 47
 48
 49def load_bisonoric_layout_by_path(layout_path: Path) -> BisonoricLayout:
 50    '''
 51    Expects the file at `layout_path` to be YAML, with a structure like this:
 52    ```
 53    push:
 54        left:
 55            - C3 G3 C4  E4 G4
 56            - B3 D4 G4  B4 D5
 57        right:
 58            - C5  E5 G5  C6  E6
 59            - G5  B5 D6  G6  B6
 60    pull:
 61        left:
 62            - G3 B3  D4  F4 A4
 63            - A3 F#4 A4  C5 E5
 64        right:
 65            - B4  D5 F5  A5  B5
 66            - F#5 A5 C6  E6  F#6
 67    ```
 68    - `push` and `pull` at the top level.
 69    - `left` and `right` inside.
 70    - Each contains a list of strings, representing rows of buttons.
 71    - The strings are the pitches of that row of keys, space delimitted.
 72    '''
 73    layout_yaml = layout_path.read_text()
 74    layout_spec = safe_load(layout_yaml)
 75    return parse_bisonoric_layout(layout_spec)
 76
 77
 78def load_bisonoric_layout_by_name(layout_name: str) -> BisonoricLayout:
 79    '''
 80    The `layout_name` must be one of the names returned by `list_layout_names()`.
 81    '''
 82    if not re.fullmatch(r'\w+', layout_name):
 83        raise ValueError('invalid layout name')
 84    layout_path = Path(__file__).parent / f'{layout_name}.yaml'
 85    return load_bisonoric_layout_by_path(layout_path)
 86
 87
 88def list_layout_names() -> Iterable[str]:
 89    '''
 90    Lists all preconfigured layouts. To change the key of a layout, use
 91    `concertina_helper.layouts.bisonoric.BisonoricLayout.transpose`.
 92
 93    >>> list_layout_names()
 94    ['20_cg', '30_jefferies_cg', '30_wheatstone_cg']
 95    '''
 96    return sorted([path.stem for path in Path(__file__).parent.glob('*.yaml')])
 97
 98
 99# TODO: Add a test and uncomment.
100# def load_unisonoric_layout_by_path(layout_path: Path) -> UnisonoricLayout:
101#     layout_yaml = layout_path.read_text()
102#     layout_spec = safe_load(layout_yaml)
103#     return _parse_unisonoric_layout(layout_spec)
def parse_unisonoric_layout( layout_spec: dict) -> concertina_helper.layouts.unisonoric.UnisonoricLayout:
38def parse_unisonoric_layout(layout_spec: dict) -> UnisonoricLayout:
39    left_matrix = _parse_matrix(layout_spec['left'])
40    right_matrix = _parse_matrix(layout_spec['right'])
41    return UnisonoricLayout(left_matrix, right_matrix)
def parse_bisonoric_layout(layout_spec: dict) -> concertina_helper.layouts.bisonoric.BisonoricLayout:
44def parse_bisonoric_layout(layout_spec: dict) -> BisonoricLayout:
45    push_layout = parse_unisonoric_layout(layout_spec['push'])
46    pull_layout = parse_unisonoric_layout(layout_spec['pull'])
47    return BisonoricLayout(push_layout=push_layout, pull_layout=pull_layout)
def load_bisonoric_layout_by_path( layout_path: pathlib.Path) -> concertina_helper.layouts.bisonoric.BisonoricLayout:
50def load_bisonoric_layout_by_path(layout_path: Path) -> BisonoricLayout:
51    '''
52    Expects the file at `layout_path` to be YAML, with a structure like this:
53    ```
54    push:
55        left:
56            - C3 G3 C4  E4 G4
57            - B3 D4 G4  B4 D5
58        right:
59            - C5  E5 G5  C6  E6
60            - G5  B5 D6  G6  B6
61    pull:
62        left:
63            - G3 B3  D4  F4 A4
64            - A3 F#4 A4  C5 E5
65        right:
66            - B4  D5 F5  A5  B5
67            - F#5 A5 C6  E6  F#6
68    ```
69    - `push` and `pull` at the top level.
70    - `left` and `right` inside.
71    - Each contains a list of strings, representing rows of buttons.
72    - The strings are the pitches of that row of keys, space delimitted.
73    '''
74    layout_yaml = layout_path.read_text()
75    layout_spec = safe_load(layout_yaml)
76    return parse_bisonoric_layout(layout_spec)

Expects the file at layout_path to be YAML, with a structure like this:

push:
    left:
        - C3 G3 C4  E4 G4
        - B3 D4 G4  B4 D5
    right:
        - C5  E5 G5  C6  E6
        - G5  B5 D6  G6  B6
pull:
    left:
        - G3 B3  D4  F4 A4
        - A3 F#4 A4  C5 E5
    right:
        - B4  D5 F5  A5  B5
        - F#5 A5 C6  E6  F#6
  • push and pull at the top level.
  • left and right inside.
  • Each contains a list of strings, representing rows of buttons.
  • The strings are the pitches of that row of keys, space delimitted.
def load_bisonoric_layout_by_name(layout_name: str) -> concertina_helper.layouts.bisonoric.BisonoricLayout:
79def load_bisonoric_layout_by_name(layout_name: str) -> BisonoricLayout:
80    '''
81    The `layout_name` must be one of the names returned by `list_layout_names()`.
82    '''
83    if not re.fullmatch(r'\w+', layout_name):
84        raise ValueError('invalid layout name')
85    layout_path = Path(__file__).parent / f'{layout_name}.yaml'
86    return load_bisonoric_layout_by_path(layout_path)

The layout_name must be one of the names returned by list_layout_names().

def list_layout_names() -> collections.abc.Iterable[str]:
89def list_layout_names() -> Iterable[str]:
90    '''
91    Lists all preconfigured layouts. To change the key of a layout, use
92    `concertina_helper.layouts.bisonoric.BisonoricLayout.transpose`.
93
94    >>> list_layout_names()
95    ['20_cg', '30_jefferies_cg', '30_wheatstone_cg']
96    '''
97    return sorted([path.stem for path in Path(__file__).parent.glob('*.yaml')])

Lists all preconfigured layouts. To change the key of a layout, use concertina_helper.layouts.bisonoric.BisonoricLayout.transpose.

>>> list_layout_names()
['20_cg', '30_jefferies_cg', '30_wheatstone_cg']