Generate . js files defining Blockly core and language messages .

def main():
  """Generate .js files defining Blockly core and language messages."""

  # Process command-line arguments.
  parser = argparse.ArgumentParser(description='Convert JSON files to JS.')
  parser.add_argument('--source_lang', default='en',
                      help='ISO 639-1 source language code')
  parser.add_argument('--source_lang_file',
                      default=os.path.join('json', 'en.json'),
                      help='Path to .json file for source language')
  parser.add_argument('--source_synonym_file',
                      default=os.path.join('json', 'synonyms.json'),
                      help='Path to .json file with synonym definitions')
  parser.add_argument('--output_dir', default='js/',
                      help='relative directory for output files')
  parser.add_argument('--key_file', default='keys.json',
                      help='relative path to input keys file')
  parser.add_argument('--quiet', action='store_true', default=False,
                      help='do not write anything to standard output')
  parser.add_argument('files', nargs='+', help='input files')
  args = parser.parse_args()
  if not args.output_dir.endswith(os.path.sep):
    args.output_dir += os.path.sep

  # Read in source language .json file, which provides any values missing
  # in target languages' .json files.
  source_defs = read_json_file(os.path.join(os.curdir, args.source_lang_file))
  # Make sure the source file doesn't contain a newline or carriage return.
  for key, value in source_defs.items():
    if _NEWLINE_PATTERN.search(value):
      print('ERROR: definition of {0} in {1} contained a newline character.'.
            format(key, args.source_lang_file))
      sys.exit(1)
  sorted_keys = source_defs.keys()
  sorted_keys.sort()

  # Read in synonyms file, which must be output in every language.
  synonym_defs = read_json_file(os.path.join(
      os.curdir, args.source_synonym_file))
  synonym_text = '\n'.join(['Blockscad.Msg.{0} = Blockscad.Msg.{1};'.format(
      key, synonym_defs[key]) for key in synonym_defs])

  # Create each output file.
  for arg_file in args.files:
    (_, filename) = os.path.split(arg_file)
    target_lang = filename[:filename.index('.')]
    if target_lang not in ('qqq', 'keys', 'synonyms'):
      target_defs = read_json_file(os.path.join(os.curdir, arg_file))

      # Verify that keys are 'ascii'
      bad_keys = [key for key in target_defs if not string_is_ascii(key)]
      if bad_keys:
        print(u'These keys in {0} contain non ascii characters: {1}'.format(
            filename, ', '.join(bad_keys)))

      # If there's a '\n' or '\r', remove it and print a warning.
      for key, value in target_defs.items():
        if _NEWLINE_PATTERN.search(value):
          print(u'WARNING: definition of {0} in {1} contained '
                'a newline character.'.
                format(key, arg_file))
          target_defs[key] = _NEWLINE_PATTERN.sub(' ', value)

      # Output file.
      outname = os.path.join(os.curdir, args.output_dir, target_lang + '.js')
      with codecs.open(outname, 'w', 'utf-8') as outfile:
        outfile.write(
            """// This file was automatically generated.  Do not modify.

'use strict';

goog.provide('Blockscad.Msg.{0}');

""".format(target_lang.replace('-', '.')))
        # For each key in the source language file, output the target value
        # if present; otherwise, output the source language value with a
        # warning comment.
        for key in sorted_keys:
          if key in target_defs:
            value = target_defs[key]
            comment = ''
            del target_defs[key]
          else:
            value = source_defs[key]
            comment = '  // untranslated'
          value = value.replace('"', '\\"')
          outfile.write(u'Blockscad.Msg.{0} = "{1}";{2}\n'.format(
              key, value, comment))

        # Announce any keys defined only for target language.
        if target_defs:
          extra_keys = [key for key in target_defs if key not in synonym_defs]
          synonym_keys = [key for key in target_defs if key in synonym_defs]
          if not args.quiet:
            if extra_keys:
              print(u'These extra keys appeared in {0}: {1}'.format(
                  filename, ', '.join(extra_keys)))
            if synonym_keys:
              print(u'These synonym keys appeared in {0}: {1}'.format(
                  filename, ', '.join(synonym_keys)))

        outfile.write(synonym_text)

      if not args.quiet:
        print('Created {0}.'.format(outname))


Creates a <target_lang > . xlf file for Soy .

def _create_xlf(target_lang):
    """Creates a <target_lang>.xlf file for Soy.

    Args:
        target_lang: The ISO 639 language code for the target language.
            This is used in the name of the file and in the metadata.

    Returns:
        A pointer to a file to which the metadata has been written.

    Raises:
        IOError: An error occurred while opening or writing the file.
    """
    filename = os.path.join(os.curdir, args.output_dir, target_lang + '.xlf')
    out_file = codecs.open(filename, 'w', 'utf-8')
    out_file.write("""<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file original="SoyMsgBundle"
        datatype="x-soy-msg-bundle"
        xml:space="preserve"
        source-language="{0}"
        target-language="{1}">
    <body>""".format(args.source_lang, target_lang))
    return out_file


Closes a <target_lang > . xlf file created with create_xlf () .

def _close_xlf(xlf_file):
    """Closes a <target_lang>.xlf file created with create_xlf().

    This includes writing the terminating XML.

    Args:
        xlf_file: A pointer to a file created by _create_xlf().

    Raises:
        IOError: An error occurred while writing to or closing the file.
    """
    xlf_file.write("""
    </body>
  </file>
</xliff>
""")
    xlf_file.close()


Creates an . xlf file corresponding to the specified . json input file .

def _process_file(path_to_json, target_lang, key_dict):
    """Creates an .xlf file corresponding to the specified .json input file.

    The name of the input file must be target_lang followed by '.json'.
    The name of the output file will be target_lang followed by '.js'.

    Args:
        path_to_json: Path to the directory of xx.json files.
        target_lang: A IETF language code (RFC 4646), such as 'es' or 'pt-br'.
        key_dict: Dictionary mapping Blockly keys (e.g., Maze.turnLeft) to
            Closure keys (hash numbers).

    Raises:
        IOError: An I/O error occurred with an input or output file.
        InputError: Input JSON could not be parsed.
        KeyError: Key found in input file but not in key file.
    """
    keyfile = os.path.join(path_to_json, target_lang + '.json')
    j = read_json_file(keyfile)
    out_file = _create_xlf(target_lang)
    for key in j:
        if key != '@metadata':
            try:
                identifier = key_dict[key]
            except KeyError, e:
                print('Key "%s" is in %s but not in %s' %
                      (key, keyfile, args.key_file))
                raise e
            target = j.get(key)
            out_file.write(u"""
      <trans-unit id="{0}" datatype="html">
        <target>{1}</target>
      </trans-unit>""".format(identifier, target))
    _close_xlf(out_file)


Parses arguments and iterates over files .

def main():
    """Parses arguments and iterates over files."""

    # Set up argument parser.
    parser = argparse.ArgumentParser(description='Convert JSON files to JS.')
    parser.add_argument('--source_lang', default='en',
                        help='ISO 639-1 source language code')
    parser.add_argument('--output_dir', default='generated',
                        help='relative directory for output files')
    parser.add_argument('--key_file', default='json' + os.path.sep + 'keys.json',
                        help='relative path to input keys file')
    parser.add_argument('--template', default='template.soy')
    parser.add_argument('--path_to_jar',
                        default='..' + os.path.sep + 'apps' + os.path.sep
                        + '_soy',
                        help='relative path from working directory to '
                        'SoyToJsSrcCompiler.jar')
    parser.add_argument('files', nargs='+', help='input files')

    # Initialize global variables.
    global args
    args = parser.parse_args()

    # Make sure output_dir ends with slash.
    if (not args.output_dir.endswith(os.path.sep)):
      args.output_dir += os.path.sep

    # Read in keys.json, mapping descriptions (e.g., Maze.turnLeft) to
    # Closure keys (long hash numbers).
    key_file = open(args.key_file)
    key_dict = json.load(key_file)
    key_file.close()

    # Process each input file.
    print('Creating .xlf files...')
    processed_langs = []
    if len(args.files) == 1:
      # Windows does not expand globs automatically.
      args.files = glob.glob(args.files[0])
    for arg_file in args.files:
      (path_to_json, filename) = os.path.split(arg_file)
      if not filename.endswith('.json'):
        raise InputError(filename, 'filenames must end with ".json"')
      target_lang = filename[:filename.index('.')]
      if not target_lang in ('qqq', 'keys'):
        processed_langs.append(target_lang)
        _process_file(path_to_json, target_lang, key_dict)

    # Output command line for Closure compiler.
    if processed_langs:
      print('Creating .js files...')
      processed_lang_list = ','.join(processed_langs)
      subprocess.check_call([
          'java',
          '-jar', os.path.join(args.path_to_jar, 'SoyToJsSrcCompiler.jar'),
          '--locales', processed_lang_list,
          '--messageFilePathFormat', args.output_dir + '{LOCALE}.xlf',
          '--outputPathFormat', args.output_dir + '{LOCALE}.js',
          '--srcs', args.template])
      if len(processed_langs) == 1:
        print('Created ' + processed_lang_list + '.js in ' + args.output_dir)
      else:
        print('Created {' + processed_lang_list + '}.js in ' + args.output_dir)

      for lang in processed_langs:
        os.remove(args.output_dir + lang + '.xlf')
      print('Removed .xlf files.')


Read a JSON file as UTF - 8 into a dictionary discarding @metadata .

def read_json_file(filename):
  """Read a JSON file as UTF-8 into a dictionary, discarding @metadata.

  Args:
    filename: The filename, which must end ".json".

  Returns:
    The dictionary.

  Raises:
    InputError: The filename did not end with ".json" or an error occurred
        while opening or reading the file.
  """
  if not filename.endswith('.json'):
    raise InputError(filename, 'filenames must end with ".json"')
  try:
    # Read in file.
    with codecs.open(filename, 'r', 'utf-8') as infile:
      defs = json.load(infile)
    if '@metadata' in defs:
      del defs['@metadata']
    return defs
  except ValueError, e:
    print('Error reading ' + filename)
    raise InputError(filename, str(e))

