| 41 | | |
| 42 | | cache = cache_file |
| 43 | | if @nocache || !File.exists?(cache) || age(cache) > (@age || MaxAge) |
| 44 | | @data = fetch |
| 45 | | File.open(cache, 'w') { |f| Marshal.dump(@data, f) } unless @nocache |
| 46 | | else |
| 47 | | @data = File.open(cache) { |f| Marshal.load(f) } |
| 48 | | end |
| 49 | | end |
| 50 | | |
| 51 | | def popularity(pkgname); @data[pkgname] || 0; end |
| 52 | | end |
| 53 | | |
| 54 | | class Package |
| 55 | | attr_reader :name, :popularity, :line |
| 56 | | def initialize(popcon, name, line) |
| 57 | | @name = name |
| 58 | | @popularity = popcon.popularity(name) |
| 59 | | @line = line || '' |
| 60 | | end |
| 61 | | |
| 62 | | def to_s; "%5d %s%s" % [popularity, name, line]; end |
| 63 | | |
| 64 | | class UnparseableLine < Exception; end |
| 65 | | def self.from_line(popcon, l) |
| 66 | | md = l.match(/^(\S+)(.*)$/) or raise UnparseableLine.new(l) |
| 67 | | return new(popcon, *md.captures) |
| 68 | | end |
| 69 | | |
| 70 | | def self.from_enums(popcon, *enums) |
| 71 | | ret = [] |
| 72 | | enums.each do |e| |
| 73 | | next if e.respond_to?(:tty?) && e.tty? |
| 74 | | e.each { |x| ret << from_line(popcon, x) } |
| 75 | | end |
| 76 | | return ret |
| 77 | | end |
| 78 | | def self.print_all(list) |
| 79 | | list.sort_by { |x| -x.popularity }.each { |x| puts x.to_s } |
| 80 | | end |
| 81 | | def self.print_enums(popcon, *enums) |
| 82 | | print_all(from_enums(popcon, *enums)) |
| 83 | | end |
| 84 | | end |
| 85 | | |
| 86 | | require 'optparse' |
| 87 | | |
| 88 | | options = {} |
| 89 | | OptionParser.new do |op| |
| 90 | | op.banner = <<USAGE |
| 91 | | Usage: popcon [OPTIONS] PKG1 PKG2 ... |
| 92 | | stdin | popcon [OPTIONS] |
| 93 | | Sort packages by popularity contest data. |
| 94 | | USAGE |
| 95 | | op.separator '' |
| 96 | | |
| 97 | | op.on('-c', '--cache FILE', 'Cache data in this file') \ |
| 98 | | { |x| options[:cache] = x } |
| 99 | | op.on('-n', '--nocache', "Don't use a cache") \ |
| 100 | | { |x| options[:nocache] = x } |
| 101 | | op.on('-a', '--age DAYS', 'Refresh cache if older than this') \ |
| 102 | | { |x| options[:age] = x.to_i } |
| 103 | | |
| 104 | | op.on('-d', '--distro DISTRO', 'Use popularity data from this distro') \ |
| 105 | | { |x| options[:distro] = x } |
| 106 | | op.on('-u', '--uri URI', 'Use popularity data from this URI') \ |
| 107 | | { |x| options[:uri] = x } |
| 108 | | end.parse! |
| 109 | | |
| 110 | | popcon = Popcon.new(options) |
| 111 | | Package.print_enums(popcon, ARGV, STDIN) |
| | 115 | end |
| | 116 | |
| | 117 | def debtags; @debtags ||= Debtags.new; end |
| | 118 | def popcon; @popcon ||= Popcon.new; end |
| | 119 | |
| | 120 | def package(name); Package.new(self, name); end |
| | 121 | def tagged_with(*tags) |
| | 122 | names = debtags.tagged_with(tags.shift) |
| | 123 | tags.each { |t| names = names & debtags.tagged_with(t) } |
| | 124 | names.map { |n| package(n) } |
| | 125 | end |
| | 126 | end |
| | 127 | |
| | 128 | require 'delegate' |
| | 129 | class AnnotatedPackage < DelegateClass(Package) |
| | 130 | attr_reader :annotation |
| | 131 | def initialize(pkg, annotation) |
| | 132 | super(pkg) |
| | 133 | @annotation = annotation || '' |
| | 134 | end |
| | 135 | |
| | 136 | class DB < Package::DB |
| | 137 | class UnparseableLine < Exception; end |
| | 138 | def package(name, ann = nil) |
| | 139 | AnnotatedPackage.new(super(name), ann) |
| | 140 | end |
| | 141 | |
| | 142 | def package_from_line(line) |
| | 143 | md = line.match(/^(\S+)(.*)$/) or return nil |
| | 144 | return package(*md.captures) |
| | 145 | end |
| | 146 | |
| | 147 | def packages_from_input(*enums) |
| | 148 | ret = [] |
| | 149 | enums.each do |e| |
| | 150 | next if e.respond_to?(:tty?) && e.tty? |
| | 151 | e.each do |line| |
| | 152 | p = package_from_line(line) or next |
| | 153 | ret << p |
| | 154 | end |
| | 155 | end |
| | 156 | return ret |
| | 157 | end |
| | 158 | end |
| | 159 | end |
| | 160 | |
| | 161 | module DebianPackageScript |
| | 162 | def self.unindent(s) |
| | 163 | ind = s.match(/\A[\t ]*/)[0] |
| | 164 | s.gsub(Regexp.new("^#{Regexp.escape(ind)}"), '') |
| | 165 | end |
| | 166 | |
| | 167 | def self.db_from_opts(cache_class, usage, opts = {}, &extra_cli_options) |
| | 168 | require 'optparse' |
| | 169 | options = {} |
| | 170 | OptionParser.new do |parser| |
| | 171 | parser.banner = usage |
| | 172 | parser.separator '' |
| | 173 | |
| | 174 | parser.on('-c', '--cache FILE', 'Cache data in this file') \ |
| | 175 | { |x| options[:cache] = x } |
| | 176 | parser.on('-n', '--nocache', "Don't use a cache") \ |
| | 177 | { |x| options[:nocache] = x } |
| | 178 | parser.on('-a', '--age DAYS', 'Refresh cache if older than this') \ |
| | 179 | { |x| options[:age] = x.to_i } |
| | 180 | parser.on('-u', '--uri URI', 'Fetch data from this URI') \ |
| | 181 | { |x| options[:uri] = x } |
| | 182 | |
| | 183 | extra_cli_options[parser, options] if extra_cli_options |
| | 184 | end.parse! |
| | 185 | |
| | 186 | cache = cache_class.new(options) |
| | 187 | db_class = opts[:db_class] || Package::DB |
| | 188 | return db_class.new(cache_class.name.downcase => cache) |
| | 189 | end |
| | 190 | |
| | 191 | # Popcon annotator |
| | 192 | def self.do_popcon |
| | 193 | usage = unindent(<<-USAGE) |
| | 194 | Usage: popcon [OPTIONS] PKG1 PKG2 ... |
| | 195 | stdin | popcon [OPTIONS] |
| | 196 | Sort packages by popularity contest data. |
| | 197 | USAGE |
| | 198 | pkgs = db_from_opts(Popcon, usage, |
| | 199 | :db_class => AnnotatedPackage::DB) do |p, o| |
| | 200 | p.on('-d', '--distro DISTRO', |
| | 201 | 'Use popularity data from this distro') { |x| o[:distro] = x } |
| | 202 | end |
| | 203 | pkgs.packages_from_input(ARGV, STDIN). |
| | 204 | sort_by { |p| -p.popularity }.each do |p| |
| | 205 | puts "%6d %s%s" % [p.popularity, p.name, p.annotation] |
| | 206 | end |
| | 207 | end |
| | 208 | |
| | 209 | # Ersatz debtags |
| | 210 | def self.do_edebtags |
| | 211 | usage = unindent(<<-USAGE) |
| | 212 | Usage: edebtags [OPTIONS] TAG1 TAG2 ... |
| | 213 | Get a list of Debian packages corresponding to the given tags. |
| | 214 | USAGE |
| | 215 | pkgs = db_from_opts(Debtags, usage) |
| | 216 | pkgs.tagged_with(*ARGV).map { |p| p.name }.sort.each { |n| puts n } |
| | 217 | end |
| | 218 | |
| | 219 | def self.run |
| | 220 | case File.basename($0) |
| | 221 | when 'edebtags': do_edebtags |
| | 222 | else do_popcon |
| | 223 | end |
| | 224 | end |
| | 225 | end |
| | 226 | |
| | 227 | DebianPackageScript.run if $0 == __FILE__ |