tdl icon indicating copy to clipboard operation
tdl copied to clipboard

[Feat] More/better name template functions

Open hafeoz opened this issue 7 months ago • 2 comments

Proposal

Name templates currently have a rather limited functionality set. Following is a list of functions I personally believed to be useful:

  • Truncate, where STRING longer than LEN will be truncated. This allows more rich, longer templates to be used without worrying OS filename character limit.
  • SafeString, where unsafe characters (invalid filename characters) will be removed/replaced from STRING. The guide's example using Replace works, but is a bit too long and is prone to accidental breakage (mistype, accidental character delete, etc.).
  • Extension and Stem, where STRING will be parsed as a filename and the extension/non-extension part of the string will be returned.
  • Pad, where NUMBER will be padded to LEN digits (e.g. {{ Pad 1 3 }} will return a string 001)

Background

I'm using tdl in a script to automatically backup my chat. The core logic of the script now looks something like this:

if tdl dl --continue --rewrite-ext --dir "$output_dir" "$cmd" "$cmd_arg" --skip-same \
    --template '{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileCaption `\n` `_` `/` `_` `\\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}_{{ replace .FileName `/` `_` `\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}'; then
    break
fi
# tdl failed. Try again with shorter name. 
if tdl dl --continue --rewrite-ext --dir "$output_dir" "$cmd" "$cmd_arg" --skip-same \
    --template '{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileCaption `\n` `_` `/` `_` `\\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}'; then
    break
fi
# Try again with even shorter name.
if tdl dl --continue --rewrite-ext --dir "$output_dir" "$cmd" "$cmd_arg" --skip-same \
    --template '{{ .DialogID }}_{{ .MessageID }}_{{ replace .FileName `/` `_` `\` `_` `:` `_` `*` `_` `?` `_` `<` `_` `>` `_` `|` `_` ` ` `_` }}'; then
    break
fi
# Last attempt.
if tdl dl --continue --rewrite-ext --dir "$output_dir" "$cmd" "$cmd_arg" --skip-same \
    --template '{{ .DialogID }}_{{ .MessageID }}'; then
    break
fi
>&2 echo "Failed to download media"
return 1

Which is fairly long and isn't very elegant. I also have to download one file at a time because the whole batch will fail even if only one file is having a long name, and I want most of the file to have a descriptive name instead of plain DialogID_MessageID.

Workarounds

I've made a small patch to add Truncate() and Pad() function because I need it desperately:

diff --git a/pkg/tplfunc/string.go b/pkg/tplfunc/string.go
index a4f9a93..514907d 100644
--- a/pkg/tplfunc/string.go
+++ b/pkg/tplfunc/string.go
@@ -3,6 +3,7 @@ package tplfunc
 import (
 	"strings"
 	"text/template"
+	"fmt"
 
 	"github.com/iancoleman/strcase"
 )
@@ -11,6 +12,7 @@ var String = []Func{
 	Repeat(), Replace(),
 	ToUpper(), ToLower(),
 	SnakeCase(), CamelCase(), KebabCase(),
+	Truncate(), Pad(),
 }
 
 func Repeat() Func {
@@ -64,3 +66,27 @@ func KebabCase() Func {
 		}
 	}
 }
+
+func Truncate() Func {
+	return func(funcMap template.FuncMap) {
+		funcMap["truncate"] = func(s string, n int) string {
+			// Handle Unicode correctly - https://stackoverflow.com/a/76502408
+			trunc := ""
+			for index, val := range s {
+				if index >= n {
+					break
+				}
+				trunc += string(val)
+			}
+			return trunc
+		}
+	}
+}
+
+func Pad() Func {
+	return func(funcMap template.FuncMap) {
+		funcMap["pad"] = func(n, l int) string {
+			return fmt.Sprintf("%*d", l, n)
+		}
+	}
+}

SafeString function is nice-to-have, but I think people should live OK without it :)

A possible solution to implement Truncate outside tdl using pure shell code is to write a jq spaghetti code to find all media with longer-than-legal filenames. It's going to be a bit fragile and really complicated though. Please paste the code below if anyone have the dare to write it.

hafeoz avatar Jul 10 '24 14:07 hafeoz