def
read_json_file
(
filename
)
:
if
not
filename
.
endswith
(
'.json'
)
:
raise
InputError
(
filename
,
'filenames must end with ".json"'
)
try
:
# Read in file.
with
codecs
.
open
(
filename
,
'r'
,
'utf-8'
)
as
infile
:
defs
=
json
.
load
(
infile
)
if
'@metadata'
in
defs
:
del
defs
[
'@metadata'
]
return
defs
except
ValueError
,
e
:
print
(
'Error reading '
+
filename
)
raise
InputError
(
filename
,
str
(
e
)
)


Creates a qqq . json file with message documentation for translatewiki . net .

def _create_qqq_file(output_dir):
    """Creates a qqq.json file with message documentation for translatewiki.net.

    The file consists of key-value pairs, where the keys are message ids and
    the values are descriptions for the translators of the messages.
    What documentation exists for the format can be found at:
    http://translatewiki.net/wiki/Translating:Localisation_for_developers#Message_documentation

    The file should be closed by _close_qqq_file().

    Parameters:
        output_dir: The output directory.

    Returns:
        A pointer to a file to which a left brace and newline have been written.

    Raises:
        IOError: An error occurred while opening or writing the file.
    """
    qqq_file_name = os.path.join(os.curdir, output_dir, 'qqq.json')
    qqq_file = codecs.open(qqq_file_name, 'w', 'utf-8')
    print 'Created file: ' + qqq_file_name
    qqq_file.write('{\n')
    return qqq_file

def
_create_qqq_file
(
output_dir
)
:
qqq_file_name
=
os
.
path
.
join
(
os
.
curdir
,
output_dir
,
'qqq.json'
)
qqq_file
=
codecs
.
open
(
qqq_file_name
,
'w'
,
'utf-8'
)
print
'Created file: '
+
qqq_file_name
qqq_file
.
write
(
'{\n'
)
return
qqq_file


Closes a qqq . json file created and opened by _create_qqq_file () .

def _close_qqq_file(qqq_file):
    """Closes a qqq.json file created and opened by _create_qqq_file().

    This writes the final newlines and right brace.

    Args:
        qqq_file: A file created by _create_qqq_file().

    Raises:
        IOError: An error occurred while writing to or closing the file.
    """
    qqq_file.write('\n}\n')
    qqq_file.close()

def _close_qqq_file ( qqq_file ) : qqq_file . write ( '\n}\n' ) qqq_file . close ( )


Creates a <lang > . json file for translatewiki . net .

def _create_lang_file(author, lang, output_dir):
    """Creates a <lang>.json file for translatewiki.net.

    The file consists of metadata, followed by key-value pairs, where the keys
    are message ids and the values are the messages in the language specified
    by the corresponding command-line argument.  The file should be closed by
    _close_lang_file().

    Args:
        author: Name and email address of contact for translators.
        lang: ISO 639-1 source language code.
        output_dir: Relative directory for output files.

    Returns:
        A pointer to a file to which the metadata has been written.

    Raises:
        IOError: An error occurred while opening or writing the file.
    """
    lang_file_name = os.path.join(os.curdir, output_dir, lang + '.json')
    lang_file = codecs.open(lang_file_name, 'w', 'utf-8')
    print 'Created file: ' + lang_file_name
    # string.format doesn't like printing braces, so break up our writes.
    lang_file.write('{\n\t"@metadata": {')
    lang_file.write("""
\t\t"author": "{0}",
\t\t"lastupdated": "{1}",
\t\t"locale": "{2}",
\t\t"messagedocumentation" : "qqq"
""".format(author, str(datetime.now()), lang))
    lang_file.write('\t},\n')
    return lang_file


Closes a <lang > . json file created with _create_lang_file () .

def _close_lang_file(lang_file):
    """Closes a <lang>.json file created with _create_lang_file().

    This also writes the terminating left brace and newline.

    Args:
        lang_file: A file opened with _create_lang_file().

    Raises:
        IOError: An error occurred while writing to or closing the file.
    """
    lang_file.write('\n}\n')
    lang_file.close()

def _close_lang_file ( lang_file ) : lang_file . write ( '\n}\n' ) lang_file . close ( )


Creates a keys . json file mapping Closure keys to Blockly keys .

def _create_key_file(output_dir):
    """Creates a keys.json file mapping Closure keys to Blockly keys.

    Args:
        output_dir: Relative directory for output files.

    Raises:
        IOError: An error occurred while creating the file.
    """
    key_file_name = os.path.join(os.curdir, output_dir, 'keys.json')
    key_file = open(key_file_name, 'w')
    key_file.write('{\n')
    print 'Created file: ' + key_file_name
    return key_file

def
_create_key_file
(
output_dir
)
:
key_file_name
=
os
.
path
.
join
(
os
.
curdir
,
output_dir
,
'keys.json'
)
key_file
=
open
(
key_file_name
,
'w'
)
key_file
.
write
(
'{\n'
)
print
'Created file: '
+
key_file_name
return
key_file


Closes a key file created and opened with _create_key_file () .

def _close_key_file(key_file):
    """Closes a key file created and opened with _create_key_file().

    Args:
        key_file: A file created by _create_key_file().

    Raises:
        IOError: An error occurred while writing to or closing the file.
    """
    key_file.write('\n}\n')
    key_file.close()

def _close_key_file ( key_file ) : key_file . write ( '\n}\n' ) key_file . close ( )


Writes the output files for the given units .

def write_files(author, lang, output_dir, units, write_key_file):
    """Writes the output files for the given units.

    There are three possible output files:
    * lang_file: JSON file mapping meanings (e.g., Maze.turnLeft) to the
      English text.  The base name of the language file is specified by the
      "lang" command-line argument.
    * key_file: JSON file mapping meanings to Soy-generated keys (long hash
      codes).  This is only output if the parameter write_key_file is True.
    * qqq_file: JSON file mapping meanings to descriptions.

    Args:
        author: Name and email address of contact for translators.
        lang: ISO 639-1 source language code.
        output_dir: Relative directory for output files.
        units: A list of dictionaries with entries for 'meaning', 'source',
            'description', and 'keys' (the last only if write_key_file is true),
            in the order desired in the output files.
        write_key_file: Whether to output a keys.json file.

    Raises:
        IOError: An error occurs opening, writing to, or closing a file.
        KeyError: An expected key is missing from units.
    """
    lang_file = _create_lang_file(author, lang, output_dir)
    qqq_file = _create_qqq_file(output_dir)
    if write_key_file:
      key_file = _create_key_file(output_dir)
    first_entry = True
    for unit in units:
        if not first_entry:
            lang_file.write(',\n')
            if write_key_file:
              key_file.write(',\n')
            qqq_file.write(',\n')
        lang_file.write(u'\t"{0}": "{1}"'.format(
            unit['meaning'],
            unit['source'].replace('"', "'")))
        if write_key_file:
          key_file.write('"{0}": "{1}"'.format(unit['meaning'], unit['key']))
        qqq_file.write(u'\t"{0}": "{1}"'.format(
            unit['meaning'],
            unit['description'].replace('"', "'").replace(
                '{lb}', '{').replace('{rb}', '}')))
        first_entry = False
    _close_lang_file(lang_file)
    if write_key_file:
      _close_key_file(key_file)
    _close_qqq_file(qqq_file)

def
write_files
(
author
,
lang
,
output_dir
,
units
,
write_key_file
)
:
lang_file
=
_create_lang_file
(
author
,
lang
,
output_dir
)
qqq_file
=
_create_qqq_file
(
output_dir
)
if
write_key_file
:
key_file
=
_create_key_file
(
output_dir
)
first_entry
=
True
for
unit
in
units
:
if
not
first_entry
:
lang_file
.
write
(
',\n'
)
if
write_key_file
:
key_file
.
write
(
',\n'
)
qqq_file
.
write
(
',\n'
)
lang_file
.
write
(
u'\t"{0}": "{1}"'
.
format
(
unit
[
'meaning'
]
,
unit
[
'source'
]
.
replace
(
'"'
,
"'"
)
)
)
if
write_key_file
:
key_file
.
write
(
'"{0}": "{1}"'
.
format
(
unit
[
'meaning'
]
,
unit
[
'key'
]
)
)
qqq_file
.
write
(
u'\t"{0}": "{1}"'
.
format
(
unit
[
'meaning'
]
,
unit
[
'description'
]
.
replace
(
'"'
,
"'"
)
.
replace
(
'{lb}'
,
'{'
)
.
replace
(
'{rb}'
,
'}'
)
)
)
first_entry
=
False
_close_lang_file
(
lang_file
)
if
write_key_file
:
_close_key_file
(
key_file
)
_close_qqq_file
(
qqq_file
)


Import a file with full path specification . Allows one to import from any directory something __import__ does not do .

def import_path(fullpath):
  """Import a file with full path specification.
  Allows one to import from any directory, something __import__ does not do.

  Args:
      fullpath:  Path and filename of import.

  Returns:
      An imported module.
  """
  path, filename = os.path.split(fullpath)
  filename, ext = os.path.splitext(filename)
  sys.path.append(path)
  module = __import__(filename)
  reload(module)  # Might be out of date.
  del sys.path[-1]
  return module

def
import_path
(
fullpath
)
:
path
,
filename
=
os
.
path
.
split
(
fullpath
)
filename
,
ext
=
os
.
path
.
splitext
(
filename
)
sys
.
path
.
append
(
path
)
module
=
__import__
(
filename
)
reload
(
module
)
# Might be out of date.
del
sys
.
path
[
1
]
return
module


def main():
  """Generate .js files defining Blockly core and language messages."""

  # Process command-line arguments.
  parser = argparse.ArgumentParser(description='Convert JSON files to JS.')
  parser.add_argument('--source_lang', default='en',
                      help='ISO 639-1 source language code')
  parser.add_argument('--source_lang_file',
                      default=os.path.join('json', 'en.json'),
                      help='Path to .json file for source language')
  parser.add_argument('--source_synonym_file',
                      default=os.path.join('json', 'synonyms.json'),
                      help='Path to .json file with synonym definitions')
  parser.add_argument('--output_dir', default='js/',
                      help='relative directory for output files')
  parser.add_argument('--key_file', default='keys.json',
                      help='relative path to input keys file')
  parser.add_argument('--quiet', action='store_true', default=False,
                      help='do not write anything to standard output')
  parser.add_argument('files', nargs='+', help='input files')
  args = parser.parse_args()
  if not args.output_dir.endswith(os.path.sep):
    args.output_dir += os.path.sep

  # Read in source language .json file, which provides any values missing
  # in target languages' .json files.
  source_defs = read_json_file(os.path.join(os.curdir, args.source_lang_file))
  # Make sure the source file doesn't contain a newline or carriage return.
  for key, value in source_defs.items():
    if _NEWLINE_PATTERN.search(value):
      print('ERROR: definition of {0} in {1} contained a newline character.'.
            format(key, args.source_lang_file))
      sys.exit(1)
  sorted_keys = source_defs.keys()
  sorted_keys.sort()

  # Read in synonyms file, which must be output in every language.
  synonym_defs = read_json_file(os.path.join(
      os.curdir, args.source_synonym_file))
  synonym_text = '\n'.join(['Blockly.Msg.{0} = Blockly.Msg.{1};'.format(
      key, synonym_defs[key]) for key in synonym_defs])

  # Create each output file.
  for arg_file in args.files:
    (_, filename) = os.path.split(arg_file)
    target_lang = filename[:filename.index('.')]
    if target_lang not in ('qqq', 'keys', 'synonyms'):
      target_defs = read_json_file(os.path.join(os.curdir, arg_file))

      # Verify that keys are 'ascii'
      bad_keys = [key for key in target_defs if not string_is_ascii(key)]
      if bad_keys:
        print(u'These keys in {0} contain non ascii characters: {1}'.format(
            filename, ', '.join(bad_keys)))

      # If there's a '\n' or '\r', remove it and print a warning.
      for key, value in target_defs.items():
        if _NEWLINE_PATTERN.search(value):
          print(u'WARNING: definition of {0} in {1} contained '
                'a newline character.'.
                format(key, arg_file))
          target_defs[key] = _NEWLINE_PATTERN.sub(' ', value)

      # Output file.
      outname = os.path.join(os.curdir, args.output_dir, target_lang + '.js')
      with codecs.open(outname, 'w', 'utf-8') as outfile:
        outfile.write(
            """// This file was automatically generated.  Do not modify.

'use strict';

goog.provide('Blockly.Msg.{0}');

goog.require('Blockly.Msg');

""".format(target_lang.replace('-', '.')))
        # For each key in the source language file, output the target value
        # if present; otherwise, output the source language value with a
        # warning comment.
        for key in sorted_keys:
          if key in target_defs:
            value = target_defs[key]
            comment = ''
            del target_defs[key]
          else:
            value = source_defs[key]
            comment = '  // untranslated'
          value = value.replace('"', '\\"')
          outfile.write(u'Blockly.Msg.{0} = "{1}";{2}\n'.format(
              key, value, comment))

        # Announce any keys defined only for target language.
        if target_defs:
          extra_keys = [key for key in target_defs if key not in synonym_defs]
          synonym_keys = [key for key in target_defs if key in synonym_defs]
          if not args.quiet:
            if extra_keys:
              print(u'These extra keys appeared in {0}: {1}'.format(
                  filename, ', '.join(extra_keys)))
            if synonym_keys:
              print(u'These synonym keys appeared in {0}: {1}'.format(
                  filename, ', '.join(synonym_keys)))

        outfile.write(synonym_text)

      if not args.quiet:
        print('Created {0}.'.format(outname))


def main():
  """Parses arguments and iterates over files.

  Raises:
    IOError: An I/O error occurred with an input or output file.
    InputError: Input JSON could not be parsed.
  """

  # Set up argument parser.
  parser = argparse.ArgumentParser(
      description='Removes duplicate key-value pairs from JSON files.')
  parser.add_argument('--suffix', default='',
                      help='optional suffix for output files; '
                      'if empty, files will be changed in place')
  parser.add_argument('files', nargs='+', help='input files')
  args = parser.parse_args()

  # Iterate over files.
  for filename in args.files:
    # Read in json using Python libraries.  This eliminates duplicates.
    print('Processing ' + filename + '...')
    try:
      with codecs.open(filename, 'r', 'utf-8') as infile:
        j = json.load(infile)
    except ValueError, e:
      print('Error reading ' + filename)
      raise InputError(file, str(e))

    # Built up output strings as an array to make output of delimiters easier.
    output = []
    for key in j:
      if key != '@metadata':
        output.append('\t"' + key + '": "' +
                      j[key].replace('\n', '\\n') + '"')

    # Output results.
    with codecs.open(filename + args.suffix, 'w', 'utf-8') as outfile:
      outfile.write('{\n')
      outfile.write(',\n'.join(output))
      outfile.write('\n}\n')


"""<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file original="SoyMsgBundle"
        datatype="x-soy-msg-bundle"
        xml:space="preserve"
        source-language="{0}"
        target-language="{1}">
    <body>"""


u"""
      <trans-unit id="{0}" datatype="html">
        <target>{1}</target>
      </trans-unit>"""


'relative path from working directory to '

# Read in keys.json, mapping descriptions (e.g., Maze.turnLeft) to

# Windows does not expand globs automatically.

# Output command line for Closure compiler.


def
read_json_file
(
filename
)
:
if
not
filename
.
endswith
(
'.json'
)
:
raise
InputError
(
filename
,
'filenames must end with ".json"'
)
try
:
# Read in file.
with
codecs
.
open
(
filename
,
'r'
,
'utf-8'
)
as
infile
:
defs
=
json
.
load
(
infile
)
if
'@metadata'
in
defs
:
del
defs
[
'@metadata'
]
return
defs
except
ValueError
,
e
:
print
(
'Error reading '
+
filename
)
raise
InputError
(
filename
,
str
(
e
)
)


def
_create_qqq_file
(
output_dir
)
:
qqq_file_name
=
os
.
path
.
join
(
os
.
curdir
,
output_dir
,
'qqq.json'
)
qqq_file
=
codecs
.
open
(
qqq_file_name
,
'w'
,
'utf-8'
)
print
'Created file: '
+
qqq_file_name
qqq_file
.
write
(
'{\n'
)
return
qqq_file


def _close_qqq_file ( qqq_file ) : qqq_file . write ( '\n}\n' ) qqq_file . close ( )


# string.format doesn't like printing braces, so break up our writes.

"""
\t\t"author": "{0}",
\t\t"lastupdated": "{1}",
\t\t"locale": "{2}",
\t\t"messagedocumentation" : "qqq"
"""


def _close_lang_file ( lang_file ) : lang_file . write ( '\n}\n' ) lang_file . close ( )


def
_create_key_file
(
output_dir
)
:
key_file_name
=
os
.
path
.
join
(
os
.
curdir
,
output_dir
,
'keys.json'
)
key_file
=
open
(
key_file_name
,
'w'
)
key_file
.
write
(
'{\n'
)
print
'Created file: '
+
key_file_name
return
key_file


def _close_key_file ( key_file ) : key_file . write ( '\n}\n' ) key_file . close ( )


def
write_files
(
author
,
lang
,
output_dir
,
units
,
write_key_file
)
:
lang_file
=
_create_lang_file
(
author
,
lang
,
output_dir
)
qqq_file
=
_create_qqq_file
(
output_dir
)
if
write_key_file
:
key_file
=
_create_key_file
(
output_dir
)
first_entry
=
True
for
unit
in
units
:
if
not
first_entry
:
lang_file
.
write
(
',\n'
)
if
write_key_file
:
key_file
.
write
(
',\n'
)
qqq_file
.
write
(
',\n'
)
lang_file
.
write
(
u'\t"{0}": "{1}"'
.
format
(
unit
[
'meaning'
]
,
unit
[
'source'
]
.
replace
(
'"'
,
"'"
)
)
)
if
write_key_file
:
key_file
.
write
(
'"{0}": "{1}"'
.
format
(
unit
[
'meaning'
]
,
unit
[
'key'
]
)
)
qqq_file
.
write
(
u'\t"{0}": "{1}"'
.
format
(
unit
[
'meaning'
]
,
unit
[
'description'
]
.
replace
(
'"'
,
"'"
)
.
replace
(
'{lb}'
,
'{'
)
.
replace
(
'{rb}'
,
'}'
)
)
)
first_entry
=
False
_close_lang_file
(
lang_file
)
if
write_key_file
:
_close_key_file
(
key_file
)
_close_qqq_file
(
qqq_file
)


Converts a trans - unit XML node into a more convenient dictionary format .

def _parse_trans_unit(trans_unit):
    """Converts a trans-unit XML node into a more convenient dictionary format.

    Args:
        trans_unit: An XML representation of a .xlf translation unit.

    Returns:
        A dictionary with useful information about the translation unit.
        The returned dictionary is guaranteed to have an entry for 'key' and
        may have entries for 'source', 'target', 'description', and 'meaning'
        if present in the argument.

    Raises:
        InputError: A required field was not present.
    """

    def get_value(tag_name):
        elts = trans_unit.getElementsByTagName(tag_name)
        if not elts:
            return None
        elif len(elts) == 1:
            return ''.join([child.toxml() for child in elts[0].childNodes])
        else:
            raise InputError('', 'Unable to extract ' + tag_name)

    result = {}
    key = trans_unit.getAttribute('id')
    if not key:
        raise InputError('', 'id attribute not found')
    result['key'] = key

    # Get source and target, if present.
    try:
        result['source'] = get_value('source')
        result['target'] = get_value('target')
    except InputError, e:
        raise InputError(key, e.msg)

    # Get notes, using the from value as key and the data as value.
    notes = trans_unit.getElementsByTagName('note')
    for note in notes:
        from_value = note.getAttribute('from')
        if from_value and len(note.childNodes) == 1:
            result[from_value] = note.childNodes[0].data
        else:
            raise InputError(key, 'Unable to extract ' + from_value)

    return result


Builds list of translation units from input file .

