DevGang
Авторизоваться

Организуйте свой рабочий стол: создайте органайзер файлов в Go

Наведите порядок на рабочем столе с помощью органайзера файлов на Go. Этот простой скрипт поможет разложить ваши файлы по категориям (например, видео, музыка) или по дате создания, освобождая место и улучшая организацию. Начнем с создания файла go.mod с директивой go mod init. Вся логика будет размещена в main.go. Скрипт будет сортировать файлы по типам (видео, музыка) или датам, создавая соответствующие каталоги. Далее – код для реализации этой задачи:

package main

type fileAnalyzer interface {
    analyzeAndSort() error
}

func analyze(fa fileAnalyzer) error {
    return fa.analyzeAndSort()
}

Для сортировки файлов по разным критериям мы разработали интерфейс fileAnalyzer с методом analyzeAndSort. Функция analyze принимает объект, реализующий этот интерфейс, и вызывает его метод.

Однако, некоторые файлы (например, файлы проекта Go или исполняемые файлы) могут не нуждаться в перемещении. Для этого создаётся «черный список» файлов, которые должны остаться на месте. Этот список реализуется в файле main.go.

var blacklist = []string{
    "go",
    "mod",
    "exe",
    "ps1",
}

Добавлены расширения файлов .go, .mod (для файлов Go), .exe (для двоичных файлов в Windows), и .ps1 (для скриптов PowerShell). Теперь последует создание вспомогательных функций.

func getFileExtension(name string) string {
    return strings.TrimPrefix(filepath.Ext(name), ".")
}

func listFiles(dirname string) ([]string, error) {
    var files []string

    list, err := os.ReadDir(dirname)
    if err != nil {
        return nil, err
    }

    for _, file := range list {
        if !file.IsDir() {
            files = append(files, file.Name())
        }
    }

    return files, nil
}

func listDirs(dirname string) ([]string, error) {
    var dirs []string

    list, err := os.ReadDir(dirname)
    if err != nil {
        return nil, err
    }

    for _, file := range list {
        if file.IsDir() {
            dirs = append(dirs, file.Name())
        }
    }

    return dirs, nil
}

func mkdir(dirname string) error {
    err := os.Mkdir(dirname, 0644)

    if err != nil && os.IsExist(err) {
        return nil
    }

    var e *os.PathError

    if err != nil && errors.As(err, &e) {
        return nil
    }

    return err
}

func moveFile(name string, dst string) error {
    return os.Rename(name, filepath.Join(dst, name))
}

func getCurrentDate(t time.Time) string {
    return t.Format("2006-01-02")
}

func filter[T any](ts []T, fn func(T) bool) []T {
    filtered := make([]T, 0)

    for i := range ts {
        if fn(ts[i]) {
            filtered = append(filtered, ts[i])
        }
    }

    return filtered
}

Функция mkdir создаёт каталог, если его нет. Однако, она не сообщает об ошибке, если каталог уже существует или при других системных проблемах (типа os.PathError).

Теперь создадим структуру, которая будет реализовывать интерфейс fileAnalyzer.

type fileTypeAnalyzer struct {
    wd     string
    mapper map[string][]string
}

func newFileTypeAnalyzer(wd string) *fileTypeAnalyzer {
    return &fileTypeAnalyzer{
        wd: wd,
        mapper: map[string][]string{
            "video":  {"mp4", "mkv", "3gp", "wmv", "flv", "avi", "mpeg", "webm"},
            "music":  {"mp3", "aac", "wav", "flac"},
            "images": {"jpg", "jpeg", "png", "gif", "svg", "tiff"},
            "docs":   {"docx", "csv", "txt", "xlsx"},
            "books":  {"pdf", "epub"},
        },
    }
}

func (f fileTypeAnalyzer) analyzeAndSort() error {
    files, err := listFiles(f.wd)
    if err != nil {
        return fmt.Errorf("could not list files: %w", err)
    }

    if err := f.createFileTypeDirs(files...); err != nil {
        return err
    }

    return f.moveFileToTypeDir(files...)
}

func (f fileTypeAnalyzer) moveFileToTypeDir(files ...string) error {
    dirs, err := listDirs(f.wd)
    if err != nil {
        return fmt.Errorf("could not list directories: %w", err)
    }

    for _, dir := range dirs {
        for _, file := range files {
            if slices.Contains(f.mapper[dir], strings.ToLower(getFileExtension(file))) {
                if err := moveFile(file, dir); err != nil {
                    return err
                }
            }
        }
    }

    files, err = listFiles(f.wd)
    if err != nil {
        return err
    }

    if len(files) == 0 {
        return nil
    }

    files = filter(files, func(f string) bool {
        return !slices.Contains(blacklist, getFileExtension(f))
    })

    for i := range files {
        if err := f.moveToMisc(files[i]); err != nil {
            return err
        }
    }

    return nil
}

func (f fileTypeAnalyzer) moveToMisc(file string) error {
    if err := mkdir("misc"); err != nil {
        return err
    }

    return moveFile(file, "misc")
}

