| OLD | NEW | 
|    1 #!/usr/bin/env python |    1 #!/usr/bin/env python | 
|    2  |    2  | 
|    3 # This file is part of the Adblock Plus web scripts, |    3 # This file is part of the Adblock Plus web scripts, | 
|    4 # Copyright (C) 2006-2016 Eyeo GmbH |    4 # Copyright (C) 2006-2016 Eyeo GmbH | 
|    5 # |    5 # | 
|    6 # Adblock Plus is free software: you can redistribute it and/or modify |    6 # Adblock Plus is free software: you can redistribute it and/or modify | 
|    7 # it under the terms of the GNU General Public License version 3 as |    7 # it under the terms of the GNU General Public License version 3 as | 
|    8 # published by the Free Software Foundation. |    8 # published by the Free Software Foundation. | 
|    9 # |    9 # | 
|   10 # Adblock Plus is distributed in the hope that it will be useful, |   10 # Adblock Plus is distributed in the hope that it will be useful, | 
| (...skipping 10 matching lines...) Expand all  Loading... | 
|   21 import subprocess |   21 import subprocess | 
|   22 import urllib2 |   22 import urllib2 | 
|   23 import time |   23 import time | 
|   24 import traceback |   24 import traceback | 
|   25 import codecs |   25 import codecs | 
|   26 import hashlib |   26 import hashlib | 
|   27 import base64 |   27 import base64 | 
|   28 import tempfile |   28 import tempfile | 
|   29 from getopt import getopt, GetoptError |   29 from getopt import getopt, GetoptError | 
|   30  |   30  | 
|   31 accepted_extensions = set([".txt"]) |   31 accepted_extensions = set(['.txt']) | 
|   32 ignore = set(["Apache.txt", "CC-BY-SA.txt", "GPL.txt", "MPL.txt"]) |   32 ignore = set(['Apache.txt', 'CC-BY-SA.txt', 'GPL.txt', 'MPL.txt']) | 
|   33 verbatim = set(["COPYING"]) |   33 verbatim = set(['COPYING']) | 
|   34  |   34  | 
|   35  |   35  | 
|   36 def combine_subscriptions(sources, target_dir, timeout=30, tempdir=None): |   36 def combine_subscriptions(sources, target_dir, timeout=30, tempdir=None): | 
|   37     if not os.path.exists(target_dir): |   37     if not os.path.exists(target_dir): | 
|   38         os.makedirs(target_dir, 0755) |   38         os.makedirs(target_dir, 0755) | 
|   39  |   39  | 
|   40     def save_file(filename, data): |   40     def save_file(filename, data): | 
|   41         handle = tempfile.NamedTemporaryFile(mode="wb", dir=tempdir, delete=Fals
     e) |   41         handle = tempfile.NamedTemporaryFile(mode='wb', dir=tempdir, delete=Fals
     e) | 
