<commit_before>import sublime
import sublime_plugin
import subprocess
import re
import os

ADD_REGION_KEY = 'diffview-highlight-addition'
MOD_REGION_KEY = 'diffview-highlight-modification'
DEL_REGION_KEY = 'diffview-highlight-deletion'

class DiffView(sublime_plugin.WindowCommand):

    diff_args = ''
    """Main Sublime command for running a diff.

    Asks for input for what to diff against; a Git SHA/branch/tag.
    """

    def run(self):
        self.window.last_diff = self
        self.last_hunk_index = 0
        self.preview = None

        # Use show_input_panel as show_quick_panel doesn't allow arbitrary data
        self.window.show_input_panel(
            "Diff against? [HEAD]",
            self.diff_args,
            self.do_diff,
            None,
            None)

    def do_diff(self, diff_args):
        """Compare the current codebase with the `diff_args`.

        Args:
            diff_args: the base SHA/tag/branch to compare against.
        """
        if diff_args == '':
            diff_args = 'HEAD'
        self.diff_args = diff_args

        # Record the starting view and position.
        self.orig_view = self.window.active_view()
        self.orig_pos = self.orig_view.sel()[0]
        self.orig_viewport = self.orig_view.viewport_position()

        # Create the diff parser
        cwd = os.path.dirname(self.window.active_view().file_name())
        self.parser = DiffParser(self.diff_args, cwd)

        if not self.parser.changed_hunks:
            # No changes; say so
            sublime.status_message("No changes...")
        else:
            # Show the list of changed hunks
            self.list_changed_hunks()

    def list_changed_hunks(self):
        """Show a list of changed hunks in a quick panel."""
        self.window.show_quick_panel(
            [h.description for h in self.parser.changed_hunks],
            self.show_hunk_diff,
            sublime.MONOSPACE_FONT,
            self.last_hunk_index,
            self.preview_hunk)

    def show_hunk_diff(self, hunk_index):
        """Open the location of the selected hunk.

        Removes any diff highlighting shown in the previews.

        Args:
            hunk_index: the selected index in the changed hunks list.
        """
        # Remove diff highlighting from all views.
        for view in self.window.views():
            view.erase_regions(ADD_REGION_KEY)
            view.erase_regions(MOD_REGION_KEY)
            view.erase_regions(DEL_REGION_KEY)

        if hunk_index == -1:
            if self.preview:
                self.preview.close()
                self.preview = None

            # Return to the original view/selection
            self.window.focus_view(self.orig_view)
            self.orig_view.sel().clear()
            self.orig_view.sel().add(self.orig_pos)
            self.orig_view.set_viewport_position(
                self.orig_viewport,
                animate=False)
            return

        self.last_hunk_index = hunk_index
        hunk = self.parser.changed_hunks[hunk_index]
        self.window.open_file(hunk.filespec(), sublime.ENCODED_POSITION)

    def preview_hunk(self, hunk_index):
        """Show a preview of the selected hunk.

        Args:
            hunk_index: the selected index in the changed hunks list.
        """
        hunk = self.parser.changed_hunks[hunk_index]
        # TODO - don't think this works when the "already existing" file isn't
        # the active view (it gets closed anyway)...
        already_exists = self.window.find_open_file(hunk.file_diff.filename)
        self.preview = self.window.open_file(
            hunk.filespec(),
            sublime.TRANSIENT | sublime.ENCODED_POSITION)
        if already_exists:
            self.preview = None
        hunk.file_diff.add_regions(self.window.active_view())

class DiffHunksList(sublime_plugin.WindowCommand):
    """Resume the previous diff.

    Displays the list of changed hunks starting from the last hunk viewed.
    """
    def run(self):
        if hasattr(self.window, 'last_diff'):
            self.window.last_diff.list_changed_hunks()

class DiffParser(object):

    STAT_CHANGED_FILE = re.compile('\s*([\w\.\-\/]+)\s*\|')
    """Representation of the entire diff.

    Args:
        diff_args: The arguments to be used for the Git diff.
        cwd: The working directory.
    """

    def __init__(self, diff_args, cwd):
        self.cwd = cwd
        self.git_base = git_command(
            ['rev-parse', '--show-toplevel'], self.cwd).rstrip()
        self.diff_args = diff_args
        self.changed_files = self._get_changed_files()
        self.changed_hunks = []
        for f in self.changed_files:
            self.changed_hunks += f.get_hunks()

    def _get_changed_files(self):
        files = []
        diff_stat = git_command(
            ['diff', '--stat', self.diff_args], self.git_base)
        for line in diff_stat.split('\n'):
            match = self.STAT_CHANGED_FILE.match(line)
            if match:
                filename = match.group(1)
                abs_filename = os.path.join(self.git_base, filename)

                # Get the diff text for this file.
                diff_text = git_command(
                    ['diff',
                     self.diff_args,
                     '-U0',
                     '--minimal',
                     '--word-diff=porcelain',
                     '--',
                     filename],
                    self.git_base)

                files.append(FileDiff(filename, abs_filename, diff_text))
        return files

class FileDiff(object):

    HUNK_MATCH = re.compile('\r?\n@@ \-(\d+),?(\d*) \+(\d+),?(\d*) @@')
    """Representation of a single file's diff.

    Args:
        filename: The filename as given by Git - i.e. relative to the Git base
            directory.
        abs_filename: The absolute filename for this file.
        diff_text: The text of the Git diff.
    """

    def __init__(self, filename, abs_filename, diff_text):
        self.filename = filename
        self.abs_filename = abs_filename
        self.old_file = 'UNDEFINED'
        self.diff_text = diff_text
        self.hunks = []

    def get_hunks(self):
        """Get the changed hunks for this file.

        Wrapper to force parsing only once, and only when the hunks are
        required.
        """
        if not self.hunks:
            self.parse_diff()
        return self.hunks

    def parse_diff(self):
        """Run the Git diff command, and parse the diff for this file into
        hunks.
        """
        hunks = self.HUNK_MATCH.split(self.diff_text)

        # First item is the header - drop it
        hunks.pop(0)
        match_len = 5
        while len(hunks) >= match_len:
            self.hunks.append(HunkDiff(self, hunks[:match_len]))
            hunks = hunks[match_len:]

    def add_regions(self, view):
        """Add all highlighted regions to the view for this file."""
        view.add_regions(
            ADD_REGION_KEY,
            [r for h in self.hunks for r in h.get_regions(view)
                if h.hunk_type == "ADD"],
            "support.class",
            flags=sublime.HIDE_ON_MINIMAP | sublime.DRAW_NO_FILL)
        view.add_regions(
            MOD_REGION_KEY,
            [r for h in self.hunks for r in h.get_regions(view)
                if h.hunk_type == "MOD"],
            "string",
            flags=sublime.DRAW_EMPTY |
            sublime.HIDE_ON_MINIMAP |
            sublime.DRAW_NO_FILL)
        view.add_regions(
            DEL_REGION_KEY,
            [r for h in self.hunks for r in h.get_regions(view)
                if h.hunk_type == "DEL"],
            "invalid",
            flags=sublime.DRAW_EMPTY |
            sublime.HIDE_ON_MINIMAP |
            sublime.DRAW_EMPTY_AS_OVERWRITE |
            sublime.DRAW_NO_FILL)

class HunkDiff(object):

    NEWLINE_MATCH = re.compile('\r?\n')
    LINE_DELIM_MATCH = re.compile('^~')
    ADD_LINE_MATCH = re.compile('^\+(.*)')
    DEL_LINE_MATCH = re.compile('^\-(.*)')
    """Representation of a single 'hunk' from a Git diff.

    Args:
        file_diff: The parent `FileDiff` object.
        match: The match parts of the hunk header.
    """

    def __init__(self, file_diff, match):
        self.file_diff = file_diff
        self.regions = []

        # Matches' meanings are:
        # - 0: start line in old file
        # - 1: num lines removed from old file (0 for ADD, missing if it's a
        #      one-line change)
        # - 2: start line in new file
        # - 3: num lines added to new file (0 for DEL, missing if it's a
        #      one-line change)
        # - 4: the remainder of the hunk, after the header
        self.old_line_start = int(match[0])
        self.old_hunk_len = 1
        if len(match[1]) > 0:
            self.old_hunk_len = int(match[1])
        self.new_line_start = int(match[2])
        self.new_hunk_len = 1
        if len(match[3]) > 0:
            self.new_hunk_len = int(match[3])
        self.context = self.NEWLINE_MATCH.split(match[4])[0]
        self.hunk_diff_lines = self.NEWLINE_MATCH.split(match[4])[1:]

        if self.old_hunk_len == 0:
            self.hunk_type = "ADD"
        elif self.new_hunk_len == 0:
            self.hunk_type = "DEL"
        else:
            self.hunk_type = "MOD"

        # Create the description that will appear in the quick_panel.
        self.description = [
            "{} : {}".format(file_diff.filename, self.new_line_start),
            self.context,
            "{} | {}{}".format(self.old_hunk_len + self.new_hunk_len,
                               "+" * self.new_hunk_len,
                               "-" * self.old_hunk_len)]

    def parse_diff(self):
        """Generate representations of the changed regions."""
        # TODO - create regions for old file too...
        # ADD and DEL are easy.
        if self.hunk_type == "ADD":
            self.regions.append(DiffRegion(
                "ADD",
                self.new_line_start,
                0,
                self.new_line_start + self.new_hunk_len,
                0))
        elif self.hunk_type == "DEL":
            self.regions.append(DiffRegion(
                "DEL",
                self.new_line_start,
                0,
                self.new_line_start + self.new_hunk_len,
                0))
        else:
            # We have a chunk that's not just whole lines...
            # Start by grouping the lines between the '~' lines.
            add_chunks, del_chunks = self.sort_chunks()

            # Handle ADD chunks.
            add_start_line = self.new_line_start
            cur_line = self.new_line_start
            add_start_col = 0
            cur_col = 0
            in_add = False
            for chunk in add_chunks:
                for segment in chunk:
                    if segment.startswith(' '):
                        if in_add:
                            # ADD region ends.
                            self.regions.append(DiffRegion(
                                "ADD",
                                add_start_line,
                                add_start_col,
                                cur_line,
                                cur_col))
                        in_add = False
                        cur_col += len(segment) - 1
                    elif segment.startswith('+'):
                        if not in_add:
                            # ADD region starts.
                            add_start_line = cur_line
                            add_start_col = cur_col
                        in_add = True
                        cur_col += len(segment) - 1
                    else:
                        print("Unexpected segment: {} in {}".format(
                            segment, chunk))

                # End of that line.
                cur_line += 1
                cur_col = 0

            # TODO - Handle DEL chunks too.
            # ...but not interesting when we're only looking at the new file.

    def sort_chunks(self):
        """Sort the sub-chunks in this hunk into those which are interesting
        for ADD regions, and those that are interesting for DEL regions.

        Returns:
            (add_chunks, del_chunks)
        """
        add_chunks = []
        del_chunks = []
        cur_chunk = []
        cur_chunk_has_del = False
        need_newline = False
        for line in self.hunk_diff_lines:
            if line.startswith('~'):
                if need_newline or not cur_chunk_has_del:
                    add_chunks.append(cur_chunk)
                    cur_chunk = []
                    cur_chunk_has_del = False
                    need_newline = False
            elif line.startswith('-'):
                cur_chunk_has_del = True
            else:
                cur_chunk.append(line)
                if line.startswith('+'):
                    need_newline = True

        return (add_chunks, del_chunks)

    def filespec(self):
        """Get the portion of code that this hunk refers to in the format
        `filename:col`.
        """
        return "{}:{}".format(self.file_diff.abs_filename, self.new_line_start)

    def get_regions(self, view):
        """Create a `sublime.Region` for each part of this hunk."""
        if not self.regions:
            self.parse_diff()
        return [sublime.Region(
            view.text_point(r.start_line - 1, r.start_col),
            view.text_point(r.end_line - 1, r.end_col)) for r in self.regions]

