def run(src,
        grammar=NODEBOX,
        format=None,
        outputfile=None,
        iterations=1,
        buff=None,
        window=True,
        title=None,
        fullscreen=None,
        close_window=False,
        server=False,
        port=7777,
        show_vars=False,
        vars=None,
        namespace=None,
        run_shell=False,
        args=[],
        verbose=False,
        background_thread=True):
    """
    Create and run a bot, the arguments all correspond to sanitized
    commandline options.

    :param background_thread: If True then use a background thread.

    Other args are split into create_args and run_args

    See create_bot for details on create_args

    run_args are passed to bot.run - see Nodebot.run or Drawbot.run

    Background thread:

    readline in python is blocking, running the app in a background
    thread opens up the main thread for IO on stdin/stdout, which
    can be used for communication with shoebot when livecoding is
    enabled.

    See shoebot.io for implementation of the shell, and the gedit
    plugin for an example of using livecoding.
    """
    # Munge shoebogt sys.argv
    sys.argv = [sys.argv[
                    0]] + args  # Remove shoebot parameters so sbot can be used in place of the python interpreter (e.g. for sphinx).

    # arguments for create_bot
    create_args = [src,
                   grammar,
                   format,
                   outputfile,
                   iterations,
                   buff,
                   window,
                   title,
                   fullscreen,
                   server,
                   port,
                   show_vars]
    create_kwargs = dict(vars=vars, namespace=namespace)
    run_args = [src]
    run_kwargs = dict(
        iterations=iterations,
        frame_limiter=window,
        verbose=verbose,
        # run forever except 1. windowed mode is off 2. if --close-window was specified and
        # 3. if an output file was indicated
        run_forever=window and not (close_window or bool(outputfile)),
    )

    # Run shoebot in a background thread so we can run a cmdline shell in the current thread
    if background_thread:
        sbot_thread = ShoebotThread(
            create_args=create_args,
            create_kwargs=create_kwargs,
            run_args=run_args,
            run_kwargs=run_kwargs,
            send_sigint=run_shell
        )
        sbot_thread.start()
        sbot = sbot_thread.sbot
    else:
        print('background thread disabled')
        # This is a debug option, things should always work using the
        # background thread (crosses fingers)
        if run_shell:
            # python readline is blocking, so ui must run in a seperate
            # thread
            raise ValueError('UI Must run in a separate thread to shell and shell needs main thread')

        sbot_thread = None
        sbot = create_bot(*create_args, **create_kwargs)
        sbot.run(*run_args, **run_kwargs)

    if run_shell:
        import shoebot.sbio.shell
        shell = shoebot.sbio.shell.ShoebotCmd(sbot, trusted=True)
        try:
            shell.cmdloop()
        except KeyboardInterrupt as e:
            publish_event(QUIT_EVENT)  # Handle Ctrl-C
            # KeyboardInterrupt is generated by os.kill from the other thread
            if verbose:
                raise
            else:
                return
    elif background_thread:
        try:
            while sbot_thread.is_alive():
                sleep(1)
        except KeyboardInterrupt:
            publish_event(QUIT_EVENT)

    if all((background_thread, sbot_thread)):
        sbot_thread.join()

    return sbot

Create and run a bot the arguments all correspond to sanitized commandline options .


