tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

mpd_report_monthly (3008B)


      1 #!/usr/bin/env ruby
      2 # frozen_string_literal: true
      3 
      4 require "date"
      5 require "open3"
      6 require "optparse"
      7 require "tempfile"
      8 
      9 REPORT_COUNT = 4
     10 MANAGED_MPD_OPTIONS = %w[-i --indexed -o --output -w --window].freeze
     11 
     12 def main(arguments)
     13   options = {output_directory: "."}
     14   parser = OptionParser.new do |opts|
     15     opts.banner = "Usage: #{opts.program_name} [-o DIRECTORY] [MONTH [YEAR]] [-- MPD_OPTION ...]"
     16     opts.on("-o", "--output-dir DIRECTORY", "write reports beneath DIRECTORY") do |directory|
     17       options[:output_directory] = directory
     18     end
     19     opts.on("-h", "--help", "show this help") do
     20       puts opts
     21       return 0
     22     end
     23   end
     24 
     25   separator = arguments.index("--")
     26   mpd_options = separator ? arguments.slice!((separator + 1)..) : []
     27   arguments.pop if arguments.last == "--"
     28   parser.parse!(arguments)
     29 
     30   month = (v = arguments.shift) ? Integer(v, 10) : Date.today.month
     31   year = (v = arguments.shift) ? Integer(v, 10) : Date.today.year
     32   raise OptionParser::InvalidArgument, arguments.first unless arguments.empty?
     33 
     34   first = Date.new(year, month, 1)
     35   output_directory = options[:output_directory]
     36   raise OptionParser::InvalidArgument, "not a directory: #{output_directory}" unless Dir.exist?(output_directory)
     37 
     38   reserved = mpd_options.find do |option|
     39     MANAGED_MPD_OPTIONS.include?(option) || option.start_with?("--window=")
     40   end
     41   raise OptionParser::InvalidArgument, "#{reserved} is managed by this helper" if reserved
     42 
     43   command = ENV["MPD_REPORT"] || begin
     44     sibling = File.join(__dir__, "mpd_report")
     45     File.executable?(sibling) ? sibling : "mpd_report"
     46   end
     47 
     48   windows = REPORT_COUNT.times.flat_map do |week|
     49     dates = [first + week * 7, first + (week + 1) * 7]
     50     epochs = dates.map { |date| Time.local(date.year, date.month, date.day).to_i }
     51     ["--window", epochs.join(",")]
     52   end
     53 
     54   stdout, stderr, status = Open3.capture3(command, *mpd_options, "--output", "-", *windows)
     55   warn stderr unless stderr.empty?
     56   return status.exitstatus || 1 unless status.success?
     57 
     58   reports = Array.new(REPORT_COUNT) { [] }
     59   stdout.each_line do |line|
     60     index, uri = line.chomp.split("\t", 2)
     61     unless index&.match?(/\A[0-3]\z/) && uri
     62       warn "#{parser.program_name}: malformed indexed output: #{line.chomp}"
     63       return 1
     64     end
     65     reports[index.to_i] << uri
     66   end
     67 
     68   reports.each_with_index do |uris, index|
     69     name = format("%d_%02d_%04d.txt", index + 1, month, year)
     70     target = File.join(output_directory, name)
     71     Tempfile.create([".#{name}.", ".tmp"], output_directory) do |file|
     72       uris.each { |uri| file.puts(uri) }
     73       file.flush
     74       file.fsync
     75       file.close
     76       File.rename(file.path, target)
     77     end
     78     puts target
     79   end
     80   0
     81 rescue OptionParser::ParseError, ArgumentError => error
     82   warn "#{parser.program_name}: #{error.message}"
     83   warn parser
     84   2
     85 rescue Errno::ENOENT => error
     86   warn "#{parser.program_name}: #{error.message}"
     87   127
     88 rescue SystemCallError => error
     89   warn "#{parser.program_name}: #{error.message}"
     90   1
     91 end
     92 
     93 exit main(ARGV)