id3_cli/app/
delete.rs

1use crate::{
2    app::{
3        field::{ArgsTable, Field, Text},
4        Run,
5    },
6    error::Error,
7    text_data::picture_type::PictureType,
8    utils::ModifyTags,
9};
10use clap::{Args, Subcommand};
11use id3::{Tag, TagLike};
12use std::{mem::replace, path::PathBuf};
13
14/// Subcommand of the `delete` subcommand.
15#[derive(Debug, Subcommand)]
16pub enum Delete {
17    /// Remove the whole ID3 tag from the audio.
18    All(DeleteAllField),
19    /// Remove a single field.
20    #[clap(flatten)]
21    Single(DeleteSingleField),
22}
23
24impl Run for Delete {
25    fn run(self) -> Result<(), Error> {
26        match self {
27            Delete::All(proc) => proc.run(),
28            Delete::Single(proc) => proc.run(),
29        }
30    }
31}
32
33/// CLI arguments of `delete all` subcommand.
34#[derive(Debug, Args)]
35#[clap(about = "")]
36pub struct DeleteAllField {
37    /// Don't create backup for the target audio file.
38    #[clap(long)]
39    pub no_backup: bool,
40    /// Path to the target audio file.
41    pub target_audio: PathBuf,
42}
43
44impl Run for DeleteAllField {
45    fn run(self) -> Result<(), Error> {
46        let DeleteAllField {
47            no_backup,
48            target_audio,
49        } = self;
50        ModifyTags::builder()
51            .no_backup(no_backup)
52            .target_audio(&target_audio)
53            .build()
54            .run(|tag| replace(tag, Tag::new()))?;
55        Ok(())
56    }
57}
58
59/// Single-field subcommand of the `delete` subcommand.
60pub type DeleteSingleField = Field<DeleteArgsTable>;
61
62impl Run for Text<DeleteArgsTable> {
63    fn run(self) -> Result<(), Error> {
64        fn delete_text(args: DeleteText, delete: impl FnOnce(&mut Tag)) -> Result<(), Error> {
65            let DeleteText {
66                no_backup,
67                target_audio,
68            } = args;
69            ModifyTags::builder()
70                .no_backup(no_backup)
71                .target_audio(&target_audio)
72                .build()
73                .run(delete)
74        }
75
76        match self {
77            Text::Title(args) => delete_text(args, Tag::remove_title),
78            Text::Artist(args) => delete_text(args, Tag::remove_artist),
79            Text::Album(args) => delete_text(args, Tag::remove_album),
80            Text::AlbumArtist(args) => delete_text(args, Tag::remove_album_artist),
81            Text::Genre(DeleteGenre::Genre(args)) => delete_text(args, Tag::remove_genre),
82        }
83    }
84}
85
86/// Table of [`Args`] types for [`Delete`].
87#[derive(Debug)]
88pub struct DeleteArgsTable;
89impl ArgsTable for DeleteArgsTable {
90    type Text = DeleteText;
91    type Genre = DeleteGenre;
92    type Comment = DeleteComment;
93    type Picture = DeletePicture;
94}
95
96/// CLI arguments of `delete <text-field>`.
97#[derive(Debug, Args)]
98#[clap(about = "")]
99pub struct DeleteText {
100    /// Don't create backup for the target audio file.
101    #[clap(long)]
102    pub no_backup: bool,
103    /// Path to the target audio file.
104    pub target_audio: PathBuf,
105}
106
107/// Genre fields.
108#[derive(Debug, Subcommand)]
109pub enum DeleteGenre {
110    #[clap(name = "genre")]
111    Genre(DeleteText),
112}
113
114/// CLI arguments of `delete comment`.
115#[derive(Debug, Args)]
116#[clap(about = "")]
117pub struct DeleteComment {
118    /// Don't create backup for the target audio file.
119    #[clap(long)]
120    pub no_backup: bool,
121    /// Comment description.
122    #[clap(long)]
123    pub description: Option<String>,
124    /// Comment content.
125    #[clap(long)]
126    pub content: Option<String>,
127    /// Path to the target audio file.
128    pub target_audio: PathBuf,
129}
130
131impl Run for DeleteComment {
132    fn run(self) -> Result<(), Error> {
133        let DeleteComment {
134            no_backup,
135            ref description,
136            ref content,
137            ref target_audio,
138        } = self;
139        let description = description.as_deref();
140        let content = content.as_deref();
141        ModifyTags::builder()
142            .no_backup(no_backup)
143            .target_audio(target_audio)
144            .build()
145            .run(|tag| tag.remove_comment(description, content))
146    }
147}
148
149/// CLI arguments of `delete picture`.
150#[derive(Debug, Args)]
151#[clap(about = "")]
152pub struct DeletePicture {
153    /// Don't create backup for the target audio file.
154    #[clap(long)]
155    pub no_backup: bool,
156    /// Path to the target audio file.
157    pub target_audio: PathBuf,
158    /// Subcommand to execute.
159    #[clap(subcommand)]
160    pub command: DeletePictureCmd,
161}
162
163impl Run for DeletePicture {
164    fn run(self) -> Result<(), Error> {
165        let DeletePicture {
166            no_backup,
167            ref target_audio,
168            command,
169        } = self;
170
171        ModifyTags {
172            no_backup,
173            target_audio,
174        }
175        .run(|tag| match command {
176            DeletePictureCmd::All => tag.remove_all_pictures(),
177            DeletePictureCmd::ByType(picture_type) => {
178                tag.remove_picture_by_type(picture_type.into())
179            }
180        })
181    }
182}
183
184/// Subcommand of `delete picture`.
185#[derive(Debug, Subcommand)]
186#[clap(about = "")]
187pub enum DeletePictureCmd {
188    /// Delete all pictures.
189    All,
190    /// Delete a picture by type.
191    #[clap(flatten)]
192    ByType(PictureType),
193}