func (f fileTypeAnalyzer) createFileTypeDirs(files ...string) error {
    for ftype, fvalues := range f.mapper {
        for _, file := range files {
            if slices.Contains(fvalues, getFileExtension(file)) {
                if err := mkdir(ftype); err != nil {
                    return fmt.Errorf("could not create folder: %w", err)
                }
            }
        }
    }

    return nil
}

Структура fileTypeAnalyzer хранит текущий рабочий каталог (wd) и словарь (сопоставитель), где ключи — типы файлов, а значения — списки их расширений. Конструктор заполняет этот словарь типами и расширениями файлов. Метод analyzeAndSort анализирует расширения, создаёт каталоги для соответствующих типов файлов и перемещает файлы в эти каталоги. Файлы, не соответствующие ни одному типу, помещаются в папку misc, за исключением файлов из «чёрного списка».

Также добавлена структура для анализа и сортировки файлов по дате создания, которая реализует тот же интерфейс fileAnalyzer.

type fileDateAnalyzer struct {
    wd string
}

func newFileDateAnalyzer(wd string) *fileDateAnalyzer {
    return &fileDateAnalyzer{
        wd: wd,
    }
}

func (f fileDateAnalyzer) analyzeAndSort() error {
    files, err := listFiles(f.wd)
    if err != nil {
        return fmt.Errorf("could not list files: %w", err)
    }

    if err := f.createFileDateDirs(files...); err != nil {
        return err
    }

    return f.moveFileToDateDir(files...)
}

func (f fileDateAnalyzer) createFileDateDirs(files ...string) error {
    for _, file := range files {
        info, err := os.Stat(file)
        if err != nil {
            return err
        }

        if !slices.Contains(blacklist, getFileExtension(file)) {
            if err := mkdir(getCurrentDate(info.ModTime())); err != nil {
                return err
            }
        }
    }

    return nil
}

func (f fileDateAnalyzer) moveFileToDateDir(files ...string) error {
    dirs, err := listDirs(f.wd)
    if err != nil {
        return fmt.Errorf("could not list directories: %w", err)
    }

    files = filter(files, func(f string) bool {
        return !slices.Contains(blacklist, getFileExtension(f))
    })

    for _, dir := range dirs {
        for _, file := range files {
            info, err := os.Stat(file)
            if err != nil {
                continue
            }

            if dir == getCurrentDate(info.ModTime()) {
                if err := moveFile(file, dir); err != nil {
                    continue
                }
            }
        }
    }

    return nil
}

Логика работы похожа на fileTypeAnalyzer, но вместо словаря типов файлов мы используем дату создания файла для создания соответствующих каталогов.

Теперь объединим все эти элементы в основной функции.

func main() {
    logger := slog.Default()
    slog.SetDefault(logger)

    var analyzer fileAnalyzer

    wd, err := os.Getwd()
    if err != nil {
        log.Fatal(err)
    }

    var mode string

    flag.StringVar(&mode, "mode", "", "Provide sort mode (type|date)")
    flag.Parse()

    switch mode {
    case "date":
        analyzer = newFileDateAnalyzer(wd)
    case "type":
        analyzer = newFileTypeAnalyzer(wd)
    default:
        fmt.Println("Provide sort mode flag: --mode=(type|date)")
        return
    }

    if err := analyze(analyzer); err != nil {
        log.Fatal(err)
    }
}

Настраивается логгер, получаем текущий рабочий каталог, переменную режима для управления флагами запуска и switch-оператор для выбора метода сортировки (по типу или дате). Затем вызываем функцию analyze, передавая соответствующую реализацию fileAnalyzer.

Всё готово. Соберите исполняемый файл (например, go build -o sorter), и протестируйте его. В папке находятся файлы разных типов для проверки.

Давайте организуем по типу файла:

Давайте организуем по дате создания файла:

В качестве бонуса, если вы используете машину с Windows и PowerShell, вот скрипт, который поможет сделать тестирование вашей программы простым и понятным.

Создайте файл task.ps1 и введите следующее:

[CmdletBinding()]
Param(
  [Parameter(Mandatory)][string]$Action
)

function Get-Dirs {
  [CmdletBinding()]
  Param(
    [Parameter(Mandatory)][string]$Path
  )
  return Get-ChildItem -Path $Path | Where-Object { $_.GetType() -eq [System.IO.DirectoryInfo] }
}

switch($Action) {
  'build' {
    Write-Host -ForegroundColor green 'building binary...'
    go build -o sorter.exe
  }
  'remove' {
    $folders = Get-Dirs -Path '.'

    foreach($folder in $folders) {
      if ((Get-ChildItem $folder).Count -eq 0) {
        Remove-Item $folder
      } else {
        Write-Host -ForegroundColor yellow 'folder not empty'
      }
    }
  }
  'unsort' {
    Write-Host -ForegroundColor green 'unsorting...'
    Get-ChildItem (Get-Dirs -Path '.') | ForEach-Object { $_ | Move-Item -Destination '.' }
  }
  Default {
    Write-Host -ForegroundColor red 'invalid action'
  }
}

Чтобы собрать двоичный файл с помощью скрипта:

Чтобы деорганизовать файлы с помощью скрипта:

Чтобы удалить каталоги с помощью скрипта:

Источник:

#Golang
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу