text: #!/usr/bin/env python
  
  # Copyright 2020 The Tilt Brush Authors
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #      http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  # Tilt Brush-specific refgraph code
  import os
  import re
  
  
  def _get_command_lookup(project_dir):
    # Returns a dict that maps stuff to a GlobalCommand name
    txt = open(os.path.join(project_dir, 'Assets/Scripts/SketchControlsScript.cs')).read()
    pat = re.compile(r'public enum GlobalCommands[^}]+}')
    vals = pat.search(txt).group(0).split('\n')[1:-1]
    vals = [v.split(',')[0].strip() for v in vals]
    vals = [v for v in vals if not v.startswith('//')]
    for v in vals:
      assert re.match(r'^[a-zA-Z_]+$', v), "Doesn't look like an enum name: %r" % (v,)
    to_index = {}
    to_name = {}
    for (i, v) in enumerate(vals):
      for key in (v, v.lower(), i, str(i)):
        to_index[key] = i
        to_name[key] = v
    return to_name #, to_index
  
  
  def _iter_prefab_and_scene(project_dir):
    pat = re.compile(r'.*\.(unity|prefab)$')
    for (r, ds, fs) in os.walk(os.path.join(project_dir, 'Assets')):
      for f in fs:
        if pat.match(f):
          yield os.path.join(r, f)
  
  
  def _iter_cs(project_dir):
    for (r, ds, fs) in os.walk(os.path.join(project_dir, 'Assets')):
      for f in fs:
        if f.endswith('.cs'):
          yield os.path.join(r, f)
  
  
  def iter_command_nodes(project_dir):
    """Yields strings like 'GlobalCommands.ToggleWatermark'"""
    to_name = _get_command_lookup(project_dir)
    for name in set(to_name.values()):
      yield 'GlobalCommands.' + name
  
  
  def iter_command_edges(project_dir):
    """Yields tuples like ('Assets/Prefabs/MyPrefab.prefab', 'GlobalCommands.ShowTos')"""
    to_name = _get_command_lookup(project_dir)
    chop = len(project_dir) + 1
  
    def command_to_node_name(command):
      # Returns a canonical node name as it appears in the graph
      return 'GlobalCommands.' + to_name[command]
  
    def file_to_node_name(filename):
      # Returns a canonical node name as it appears in the graph
      # Relative to the project root, forward slashes, etc
      return os.path.relpath(filename, project_dir).replace('\\', '/')
  
    # Find references in .prefab, .unity files
    yaml_pat = re.compile(r'm_(Delayed)?Command: (?P<cmd>\d+)')
    for path in _iter_prefab_and_scene(project_dir):
      for match in yaml_pat.finditer(open(path).read()):
        yield file_to_node_name(path), command_to_node_name(match.group('cmd'))
  
    # Find references in .cs files
    cs_pat = re.compile(r'GlobalCommands\.[A-Za-z0-9_]+')
    for path in _iter_cs(project_dir):
      if path.endswith('DummyCommandRefs.cs'): continue
      for match in cs_pat.finditer(open(path).read()):
        yield file_to_node_name(path), match.group(0)
  
  
  def create_dummy_cs(project_dir, command_edges):
    from collections import defaultdict
    file_to_commands = defaultdict(set)
    for src, dst in command_edges:
      if src.endswith('.cs'): break
      file_to_commands[src].add(dst)
    file_to_commands = [(k, sorted(file_to_commands[k]))
                        for k in sorted(file_to_commands.keys())]
  
    def as_func(file_and_commands):
      file_name, commands = file_and_commands
      func_name = os.path.splitext(file_name)[0][7:]
      func_name = func_name.replace('/', '_').replace('.', '_')
      return ('  public static void %s() {\n' % func_name +
              '\n'.join('    Use(%s);' % c for c in commands) +
              '\n  }\n')
  
    cs_name = os.path.join(project_dir, 'Assets/Editor/DummyCommandRefs.cs')
    with file(cs_name, 'w') as outf:
      outf.write('''// Copyright 2020 The Tilt Brush Authors
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //      http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  using GlobalCommands = TiltBrush.SketchControlsScript.GlobalCommands;
  namespace TiltBrush {
  /// Auto-generated by Support/bin/analyze_refgraph.py --recreate
  /// The purpose of this file is to make it easier to find GlobalCommand references
  /// which come from serialized data.
  public static class DummyCommandRefs {
    static void Use(GlobalCommands c) { UnityEngine.Debug.Log(c.ToString()); }
  
  %s}
  }''' % '\n'.join(map(as_func, file_to_commands)))
  
  
  if __name__ == '__main__':
    # This is also done as part of analyze_refgraph.py --recreate
    project_dir = 'c:/src/tb'
    create_dummy_cs(project_dir, iter_command_edges(project_dir))
  