tools

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

mpd_report_daily (3068B)


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