class DiffRegion(object):
    """Class representing a region that's changed.

    Args:
        type: "ADD", "MOD" or "DEL"
        start_line: The line where the region starts
        start_col: The column where the region starts
        end_line: The line where the region ends
        end_col: The column where the region ends
    """

    def __init__(self, diff_type, start_line, start_col, end_line, end_col):
        self.diff_type = diff_type
        self.start_line = start_line
        self.start_col = start_col
        self.end_line = end_line
        self.end_col = end_col

def git_command(args, cwd):
    """Wrapper to run a Git command."""
    p = subprocess.Popen(['git'] + args,
                         stdout=subprocess.PIPE,
                         shell=True,
                         cwd=cwd)
    out, err = p.communicate()
    return out.decode('utf-8')
<commit_msg>Pass string to subprocess shell, not array<commit_after>import sublime
import sublime_plugin
import subprocess
import re
import os

ADD_REGION_KEY = 'diffview-highlight-addition'
MOD_REGION_KEY = 'diffview-highlight-modification'
DEL_REGION_KEY = 'diffview-highlight-deletion'

class DiffView(sublime_plugin.WindowCommand):

    diff_args = ''
    """Main Sublime command for running a diff.

    Asks for input for what to diff against; a Git SHA/branch/tag.
    """

    def run(self):
        self.window.last_diff = self
        self.last_hunk_index = 0
        self.preview = None

        # Use show_input_panel as show_quick_panel doesn't allow arbitrary data
        self.window.show_input_panel(
            "Diff against? [HEAD]",
            self.diff_args,
            self.do_diff,
            None,
            None)

    def do_diff(self, diff_args):
        """Compare the current codebase with the `diff_args`.

        Args:
            diff_args: the base SHA/tag/branch to compare against.
        """
        if diff_args == '':
            diff_args = 'HEAD'
        self.diff_args = diff_args

        # Record the starting view and position.
        self.orig_view = self.window.active_view()
        self.orig_pos = self.orig_view.sel()[0]
        self.orig_viewport = self.orig_view.viewport_position()

        # Create the diff parser
        cwd = os.path.dirname(self.window.active_view().file_name())
        self.parser = DiffParser(self.diff_args, cwd)

        if not self.parser.changed_hunks:
            # No changes; say so
            sublime.status_message("No changes...")
        else:
            # Show the list of changed hunks
            self.list_changed_hunks()

    def list_changed_hunks(self):
        """Show a list of changed hunks in a quick panel."""
        self.window.show_quick_panel(
            [h.description for h in self.parser.changed_hunks],
            self.show_hunk_diff,
            sublime.MONOSPACE_FONT,
            self.last_hunk_index,
            self.preview_hunk)

    def show_hunk_diff(self, hunk_index):
        """Open the location of the selected hunk.

        Removes any diff highlighting shown in the previews.

        Args:
            hunk_index: the selected index in the changed hunks list.
        """
        # Remove diff highlighting from all views.
        for view in self.window.views():
            view.erase_regions(ADD_REGION_KEY)
            view.erase_regions(MOD_REGION_KEY)
            view.erase_regions(DEL_REGION_KEY)

        if hunk_index == -1:
            if self.preview:
                self.preview.close()
                self.preview = None

            # Return to the original view/selection
            self.window.focus_view(self.orig_view)
            self.orig_view.sel().clear()
            self.orig_view.sel().add(self.orig_pos)
            self.orig_view.set_viewport_position(
                self.orig_viewport,
                animate=False)
            return

        self.last_hunk_index = hunk_index
        hunk = self.parser.changed_hunks[hunk_index]
        self.window.open_file(hunk.filespec(), sublime.ENCODED_POSITION)

    def preview_hunk(self, hunk_index):
        """Show a preview of the selected hunk.

        Args:
            hunk_index: the selected index in the changed hunks list.
        """
        hunk = self.parser.changed_hunks[hunk_index]
        # TODO - don't think this works when the "already existing" file isn't
        # the active view (it gets closed anyway)...
        already_exists = self.window.find_open_file(hunk.file_diff.filename)
        self.preview = self.window.open_file(
            hunk.filespec(),
            sublime.TRANSIENT | sublime.ENCODED_POSITION)
        if already_exists:
            self.preview = None
        hunk.file_diff.add_regions(self.window.active_view())

class DiffHunksList(sublime_plugin.WindowCommand):
    """Resume the previous diff.

    Displays the list of changed hunks starting from the last hunk viewed.
    """
    def run(self):
        if hasattr(self.window, 'last_diff'):
            self.window.last_diff.list_changed_hunks()

class DiffParser(object):

    STAT_CHANGED_FILE = re.compile('\s*([\w\.\-\/]+)\s*\|')
    """Representation of the entire diff.

    Args:
        diff_args: The arguments to be used for the Git diff.
        cwd: The working directory.
    """

    def __init__(self, diff_args, cwd):
        self.cwd = cwd
        self.git_base = git_command(
            ['rev-parse', '--show-toplevel'], self.cwd).rstrip()
        self.diff_args = diff_args
        self.changed_files = self._get_changed_files()
        self.changed_hunks = []
        for f in self.changed_files:
            self.changed_hunks += f.get_hunks()

    def _get_changed_files(self):
        files = []
        diff_stat = git_command(
            ['diff', '--stat', self.diff_args], self.git_base)
        for line in diff_stat.split('\n'):
            match = self.STAT_CHANGED_FILE.match(line)
            if match:
                filename = match.group(1)
                abs_filename = os.path.join(self.git_base, filename)

                # Get the diff text for this file.
                diff_text = git_command(
                    ['diff',
                     self.diff_args,
                     '-U0',
                     '--minimal',
                     '--word-diff=porcelain',
                     '--',
                     filename],
                    self.git_base)

                files.append(FileDiff(filename, abs_filename, diff_text))
        return files

class FileDiff(object):

    HUNK_MATCH = re.compile('\r?\n@@ \-(\d+),?(\d*) \+(\d+),?(\d*) @@')
    """Representation of a single file's diff.

    Args:
        filename: The filename as given by Git - i.e. relative to the Git base
            directory.
        abs_filename: The absolute filename for this file.
        diff_text: The text of the Git diff.
    """

    def __init__(self, filename, abs_filename, diff_text):
        self.filename = filename
        self.abs_filename = abs_filename
        self.old_file = 'UNDEFINED'
        self.diff_text = diff_text
        self.hunks = []

    def get_hunks(self):
        """Get the changed hunks for this file.

        Wrapper to force parsing only once, and only when the hunks are
        required.
        """
        if not self.hunks:
            self.parse_diff()
        return self.hunks

    def parse_diff(self):
        """Run the Git diff command, and parse the diff for this file into
        hunks.
        """
        hunks = self.HUNK_MATCH.split(self.diff_text)

        # First item is the header - drop it
        hunks.pop(0)
        match_len = 5
        while len(hunks) >= match_len:
            self.hunks.append(HunkDiff(self, hunks[:match_len]))
            hunks = hunks[match_len:]

    def add_regions(self, view):
        """Add all highlighted regions to the view for this file."""
        view.add_regions(
            ADD_REGION_KEY,
            [r for h in self.hunks for r in h.get_regions(view)
                if h.hunk_type == "ADD"],
            "support.class",
            flags=sublime.HIDE_ON_MINIMAP | sublime.DRAW_NO_FILL)
        view.add_regions(
            MOD_REGION_KEY,
            [r for h in self.hunks for r in h.get_regions(view)
                if h.hunk_type == "MOD"],
            "string",
            flags=sublime.DRAW_EMPTY |
            sublime.HIDE_ON_MINIMAP |
            sublime.DRAW_NO_FILL)
        view.add_regions(
            DEL_REGION_KEY,
            [r for h in self.hunks for r in h.get_regions(view)
                if h.hunk_type == "DEL"],
            "invalid",
            flags=sublime.DRAW_EMPTY |
            sublime.HIDE_ON_MINIMAP |
            sublime.DRAW_EMPTY_AS_OVERWRITE |
            sublime.DRAW_NO_FILL)