def _process_file(filename):
    """Builds list of translation units from input file.

    Each translation unit in the input file includes:
    - an id (opaquely generated by Soy)
    - the Blockly name for the message
    - the text in the source language (generally English)
    - a description for the translator

    The Soy and Blockly ids are joined with a hyphen and serve as the
    keys in both output files.  The value is the corresponding text (in the
    <lang>.json file) or the description (in the qqq.json file).

    Args:
        filename: The name of an .xlf file produced by Closure.

    Raises:
        IOError: An I/O error occurred with an input or output file.
        InputError: The input file could not be parsed or lacked required
            fields.

    Returns:
        A list of dictionaries produced by parse_trans_unit().
    """
    try:
        results = []  # list of dictionaries (return value)
        names = []    # list of names of encountered keys (local variable)
        try:
            parsed_xml = minidom.parse(filename)
        except IOError:
            # Don't get caught by below handler
            raise
        except Exception, e:
            print
            raise InputError(filename, str(e))

        # Make sure needed fields are present and non-empty.
        for trans_unit in parsed_xml.getElementsByTagName('trans-unit'):
            unit = _parse_trans_unit(trans_unit)
            for key in ['description', 'meaning', 'source']:
                if not key in unit or not unit[key]:
                    raise InputError(filename + ':' + unit['key'],
                                     key + ' not found')
            if unit['description'].lower() == 'ibid':
              if unit['meaning'] not in names:
                # If the term has not already been described, the use of 'ibid'
                # is an error.
                raise InputError(
                    filename,
                    'First encountered definition of: ' + unit['meaning']
                    + ' has definition: ' + unit['description']
                    + '.  This error can occur if the definition was not'
                    + ' provided on the first appearance of the message'
                    + ' or if the source (English-language) messages differ.')
              else:
                # If term has already been described, 'ibid' was used correctly,
                # and we output nothing.
                pass
            else:
              if unit['meaning'] in names:
                raise InputError(filename,
                                 'Second definition of: ' + unit['meaning'])
              names.append(unit['meaning'])
              results.append(unit)

        return results
    except IOError, e:
        print 'Error with file {0}: {1}'.format(filename, e.strerror)
        sys.exit(1)


Sorts the translation units by their definition order in the template .

def sort_units(units, templates):
    """Sorts the translation units by their definition order in the template.

    Args:
        units: A list of dictionaries produced by parse_trans_unit()
            that have a non-empty value for the key 'meaning'.
        templates: A string containing the Soy templates in which each of
            the units' meanings is defined.

    Returns:
        A new list of translation units, sorted by the order in which
        their meaning is defined in the templates.

    Raises:
        InputError: If a meaning definition cannot be found in the
            templates.
    """
    def key_function(unit):
        match = re.search(
            '\\smeaning\\s*=\\s*"{0}"\\s'.format(unit['meaning']),
            templates)
        if match:
            return match.start()
        else:
            raise InputError(args.templates,
                             'msg definition for meaning not found: ' +
                             unit['meaning'])
    return sorted(units, key=key_function)


Parses arguments and processes the specified file .

def main():
    """Parses arguments and processes the specified file.

    Raises:
        IOError: An I/O error occurred with an input or output file.
        InputError: Input files lacked required fields.
    """
    # Set up argument parser.
    parser = argparse.ArgumentParser(description='Create translation files.')
    parser.add_argument(
        '--author',
        default='Ellen Spertus <ellen.spertus@gmail.com>',
        help='name and email address of contact for translators')
    parser.add_argument('--lang', default='en',
                        help='ISO 639-1 source language code')
    parser.add_argument('--output_dir', default='json',
                        help='relative directory for output files')
    parser.add_argument('--xlf', help='file containing xlf definitions')
    parser.add_argument('--templates', default=['template.soy'], nargs='+',
                        help='relative path to Soy templates, comma or space '
                        'separated (used for ordering messages)')
    global args
    args = parser.parse_args()

    # Make sure output_dir ends with slash.
    if (not args.output_dir.endswith(os.path.sep)):
      args.output_dir += os.path.sep

    # Process the input file, and sort the entries.
    units = _process_file(args.xlf)
    files = []
    for arg in args.templates:
      for filename in arg.split(','):
        filename = filename.strip();
        if filename:
          with open(filename) as myfile:
            files.append(' '.join(line.strip() for line in myfile))
    sorted_units = sort_units(units, ' '.join(files))

    # Write the output files.
    write_files(args.author, args.lang, args.output_dir, sorted_units, True)

    # Delete the input .xlf file.
    os.remove(args.xlf)
    print('Removed ' + args.xlf)


Converts the contents of a file into javascript that uses goog . scope .

def Transform(lines):
  """Converts the contents of a file into javascript that uses goog.scope.

  Arguments:
    lines: A list of strings, corresponding to each line of the file.
  Returns:
    A new list of strings, or None if the file was not modified.
  """
  requires = []

  # Do an initial scan to be sure that this file can be processed.
  for line in lines:
    # Skip this file if it has already been scopified.
    if line.find('goog.scope') != -1:
      return None

    # If there are any global vars or functions, then we also have
    # to skip the whole file. We might be able to deal with this
    # more elegantly.
    if line.find('var ') == 0 or line.find('function ') == 0:
      return None

    for match in REQUIRES_RE.finditer(line):
      requires.append(match.group(1))

  if len(requires) == 0:
    return None

  # Backwards-sort the requires, so that when one is a substring of another,
  # we match the longer one first.
  for val in DEFAULT_ALIASES.values():
    if requires.count(val) == 0:
      requires.append(val)

  requires.sort()
  requires.reverse()

  # Generate a map of requires to their aliases
  aliases_to_globals = DEFAULT_ALIASES.copy()
  for req in requires:
    index = req.rfind('.')
    if index == -1:
      alias = req
    else:
      alias = req[(index + 1):]

    # Don't scopify lowercase namespaces, because they may conflict with
    # local variables.
    if alias[0].isupper():
      aliases_to_globals[alias] = req

  aliases_to_matchers = {}
  globals_to_aliases = {}
  for alias, symbol in aliases_to_globals.items():
    globals_to_aliases[symbol] = alias
    aliases_to_matchers[alias] = re.compile('\\b%s\\b' % symbol)

  # Insert a goog.scope that aliases all required symbols.
  result = []

  START = 0
  SEEN_REQUIRES = 1
  IN_SCOPE = 2

  mode = START
  aliases_used = set()
  insertion_index = None
  num_blank_lines = 0
  for line in lines:
    if mode == START:
      result.append(line)

      if re.search(REQUIRES_RE, line):
        mode = SEEN_REQUIRES

    elif mode == SEEN_REQUIRES:
      if (line and
          not re.search(REQUIRES_RE, line) and
          not line.isspace()):
        # There should be two blank lines before goog.scope
        result += ['\n'] * 2
        result.append('goog.scope(function() {\n')
        insertion_index = len(result)
        result += ['\n'] * num_blank_lines
        mode = IN_SCOPE
      elif line.isspace():
        # Keep track of the number of blank lines before each block of code so
        # that we can move them after the goog.scope line if necessary.
        num_blank_lines += 1
      else:
        # Print the blank lines we saw before this code block
        result += ['\n'] * num_blank_lines
        num_blank_lines = 0
        result.append(line)

    if mode == IN_SCOPE:
      for symbol in requires:
        if not symbol in globals_to_aliases:
          continue

        alias = globals_to_aliases[symbol]
        matcher = aliases_to_matchers[alias]
        for match in matcher.finditer(line):
          # Check to make sure we're not in a string.
          # We do this by being as conservative as possible:
          # if there are any quote or double quote characters
          # before the symbol on this line, then bail out.
          before_symbol = line[:match.start(0)]
          if before_symbol.count('"') > 0 or before_symbol.count("'") > 0:
            continue

          line = line.replace(match.group(0), alias)
          aliases_used.add(alias)

      if line.isspace():
        # Truncate all-whitespace lines
        result.append('\n')
      else:
        result.append(line)

  if len(aliases_used):
    aliases_used = [alias for alias in aliases_used]
    aliases_used.sort()
    aliases_used.reverse()
    for alias in aliases_used:
      symbol = aliases_to_globals[alias]
      result.insert(insertion_index,
                    'var %s = %s;\n' % (alias, symbol))
    result.append('});  // goog.scope\n')
    return result
  else:
    return None


Converts a file into javascript that uses goog . scope .

def TransformFileAt(path):
  """Converts a file into javascript that uses goog.scope.

  Arguments:
    path: A path to a file.
  """
  f = open(path)
  lines = Transform(f.readlines())
  if lines:
    f = open(path, 'w')
    for l in lines:
      f.write(l)
    f.close()

def TransformFileAt ( path ) : f = open ( path ) lines = Transform ( f . readlines ( ) ) if lines : f = open ( path , 'w' ) for l in lines : f . write ( l ) f . close ( )


Returns true if the provided reference is a file and exists .

def IsValidFile(ref):
  """Returns true if the provided reference is a file and exists."""
  return os.path.isfile(ref)

def IsValidFile ( ref ) : return os . path . isfile ( ref )


Returns true if the provided reference is a Javascript file .

def IsJsFile(ref):
  """Returns true if the provided reference is a Javascript file."""
  return ref.endswith('.js')

def IsJsFile ( ref ) : return ref . endswith ( '.js' )


Returns true if the provided reference is a namespace .

def IsNamespace(ref):
  """Returns true if the provided reference is a namespace."""
  return re.match(ns_regex, ref) is not None

def IsNamespace ( ref ) : return re . match ( ns_regex , ref ) is not None


Returns true if the provided reference is a directory .

def IsDirectory(ref):
  """Returns true if the provided reference is a directory."""
  return os.path.isdir(ref)

def IsDirectory ( ref ) : return os . path . isdir ( ref )


Expands any directory references into inputs .

def ExpandDirectories(refs):
  """Expands any directory references into inputs.

  Description:
    Looks for any directories in the provided references.  Found directories
    are recursively searched for .js files, which are then added to the result
    list.

  Args:
    refs: a list of references such as files, directories, and namespaces

  Returns:
    A list of references with directories removed and replaced by any
    .js files that are found in them. Also, the paths will be normalized.
  """
  result = []
  for ref in refs:
    if IsDirectory(ref):
      # Disable 'Unused variable' for subdirs
      # pylint: disable=unused-variable
      for (directory, subdirs, filenames) in os.walk(ref):
        for filename in filenames:
          if IsJsFile(filename):
            result.append(os.path.join(directory, filename))
    else:
      result.append(ref)
  return map(os.path.normpath, result)