def save_as(self):
        """
        Return True if the buffer was saved
        """
        chooser = ShoebotFileChooserDialog(_('Save File'), None, Gtk.FileChooserAction.SAVE,
                                           (Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT,
                                            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
        chooser.set_do_overwrite_confirmation(True)
        chooser.set_transient_for(self)
        saved = chooser.run() == Gtk.ResponseType.ACCEPT
        if saved:
            old_filename = self.filename
            self.source_buffer.filename = chooser.get_filename()
            if not self.save():
                self.filename = old_filename
        chooser.destroy()
        return saved

def
save_as
(
self
)
:
chooser
=
ShoebotFileChooserDialog
(
_
(
'Save File'
)
,
,
Gtk
.
FileChooserAction
.
SAVE
,
(
Gtk
.
STOCK_SAVE
,
Gtk
.
ResponseType
.
ACCEPT
,
Gtk
.
STOCK_CANCEL
,
Gtk
.
ResponseType
.
CANCEL
)
)
chooser
.
set_do_overwrite_confirmation
(
True
)
chooser
.
set_transient_for
(
self
)
saved
=
chooser
.
run
(
)
==
Gtk
.
ResponseType
.
ACCEPT
if
saved
:
old_filename
=
self
.
filename
self
.
source_buffer
.
filename
=
chooser
.
get_filename
(
)
if
not
self
.
save
(
)
:
self
.
filename
=
old_filename
chooser
.
destroy
(
)
return
saved


def widget_changed(self, widget, v):
        ''' Called when a slider is adjusted. '''
        # set the appropriate bot var
        if v.type is NUMBER:
            self.bot._namespace[v.name] = widget.get_value()
            self.bot._vars[v.name].value = widget.get_value()  ## Not sure if this is how to do this - stu
            publish_event(VARIABLE_UPDATED_EVENT, v)  # pretty dumb for now
        elif v.type is BOOLEAN:
            self.bot._namespace[v.name] = widget.get_active()
            self.bot._vars[v.name].value = widget.get_active()  ## Not sure if this is how to do this - stu
            publish_event(VARIABLE_UPDATED_EVENT, v)  # pretty dumb for now
        elif v.type is TEXT:
            self.bot._namespace[v.name] = widget.get_text()
            self.bot._vars[v.name].value = widget.get_text()  ## Not sure if this is how to do this - stu
            publish_event(VARIABLE_UPDATED_EVENT, v)

def widget_changed ( self , widget , v ) : if v . type is NUMBER : self . bot . _namespace [ v . name ] = widget . get_value ( ) self . bot . _vars [ v . name ] . value = widget . get_value ( ) publish_event ( VARIABLE_UPDATED_EVENT , v ) elif v . type is BOOLEAN : self . bot . _namespace [ v . name ] = widget . get_active ( ) self . bot . _vars [ v . name ] . value = widget . get_active ( ) publish_event ( VARIABLE_UPDATED_EVENT , v ) elif v . type is TEXT : self . bot . _namespace [ v . name ] = widget . get_text ( ) self . bot . _vars [ v . name ] . value = widget . get_text ( ) publish_event ( VARIABLE_UPDATED_EVENT , v )


def var_added(self, v):
        """
        var was added in the bot while it ran, possibly
        by livecoding

        :param v:
        :return:
        """
        self.add_variable(v)

        self.window.set_size_request(400, 35 * len(self.widgets.keys()))
        self.window.show_all()

def var_added ( self , v ) : self . add_variable ( v ) self . window . set_size_request ( 400 , 35 * len ( self . widgets . keys ( ) ) ) self . window . show_all ( )

var was added in the bot while it ran possibly by livecoding


def var_deleted(self, v):
        """
        var was added in the bot

        :param v:
        :return:
        """
        widget = self.widgets[v.name]

        # widgets are all in a single container ..
        parent = widget.get_parent()
        self.container.remove(parent)
        del self.widgets[v.name]

        self.window.set_size_request(400, 35 * len(self.widgets.keys()))
        self.window.show_all()

def var_deleted ( self , v ) : widget = self . widgets [ v . name ] parent = widget . get_parent ( ) self . container . remove ( parent ) del self . widgets [ v . name ] self . window . set_size_request ( 400 , 35 * len ( self . widgets . keys ( ) ) ) self . window . show_all ( )


def parse(svg, cached=False, _copy=True):

    """ Returns cached copies unless otherwise specified.
    """

    if not cached:
        dom = parser.parseString(svg)
        paths = parse_node(dom, [])
    else:
        id = _cache.id(svg)
        if not _cache.has_key(id):
            dom = parser.parseString(svg)
            _cache.save(id, parse_node(dom, []))
        paths = _cache.load(id, _copy)

    return paths

def parse ( svg , cached = False , _copy = True ) : if not cached : dom = parser . parseString ( svg ) paths = parse_node ( dom , [ ] ) else : id = _cache . id ( svg ) if not _cache . has_key ( id ) : dom = parser . parseString ( svg ) _cache . save ( id , parse_node ( dom , [ ] ) ) paths = _cache . load ( id , _copy ) return paths

Returns cached copies unless otherwise specified .


def get_attribute(element, attribute, default=0):

    """ Returns XML element's attribute, or default if none.
    """

    a = element.getAttribute(attribute)
    if a == "":
        return default
    return a

def get_attribute ( element , attribute , default = 0 ) : a = element . getAttribute ( attribute ) if a == "" : return default return a

Returns XML element s attribute or default if none .


def add_color_info(e, path):

    """ Expand the path with color information.

    Attempts to extract fill and stroke colors
    from the element and adds it to path attributes.

    """

    _ctx.colormode(RGB, 1.0)

    def _color(hex, alpha=1.0):
        if hex == "none": return None
        n = int(hex[1:],16)
        r = (n>>16)&0xff
        g = (n>>8)&0xff
        b = n&0xff
        return _ctx.color(r/255.0, g/255.0, b/255.0, alpha)

    path.fill = (0,0,0,0)
    path.stroke = (0,0,0,0)
    path.strokewidth = 0

    # See if we can find an opacity attribute,
    # which is the color's alpha.
    alpha = get_attribute(e, "opacity", default="")
    if alpha == "":
        alpha = 1.0
    else:
        alpha = float(alpha)

    # Colors stored as fill="" or stroke="" attributes.
    try: path.fill = _color(get_attribute(e, "fill", default="#00000"), alpha)
    except:
        pass
    try: path.stroke = _color(get_attribute(e, "stroke", default="none"), alpha)
    except:
        pass
    try: path.strokewidth = float(get_attribute(e, "stroke-width", default="1"))
    except:
        pass

    # Colors stored as a CSS style attribute, for example:
    # style="fill:#ff6600;stroke:#ffe600;stroke-width:0.06742057"
    style = get_attribute(e, "style", default="").split(";")
    for s in style:
        try:
            if s.startswith("fill:"):
                path.fill = _color(s.replace("fill:", ""))
            elif s.startswith("stroke:"):
                path.stroke = _color(s.replace("stroke:", ""))
            elif s.startswith("stroke-width:"):
                path.strokewidth = float(s.replace("stroke-width:", ""))
        except:
            pass

    # A path with beginning and ending coordinate
    # at the same location is considered closed.
    # Unless it contains a MOVETO somewhere in the middle.
    path.closed = False
    if path[0].x == path[len(path)-1].x and \
       path[0].y == path[len(path)-1].y:
        path.closed = True
    for i in range(1,-1):
        if path[i].cmd == MOVETO:
            path.closed = False

    return path

def add_color_info ( e , path ) : _ctx . colormode ( RGB , 1.0 ) def _color ( hex , alpha = 1.0 ) : if hex == "none" : return None n = int ( hex [ 1 : ] , 16 ) r = ( n >> 16 ) & 0xff g = ( n >> 8 ) & 0xff b = n & 0xff return _ctx . color ( r / 255.0 , g / 255.0 , b / 255.0 , alpha ) path . fill = ( 0 , 0 , 0 , 0 ) path . stroke = ( 0 , 0 , 0 , 0 ) path . strokewidth = 0 alpha = get_attribute ( e , "opacity" , default = "" ) if alpha == "" : alpha = 1.0 else : alpha = float ( alpha ) try : path . fill = _color ( get_attribute ( e , "fill" , default = "#00000" ) , alpha ) except : pass try : path . stroke = _color ( get_attribute ( e , "stroke" , default = "none" ) , alpha ) except : pass try : path . strokewidth = float ( get_attribute ( e , "stroke-width" , default = "1" ) ) except : pass style = get_attribute ( e , "style" , default = "" ) . split ( ";" ) for s in style : try : if s . startswith ( "fill:" ) : path . fill = _color ( s . replace ( "fill:" , "" ) ) elif s . startswith ( "stroke:" ) : path . stroke = _color ( s . replace ( "stroke:" , "" ) ) elif s . startswith ( "stroke-width:" ) : path . strokewidth = float ( s . replace ( "stroke-width:" , "" ) ) except : pass path . closed = False if path [ 0 ] . x == path [ len ( path ) - 1 ] . x and path [ 0 ] . y == path [ len ( path ) - 1 ] . y : path . closed = True for i in range ( 1 , - 1 ) : if path [ i ] . cmd == MOVETO : path . closed = False return path

Expand the path with color information . Attempts to extract fill and stroke colors from the element and adds it to path attributes .


def copy(self, graph):

        """ Returns a copy of the event handler, remembering the last node clicked.
        """

        e = events(graph, self._ctx)
        e.clicked = self.clicked
        return e

def copy ( self , graph ) : e = events ( graph , self . _ctx ) e . clicked = self . clicked return e

Returns a copy of the event handler remembering the last node clicked .


def drag(self, node):

        """ Drags given node to mouse location.
        """

        dx = self.mouse.x - self.graph.x
        dy = self.mouse.y - self.graph.y

        # A dashed line indicates the drag vector.
        s = self.graph.styles.default
        self._ctx.nofill()
        self._ctx.nostroke()
        if s.stroke:
            self._ctx.strokewidth(s.strokewidth)
            self._ctx.stroke(
                s.stroke.r,
                s.stroke.g,
                s.stroke.g,
                0.75
            )
        p = self._ctx.line(node.x, node.y, dx, dy, draw=False)
        try: p._nsBezierPath.setLineDash_count_phase_([2,4], 2, 50)
        except:
            pass
        self._ctx.drawpath(p)
        r = node.__class__(None).r * 0.75
        self._ctx.oval(dx-r/2, dy-r/2, r, r)

        node.vx = dx / self.graph.d
        node.vy = dy / self.graph.d

def drag ( self , node ) : dx = self . mouse . x - self . graph . x dy = self . mouse . y - self . graph . y s = self . graph . styles . default self . _ctx . nofill ( ) self . _ctx . nostroke ( ) if s . stroke : self . _ctx . strokewidth ( s . strokewidth ) self . _ctx . stroke ( s . stroke . r , s . stroke . g , s . stroke . g , 0.75 ) p = self . _ctx . line ( node . x , node . y , dx , dy , draw = False ) try : p . _nsBezierPath . setLineDash_count_phase_ ( [ 2 , 4 ] , 2 , 50 ) except : pass self . _ctx . drawpath ( p ) r = node . __class__ ( None ) . r * 0.75 self . _ctx . oval ( dx - r / 2 , dy - r / 2 , r , r ) node . vx = dx / self . graph . d node . vy = dy / self . graph . d


def hover(self, node):

        """ Displays a popup when hovering over a node.
        """

        if self.popup == False: return
        if self.popup == True or self.popup.node != node:
            if self.popup_text.has_key(node.id):
                texts = self.popup_text[node.id]
            else:
                texts = None
            self.popup = popup(self._ctx, node, texts)
        self.popup.draw()

def hover ( self , node ) : if self . popup == False : return if self . popup == True or self . popup . node != node : if self . popup_text . has_key ( node . id ) : texts = self . popup_text [ node . id ] else : texts = None self . popup = popup ( self . _ctx , node , texts ) self . popup . draw ( )

Displays a popup when hovering over a node .


def textpath(self, i):

        """ Returns a cached textpath of the given text in queue.
        """

        if len(self._textpaths) == i:
            self._ctx.font(self.font, self.fontsize)
            txt = self.q[i]
            if len(self.q) > 1:
                # Indicate current text (e.g. 5/13).
                txt += " ("+str(i+1)+"/" + str(len(self.q))+")"
            p = self._ctx.textpath(txt, 0, 0, width=self._w)
            h = self._ctx.textheight(txt, width=self._w)
            self._textpaths.append((p, h))

        return self._textpaths[i]

def
textpath
(
self
,
i
)
:
if
len
(
self
.
_textpaths
)
==
i
:
self
.
_ctx
.
font
(
self
.
font
,
self
.
fontsize
)
txt
=
self
.
q
[
i
]
if
len
(
self
.
q
)
>
1
:
txt
+=
" ("
+
str
(
i
+
1
)
+
"/"
+
str
(
len
(
self
.
q
)
)
+
")"
p
=
self
.
_ctx
.
textpath
(
txt
,
0
,
0
,
width
=
self
.
_w
)
h
=
self
.
_ctx
.
textheight
(
txt
,
width
=
self
.
_w
)
self
.
_textpaths
.
append
(
(
p
,
h
)
)
return
self
.
_textpaths
[
i
]

Returns a cached textpath of the given text in queue .


def update(self):

        """ Rotates the queued texts and determines display time.
        """

        if self.delay > 0:
            # It takes a while for the popup to appear.
            self.delay -= 1; return

        if self.fi == 0:
            # Only one text in queue, displayed infinitely.
            if len(self.q) == 1:
                self.fn = float("inf")
            # Else, display time depends on text length.
            else:
                self.fn = len(self.q[self.i]) / self.speed
                self.fn = max(self.fn, self.mf)

        self.fi += 1
        if self.fi > self.fn:
            # Rotate to the next text in queue.
            self.fi = 0
            self.i = (self.i+1) % len(self.q)

def update ( self ) : if self . delay > 0 : self . delay -= 1 return if self . fi == 0 : if len ( self . q ) == 1 : self . fn = float ( "inf" ) else : self . fn = len ( self . q [ self . i ] ) / self . speed self . fn = max ( self . fn , self . mf ) self . fi += 1 if self . fi > self . fn : self . fi = 0 self . i = ( self . i + 1 ) % len ( self . q )

Rotates the queued texts and determines display time .


def draw(self):

        """ Draws a popup rectangle with a rotating text queue.
        """

        if len(self.q) > 0:
            self.update()

            if self.delay == 0:

                # Rounded rectangle in the given background color.
                p, h = self.textpath(self.i)
                f = self.fontsize
                self._ctx.fill(self.background)
                self._ctx.rect(
                    self.node.x + f*1.0,
                    self.node.y + f*0.5,
                    self._w + f,
                    h + f*1.5,
                    roundness=0.2
                )

                # Fade in/out the current text.
                alpha = 1.0
                if self.fi < 5:
                    alpha = 0.2 * self.fi
                if self.fn-self.fi < 5:
                    alpha = 0.2 * (self.fn-self.fi)
                self._ctx.fill(
                    self.text.r,
                    self.text.g,
                    self.text.b,
                    self.text.a * alpha
                )

                self._ctx.translate(self.node.x + f*2.0, self.node.y + f*2.5)
                self._ctx.drawpath(p)

def draw ( self ) : if len ( self . q ) > 0 : self . update ( ) if self . delay == 0 : p , h = self . textpath ( self . i ) f = self . fontsize self . _ctx . fill ( self . background ) self . _ctx . rect ( self . node . x + f * 1.0 , self . node . y + f * 0.5 , self . _w + f , h + f * 1.5 , roundness = 0.2 ) alpha = 1.0 if self . fi < 5 : alpha = 0.2 * self . fi if self . fn - self . fi < 5 : alpha = 0.2 * ( self . fn - self . fi ) self . _ctx . fill ( self . text . r , self . text . g , self . text . b , self . text . a * alpha ) self . _ctx . translate ( self . node . x + f * 2.0 , self . node . y + f * 2.5 ) self . _ctx . drawpath ( p )

Draws a popup rectangle with a rotating text queue .


def write_main(argv):
    """
    write FILENAME
        Write a local copy of FILENAME using FILENAME_tweaks for local tweaks.
    """
    if len(argv) != 1:
        print("Please provide the name of a file to write.")
        return 1

    filename = argv[0]
    resource_name = "files/" + filename
    tweaks_name = amend_filename(filename, "_tweaks")

    if not pkg_resources.resource_exists("edx_lint", resource_name):
        print(u"Don't have file %r to write." % filename)
        return 2

    if os.path.exists(filename):
        print(u"Checking existing copy of %s" % filename)
        tef = TamperEvidentFile(filename)
        if not tef.validate():
            bak_name = amend_filename(filename, "_backup")
            print(u"Your copy of %s seems to have been edited, renaming it to %s" % (filename, bak_name))
            if os.path.exists(bak_name):
                print(u"A previous %s exists, deleting it" % bak_name)
                os.remove(bak_name)
            os.rename(filename, bak_name)

    print(u"Reading edx_lint/files/%s" % filename)
    cfg = configparser.RawConfigParser()
    resource_string = pkg_resources.resource_string("edx_lint", resource_name).decode("utf8")

    # pkg_resources always reads binary data (in both python2 and python3).
    # ConfigParser.read_string only exists in python3, so we have to wrap the string
    # from pkg_resources in a cStringIO so that we can pass it into ConfigParser.readfp.
    if six.PY2:
        cfg.readfp(cStringIO(resource_string), resource_name)
    else:
        cfg.read_string(resource_string, resource_name)     # pylint: disable=no-member

    if os.path.exists(tweaks_name):
        print(u"Applying local tweaks from %s" % tweaks_name)
        cfg_tweaks = configparser.RawConfigParser()
        cfg_tweaks.read([tweaks_name])

        merge_configs(cfg, cfg_tweaks)

    print(u"Writing %s" % filename)
    output_text = cStringIO()
    output_text.write(WARNING_HEADER.format(filename=filename, tweaks_name=tweaks_name))
    cfg.write(output_text)

    out_tef = TamperEvidentFile(filename)
    if six.PY2:
        output_bytes = output_text.getvalue()
    else:
        output_bytes = output_text.getvalue().encode("utf8")
    out_tef.write(output_bytes)

    return 0

write FILENAME Write a local copy of FILENAME using FILENAME_tweaks for local tweaks .


def amend_filename(filename, amend):
    """Amend a filename with a suffix.

    amend_filename("foo.txt", "_tweak") --> "foo_tweak.txt"

    """
    base, ext = os.path.splitext(filename)
    amended_name = base + amend + ext
    return amended_name

def amend_filename ( filename , amend ) : base , ext = os . path . splitext ( filename ) amended_name = base + amend + ext return amended_name


def check_main(argv):
    """
    check FILENAME
        Check that FILENAME has not been edited since writing.
    """
    if len(argv) != 1:
        print("Please provide the name of a file to check.")
        return 1

    filename = argv[0]

    if os.path.exists(filename):
        print(u"Checking existing copy of %s" % filename)
        tef = TamperEvidentFile(filename)
        if tef.validate():
            print(u"Your copy of %s is good" % filename)
        else:
            print(u"Your copy of %s seems to have been edited" % filename)
    else:
        print(u"You don't have a copy of %s" % filename)

    return 0

check FILENAME Check that FILENAME has not been edited since writing .


def merge_configs(main, tweaks):
    """Merge tweaks into a main config file."""
    for section in tweaks.sections():
        for option in tweaks.options(section):
            value = tweaks.get(section, option)
            if option.endswith("+"):
                option = option[:-1]
                value = main.get(section, option) + value
            main.set(section, option, value)

def merge_configs ( main , tweaks ) : for section in tweaks . sections ( ) : for option in tweaks . options ( section ) : value = tweaks . get ( section , option ) if option . endswith ( "+" ) : option = option [ : - 1 ] value = main . get ( section , option ) + value main . set ( section , option , value )


def write(self, text, hashline=b"# {}"):
        u"""
        Write `text` to the file.

        Writes the text to the file, with a final line checksumming the
        contents.  The entire file must be written with one `.write()` call.

        The last line is written with the `hashline` format string, which can
        be changed to accommodate different file syntaxes.

        Both arguments are UTF8 byte strings.

        Arguments:
            text (UTF8 byte string): the contents of the file to write.

            hashline (UTF8 byte string): the format of the last line to append
                to the file, with "{}" replaced with the hash.

        """
        if not text.endswith(b"\n"):
            text += b"\n"

        actual_hash = hashlib.sha1(text).hexdigest()

        with open(self.filename, "wb") as f:
            f.write(text)
            f.write(hashline.decode("utf8").format(actual_hash).encode("utf8"))
            f.write(b"\n")

def
write
(
self
,
text
,
hashline
=
b"# {}"
)
:
u
if
not
text
.
endswith
(
b"\n"
)
:
text
+=
b"\n"
actual_hash
=
hashlib
.
sha1
(
text
)
.
hexdigest
(
)
with
open
(
self
.
filename
,
"wb"
)
as
f
:
f
.
write
(
text
)
f
.
write
(
hashline
.
decode
(
"utf8"
)
.
format
(
actual_hash
)
.
encode
(
"utf8"
)
)
f
.
write
(
b"\n"
)


def validate(self):
        """
        Check if the file still has its original contents.

        Returns True if the file is unchanged, False if it has been tampered
        with.
        """

        with open(self.filename, "rb") as f:
            text = f.read()

        start_last_line = text.rfind(b"\n", 0, -1)
        if start_last_line == -1:
            return False

        original_text = text[:start_last_line+1]
        last_line = text[start_last_line+1:]

        expected_hash = hashlib.sha1(original_text).hexdigest().encode('utf8')
        match = re.search(b"[0-9a-f]{40}", last_line)
        if not match:
            return False
        actual_hash = match.group(0)
        return actual_hash == expected_hash

def validate ( self ) : with open ( self . filename , "rb" ) as f : text = f . read ( ) start_last_line = text . rfind ( b"\n" , 0 , - 1 ) if start_last_line == - 1 : return False original_text = text [ : start_last_line + 1 ] last_line = text [ start_last_line + 1 : ] expected_hash = hashlib . sha1 ( original_text ) . hexdigest ( ) . encode ( 'utf8' ) match = re . search ( b"[0-9a-f]{40}" , last_line ) if not match : return False actual_hash = match . group ( 0 ) return actual_hash == expected_hash

Check if the file still has its original contents .


def check_visitors(cls):
    """Check that a checker's visitors are correctly named.

    A checker has methods named visit_NODETYPE, but it's easy to mis-name
    a visit method, and it will never be called.  This decorator checks
    the class to see that all of its visitors are named after an existing
    node class.
    """
    for name in dir(cls):
        if name.startswith("visit_"):
            if name[6:] not in CLASS_NAMES:
                raise Exception(u"Method {} doesn't correspond to a node class".format(name))
    return cls

Check that a checker s visitors are correctly named .


def usable_class_name(node):
    """Make a reasonable class name for a class node."""
    name = node.qname()
    for prefix in ["__builtin__.", "builtins.", "."]:
        if name.startswith(prefix):
            name = name[len(prefix):]
    return name

def usable_class_name ( node ) : name = node . qname ( ) for prefix in [ "__builtin__." , "builtins." , "." ] : if name . startswith ( prefix ) : name = name [ len ( prefix ) : ] return name

Make a reasonable class name for a class node .


def parse_pylint_output(pylint_output):
    """
    Parse the pylint output-format=parseable lines into PylintError tuples.
    """
    for line in pylint_output:
        if not line.strip():
            continue

        if line[0:5] in ("-"*5, "*"*5):
            continue

        parsed = PYLINT_PARSEABLE_REGEX.search(line)
        if parsed is None:
            LOG.warning(
                u"Unable to parse %r. If this is a lint failure, please re-run pylint with the "
                u"--output-format=parseable option, otherwise, you can ignore this message.",
                line
            )
            continue

        parsed_dict = parsed.groupdict()
        parsed_dict['linenum'] = int(parsed_dict['linenum'])
        yield PylintError(**parsed_dict)

Parse the pylint output - format = parseable lines into PylintError tuples .


def main(argv=None):
    """The edx_lint command entry point."""
    if argv is None:
        argv = sys.argv[1:]

    if not argv or argv[0] == "help":
        show_help()
        return 0
    elif argv[0] == "check":
        return check_main(argv[1:])
    elif argv[0] == "list":
        return list_main(argv[1:])
    elif argv[0] == "write":
        return write_main(argv[1:])
    else:
        print(u"Don't understand {!r}".format(" ".join(argv)))
        show_help()
        return 1

def
main
(
argv
=
)
:
if
argv
is
:
argv
=
sys
.
argv
[
1
:
]
if
not
argv
or
argv
[
0
]
==
"help"
:
show_help
(
)
return
0
elif
argv
[
0
]
==
"check"
:
return
check_main
(
argv
[
1
:
]
)
elif
argv
[
0
]
==
"list"
:
return
list_main
(
argv
[
1
:
]
)
elif
argv
[
0
]
==
"write"
:
return
write_main
(
argv
[
1
:
]
)
else
:
print
(
u"Don't understand {!r}"
.
format
(
" "
.
join
(
argv
)
)
)
show_help
(
)
return
1


def show_help():
    """Print the help string for the edx_lint command."""
    print("""\
Manage local config files from masters in edx_lint.

Commands:
""")
    for cmd in [write_main, check_main, list_main]:
        print(cmd.__doc__.lstrip("\n"))

def show_help ( ) : print ( ) for cmd in [ write_main , check_main , list_main ] : print ( cmd . __doc__ . lstrip ( "\n" ) )

Print the help string for the edx_lint command .


def trans_new(name, transform, inverse, breaks=None,
              minor_breaks=None, _format=None,
              domain=(-np.inf, np.inf), doc='', **kwargs):
    """
    Create a transformation class object

    Parameters
    ----------
    name : str
        Name of the transformation
    transform : callable ``f(x)``
        A function (preferably a `ufunc`) that computes
        the transformation.
    inverse : callable ``f(x)``
        A function (preferably a `ufunc`) that computes
        the inverse of the transformation.
    breaks : callable ``f(limits)``
        Function to compute the breaks for this transform.
        If None, then a default good enough for a linear
        domain is used.
    minor_breaks : callable ``f(major, limits)``
        Function to compute the minor breaks for this
        transform. If None, then a default good enough for
        a linear domain is used.
    _format : callable ``f(breaks)``
        Function to format the generated breaks.
    domain : array_like
        Domain over which the transformation is valid.
        It should be of length 2.
    doc : str
        Docstring for the class.
    **kwargs : dict
        Attributes of the transform, e.g if base is passed
        in kwargs, then `t.base` would be a valied attribute.

    Returns
    -------
    out : trans
        Transform class
    """
    def _get(func):
        if isinstance(func, (classmethod, staticmethod, MethodType)):
            return func
        else:
            return staticmethod(func)

    klass_name = '{}_trans'.format(name)

    d = {'transform': _get(transform),
         'inverse': _get(inverse),
         'domain': domain,
         '__doc__': doc,
         **kwargs}

    if breaks:
        d['breaks_'] = _get(breaks)

    if minor_breaks:
        d['minor_breaks'] = _get(minor_breaks)

    if _format:
        d['format'] = _get(_format)

    return type(klass_name, (trans,), d)

def trans_new ( name , transform , inverse , breaks = None , minor_breaks = None , _format = None , domain = ( - np . inf , np . inf ) , doc = '' , ** kwargs ) : def _get ( func ) : if isinstance ( func , ( classmethod , staticmethod , MethodType ) ) : return func else : return staticmethod ( func ) klass_name = '{}_trans' . format ( name ) d = { 'transform' : _get ( transform ) , 'inverse' : _get ( inverse ) , 'domain' : domain , '__doc__' : doc , ** kwargs } if breaks : d [ 'breaks_' ] = _get ( breaks ) if minor_breaks : d [ 'minor_breaks' ] = _get ( minor_breaks ) if _format : d [ 'format' ] = _get ( _format ) return type ( klass_name , ( trans , ) , d )


def gettrans(t):
    """
    Return a trans object

    Parameters
    ----------
    t : str | callable | type | trans
        name of transformation function

    Returns
    -------
    out : trans
    """
    obj = t
    # Make sure trans object is instantiated
    if isinstance(obj, str):
        name = '{}_trans'.format(obj)
        obj = globals()[name]()
    if callable(obj):
        obj = obj()
    if isinstance(obj, type):
        obj = obj()

    if not isinstance(obj, trans):
        raise ValueError("Could not get transform object.")

    return obj

def
gettrans
(
t
)
:
obj
=
t
if
isinstance
(
obj
,
str
)
:
name
=
'{}_trans'
.
format
(
obj
)
obj
=
globals
(
)
[
name
]
(
)
if
callable
(
obj
)
:
obj
=
obj
(
)
if
isinstance
(
obj
,
type
)
:
obj
=
obj
(
)
if
not
isinstance
(
obj
,
trans
)
:
raise
ValueError
(
"Could not get transform object."
)
return
obj


def breaks(self, limits):
        """
        Calculate breaks in data space and return them
        in transformed space.

        Expects limits to be in *transform space*, this
        is the same space as that where the domain is
        specified.

        This method wraps around :meth:`breaks_` to ensure
        that the calculated breaks are within the domain
        the transform. This is helpful in cases where an
        aesthetic requests breaks with limits expanded for
        some padding, yet the expansion goes beyond the
        domain of the transform. e.g for a probability
        transform the breaks will be in the domain
        ``[0, 1]`` despite any outward limits.

        Parameters
        ----------
        limits : tuple
            The scale limits. Size 2.

        Returns
        -------
        out : array_like
            Major breaks
        """
        # clip the breaks to the domain,
        # e.g. probabilities will be in [0, 1] domain
        vmin = np.max([self.domain[0], limits[0]])
        vmax = np.min([self.domain[1], limits[1]])
        breaks = np.asarray(self.breaks_([vmin, vmax]))

        # Some methods(mpl_breaks, extended_breaks) that
        # calculate breaks take the limits as guide posts and
        # not hard limits.
        breaks = breaks.compress((breaks >= self.domain[0]) &
                                 (breaks <= self.domain[1]))
        return breaks

def breaks ( self , limits ) : vmin = np . max ( [ self . domain [ 0 ] , limits [ 0 ] ] ) vmax = np . min ( [ self . domain [ 1 ] , limits [ 1 ] ] ) breaks = np . asarray ( self . breaks_ ( [ vmin , vmax ] ) ) breaks = breaks . compress ( ( breaks >= self . domain [ 0 ] ) & ( breaks <= self . domain [ 1 ] ) ) return breaks

Calculate breaks in data space and return them in transformed space .


def transform(x):
        """
        Transform from date to a numerical format
        """
        try:
            x = date2num(x)
        except AttributeError:
            # numpy datetime64
            # This is not ideal because the operations do not
            # preserve the np.datetime64 type. May be need
            # a datetime64_trans
            x = [pd.Timestamp(item) for item in x]
            x = date2num(x)
        return x

def transform ( x ) : try : x = date2num ( x ) except AttributeError : x = [ pd . Timestamp ( item ) for item in x ] x = date2num ( x ) return x


def rescale(x, to=(0, 1), _from=None):
    """
    Rescale numeric vector to have specified minimum and maximum.

    Parameters
    ----------
    x : array_like | numeric
        1D vector of values to manipulate.
    to : tuple
        output range (numeric vector of length two)
    _from : tuple
        input range (numeric vector of length two).
        If not given, is calculated from the range of x

    Returns
    -------
    out : array_like
        Rescaled values

    Examples
    --------
    >>> x = [0, 2, 4, 6, 8, 10]
    >>> rescale(x)
    array([0. , 0.2, 0.4, 0.6, 0.8, 1. ])
    >>> rescale(x, to=(0, 2))
    array([0. , 0.4, 0.8, 1.2, 1.6, 2. ])
    >>> rescale(x, to=(0, 2), _from=(0, 20))
    array([0. , 0.2, 0.4, 0.6, 0.8, 1. ])
    """
    if _from is None:
        _from = np.min(x), np.max(x)
    return np.interp(x, _from, to)

def rescale ( x , to = ( 0 , 1 ) , _from = None ) : if _from is None : _from = np . min ( x ) , np . max ( x ) return np . interp ( x , _from , to )

Rescale numeric vector to have specified minimum and maximum .


def rescale_mid(x, to=(0, 1), _from=None, mid=0):
    """
    Rescale numeric vector to have specified minimum, midpoint,
    and maximum.

    Parameters
    ----------
    x : array_like | numeric
        1D vector of values to manipulate.
    to : tuple
        output range (numeric vector of length two)
    _from : tuple
        input range (numeric vector of length two).
        If not given, is calculated from the range of x
    mid	: numeric
        mid-point of input range

    Returns
    -------
    out : array_like
        Rescaled values

    Examples
    --------
    >>> rescale_mid([1, 2, 3], mid=1)
    array([0.5 , 0.75, 1.  ])
    >>> rescale_mid([1, 2, 3], mid=2)
    array([0. , 0.5, 1. ])
    """
    array_like = True

    try:
        len(x)
    except TypeError:
        array_like = False
        x = [x]

    if not hasattr(x, 'dtype'):
        x = np.asarray(x)

    if _from is None:
        _from = np.array([np.min(x), np.max(x)])
    else:
        _from = np.asarray(_from)

    if (zero_range(_from) or zero_range(to)):
        out = np.repeat(np.mean(to), len(x))
    else:
        extent = 2 * np.max(np.abs(_from - mid))
        out = (x - mid) / extent * np.diff(to) + np.mean(to)

    if not array_like:
        out = out[0]
    return out

def rescale_mid ( x , to = ( 0 , 1 ) , _from = None , mid = 0 ) : array_like = True try : len ( x ) except TypeError : array_like = False x = [ x ] if not hasattr ( x , 'dtype' ) : x = np . asarray ( x ) if _from is None : _from = np . array ( [ np . min ( x ) , np . max ( x ) ] ) else : _from = np . asarray ( _from ) if ( zero_range ( _from ) or zero_range ( to ) ) : out = np . repeat ( np . mean ( to ) , len ( x ) ) else : extent = 2 * np . max ( np . abs ( _from - mid ) ) out = ( x - mid ) / extent * np . diff ( to ) + np . mean ( to ) if not array_like : out = out [ 0 ] return out

Rescale numeric vector to have specified minimum midpoint and maximum .


def rescale_max(x, to=(0, 1), _from=None):
    """
    Rescale numeric vector to have specified maximum.

    Parameters
    ----------
    x : array_like | numeric
        1D vector of values to manipulate.
    to : tuple
        output range (numeric vector of length two)
    _from : tuple
        input range (numeric vector of length two).
        If not given, is calculated from the range of x.
        Only the 2nd (max) element is essential to the
        output.

    Returns
    -------
    out : array_like
        Rescaled values

    Examples
    --------
    >>> x = [0, 2, 4, 6, 8, 10]
    >>> rescale_max(x, (0, 3))
    array([0. , 0.6, 1.2, 1.8, 2.4, 3. ])

    Only the 2nd (max) element of the parameters ``to``
    and ``_from`` are essential to the output.

    >>> rescale_max(x, (1, 3))
    array([0. , 0.6, 1.2, 1.8, 2.4, 3. ])
    >>> rescale_max(x, (0, 20))
    array([ 0.,  4.,  8., 12., 16., 20.])

    If :python:`max(x) < _from[1]` then values will be
    scaled beyond the requested (:python:`to[1]`) maximum.

    >>> rescale_max(x, to=(1, 3), _from=(-1, 6))
    array([0., 1., 2., 3., 4., 5.])

    """
    array_like = True

    try:
        len(x)
    except TypeError:
        array_like = False
        x = [x]

    if not hasattr(x, 'dtype'):
        x = np.asarray(x)

    if _from is None:
        _from = np.array([np.min(x), np.max(x)])

    out = x/_from[1] * to[1]

    if not array_like:
        out = out[0]
    return out

def rescale_max ( x , to = ( 0 , 1 ) , _from = None ) : array_like = True try : len ( x ) except TypeError : array_like = False x = [ x ] if not hasattr ( x , 'dtype' ) : x = np . asarray ( x ) if _from is None : _from = np . array ( [ np . min ( x ) , np . max ( x ) ] ) out = x / _from [ 1 ] * to [ 1 ] if not array_like : out = out [ 0 ] return out

Rescale numeric vector to have specified maximum .


def squish_infinite(x, range=(0, 1)):
    """
    Truncate infinite values to a range.

    Parameters
    ----------
    x : array_like
        Values that should have infinities squished.
    range : tuple
        The range onto which to squish the infinites.
        Must be of size 2.

    Returns
    -------
    out : array_like
        Values with infinites squished.

    Examples
    --------
    >>> squish_infinite([0, .5, .25, np.inf, .44])
    [0.0, 0.5, 0.25, 1.0, 0.44]
    >>> squish_infinite([0, -np.inf, .5, .25, np.inf], (-10, 9))
    [0.0, -10.0, 0.5, 0.25, 9.0]
    """
    xtype = type(x)

    if not hasattr(x, 'dtype'):
        x = np.asarray(x)

    x[x == -np.inf] = range[0]
    x[x == np.inf] = range[1]

    if not isinstance(x, xtype):
        x = xtype(x)
    return x

def squish_infinite ( x , range = ( 0 , 1 ) ) : xtype = type ( x ) if not hasattr ( x , 'dtype' ) : x = np . asarray ( x ) x [ x == - np . inf ] = range [ 0 ] x [ x == np . inf ] = range [ 1 ] if not isinstance ( x , xtype ) : x = xtype ( x ) return x


def squish(x, range=(0, 1), only_finite=True):
    """
    Squish values into range.

    Parameters
    ----------
    x : array_like
        Values that should have out of range values squished.
    range : tuple
        The range onto which to squish the values.
    only_finite: boolean
        When true, only squishes finite values.

    Returns
    -------
    out : array_like
        Values with out of range values squished.

    Examples
    --------
    >>> squish([-1.5, 0.2, 0.5, 0.8, 1.0, 1.2])
    [0.0, 0.2, 0.5, 0.8, 1.0, 1.0]

    >>> squish([-np.inf, -1.5, 0.2, 0.5, 0.8, 1.0, np.inf], only_finite=False)
    [0.0, 0.0, 0.2, 0.5, 0.8, 1.0, 1.0]
    """
    xtype = type(x)

    if not hasattr(x, 'dtype'):
        x = np.asarray(x)

    finite = np.isfinite(x) if only_finite else True

    x[np.logical_and(x < range[0], finite)] = range[0]
    x[np.logical_and(x > range[1], finite)] = range[1]

    if not isinstance(x, xtype):
        x = xtype(x)
    return x

def squish ( x , range = ( 0 , 1 ) , only_finite = True ) : xtype = type ( x ) if not hasattr ( x , 'dtype' ) : x = np . asarray ( x ) finite = np . isfinite ( x ) if only_finite else True x [ np . logical_and ( x < range [ 0 ] , finite ) ] = range [ 0 ] x [ np . logical_and ( x > range [ 1 ] , finite ) ] = range [ 1 ] if not isinstance ( x , xtype ) : x = xtype ( x ) return x


def _censor_with(x, range, value=None):
    """
    Censor any values outside of range with ``None``
    """
    return [val if range[0] <= val <= range[1] else value
            for val in x]

def _censor_with ( x , range , value = None ) : return [ val if range [ 0 ] <= val <= range [ 1 ] else value for val in x ]

Censor any values outside of range with None


def zero_range(x, tol=np.finfo(float).eps * 100):
    """
    Determine if range of vector is close to zero.

    Parameters
    ----------
    x : array_like | numeric
        Value(s) to check. If it is an array_like, it
        should be of length 2.
    tol : float
        Tolerance. Default tolerance is the `machine epsilon`_
        times :math:`10^2`.

    Returns
    -------
    out : bool
        Whether ``x`` has zero range.

    Examples
    --------
    >>> zero_range([1, 1])
    True
    >>> zero_range([1, 2])
    False
    >>> zero_range([1, 2], tol=2)
    True

    .. _machine epsilon: https://en.wikipedia.org/wiki/Machine_epsilon
    """
    try:
        if len(x) == 1:
            return True
    except TypeError:
        return True

    if len(x) != 2:
        raise ValueError('x must be length 1 or 2')

    # Deals with array_likes that have non-standard indices
    x = tuple(x)

    # datetime - pandas, cpython
    if isinstance(x[0], (pd.Timestamp, datetime.datetime)):
        # date2num include timezone info, .toordinal() does not
        x = date2num(x)
    # datetime - numpy
    elif isinstance(x[0], np.datetime64):
        return x[0] == x[1]
    # timedelta - pandas, cpython
    elif isinstance(x[0], (pd.Timedelta, datetime.timedelta)):
        x = x[0].total_seconds(), x[1].total_seconds()
    # timedelta - numpy
    elif isinstance(x[0], np.timedelta64):
        return x[0] == x[1]
    elif not isinstance(x[0], (float, int, np.number)):
        raise TypeError(
            "zero_range objects cannot work with objects "
            "of type '{}'".format(type(x[0])))

    if any(np.isnan(x)):
        return np.nan

    if x[0] == x[1]:
        return True

    if all(np.isinf(x)):
        return False

    m = np.abs(x).min()
    if m == 0:
        return False

    return np.abs((x[0] - x[1]) / m) < tol

Determine if range of vector is close to zero .


def expand_range(range, mul=0, add=0, zero_width=1):
    """
    Expand a range with a multiplicative or additive constant

    Parameters
    ----------
    range : tuple
        Range of data. Size 2.
    mul : int | float
        Multiplicative constant
    add : int | float | timedelta
        Additive constant
    zero_width : int | float | timedelta
        Distance to use if range has zero width

    Returns
    -------
    out : tuple
        Expanded range

    Examples
    --------
    >>> expand_range((3, 8))
    (3, 8)
    >>> expand_range((0, 10), mul=0.1)
    (-1.0, 11.0)
    >>> expand_range((0, 10), add=2)
    (-2, 12)
    >>> expand_range((0, 10), mul=.1, add=2)
    (-3.0, 13.0)
    >>> expand_range((0, 1))
    (0, 1)

    When the range has zero width

    >>> expand_range((5, 5))
    (4.5, 5.5)

    Notes
    -----
    If expanding *datetime* or *timedelta* types, **add** and
    **zero_width** must be suitable *timedeltas* i.e. You should
    not mix types between **Numpy**, **Pandas** and the
    :mod:`datetime` module.

    In Python 2, you cannot multiplicative constant **mul** cannot be
    a :class:`float`.
    """
    x = range

    # Enforce tuple
    try:
        x[0]
    except TypeError:
        x = (x, x)

    # The expansion cases
    if zero_range(x):
        new = x[0]-zero_width/2, x[0]+zero_width/2
    else:
        dx = (x[1] - x[0]) * mul + add
        new = x[0]-dx, x[1]+dx

    return new

def expand_range ( range , mul = 0 , add = 0 , zero_width = 1 ) : x = range try : x [ 0 ] except TypeError : x = ( x , x ) if zero_range ( x ) : new = x [ 0 ] - zero_width / 2 , x [ 0 ] + zero_width / 2 else : dx = ( x [ 1 ] - x [ 0 ] ) * mul + add new = x [ 0 ] - dx , x [ 1 ] + dx return new


def expand_range_distinct(range, expand=(0, 0, 0, 0), zero_width=1):
    """
    Expand a range with a multiplicative or additive constants

    Similar to :func:`expand_range` but both sides of the range
    expanded using different constants

    Parameters
    ----------
    range : tuple
        Range of data. Size 2
    expand : tuple
        Length 2 or 4. If length is 2, then the same constants
        are used for both sides. If length is 4 then the first
        two are are the Multiplicative (*mul*) and Additive (*add*)
        constants for the lower limit, and the second two are
        the constants for the upper limit.
    zero_width : int | float | timedelta
        Distance to use if range has zero width

    Returns
    -------
    out : tuple
        Expanded range

    Examples
    --------
    >>> expand_range_distinct((3, 8))
    (3, 8)
    >>> expand_range_distinct((0, 10), (0.1, 0))
    (-1.0, 11.0)
    >>> expand_range_distinct((0, 10), (0.1, 0, 0.1, 0))
    (-1.0, 11.0)
    >>> expand_range_distinct((0, 10), (0.1, 0, 0, 0))
    (-1.0, 10)
    >>> expand_range_distinct((0, 10), (0, 2))
    (-2, 12)
    >>> expand_range_distinct((0, 10), (0, 2, 0, 2))
    (-2, 12)
    >>> expand_range_distinct((0, 10), (0, 0, 0, 2))
    (0, 12)
    >>> expand_range_distinct((0, 10), (.1, 2))
    (-3.0, 13.0)
    >>> expand_range_distinct((0, 10), (.1, 2, .1, 2))
    (-3.0, 13.0)
    >>> expand_range_distinct((0, 10), (0, 0, .1, 2))
    (0, 13.0)
    """

    if len(expand) == 2:
        expand = tuple(expand) * 2

    lower = expand_range(range, expand[0], expand[1], zero_width)[0]
    upper = expand_range(range, expand[2], expand[3], zero_width)[1]
    return (lower, upper)

def expand_range_distinct ( range , expand = ( 0 , 0 , 0 , 0 ) , zero_width = 1 ) : if len ( expand ) == 2 : expand = tuple ( expand ) * 2 lower = expand_range ( range , expand [ 0 ] , expand [ 1 ] , zero_width ) [ 0 ] upper = expand_range ( range , expand [ 2 ] , expand [ 3 ] , zero_width ) [ 1 ] return ( lower , upper )


def _extend_breaks(self, major):
        """
        Append 2 extra breaks at either end of major

        If breaks of transform space are non-equidistant,
        :func:`minor_breaks` add minor breaks beyond the first
        and last major breaks. The solutions is to extend those
        breaks (in transformed space) before the minor break call
        is made. How the breaks depends on the type of transform.
        """
        trans = self.trans
        trans = trans if isinstance(trans, type) else trans.__class__
        # so far we are only certain about this extending stuff
        # making sense for log transform
        is_log = trans.__name__.startswith('log')
        diff = np.diff(major)
        step = diff[0]
        if is_log and all(diff == step):
            major = np.hstack([major[0]-step, major, major[-1]+step])
        return major

def _extend_breaks ( self , major ) : trans = self . trans trans = trans if isinstance ( trans , type ) else trans . __class__ is_log = trans . __name__ . startswith ( 'log' ) diff = np . diff ( major ) step = diff [ 0 ] if is_log and all ( diff == step ) : major = np . hstack ( [ major [ 0 ] - step , major , major [ - 1 ] + step ] ) return major


def best_units(self, sequence):
        """
        Determine good units for representing a sequence of timedeltas
        """
        # Read
        #   [(0.9, 's'),
        #    (9, 'm)]
        # as, break ranges between 0.9 seconds (inclusive)
        # and 9 minutes are represented in seconds. And so on.
        ts_range = self.value(max(sequence)) - self.value(min(sequence))
        package = self.determine_package(sequence[0])
        if package == 'pandas':
            cuts = [
                (0.9, 'us'),
                (0.9, 'ms'),
                (0.9, 's'),
                (9, 'm'),
                (6, 'h'),
                (4, 'd'),
                (4, 'w'),
                (4, 'M'),
                (3, 'y')]
            denomination = NANOSECONDS
            base_units = 'ns'
        else:
            cuts = [
                (0.9, 's'),
                (9, 'm'),
                (6, 'h'),
                (4, 'd'),
                (4, 'w'),
                (4, 'M'),
                (3, 'y')]
            denomination = SECONDS
            base_units = 'ms'

        for size, units in reversed(cuts):
            if ts_range >= size*denomination[units]:
                return units

        return base_units

def best_units ( self , sequence ) : ts_range = self . value ( max ( sequence ) ) - self . value ( min ( sequence ) ) package = self . determine_package ( sequence [ 0 ] ) if package == 'pandas' : cuts = [ ( 0.9 , 'us' ) , ( 0.9 , 'ms' ) , ( 0.9 , 's' ) , ( 9 , 'm' ) , ( 6 , 'h' ) , ( 4 , 'd' ) , ( 4 , 'w' ) , ( 4 , 'M' ) , ( 3 , 'y' ) ] denomination = NANOSECONDS base_units = 'ns' else : cuts = [ ( 0.9 , 's' ) , ( 9 , 'm' ) , ( 6 , 'h' ) , ( 4 , 'd' ) , ( 4 , 'w' ) , ( 4 , 'M' ) , ( 3 , 'y' ) ] denomination = SECONDS base_units = 'ms' for size , units in reversed ( cuts ) : if ts_range >= size * denomination [ units ] : return units return base_units


def scaled_limits(self):
        """
        Minimum and Maximum to use for computing breaks
        """
        _min = self.limits[0]/self.factor
        _max = self.limits[1]/self.factor
        return _min, _max

def scaled_limits ( self ) : _min = self . limits [ 0 ] / self . factor _max = self . limits [ 1 ] / self . factor return _min , _max


def numeric_to_timedelta(self, numerics):
        """
        Convert sequence of numerics to timedelta
        """
        if self.package == 'pandas':
            return [self.type(int(x*self.factor), units='ns')
                    for x in numerics]
        else:
            return [self.type(seconds=x*self.factor)
                    for x in numerics]

def numeric_to_timedelta ( self , numerics ) : if self . package == 'pandas' : return [ self . type ( int ( x * self . factor ) , units = 'ns' ) for x in numerics ] else : return [ self . type ( seconds = x * self . factor ) for x in numerics ]


def to_numeric(self, td):
        """
        Convert timedelta to a number corresponding to the
        appropriate units. The appropriate units are those
        determined with the object is initialised.
        """
        if self.package == 'pandas':
            return td.value/NANOSECONDS[self.units]
        else:
            return td.total_seconds()/SECONDS[self.units]

def to_numeric ( self , td ) : if self . package == 'pandas' : return td . value / NANOSECONDS [ self . units ] else : return td . total_seconds ( ) / SECONDS [ self . units ]

Convert timedelta to a number corresponding to the appropriate units . The appropriate units are those determined with the object is initialised .


def round_any(x, accuracy, f=np.round):
    """
    Round to multiple of any number.
    """
    if not hasattr(x, 'dtype'):
        x = np.asarray(x)

    return f(x / accuracy) * accuracy

def round_any ( x , accuracy , f = np . round ) : if not hasattr ( x , 'dtype' ) : x = np . asarray ( x ) return f ( x / accuracy ) * accuracy


def min_max(x, na_rm=False, finite=True):
    """
    Return the minimum and maximum of x

    Parameters
    ----------
    x : array_like
        Sequence
    na_rm : bool
        Whether to remove ``nan`` values.
    finite : bool
        Whether to consider only finite values.

    Returns
    -------
    out : tuple
        (minimum, maximum) of x
    """
    if not hasattr(x, 'dtype'):
        x = np.asarray(x)

    if na_rm and finite:
        x = x[np.isfinite(x)]
    elif not na_rm and np.any(np.isnan(x)):
        return np.nan, np.nan
    elif na_rm:
        x = x[~np.isnan(x)]
    elif finite:
        x = x[~np.isinf(x)]

    if (len(x)):
        return np.min(x), np.max(x)
    else:
        return float('-inf'), float('inf')

def min_max ( x , na_rm = False , finite = True ) : if not hasattr ( x , 'dtype' ) : x = np . asarray ( x ) if na_rm and finite : x = x [ np . isfinite ( x ) ] elif not na_rm and np . any ( np . isnan ( x ) ) : return np . nan , np . nan elif na_rm : x = x [ ~ np . isnan ( x ) ] elif finite : x = x [ ~ np . isinf ( x ) ] if ( len ( x ) ) : return np . min ( x ) , np . max ( x ) else : return float ( '-inf' ) , float ( 'inf' )


def precision(x):
    """
    Return the precision of x

    Parameters
    ----------
    x : array_like | numeric
        Value(s) whose for which to compute the precision.

    Returns
    -------
    out : numeric
        The precision of ``x`` or that the values in ``x``.

    Notes
    -----
    The precision is computed in base 10.

    Examples
    --------
    >>> precision(0.08)
    0.01
    >>> precision(9)
    1
    >>> precision(16)
    10
    """
    from .bounds import zero_range

    rng = min_max(x, na_rm=True)
    if zero_range(rng):
        span = np.abs(rng[0])
    else:
        span = np.diff(rng)[0]

    if span == 0:
        return 1
    else:
        return 10 ** int(np.floor(np.log10(span)))

def precision ( x ) : from . bounds import zero_range rng = min_max ( x , na_rm = True ) if zero_range ( rng ) : span = np . abs ( rng [ 0 ] ) else : span = np . diff ( rng ) [ 0 ] if span == 0 : return 1 else : return 10 ** int ( np . floor ( np . log10 ( span ) ) )


def multitype_sort(a):
    """
    Sort elements of multiple types

    x is assumed to contain elements of different types, such that
    plain sort would raise a `TypeError`.

    Parameters
    ----------
    a : array-like
        Array of items to be sorted

    Returns
    -------
    out : list
        Items sorted within their type groups.
    """
    types = defaultdict(list)
    numbers = {int, float, complex}

    for x in a:
        t = type(x)
        if t in numbers:
            types['number'].append(x)
        else:
            types[t].append(x)

    for t in types:
        types[t] = np.sort(types[t])

    return list(chain(*(types[t] for t in types)))

def multitype_sort ( a ) : types = defaultdict ( list ) numbers = { int , float , complex } for x in a : t = type ( x ) if t in numbers : types [ 'number' ] . append ( x ) else : types [ t ] . append ( x ) for t in types : types [ t ] = np . sort ( types [ t ] ) return list ( chain ( * ( types [ t ] for t in types ) ) )


def nearest_int(x):
    """
    Return nearest long integer to x
    """
    if x == 0:
        return np.int64(0)
    elif x > 0:
        return np.int64(x + 0.5)
    else:
        return np.int64(x - 0.5)

def nearest_int ( x ) : if x == 0 : return np . int64 ( 0 ) elif x > 0 : return np . int64 ( x + 0.5 ) else : return np . int64 ( x - 0.5 )


def is_close_to_int(x):
    """
    Check if value is close to an integer

    Parameters
    ----------
    x : float
        Numeric value to check

    Returns
    -------
    out : bool
    """
    if not np.isfinite(x):
        return False
    return abs(x - nearest_int(x)) < 1e-10

def is_close_to_int ( x ) : if not np . isfinite ( x ) : return False return abs ( x - nearest_int ( x ) ) < 1e-10


def same_log10_order_of_magnitude(x, delta=0.1):
    """
    Return true if range is approximately in same order of magnitude

    For example these sequences are in the same order of magnitude:

        - [1, 8, 5]     # [1, 10)
        - [35, 20, 80]  # [10 100)
        - [232, 730]    # [100, 1000)

    Parameters
    ----------
    x : array-like
         Values in base 10. Must be size 2 and
        ``rng[0] <= rng[1]``.
    delta : float
        Fuzz factor for approximation. It is multiplicative.
    """
    dmin = np.log10(np.min(x)*(1-delta))
    dmax = np.log10(np.max(x)*(1+delta))
    return np.floor(dmin) == np.floor(dmax)

def same_log10_order_of_magnitude ( x , delta = 0.1 ) : dmin = np . log10 ( np . min ( x ) * ( 1 - delta ) ) dmax = np . log10 ( np . max ( x ) * ( 1 + delta ) ) return np . floor ( dmin ) == np . floor ( dmax )


def _format(formatter, x):
    """
    Helper to format and tidy up
    """
    # For MPL to play nice
    formatter.create_dummy_axis()
    # For sensible decimal places
    formatter.set_locs([val for val in x if ~np.isnan(val)])
    try:
        oom = int(formatter.orderOfMagnitude)
    except AttributeError:
        oom = 0
    labels = [formatter(tick) for tick in x]

    # Remove unnecessary decimals
    pattern = re.compile(r'\.0+$')
    for i, label in enumerate(labels):
        match = pattern.search(label)
        if match:
            labels[i] = pattern.sub('', label)

    # MPL does not add the exponential component
    if oom:
        labels = ['{}e{}'.format(s, oom) if s != '0' else s
                  for s in labels]
    return labels

def _format ( formatter , x ) : formatter . create_dummy_axis ( ) formatter . set_locs ( [ val for val in x if ~ np . isnan ( val ) ] ) try : oom = int ( formatter . orderOfMagnitude ) except AttributeError : oom = 0 labels = [ formatter ( tick ) for tick in x ] pattern = re . compile ( r'\.0+$' ) for i , label in enumerate ( labels ) : match = pattern . search ( label ) if match : labels [ i ] = pattern . sub ( '' , label ) if oom : labels = [ '{}e{}' . format ( s , oom ) if s != '0' else s for s in labels ] return labels


def _tidyup_labels(self, labels):
        """
        Make all labels uniform in format and remove redundant zeros
        for labels in exponential format.

        Parameters
        ----------
        labels : list-like
            Labels to be tidied.

        Returns
        -------
        out : list-like
            Labels
        """
        def remove_zeroes(s):
            """
            Remove unnecessary zeros for float string s
            """
            tup = s.split('e')
            if len(tup) == 2:
                mantissa = tup[0].rstrip('0').rstrip('.')
                exponent = int(tup[1])
                if exponent:
                    s = '%se%d' % (mantissa, exponent)
                else:
                    s = mantissa
            return s

        def as_exp(s):
            """
            Float string s as in exponential format
            """
            return s if 'e' in s else '{:1.0e}'.format(float(s))

        # If any are in exponential format, make all of
        # them expontential
        has_e = np.array(['e' in x for x in labels])
        if not np.all(has_e) and not np.all(~has_e):
            labels = [as_exp(x) for x in labels]

        labels = [remove_zeroes(x) for x in labels]
        return labels

def _tidyup_labels ( self , labels ) : def remove_zeroes ( s ) : tup = s . split ( 'e' ) if len ( tup ) == 2 : mantissa = tup [ 0 ] . rstrip ( '0' ) . rstrip ( '.' ) exponent = int ( tup [ 1 ] ) if exponent : s = '%se%d' % ( mantissa , exponent ) else : s = mantissa return s def as_exp ( s ) : return s if 'e' in s else '{:1.0e}' . format ( float ( s ) ) has_e = np . array ( [ 'e' in x for x in labels ] ) if not np . all ( has_e ) and not np . all ( ~ has_e ) : labels = [ as_exp ( x ) for x in labels ] labels = [ remove_zeroes ( x ) for x in labels ] return labels

Make all labels uniform in format and remove redundant zeros for labels in exponential format .


def hls_palette(n_colors=6, h=.01, l=.6, s=.65):
    """
    Get a set of evenly spaced colors in HLS hue space.

    h, l, and s should be between 0 and 1

    Parameters
    ----------

    n_colors : int
        number of colors in the palette
    h : float
        first hue
    l : float
        lightness
    s : float
        saturation

    Returns
    -------
    palette : list
        List of colors as RGB hex strings.

    See Also
    --------
    husl_palette : Make a palette using evenly spaced circular
        hues in the HUSL system.

    Examples
    --------
    >>> len(hls_palette(2))
    2
    >>> len(hls_palette(9))
    9
    """
    hues = np.linspace(0, 1, n_colors + 1)[:-1]
    hues += h
    hues %= 1
    hues -= hues.astype(int)
    palette = [colorsys.hls_to_rgb(h_i, l, s) for h_i in hues]
    return palette

def hls_palette ( n_colors = 6 , h = .01 , l = .6 , s = .65 ) : hues = np . linspace ( 0 , 1 , n_colors + 1 ) [ : - 1 ] hues += h hues %= 1 hues -= hues . astype ( int ) palette = [ colorsys . hls_to_rgb ( h_i , l , s ) for h_i in hues ] return palette

Get a set of evenly spaced colors in HLS hue space .


def husl_palette(n_colors=6, h=.01, s=.9, l=.65):
    """
    Get a set of evenly spaced colors in HUSL hue space.

    h, s, and l should be between 0 and 1

    Parameters
    ----------

    n_colors : int
        number of colors in the palette
    h : float
        first hue
    s : float
        saturation
    l : float
        lightness

    Returns
    -------
    palette : list
        List of colors as RGB hex strings.

    See Also
    --------
    hls_palette : Make a palette using evenly spaced circular
        hues in the HSL system.

    Examples
    --------
    >>> len(husl_palette(3))
    3
    >>> len(husl_palette(11))
    11
    """
    hues = np.linspace(0, 1, n_colors + 1)[:-1]
    hues += h
    hues %= 1
    hues *= 359
    s *= 99
    l *= 99
    palette = [husl.husl_to_rgb(h_i, s, l) for h_i in hues]
    return palette

def husl_palette ( n_colors = 6 , h = .01 , s = .9 , l = .65 ) : hues = np . linspace ( 0 , 1 , n_colors + 1 ) [ : - 1 ] hues += h hues %= 1 hues *= 359 s *= 99 l *= 99 palette = [ husl . husl_to_rgb ( h_i , s , l ) for h_i in hues ] return palette

Get a set of evenly spaced colors in HUSL hue space .


def grey_pal(start=0.2, end=0.8):
    """
    Utility for creating continuous grey scale palette

    Parameters
    ----------
    start : float
        grey value at low end of palette
    end : float
        grey value at high end of palette

    Returns
    -------
    out : function
        Continuous color palette that takes a single
        :class:`int` parameter ``n`` and returns ``n``
        equally spaced colors.

    Examples
    --------
    >>> palette = grey_pal()
    >>> palette(5)
    ['#333333', '#737373', '#989898', '#b5b5b5', '#cccccc']
    """
    gamma = 2.2
    ends = ((0.0, start, start), (1.0, end, end))
    cdict = {'red': ends, 'green': ends, 'blue': ends}
    grey_cmap = mcolors.LinearSegmentedColormap('grey', cdict)

    def continuous_grey_palette(n):
        colors = []
        # The grey scale points are linearly separated in
        # gamma encoded space
        for x in np.linspace(start**gamma, end**gamma, n):
            # Map points onto the [0, 1] palette domain
            x = (x ** (1./gamma) - start) / (end - start)
            colors.append(mcolors.rgb2hex(grey_cmap(x)))
        return colors

    return continuous_grey_palette

def grey_pal ( start = 0.2 , end = 0.8 ) : gamma = 2.2 ends = ( ( 0.0 , start , start ) , ( 1.0 , end , end ) ) cdict = { 'red' : ends , 'green' : ends , 'blue' : ends } grey_cmap = mcolors . LinearSegmentedColormap ( 'grey' , cdict ) def continuous_grey_palette ( n ) : colors = [ ] for x in np . linspace ( start ** gamma , end ** gamma , n ) : x = ( x ** ( 1. / gamma ) - start ) / ( end - start ) colors . append ( mcolors . rgb2hex ( grey_cmap ( x ) ) ) return colors return continuous_grey_palette


def hue_pal(h=.01, l=.6, s=.65, color_space='hls'):
    """
    Utility for making hue palettes for color schemes.

    Parameters
    ----------
    h : float
        first hue. In the [0, 1] range
    l : float
        lightness. In the [0, 1] range
    s : float
        saturation. In the [0, 1] range
    color_space : 'hls' | 'husl'
        Color space to use for the palette

    Returns
    -------
    out : function
        A discrete color palette that takes a single
        :class:`int` parameter ``n`` and returns ``n``
        equally spaced colors. Though the palette
        is continuous, since it is varies the hue it
        is good for categorical data. However if ``n``
        is large enough the colors show continuity.

    Examples
    --------
    >>> hue_pal()(5)
    ['#db5f57', '#b9db57', '#57db94', '#5784db', '#c957db']
    >>> hue_pal(color_space='husl')(5)
    ['#e0697e', '#9b9054', '#569d79', '#5b98ab', '#b675d7']
    """
    if not all([0 <= val <= 1 for val in (h, l, s)]):
        msg = ("hue_pal expects values to be between 0 and 1. "
               " I got h={}, l={}, s={}".format(h, l, s))
        raise ValueError(msg)

    if color_space not in ('hls', 'husl'):
        msg = "color_space should be one of ['hls', 'husl']"
        raise ValueError(msg)

    name = '{}_palette'.format(color_space)
    palette = globals()[name]

    def _hue_pal(n):
        colors = palette(n, h=h, l=l, s=s)
        return [mcolors.rgb2hex(c) for c in colors]

    return _hue_pal

Utility for making hue palettes for color schemes .


def brewer_pal(type='seq', palette=1):
    """
    Utility for making a brewer palette

    Parameters
    ----------
    type : 'sequential' | 'qualitative' | 'diverging'
        Type of palette. Sequential, Qualitative or
        Diverging. The following abbreviations may
        be used, ``seq``, ``qual`` or ``div``.

    palette : int | str
        Which palette to choose from. If is an integer,
        it must be in the range ``[0, m]``, where ``m``
        depends on the number sequential, qualitative or
        diverging palettes. If it is a string, then it
        is the name of the palette.

    Returns
    -------
    out : function
        A color palette that takes a single
        :class:`int` parameter ``n`` and returns ``n``
        colors. The maximum value of ``n`` varies
        depending on the parameters.

    Examples
    --------
    >>> brewer_pal()(5)
    ['#EFF3FF', '#BDD7E7', '#6BAED6', '#3182BD', '#08519C']
    >>> brewer_pal('qual')(5)
    ['#7FC97F', '#BEAED4', '#FDC086', '#FFFF99', '#386CB0']
    >>> brewer_pal('qual', 2)(5)
    ['#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E']
    >>> brewer_pal('seq', 'PuBuGn')(5)
    ['#F6EFF7', '#BDC9E1', '#67A9CF', '#1C9099', '#016C59']

    The available color names for each palette type can be
    obtained using the following code::

        import palettable.colorbrewer as brewer

        print([k for k in brewer.COLOR_MAPS['Sequential'].keys()])
        print([k for k in brewer.COLOR_MAPS['Qualitative'].keys()])
        print([k for k in brewer.COLOR_MAPS['Diverging'].keys()])
    """
    def full_type_name(text):
        abbrevs = {
            'seq': 'Sequential',
            'qual': 'Qualitative',
            'div': 'Diverging'
        }
        text = abbrevs.get(text, text)
        return text.title()

    def number_to_palette_name(ctype, n):
        """
        Return palette name that corresponds to a given number

        Uses alphabetical ordering
        """
        n -= 1
        palettes = sorted(colorbrewer.COLOR_MAPS[ctype].keys())
        if n < len(palettes):
            return palettes[n]

        raise ValueError(
            "There are only '{}' palettes of type {}. "
            "You requested palette no. {}".format(len(palettes),
                                                  ctype, n+1))

    def max_palette_colors(type, palette_name):
        """
        Return the number of colors in the brewer palette
        """
        if type == 'Sequential':
            return 9
        elif type == 'Diverging':
            return 11
        else:
            # Qualitative palettes have different limits
            qlimit = {'Accent': 8, 'Dark2': 8, 'Paired': 12,
                      'Pastel1': 9, 'Pastel2': 8, 'Set1': 9,
                      'Set2': 8, 'Set3': 12}
            return qlimit[palette_name]

    type = full_type_name(type)
    if isinstance(palette, int):
        palette_name = number_to_palette_name(type, palette)
    else:
        palette_name = palette

    nmax = max_palette_colors(type, palette_name)

    def _brewer_pal(n):
        # Only draw the maximum allowable colors from the palette
        # and fill any remaining spots with None
        _n = n if n <= nmax else nmax
        try:
            bmap = colorbrewer.get_map(palette_name, type, _n)
        except ValueError as err:
            # Some palettes have a minimum no. of colors set at 3
            # We get around that restriction.
            if 0 <= _n < 3:
                bmap = colorbrewer.get_map(palette_name, type, 3)
            else:
                raise err

        hex_colors = bmap.hex_colors[:n]
        if n > nmax:
            msg = ("Warning message:"
                   "Brewer palette {} has a maximum of {} colors"
                   "Returning the palette you asked for with"
                   "that many colors".format(palette_name, nmax))
            warnings.warn(msg)
            hex_colors = hex_colors + [None] * (n - nmax)
        return hex_colors

    return _brewer_pal


def gradient_n_pal(colors, values=None, name='gradientn'):
    """
    Create a n color gradient palette

    Parameters
    ----------
    colors : list
        list of colors
    values : list, optional
        list of points in the range [0, 1] at which to
        place each color. Must be the same size as
        `colors`. Default to evenly space the colors
    name : str
        Name to call the resultant MPL colormap

    Returns
    -------
    out : function
        Continuous color palette that takes a single
        parameter either a :class:`float` or a sequence
        of floats maps those value(s) onto the palette
        and returns color(s). The float(s) must be
        in the range [0, 1].

    Examples
    --------
    >>> palette = gradient_n_pal(['red', 'blue'])
    >>> palette([0, .25, .5, .75, 1])
    ['#ff0000', '#bf0040', '#7f0080', '#3f00c0', '#0000ff']
    """
    # Note: For better results across devices and media types,
    # it would be better to do the interpolation in
    # Lab color space.
    if values is None:
        colormap = mcolors.LinearSegmentedColormap.from_list(
            name, colors)
    else:
        colormap = mcolors.LinearSegmentedColormap.from_list(
            name, list(zip(values, colors)))

    def _gradient_n_pal(vals):
        return ratios_to_colors(vals, colormap)

    return _gradient_n_pal

def gradient_n_pal ( colors , values = None , name = 'gradientn' ) : if values is None : colormap = mcolors . LinearSegmentedColormap . from_list ( name , colors ) else : colormap = mcolors . LinearSegmentedColormap . from_list ( name , list ( zip ( values , colors ) ) ) def _gradient_n_pal ( vals ) : return ratios_to_colors ( vals , colormap ) return _gradient_n_pal


def cmap_pal(name=None, lut=None):
    """
    Create a continuous palette using an MPL colormap

    Parameters
    ----------
    name : str
        Name of colormap
    lut : None | int
        This is the number of entries desired in the lookup table.
        Default is ``None``, leave it up Matplotlib.

    Returns
    -------
    out : function
        Continuous color palette that takes a single
        parameter either a :class:`float` or a sequence
        of floats maps those value(s) onto the palette
        and returns color(s). The float(s) must be
        in the range [0, 1].

    Examples
    --------
    >>> palette = cmap_pal('viridis')
    >>> palette([.1, .2, .3, .4, .5])
    ['#482475', '#414487', '#355f8d', '#2a788e', '#21918c']
    """
    colormap = get_cmap(name, lut)

    def _cmap_pal(vals):
        return ratios_to_colors(vals, colormap)

    return _cmap_pal

def cmap_pal ( name = None , lut = None ) : colormap = get_cmap ( name , lut ) def _cmap_pal ( vals ) : return ratios_to_colors ( vals , colormap ) return _cmap_pal


def cmap_d_pal(name=None, lut=None):
    """
    Create a discrete palette using an MPL Listed colormap

    Parameters
    ----------
    name : str
        Name of colormap
    lut : None | int
        This is the number of entries desired in the lookup table.
        Default is ``None``, leave it up Matplotlib.

    Returns
    -------
    out : function
        A discrete color palette that takes a single
        :class:`int` parameter ``n`` and returns ``n``
        colors. The maximum value of ``n`` varies
        depending on the parameters.

    Examples
    --------
    >>> palette = cmap_d_pal('viridis')
    >>> palette(5)
    ['#440154', '#3b528b', '#21918c', '#5cc863', '#fde725']
    """
    colormap = get_cmap(name, lut)

    if not isinstance(colormap, mcolors.ListedColormap):
        raise ValueError(
            "For a discrete palette, cmap must be of type "
            "matplotlib.colors.ListedColormap")

    ncolors = len(colormap.colors)

    def _cmap_d_pal(n):
        if n > ncolors:
            raise ValueError(
                "cmap `{}` has {} colors you requested {} "
                "colors.".format(name, ncolors, n))

        if ncolors < 256:
            return [mcolors.rgb2hex(c) for c in colormap.colors[:n]]
        else:
            # Assume these are continuous and get colors equally spaced
            # intervals  e.g. viridis is defined with 256 colors
            idx = np.linspace(0, ncolors-1, n).round().astype(int)
            return [mcolors.rgb2hex(colormap.colors[i]) for i in idx]

    return _cmap_d_pal


def desaturate_pal(color, prop, reverse=False):
    """
    Create a palette that desaturate a color by some proportion

    Parameters
    ----------
    color : matplotlib color
        hex, rgb-tuple, or html color name
    prop : float
        saturation channel of color will be multiplied by
        this value
    reverse : bool
        Whether to reverse the palette.

    Returns
    -------
    out : function
        Continuous color palette that takes a single
        parameter either a :class:`float` or a sequence
        of floats maps those value(s) onto the palette
        and returns color(s). The float(s) must be
        in the range [0, 1].

    Examples
    --------
    >>> palette = desaturate_pal('red', .1)
    >>> palette([0, .25, .5, .75, 1])
    ['#ff0000', '#e21d1d', '#c53a3a', '#a95656', '#8c7373']
    """
    if not 0 <= prop <= 1:
        raise ValueError("prop must be between 0 and 1")

    # Get rgb tuple rep
    # Convert to hls
    # Desaturate the saturation channel
    # Convert back to rgb
    rgb = mcolors.colorConverter.to_rgb(color)
    h, l, s = colorsys.rgb_to_hls(*rgb)
    s *= prop
    desaturated_color = colorsys.hls_to_rgb(h, l, s)
    colors = [color, desaturated_color]
    if reverse:
        colors = colors[::-1]
    return gradient_n_pal(colors, name='desaturated')

def
desaturate_pal
(
color
,
prop
,
reverse
=
False
)
:
if
not
0
<=
prop
<=
1
:
raise
ValueError
(
"prop must be between 0 and 1"
)
rgb
=
mcolors
.
colorConverter
.
to_rgb
(
color
)
h
,
l
,
s
=
colorsys
.
rgb_to_hls
(
*
rgb
)
s
*=
prop
desaturated_color
=
colorsys
.
hls_to_rgb
(
h
,
l
,
s
)
colors
=
[
color
,
desaturated_color
]
if
reverse
:
colors
=
colors
[
:
:
1
]
return
gradient_n_pal
(
colors
,
name
=
'desaturated'
)


def manual_pal(values):
    """
    Create a palette from a list of values

    Parameters
    ----------
    values : sequence
        Values that will be returned by the palette function.

    Returns
    -------
    out : function
        A function palette that takes a single
        :class:`int` parameter ``n`` and returns ``n`` values.

    Examples
    --------
    >>> palette = manual_pal(['a', 'b', 'c', 'd', 'e'])
    >>> palette(3)
    ['a', 'b', 'c']
    """
    max_n = len(values)

    def _manual_pal(n):
        if n > max_n:
            msg = ("Palette can return a maximum of {} values. "
                   "{} were requested from it.")
            warnings.warn(msg.format(max_n, n))

        return values[:n]

    return _manual_pal


def cubehelix_pal(start=0, rot=.4, gamma=1.0, hue=0.8,
                  light=.85, dark=.15, reverse=False):
    """
    Utility for creating continuous palette from the cubehelix system.

    This produces a colormap with linearly-decreasing (or increasing)
    brightness. That means that information will be preserved if printed to
    black and white or viewed by someone who is colorblind.

    Parameters
    ----------
    start : float (0 <= start <= 3)
        The hue at the start of the helix.
    rot : float
        Rotations around the hue wheel over the range of the palette.
    gamma : float (0 <= gamma)
        Gamma factor to emphasize darker (gamma < 1) or lighter (gamma > 1)
        colors.
    hue : float (0 <= hue <= 1)
        Saturation of the colors.
    dark : float (0 <= dark <= 1)
        Intensity of the darkest color in the palette.
    light : float (0 <= light <= 1)
        Intensity of the lightest color in the palette.
    reverse : bool
        If True, the palette will go from dark to light.

    Returns
    -------
    out : function
        Continuous color palette that takes a single
        :class:`int` parameter ``n`` and returns ``n``
        equally spaced colors.

    References
    ----------
    Green, D. A. (2011). "A colour scheme for the display of astronomical
    intensity images". Bulletin of the Astromical Society of India, Vol. 39,
    p. 289-295.

    Examples
    --------
    >>> palette = cubehelix_pal()
    >>> palette(5)
    ['#edd1cb', '#d499a7', '#aa688f', '#6e4071', '#2d1e3e']
    """
    cdict = mpl._cm.cubehelix(gamma, start, rot, hue)
    cubehelix_cmap = mpl.colors.LinearSegmentedColormap('cubehelix', cdict)

    def cubehelix_palette(n):
        values = np.linspace(light, dark, n)
        return [mcolors.rgb2hex(cubehelix_cmap(x)) for x in values]

    return cubehelix_palette

def cubehelix_pal ( start = 0 , rot = .4 , gamma = 1.0 , hue = 0.8 , light = .85 , dark = .15 , reverse = False ) : cdict = mpl . _cm . cubehelix ( gamma , start , rot , hue ) cubehelix_cmap = mpl . colors . LinearSegmentedColormap ( 'cubehelix' , cdict ) def cubehelix_palette ( n ) : values = np . linspace ( light , dark , n ) return [ mcolors . rgb2hex ( cubehelix_cmap ( x ) ) for x in values ] return cubehelix_palette

Utility for creating continuous palette from the cubehelix system .


def apply(cls, x, palette, na_value=None, trans=None):
        """
        Scale data continuously

        Parameters
        ----------
        x : array_like
            Continuous values to scale
        palette : callable ``f(x)``
            Palette to use
        na_value : object
            Value to use for missing values.
        trans : trans
            How to transform the data before scaling. If
            ``None``, no transformation is done.

        Returns
        -------
        out : array_like
            Scaled values
        """
        if trans is not None:
            x = trans.transform(x)

        limits = cls.train(x)
        return cls.map(x, palette, limits, na_value)

def apply ( cls , x , palette , na_value = None , trans = None ) : if trans is not None : x = trans . transform ( x ) limits = cls . train ( x ) return cls . map ( x , palette , limits , na_value )


def map(cls, x, palette, limits, na_value=None, oob=censor):
        """
        Map values to a continuous palette

        Parameters
        ----------
        x : array_like
            Continuous values to scale
        palette : callable ``f(x)``
            palette to use
        na_value : object
            Value to use for missing values.
        oob : callable ``f(x)``
            Function to deal with values that are
            beyond the limits

        Returns
        -------
        out : array_like
            Values mapped onto a palette
        """
        x = oob(rescale(x, _from=limits))
        pal = palette(x)
        try:
            pal[pd.isnull(x)] = na_value
        except TypeError:
            pal = [v if not pd.isnull(v) else na_value for v in pal]

        return pal

def map ( cls , x , palette , limits , na_value = None , oob = censor ) : x = oob ( rescale ( x , _from = limits ) ) pal = palette ( x ) try : pal [ pd . isnull ( x ) ] = na_value except TypeError : pal = [ v if not pd . isnull ( v ) else na_value for v in pal ] return pal


def map(cls, x, palette, limits, na_value=None):
        """
        Map values to a discrete palette

        Parameters
        ----------
        palette : callable ``f(x)``
            palette to use
        x : array_like
            Continuous values to scale
        na_value : object
            Value to use for missing values.

        Returns
        -------
        out : array_like
            Values mapped onto a palette
        """
        n = len(limits)
        pal = palette(n)[match(x, limits)]
        try:
            pal[pd.isnull(x)] = na_value
        except TypeError:
            pal = [v if not pd.isnull(v) else na_value for v in pal]

        return pal

def map ( cls , x , palette , limits , na_value = None ) : n = len ( limits ) pal = palette ( n ) [ match ( x , limits ) ] try : pal [ pd . isnull ( x ) ] = na_value except TypeError : pal = [ v if not pd . isnull ( v ) else na_value for v in pal ] return pal


def parse(type: Type):
        """
        Register a parser for a attribute type.

        Parsers will be used to parse `str` type objects from either
        the commandline arguments or environment variables.

        Args:
            type: the type the decorated function will be responsible
                for parsing a environment variable to.
        """

        def decorator(parser):
            EnvVar.parsers[type] = parser
            return parser

        return decorator

def parse ( type : Type ) : def decorator ( parser ) : EnvVar . parsers [ type ] = parser return parser return decorator

Register a parser for a attribute type .


def _patched_run_hook(hook_name, project_dir, context):
    """Used to patch cookiecutter's ``run_hook`` function.

    This patched version ensures that the temple.yaml file is created before
    any cookiecutter hooks are executed
    """
    if hook_name == 'post_gen_project':
        with temple.utils.cd(project_dir):
            temple.utils.write_temple_config(context['cookiecutter'],
                                             context['template'],
                                             context['version'])
    return cc_hooks.run_hook(hook_name, project_dir, context)

def _patched_run_hook ( hook_name , project_dir , context ) : if hook_name == 'post_gen_project' : with temple . utils . cd ( project_dir ) : temple . utils . write_temple_config ( context [ 'cookiecutter' ] , context [ 'template' ] , context [ 'version' ] ) return cc_hooks . run_hook ( hook_name , project_dir , context )

Used to patch cookiecutter s run_hook function .


def _generate_files(repo_dir, config, template, version):
    """Uses cookiecutter to generate files for the project.

    Monkeypatches cookiecutter's "run_hook" to ensure that the temple.yaml file is
    generated before any hooks run. This is important to ensure that hooks can also
    perform any actions involving temple.yaml
    """
    with unittest.mock.patch('cookiecutter.generate.run_hook', side_effect=_patched_run_hook):
        cc_generate.generate_files(repo_dir=repo_dir,
                                   context={'cookiecutter': config,
                                            'template': template,
                                            'version': version},
                                   overwrite_if_exists=False,
                                   output_dir='.')

def _generate_files ( repo_dir , config , template , version ) : with unittest . mock . patch ( 'cookiecutter.generate.run_hook' , side_effect = _patched_run_hook ) : cc_generate . generate_files ( repo_dir = repo_dir , context = { 'cookiecutter' : config , 'template' : template , 'version' : version } , overwrite_if_exists = False , output_dir = '.' )

Uses cookiecutter to generate files for the project .


def setup(template, version=None):
    """Sets up a new project from a template

    Note that the `temple.constants.TEMPLE_ENV_VAR` is set to 'setup' during the duration
    of this function.

    Args:
        template (str): The git SSH path to a template
        version (str, optional): The version of the template to use when updating. Defaults
            to the latest version
    """
    temple.check.is_git_ssh_path(template)
    temple.check.not_in_git_repo()

    repo_path = temple.utils.get_repo_path(template)
    msg = (
        'You will be prompted for the parameters of your new project.'
        ' Please read the docs at https://github.com/{} before entering parameters.'
    ).format(repo_path)
    print(msg)

    cc_repo_dir, config = temple.utils.get_cookiecutter_config(template, version=version)

    if not version:
        with temple.utils.cd(cc_repo_dir):
            ret = temple.utils.shell('git rev-parse HEAD', stdout=subprocess.PIPE)
            version = ret.stdout.decode('utf-8').strip()

    _generate_files(repo_dir=cc_repo_dir, config=config, template=template, version=version)


def _parse_link_header(headers):
    """Parses Github's link header for pagination.

    TODO eventually use a github client for this
    """
    links = {}
    if 'link' in headers:
        link_headers = headers['link'].split(', ')
        for link_header in link_headers:
            (url, rel) = link_header.split('; ')
            url = url[1:-1]
            rel = rel[5:-1]
            links[rel] = url
    return links

def
_parse_link_header
(
headers
)
:
links
=
{
}
if
'link'
in
headers
:
link_headers
=
headers
[
'link'
]
.
split
(
', '
)
for
link_header
in
link_headers
:
(
url
,
rel
)
=
link_header
.
split
(
'; '
)
url
=
url
[
1
:
1
]
rel
=
rel
[
5
:
1
]
links
[
rel
]
=
url
return
links

Parses Github s link header for pagination .


def _code_search(query, github_user=None):
    """Performs a Github API code search

    Args:
        query (str): The query sent to Github's code search
        github_user (str, optional): The Github user being searched in the query string

    Returns:
        dict: A dictionary of repository information keyed on the git SSH url

    Raises:
        `InvalidGithubUserError`: When ``github_user`` is invalid
    """
    github_client = temple.utils.GithubClient()
    headers = {'Accept': 'application/vnd.github.v3.text-match+json'}

    resp = github_client.get('/search/code',
                             params={'q': query, 'per_page': 100},
                             headers=headers)

    if resp.status_code == requests.codes.unprocessable_entity and github_user:
        raise temple.exceptions.InvalidGithubUserError(
            'Invalid Github user or org - "{}"'.format(github_user))
    resp.raise_for_status()

    resp_data = resp.json()

    repositories = collections.defaultdict(dict)
    while True:
        repositories.update({
            'git@github.com:{}.git'.format(repo['repository']['full_name']): repo['repository']
            for repo in resp_data['items']
        })

        next_url = _parse_link_header(resp.headers).get('next')
        if next_url:
            resp = requests.get(next_url, headers=headers)
            resp.raise_for_status()
            resp_data = resp.json()
        else:
            break

    return repositories


def ls(github_user, template=None):
    """Lists all temple templates and packages associated with those templates

    If ``template`` is None, returns the available templates for the configured
    Github org.

    If ``template`` is a Github path to a template, returns all projects spun
    up with that template.

    ``ls`` uses the github search API to find results.

    Note that the `temple.constants.TEMPLE_ENV_VAR` is set to 'ls' for the duration of this
    function.

    Args:
        github_user (str): The github user or org being searched.
        template (str, optional): The template git repo path. If provided, lists
            all projects that have been created with the provided template. Note
            that the template path is the SSH path
            (e.g. git@github.com:CloverHealth/temple.git)

    Returns:
        dict: A dictionary of repository information keyed on the SSH Github url

    Raises:
        `InvalidGithubUserError`: When ``github_user`` is invalid
    """
    temple.check.has_env_vars(temple.constants.GITHUB_API_TOKEN_ENV_VAR)

    if template:
        temple.check.is_git_ssh_path(template)
        search_q = 'user:{} filename:{} {}'.format(
            github_user,
            temple.constants.TEMPLE_CONFIG_FILE,
            template)
    else:
        search_q = 'user:{} cookiecutter.json in:path'.format(github_user)

    results = _code_search(search_q, github_user)
    return collections.OrderedDict(sorted(results.items()))

def
ls
(
github_user
,
template
=
)
:
temple
.
check
.
has_env_vars
(
temple
.
constants
.
GITHUB_API_TOKEN_ENV_VAR
)
if
template
:
temple
.
check
.
is_git_ssh_path
(
template
)
search_q
=
'user:{} filename:{} {}'
.
format
(
github_user
,
temple
.
constants
.
TEMPLE_CONFIG_FILE
,
template
)
else
:
search_q
=
'user:{} cookiecutter.json in:path'
.
format
(
github_user
)
results
=
_code_search
(
search_q
,
github_user
)
return
collections
.
OrderedDict
(
sorted
(
results
.
items
(
)
)
)


def update(check, enter_parameters, version):
    """
    Update package with latest template. Must be inside of the project
    folder to run.

    Using "-e" will prompt for re-entering the template parameters again
    even if the project is up to date.

    Use "-v" to update to a particular version of a template.

    Using "-c" will perform a check that the project is up to date
    with the latest version of the template (or the version specified by "-v").
    No updating will happen when using this option.
    """
    if check:
        if temple.update.up_to_date(version=version):
            print('Temple package is up to date')
        else:
            msg = (
                'This temple package is out of date with the latest template.'
                ' Update your package by running "temple update" and commiting changes.'
            )
            raise temple.exceptions.NotUpToDateWithTemplateError(msg)
    else:
        temple.update.update(new_version=version, enter_parameters=enter_parameters)

Update package with latest template . Must be inside of the project folder to run .


def ls(github_user, template, long_format):
    """
    List packages created with temple. Enter a github user or
    organization to list all templates under the user or org.
    Using a template path as the second argument will list all projects
    that have been started with that template.

    Use "-l" to print the Github repository descriptions of templates
    or projects.
    """
    github_urls = temple.ls.ls(github_user, template=template)
    for ssh_path, info in github_urls.items():
        if long_format:
            print(ssh_path, '-', info['description'] or '(no project description found)')
        else:
            print(ssh_path)

def
ls
(
github_user
,
template
,
long_format
)
:
github_urls
=
temple
.
ls
.
ls
(
github_user
,
template
=
template
)
for
ssh_path
,
info
in
github_urls
.
items
(
)
:
if
long_format
:
print
(
ssh_path
,
'-'
,
info
[
'description'
]
or
'(no project description found)'
)
else
:
print
(
ssh_path
)

List packages created with temple . Enter a github user or organization to list all templates under the user or org . Using a template path as the second argument will list all projects that have been started with that template .


def switch(template, version):
    """
    Switch a project's template to a different template.
    """
    temple.update.update(new_template=template, new_version=version)

def switch ( template , version ) : temple . update . update ( new_template = template , new_version = version )

Switch a project s template to a different template .


def _in_git_repo():
    """Returns True if inside a git repo, False otherwise"""
    ret = temple.utils.shell('git rev-parse', stderr=subprocess.DEVNULL, check=False)
    return ret.returncode == 0

def
_in_git_repo
(
)
:
ret
=
temple
.
utils
.
shell
(
'git rev-parse'
,
stderr
=
subprocess
.
DEVNULL
,
check
=
False
)
return
ret
.
returncode
==
0

Returns True if inside a git repo False otherwise


def _has_branch(branch):
    """Return True if the target branch exists."""
    ret = temple.utils.shell('git rev-parse --verify {}'.format(branch),
                             stderr=subprocess.DEVNULL,
                             stdout=subprocess.DEVNULL,
                             check=False)
    return ret.returncode == 0

def
_has_branch
(
branch
)
:
ret
=
temple
.
utils
.
shell
(
'git rev-parse --verify {}'
.
format
(
branch
)
,
stderr
=
subprocess
.
DEVNULL
,
stdout
=
subprocess
.
DEVNULL
,
check
=
False
)
return
ret
.
returncode
==
0

Return True if the target branch exists .


def not_has_branch(branch):
    """Raises `ExistingBranchError` if the specified branch exists."""
    if _has_branch(branch):
        msg = 'Cannot proceed while {} branch exists; remove and try again.'.format(branch)
        raise temple.exceptions.ExistingBranchError(msg)

Raises ExistingBranchError if the specified branch exists .


def has_env_vars(*env_vars):
    """Raises `InvalidEnvironmentError` when one isnt set"""
    for env_var in env_vars:
        if not os.environ.get(env_var):
            msg = (
                'Must set {} environment variable. View docs for setting up environment at {}'
            ).format(env_var, temple.constants.TEMPLE_DOCS_URL)
            raise temple.exceptions.InvalidEnvironmentError(msg)

Raises InvalidEnvironmentError when one isnt set


def is_temple_project():
    """Raises `InvalidTempleProjectError` if repository is not a temple project"""
    if not os.path.exists(temple.constants.TEMPLE_CONFIG_FILE):
        msg = 'No {} file found in repository.'.format(temple.constants.TEMPLE_CONFIG_FILE)
        raise temple.exceptions.InvalidTempleProjectError(msg)

def
is_temple_project
(
)
:
if
not
os
.
path
.
exists
(
temple
.
constants
.
TEMPLE_CONFIG_FILE
)
:
msg
=
'No {} file found in repository.'
.
format
(
temple
.
constants
.
TEMPLE_CONFIG_FILE
)
raise
temple
.
exceptions
.
InvalidTempleProjectError
(
msg
)

Raises InvalidTempleProjectError if repository is not a temple project


def _get_current_branch():
    """Determine the current git branch"""
    result = temple.utils.shell('git rev-parse --abbrev-ref HEAD', stdout=subprocess.PIPE)
    return result.stdout.decode('utf8').strip()

def
_get_current_branch
(
)
:
result
=
temple
.
utils
.
shell
(
'git rev-parse --abbrev-ref HEAD'
,
stdout
=
subprocess
.
PIPE
)
return
result
.
stdout
.
decode
(
'utf8'
)
.
strip
(
)


def clean():
    """Cleans up temporary resources

    Tries to clean up:

    1. The temporary update branch used during ``temple update``
    2. The primary update branch used during ``temple update``
    """
    temple.check.in_git_repo()

    current_branch = _get_current_branch()
    update_branch = temple.constants.UPDATE_BRANCH_NAME
    temp_update_branch = temple.constants.TEMP_UPDATE_BRANCH_NAME

    if current_branch in (update_branch, temp_update_branch):
        err_msg = (
            'You must change from the "{}" branch since it will be deleted during cleanup'
        ).format(current_branch)
        raise temple.exceptions.InvalidCurrentBranchError(err_msg)

    if temple.check._has_branch(update_branch):
        temple.utils.shell('git branch -D {}'.format(update_branch))
    if temple.check._has_branch(temp_update_branch):
        temple.utils.shell('git branch -D {}'.format(temp_update_branch))