Skip to content

Commit

Permalink
Rewrite distribute field
Browse files Browse the repository at this point in the history
  • Loading branch information
Josef-Friedrich committed Jan 23, 2024
1 parent d7fe8bb commit 0a6350a
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 71 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ CLI Usage
-n, --no-whitespace Replace all whitespaces with dashes or sometimes underlines.
-K FIELDS, --skip-if-empty FIELDS
Skip rename action if FIELDS are empty. Separate FIELDS
using commas: combined_composer,combined_title
using commas: composer,title
-t RENAME_TARGET, --target RENAME_TARGET
Target directory

Expand Down
2 changes: 1 addition & 1 deletion docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ Comande line interface
-n, --no-whitespace Replace all whitespaces with dashes or sometimes underlines.
-K FIELDS, --skip-if-empty FIELDS
Skip rename action if FIELDS are empty. Separate FIELDS
using commas: combined_composer,combined_title
using commas: composer,title
-t RENAME_TARGET, --target RENAME_TARGET
Target directory

Expand Down
6 changes: 3 additions & 3 deletions mscxyz/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ def _split_lines(self, text: typing.Text, width: int) -> typing.List[str]:
"-f",
"--format",
dest="rename_format",
default="$combined_title ($combined_composer)",
default="$title ($composer)",
help="Format string.",
)

Expand Down Expand Up @@ -452,7 +452,7 @@ def _split_lines(self, text: typing.Text, width: int) -> typing.List[str]:
dest="rename_skip",
metavar="FIELDS",
help="Skip rename action if FIELDS are empty. Separate FIELDS using "
"commas: combined_composer,combined_title",
"commas: composer,title",
)