|   42         handle.write(data.encode("utf-8")) |   42         handle.write(data.encode('utf-8')) | 
|   43         handle.close() |   43         handle.close() | 
|   44  |   44  | 
|   45         if hasattr(os, "chmod"): |   45         if hasattr(os, 'chmod'): | 
|   46             os.chmod(handle.name, 0644) |   46             os.chmod(handle.name, 0644) | 
|   47  |   47  | 
|   48         try: |   48         try: | 
|   49             subprocess.check_output(["7za", "a", "-tgzip", "-mx=9", "-bd", "-mpa
     ss=5", handle.name + ".gz", handle.name]) |   49             subprocess.check_output(['7za', 'a', '-tgzip', '-mx=9', '-bd', '-mpa
     ss=5', handle.name + '.gz', handle.name]) | 
|   50         except: |   50         except: | 
|   51             print >>sys.stderr, "Failed to compress file %s. Please ensure that 
     p7zip is installed on the system." % handle.name |   51             print >>sys.stderr, 'Failed to compress file %s. Please ensure that 
     p7zip is installed on the system.' % handle.name | 
|   52  |   52  | 
|   53         path = os.path.join(target_dir, filename) |   53         path = os.path.join(target_dir, filename) | 
|   54         os.rename(handle.name, path) |   54         os.rename(handle.name, path) | 
|   55         os.rename(handle.name + ".gz", path + ".gz") |   55         os.rename(handle.name + '.gz', path + '.gz') | 
|   56  |   56  | 
|   57     known = set() |   57     known = set() | 
|   58     for source_name, source in sources.iteritems(): |   58     for source_name, source in sources.iteritems(): | 
|   59         for filename in source.list_top_level_files(): |   59         for filename in source.list_top_level_files(): | 
|   60             if filename in ignore or filename.startswith("."): |   60             if filename in ignore or filename.startswith('.'): | 
|   61                 continue |   61                 continue | 
|   62             if filename in verbatim: |   62             if filename in verbatim: | 
|   63                 process_verbatim_file(source, save_file, filename) |   63                 process_verbatim_file(source, save_file, filename) | 
|   64             elif not os.path.splitext(filename)[1] in accepted_extensions: |   64             elif not os.path.splitext(filename)[1] in accepted_extensions: | 
|   65                 continue |   65                 continue | 
|   66             else: |   66             else: | 
|   67                 try: |   67                 try: | 
|   68                     process_subscription_file(source_name, sources, save_file, f
     ilename, timeout) |   68                     process_subscription_file(source_name, sources, save_file, f
     ilename, timeout) | 
|   69                 except: |   69                 except: | 
|   70                     print >>sys.stderr, 'Error processing subscription file "%s"
     ' % filename |   70                     print >>sys.stderr, 'Error processing subscription file "%s"
     ' % filename | 
|   71                     traceback.print_exc() |   71                     traceback.print_exc() | 
|   72                     print >>sys.stderr |   72                     print >>sys.stderr | 
|   73                 known.add(os.path.splitext(filename)[0] + ".tpl") |   73                 known.add(os.path.splitext(filename)[0] + '.tpl') | 
|   74                 known.add(os.path.splitext(filename)[0] + ".tpl.gz") |   74                 known.add(os.path.splitext(filename)[0] + '.tpl.gz') | 
|   75             known.add(filename) |   75             known.add(filename) | 
|   76             known.add(filename + ".gz") |   76             known.add(filename + '.gz') | 
|   77  |   77  | 
|   78     for filename in os.listdir(target_dir): |   78     for filename in os.listdir(target_dir): | 
|   79         if filename.startswith("."): |   79         if filename.startswith('.'): | 
|   80             continue |   80             continue | 
|   81         if not filename in known: |   81         if not filename in known: | 
|   82             os.remove(os.path.join(target_dir, filename)) |   82             os.remove(os.path.join(target_dir, filename)) | 
|   83  |   83  | 
|   84  |   84  | 
|   85 def process_verbatim_file(source, save_file, filename): |   85 def process_verbatim_file(source, save_file, filename): | 
|   86     save_file(filename, source.read_file(filename)) |   86     save_file(filename, source.read_file(filename)) | 
|   87  |   87  | 
|   88  |   88  | 
|   89 def process_subscription_file(source_name, sources, save_file, filename, timeout
     ): |   89 def process_subscription_file(source_name, sources, save_file, filename, timeout
     ): | 
|   90     source = sources[source_name] |   90     source = sources[source_name] | 
|   91     lines = source.read_file(filename).splitlines() |   91     lines = source.read_file(filename).splitlines() | 
|   92  |   92  | 
|   93     header = "" |   93     header = '' | 
|   94     if len(lines) > 0: |   94     if len(lines) > 0: | 
|   95         header = lines.pop(0) |   95         header = lines.pop(0) | 
|   96     if not re.search(r"\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]", header, re.I): |   96     if not re.search(r'\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\]', header, re.I): | 
|   97         raise Exception("This is not a valid Adblock Plus subscription file.") |   97         raise Exception('This is not a valid Adblock Plus subscription file.') | 
|   98  |   98  | 
|   99     lines = resolve_includes(source_name, sources, lines, timeout) |   99     lines = resolve_includes(source_name, sources, lines, timeout) | 
|  100     seen = set(["checksum", "version"]) |  100     seen = set(['checksum', 'version']) | 
|  101  |  101  | 
|  102     def check_line(line): |  102     def check_line(line): | 
|  103         if line == "": |  103         if line == '': | 
|  104             return False |  104             return False | 
|  105         match = re.search(r"^\s*!\s*(Redirect|Homepage|Title|Checksum|Version|Ex
     pires)\s*:", line, re.M | re.I) |  105         match = re.search(r'^\s*!\s*(Redirect|Homepage|Title|Checksum|Version|Ex
     pires)\s*:', line, re.M | re.I) | 
|  106         if not match: |  106         if not match: | 
|  107             return True |  107             return True | 
|  108         key = match.group(1).lower() |  108         key = match.group(1).lower() | 
|  109         if key in seen: |  109         if key in seen: | 
|  110             return False |  110             return False | 
|  111         seen.add(key) |  111         seen.add(key) | 
|  112         return True |  112         return True | 
|  113     lines = filter(check_line, lines) |  113     lines = filter(check_line, lines) | 
|  114  |  114  | 
|  115     write_tpl(save_file, os.path.splitext(filename)[0] + ".tpl", lines) |  115     write_tpl(save_file, os.path.splitext(filename)[0] + '.tpl', lines) | 
|  116  |  116  | 
|  117     lines.insert(0, "! Version: %s" % time.strftime("%Y%m%d%H%M", time.gmtime())
     ) |  117     lines.insert(0, '! Version: %s' % time.strftime('%Y%m%d%H%M', time.gmtime())
     ) | 
|  118  |  118  | 
|  119     checksum = hashlib.md5() |  119     checksum = hashlib.md5() | 
|  120     checksum.update("\n".join([header] + lines).encode("utf-8")) |  120     checksum.update('\n'.join([header] + lines).encode('utf-8')) | 
|  121     lines.insert(0, "! Checksum: %s" % base64.b64encode(checksum.digest()).rstri
     p("=")) |  121     lines.insert(0, '! Checksum: %s' % base64.b64encode(checksum.digest()).rstri
     p('=')) | 
|  122     lines.insert(0, header) |  122     lines.insert(0, header) | 
|  123     save_file(filename, "\n".join(lines)) |  123     save_file(filename, '\n'.join(lines)) | 
|  124  |  124  | 
|  125  |  125  | 
|  126 def resolve_includes(source_name, sources, lines, timeout, level=0): |  126 def resolve_includes(source_name, sources, lines, timeout, level=0): | 
|  127     if level > 5: |  127     if level > 5: | 
|  128         raise Exception("There are too many nested includes, which is probably t
     he result of a circular reference somewhere.") |  128         raise Exception('There are too many nested includes, which is probably t
     he result of a circular reference somewhere.') | 
|  129  |  129  | 
|  130     result = [] |  130     result = [] | 
|  131     for line in lines: |  131     for line in lines: | 
|  132         match = re.search(r"^\s*%include\s+(.*)%\s*$", line) |  132         match = re.search(r'^\s*%include\s+(.*)%\s*$', line) | 
|  133         if match: |  133         if match: | 
|  134             filename = match.group(1) |  134             filename = match.group(1) | 
|  135             newlines = None |  135             newlines = None | 
|  136             if re.match(r"^https?://", filename): |  136             if re.match(r'^https?://', filename): | 
|  137                 result.append("! *** Fetched from: %s ***" % filename) |  137                 result.append('! *** Fetched from: %s ***' % filename) | 
|  138  |  138  | 
|  139                 for i in range(3): |  139                 for i in range(3): | 
|  140                     try: |  140                     try: | 
|  141                         request = urllib2.urlopen(filename, None, timeout) |  141                         request = urllib2.urlopen(filename, None, timeout) | 
|  142                         data = request.read() |  142                         data = request.read() | 
|  143                         error = None |  143                         error = None | 
|  144                         break |  144                         break | 
|  145                     except urllib2.URLError, e: |  145                     except urllib2.URLError, e: | 
|  146                         error = e |  146                         error = e | 
|  147                         time.sleep(5) |  147                         time.sleep(5) | 
|  148                 if error: |  148                 if error: | 
|  149                     raise error |  149                     raise error | 
|  150  |  150  | 
|  151                 # We should really get the charset from the headers rather than 
     assuming |  151                 # We should really get the charset from the headers rather than 
     assuming | 
|  152                 # that it is UTF-8. However, some of the Google Code mirrors are |  152                 # that it is UTF-8. However, some of the Google Code mirrors are | 
|  153                 # misconfigured and will return ISO-8859-1 as charset instead of
      UTF-8. |  153                 # misconfigured and will return ISO-8859-1 as charset instead of
      UTF-8. | 
|  154                 newlines = data.decode("utf-8").splitlines() |  154                 newlines = data.decode('utf-8').splitlines() | 
|  155                 newlines = filter(lambda l: not re.search(r"^\s*!\s*(Redirect|Ho
     mepage|Title|Version|Expires)\s*:", l, re.M | re.I), newlines) |  155                 newlines = filter(lambda l: not re.search(r'^\s*!\s*(Redirect|Ho
     mepage|Title|Version|Expires)\s*:', l, re.M | re.I), newlines) | 
|  156             else: |  156             else: | 
|  157                 result.append("! *** %s ***" % filename) |  157                 result.append('! *** %s ***' % filename) | 
|  158  |  158  | 
|  159                 include_source = source_name |  159                 include_source = source_name | 
|  160                 if ":" in filename: |  160                 if ':' in filename: | 
|  161                     include_source, filename = filename.split(":", 1) |  161                     include_source, filename = filename.split(':', 1) | 
|  162                 if not include_source in sources: |  162                 if not include_source in sources: | 
|  163                     raise Exception('Cannot include file from repository "%s", t
     his repository is unknown' % include_source) |  163                     raise Exception('Cannot include file from repository "%s", t
     his repository is unknown' % include_source) | 
|  164  |  164  | 
|  165                 source = sources[include_source] |  165                 source = sources[include_source] | 
|  166                 newlines = source.read_file(filename).splitlines() |  166                 newlines = source.read_file(filename).splitlines() | 
|  167                 newlines = resolve_includes(include_source, sources, newlines, t
     imeout, level + 1) |  167                 newlines = resolve_includes(include_source, sources, newlines, t
     imeout, level + 1) | 
|  168  |  168  | 
|  169             if len(newlines) and re.search(r"\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\
     ]", newlines[0], re.I): |  169             if len(newlines) and re.search(r'\[Adblock(?:\s*Plus\s*([\d\.]+)?)?\
     ]', newlines[0], re.I): | 
|  170                 del newlines[0] |  170                 del newlines[0] | 
|  171             result.extend(newlines) |  171             result.extend(newlines) | 
|  172         else: |  172         else: | 
|  173             if line.find("%timestamp%") >= 0: |  173             if line.find('%timestamp%') >= 0: | 
|  174                 if level == 0: |  174                 if level == 0: | 
|  175                     line = line.replace("%timestamp%", time.strftime("%d %b %Y %
     H:%M UTC", time.gmtime())) |  175                     line = line.replace('%timestamp%', time.strftime('%d %b %Y %
     H:%M UTC', time.gmtime())) | 
|  176                 else: |  176                 else: | 
|  177                     line = "" |  177                     line = '' | 
|  178             result.append(line) |  178             result.append(line) | 
|  179     return result |  179     return result | 
|  180  |  180  | 
|  181  |  181  | 
|  182 def write_tpl(save_file, filename, lines): |  182 def write_tpl(save_file, filename, lines): | 
|  183     result = [] |  183     result = [] | 
|  184     result.append("msFilterList") |  184     result.append('msFilterList') | 
|  185     for line in lines: |  185     for line in lines: | 
|  186         if re.search(r"^\s*!", line): |  186         if re.search(r'^\s*!', line): | 
|  187             # This is a comment. Handle "Expires" comment in a special way, keep
      the rest. |  187             # This is a comment. Handle "Expires" comment in a special way, keep
      the rest. | 
|  188             match = re.search(r"^\s*!\s*Expires\s*:\s*(\d+)\s*(h)?", line, re.I) |  188             match = re.search(r'^\s*!\s*Expires\s*:\s*(\d+)\s*(h)?', line, re.I) | 
|  189             if match: |  189             if match: | 
|  190                 interval = int(match.group(1)) |  190                 interval = int(match.group(1)) | 
|  191                 if match.group(2): |  191                 if match.group(2): | 
|  192                     interval = int(interval / 24) |  192                     interval = int(interval / 24) | 
|  193                 result.append(": Expires=%i" % interval) |  193                 result.append(': Expires=%i' % interval) | 
|  194             else: |  194             else: | 
|  195                 result.append(re.sub(r"^\s*!", "#", re.sub(r"--!$", "--#", line)
     )) |  195                 result.append(re.sub(r'^\s*!', '#', re.sub(r'--!$', '--#', line)
     )) | 
|  196         elif line.find("#") >= 0: |  196         elif line.find('#') >= 0: | 
|  197             # Element hiding rules are not supported in MSIE, drop them |  197             # Element hiding rules are not supported in MSIE, drop them | 
|  198             pass |  198             pass | 
|  199         else: |  199         else: | 
|  200             # We have a blocking or exception rule, try to convert it |  200             # We have a blocking or exception rule, try to convert it | 
|  201             origline = line |  201             origline = line | 
|  202  |  202  | 
|  203             is_exception = False |  203             is_exception = False | 
|  204             if line.startswith("@@"): |  204             if line.startswith('@@'): | 
|  205                 is_exception = True |  205                 is_exception = True | 
|  206                 line = line[2:] |  206                 line = line[2:] | 
|  207  |  207  | 
|  208             has_unsupported = False |  208             has_unsupported = False | 
|  209             requires_script = False |  209             requires_script = False | 
|  210             match = re.search(r"^(.*?)\$(.*)", line) |  210             match = re.search(r'^(.*?)\$(.*)', line) | 
|  211             if match: |  211             if match: | 
|  212                 # This rule has options, check whether any of them are important |  212                 # This rule has options, check whether any of them are important | 
|  213                 line = match.group(1) |  213                 line = match.group(1) | 
|  214                 options = match.group(2).replace("_", "-").lower().split(",") |  214                 options = match.group(2).replace('_', '-').lower().split(',') | 
|  215  |  215  | 
|  216                 # Remove first-party only exceptions, we will allow an ad server
      everywhere otherwise |  216                 # Remove first-party only exceptions, we will allow an ad server
      everywhere otherwise | 
|  217                 if is_exception and "~third-party" in options: |  217                 if is_exception and '~third-party' in options: | 
|  218                     has_unsupported = True |  218                     has_unsupported = True | 
|  219  |  219  | 
|  220                 # A number of options are not supported in MSIE but can be safel
     y ignored, remove them |  220                 # A number of options are not supported in MSIE but can be safel
     y ignored, remove them | 
|  221                 options = filter(lambda o: not o in ("", "third-party", "~third-
     party", "match-case", "~match-case", "~other", "~donottrack"), options) |  221                 options = filter(lambda o: not o in ('', 'third-party', '~third-
     party', 'match-case', '~match-case', '~other', '~donottrack'), options) | 
|  222  |  222  | 
|  223                 # Also ignore domain negation of whitelists |  223                 # Also ignore domain negation of whitelists | 
|  224                 if is_exception: |  224                 if is_exception: | 
|  225                     options = filter(lambda o: not o.startswith("domain=~"), opt
     ions) |  225                     options = filter(lambda o: not o.startswith('domain=~'), opt
     ions) | 
|  226  |  226  | 
|  227                 unsupported = filter(lambda o: o in ("other", "elemhide"), optio
     ns) |  227                 unsupported = filter(lambda o: o in ('other', 'elemhide'), optio
     ns) | 
|  228                 if unsupported and len(unsupported) == len(options): |  228                 if unsupported and len(unsupported) == len(options): | 
|  229                     # The rule only applies to types that are not supported in M
     SIE |  229                     # The rule only applies to types that are not supported in M
     SIE | 
|  230                     has_unsupported = True |  230                     has_unsupported = True | 
|  231                 elif "donottrack" in options: |  231                 elif 'donottrack' in options: | 
|  232                     # Do-Not-Track rules have to be removed even if $donottrack 
     is combined with other options |  232                     # Do-Not-Track rules have to be removed even if $donottrack 
     is combined with other options | 
|  233                     has_unsupported = True |  233                     has_unsupported = True | 
|  234                 elif "script" in options and len(options) == len(unsupported) + 
     1: |  234                 elif 'script' in options and len(options) == len(unsupported) + 
     1: | 
|  235                     # Mark rules that only apply to scripts for approximate conv
     ersion |  235                     # Mark rules that only apply to scripts for approximate conv
     ersion | 
|  236                     requires_script = True |  236                     requires_script = True | 
|  237                 elif len(options) > 0: |  237                 elif len(options) > 0: | 
|  238                     # The rule has further options that aren't available in TPLs
     . For |  238                     # The rule has further options that aren't available in TPLs
     . For | 
|  239                     # exception rules that aren't specific to a domain we ignore
      all |  239                     # exception rules that aren't specific to a domain we ignore
      all | 
|  240                     # remaining options to avoid potential false positives. Othe
     r rules |  240                     # remaining options to avoid potential false positives. Othe
     r rules | 
|  241                     # simply aren't included in the TPL file. |  241                     # simply aren't included in the TPL file. | 
|  242                     if is_exception: |  242                     if is_exception: | 
|  243                         has_unsupported = any([o.startswith("domain=") for o in 
     options]) |  243                         has_unsupported = any([o.startswith('domain=') for o in 
     options]) | 
|  244                     else: |  244                     else: | 
|  245                         has_unsupported = True |  245                         has_unsupported = True | 
|  246  |  246  | 
|  247             if has_unsupported: |  247             if has_unsupported: | 
|  248                 # Do not include filters with unsupported options |  248                 # Do not include filters with unsupported options | 
|  249                 result.append("# " + origline) |  249                 result.append('# ' + origline) | 
|  250             else: |  250             else: | 
|  251                 line = line.replace("^", "/")  # Assume that separator placehold
     ers mean slashes |  251                 line = line.replace('^', '/')  # Assume that separator placehold
     ers mean slashes | 
|  252  |  252  | 
|  253                 # Try to extract domain info |  253                 # Try to extract domain info | 
|  254                 domain = None |  254                 domain = None | 
|  255                 match = re.search(r"^(\|\||\|\w+://)([^*:/]+)(:\d+)?(/.*)", line
     ) |  255                 match = re.search(r'^(\|\||\|\w+://)([^*:/]+)(:\d+)?(/.*)', line
     ) | 
|  256                 if match: |  256                 if match: | 
|  257                     domain = match.group(2) |  257                     domain = match.group(2) | 
|  258                     line = match.group(4) |  258                     line = match.group(4) | 
|  259                 else: |  259                 else: | 
|  260                     # No domain info, remove anchors at the rule start |  260                     # No domain info, remove anchors at the rule start | 
|  261                     line = re.sub(r"^\|\|", "http://", line) |  261                     line = re.sub(r'^\|\|', 'http://', line) | 
|  262                     line = re.sub(r"^\|", "", line) |  262                     line = re.sub(r'^\|', '', line) | 
|  263                 # Remove anchors at the rule end |  263                 # Remove anchors at the rule end | 
|  264                 line = re.sub(r"\|$", "", line) |  264                 line = re.sub(r'\|$', '', line) | 
|  265                 # Remove unnecessary asterisks at the ends of lines |  265                 # Remove unnecessary asterisks at the ends of lines | 
|  266                 line = re.sub(r"\*$", "", line) |  266                 line = re.sub(r'\*$', '', line) | 
|  267                 # Emulate $script by appending *.js to the rule |  267                 # Emulate $script by appending *.js to the rule | 
|  268                 if requires_script: |  268                 if requires_script: | 
|  269                     line += "*.js" |  269                     line += '*.js' | 
|  270                 if line.startswith("/*"): |  270                 if line.startswith('/*'): | 
|  271                     line = line[2:] |  271                     line = line[2:] | 
|  272                 if domain: |  272                 if domain: | 
|  273                     line = "%sd %s %s" % ("+" if is_exception else "-", domain, 
     line) |  273                     line = '%sd %s %s' % ('+' if is_exception else '-', domain, 
     line) | 
|  274                     line = re.sub(r"\s+/$", "", line) |  274                     line = re.sub(r'\s+/$', '', line) | 
|  275                     result.append(line) |  275                     result.append(line) | 
|  276                 elif is_exception: |  276                 elif is_exception: | 
|  277                     # Exception rules without domains are unsupported |  277                     # Exception rules without domains are unsupported | 
|  278                     result.append("# " + origline) |  278                     result.append('# ' + origline) | 
|  279                 else: |  279                 else: | 
|  280                     result.append("- " + line) |  280                     result.append('- ' + line) | 
|  281     save_file(filename, "\n".join(result) + "\n") |  281     save_file(filename, '\n'.join(result) + '\n') | 
|  282  |  282  | 
|  283  |  283  | 
|  284 class FileSource: |  284 class FileSource: | 
|  285     def __init__(self, dir): |  285     def __init__(self, dir): | 
|  286         self._dir = dir |  286         self._dir = dir | 
|  287         if os.path.exists(os.path.join(dir, ".hg")): |  287         if os.path.exists(os.path.join(dir, '.hg')): | 
|  288             # This is a Mercurial repository, try updating |  288             # This is a Mercurial repository, try updating | 
|  289             subprocess.call(["hg", "-q", "-R", dir, "pull", "--update"]) |  289             subprocess.call(['hg', '-q', '-R', dir, 'pull', '--update']) | 
|  290  |  290  | 
|  291     def get_path(self, filename): |  291     def get_path(self, filename): | 
|  292         return os.path.join(self._dir, *filename.split("/")) |  292         return os.path.join(self._dir, *filename.split('/')) | 
|  293  |  293  | 
|  294     def read_file(self, filename): |  294     def read_file(self, filename): | 
|  295         path = self.get_path(filename) |  295         path = self.get_path(filename) | 
|  296         if os.path.relpath(path, self._dir).startswith("."): |  296         if os.path.relpath(path, self._dir).startswith('.'): | 
|  297             raise Exception("Attempt to access a file outside the repository") |  297             raise Exception('Attempt to access a file outside the repository') | 
|  298         with codecs.open(path, "rb", encoding="utf-8") as handle: |  298         with codecs.open(path, 'rb', encoding='utf-8') as handle: | 
|  299             return handle.read() |  299             return handle.read() | 
|  300  |  300  | 
|  301     def list_top_level_files(self): |  301     def list_top_level_files(self): | 
|  302         for filename in os.listdir(self._dir): |  302         for filename in os.listdir(self._dir): | 
|  303             path = os.path.join(self._dir, filename) |  303             path = os.path.join(self._dir, filename) | 
|  304             if os.path.isfile(path): |  304             if os.path.isfile(path): | 
|  305                 yield filename |  305                 yield filename | 
|  306  |  306  | 
|  307  |  307  | 
|  308 def usage(): |  308 def usage(): | 
|  309     print """Usage: %s source_name=source_dir ... [output_dir] |  309     print '''Usage: %s source_name=source_dir ... [output_dir] | 
|  310  |  310  | 
|  311 Options: |  311 Options: | 
|  312   -h          --help              Print this message and exit |  312   -h          --help              Print this message and exit | 
|  313   -t seconds  --timeout=seconds   Timeout when fetching remote subscriptions |  313   -t seconds  --timeout=seconds   Timeout when fetching remote subscriptions | 
|  314 """ % os.path.basename(sys.argv[0]) |  314 ''' % os.path.basename(sys.argv[0]) | 
|  315  |  315  | 
|  316 if __name__ == "__main__": |  316 if __name__ == '__main__': | 
|  317     try: |  317     try: | 
|  318         opts, args = getopt(sys.argv[1:], "ht:", ["help", "timeout="]) |  318         opts, args = getopt(sys.argv[1:], 'ht:', ['help', 'timeout=']) | 
|  319     except GetoptError, e: |  319     except GetoptError, e: | 
|  320         print str(e) |  320         print str(e) | 
|  321         usage() |  321         usage() | 
|  322         sys.exit(2) |  322         sys.exit(2) | 
|  323  |  323  | 
|  324     target_dir = "subscriptions" |  324     target_dir = 'subscriptions' | 
|  325     sources = {} |  325     sources = {} | 
|  326     for arg in args: |  326     for arg in args: | 
|  327         if "=" in arg: |  327         if '=' in arg: | 
|  328             source_name, source_dir = arg.split("=", 1) |  328             source_name, source_dir = arg.split('=', 1) | 
|  329             sources[source_name] = FileSource(source_dir) |  329             sources[source_name] = FileSource(source_dir) | 
|  330         else: |  330         else: | 
|  331             target_dir = arg |  331             target_dir = arg | 
|  332     if not sources: |  332     if not sources: | 
|  333         sources[""] = FileSource(".") |  333         sources[''] = FileSource('.') | 
|  334  |  334  | 
|  335     timeout = 30 |  335     timeout = 30 | 
|  336     for option, value in opts: |  336     for option, value in opts: | 
|  337         if option in ("-h", "--help"): |  337         if option in ('-h', '--help'): | 
|  338             usage() |  338             usage() | 
|  339             sys.exit() |  339             sys.exit() | 
|  340         elif option in ("-t", "--timeout"): |  340         elif option in ('-t', '--timeout'): | 
|  341             timeout = int(value) |  341             timeout = int(value) | 
|  342  |  342  | 
|  343     combine_subscriptions(sources, target_dir, timeout) |  343     combine_subscriptions(sources, target_dir, timeout) | 
| OLD | NEW |