| | 100 | def parse_name(name): |
|---|
| | 101 | coax = lambda x: int(x.strip('-_cv')) |
|---|
| | 102 | v = vol.search(name) |
|---|
| | 103 | c = chap.search(name) |
|---|
| | 104 | ret = {'vol' : None, 'chap' : None} |
|---|
| | 105 | if v: ret['vol'] = coax(v.group('vol')) |
|---|
| | 106 | if c: ret['chap'] = coax(c.group('chap')) |
|---|
| | 107 | return ret |
|---|
| | 108 | |
|---|
| | 109 | class FilterFormatExc(Exception): pass |
|---|
| | 110 | |
|---|
| | 111 | def num_filter(pstr): |
|---|
| | 112 | stripped = pstr.strip('!<>') |
|---|
| | 113 | pl = numToList(stripped) |
|---|
| | 114 | if len(pl) > 1: |
|---|
| | 115 | # we are dealing with a range here; only '!' or none are legal |
|---|
| | 116 | if pstr.startswith('!'): |
|---|
| | 117 | return lambda x: int(x) not in pl |
|---|
| | 118 | elif pstr != stripped: |
|---|
| | 119 | raise FilterFormatExc('Only "!" is allowed with multiple numbers') |
|---|
| | 120 | return lambda x: int(x) in pl |
|---|
| | 121 | # okay, there is only 1 number, all '!<>' are legal |
|---|
| | 122 | # if we don't use '!<>' |
|---|
| | 123 | if stripped == pstr: |
|---|
| | 124 | return lambda x: int(stripped) == int(x) |
|---|
| | 125 | elif pstr.startswith('!'): |
|---|
| | 126 | return lambda x: int(x) != int(stripped) |
|---|
| | 127 | elif pstr.startswith('>'): |
|---|
| | 128 | return lambda x: int(x) > int(stripped) |
|---|
| | 129 | elif pstr.startswith('<'): |
|---|
| | 130 | return lambda x: int(x) < int(stripped) |
|---|
| | 131 | |
|---|
| | 132 | raise FilterFormatExc('Only "!><" are allowed with a single number') |
|---|
| | 133 | |
|---|
| | 134 | def build_filter(pstr, num=True): |
|---|
| | 135 | if num: return num_filter(pstr) |
|---|
| | 136 | return lambda x: pstr.lower() not in x.lower() |
|---|
| | 137 | |
|---|
| | 138 | def pack_desc(pack): |
|---|
| | 139 | 'turn a pack into its desc string' |
|---|
| | 140 | desc = ' pack #%s on %s (%s gets @ %s)' % (pack['pack'], pack['bot'], pack['gets'], pack['size']) |
|---|
| | 141 | name = pack['name'].ljust(40) |
|---|
| | 142 | return name + desc |
|---|
| | 143 | |
|---|
| | 144 | def apply_filter(filt, x): |
|---|
| | 145 | try: return filt(x) |
|---|
| | 146 | except: return True |
|---|
| | 147 | |
|---|
| | 148 | def main(): |
|---|
| | 149 | global cookie |
|---|
| | 150 | sf, bf, vf, cf = False, False, False, False |
|---|
| | 151 | opts, querystr = handleOptions() |
|---|
| | 152 | if opts.string: |
|---|
| | 153 | sf = build_filter(opts.string, False) |
|---|
| | 154 | if opts.bot: |
|---|
| | 155 | _bf = build_filter(opts.bot, False) |
|---|
| | 156 | bf = lambda x: not _bf(x) |
|---|
| | 157 | if opts.vol: |
|---|
| | 158 | dbg('Filter: %s' % opts.vol) |
|---|
| | 159 | try: vf = build_filter(opts.vol) |
|---|
| | 160 | except FilterFormatExc, ex: |
|---|
| | 161 | print "Error: %s" % ex |
|---|
| | 162 | sys.exit(-1) |
|---|
| | 163 | if opts.chap: |
|---|
| | 164 | try: cf = build_filter(opts.chap) |
|---|
| | 165 | except FilterFormatExc, ex: |
|---|
| | 166 | print "Error: %s" % ex |
|---|
| | 167 | sys.exit(-1) |
|---|
| | 168 | dbg('query: <%s>' % querystr) |
|---|
| | 169 | cookie = get_cookie() # yum |
|---|
| | 170 | dbg('cookie: <%r>' % cookie) |
|---|
| | 171 | results = do_query(querystr) |
|---|
| | 172 | if sf: results = filter(lambda x: apply_filter(sf, pack_desc(x)), results) |
|---|
| | 173 | if bf: results = filter(lambda x: apply_filter(bf, x['bot']), results) |
|---|
| | 174 | if vf: results = filter(lambda x: apply_filter(vf, parse_name(x['name'])['vol']), results) |
|---|
| | 175 | if cf: results = filter(lambda x: apply_filter(cf, parse_name(x['name'])['chap']), results) |
|---|
| | 176 | if opts.onlyvols: |
|---|
| | 177 | results = filter(lambda x: parse_name(x['name'])['vol'] != None, results) |
|---|
| | 178 | if opts.onlychaps: |
|---|
| | 179 | results = filter(lambda x: parse_name(x['name'])['chap'] != None, results) |
|---|
| | 180 | for item in results: |
|---|
| | 181 | print pack_desc(item) |
|---|
| | 182 | |
|---|
| | 183 | if opts.xdccq: |
|---|
| | 184 | bots = set() |
|---|
| | 185 | for item in results: bots.add(item['bot']) |
|---|
| | 186 | for bot in bots: |
|---|
| | 187 | packs = [p['pack'] for p in results if p['bot'] == bot] |
|---|
| | 188 | print 'xdccq get %s %s' % (bot, ','.join(map(str, packs))) |
|---|
| | 189 | |
|---|
| | 190 | |
|---|
| | 191 | def handleOptions(): |
|---|
| | 192 | from optparse import OptionParser, OptionGroup |
|---|
| | 193 | |
|---|
| | 194 | parser = OptionParser(usage='%prog [options] Query', version=VERSION) |
|---|
| | 195 | parser.add_option('-x', '--xdccq', action='store_true', dest='xdccq', help='output for use in xdccq') |
|---|
| | 196 | |
|---|
| | 197 | group_filter = OptionGroup(parser, "Filtering options") |
|---|
| | 198 | group_filter.add_option('-s', '--string', action='store', dest='string', help='filter out by string match') |
|---|
| | 199 | group_filter.add_option('-b', '--bot', action='store', dest='bot', help='filter bots') |
|---|
| | 200 | group_filter.add_option('-v', '--vol', action='store', dest='vol', help='vilter volumes') |
|---|
| | 201 | group_filter.add_option('-c', '--chap', action='store', dest='chap', help='filter chapters') |
|---|
| | 202 | group_filter.add_option('-V', '--vols-only', action='store_true', dest='onlyvols', help='only show volumes') |
|---|
| | 203 | group_filter.add_option('-C', '--chaps-only', action='store_true', dest='onlychaps', help='only show chapters') |
|---|
| | 204 | |
|---|
| | 205 | group_debug = OptionGroup(parser, "Debugging options") |
|---|
| | 206 | group_debug.add_option('', '--test', action='store_true', dest='test', help='run unit tests') |
|---|
| | 207 | |
|---|
| | 208 | parser.add_option_group(group_filter) |
|---|
| | 209 | parser.add_option_group(group_debug) |
|---|
| | 210 | |
|---|
| | 211 | options, args = parser.parse_args() |
|---|
| | 212 | querystr = ' '.join(args).strip() |
|---|
| | 213 | |
|---|
| | 214 | # if we have requested the unit test, run it and exit |
|---|
| | 215 | if options.test: |
|---|
| | 216 | for arg in sys.argv: sys.argv.remove(arg) |
|---|
| | 217 | dotests() |
|---|
| | 218 | |
|---|
| | 219 | # if we lack a query string, print the usage and exit |
|---|
| | 220 | if not querystr: |
|---|
| | 221 | parser.print_usage() |
|---|
| | 222 | sys.exit(-1) |
|---|
| | 223 | |
|---|
| | 224 | return options, querystr |
|---|
| | 225 | |
|---|
| | 226 | ### Testing ### |
|---|
| | 227 | |
|---|
| | 228 | testStrings = [ |
|---|
| | 229 | ("Hajime_no_Ippo_v70_c02[SC].zip", 70, 2), |
|---|
| | 230 | ("Migiko_Nippon_Ichi[solaris-svu].zip", None, None), |
|---|
| | 231 | ("Tsuki_no_Shippo_v04_c23[ShoujoCrusade][e-scans].zip", 4, 23), |
|---|
| | 232 | ("One_Piece_c477[FH].zip", None, 477), |
|---|
| | 233 | ("One_Piece_ c479[OPHQ].zip", None, 479), |
|---|
| | 234 | ("One_Piece-442[Null][KEFI].zip", None, 442) |
|---|
| | 235 | ] |
|---|
| | 236 | |
|---|
| | 237 | testNumtolistStrings = [ |
|---|
| | 238 | ("1", [1]), |
|---|
| | 239 | ("1,2", [1,2]), |
|---|
| | 240 | ("1,3-9,11", [1,3,4,5,6,7,8,9,11]) |
|---|
| | 241 | ] |
|---|
| | 242 | |
|---|
| | 243 | testFilters = [ |
|---|
| | 244 | ("!234", [1,2,234,5], [1,2,5]), |
|---|
| | 245 | ("<40", [38,39,40,41,42], [38,39]), |
|---|
| | 246 | (">10", [7,8,9,10,11,12], [11,12]), |
|---|
| | 247 | ("!5-8", [4,5,6,7,8,9], [4,9]) |
|---|
| | 248 | ] |
|---|
| | 249 | |
|---|
| | 250 | testStrFilter = [ |
|---|
| | 251 | ("fo", ['foo','faz','feo','fo'], ['faz','feo']) |
|---|
| | 252 | ] |
|---|
| | 253 | |
|---|
| | 254 | # yes, negative numbers are not allowed |
|---|
| | 255 | testNumToListException = ["10,5-2", "11,G,3", "11,-3,-1"] |
|---|
| | 256 | |
|---|
| | 257 | def listcmp(l,r): |
|---|
| | 258 | if len(l) != len(r): return False |
|---|
| | 259 | for item in zip(l,r): |
|---|
| | 260 | if item[0] != item[1]: return False |
|---|
| | 261 | return True |
|---|
| | 262 | |
|---|
| | 263 | class TestRegexMatching(unittest.TestCase): |
|---|
| | 264 | def testMatching(self): |
|---|
| | 265 | for s in testStrings: |
|---|
| | 266 | m = parse_name(s[0]) |
|---|
| | 267 | self.assertEqual(m['vol'], s[1]) |
|---|
| | 268 | self.assertEqual(m['chap'], s[2]) |
|---|
| | 269 | |
|---|
| | 270 | #TODO: test that bad patterns fail properly |
|---|
| | 271 | class TestFilterApplication(unittest.TestCase): |
|---|
| | 272 | def testNumberFiltering(self): |
|---|
| | 273 | for s in testFilters: |
|---|
| | 274 | filt = build_filter(s[0], True) |
|---|
| | 275 | l = filter(filt, s[1]) |
|---|
| | 276 | self.assertEquals(True, listcmp(l, s[2])) |
|---|
| | 277 | |
|---|
| | 278 | def testStrFiltering(self): |
|---|
| | 279 | for s in testStrFilter: |
|---|
| | 280 | filt = build_filter(s[0], False) |
|---|
| | 281 | l = filter(filt, s[1]) |
|---|
| | 282 | self.assertEquals(True, listcmp(l, s[2])) |
|---|
| | 283 | |
|---|
| | 284 | |
|---|
| | 285 | class TestNumtolist(unittest.TestCase): |
|---|
| | 286 | def testNumtolist(self): |
|---|
| | 287 | for s in testNumtolistStrings: |
|---|
| | 288 | l = numToList(s[0]) |
|---|
| | 289 | self.assertEquals(True, listcmp(l, s[1])) |
|---|
| | 290 | |
|---|
| | 291 | def testNumtolistExc(self): |
|---|
| | 292 | s = testNumToListException |
|---|
| | 293 | self.assertRaises(ValueError, numToList, s[0]) |
|---|
| | 294 | self.assertRaises(ValueError, numToList, s[1]) |
|---|
| | 295 | self.assertRaises(ValueError, numToList, s[2]) |
|---|
| | 296 | |
|---|
| | 297 | def dotests(): |
|---|
| | 298 | unittest.main() |
|---|
| | 299 | sys.exit(0) |
|---|
| | 300 | |
|---|