group_rename.add_argument(
Expand Down Expand Up @@ -811,7 +811,7 @@ def list_styles(version: int) -> None:

if args.meta_dist:
for a in args.meta_dist:
score.meta.distribute_field(source_fields=a[0], format_string=a[1])
score.fields.distribute(source_fields=a[0], format_string=a[1])

if args.meta_delete:
score.meta.delete_duplicates()
Expand Down
54 changes: 46 additions & 8 deletions mscxyz/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
from __future__ import annotations

import json
import re
import typing
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Union
from typing import Any, Mapping, Union

from mscxyz.meta import FormatStringNoFieldError, UnmatchedFormatStringError

if typing.TYPE_CHECKING:
from mscxyz.score import Score


FieldValue = Union[str, int, float]
FieldsExport = dict[str, FieldValue]
FieldsExport = Mapping[str, FieldValue]


@dataclass
Expand Down Expand Up @@ -226,8 +229,13 @@ def __init__(self, score: "Score") -> None:
def names(self) -> tuple[str, ...]:
return tuple(self.__fields_by_name.keys())

def __access_attr(self, attr_path: str) -> Any | None:
attrs = attr_path.split(".")
def get_field(self, name: str) -> Field:
return self.__fields_by_name[name]

def get(self, name: str) -> Any | None:
field = self.get_field(name)

attrs = field.attr_path.split(".")
value = self.score
for attr in attrs:
value = getattr(value, attr)
Expand All @@ -236,9 +244,19 @@ def __access_attr(self, attr_path: str) -> Any | None:

return value

def get(self, name: str) -> Any | None:
field = self.__fields_by_name[name]
return self.__access_attr(field.attr_path)
def set(self, name: str, value: Any) -> Any | None:
field = self.get_field(name)
attrs = field.attr_path.split(".")

last = attrs.pop()

obj = self.score
for attr in attrs:
obj = getattr(obj, attr)
if obj is None:
raise Exception(f"Cannot set attribute {field.attr_path}")

setattr(obj, last, value)

def show(self, pre: dict[str, str], post: dict[str, str]) -> None:
pass
Expand Down Expand Up @@ -283,7 +301,7 @@ def show(self, pre: dict[str, str], post: dict[str, str]) -> None:

# print("{}: {}".format(utils.color(field, field_color), " ".join(line)))

def export_to_dict(self) -> FieldsExport:
def export_to_dict(self) -> dict[str, FieldValue]:
output: FieldsExport = {}
for field in self.names:
value = self.get(field)
Expand All @@ -293,6 +311,26 @@ def export_to_dict(self) -> FieldsExport:
output[field] = value
return output

def distribute(self, source_fields: str, format_string: str) -> None:
f: list[str] = source_fields.split(",")
for source_field in f:
source = self.get(source_field)
source = str(source)

fields = re.findall(r"\$([a-z_]*)", format_string)
if not fields:
raise FormatStringNoFieldError(format_string)
regex = re.sub(r"\$[a-z_]*", "(.*)", format_string)
match = re.search(regex, source)
if not match:
raise UnmatchedFormatStringError(format_string, source)
values = match.groups()
results: dict[str, str] = dict(zip(fields, values))
if results:
for field, value in results.items():
self.set(field, value)
return

def export_json(self) -> Path:
"""
Export the data as a JSON file.
Expand Down
31 changes: 0 additions & 31 deletions mscxyz/meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,27 +47,6 @@ def __init__(self, format_string: str) -> None:
Exception.__init__(self, self.msg)


def distribute_field(source: str, format_string: str) -> dict[str, str]:
"""
Distributes the values from the source string into a dictionary based on the format string.
:param source: The source string from which values will be extracted.
:param format_string: The format string that specifies the pattern of the values to be extracted.
:return: A dictionary mapping field names to their corresponding values.
:raises FormatStringNoFieldError: If the format string does not contain any field markers.
:raises UnmatchedFormatStringError: If the format string does not match the source string.
"""
fields = re.findall(r"\$([a-z_]*)", format_string)
if not fields:
raise FormatStringNoFieldError(format_string)
regex = re.sub(r"\$[a-z_]*", "(.*)", format_string)
match = re.search(regex, source)
if not match:
raise UnmatchedFormatStringError(format_string, source)
values = match.groups()
return dict(zip(fields, values))


def to_underscore(field: str) -> str:
"""
Convert a camel case string to snake case.
Expand Down Expand Up @@ -890,16 +869,6 @@ def sync_fields(self) -> None:
self.combined.composer = self.combined.composer
self.combined.lyricist = self.combined.lyricist

def distribute_field(self, source_fields: str, format_string: str) -> None:
f: list[str] = source_fields.split(",")
for source_field in f:
source = getattr(self.interface, source_field)
results: dict[str, str] = distribute_field(source, format_string)
if results:
for field, value in results.items():
setattr(self.interface, field, value)
return

def write_to_log_file(self, log_file: str, format_string: str) -> None:
log = open(log_file, "w")
log.write(tmep.parse(format_string, self.interface.export_to_dict()) + "\n")
Expand Down
16 changes: 9 additions & 7 deletions mscxyz/rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import tmep
from tmep.format import alphanum, asciify, nowhitespace

from mscxyz.fields import FieldsExport
from mscxyz.score import Score
from mscxyz.settings import get_args
from mscxyz.utils import color
Expand All @@ -23,11 +24,12 @@ def create_dir(path: str) -> None:
raise


def prepare_fields(fields: dict[str, str]) -> dict[str, str]:
def prepare_fields(fields: FieldsExport) -> dict[str, str]:
args = get_args()
out: dict[str, str] = {}
output: dict[str, str] = {}
for field, value in fields.items():
if value:
value = str(value)
if args.rename_alphanum:
value = alphanum(value)
value = value.strip()
Expand All @@ -37,11 +39,11 @@ def prepare_fields(fields: dict[str, str]) -> dict[str, str]:
value = nowhitespace(value)
value = value.strip()
value = value.replace("/", "-")
out[field] = value
return out
output[field] = value
return output


def apply_format_string(fields: dict[str, str]) -> str:
def apply_format_string(fields: FieldsExport) -> str:
args = get_args()
fields = prepare_fields(fields)
name = tmep.parse(args.rename_format, fields)
Expand All @@ -66,13 +68,13 @@ def get_checksum(filename: str) -> str:
def rename(score: Score) -> Score:
args = get_args()

meta_values: dict[str, str] = score.meta.interface.export_to_dict()
meta_values = score.fields.export_to_dict()
target_filename: str = apply_format_string(meta_values)

if args.rename_skip:
skips: list[str] = args.rename_skip.split(",")
for skip in skips:
if not meta_values[skip]:
if skip not in meta_values:
print(color("Field “{}” is empty! Skipping".format(skip), "red"))
return score

Expand Down
2 changes: 1 addition & 1 deletion mscxyz/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class DefaultArguments:
rename_rename: bool = False
rename_alphanum: bool = False
rename_ascii: bool = False
rename_format: str = "$combined_title ($combined_composer)"
rename_format: str = "$title ($composer)"
rename_no_whitespace = False
rename_skip: Optional[str] = None
rename_target: Optional[str] = None
Expand Down
2 changes: 1 addition & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_args_general_rename(self) -> None:
assert args.rename_rename
assert args.rename_alphanum is False
assert args.rename_ascii is False
assert args.rename_format == "$combined_title ($combined_composer)"
assert args.rename_format == "$title ($composer)"
assert args.rename_target is None


Expand Down
11 changes: 11 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,14 @@ def test_method_export_to_dict(self, fields: FieldsManager) -> None:

def test_method_get(self, fields: FieldsManager) -> None:
assert fields.get("title") == "Title"

def test_method_set(self, fields: FieldsManager) -> None:
new = "New Title"
fields.set("title", new)
assert fields.get("title") == new

def test_distribute(self, fields: FieldsManager) -> None:
assert fields.distribute("title,compose", "$title - $composer") == {
"composer": "Queen",
"title": "We are the champions",
}
2 changes: 1 addition & 1 deletion tests/test_how_to.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def test_rename(self, tmp_path: Path) -> None:
"--target",
dest,
"--format",
"%lower{%shorten{$combined_title,1}}/$combined_title",
"%lower{%shorten{$title,1}}/$title",
"--no-whitespace",
src,
).execute()
Expand Down
23 changes: 9 additions & 14 deletions tests/test_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
Meta,
Metatag,
Vbox,
distribute_field,
export_to_dict,
to_underscore,
)
Expand Down Expand Up @@ -60,11 +59,6 @@ def test_format_string_no_field_error(self) -> None:


class TestFunctions:
def test_distribute_field(self) -> None:
assert distribute_field(
"We are the champions - Queen", "$title - $composer"
) == {"composer": "Queen", "title": "We are the champions"}

def test_to_underscore(self) -> None:
assert to_underscore("PascalCase") == "_pascal_case"
assert to_underscore("lowerCamelCase") == "lower_camel_case"
Expand Down Expand Up @@ -402,23 +396,24 @@ def test_distribute_field(self) -> None:
Cli(
"--distribute-field",
"vbox_title",
"$combined_title - $combined_composer",
"$title - $composer",
)
.append_score("meta-distribute-field.mscz")
.execute()
)
i = c.post.meta.interface
assert i.vbox_composer == "Composer"
assert i.metatag_composer == "Composer"
assert i.vbox_title == "Title"
assert i.metatag_work_title == "Title"
f = c.post.fields

assert f.get("vbox_composer") == "Composer"
assert f.get("metatag_composer") == "Composer"
assert f.get("vbox_title") == "Title"
assert f.get("metatag_work_title") == "Title"

def test_distribute_field_multple_source_fields(self) -> None:
c = (
Cli(
"--distribute-field",
"vbox_title,readonly_basename",
"$combined_title - $combined_composer",
"$title - $composer",
)
.append_score("Title - Composer.mscz")
.execute()
Expand Down Expand Up @@ -766,7 +761,7 @@ def test_with_templating(self) -> None:

def test_option_log(tmp_path: Path) -> None:
log = tmp_path / "log.txt"
Cli("--log", log, "$combined_title-$combined_composer").execute()
Cli("--log", log, "$title-$composer").execute()
assert open(log, "r").readline() == "Title-Composer\n"


Expand Down
6 changes: 3 additions & 3 deletions tests/test_rename.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
class TestFunctions:
def test_function_prepare_fields(self) -> None:
reset_args()
fields: dict[str, str] = {
fields = {
"field1": " Subtitle ",
"field2": "Title / Composer",
}
Expand All @@ -27,7 +27,7 @@ def test_function_prepare_fields(self) -> None:
def test_function_apply_format_string(self) -> None:
reset_args()
score = get_score("meta-all-values.mscx")
fields: dict[str, str] = score.meta.interface.export_to_dict()
fields = score.fields.export_to_dict()
name: str = rename.apply_format_string(fields)
assert name == "vbox_title (vbox_composer)"

Expand All @@ -36,7 +36,7 @@ def test_function_get_checksum(self) -> None:
assert rename.get_checksum(tmp) == "dacd912aa0f6a1a67c3b13bb947395509e19dce2"


class TestIntegration:
class TestCli:
@pytest.mark.parametrize("version", supported_versions)
def test_simple(self, version: int, cwd_tmpdir: Path) -> None:
stdout: str = Cli(
Expand Down

0 comments on commit 0a6350a

Please sign in to comment.