class HunkDiff(object):

    NEWLINE_MATCH = re.compile('\r?\n')
    LINE_DELIM_MATCH = re.compile('^~')
    ADD_LINE_MATCH = re.compile('^\+(.*)')
    DEL_LINE_MATCH = re.compile('^\-(.*)')
    """Representation of a single 'hunk' from a Git diff.

    Args:
        file_diff: The parent `FileDiff` object.
        match: The match parts of the hunk header.
    """

    def __init__(self, file_diff, match):
        self.file_diff = file_diff
        self.regions = []

        # Matches' meanings are:
        # - 0: start line in old file
        # - 1: num lines removed from old file (0 for ADD, missing if it's a
        #      one-line change)
        # - 2: start line in new file
        # - 3: num lines added to new file (0 for DEL, missing if it's a
        #      one-line change)
        # - 4: the remainder of the hunk, after the header
        self.old_line_start = int(match[0])
        self.old_hunk_len = 1
        if len(match[1]) > 0:
            self.old_hunk_len = int(match[1])
        self.new_line_start = int(match[2])
        self.new_hunk_len = 1
        if len(match[3]) > 0:
            self.new_hunk_len = int(match[3])
        self.context = self.NEWLINE_MATCH.split(match[4])[0]
        self.hunk_diff_lines = self.NEWLINE_MATCH.split(match[4])[1:]

        if self.old_hunk_len == 0:
            self.hunk_type = "ADD"
        elif self.new_hunk_len == 0:
            self.hunk_type = "DEL"
        else:
            self.hunk_type = "MOD"

        # Create the description that will appear in the quick_panel.
        self.description = [
            "{} : {}".format(file_diff.filename, self.new_line_start),
            self.context,
            "{} | {}{}".format(self.old_hunk_len + self.new_hunk_len,
                               "+" * self.new_hunk_len,
                               "-" * self.old_hunk_len)]

    def parse_diff(self):
        """Generate representations of the changed regions."""
        # TODO - create regions for old file too...
        # ADD and DEL are easy.
        if self.hunk_type == "ADD":
            self.regions.append(DiffRegion(
                "ADD",
                self.new_line_start,
                0,
                self.new_line_start + self.new_hunk_len,
                0))
        elif self.hunk_type == "DEL":
            self.regions.append(DiffRegion(
                "DEL",
                self.new_line_start,
                0,
                self.new_line_start + self.new_hunk_len,
                0))
        else:
            # We have a chunk that's not just whole lines...
            # Start by grouping the lines between the '~' lines.
            add_chunks, del_chunks = self.sort_chunks()

            # Handle ADD chunks.
            add_start_line = self.new_line_start
            cur_line = self.new_line_start
            add_start_col = 0
            cur_col = 0
            in_add = False
            for chunk in add_chunks:
                for segment in chunk:
                    if segment.startswith(' '):
                        if in_add:
                            # ADD region ends.
                            self.regions.append(DiffRegion(
                                "ADD",
                                add_start_line,
                                add_start_col,
                                cur_line,
                                cur_col))
                        in_add = False
                        cur_col += len(segment) - 1
                    elif segment.startswith('+'):
                        if not in_add:
                            # ADD region starts.
                            add_start_line = cur_line
                            add_start_col = cur_col
                        in_add = True
                        cur_col += len(segment) - 1
                    else:
                        print("Unexpected segment: {} in {}".format(
                            segment, chunk))

                # End of that line.
                cur_line += 1
                cur_col = 0

            # TODO - Handle DEL chunks too.
            # ...but not interesting when we're only looking at the new file.

    def sort_chunks(self):
        """Sort the sub-chunks in this hunk into those which are interesting
        for ADD regions, and those that are interesting for DEL regions.

        Returns:
            (add_chunks, del_chunks)
        """
        add_chunks = []
        del_chunks = []
        cur_chunk = []
        cur_chunk_has_del = False
        need_newline = False
        for line in self.hunk_diff_lines:
            if line.startswith('~'):
                if need_newline or not cur_chunk_has_del:
                    add_chunks.append(cur_chunk)
                    cur_chunk = []
                    cur_chunk_has_del = False
                    need_newline = False
            elif line.startswith('-'):
                cur_chunk_has_del = True
            else:
                cur_chunk.append(line)
                if line.startswith('+'):
                    need_newline = True

        return (add_chunks, del_chunks)

    def filespec(self):
        """Get the portion of code that this hunk refers to in the format
        `filename:col`.
        """
        return "{}:{}".format(self.file_diff.abs_filename, self.new_line_start)

    def get_regions(self, view):
        """Create a `sublime.Region` for each part of this hunk."""
        if not self.regions:
            self.parse_diff()
        return [sublime.Region(
            view.text_point(r.start_line - 1, r.start_col),
            view.text_point(r.end_line - 1, r.end_col)) for r in self.regions]

class DiffRegion(object):
    """Class representing a region that's changed.

    Args:
        type: "ADD", "MOD" or "DEL"
        start_line: The line where the region starts
        start_col: The column where the region starts
        end_line: The line where the region ends
        end_col: The column where the region ends
    """

    def __init__(self, diff_type, start_line, start_col, end_line, end_col):
        self.diff_type = diff_type
        self.start_line = start_line
        self.start_col = start_col
        self.end_line = end_line
        self.end_col = end_col

def git_command(args, cwd):
    """Wrapper to run a Git command."""
    # Using shell, just pass a string to subprocess.
    p = subprocess.Popen(" ".join(['git'] + args),
                         stdout=subprocess.PIPE,
                         shell=True,
                         cwd=cwd)
    out, err = p.communicate()
    return out.decode('utf-8')
<|endoftext|>


<commit_before>"""Classes to represent arbitrary sets (including sets of sets).

This module implements sets using dictionaries whose values are
ignored.  The usual operations (union, intersection, deletion, etc.)
are provided as both methods and operators.

Important: sets are not sequences!  While they support 'x in s',
'len(s)', and 'for x in s', none of those operations are unique for
sequences; for example, mappings support all three as well.  The
characteristic operation for sequences is subscripting with small
integers: s[i], for i in range(len(s)).  Sets don't support
subscripting at all.  Also, sequences allow multiple occurrences and
their elements have a definite order; sets on the other hand don't
record multiple occurrences and don't remember the order of element
insertion (which is why they don't support s[i]).

The following classes are provided:

BaseSet -- All the operations common to both mutable and immutable
    sets. This is an abstract class, not meant to be directly
    instantiated.

Set -- Mutable sets, subclass of BaseSet; not hashable.

ImmutableSet -- Immutable sets, subclass of BaseSet; hashable.
    An iterable argument is mandatory to create an ImmutableSet.

_TemporarilyImmutableSet -- Not a subclass of BaseSet: just a wrapper
    around a Set, hashable, giving the same hash value as the
    immutable set equivalent would have.  Do not use this class
    directly.

Only hashable objects can be added to a Set. In particular, you cannot
really add a Set as an element to another Set; if you try, what is
actually added is an ImmutableSet built from it (it compares equal to
the one you tried adding).

When you ask if `x in y' where x is a Set and y is a Set or
ImmutableSet, x is wrapped into a _TemporarilyImmutableSet z, and
what's tested is actually `z in y'.

"""

# Code history:
#
# - Greg V. Wilson wrote the first version, using a different approach
#   to the mutable/immutable problem, and inheriting from dict.
#
# - Alex Martelli modified Greg's version to implement the current
#   Set/ImmutableSet approach, and make the data an attribute.
#
# - Guido van Rossum rewrote much of the code, made some API changes,
#   and cleaned up the docstrings.
#
# - Raymond Hettinger added a number of speedups and other
#   improvements.

__all__ = ['BaseSet', 'Set', 'ImmutableSet']

