קורס Advanced Python3 שיעור תרגול ביטויים רגולאריים


זה נושא דיון מלווה לערך המקורי ב- https://www.tocode.co.il/bundles/advanced-python3/lessons/07-regexp-lab

זה הפתרון שלי לתרגיל

התוצאה לאחר ההרצה:
פותח בתיקיית הפרוייקט את העץ הבא של הקבצים:

#main.py

import re
from typing import Match
import fileinput
from pathlib import Path

songParts = {
    "artist": r"[\w\s-]+",
    "track": r"[0-9]+",
    "title": r"[\w\s-]+",
    "year": r"[0-9]+"
}
items_reg = re.compile(r"<(\w+)>")


def escape_speical_chars(text):
    return text.replace(".", "\.").replace("(", "\(").replace(")", "\)")


def replace_item_pattern(match_text: Match[str], settings_dict: dict[str]):
    group_name = match_text.group(1)
    return f"(?P<{group_name}>{settings_dict[group_name]})"

def replace_item(match_text: Match[str] , data_dict:dict[str]):
    group_name = match_text.group(1)
    return f"{data_dict[group_name]}"


def get_song_match_pattern(song_format: str):
    return re.compile(items_reg.sub(
        lambda m: replace_item_pattern(m, songParts),
        escape_speical_chars(song_format)
    ))

def rename(song_format: str, output_format:str ,path_to_file:str ):
    match_pattern = get_song_match_pattern(song_format)
    for songData in fileinput.input(path_to_file):
        match_data = match_pattern.match(songData.strip())
        data = match_data.groupdict()
        new_file_path = items_reg.sub(lambda m:replace_item(m,data) , output_format)
        filepath = Path(new_file_path)
        filepath.parent.mkdir(parents=True, exist_ok=True)
        with filepath.open("w",encoding="utf-8") as f:
            f.close()

def main():
    rename("<artist> - <track> <title> (<year>).mp3",
        "<artist>/<year>/<track> <title>.mp3",
       "./songs.txt"
    )

main()

וזה למעשה קובץ השירים (הוספתי את הנתיב אליו כארגומנט שלישי לפונקציה , כי ממה שהבנתי היא אמורה לקרוא את השירים מתוך הקובץ למרות שזה לא צויין בתיאור המטלה )

Bob Dylan - 01 Youre No Good (1962).mp3
Bob Dylan - 02 Talkin New York (1962).mp3
Bob Dylan - 03 In My Time of Dyin (1962).mp3
Bob Dylan - 04 Man of Constant Sorrow (1962).mp3
Bob Dylan - 05 Fixin to Die (1962).mp3
Bob Dylan - 06 Pretty Peggy-O (1962).mp3
לייק 1

פיתרון מצוין! זה למעשה אחד מתרגילי הביטויים הרגולאריים האהוב עליי בכל הזמנים ואני מקווה שגם אתה נהנית ממנו.

מבחינת תיאור התרגיל הכוונה לא היתה לקחת את שמות השירים מקובץ אלא להסתכל איזה קבצים יש בתיקייה ובאמת לשנות את השמות שלהם בתיקייה
(כלומר לפני ההרצה יוצרים קבצים עם השמות ״הישנים״ בתיקייה ואז מריצים ורואים אותם משתנים).

זו ההצעה שלי לפתרון.
כיוון שכתוב שעל הפונקציה להיות מספיק גנרית ולתמוך בכל פורמט, לא הנחתי שאפשר להסתמך על השמות (artist, track וכו’) ועל סוג התווים הצפויים להם, רק שכל תת-מחרוזת כזו תתחיל בתו word character

import glob
import logging
import os
import re
import shutil


def rename(in_format: str, out_format: str):
    pattern = re.sub(r'(\<\w+\>)', r'(?P\1\\w+.*)', re.escape(in_format))
    repl = re.sub(r'(\<\w+\>)', r'\\g\1', out_format)
    files = []
    for file in glob.glob('*.mp3'):
        files.append({'src': file, 'dst': re.sub(pattern, repl, file)})

    for file in files:
        (dirs, file_name) = re.split(r'/(?!.*\/)', file['dst'], maxsplit=1)
        os.makedirs(dirs, exist_ok=True)
        shutil.move(file['src'], file['dst'])
        logging.info(f"File move - src: {file['src']}; dst: {file['dst']};")


def main():
    logging.getLogger().setLevel(logging.INFO)
    rename('<artist> - <track> <title> (<year>).mp3',
           '<artist>/<year>/<track> <title>.mp3')


if __name__ == '__main__':
    main()

הכיוון מעולה. שווה לשים לב שלפעמים אתה כן יודע דברים, למשל אתה יודע ששנה מורכבת רק מספרות, ואולי מספר שיר יכול להיות שתי ספרות. זה עוזר אם למשל תבנית שם הקובץ היא:

<artist> - <year> <title>

או:

<artist>/<year> <album>/<title>

אפשר להוסיף מילון עבור תוויות מוכרות ופונקציית עזר שתשתמש בו. זה יכול לשפר התמודדות עם תבניות ללא מפרידים ברורים:

import glob
import logging
import os
import re
import shutil

PATTERN_BY_LABEL = {
    '<track>': r'\d\d',
    '<year>': r'\d\d(\d\d)?',
}


def micro_pattern(match_obj: re.Match) -> str:
    label = match_obj.group(1)
    return f"(?P{label}" + PATTERN_BY_LABEL.get(label.casefold(), r'\w+.*') + ')'


def rename(in_format: str, out_format: str):
    pattern = re.sub(r'(\<\w+\>)', micro_pattern, re.escape(in_format))
    logging.debug(f"pattern:\n\t{pattern}")
    repl = re.sub(r'(\<\w+\>)', r'\\g\1', out_format)
    files = []
    for file in glob.glob('*.mp3'):
        files.append({'src': file, 'dst': re.sub(pattern, repl, file)})

    for file in files:
        (dirs, file_name) = re.split(r'/(?!.*\/)', file['dst'], maxsplit=1)
        os.makedirs(dirs, exist_ok=True)
        shutil.move(file['src'], file['dst'])
        logging.info(f"File move:\nsrc: {file['src']}\ndst: {file['dst']}\n")


def main():
    logging.getLogger().setLevel(logging.INFO)
    rename('<artist> - <track> <title> (<year>).mp3', '<artist>/<year>/<track> <title>.mp3')
    # rename('[<track>] <artist> - <year> <title>.mp3', '<artist>/<year>/<track> <title>.mp3')


if __name__ == '__main__':
    main()
לייק 1