|
5 | 5 | from beets import config, logging, plugins, ui |
6 | 6 | from beets.util import displayable_path, normpath, syspath |
7 | 7 |
|
8 | | -from .._utils import parse_logfiles |
9 | 8 | from .session import TerminalImportSession |
10 | 9 |
|
11 | 10 | # Global logger. |
12 | 11 | log = logging.getLogger("beets") |
13 | 12 |
|
14 | 13 |
|
| 14 | +def paths_from_logfile(path): |
| 15 | + """Parse the logfile and yield skipped paths to pass to the `import` |
| 16 | + command. |
| 17 | + """ |
| 18 | + with open(path, encoding="utf-8") as fp: |
| 19 | + for i, line in enumerate(fp, start=1): |
| 20 | + verb, sep, paths = line.rstrip("\n").partition(" ") |
| 21 | + if not sep: |
| 22 | + raise ValueError(f"line {i} is invalid") |
| 23 | + |
| 24 | + # Ignore informational lines that don't need to be re-imported. |
| 25 | + if verb in {"import", "duplicate-keep", "duplicate-replace"}: |
| 26 | + continue |
| 27 | + |
| 28 | + if verb not in {"asis", "skip", "duplicate-skip"}: |
| 29 | + raise ValueError(f"line {i} contains unknown verb {verb}") |
| 30 | + |
| 31 | + yield os.path.commonpath(paths.split("; ")) |
| 32 | + |
| 33 | + |
| 34 | +def parse_logfiles(logfiles): |
| 35 | + """Parse all `logfiles` and yield paths from it.""" |
| 36 | + for logfile in logfiles: |
| 37 | + try: |
| 38 | + yield from paths_from_logfile(syspath(normpath(logfile))) |
| 39 | + except ValueError as err: |
| 40 | + raise ui.UserError( |
| 41 | + f"malformed logfile {displayable_path(logfile)}: {err}" |
| 42 | + ) from err |
| 43 | + except OSError as err: |
| 44 | + raise ui.UserError( |
| 45 | + f"unreadable logfile {displayable_path(logfile)}: {err}" |
| 46 | + ) from err |
| 47 | + |
| 48 | + |
15 | 49 | def import_files(lib, paths: list[bytes], query): |
16 | 50 | """Import the files in the given list of paths or matching the |
17 | 51 | query. |
@@ -97,6 +131,32 @@ def import_func(lib, opts, args: list[str]): |
97 | 131 | import_files(lib, byte_paths, query) |
98 | 132 |
|
99 | 133 |
|
| 134 | +def _store_dict(option, opt_str, value, parser): |
| 135 | + """Custom action callback to parse options which have ``key=value`` |
| 136 | + pairs as values. All such pairs passed for this option are |
| 137 | + aggregated into a dictionary. |
| 138 | + """ |
| 139 | + dest = option.dest |
| 140 | + option_values = getattr(parser.values, dest, None) |
| 141 | + |
| 142 | + if option_values is None: |
| 143 | + # This is the first supplied ``key=value`` pair of option. |
| 144 | + # Initialize empty dictionary and get a reference to it. |
| 145 | + setattr(parser.values, dest, {}) |
| 146 | + option_values = getattr(parser.values, dest) |
| 147 | + |
| 148 | + try: |
| 149 | + key, value = value.split("=", 1) |
| 150 | + if not (key and value): |
| 151 | + raise ValueError |
| 152 | + except ValueError: |
| 153 | + raise ui.UserError( |
| 154 | + f"supplied argument `{value}' is not of the form `key=value'" |
| 155 | + ) |
| 156 | + |
| 157 | + option_values[key] = value |
| 158 | + |
| 159 | + |
100 | 160 | import_cmd = ui.Subcommand( |
101 | 161 | "import", help="import new music", aliases=("imp", "im") |
102 | 162 | ) |
@@ -274,7 +334,7 @@ def import_func(lib, opts, args: list[str]): |
274 | 334 | "--set", |
275 | 335 | dest="set_fields", |
276 | 336 | action="callback", |
277 | | - callback=ui._store_dict, |
| 337 | + callback=_store_dict, |
278 | 338 | metavar="FIELD=VALUE", |
279 | 339 | help="set the given fields to the supplied values", |
280 | 340 | ) |
|
0 commit comments