def
ExpandDirectories
(
refs
)
:
result
=
[
]
for
ref
in
refs
:
if
IsDirectory
(
ref
)
:
# Disable 'Unused variable' for subdirs
# pylint: disable=unused-variable
for
(
directory
,
subdirs
,
filenames
)
in
os
.
walk
(
ref
)
:
for
filename
in
filenames
:
if
IsJsFile
(
filename
)
:
result
.
append
(
os
.
path
.
join
(
directory
,
filename
)
)
else
:
result
.
append
(
ref
)
return
map
(
os
.
path
.
normpath
,
result
)


Build a list of dependencies from a list of files .

def BuildDependenciesFromFiles(files):
  """Build a list of dependencies from a list of files.

  Description:
    Takes a list of files, extracts their provides and requires, and builds
    out a list of dependency objects.

  Args:
    files: a list of files to be parsed for goog.provides and goog.requires.

  Returns:
    A list of dependency objects, one for each file in the files argument.
  """
  result = []
  filenames = set()
  for filename in files:
    if filename in filenames:
      continue

    # Python 3 requires the file encoding to be specified
    if (sys.version_info[0] < 3):
      file_handle = open(filename, 'r')
    else:
      file_handle = open(filename, 'r', encoding='utf8')

    try:
      dep = CreateDependencyInfo(filename, file_handle)
      result.append(dep)
    finally:
      file_handle.close()

    filenames.add(filename)

  return result


def CreateDependencyInfo(filename, source):
  """Create dependency info.

  Args:
    filename: Filename for source.
    source: File-like object containing source.

  Returns:
    A DependencyInfo object with provides and requires filled.
  """
  dep = DependencyInfo(filename)
  for line in source:
    if re.match(req_regex, line):
      dep.requires.append(re.search(req_regex, line).group(1))
    if re.match(prov_regex, line):
      dep.provides.append(re.search(prov_regex, line).group(1))
  return dep

def CreateDependencyInfo ( filename , source ) : dep = DependencyInfo ( filename ) for line in source : if re . match ( req_regex , line ) : dep . requires . append ( re . search ( req_regex , line ) . group ( 1 ) ) if re . match ( prov_regex , line ) : dep . provides . append ( re . search ( prov_regex , line ) . group ( 1 ) ) return dep


Builds a hash for searching dependencies by the namespaces they provide .

def BuildDependencyHashFromDependencies(deps):
  """Builds a hash for searching dependencies by the namespaces they provide.

  Description:
    Dependency objects can provide multiple namespaces.  This method enumerates
    the provides of each dependency and adds them to a hash that can be used
    to easily resolve a given dependency by a namespace it provides.

  Args:
    deps: a list of dependency objects used to build the hash.

  Raises:
    Exception: If a multiple files try to provide the same namepace.

  Returns:
    A hash table { namespace: dependency } that can be used to resolve a
    dependency by a namespace it provides.
  """
  dep_hash = {}
  for dep in deps:
    for provide in dep.provides:
      if provide in dep_hash:
        raise Exception('Duplicate provide (%s) in (%s, %s)' % (
            provide,
            dep_hash[provide].filename,
            dep.filename))
      dep_hash[provide] = dep
  return dep_hash

def
BuildDependencyHashFromDependencies
(
deps
)
:
dep_hash
=
{
}
for
dep
in
deps
:
for
provide
in
dep
.
provides
:
if
provide
in
dep_hash
:
raise
Exception
(
'Duplicate provide (%s) in (%s, %s)'
%
(
provide
,
dep_hash
[
provide
]
.
filename
,
dep
.
filename
)
)
dep_hash
[
provide
]
=
dep
return
dep_hash


Calculates the dependencies for given inputs .

def CalculateDependencies(paths, inputs):
  """Calculates the dependencies for given inputs.

  Description:
    This method takes a list of paths (files, directories) and builds a
    searchable data structure based on the namespaces that each .js file
    provides.  It then parses through each input, resolving dependencies
    against this data structure.  The final output is a list of files,
    including the inputs, that represent all of the code that is needed to
    compile the given inputs.

  Args:
    paths: the references (files, directories) that are used to build the
      dependency hash.
    inputs: the inputs (files, directories, namespaces) that have dependencies
      that need to be calculated.

  Raises:
    Exception: if a provided input is invalid.

  Returns:
    A list of all files, including inputs, that are needed to compile the given
    inputs.
  """
  deps = BuildDependenciesFromFiles(paths + inputs)
  search_hash = BuildDependencyHashFromDependencies(deps)
  result_list = []
  seen_list = []
  for input_file in inputs:
    if IsNamespace(input_file):
      namespace = re.search(ns_regex, input_file).group(1)
      if namespace not in search_hash:
        raise Exception('Invalid namespace (%s)' % namespace)
      input_file = search_hash[namespace].filename
    if not IsValidFile(input_file) or not IsJsFile(input_file):
      raise Exception('Invalid file (%s)' % input_file)
    seen_list.append(input_file)
    file_handle = open(input_file, 'r')
    try:
      for line in file_handle:
        if re.match(req_regex, line):
          require = re.search(req_regex, line).group(1)
          ResolveDependencies(require, search_hash, result_list, seen_list)
    finally:
      file_handle.close()
    result_list.append(input_file)

  # All files depend on base.js, so put it first.
  base_js_path = FindClosureBasePath(paths)
  if base_js_path:
    result_list.insert(0, base_js_path)
  else:
    logging.warning('Closure Library base.js not found.')

  return result_list


Given a list of file paths return Closure base . js path if any .

def FindClosureBasePath(paths):
  """Given a list of file paths, return Closure base.js path, if any.

  Args:
    paths: A list of paths.

  Returns:
    The path to Closure's base.js file including filename, if found.
  """

  for path in paths:
    pathname, filename = os.path.split(path)

    if filename == 'base.js':
      f = open(path)

      is_base = False

      # Sanity check that this is the Closure base file.  Check that this
      # is where goog is defined.  This is determined by the @provideGoog
      # flag.
      for line in f:
        if '@provideGoog' in line:
          is_base = True
          break

      f.close()

      if is_base:
        return path


Takes a given requirement and resolves all of the dependencies for it .

def ResolveDependencies(require, search_hash, result_list, seen_list):
  """Takes a given requirement and resolves all of the dependencies for it.

  Description:
    A given requirement may require other dependencies.  This method
    recursively resolves all dependencies for the given requirement.

  Raises:
    Exception: when require does not exist in the search_hash.

  Args:
    require: the namespace to resolve dependencies for.
    search_hash: the data structure used for resolving dependencies.
    result_list: a list of filenames that have been calculated as dependencies.
      This variable is the output for this function.
    seen_list: a list of filenames that have been 'seen'.  This is required
      for the dependency->dependant ordering.
  """
  if require not in search_hash:
    raise Exception('Missing provider for (%s)' % require)

  dep = search_hash[require]
  if not dep.filename in seen_list:
    seen_list.append(dep.filename)
    for sub_require in dep.requires:
      ResolveDependencies(sub_require, search_hash, result_list, seen_list)
    result_list.append(dep.filename)

def
ResolveDependencies
(
require
,
search_hash
,
result_list
,
seen_list
)
:
if
require
not
in
search_hash
:
raise
Exception
(
'Missing provider for (%s)'
%
require
)
dep
=
search_hash
[
require
]
if
not
dep
.
filename
in
seen_list
:
seen_list
.
append
(
dep
.
filename
)
for
sub_require
in
dep
.
requires
:
ResolveDependencies
(
sub_require
,
search_hash
,
result_list
,
seen_list
)
result_list
.
append
(
dep
.
filename
)


Returns a JS string for a dependency statement in the deps . js file .

def GetDepsLine(dep, base_path):
  """Returns a JS string for a dependency statement in the deps.js file.

  Args:
    dep: The dependency that we're printing.
    base_path: The path to Closure's base.js including filename.
  """
  return 'goog.addDependency("%s", %s, %s);' % (
      GetRelpath(dep.filename, base_path), dep.provides, dep.requires)

def
GetDepsLine
(
dep
,
base_path
)
:
return
'goog.addDependency("%s", %s, %s);'
%
(
GetRelpath
(
dep
.
filename
,
base_path
)
,
dep
.
provides
,
dep
.
requires
)


Return a relative path to |path| from |start| .

def GetRelpath(path, start):
  """Return a relative path to |path| from |start|."""
  # NOTE: Python 2.6 provides os.path.relpath, which has almost the same
  # functionality as this function. Since we want to support 2.4, we have
  # to implement it manually. :(
  path_list = os.path.abspath(os.path.normpath(path)).split(os.sep)
  start_list = os.path.abspath(
      os.path.normpath(os.path.dirname(start))).split(os.sep)

  common_prefix_count = 0
  for i in range(0, min(len(path_list), len(start_list))):
    if path_list[i] != start_list[i]:
      break
    common_prefix_count += 1

  # Always use forward slashes, because this will get expanded to a url,
  # not a file path.
  return '/'.join(['..'] * (len(start_list) - common_prefix_count) +
                  path_list[common_prefix_count:])


Print out a deps . js file from a list of source paths .

def PrintDeps(source_paths, deps, out):
  """Print out a deps.js file from a list of source paths.

  Args:
    source_paths: Paths that we should generate dependency info for.
    deps: Paths that provide dependency info. Their dependency info should
        not appear in the deps file.
    out: The output file.

  Returns:
    True on success, false if it was unable to find the base path
    to generate deps relative to.
  """
  base_path = FindClosureBasePath(source_paths + deps)
  if not base_path:
    return False

  PrintLine('// This file was autogenerated by calcdeps.py', out)
  excludesSet = set(deps)

  for dep in BuildDependenciesFromFiles(source_paths + deps):
    if not dep.filename in excludesSet:
      PrintLine(GetDepsLine(dep, base_path), out)

  return True


Returns the string for the current version of Java installed .

def GetJavaVersion():
  """Returns the string for the current version of Java installed."""
  proc = subprocess.Popen(['java', '-version'], stderr=subprocess.PIPE)
  proc.wait()
  version_line = proc.stderr.read().splitlines()[0]
  return version_regex.search(version_line).group()

def GetJavaVersion ( ) : proc = subprocess . Popen ( [ 'java' , '-version' ] , stderr = subprocess . PIPE ) proc . wait ( ) version_line = proc . stderr . read ( ) . splitlines ( ) [ 0 ] return version_regex . search ( version_line ) . group ( )


