mkm3u: Produce makefile fragments for tracking dependencies.
authorMark Wooding <mdw@distorted.org.uk>
Tue, 22 Mar 2022 01:25:53 +0000 (01:25 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Tue, 22 Mar 2022 01:25:53 +0000 (01:25 +0000)
This is extra annoying because `make' can't handle spaces, so I must do
this the hard way.

.gitignore
Makefile
mkm3u

index 0940b00..c1a6939 100644 (file)
@@ -1,3 +1,4 @@
+*.dep
 *.m3u8
 *.m3u8.new
 !/ref/*.m3u8
index 33136ba..316f3ea 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,9 @@ all:
 clean::; rm -f $(CLEANFILES)
 realclean::; rm -f $(REALCLEANFILES)
 force:
-.PHONY: all clean
+.PHONY: all clean force
 .SECONDEXPANSION: # not sorry
+SHELL = bash
 
 V                      ?= 0
 V_AT                    = $(V_AT_$V)
@@ -178,7 +179,9 @@ $(call declare-playlist, drwho-war-games, D/Doctor Who/S06E07 ZZ. The War Games)
 $(call declare-playlist, drwho-silurians, D/Doctor Who/S07E02 BBB. Doctor Who and the Silurians)
 
 M3US                    = $(addsuffix .m3u8,$(PLAYLISTS))
+DEPFILES               += $(addsuffix .dep,$(PLAYLISTS))
 TARGETS                        += $(M3US)
+CLEANFILES             += $(DEPFILES)
 
 CLEANFILES             += mkm3u.cache-stamp
 mkm3u.cache-stamp:
@@ -190,7 +193,7 @@ mkm3u.cache-stamp:
 CLEANFILES             += *.m3u8.new
 $(M3US): %.m3u8: $$($$*_EPLS) mkm3u mkm3u.cache-stamp
        $(call v-tag,MKM3U)./mkm3u $(MKM3UFLAGS) $($*_MKM3UFLAGS) \
-               "$<" >"$@.new" && mv "$@.new" "$@"
+               -M$*.dep -O$@ -o"$@.new" "$<" && mv "$@.new" "$@"
 
 CHECKS                  = $(foreach p,$(PLAYLISTS), check/$p)
 check: $(CHECKS)
@@ -236,6 +239,14 @@ $(FORCE_SAVE): force-save/%: %.m3u8
                fi
 .PHONY: save $(SAVE)
 
+comma                   = ,
+check-deps              = $(shell if [ -f $1 ]; then for i in $2; do \
+                                 if ! [ "$$i" -ot $1 ]; then \
+                                   echo force; break; \
+                                 fi; \
+                               done; fi)
+-include $(DEPFILES)
+
 all: $(TARGETS)
 
 p:; : $p
diff --git a/mkm3u b/mkm3u
index 9053c5f..7141a32 100755 (executable)
--- a/mkm3u
+++ b/mkm3u
@@ -512,6 +512,7 @@ class Playlist (object):
     if me.episodes:
       me.seasons.append(me.episodes)
       me.episodes = []
+
   def write(me, f):
     f.write("#EXTM3U\n")
     for season in me.seasons:
@@ -530,6 +531,19 @@ class Playlist (object):
             f.write("#EXTINF:%d,,%s: %s\n%s\n" %
                     (ch.duration, label, ch.title, ch.url))
 
+  def write_deps(me, f, out):
+    deps = set()
+    for season in me.seasons:
+      for ep in season: deps.add(ep.source.fn)
+    f.write("### -*-makefile-*-\n")
+    f.write("%s: $(call check-deps, %s," % (out, out))
+    for dep in sorted(deps):
+      f.write(" \\\n\t'%s'" %
+                OS.path.join(ROOT, dep)
+                  .replace(",", "$(comma)")
+                  .replace("'", "'\\''"))
+    f.write(")\n")
+
 DEFAULT_EXPVAR = 0.05
 R_DURMULT = RX.compile(r""" ^
         (\d+ (?: \. \d+)?) x
@@ -809,28 +823,41 @@ class EpisodeListParser (object):
     return me._pl
 
 op = OP.OptionParser \
-  (usage = "%prog [-c] [-d CACHE] [-s SERIES] EPLS\n"
+  (usage = "%prog [-c] [-M DEPS] [-d CACHE] [-o OUT] [-s SERIES] EPLS\n"
            "%prog -i -d CACHE",
    description = "Generate M3U playlists from an episode list.")
+op.add_option("-M", "--make-deps", metavar = "DEPS",
+              dest = "deps", type = "str", default = None,
+              help = "Write a `make' fragment for dependencies")
 op.add_option("-c", "--chapters",
               dest = "chaptersp", action = "store_true", default = False,
               help = "Output individual chapter names")
 op.add_option("-i", "--init-db",
               dest = "initdbp", action = "store_true", default = False,
               help = "Initialize the database")
-op.add_option("-d", "--database",
+op.add_option("-d", "--database", metavar = "CACHE",
               dest = "database", type = "str", default = None,
               help = "Set filename for cache database")
-op.add_option("-s", "--series",
+op.add_option("-o", "--output", metavar = "OUT",
+              dest = "output", type = "str", default = None,
+              help = "Write output playlist to OUT")
+op.add_option("-O", "--fake-output", metavar = "OUT",
+              dest = "fakeout", type = "str", default = None,
+              help = "Pretend output goes to OUT for purposes of `-M'")
+op.add_option("-s", "--series", metavar = "SERIES",
               dest = "series", type = "str", default = None,
               help = "Output only the listed SERIES (comma-separated)")
 try:
   opts, argv = op.parse_args()
+
   if opts.initdbp:
     if opts.chaptersp or opts.series is not None or \
+       opts.output is not None or opts.deps is not None or \
+       opts.fakeout is not None or \
        opts.database is None or len(argv):
       op.print_usage(file = SYS.stderr); SYS.exit(2)
     setup_db(opts.database)
+
   else:
     if len(argv) != 1: op.print_usage(file = SYS.stderr); SYS.exit(2)
     if opts.database is not None: init_db(opts.database)
@@ -839,10 +866,29 @@ try:
     else:
       series_wanted = set()
       for name in opts.series.split(","): series_wanted.add(name)
+    if opts.deps is not None:
+      if (opts.output is None or opts.output == "-") and opts.fakeout is None:
+        raise ExpectedError("can't write dep fragment without output file")
+      if opts.fakeout is None: opts.fakeout = opts.output
+    else:
+      if opts.fakeout is not None:
+        raise ExpectedError("fake output set but no dep fragment")
+
     ep = EpisodeListParser(series_wanted, opts.chaptersp)
     ep.parse_file(argv[0])
     pl = ep.done()
-    pl.write(SYS.stdout)
+
+    if opts.output is None or opts.output == "-":
+      pl.write(SYS.stdout)
+    else:
+      with open(opts.output, "w") as f: pl.write(f)
+
+    if opts.deps:
+      if opts.deps == "-":
+        pl.write_deps(SYS.stdout, opts.fakeout)
+      else:
+        with open(opts.deps, "w") as f: pl.write_deps(f, opts.fakeout)
+
 except (ExpectedError, IOError, OSError) as e:
   LOC.report(e)
   SYS.exit(2)