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)