Filters the given files by the exlusions specified at the command line .

def FilterByExcludes(options, files):
  """Filters the given files by the exlusions specified at the command line.

  Args:
    options: The flags to calcdeps.
    files: The files to filter.
  Returns:
    A list of files.
  """
  excludes = []
  if options.excludes:
    excludes = ExpandDirectories(options.excludes)

  excludesSet = set(excludes)
  return [i for i in files if not i in excludesSet]

def FilterByExcludes ( options , files ) : excludes = [ ] if options . excludes : excludes = ExpandDirectories ( options . excludes ) excludesSet = set ( excludes ) return [ i for i in files if not i in excludesSet ]


Generates the path files from flag options .

def GetPathsFromOptions(options):
  """Generates the path files from flag options.

  Args:
    options: The flags to calcdeps.
  Returns:
    A list of files in the specified paths. (strings).
  """

  search_paths = options.paths
  if not search_paths:
    search_paths = ['.']  # Add default folder if no path is specified.

  search_paths = ExpandDirectories(search_paths)
  return FilterByExcludes(options, search_paths)


Generates the inputs from flag options .

def GetInputsFromOptions(options):
  """Generates the inputs from flag options.

  Args:
    options: The flags to calcdeps.
  Returns:
    A list of inputs (strings).
  """
  inputs = options.inputs
  if not inputs:  # Parse stdin
    logging.info('No inputs specified. Reading from stdin...')
    inputs = filter(None, [line.strip('\n') for line in sys.stdin.readlines()])

  logging.info('Scanning files...')
  inputs = ExpandDirectories(inputs)

  return FilterByExcludes(options, inputs)


Prepares command - line call to Closure compiler .

def Compile(compiler_jar_path, source_paths, out, flags=None):
  """Prepares command-line call to Closure compiler.

  Args:
    compiler_jar_path: Path to the Closure compiler .jar file.
    source_paths: Source paths to build, in order.
    flags: A list of additional flags to pass on to Closure compiler.
  """
  args = ['java', '-jar', compiler_jar_path]
  for path in source_paths:
    args += ['--js', path]

  if flags:
    args += flags

  logging.info('Compiling with the following command: %s', ' '.join(args))
  proc = subprocess.Popen(args, stdout=subprocess.PIPE)
  (stdoutdata, stderrdata) = proc.communicate()
  if proc.returncode != 0:
    logging.error('JavaScript compilation failed.')
    sys.exit(1)
  else:
    out.write(stdoutdata)


def main():
  """The entrypoint for this script."""

  logging.basicConfig(format='calcdeps.py: %(message)s', level=logging.INFO)

  usage = 'usage: %prog [options] arg'
  parser = optparse.OptionParser(usage)
  parser.add_option('-i',
                    '--input',
                    dest='inputs',
                    action='append',
                    help='The inputs to calculate dependencies for. Valid '
                    'values can be files, directories, or namespaces '
                    '(ns:goog.net.XhrIo).  Only relevant to "list" and '
                    '"script" output.')
  parser.add_option('-p',
                    '--path',
                    dest='paths',
                    action='append',
                    help='The paths that should be traversed to build the '
                    'dependencies.')
  parser.add_option('-d',
                    '--dep',
                    dest='deps',
                    action='append',
                    help='Directories or files that should be traversed to '
                    'find required dependencies for the deps file. '
                    'Does not generate dependency information for names '
                    'provided by these files. Only useful in "deps" mode.')
  parser.add_option('-e',
                    '--exclude',
                    dest='excludes',
                    action='append',
                    help='Files or directories to exclude from the --path '
                    'and --input flags')
  parser.add_option('-o',
                    '--output_mode',
                    dest='output_mode',
                    action='store',
                    default='list',
                    help='The type of output to generate from this script. '
                    'Options are "list" for a list of filenames, "script" '
                    'for a single script containing the contents of all the '
                    'file, "deps" to generate a deps.js file for all '
                    'paths, or "compiled" to produce compiled output with '
                    'the Closure compiler.')
  parser.add_option('-c',
                    '--compiler_jar',
                    dest='compiler_jar',
                    action='store',
                    help='The location of the Closure compiler .jar file.')
  parser.add_option('-f',
                    '--compiler_flag',
                    '--compiler_flags', # for backwards compatability
                    dest='compiler_flags',
                    action='append',
                    help='Additional flag to pass to the Closure compiler. '
                    'May be specified multiple times to pass multiple flags.')
  parser.add_option('--output_file',
                    dest='output_file',
                    action='store',
                    help=('If specified, write output to this path instead of '
                          'writing to standard output.'))

  (options, args) = parser.parse_args()

  search_paths = GetPathsFromOptions(options)

  if options.output_file:
    out = open(options.output_file, 'w')
  else:
    out = sys.stdout

  if options.output_mode == 'deps':
    result = PrintDeps(search_paths, ExpandDirectories(options.deps or []), out)
    if not result:
      logging.error('Could not find Closure Library in the specified paths')
      sys.exit(1)

    return

  inputs = GetInputsFromOptions(options)

  logging.info('Finding Closure dependencies...')
  deps = CalculateDependencies(search_paths, inputs)
  output_mode = options.output_mode

  if output_mode == 'script':
    PrintScript(deps, out)
  elif output_mode == 'list':
    # Just print out a dep per line
    for dep in deps:
      PrintLine(dep, out)
  elif output_mode == 'compiled':
    # Make sure a .jar is specified.
    if not options.compiler_jar:
      logging.error('--compiler_jar flag must be specified if --output is '
                    '"compiled"')
      sys.exit(1)

    # User friendly version check.
    if distutils and not (distutils.version.LooseVersion(GetJavaVersion()) >
        distutils.version.LooseVersion('1.6')):
      logging.error('Closure Compiler requires Java 1.6 or higher.')
      logging.error('Please visit http://www.java.com/getjava')
      sys.exit(1)

    Compile(options.compiler_jar, deps, out, options.compiler_flags)

  else:
    logging.error('Invalid value for --output flag.')
    sys.exit(1)


Match the first function seen in the script .

def _MatchFirstFunction(script):
  """Match the first function seen in the script."""
  return _FUNCTION_REGEX.search(script)

def _MatchFirstFunction ( script ) : return _FUNCTION_REGEX . search ( script )


Parse an argument string ( inside parens ) into parameter names .

def _ParseArgString(arg_string):
  """Parse an argument string (inside parens) into parameter names."""
  for arg in arg_string.split(','):
    arg = arg.strip()
    if arg:
      yield arg

def _ParseArgString ( arg_string ) : for arg in arg_string . split ( ',' ) : arg = arg . strip ( ) if arg : yield arg


def _ExtractFunctionBody(script, indentation=0):
  """Attempt to return the function body."""

  # Real extraction would require a token parser and state machines.
  # We look for first bracket at the same level of indentation.
  regex_str = r'{(.*?)^[ ]{%d}}' % indentation

  function_regex = re.compile(regex_str, re.MULTILINE | re.DOTALL)
  match = function_regex.search(script)
  if match:
    return match.group(1)


Attempt to determine if the function body returns a value .

def _ContainsReturnValue(function_body):
  """Attempt to determine if the function body returns a value."""
  return_regex = re.compile(r'\breturn\b[^;]')

  # If this matches, we assume they're returning something.
  return bool(return_regex.search(function_body))


Insert a string into another string at a given index .

def _InsertString(original_string, inserted_string, index):
  """Insert a string into another string at a given index."""
  return original_string[0:index] + inserted_string + original_string[index:]

def _InsertString ( original_string , inserted_string , index ) : return original_string [ 0 : index ] + inserted_string + original_string [ index : ]


def _GenerateJsDoc(args, return_val=False):
  """Generate JSDoc for a function.

  Args:
    args: A list of names of the argument.
    return_val: Whether the function has a return value.

  Returns:
    The JSDoc as a string.
  """

  lines = []
  lines.append('/**')

  lines += [' * @param {} %s' % arg for arg in args]

  if return_val:
    lines.append(' * @return')

  lines.append(' */')

  return '\n'.join(lines) + '\n'

def
_GenerateJsDoc
(
args
,
return_val
=
False
)
:
lines
=
[
]
lines
.
append
(
'/**'
)
lines
+=
[
' * @param {} %s'
%
arg
for
arg
in
args
]
if
return_val
:
lines
.
append
(
' * @return'
)
lines
.
append
(
' */'
)
return
'\n'
.
join
(
lines
)
+
'\n'


Indent string some number of characters .

def _IndentString(source_string, indentation):
  """Indent string some number of characters."""
  lines = [(indentation * ' ') + line
           for line in source_string.splitlines(True)]
  return ''.join(lines)

def
_IndentString
(
source_string
,
indentation
)
:
lines
=
[
(
indentation
*
' '
)
+
line
for
line
in
source_string
.
splitlines
(
True
)
]
return
''
.
join
(
lines
)


Attempt to insert JSDoc for the first seen function in the script .

def InsertJsDoc(script):
  """Attempt to insert JSDoc for the first seen function in the script.

  Args:
    script: The script, as a string.

  Returns:
    Returns the new string if function was found and JSDoc inserted. Otherwise
      returns None.
  """

  match = _MatchFirstFunction(script)
  if not match:
    return

  # Add argument flags.
  args_string = match.group('arguments')
  args = _ParseArgString(args_string)

  start_index = match.start(0)
  function_to_end = script[start_index:]

  lvalue_indentation = len(match.group('indentation'))

  return_val = False
  function_body = _ExtractFunctionBody(function_to_end, lvalue_indentation)
  if function_body:
    return_val = _ContainsReturnValue(function_body)

  jsdoc = _GenerateJsDoc(args, return_val)
  if lvalue_indentation:
    jsdoc = _IndentString(jsdoc, lvalue_indentation)

  return _InsertString(script, jsdoc, start_index)

