Beispiel #1
0
  def do_list(self, _):
    """Shows the opened archives.
    """
    print("%s %s" % (colored("Destination:", "green"), self.destination.path))
    source_text = colored("Sources:", "yellow")
    if not self.destination.sources:
      source_text += " (None)"

    print(source_text)
    for source in self.destination.sources:
      print(f"  {source}")
Beispiel #2
0
  def print_directory(directory, full):
    """Prints details about a chat directory.

    Args:
        directory: Directory object to print.
        full: True to print all chats; False for limited set.
    """
    def print_chats(chats, padding, label):
      for chat in chats:
        print("%s%s %s" % (" " * padding, label, chat.name))

    padding = 2
    label = ""
    if not directory.chats:
      if directory.merges:
        label += "%s " % colored("+", "yellow")
      elif directory.conflicts:
        label += "%s " % colored("C", "red")
      else:
        label += "%s " % colored("i", "blue")

    print("%s%s%s" % (" " * padding, label, directory.path.name))
    padding *= 2
    if full:
      print_chats(directory.chats, padding, colored("o", "green"))
      print_chats(directory.merges, padding, colored("+", "yellow"))
      print_chats(directory.ignores, padding, colored("i", "blue"))

    print_chats(directory.conflicts, padding, colored("CC", "red"))
    print_chats(directory.manual_ignores, padding, colored("ii", "blue"))
Beispiel #3
0
  def print_search(search, full):
    """Prints details about a user's search.

    Args:
        search: Search object to print.
        full: True to print all results; False for limited set.
    """
    if full:
      for chat in search.results:
        print(chat)

    total_text = colored(f"{len(search.results)} chats", "yellow")
    print(f"{total_text} for '{search.query}'")
Beispiel #4
0
  def do_search(self, line):
    """Searches all sources for chats matching the user's input.

    Args:
        line: A substring to use when searching for chats.
    """
    self.last_search = Search(line)

    for source in self.destination.sources.values():
      results = source.search(line)
      results_text = colored(f"{len(results)} chats", "yellow")
      print(f"Found {results_text} in {source.path}")
      self.last_search.results.extend(results)

    self.print_search(self.last_search, full=False)