class BaseSet(object):
    """Common base class for mutable and immutable sets."""

    __slots__ = ['_data']

    # Constructor

    def __init__(self):
        """This is an abstract class."""
        # Don't call this from a concrete subclass!
        if self.__class__ is BaseSet:
            raise TypeError, ("BaseSet is an abstract class.  "
                              "Use Set or ImmutableSet.")

    # Standard protocols: __len__, __repr__, __str__, __iter__

    def __len__(self):
        """Return the number of elements of a set."""
        return len(self._data)

    def __repr__(self):
        """Return string representation of a set.

        This looks like 'Set([<list of elements>])'.
        """
        return self._repr()

    # __str__ is the same as __repr__
    __str__ = __repr__

    def _repr(self, sorted=False):
        elements = self._data.keys()
        if sorted:
            elements.sort()
        return '%s(%r)' % (self.__class__.__name__, elements)

    def __iter__(self):
        """Return an iterator over the elements or a set.

        This is the keys iterator for the underlying dict.
        """
        return self._data.iterkeys()

    # Equality comparisons using the underlying dicts

    def __eq__(self, other):
        self._binary_sanity_check(other)
        return self._data == other._data

    def __ne__(self, other):
        self._binary_sanity_check(other)
        return self._data != other._data

    # Copying operations

    def copy(self):
        """Return a shallow copy of a set."""
        result = self.__class__()
        result._data.update(self._data)
        return result

    __copy__ = copy # For the copy module

    def __deepcopy__(self, memo):
        """Return a deep copy of a set; used by copy module."""
        # This pre-creates the result and inserts it in the memo
        # early, in case the deep copy recurses into another reference
        # to this same set.  A set can't be an element of itself, but
        # it can certainly contain an object that has a reference to
        # itself.
        from copy import deepcopy
        result = self.__class__()
        memo[id(self)] = result
        data = result._data
        value = True
        for elt in self:
            data[deepcopy(elt, memo)] = value
        return result

    # Standard set operations: union, intersection, both differences.
    # Each has an operator version (e.g. __or__, invoked with |) and a
    # method version (e.g. union).

    def __or__(self, other):
        """Return the union of two sets as a new set.

        (I.e. all elements that are in either set.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        result = self.__class__(self._data)
        result._data.update(other._data)
        return result

    def union(self, other):
        """Return the union of two sets as a new set.

        (I.e. all elements that are in either set.)
        """
        return self | other

    def __and__(self, other):
        """Return the intersection of two sets as a new set.

        (I.e. all elements that are in both sets.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        if len(self) <= len(other):
            little, big = self, other
        else:
            little, big = other, self
        result = self.__class__()
        data = result._data
        value = True
        for elt in little:
            if elt in big:
                data[elt] = value
        return result

    def intersection(self, other):
        """Return the intersection of two sets as a new set.

        (I.e. all elements that are in both sets.)
        """
        return self & other

    def __xor__(self, other):
        """Return the symmetric difference of two sets as a new set.

        (I.e. all elements that are in exactly one of the sets.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        result = self.__class__()
        data = result._data
        value = True
        for elt in self:
            if elt not in other:
                data[elt] = value
        for elt in other:
            if elt not in self:
                data[elt] = value
        return result

    def symmetric_difference(self, other):
        """Return the symmetric difference of two sets as a new set.

        (I.e. all elements that are in exactly one of the sets.)
        """
        return self ^ other

    def  __sub__(self, other):
        """Return the difference of two sets as a new Set.

        (I.e. all elements that are in this set and not in the other.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        result = self.__class__()
        data = result._data
        value = True
        for elt in self:
            if elt not in other:
                data[elt] = value
        return result

    def difference(self, other):
        """Return the difference of two sets as a new Set.

        (I.e. all elements that are in this set and not in the other.)
        """
        return self - other

    # Membership test

    def __contains__(self, element):
        """Report whether an element is a member of a set.

        (Called in response to the expression `element in self'.)
        """
        try:
            return element in self._data
        except TypeError:
            transform = getattr(element, "_as_temporarily_immutable", None)
            if transform is None:
                raise # re-raise the TypeError exception we caught
            return transform() in self._data

    # Subset and superset test

    def issubset(self, other):
        """Report whether another set contains this set."""
        self._binary_sanity_check(other)
        if len(self) > len(other):  # Fast check for obvious cases
            return False
        for elt in self:
            if elt not in other:
                return False
        return True

    def issuperset(self, other):
        """Report whether this set contains another set."""
        self._binary_sanity_check(other)
        if len(self) < len(other):  # Fast check for obvious cases
            return False
        for elt in other:
            if elt not in self:
                return False
        return True

    # Assorted helpers

    def _binary_sanity_check(self, other):
        # Check that the other argument to a binary operation is also
        # a set, raising a TypeError otherwise.
        if not isinstance(other, BaseSet):
            raise TypeError, "Binary operation only permitted between sets"

    def _compute_hash(self):
        # Calculate hash code for a set by xor'ing the hash codes of
        # the elements.  This ensures that the hash code does not depend
        # on the order in which elements are added to the set.  This is
        # not called __hash__ because a BaseSet should not be hashable;
        # only an ImmutableSet is hashable.
        result = 0
        for elt in self:
            result ^= hash(elt)
        return result

    def _update(self, iterable):
        # The main loop for update() and the subclass __init__() methods.
        data = self._data
        value = True
        it = iter(iterable)
        while True:
            try:
                for element in it:
                    data[element] = value
                return
            except TypeError:
                transform = getattr(element, "_as_immutable", None)
                if transform is None:
                    raise # re-raise the TypeError exception we caught
                data[transform()] = value

class ImmutableSet(BaseSet):
    """Immutable set class."""

    __slots__ = ['_hashcode']

    # BaseSet + hashing

    def __init__(self, iterable=None):
        """Construct an immutable set from an optional iterable."""
        self._hashcode = None
        self._data = {}
        if iterable is not None:
            self._update(iterable)

    def __hash__(self):
        if self._hashcode is None:
            self._hashcode = self._compute_hash()
        return self._hashcode

class Set(BaseSet):
    """ Mutable set class."""

    __slots__ = []

    # BaseSet + operations requiring mutability; no hashing

    def __init__(self, iterable=None):
        """Construct a set from an optional iterable."""
        self._data = {}
        if iterable is not None:
            self._update(iterable)

    def __hash__(self):
        """A Set cannot be hashed."""
        # We inherit object.__hash__, so we must deny this explicitly
        raise TypeError, "Can't hash a Set, only an ImmutableSet."

    # In-place union, intersection, differences

    def __ior__(self, other):
        """Update a set with the union of itself and another."""
        self._binary_sanity_check(other)
        self._data.update(other._data)
        return self

    def union_update(self, other):
        """Update a set with the union of itself and another."""
        self |= other

    def __iand__(self, other):
        """Update a set with the intersection of itself and another."""
        self._binary_sanity_check(other)
        for elt in self._data.keys():
            if elt not in other:
                del self._data[elt]
        return self

    def intersection_update(self, other):
        """Update a set with the intersection of itself and another."""
        self &= other

    def __ixor__(self, other):
        """Update a set with the symmetric difference of itself and another."""
        self._binary_sanity_check(other)
        data = self._data
        value = True
        for elt in other:
            if elt in data:
                del data[elt]
            else:
                data[elt] = value
        return self

    def symmetric_difference_update(self, other):
        """Update a set with the symmetric difference of itself and another."""
        self ^= other

    def __isub__(self, other):
        """Remove all elements of another set from this set."""
        self._binary_sanity_check(other)
        data = self._data
        for elt in other:
            if elt in data:
                del data[elt]
        return self

    def difference_update(self, other):
        """Remove all elements of another set from this set."""
        self -= other

    # Python dict-like mass mutations: update, clear

    def update(self, iterable):
        """Add all values from an iterable (such as a list or file)."""
        self._update(iterable)

    def clear(self):
        """Remove all elements from this set."""
        self._data.clear()

    # Single-element mutations: add, remove, discard

    def add(self, element):
        """Add an element to a set.

        This has no effect if the element is already present.
        """
        try:
            self._data[element] = True
        except TypeError:
            transform = getattr(element, "_as_immutable", None)
            if transform is None:
                raise # re-raise the TypeError exception we caught
            self._data[transform()] = True

    def remove(self, element):
        """Remove an element from a set; it must be a member.

        If the element is not a member, raise a KeyError.
        """
        try:
            del self._data[element]
        except TypeError:
            transform = getattr(element, "_as_temporarily_immutable", None)
            if transform is None:
                raise # re-raise the TypeError exception we caught
            del self._data[transform()]

    def discard(self, element):
        """Remove an element from a set if it is a member.

        If the element is not a member, do nothing.
        """
        try:
            self.remove(element)
        except KeyError:
            pass

    def pop(self):
        """Remove and return an arbitrary set element."""
        return self._data.popitem()[0]

    def _as_immutable(self):
        # Return a copy of self as an immutable set
        return ImmutableSet(self)

    def _as_temporarily_immutable(self):
        # Return self wrapped in a temporarily immutable set
        return _TemporarilyImmutableSet(self)

class _TemporarilyImmutableSet(BaseSet):
    # Wrap a mutable set as if it was temporarily immutable.
    # This only supplies hashing and equality comparisons.

    def __init__(self, set):
        self._set = set
        self._data = set._data  # Needed by ImmutableSet.__eq__()

    def __hash__(self):
        return self._set._compute_hash()
<commit_msg>Record a clue about why __or__ is not union, etc.<commit_after>"""Classes to represent arbitrary sets (including sets of sets).

This module implements sets using dictionaries whose values are
ignored.  The usual operations (union, intersection, deletion, etc.)
are provided as both methods and operators.

Important: sets are not sequences!  While they support 'x in s',
'len(s)', and 'for x in s', none of those operations are unique for
sequences; for example, mappings support all three as well.  The
characteristic operation for sequences is subscripting with small
integers: s[i], for i in range(len(s)).  Sets don't support
subscripting at all.  Also, sequences allow multiple occurrences and
their elements have a definite order; sets on the other hand don't
record multiple occurrences and don't remember the order of element
insertion (which is why they don't support s[i]).

The following classes are provided:

BaseSet -- All the operations common to both mutable and immutable
    sets. This is an abstract class, not meant to be directly
    instantiated.

Set -- Mutable sets, subclass of BaseSet; not hashable.

ImmutableSet -- Immutable sets, subclass of BaseSet; hashable.
    An iterable argument is mandatory to create an ImmutableSet.

_TemporarilyImmutableSet -- Not a subclass of BaseSet: just a wrapper
    around a Set, hashable, giving the same hash value as the
    immutable set equivalent would have.  Do not use this class
    directly.

Only hashable objects can be added to a Set. In particular, you cannot
really add a Set as an element to another Set; if you try, what is
actually added is an ImmutableSet built from it (it compares equal to
the one you tried adding).

When you ask if `x in y' where x is a Set and y is a Set or
ImmutableSet, x is wrapped into a _TemporarilyImmutableSet z, and
what's tested is actually `z in y'.

"""

# Code history:
#
# - Greg V. Wilson wrote the first version, using a different approach
#   to the mutable/immutable problem, and inheriting from dict.
#
# - Alex Martelli modified Greg's version to implement the current
#   Set/ImmutableSet approach, and make the data an attribute.
#
# - Guido van Rossum rewrote much of the code, made some API changes,
#   and cleaned up the docstrings.
#
# - Raymond Hettinger added a number of speedups and other
#   improvements.

__all__ = ['BaseSet', 'Set', 'ImmutableSet']

class BaseSet(object):
    """Common base class for mutable and immutable sets."""

    __slots__ = ['_data']

    # Constructor

    def __init__(self):
        """This is an abstract class."""
        # Don't call this from a concrete subclass!
        if self.__class__ is BaseSet:
            raise TypeError, ("BaseSet is an abstract class.  "
                              "Use Set or ImmutableSet.")

    # Standard protocols: __len__, __repr__, __str__, __iter__

    def __len__(self):
        """Return the number of elements of a set."""
        return len(self._data)

    def __repr__(self):
        """Return string representation of a set.

        This looks like 'Set([<list of elements>])'.
        """
        return self._repr()

    # __str__ is the same as __repr__
    __str__ = __repr__

    def _repr(self, sorted=False):
        elements = self._data.keys()
        if sorted:
            elements.sort()
        return '%s(%r)' % (self.__class__.__name__, elements)

    def __iter__(self):
        """Return an iterator over the elements or a set.

        This is the keys iterator for the underlying dict.
        """
        return self._data.iterkeys()

    # Equality comparisons using the underlying dicts

    def __eq__(self, other):
        self._binary_sanity_check(other)
        return self._data == other._data

    def __ne__(self, other):
        self._binary_sanity_check(other)
        return self._data != other._data

    # Copying operations

    def copy(self):
        """Return a shallow copy of a set."""
        result = self.__class__()
        result._data.update(self._data)
        return result

    __copy__ = copy # For the copy module

    def __deepcopy__(self, memo):
        """Return a deep copy of a set; used by copy module."""
        # This pre-creates the result and inserts it in the memo
        # early, in case the deep copy recurses into another reference
        # to this same set.  A set can't be an element of itself, but
        # it can certainly contain an object that has a reference to
        # itself.
        from copy import deepcopy
        result = self.__class__()
        memo[id(self)] = result
        data = result._data
        value = True
        for elt in self:
            data[deepcopy(elt, memo)] = value
        return result

    # Standard set operations: union, intersection, both differences.
    # Each has an operator version (e.g. __or__, invoked with |) and a
    # method version (e.g. union).
    # Subtle:  Each pair requires distinct code so that the outcome is
    # correct when the type of other isn't suitable.  For example, if
    # we did "union = __or__" instead, then Set().union(3) would return
    # NotImplemented instead of raising TypeError (albeit that *why* it
    # raises TypeError as-is is also a bit subtle).

    def __or__(self, other):
        """Return the union of two sets as a new set.

        (I.e. all elements that are in either set.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        result = self.__class__(self._data)
        result._data.update(other._data)
        return result

    def union(self, other):
        """Return the union of two sets as a new set.

        (I.e. all elements that are in either set.)
        """
        return self | other

    def __and__(self, other):
        """Return the intersection of two sets as a new set.

        (I.e. all elements that are in both sets.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        if len(self) <= len(other):
            little, big = self, other
        else:
            little, big = other, self
        result = self.__class__()
        data = result._data
        value = True
        for elt in little:
            if elt in big:
                data[elt] = value
        return result

    def intersection(self, other):
        """Return the intersection of two sets as a new set.

        (I.e. all elements that are in both sets.)
        """
        return self & other

    def __xor__(self, other):
        """Return the symmetric difference of two sets as a new set.

        (I.e. all elements that are in exactly one of the sets.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        result = self.__class__()
        data = result._data
        value = True
        for elt in self:
            if elt not in other:
                data[elt] = value
        for elt in other:
            if elt not in self:
                data[elt] = value
        return result

    def symmetric_difference(self, other):
        """Return the symmetric difference of two sets as a new set.

        (I.e. all elements that are in exactly one of the sets.)
        """
        return self ^ other

    def  __sub__(self, other):
        """Return the difference of two sets as a new Set.

        (I.e. all elements that are in this set and not in the other.)
        """
        if not isinstance(other, BaseSet):
            return NotImplemented
        result = self.__class__()
        data = result._data
        value = True
        for elt in self:
            if elt not in other:
                data[elt] = value
        return result

    def difference(self, other):
        """Return the difference of two sets as a new Set.

        (I.e. all elements that are in this set and not in the other.)
        """
        return self - other

    # Membership test

    def __contains__(self, element):
        """Report whether an element is a member of a set.

        (Called in response to the expression `element in self'.)
        """
        try:
            return element in self._data
        except TypeError:
            transform = getattr(element, "_as_temporarily_immutable", None)
            if transform is None:
                raise # re-raise the TypeError exception we caught
            return transform() in self._data

    # Subset and superset test

    def issubset(self, other):
        """Report whether another set contains this set."""
        self._binary_sanity_check(other)
        if len(self) > len(other):  # Fast check for obvious cases
            return False
        for elt in self:
            if elt not in other:
                return False
        return True

    def issuperset(self, other):
        """Report whether this set contains another set."""
        self._binary_sanity_check(other)
        if len(self) < len(other):  # Fast check for obvious cases
            return False
        for elt in other:
            if elt not in self:
                return False
        return True

    # Assorted helpers

    def _binary_sanity_check(self, other):
        # Check that the other argument to a binary operation is also
        # a set, raising a TypeError otherwise.
        if not isinstance(other, BaseSet):
            raise TypeError, "Binary operation only permitted between sets"

    def _compute_hash(self):
        # Calculate hash code for a set by xor'ing the hash codes of
        # the elements.  This ensures that the hash code does not depend
        # on the order in which elements are added to the set.  This is
        # not called __hash__ because a BaseSet should not be hashable;
        # only an ImmutableSet is hashable.
        result = 0
        for elt in self:
            result ^= hash(elt)
        return result

    def _update(self, iterable):
        # The main loop for update() and the subclass __init__() methods.
        data = self._data
        value = True
        it = iter(iterable)
        while True:
            try:
                for element in it:
                    data[element] = value
                return
            except TypeError:
                transform = getattr(element, "_as_immutable", None)
                if transform is None:
                    raise # re-raise the TypeError exception we caught
                data[transform()] = value

class ImmutableSet(BaseSet):
    """Immutable set class."""

    __slots__ = ['_hashcode']

    # BaseSet + hashing

    def __init__(self, iterable=None):
        """Construct an immutable set from an optional iterable."""
        self._hashcode = None
        self._data = {}
        if iterable is not None:
            self._update(iterable)

    def __hash__(self):
        if self._hashcode is None:
            self._hashcode = self._compute_hash()
        return self._hashcode

class Set(BaseSet):
    """ Mutable set class."""

    __slots__ = []

    # BaseSet + operations requiring mutability; no hashing

    def __init__(self, iterable=None):
        """Construct a set from an optional iterable."""
        self._data = {}
        if iterable is not None:
            self._update(iterable)

    def __hash__(self):
        """A Set cannot be hashed."""
        # We inherit object.__hash__, so we must deny this explicitly
        raise TypeError, "Can't hash a Set, only an ImmutableSet."

    # In-place union, intersection, differences

    def __ior__(self, other):
        """Update a set with the union of itself and another."""
        self._binary_sanity_check(other)
        self._data.update(other._data)
        return self

    def union_update(self, other):
        """Update a set with the union of itself and another."""
        self |= other

    def __iand__(self, other):
        """Update a set with the intersection of itself and another."""
        self._binary_sanity_check(other)
        for elt in self._data.keys():
            if elt not in other:
                del self._data[elt]
        return self

    def intersection_update(self, other):
        """Update a set with the intersection of itself and another."""
        self &= other

    def __ixor__(self, other):
        """Update a set with the symmetric difference of itself and another."""
        self._binary_sanity_check(other)
        data = self._data
        value = True
        for elt in other:
            if elt in data:
                del data[elt]
            else:
                data[elt] = value
        return self

    def symmetric_difference_update(self, other):
        """Update a set with the symmetric difference of itself and another."""
        self ^= other

    def __isub__(self, other):
        """Remove all elements of another set from this set."""
        self._binary_sanity_check(other)
        data = self._data
        for elt in other:
            if elt in data:
                del data[elt]
        return self

    def difference_update(self, other):
        """Remove all elements of another set from this set."""
        self -= other

    # Python dict-like mass mutations: update, clear

    def update(self, iterable):
        """Add all values from an iterable (such as a list or file)."""
        self._update(iterable)

    def clear(self):
        """Remove all elements from this set."""
        self._data.clear()

    # Single-element mutations: add, remove, discard

    def add(self, element):
        """Add an element to a set.

        This has no effect if the element is already present.
        """
        try:
            self._data[element] = True
        except TypeError:
            transform = getattr(element, "_as_immutable", None)
            if transform is None:
                raise # re-raise the TypeError exception we caught
            self._data[transform()] = True

    def remove(self, element):
        """Remove an element from a set; it must be a member.

        If the element is not a member, raise a KeyError.
        """
        try:
            del self._data[element]
        except TypeError:
            transform = getattr(element, "_as_temporarily_immutable", None)
            if transform is None:
                raise # re-raise the TypeError exception we caught
            del self._data[transform()]

    def discard(self, element):
        """Remove an element from a set if it is a member.

        If the element is not a member, do nothing.
        """
        try:
            self.remove(element)
        except KeyError:
            pass

    def pop(self):
        """Remove and return an arbitrary set element."""
        return self._data.popitem()[0]

    def _as_immutable(self):
        # Return a copy of self as an immutable set
        return ImmutableSet(self)

    def _as_temporarily_immutable(self):
        # Return self wrapped in a temporarily immutable set
        return _TemporarilyImmutableSet(self)

class _TemporarilyImmutableSet(BaseSet):
    # Wrap a mutable set as if it was temporarily immutable.
    # This only supplies hashing and equality comparisons.

    def __init__(self, set):
        self._set = set
        self._data = set._data  # Needed by ImmutableSet.__eq__()

    def __hash__(self):
        return self._set._compute_hash()
<|endoftext|>


<commit_before>"""Append module search paths for third-party packages to sys.path.

****************************************************************
* This module is automatically imported during initialization. *
****************************************************************

In earlier versions of Python (up to 1.5a3), scripts or modules that
needed to use site-specific modules would place ``import site''
somewhere near the top of their code.  Because of the automatic
import, this is no longer necessary (but code that does it still
works).

This will append site-specific paths to to the module search path.  On
Unix, it starts with sys.prefix and sys.exec_prefix (if different) and
appends lib/python<version>/site-packages as well as lib/site-python.
On other platforms (mainly Mac and Windows), it uses just sys.prefix
(and sys.exec_prefix, if different, but this is unlikely).  The
resulting directories, if they exist, are appended to sys.path, and
also inspected for path configuration files.

A path configuration file is a file whose name has the form
<package>.pth; its contents are additional directories (one per line)
to be added to sys.path.  Non-existing directories (or
non-directories) are never added to sys.path; no directory is added to
sys.path more than once.  Blank lines and lines beginning with
\code{#} are skipped.

For example, suppose sys.prefix and sys.exec_prefix are set to
/usr/local and there is a directory /usr/local/lib/python1.5/site-packages
with three subdirectories, foo, bar and spam, and two path
configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
following:

  # foo package configuration
  foo
  bar
  bletch

and bar.pth contains:

  # bar package configuration
  bar

Then the following directories are added to sys.path, in this order:

  /usr/local/lib/python1.5/site-packages/bar
  /usr/local/lib/python1.5/site-packages/foo

Note that bletch is omitted because it doesn't exist; bar precedes foo
because bar.pth comes alphabetically before foo.pth; and spam is
omitted because it is not mentioned in either path configuration file.

After these path manipulations, an attempt is made to import a module
named sitecustomize, which can perform arbitrary additional
site-specific customizations.  If this import fails with an
ImportError exception, it is silently ignored.

"""

import sys, os

def makepath(*paths):
    dir = os.path.join(*paths)
    return os.path.normcase(os.path.abspath(dir))

L = sys.modules.values()
for m in L:
    if hasattr(m, "__file__"):
        m.__file__ = makepath(m.__file__)
del m, L

# This ensures that the initial path provided by the interpreter contains
# only absolute pathnames, even if we're running from the build directory.
L = []
for dir in sys.path:
    dir = makepath(dir)
    if dir not in L:
        L.append(dir)
sys.path[:] = L
del dir, L

def addsitedir(sitedir):
    sitedir = makepath(sitedir)
    if sitedir not in sys.path:
        sys.path.append(sitedir)        # Add path component
    try:
        names = os.listdir(sitedir)
    except os.error:
        return
    names = map(os.path.normcase, names)
    names.sort()
    for name in names:
        if name[-4:] == ".pth":
            addpackage(sitedir, name)

def addpackage(sitedir, name):
    fullname = os.path.join(sitedir, name)
    try:
        f = open(fullname)
    except IOError:
        return
    while 1:
        dir = f.readline()
        if not dir:
            break
        if dir[0] == '#':
            continue
        if dir[-1] == '\n':
            dir = dir[:-1]
        dir = makepath(sitedir, dir)
        if dir not in sys.path and os.path.exists(dir):
            sys.path.append(dir)

prefixes = [sys.prefix]
if sys.exec_prefix != sys.prefix:
    prefixes.append(sys.exec_prefix)
for prefix in prefixes:
    if prefix:
        if os.sep == '/':
            sitedirs = [makepath(prefix,
                                 "lib",
                                 "python" + sys.version[:3],
                                 "site-packages"),
                        makepath(prefix, "lib", "site-python")]
        else:
            sitedirs = [prefix]
        for sitedir in sitedirs:
            if os.path.isdir(sitedir):
                addsitedir(sitedir)

# Define new built-ins 'quit' and 'exit'.
# These are simply strings that display a hint on how to exit.
if os.sep == ':':
    exit = 'Use Cmd-Q to quit.'
elif os.sep == '\\':
    exit = 'Use Ctrl-Z plus Return to exit.'
else:
    exit = 'Use Ctrl-D (i.e. EOF) to exit.'
import __builtin__
__builtin__.quit = __builtin__.exit = exit
del exit

# interactive prompt objects for printing the license text, a list of
# contributors and the copyright notice.
class _Printer:
    MAXLINES = 23

    def __init__(self, name, data, files=(), dirs=()):
        self.__name = name
        self.__data = data
        self.__files = files
        self.__dirs = dirs
        self.__lines = None

    def __setup(self):
        if self.__lines:
            return
        data = None
        for dir in self.__dirs:
            for file in self.__files:
                file = os.path.join(dir, file)
                try:
                    fp = open(file)
                    data = fp.read()
                    fp.close()
                    break
                except IOError:
                    pass
            if data:
                break
        if not data:
            data = self.__data
        self.__lines = data.split('\n')
        self.__linecnt = len(self.__lines)

    def __repr__(self):
        self.__setup()
        if len(self.__lines) <= self.MAXLINES:
            return "\n".join(self.__lines)
        else:
            return "Type %s() to see the full %s text" % ((self.__name,)*2)

    def __call__(self):
        self.__setup()
        prompt = 'Hit Return for more, or q (and Return) to quit: '
        lineno = 0
        while 1:
            try:
                for i in range(lineno, lineno + self.MAXLINES):
                    print self.__lines[i]
            except IndexError:
                break
            else:
                lineno += self.MAXLINES
                key = None
                while key is None:
                    key = raw_input(prompt)
                    if key not in ('', 'q'):
                        key = None
                if key == 'q':
                    break

__builtin__.copyright = _Printer("copyright", sys.copyright)
__builtin__.credits = _Printer("credits",
    "Python development is led by BeOpen PythonLabs (www.pythonlabs.com).")
here = os.path.dirname(os.__file__)
__builtin__.license = _Printer(
    "license", "See http://www.pythonlabs.com/products/python2.0/license.html",
    ["LICENSE.txt", "LICENSE"],
    [here, os.path.join(here, os.pardir), os.curdir])

# Set the string encoding used by the Unicode implementation.  The
# default is 'ascii', but if you're willing to experiment, you can
# change this.

encoding = "ascii" # Default value set by _PyUnicode_Init()

if 0:
    # Enable to support locale aware default string encodings.
    import locale
    loc = locale.getdefaultlocale()
    if loc[1]:
        encoding = loc[1]

if 0:
    # Enable to switch off string to Unicode coercion and implicit
    # Unicode to string conversion.
    encoding = "undefined"

if encoding != "ascii":
    sys.setdefaultencoding(encoding)

#
# Run custom site specific code, if available.
#
try:
    import sitecustomize
except ImportError:
    pass

#
# Remove sys.setdefaultencoding() so that users cannot change the
# encoding after initialization.  The test for presence is needed when
# this module is run as a script, becuase this code is executed twice.
#
if hasattr(sys, "setdefaultencoding"):
    del sys.setdefaultencoding

def _test():
    print "sys.path = ["
    for dir in sys.path:
        print "    %s," % `dir`
    print "]"

if __name__ == '__main__':
    _test()
<commit_msg>Fix `credits' to credit the Jython developers when running under that interpreter.  Update and reword the credits for CPython.  Closes SF patch #102665.<commit_after>"""Append module search paths for third-party packages to sys.path.

****************************************************************
* This module is automatically imported during initialization. *
****************************************************************

In earlier versions of Python (up to 1.5a3), scripts or modules that
needed to use site-specific modules would place ``import site''
somewhere near the top of their code.  Because of the automatic
import, this is no longer necessary (but code that does it still
works).

This will append site-specific paths to to the module search path.  On
Unix, it starts with sys.prefix and sys.exec_prefix (if different) and
appends lib/python<version>/site-packages as well as lib/site-python.
On other platforms (mainly Mac and Windows), it uses just sys.prefix
(and sys.exec_prefix, if different, but this is unlikely).  The
resulting directories, if they exist, are appended to sys.path, and
also inspected for path configuration files.

A path configuration file is a file whose name has the form
<package>.pth; its contents are additional directories (one per line)
to be added to sys.path.  Non-existing directories (or
non-directories) are never added to sys.path; no directory is added to
sys.path more than once.  Blank lines and lines beginning with
\code{#} are skipped.

For example, suppose sys.prefix and sys.exec_prefix are set to
/usr/local and there is a directory /usr/local/lib/python1.5/site-packages
with three subdirectories, foo, bar and spam, and two path
configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
following:

  # foo package configuration
  foo
  bar
  bletch

and bar.pth contains:

  # bar package configuration
  bar

Then the following directories are added to sys.path, in this order:

  /usr/local/lib/python1.5/site-packages/bar
  /usr/local/lib/python1.5/site-packages/foo

Note that bletch is omitted because it doesn't exist; bar precedes foo
because bar.pth comes alphabetically before foo.pth; and spam is
omitted because it is not mentioned in either path configuration file.

After these path manipulations, an attempt is made to import a module
named sitecustomize, which can perform arbitrary additional
site-specific customizations.  If this import fails with an
ImportError exception, it is silently ignored.

"""

import sys, os

def makepath(*paths):
    dir = os.path.join(*paths)
    return os.path.normcase(os.path.abspath(dir))

L = sys.modules.values()
for m in L:
    if hasattr(m, "__file__"):
        m.__file__ = makepath(m.__file__)
del m, L

# This ensures that the initial path provided by the interpreter contains
# only absolute pathnames, even if we're running from the build directory.
L = []
for dir in sys.path:
    dir = makepath(dir)
    if dir not in L:
        L.append(dir)
sys.path[:] = L
del dir, L

def addsitedir(sitedir):
    sitedir = makepath(sitedir)
    if sitedir not in sys.path:
        sys.path.append(sitedir)        # Add path component
    try:
        names = os.listdir(sitedir)
    except os.error:
        return
    names = map(os.path.normcase, names)
    names.sort()
    for name in names:
        if name[-4:] == ".pth":
            addpackage(sitedir, name)

def addpackage(sitedir, name):
    fullname = os.path.join(sitedir, name)
    try:
        f = open(fullname)
    except IOError:
        return
    while 1:
        dir = f.readline()
        if not dir:
            break
        if dir[0] == '#':
            continue
        if dir[-1] == '\n':
            dir = dir[:-1]
        dir = makepath(sitedir, dir)
        if dir not in sys.path and os.path.exists(dir):
            sys.path.append(dir)

prefixes = [sys.prefix]
if sys.exec_prefix != sys.prefix:
    prefixes.append(sys.exec_prefix)
for prefix in prefixes:
    if prefix:
        if os.sep == '/':
            sitedirs = [makepath(prefix,
                                 "lib",
                                 "python" + sys.version[:3],
                                 "site-packages"),
                        makepath(prefix, "lib", "site-python")]
        else:
            sitedirs = [prefix]
        for sitedir in sitedirs:
            if os.path.isdir(sitedir):
                addsitedir(sitedir)

# Define new built-ins 'quit' and 'exit'.
# These are simply strings that display a hint on how to exit.
if os.sep == ':':
    exit = 'Use Cmd-Q to quit.'
elif os.sep == '\\':
    exit = 'Use Ctrl-Z plus Return to exit.'
else:
    exit = 'Use Ctrl-D (i.e. EOF) to exit.'
import __builtin__
__builtin__.quit = __builtin__.exit = exit
del exit

# interactive prompt objects for printing the license text, a list of
# contributors and the copyright notice.
class _Printer:
    MAXLINES = 23

    def __init__(self, name, data, files=(), dirs=()):
        self.__name = name
        self.__data = data
        self.__files = files
        self.__dirs = dirs
        self.__lines = None

    def __setup(self):
        if self.__lines:
            return
        data = None
        for dir in self.__dirs:
            for file in self.__files:
                file = os.path.join(dir, file)
                try:
                    fp = open(file)
                    data = fp.read()
                    fp.close()
                    break
                except IOError:
                    pass
            if data:
                break
        if not data:
            data = self.__data
        self.__lines = data.split('\n')
        self.__linecnt = len(self.__lines)

    def __repr__(self):
        self.__setup()
        if len(self.__lines) <= self.MAXLINES:
            return "\n".join(self.__lines)
        else:
            return "Type %s() to see the full %s text" % ((self.__name,)*2)

    def __call__(self):
        self.__setup()
        prompt = 'Hit Return for more, or q (and Return) to quit: '
        lineno = 0
        while 1:
            try:
                for i in range(lineno, lineno + self.MAXLINES):
                    print self.__lines[i]
            except IndexError:
                break
            else:
                lineno += self.MAXLINES
                key = None
                while key is None:
                    key = raw_input(prompt)
                    if key not in ('', 'q'):
                        key = None
                if key == 'q':
                    break

__builtin__.copyright = _Printer("copyright", sys.copyright)
if sys.platform[:4] == 'java':
    __builtin__.credits = _Printer(
        "credits",
        "Jython is maintained by the Jython developers (www.jython.org).")
else:
    __builtin__.credits = _Printer("credits", """\
Thanks to CWI, CNRI, BeOpen.com, Digital Creations and a cast of thousands
for supporting Python development.  See www.python.org for more information.""")
here = os.path.dirname(os.__file__)
__builtin__.license = _Printer(
    "license", "See http://www.pythonlabs.com/products/python2.0/license.html",
    ["LICENSE.txt", "LICENSE"],
    [here, os.path.join(here, os.pardir), os.curdir])

# Set the string encoding used by the Unicode implementation.  The
# default is 'ascii', but if you're willing to experiment, you can
# change this.

encoding = "ascii" # Default value set by _PyUnicode_Init()

if 0:
    # Enable to support locale aware default string encodings.
    import locale
    loc = locale.getdefaultlocale()
    if loc[1]:
        encoding = loc[1]

if 0:
    # Enable to switch off string to Unicode coercion and implicit
    # Unicode to string conversion.
    encoding = "undefined"

if encoding != "ascii":
    sys.setdefaultencoding(encoding)

#
# Run custom site specific code, if available.
#
try:
    import sitecustomize
except ImportError:
    pass

#
# Remove sys.setdefaultencoding() so that users cannot change the
# encoding after initialization.  The test for presence is needed when
# this module is run as a script, because this code is executed twice.
#
if hasattr(sys, "setdefaultencoding"):
    del sys.setdefaultencoding

def _test():
    print "sys.path = ["
    for dir in sys.path:
        print "    %s," % `dir`
    print "]"

if __name__ == '__main__':
    _test()
<|endoftext|>


<commit_before>#!/usr/bin/env python

# Permission denied when running the script in the shell? chmod 755 the script .py file

import sys
import os
import re
import subprocess
import titlecase

# COMIC_TAGGER_PATH = 'COMIC_TAGGER_PATH/Applications/ComicTagger.app/Contents/MacOS/ComicTagger'
COMIC_TAGGER_PATH = 'ComicTagger'

def clean_issue(source):
    assert isinstance(source, str)
    return source.lstrip('0')

def escape_for_shell(source):
    return source.replace(' ', '\ ').replace('(', '\(').replace(')', '\)')

def escape_for_comictagger(source):
    return source.replace(',', '^,').replace('=', '^=')

def clean_title(source):
    return titlecase.titlecase(source.replace('.', ' ').lstrip().rstrip())

def show_help():
    print 'Help goes here'

#def issue_is_valid(issue):
#    return str.isdigit(issue)

# todo: allow script to run in interactive mode
# Interactive mode would let the user provide a simple y/n answer
# as to whether each files extracted info should be saved to file

arguments = sys.argv

if len(arguments) < 2:  # the sys.argv[0] contains the script name, so there is always at least one argument
    show_help()
    quit()

folder_name = arguments[1]
print folder_name

if not os.path.isdir(folder_name):
    print "Parameter must be a folder"
    quit()

directory_list = os.listdir(folder_name)

for filename in directory_list:
    full_file_path = os.path.join(folder_name, filename)

    if os.path.isfile(full_file_path):
        print "Processing: %s" % filename
        # look for the issue number and title
        match = re.search('\.(\d*)\.\.(.*)\.cbz|cbr', filename)
        if match:
            issue = match.group(1)
            title = match.group(2)

            issue = clean_issue(issue)
            title = clean_title(title)

            # todo: validate issue is a clean usable number

            print "Found Issue: %s" % issue
            print "Found Title: %s" % title

            # todo: check existing tags and report/skip if there is no work to do
            # command = '%s -p %s' % (COMIC_TAGGER_PATH, escape_spaces(full_file_path))

            # Ask user for permission to modify
            answer = raw_input("Update tags for this file? (y/n): ")

            if answer == "y":
                command = '%s -s -m "title=%s,issue=%s" -t cr %s' % (COMIC_TAGGER_PATH, escape_for_comictagger(title), issue, escape_for_shell(full_file_path))
                return_code = subprocess.call(command, shell=True)
                # if return_code != 0:
                #     print "Return code: %s" % return_code

        print ""<commit_msg>Basic help is now complete<commit_after>#!/usr/bin/env python

# Permission denied when running the script in the shell? chmod 755 the script .py file

import sys
import os
import re
import subprocess
import titlecase

# COMIC_TAGGER_PATH = 'COMIC_TAGGER_PATH/Applications/ComicTagger.app/Contents/MacOS/ComicTagger'
COMIC_TAGGER_PATH = 'ComicTagger'

def clean_issue(source):
    assert isinstance(source, str)
    return source.lstrip('0')

def escape_for_shell(source):
    return source.replace(' ', '\ ').replace('(', '\(').replace(')', '\)')

def escape_for_comictagger(source):
    return source.replace(',', '^,').replace('=', '^=')

def clean_title(source):
    return titlecase.titlecase(source.replace('.', ' ').lstrip().rstrip())

def show_help():
    # print 'Usage: ComicTagger [OPTION]... [FOLDER]'
    print ''
    print 'Usage: ComicTagger [FOLDER]'
    print ''
    print 'A utility for detecting Issue and Titles from comic archives downloaded with manga_downloader (https://github.com/jiaweihli/manga_downloader), then inserting that information into the archive using ComicTagger'
    print ''
    print 'The name of all files in the specified folder will be examined for issue number and title and then the user is asked if the data should be inserted'
    print ''

#def issue_is_valid(issue):
#    return str.isdigit(issue)

# todo: allow script to run in interactive mode
# Interactive mode would let the user provide a simple y/n answer
# as to whether each files extracted info should be saved to file

arguments = sys.argv

if len(arguments) < 2:  # the sys.argv[0] contains the script name, so there is always at least one argument
    show_help()
    quit()

folder_name = arguments[1]

if not os.path.isdir(folder_name):
    print "Parameter must be a folder"
    quit()

directory_list = os.listdir(folder_name)

for filename in directory_list:
    full_file_path = os.path.join(folder_name, filename)

    if os.path.isfile(full_file_path):
        print "Processing: %s" % filename
        # look for the issue number and title
        match = re.search('\.(\d*)\.\.(.*)\.cbz|cbr', filename)
        if match:
            issue = match.group(1)
            title = match.group(2)

            issue = clean_issue(issue)
            title = clean_title(title)

            # todo: validate issue is a clean usable number

            print "Found Issue: %s" % issue
            print "Found Title: %s" % title

            # todo: check existing tags and report/skip if there is no work to do
            # command = '%s -p %s' % (COMIC_TAGGER_PATH, escape_spaces(full_file_path))

            # Ask user for permission to modify
            answer = raw_input("Update tags for this file? (y/n): ")

            if answer == "y":
                command = '%s -s -m "title=%s,issue=%s" -t cr %s' % (COMIC_TAGGER_PATH, escape_for_comictagger(title), issue, escape_for_shell(full_file_path))
                return_code = subprocess.call(command, shell=True)
                # if return_code != 0:
                #     print "Return code: %s" % return_code

        print ""<|endoftext|>


<commit_before>import random
bases = ['A','T','C','G']
seq1= ''
for iteration in range(500):
    seq1 += bases[]random.randint(0,3)
seq2 = seq1
print seq1
print seq2<commit_msg>Script to generate test sequences which have been subject to known mutations to see how Circos reacts.<commit_after>import random
def insertion(mut_seq,changes_list):
    i = random.randint(0,len(mut_seq)-1)
    base = ['A','T','C','G'][random.randint(0,3)]
    new_seq = mut_seq[:i] + str(base) + mut_seq[i:]
    changes_list += ['Insertion of '+str(base)+' at position'+str(i)]
    return new_seq,changes_list

def deletion(mut_seq,changes_list):
    i = random.randint(0,len(mut_seq)-1)
    new_seq = mut_seq[:i] + mut_seq[i+1:]
    changes_list += ['Deletion at position'+str(i)]
    return new_seq,changes_list

def inversion(mut_seq,changes_list):
    i = random.randint(0,len(mut_seq)-1)
    j = random.randint(0,len(mut_seq)-1)
    if i=j:
        break
    elif i<j:
        endpoints = (i,j)
    else:
        endpoints = (j,i)
    new_seq = mut_seq[:endpoints[0]] + mut_seq[endpoints[0]:endpoints[1]:-1] + mut_seq[endpoints[1]:]
    changes_list += ['Inversion between positions ' + str(endpoints[0]) + ' and ' + str(endpoints[1])]
    return new_seq,changes_list

def translocation(mut_seq,changes_list):
    i = random.randint(0,len(mut_seq)-1)
    j = random.randint(0,len(mut_seq)-1)
    k = random.randint(0,len(mut_seq)-1)
    l = random.randint(0,len(mut_seq)-1)
    if i=j:
        break
    elif i<j:
        endpoints = (i,j)
    else:
        endpoints = (j,i)
    if i=j:
        break
    elif i<j:
        new_pos = (i,j)
    else:
        new_pos = (j,i)
    transposon = mut_seq[endpoints[0]:endpoints[1]]

    changes_list += ['Inversion between positions ' + str(endpoints[0]) + ' and ' + str(endpoints[1])]
    return new_seq,changes_list

def main():
    bases = ['A','T','C','G']
    base_seq= ''
    for iteration in range(500):
        base_seq += bases[random.randint(0,3)]
    mut_seq = base_seq
    print base_seq
    print mut_seq
    changes_list = []
    mut_seq,changes_list = insertion(mut_seq,changes_list)
    print 'Sequence:',mut_seq
    for change in changes_list:
        print change
    mut_seq,changes_list = deletion(mut_seq,changes_list)
    print 'Sequence:',mut_seq
    for change in changes_list:
        print change
    mut_seq,changes_list = translocation(mut_seq,changes_list)
    print 'Sequence:',mut_seq
    for change in changes_list:
        print change
    mut_seq,changes_list = inversion(mut_seq,changes_list)
    print 'Sequence:',mut_seq
    for change in changes_list:
        print change

main()
<|endoftext|>


<commit_before>import requests
import base64
import json
import logging
import time

from O365.event import Event

log = logging.getLogger(__name__)

class Calendar( object ):
	'''
	Calendar manages lists of events on an associated calendar on office365.

	Methods:
		getName - Returns the name of the calendar.
		getCalendarId - returns the GUID that identifies the calendar on office365
		getId - synonym of getCalendarId
		getEvents - kicks off the process of fetching events.
		fetchEvents - legacy duplicate of getEvents

	Variable:
		events_url - the url that is actually called to fetch events. takes an ID, start, and end date.
		time_string - used for converting between struct_time and json's time format.
	'''
	events_url = 'https://outlook.office365.com/api/v1.0/me/calendars/{0}/calendarview?startDateTime={1}&endDateTime={2}&$top={3}'
	time_string = '%Y-%m-%dT%H:%M:%SZ'

	def __init__(self, json=None, auth=None, verify=True):
		'''
		Wraps all the information for managing calendars.
		'''
		self.json = json
		self.auth = auth
		self.events = []

		if json:
			log.debug('translating calendar information into local variables.')
			self.calendarId = json['Id']
			self.name = json['Name']

		self.verify = verify

	def getName(self):
		'''Get the calendar's Name.'''
		return self.json['Name']

	def getCalendarId(self):
		'''Get calendar's GUID for office 365. mostly used internally in this library.'''
		return self.json['Id']

	def getId(self):
		'''Get calendar's GUID for office 365. mostly used internally in this library.'''
		return self.getCalendarId()

	def fetchEvents(self,start=None,end=None):
		'''
		So I originally made this function "fetchEvents" which was a terrible idea. Everything else
		is "getX" except events which were appearenty to good for that. So this function is just a
		pass through for legacy sake.
		'''
		return self.getEvents(start,end)

	def getEvents(self,start=None,end=None, eventCount=10):
		'''
		Pulls events in for this calendar. default range is today to a year now.

		Keyword Arguments:
		start -- The starting date from where you want to begin requesting events. The expected
		type is a struct_time. Default is today.
		end -- The ending date to where you want to end requesting events. The expected
		type is a struct_time. Default is a year from start.
		'''

		# If no start time has been supplied, it is assumed you want to start as of now.
		if not start:
			start = time.strftime(self.time_string)

		# If no end time has been supplied, it is assumed you want the end time to be a year
		# from what ever the start date was.
		if not end:
			end = time.time()
			end += 3600*24*365
			end = time.gmtime(end)
			end = time.strftime(self.time_string,end)

		# This is where the actual call to Office365 happens.
		response = requests.get(self.events_url.format(self.json['Id'],start,end,eventCount) ,auth=self.auth, verify=self.verify)
		log.info('Response from O365: %s', str(response))

		#This takes that response and then parses it into individual calendar events.
		for event in response.json()['value']:
			try:
				duplicate = False

				# checks to see if the event is a duplicate. if it is local changes are clobbered.
				for i,e in enumerate(self.events):
					if e.json['Id'] == event['Id']:
						self.events[i] = Event(event,self.auth,self)
						duplicate = True
						break

				if not duplicate:
					self.events.append(Event(event,self.auth,self))

				log.debug('appended event: %s',event['Subject'])
			except Exception as e:
				log.info('failed to append calendar: %',str(e))

		log.debug('all events retrieved and put in to the list.')
		return True

# To the King!
<commit_msg>Add __str__ method to cal<commit_after>import requests
import base64
import json
import logging
import time

from O365.event import Event

log = logging.getLogger(__name__)

class Calendar( object ):
	'''
	Calendar manages lists of events on an associated calendar on office365.

	Methods:
		getName - Returns the name of the calendar.
		getCalendarId - returns the GUID that identifies the calendar on office365
		getId - synonym of getCalendarId
		getEvents - kicks off the process of fetching events.
		fetchEvents - legacy duplicate of getEvents

	Variable:
		events_url - the url that is actually called to fetch events. takes an ID, start, and end date.
		time_string - used for converting between struct_time and json's time format.
	'''
	events_url = 'https://outlook.office365.com/api/v1.0/me/calendars/{0}/calendarview?startDateTime={1}&endDateTime={2}&$top={3}'
	time_string = '%Y-%m-%dT%H:%M:%SZ'

	def __init__(self, json=None, auth=None, verify=True):
		'''
		Wraps all the information for managing calendars.
		'''
		self.json = json
		self.auth = auth
		self.events = []

		if json:
			log.debug('translating calendar information into local variables.')
			self.calendarId = json['Id']
			self.name = json['Name']

		self.verify = verify

	def __str__(self):
		'''Return informal, nicely printable string.'''
		return self.getName()

	def getName(self):
		'''Get the calendar's Name.'''
		return self.json['Name']

	def getCalendarId(self):
		'''Get calendar's GUID for office 365. mostly used internally in this library.'''
		return self.json['Id']

	def getId(self):
		'''Get calendar's GUID for office 365. mostly used internally in this library.'''
		return self.getCalendarId()

	def fetchEvents(self,start=None,end=None):
		'''
		So I originally made this function "fetchEvents" which was a terrible idea. Everything else
		is "getX" except events which were appearenty to good for that. So this function is just a
		pass through for legacy sake.
		'''
		return self.getEvents(start,end)

	def getEvents(self,start=None,end=None, eventCount=10):
		'''
		Pulls events in for this calendar. default range is today to a year now.

		Keyword Arguments:
		start -- The starting date from where you want to begin requesting events. The expected
		type is a struct_time. Default is today.
		end -- The ending date to where you want to end requesting events. The expected
		type is a struct_time. Default is a year from start.
		'''

		# If no start time has been supplied, it is assumed you want to start as of now.
		if not start:
			start = time.strftime(self.time_string)

		# If no end time has been supplied, it is assumed you want the end time to be a year
		# from what ever the start date was.
		if not end:
			end = time.time()
			end += 3600*24*365
			end = time.gmtime(end)
			end = time.strftime(self.time_string,end)

		# This is where the actual call to Office365 happens.
		response = requests.get(self.events_url.format(self.json['Id'],start,end,eventCount) ,auth=self.auth, verify=self.verify)
		log.info('Response from O365: %s', str(response))

		#This takes that response and then parses it into individual calendar events.
		for event in response.json()['value']:
			try:
				duplicate = False

				# checks to see if the event is a duplicate. if it is local changes are clobbered.
				for i,e in enumerate(self.events):
					if e.json['Id'] == event['Id']:
						self.events[i] = Event(event,self.auth,self)
						duplicate = True
						break

				if not duplicate:
					self.events.append(Event(event,self.auth,self))

				log.debug('appended event: %s',event['Subject'])
			except Exception as e:
				log.info('failed to append calendar: %',str(e))

		log.debug('all events retrieved and put in to the list.')
		return True

# To the King!
<|endoftext|>