def
InsertJsDoc
(
script
)
:
match
=
_MatchFirstFunction
(
script
)
if
not
match
:
return
# Add argument flags.
args_string
=
match
.
group
(
'arguments'
)
args
=
_ParseArgString
(
args_string
)
start_index
=
match
.
start
(
0
)
function_to_end
=
script
[
start_index
:
]
lvalue_indentation
=
len
(
match
.
group
(
'indentation'
)
)
return_val
=
False
function_body
=
_ExtractFunctionBody
(
function_to_end
,
lvalue_indentation
)
if
function_body
:
return_val
=
_ContainsReturnValue
(
function_body
)
jsdoc
=
_GenerateJsDoc
(
args
,
return_val
)
if
lvalue_indentation
:
jsdoc
=
_IndentString
(
jsdoc
,
lvalue_indentation
)
return
_InsertString
(
script
,
jsdoc
,
start_index
)


Initializes the tree with a set of sources .

def __init__(self, sources):
    """Initializes the tree with a set of sources.

    Args:
      sources: A set of JavaScript sources.

    Raises:
      MultipleProvideError: A namespace is provided by muplitple sources.
      NamespaceNotFoundError: A namespace is required but never provided.
    """

    self._sources = sources
    self._provides_map = dict()

    # Ensure nothing was provided twice.
    for source in sources:
      for provide in source.provides:
        if provide in self._provides_map:
          raise MultipleProvideError(
              provide, [self._provides_map[provide], source])

        self._provides_map[provide] = source

    # Check that all required namespaces are provided.
    for source in sources:
      for require in source.requires:
        if require not in self._provides_map:
          raise NamespaceNotFoundError(require, source)


Get source dependencies in order for the given namespaces .

def GetDependencies(self, required_namespaces):
    """Get source dependencies, in order, for the given namespaces.

    Args:
      required_namespaces: A string (for one) or list (for one or more) of
        namespaces.

    Returns:
      A list of source objects that provide those namespaces and all
      requirements, in dependency order.

    Raises:
      NamespaceNotFoundError: A namespace is requested but doesn't exist.
      CircularDependencyError: A cycle is detected in the dependency tree.
    """
    if isinstance(required_namespaces, str):
      required_namespaces = [required_namespaces]

    deps_sources = []

    for namespace in required_namespaces:
      for source in DepsTree._ResolveDependencies(
          namespace, [], self._provides_map, []):
        if source not in deps_sources:
          deps_sources.append(source)

    return deps_sources

def GetDependencies ( self , required_namespaces ) : if isinstance ( required_namespaces , str ) : required_namespaces = [ required_namespaces ] deps_sources = [ ] for namespace in required_namespaces : for source in DepsTree . _ResolveDependencies ( namespace , [ ] , self . _provides_map , [ ] ) : if source not in deps_sources : deps_sources . append ( source ) return deps_sources


Resolve dependencies for Closure source files .

def _ResolveDependencies(required_namespace, deps_list, provides_map,
                           traversal_path):
    """Resolve dependencies for Closure source files.

    Follows the dependency tree down and builds a list of sources in dependency
    order.  This function will recursively call itself to fill all dependencies
    below the requested namespaces, and then append its sources at the end of
    the list.

    Args:
      required_namespace: String of required namespace.
      deps_list: List of sources in dependency order.  This function will append
        the required source once all of its dependencies are satisfied.
      provides_map: Map from namespace to source that provides it.
      traversal_path: List of namespaces of our path from the root down the
        dependency/recursion tree.  Used to identify cyclical dependencies.
        This is a list used as a stack -- when the function is entered, the
        current namespace is pushed and popped right before returning.
        Each recursive call will check that the current namespace does not
        appear in the list, throwing a CircularDependencyError if it does.

    Returns:
      The given deps_list object filled with sources in dependency order.

    Raises:
      NamespaceNotFoundError: A namespace is requested but doesn't exist.
      CircularDependencyError: A cycle is detected in the dependency tree.
    """

    source = provides_map.get(required_namespace)
    if not source:
      raise NamespaceNotFoundError(required_namespace)

    if required_namespace in traversal_path:
      traversal_path.append(required_namespace)  # do this *after* the test

      # This must be a cycle.
      raise CircularDependencyError(traversal_path)

    # If we don't have the source yet, we'll have to visit this namespace and
    # add the required dependencies to deps_list.
    if source not in deps_list:
      traversal_path.append(required_namespace)

      for require in source.requires:

        # Append all other dependencies before we append our own.
        DepsTree._ResolveDependencies(require, deps_list, provides_map,
                                      traversal_path)
      deps_list.append(source)

      traversal_path.pop()

    return deps_list


Get the version string from the Java VM .

def _GetJavaVersionString():
  """Get the version string from the Java VM."""
  return subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT)

def _GetJavaVersionString ( ) : return subprocess . check_output ( [ 'java' , '-version' ] , stderr = subprocess . STDOUT )


Returns a 2 - tuple for the current version of Java installed .

def _ParseJavaVersion(version_string):
  """Returns a 2-tuple for the current version of Java installed.

  Args:
    version_string: String of the Java version (e.g. '1.7.2-ea').

  Returns:
    The major and minor versions, as a 2-tuple (e.g. (1, 7)).
  """
  match = _VERSION_REGEX.search(version_string)
  if match:
    version = tuple(int(x, 10) for x in match.groups())
    assert len(version) == 2
    return version

def _ParseJavaVersion ( version_string ) : match = _VERSION_REGEX . search ( version_string ) if match : version = tuple ( int ( x , 10 ) for x in match . groups ( ) ) assert len ( version ) == 2 return version


Determines whether the JVM supports 32 - bit mode on the platform .

def _JavaSupports32BitMode():
  """Determines whether the JVM supports 32-bit mode on the platform."""
  # Suppresses process output to stderr and stdout from showing up in the
  # console as we're only trying to determine 32-bit JVM support.
  supported = False
  try:
    devnull = open(os.devnull, 'wb')
    return subprocess.call(
        ['java', '-d32', '-version'], stdout=devnull, stderr=devnull) == 0
  except IOError:
    pass
  else:
    devnull.close()
  return supported


Assembles arguments for call to JsCompiler .

def _GetJsCompilerArgs(compiler_jar_path, java_version, source_paths,
                       jvm_flags, compiler_flags):
  """Assembles arguments for call to JsCompiler."""

  if java_version < (1, 7):
    raise JsCompilerError('Closure Compiler requires Java 1.7 or higher. '
                          'Please visit http://www.java.com/getjava')

  args = ['java']

  # Add JVM flags we believe will produce the best performance.  See
  # https://groups.google.com/forum/#!topic/closure-library-discuss/7w_O9-vzlj4

  # Attempt 32-bit mode if available (Java 7 on Mac OS X does not support 32-bit
  # mode, for example).
  if _JavaSupports32BitMode():
    args += ['-d32']

  # Prefer the "client" VM.
  args += ['-client']

  # Add JVM flags, if any
  if jvm_flags:
    args += jvm_flags

  # Add the application JAR.
  args += ['-jar', compiler_jar_path]

  for path in source_paths:
    args += ['--js', path]

  # Add compiler flags, if any.
  if compiler_flags:
    args += compiler_flags

  return args


Prepares command - line call to Closure Compiler .

def Compile(compiler_jar_path, source_paths,
            jvm_flags=None,
            compiler_flags=None):
  """Prepares command-line call to Closure Compiler.

  Args:
    compiler_jar_path: Path to the Closure compiler .jar file.
    source_paths: Source paths to build, in order.
    jvm_flags: A list of additional flags to pass on to JVM.
    compiler_flags: A list of additional flags to pass on to Closure Compiler.

  Returns:
    The compiled source, as a string, or None if compilation failed.
  """

  java_version = _ParseJavaVersion(_GetJavaVersionString())

  args = _GetJsCompilerArgs(
      compiler_jar_path, java_version, source_paths, jvm_flags, compiler_flags)

  logging.info('Compiling with the following command: %s', ' '.join(args))

  try:
    return subprocess.check_output(args)
  except subprocess.CalledProcessError:
    raise JsCompilerError('JavaScript compilation failed.')


def _GetOptionsParser():
  """Get the options parser."""

  parser = optparse.OptionParser(__doc__)
  parser.add_option('-i',
                    '--input',
                    dest='inputs',
                    action='append',
                    default=[],
                    help='One or more input files to calculate dependencies '
                    'for.  The namespaces in this file will be combined with '
                    'those given with the -n flag to form the set of '
                    'namespaces to find dependencies for.')
  parser.add_option('-n',
                    '--namespace',
                    dest='namespaces',
                    action='append',
                    default=[],
                    help='One or more namespaces to calculate dependencies '
                    'for.  These namespaces will be combined with those given '
                    'with the -i flag to form the set of namespaces to find '
                    'dependencies for.  A Closure namespace is a '
                    'dot-delimited path expression declared with a call to '
                    'goog.provide() (e.g. "goog.array" or "foo.bar").')
  parser.add_option('--root',
                    dest='roots',
                    action='append',
                    default=[],
                    help='The paths that should be traversed to build the '
                    'dependencies.')
  parser.add_option('-o',
                    '--output_mode',
                    dest='output_mode',
                    type='choice',
                    action='store',
                    choices=['list', 'script', 'compiled'],
                    default='list',
                    help='The type of output to generate from this script. '
                    'Options are "list" for a list of filenames, "script" '
                    'for a single script containing the contents of all the '
                    'files, or "compiled" to produce compiled output with '
                    'the Closure Compiler.  Default is "list".')
  parser.add_option('-c',
                    '--compiler_jar',
                    dest='compiler_jar',
                    action='store',
                    help='The location of the Closure compiler .jar file.')
  parser.add_option('-f',
                    '--compiler_flags',
                    dest='compiler_flags',
                    default=[],
                    action='append',
                    help='Additional flags to pass to the Closure compiler. '
                    'To pass multiple flags, --compiler_flags has to be '
                    'specified multiple times.')
  parser.add_option('-j',
                    '--jvm_flags',
                    dest='jvm_flags',
                    default=[],
                    action='append',
                    help='Additional flags to pass to the JVM compiler. '
                    'To pass multiple flags, --jvm_flags has to be '
                    'specified multiple times.')
  parser.add_option('--output_file',
                    dest='output_file',
                    action='store',
                    help=('If specified, write output to this path instead of '
                          'writing to standard output.'))

  return parser


def _GetInputByPath(path, sources):
  """Get the source identified by a path.

  Args:
    path: str, A path to a file that identifies a source.
    sources: An iterable collection of source objects.

  Returns:
    The source from sources identified by path, if found.  Converts to
    absolute paths for comparison.
  """
  for js_source in sources:
    # Convert both to absolute paths for comparison.
    if os.path.abspath(path) == os.path.abspath(js_source.GetPath()):
      return js_source