Beispiel #5
0
class Messages(Cmd):
  """Messages CLI.

  Derives from cmd.Cmd and defines commands.

  Attributes:
      prompt: String to precede CLI input.
      history: Path to CLI history file.
      destination: Archive object for chat destination.
      last_search: Search object representing the last search.
  """
  prompt = "%s " % colored("<messages>", "cyan", attrs=["bold"], escape=True)

  history = Path("~/.messages_history").expanduser()

  def __init__(self, destination=None, sources=None):
    """Creates Messages instance.

    Args:
        destination: Archive object into which chats should be merged.
        sources: A list of Archive objects to use as chat sources.
    """
    super().__init__()

    if not destination:
      raise ValueError("A destination is required.")

    self.destination = destination
    self.last_search = Search()

    for source in sources or []:
      self.destination.merge(source)

  def cmdloop(self):
    """Runs the main loop.

    Handles CLI history and restarting the command prompt.
    """
    readline.set_history_length(1000)
    atexit.register(readline.write_history_file, self.history)
    if self.history.exists():
      readline.read_history_file(self.history)

    while True:
      try:
        super().cmdloop()
        break
      except KeyboardInterrupt:
        print()
        self.emptyline()

  def onecmd(self, line):
    """Executes one command.

    Returns:
        True to stop processing commands; else False.
    """
    try:
      return super().onecmd(line)
    except ValueError as ex:
      print(f"command failed: {ex}")
      return False

  def emptyline(self):
    """Processes an empty command.

    Returns:
        False to continue processing commands.
    """
    return False

  # pylint: disable=invalid-name
  def do_EOF(self, _):
    """Handles end of line.

    Returns:
        True to stop processing commands.
    """
    print()
    return self.onecmd("quit")

  def do_quit(self, _):
    """Handles quitting the CLI.

    Returns:
        True to stop processing commands.
    """
    return True

  def do_merge(self, line):
    """Merges an archive from a relative path as a source.

    Args:
        line: The relative path given by the user as the root.
    """
    self.destination.merge(Archive.from_user(line))
    return self.onecmd("list")

  def do_list(self, _):
    """Shows the opened archives.
    """
    print("%s %s" % (colored("Destination:", "green"), self.destination.path))
    source_text = colored("Sources:", "yellow")
    if not self.destination.sources:
      source_text += " (None)"

    print(source_text)
    for source in self.destination.sources:
      print(f"  {source}")

  @staticmethod
  def print_directory(directory, full):
    """Prints details about a chat directory.

    Args:
        directory: Directory object to print.
        full: True to print all chats; False for limited set.
    """
    def print_chats(chats, padding, label):
      for chat in chats:
        print("%s%s %s" % (" " * padding, label, chat.name))

    padding = 2
    label = ""
    if not directory.chats:
      if directory.merges:
        label += "%s " % colored("+", "yellow")
      elif directory.conflicts:
        label += "%s " % colored("C", "red")
      else:
        label += "%s " % colored("i", "blue")

    print("%s%s%s" % (" " * padding, label, directory.path.name))
    padding *= 2
    if full:
      print_chats(directory.chats, padding, colored("o", "green"))
      print_chats(directory.merges, padding, colored("+", "yellow"))
      print_chats(directory.ignores, padding, colored("i", "blue"))

    print_chats(directory.conflicts, padding, colored("CC", "red"))
    print_chats(directory.manual_ignores, padding, colored("ii", "blue"))

  def do_show(self, _):
    """Shows destination archive details.
    """
    print(f"{self.destination.path}")
    for directory in self.destination.directories.values():
      self.print_directory(directory, full=True)

  def do_diff(self, _):
    """Shows archive details most relevant for merging.
    """
    print(f"{self.destination.path}")
    for directory in self.destination.directories.values():
      if directory.conflicts or directory.manual_ignores:
        self.print_directory(directory, full=False)

  @staticmethod
  def print_search(search, full):
    """Prints details about a user's search.

    Args:
        search: Search object to print.
        full: True to print all results; False for limited set.
    """
    if full:
      for chat in search.results:
        print(chat)

    total_text = colored(f"{len(search.results)} chats", "yellow")
    print(f"{total_text} for '{search.query}'")

  def do_search(self, line):
    """Searches all sources for chats matching the user's input.

    Args:
        line: A substring to use when searching for chats.
    """
    self.last_search = Search(line)

    for source in self.destination.sources.values():
      results = source.search(line)
      results_text = colored(f"{len(results)} chats", "yellow")
      print(f"Found {results_text} in {source.path}")
      self.last_search.results.extend(results)

    self.print_search(self.last_search, full=False)

  def do_results(self, _):
    """Show the results from the last search.
    """
    self.print_search(self.last_search, full=True)

  def do_ignore(self, _):
    """Sets the merge to ignore chats from the last search.
    """
    ignored = self.destination.ignore(self.last_search.results)
    text = "Ignored %s" % colored(f"{len(ignored)} chats", "blue")
    print(f"{text} for '{self.last_search.query}' in {self.destination.path}")

  def do_simulate(self, _):
    """Simulates the flush of the destination archive.
    """
    self.destination.flush(print, simulate=True)

  def do_flush(self, _):
    """Writes the destination archive with its current state of merges.
    """
    if self.destination.can_flush():
      if confirm(f"Flush will modify {self.destination.path}."):
        path = Path.cwd() / "messages_results.txt"
        path.touch()
        with path.open("w") as out:
          self.destination.flush(lambda tx=None: out.write(f"{tx or ''}\n"))
      else:
        print("Canceled flush.")
    else:
      print(f"Resolve conflicts before flushing {self.destination.path}.")
Beispiel #6
0
 def do_ignore(self, _):
   """Sets the merge to ignore chats from the last search.
   """
   ignored = self.destination.ignore(self.last_search.results)
   text = "Ignored %s" % colored(f"{len(ignored)} chats", "blue")
   print(f"{text} for '{self.last_search.query}' in {self.destination.path}")