Given a set of sources returns the one base . js file .

def _GetClosureBaseFile(sources):
  """Given a set of sources, returns the one base.js file.

  Note that if zero or two or more base.js files are found, an error message
  will be written and the program will be exited.

  Args:
    sources: An iterable of _PathSource objects.

  Returns:
    The _PathSource representing the base Closure file.
  """
  base_files = [
      js_source for js_source in sources if _IsClosureBaseFile(js_source)]

  if not base_files:
    logging.error('No Closure base.js file found.')
    sys.exit(1)
  if len(base_files) > 1:
    logging.error('More than one Closure base.js files found at these paths:')
    for base_file in base_files:
      logging.error(base_file.GetPath())
    sys.exit(1)
  return base_files[0]


Returns true if the given _PathSource is the Closure base . js source .

def _IsClosureBaseFile(js_source):
  """Returns true if the given _PathSource is the Closure base.js source."""
  return (os.path.basename(js_source.GetPath()) == 'base.js' and
          js_source.provides == set(['goog']))

def _IsClosureBaseFile ( js_source ) : return ( os . path . basename ( js_source . GetPath ( ) ) == 'base.js' and js_source . provides == set ( [ 'goog' ] ) )


def __init__(self, path):
    """Initialize a source.

    Args:
      path: str, Path to a JavaScript file.  The source string will be read
        from this file.
    """
    super(_PathSource, self).__init__(source.GetFileContents(path))

    self._path = path

def __init__ ( self , path ) : super ( _PathSource , self ) . __init__ ( source . GetFileContents ( path ) ) self . _path = path


def GetPath(self):
    """Returns the path."""
    return self._path

def GetPath ( self ) : return self . _path


def MakeDepsFile(source_map):
  """Make a generated deps file.

  Args:
    source_map: A dict map of the source path to source.Source object.

  Returns:
    str, A generated deps file source.
  """

  # Write in path alphabetical order
  paths = sorted(source_map.keys())

  lines = []

  for path in paths:
    js_source = source_map[path]

    # We don't need to add entries that don't provide anything.
    if js_source.provides:
      lines.append(_GetDepsLine(path, js_source))

  return ''.join(lines)


Get a deps . js file string for a source .

def _GetDepsLine(path, js_source):
  """Get a deps.js file string for a source."""

  provides = sorted(js_source.provides)
  requires = sorted(js_source.requires)
  module = 'true' if js_source.is_goog_module else 'false'

  return 'goog.addDependency(\'%s\', %s, %s, %s);\n' % (
      path, provides, requires, module)


def _GetOptionsParser():
  """Get the options parser."""

  parser = optparse.OptionParser(__doc__)

  parser.add_option('--output_file',
                    dest='output_file',
                    action='store',
                    help=('If specified, write output to this path instead of '
                          'writing to standard output.'))
  parser.add_option('--root',
                    dest='roots',
                    default=[],
                    action='append',
                    help='A root directory to scan for JS source files. '
                    'Paths of JS files in generated deps file will be '
                    'relative to this path.  This flag may be specified '
                    'multiple times.')
  parser.add_option('--root_with_prefix',
                    dest='roots_with_prefix',
                    default=[],
                    action='append',
                    help='A root directory to scan for JS source files, plus '
                    'a prefix (if either contains a space, surround with '
                    'quotes).  Paths in generated deps file will be relative '
                    'to the root, but preceded by the prefix.  This flag '
                    'may be specified multiple times.')
  parser.add_option('--path_with_depspath',
                    dest='paths_with_depspath',
                    default=[],
                    action='append',
                    help='A path to a source file and an alternate path to '
                    'the file in the generated deps file (if either contains '
                    'a space, surround with whitespace). This flag may be '
                    'specified multiple times.')
  return parser


Replaces OS - specific path separators with POSIX - style slashes .

def _NormalizePathSeparators(path):
  """Replaces OS-specific path separators with POSIX-style slashes.

  Args:
    path: str, A file path.

  Returns:
    str, The path with any OS-specific path separators (such as backslash on
      Windows) replaced with URL-compatible forward slashes. A no-op on systems
      that use POSIX paths.
  """
  return path.replace(os.sep, posixpath.sep)

def _NormalizePathSeparators ( path ) : return path . replace ( os . sep , posixpath . sep )


Scans a top root directory for . js sources .

def _GetRelativePathToSourceDict(root, prefix=''):
  """Scans a top root directory for .js sources.

  Args:
    root: str, Root directory.
    prefix: str, Prefix for returned paths.

  Returns:
    dict, A map of relative paths (with prefix, if given), to source.Source
      objects.
  """
  # Remember and restore the cwd when we're done. We work from the root so
  # that paths are relative from the root.
  start_wd = os.getcwd()
  os.chdir(root)

  path_to_source = {}
  for path in treescan.ScanTreeForJsFiles('.'):
    prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path))
    path_to_source[prefixed_path] = source.Source(source.GetFileContents(path))

  os.chdir(start_wd)

  return path_to_source


Return a string as a shell - parsed tuple . Two values expected .

def _GetPair(s):
  """Return a string as a shell-parsed tuple.  Two values expected."""
  try:
    # shlex uses '\' as an escape character, so they must be escaped.
    s = s.replace('\\', '\\\\')
    first, second = shlex.split(s)
    return (first, second)
  except:
    raise Exception('Unable to parse input line as a pair: %s' % s)


def main():
  """CLI frontend to MakeDepsFile."""
  logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
                      level=logging.INFO)
  options, args = _GetOptionsParser().parse_args()

  path_to_source = {}

  # Roots without prefixes
  for root in options.roots:
    path_to_source.update(_GetRelativePathToSourceDict(root))

  # Roots with prefixes
  for root_and_prefix in options.roots_with_prefix:
    root, prefix = _GetPair(root_and_prefix)
    path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix))

  # Source paths
  for path in args:
    path_to_source[path] = source.Source(source.GetFileContents(path))

  # Source paths with alternate deps paths
  for path_with_depspath in options.paths_with_depspath:
    srcpath, depspath = _GetPair(path_with_depspath)
    path_to_source[depspath] = source.Source(source.GetFileContents(srcpath))

  # Make our output pipe.
  if options.output_file:
    out = open(options.output_file, 'w')
  else:
    out = sys.stdout

  out.write('// This file was autogenerated by %s.\n' % sys.argv[0])
  out.write('// Please do not edit.\n')

  out.write(MakeDepsFile(path_to_source))


Scans a directory tree for JavaScript files .

def ScanTreeForJsFiles(root):
  """Scans a directory tree for JavaScript files.

  Args:
    root: str, Path to a root directory.

  Returns:
    An iterable of paths to JS files, relative to cwd.
  """
  return ScanTree(root, path_filter=_JS_FILE_REGEX)

def ScanTreeForJsFiles ( root ) : return ScanTree ( root , path_filter = _JS_FILE_REGEX )


def ScanTree(root, path_filter=None, ignore_hidden=True):
  """Scans a directory tree for files.

  Args:
    root: str, Path to a root directory.
    path_filter: A regular expression filter.  If set, only paths matching
      the path_filter are returned.
    ignore_hidden: If True, do not follow or return hidden directories or files
      (those starting with a '.' character).

  Yields:
    A string path to files, relative to cwd.
  """

  def OnError(os_error):
    raise os_error

  for dirpath, dirnames, filenames in os.walk(root, onerror=OnError):
    # os.walk allows us to modify dirnames to prevent decent into particular
    # directories.  Avoid hidden directories.
    for dirname in dirnames:
      if ignore_hidden and dirname.startswith('.'):
        dirnames.remove(dirname)

    for filename in filenames:

      # nothing that starts with '.'
      if ignore_hidden and filename.startswith('.'):
        continue

      fullpath = os.path.join(dirpath, filename)

      if path_filter and not path_filter.match(fullpath):
        continue

      yield os.path.normpath(fullpath)


def GetFileContents(path):
  """Get a file's contents as a string.

  Args:
    path: str, Path to file.

  Returns:
    str, Contents of file.

  Raises:
    IOError: An error occurred opening or reading the file.

  """
  fileobj = open(path)
  try:
    return fileobj.read()
  finally:
    fileobj.close()

def GetFileContents ( path ) : fileobj = open ( path ) try : return fileobj . read ( ) finally : fileobj . close ( )


def __init__(self, source):
    """Initialize a source.

    Args:
      source: str, The JavaScript source.
    """

    self.provides = set()
    self.requires = set()
    self.is_goog_module = False

    self._source = source
    self._ScanSource()

def __init__ ( self , source ) : self . provides = set ( ) self . requires = set ( ) self . is_goog_module = False self . _source = source self . _ScanSource ( )


def GetSource(self):
    """Get the source as a string."""
    return self._source

def GetSource ( self ) : return self . _source


def _HasProvideGoogFlag(cls, source):
    """Determines whether the @provideGoog flag is in a comment."""
    for comment_content in cls._COMMENT_REGEX.findall(source):
      if '@provideGoog' in comment_content:
        return True

    return False

def _HasProvideGoogFlag ( cls , source ) : for comment_content in cls . _COMMENT_REGEX . findall ( source ) : if '@provideGoog' in comment_content : return True return False


Fill in provides and requires by scanning the source .

def _ScanSource(self):
    """Fill in provides and requires by scanning the source."""

    stripped_source = self._StripComments(self.GetSource())

    source_lines = stripped_source.splitlines()
    for line in source_lines:
      match = _PROVIDE_REGEX.match(line)
      if match:
        self.provides.add(match.group(1))
      match = _MODULE_REGEX.match(line)
      if match:
        self.provides.add(match.group(1))
        self.is_goog_module = True
      match = _REQUIRES_REGEX.match(line)
      if match:
        self.requires.add(match.group(1))

    # Closure's base file implicitly provides 'goog'.
    # This is indicated with the @provideGoog flag.
    if self._HasProvideGoogFlag(self.GetSource()):

      if len(self.provides) or len(self.requires):
        raise Exception(
            'Base file should not provide or require namespaces.')

      self.provides.add('goog')