Metadata-Version: 2.0
Name: mistool
Version: 1.2.1b0
Summary: Miscellaneous missing tools that can help the py-developper.
Home-page: https://github.com/bc-python-tools/mistool
Author: Christophe BAL
Author-email: projetmbc@gmail.com
License: GPLv3
Keywords: python latex os path string terminal tool
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License (GPL)
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Topic :: Utilities
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Desktop Environment :: File Managers
Classifier: Topic :: System :: Logging
Classifier: Topic :: Text Processing :: Markup :: LaTeX

What about this package ?
=========================

**misTool** is the contraction of **missing**, **miscellaneous** and
**tool(s)**. This package contains some modules that could be useful for
Python developments.

***If you want more informations and examples than thereafter, just take
a look at the docstrings.***

I beg your pardon for my english...
===================================

English is not my native language, so be nice if you notice
misunderstandings, misspellings or grammatical errors in my documents
and codes.

What's new in this version ``1.2.1-beta`` ?
===========================================

The function ``between`` in ``string_use`` has a new optional argument
``keepseps`` so as to keep or not the seprators.

What's new in this version ``1.2.0-beta`` ?
===========================================

The module ``date_use`` has been changed to ``datetime_use`` : see the
presentation above to know how to work now with dates inside
**misTool**.

What's new in this version ``1.1.1-beta`` ?
===========================================

In the module ``python_use``, the new class ``MKOrderedDict`` allows to
define kinds of ordered dictionaries accepting several times the same
key but at different "places".

**Warning !** Here are some important changes.

1. In the module ``python_use``, the class ``OrderedRecuDict`` becomes
   ``RecuOrderedDict``.

2. In the module ``string_use``, the function ``ascii_it`` becomes
   ``asciify``.

The module ``os_use``
=====================

Changing the working directory for commands
-------------------------------------------

With ``os_use.cd``, you have a context which changes temporarily the
directory where launching terminal like commands. When the context is
closed, the working directory goes back to the one just before the call
of ``os_use.cd``.

Let's see an example. We suppose that we have the following directory
with the absolute path ``/Users/projetmbc/basic_dir`` in a Unix system.

::

    + basic_dir
        * latex_1.tex
        * latex_2.tex
        * python_1.py
        * python_2.py
        * python_3.py
        * python_4.py
        * text_1.txt
        * text_2.txt
        * text_3.txt
        + empty_dir
        + sub_dir
            * code_A.py
            * code_B.py
            * slide_A.pdf
            * slide_B.pdf
            + sub_sub_dir
                * doc.pdf

The following code first goes inside ``/Users/projetmbc/basic_dir`` and
then it moves to ``/Users/projetmbc/basic_dir/sub_dir``. With
``subprocess.call("ls")``, we simply use the Unix command ``ls`` so as
to list files and folders inside the current working directory.

.. code:: python

    >>> import subprocess
    >>> from mistool.os_use import cd
    >>> with cd("/Users/projetmbc/basic_dir"):
    ...     subprocess.call("ls")
    empty_dir   python_1.py python_4.py text_2.txt
    latex_1.tex python_2.py sub_dir     text_3.txt
    latex_2.tex python_3.py text_1.txt
    >>> with cd("/Users/projetmbc/basic_dir/sub_dir"):
    ...     subprocess.call("ls")
    code_A.py   slide_A.pdf sub_sub_dir
    code_B.py   slide_B.pdf

Launching commands like in a terminal
-------------------------------------

The aim of the function ``os_use.runthis`` is to simplify a lot the
launching of subprocesses *(just use commands as you were inside your
terminal)*. Let's consider the basic following Python script with
absolute path ``/Users/projetmbc/script.py``.

.. code:: python

    print("Everything is ok.")

To launch this program, we just have to use the single string Unix
command ``python3 /Users/projetmbc/script.py`` like in the following
lines. You can see that by default nothing is printed, so you have to
use ``showoutput = True`` if you want to see what the script launched
prints.

.. code:: python

    >>> from mistool.os_use import PPath, runthis
    >>> pyfile = PPath("/Users/projetmbc/script.py")
    >>> runthis(cmd = "python3 {0}".format(ppath))
    >>> runthis(cmd = "python3 {0}".format(ppath), showoutput = True)
    Everything is ok.

System used and environment's path
----------------------------------

The call to ``os_use.system()`` returns the name, in lower case, of the
OS used : possible strings returned can be for example ``"windows"``,
``"mac"``, ``"linux"`` and also ``"java"``.

``os_use.pathenv()`` gives you the paths of executables known by your OS
*(this is indeed an alias for ``os.getenv('PATH')``)*.

Enhanced version of the class ``pathlib.Path``
----------------------------------------------

The class ``os_use.PPath`` adds several methods to the useful class
``pathlib.Path``. Here are examples.

Informations about one path
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following code shows additional informations given by the class
``os_use.PPath``.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> path = PPath("dir/subdir/file.txt")
    >>> path.parent
    PPath('dir/subdir')
    >>> print(path.depth)
    2
    >>> print(path.ext)
    'txt'

Another useful method named ``is_protected`` works as explained below.

1. If the path does not point to an existing file or folder, an OS error
   is raised.

2. If the path is the one of a folder, the answer returned is ``True``
   for a modifiable directory and ``False`` otherwise.

3. Finally if the path points to a file, then that is its parent folder
   which is tested.

There is also the method ``is_empty`` which can give three different
responses.

1. If the path is the one of an empty directory, ``False`` is returned.

2. ``True`` is returned when the path corresponds to an non-empty
   folder.

3. If the path doesn't point to an existing directory an OS error is
   raised.

Changing one path
~~~~~~~~~~~~~~~~~

Changing or adding an extension is very easy with the method
``with_ext``.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> path_no_ext = PPath("dir/subdir")
    >>> path_no_ext.with_ext("ext")
    PPath('dir/subdir.ext')
    >>> path_ext = PPath("dir/subdir/file.txt")
    >>> path_ext.with_ext("ext")
    PPath('dir/subdir/file.ext')

Obtaining a short version or a normalized one of a path needs no effort.
Here is how to do that *(``~`` is a shortcut for the main OS user's
folder)*.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> path_too_long = PPath("~/dir_1/dir_2/dir_3/../../file.txt")
    >>> path_too_long.normpath
    PPath('/Users/projetmbc/dir_1/file.txt')
    >>> path_long = PPath("/Users/projetmbc/dir_1/dir_2/dir_3/../../file.txt")
    >>> path_long.shortpath
    PPath('~/dir_1/file.txt')

Comparing paths
~~~~~~~~~~~~~~~

The "common" folder of several paths is obtained by using the method
``common_with`` or equivalently the magic operator ``&``.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> path        = PPath("/Users/projetmbc/source/doc")
    >>> path_1      = PPath("/Users/projetmbc/README")
    >>> path_2      = PPath("/Users/projetmbc/source/misTool/os_use.py")
    >>> path_danger = PPath("/NoUser/projects")
    >>> path.common_with(path_1)           # Same as ``path & path_1``
    PPath('/Users/projetmbc')
    >>> path.common_with(path_2)           # Same as ``path & path_2``
    PPath('/Users/projetmbc/source')
    >>> path.common_with(path_danger)      # No error raised !
    PPath('/')
    >>> path.common_with(path_1, path_2)   # Same as ``path & path_1 & path_2``
    PPath('/Users/projetmbc')
    >>> path.common_with([path_1, path_2]) # Same as ``path & [path_1, path_2]``
    PPath('/Users/projetmbc')

The class ``os_use.PPath`` adds a magic method so as to use
``path - anotherpath`` instead of ``path.relative_to(anotherpath)``
where the method ``relative_to`` is implemented by the class
``pathlib.Path``.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> main    = PPath("/Users/projetmbc")
    >>> path_1  = PPath("/Users/projetmbc/README")
    >>> path_2  = PPath("/Users/projetmbc/source/misTool/os_use.py")
    >>> path_1 - main
    PPath('README')
    >>> path_2 - main
    PPath('source/misTool/os_use.py')
    >>> path_2 - path_1
    Traceback (most recent call last):
    [...]
    ValueError: '/Users/projetmbc/source/misTool/os_use.py' does not start with '/Users/projetmbc/README'

If you need to know the depth of one path relatively to another, just
call the method ``depth_in``.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> main    = PPath("/Users/projetmbc")
    >>> path_1  = PPath("/Users/projetmbc/README")
    >>> path_2  = PPath("/Users/projetmbc/source/misTool/os_use.py")
    >>> path_pb = PPath("/NoUser/projects")
    >>> print(path_1.depth_in(main))
    0
    >>> print(path_2.depth_in(main))
    2
    >>> print(path_pb.depth_in(main))
    Traceback (most recent call last):
    [...]
    ValueError: '/NoUser/projects' does not start with '/Users/projetmbc'

The special concept of "regpath"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A "regpath" is a query mixing all the power of regexes and the Unix-glob
special characters *(there are also some additional query features)*. We
will use some "regpaths" in the incoming examples.

**See the docstring of the method ``regpath2meta`` for complete
informations about the "regpaths".**

Walk and see
~~~~~~~~~~~~

The method ``see`` **tries** to open the current path with a possible
associated application. For example, an HTML file will be opened by your
default browser.

You can walk very easily inside a directory thanks to the method
``walk`` and the "regpaths" *(see the previous section)*. For example,
let's suppose that we have the following directory with absolute path
``/Users/projetmbc/basic_dir`` in a Unix system.

::

    + basic_dir
        * latex_1.tex
        * latex_2.tex
        * python_1.py
        * python_2.py
        * python_3.py
        * python_4.py
        * text_1.txt
        * text_2.txt
        * text_3.txt
        + empty_dir
        + sub_dir
            * code_A.py
            * code_B.py
            * slide_A.pdf
            * slide_B.pdf
            + sub_sub_dir
                * doc.pdf

Here are easy to understand examples where the regpath ``"*"`` is for a
non-recursive search contrary to the regpath ``"**"``.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> folder = PPath("/Users/projetmbc/basic_dir")
    >>> for p in folder.walk("dir::**"):
    ...     print("+", p)
    ...
    + /Users/projetmbc/basic_dir/empty_dir
    + /Users/projetmbc/basic_dir/sub_dir
    + /Users/projetmbc/basic_dir/sub_dir/sub_sub_dir
    >>> for p in folder.walk("file::**.py"):
    ...     print("+", p)
    ...
    + /Users/projetmbc/basic_dir/python_1.py
    + /Users/projetmbc/basic_dir/python_2.py
    + /Users/projetmbc/basic_dir/python_3.py
    + /Users/projetmbc/basic_dir/python_4.py
    + /Users/projetmbc/basic_dir/sub_dir/code_A.py
    + /Users/projetmbc/basic_dir/sub_dir/code_B.py
    >>> for p in folder.walk("file::*.py"):
    ...     print("+", p)
    ...
    + /Users/projetmbc/basic_dir/python_1.py
    + /Users/projetmbc/basic_dir/python_2.py
    + /Users/projetmbc/basic_dir/python_3.py
    + /Users/projetmbc/basic_dir/python_4.py

Create
~~~~~~

Creating files and folders is straight forward with the method
``create`` even if this needs to add several parent directories that
don't yet exist. In the following example, we suppose that the current
directory has absolute path ``/Users/projetmbc``, and doesn't contain
any subfolder.

.. code:: python

    >>> from mistool.os_use import PPath
    >>> path_1 = PPath("test/README")
    >>> path_1.is_file()
    False
    >>> path_1.create("file")
    >>> path_1.is_file()
    True
    >>> path_2 = PPath("test/README")
    >>> path_2.create("dir")
    Traceback (most recent call last):
    [...]
    ValueError: path points to an existing file.

Remove
~~~~~~

If you want to destroy a whole directory, or simply a file, given by its
``PPath``, just use the method ``remove``.

**Warning ! Because removing a file or a directory can be a dangerous
thing, you can use the method ``can_be_removed`` which by default will
raise an OS error if the ``PPath`` is one of an existing file or
folder.**

The method ``clean`` allows to remove specific files and/or directories
matching a regpath given as an argument.

Move & copy
~~~~~~~~~~~

By default, the method ``copy_to`` allows you to copy a file or a
directory into another location, whereas the method ``move_to`` will
move a file or a directory to another place.

The module ``string_use``
=========================

Multi-replacements
------------------

The class ``string_use.MultiReplace`` makes possible to do
multi-replacements recursively or not *(by default
``mode = "norecu"``)*.

.. code:: python

    >>> from mistool.string_use import MultiReplace
    >>> from mistool.config.pattern import PATTERNS_WORDS
    >>> oldnew = {
    ...     'W1': "Word #1",
    ...     'W2': "Word #2",
    ...     'W3': "W1 and W2"
    ... }
    >>> mreplace = MultiReplace(
    ...     oldnew  = oldnew,
    ...     mode    = "recu",
    ...     pattern = PATTERNS_WORDS['var']
    ... )
    >>> print(mreplace("W1 and W2 = W3"))
    Word #1 and Word #2 = Word #1 and Word #2
    >>> mreplace.mode = "norecu"  
    >>> mreplace.build()
    >>> print(mreplace("W1 and W2 = W3"))
    Word #1 and Word #2 = W1 and W2

The code above show that cyclic definitions will raise a ``ValueError``
exception.

.. code:: python

    >>> from mistool.string_use import MultiReplace
    >>> from mistool.config.pattern import PATTERNS_WORDS
    >>> oldnew = {
    ...     'WRONG_1': "one small text and  WRONG_2",
    ...     'WRONG_2': "one small text, and then WRONG_3",
    ...     'WRONG_3': "with WRONG_1, there is one problem here"
    ... }
    >>> mreplace = MultiReplace(
    ...     oldnew  = oldnew,
    ...     mode    = "recu",
    ...     pattern = PATTERNS_WORDS["var"]
    ... )
    Traceback (most recent call last):
    [...]
    ValueError: the following viscious circle has been found.
         + WRONG_2 --> WRONG_3 --> WRONG_1 --> WRONG_2

Multi-splits
------------

The aim of the class ``string_use.MultiSplit`` is to split a text on
several semantic depths. Here is an example of use.

.. code:: python

    >>> from mistool.string_use import MultiSplit
    >>> msplit = MultiSplit(seps = "|")
    >>> print(msplit("p_1 ; p_2 ; p_3 | r_1 ; r_2 | s"))
    [
        'p_1 ; p_2 ; p_3 ',
        ' r_1 ; r_2 ',
        ' s'
    ]
    >>> msplit.seps  = ["|", ";"]
    >>> msplit.strip = True
    >>> print(msplit("p_1 ; p_2 ; p_3 | r_1 ; r_2 | s"))
    [
        ['p_1', 'p_2', 'p_3'],
        ['r_1', 'r_2'],
        ['s']
    ]

Before, between and after
-------------------------

The function ``string_use.between`` looks for two separators such as to
return the text before, between and after the first matching of this
separators. By default, separators are not kept but you can ask to the
function to keep them. ``None`` is returned if no matching has been
found. Just take a look at a concrete example.

.. code:: python

    >>> from mistool.string_use import between
    >>> text = "f(x ; y) = x**2 + y**2"
    >>> seps = ["(", ")"]
    >>> print(between(text, seps))
    [
        'f',                # Before
        'x ; y',            # Between
        ' = x**2 + y**2'    # After
    ]
    >>> print(between(text, seps, True))
    [
        'f(',                # Before
        'x ; y',             # Between
        ') = x**2 + y**2'    # After
    ]
    >>> seps = ["{", "}"]
    >>> print(between(text, seps))
    None

Join with a last special text
-----------------------------

You can join several strings with a special final separator as the
examples above show.

.. code:: python

    >>> from mistool.string_use import joinand
    >>> texts = ["1", "2", "3"]
    >>> print(joinand(texts))
    1, 2 and 3
    >>> print(joinand(texts = texts, andtext = "et"))
    1, 2 et 3
    >>> print(joinand(texts = texts, sep = " + ", andtext = "="))
    1 + 2 = 3

Playing with cases of letters
-----------------------------

The function ``string_use.case`` gives more auto-formatting of strings
*(the last formatting looks strange but it is useful for an incoming
project of the author of ``mistool``)*.

.. code:: python

    >>> from mistool.string_use import case
    >>> text = "onE eXamPLe"
    >>> for kind in ['lower', 'upper', 'sentence', 'title']:
    ...     print("{0}  [{1}]".format(case(text, kind), kind))
    ...
    one example   [lower]
    ONE EXAMPLE   [upper]
    One example   [sentence]
    One Example   [title]

A camel case string can be "uncamelized" by the function
``string_use.camelto``. Here is how to use it *(you can change the
separator by using the optional argument ``sep`` which is ``"_"`` by
default)*.

.. code:: python

    >>> from mistool.string_use import camelto
    >>> text = "OneSmallExampLE"
    >>> for kind in ['lower', 'upper', 'sentence', 'title']:
    ...     print("{0}  [{1}]".format(camelto(text, kind), kind))
    ...
    one_small_examp_l_e   [lower]
    ONE_SMALL_EXAMP_L_E   [upper]
    One_small_examp_l_e   [sentence]
    One_Small_Examp_L_E   [title]

If you need to check the case of a string, just use
``string_use.iscase(text, kind)``.

Playing with ASCII
------------------

You can check if a string is a pure ASCII one.

.. code:: python

    >>> from mistool.string_use import isascii
    >>> print(isascii("Vive la France !"))
    True
    >>> print(isascii("¡Viva España!"))
    False

You can also transform a string to a pure ASCII one *(this will not
always work but in case of failure you can contribute very easily to
enhance ``string_use.asciify``)*.

.. code:: python

    >>> from mistool.string_use import asciify
    >>> print(asciify("¡Viva España!"))
    Viva Espana!
    >>> oldnew = {'!': ""}
    >>> print(asciify(text = "¡Viva España!", oldnew = oldnew))
    Viva Espana

The last example above shows how to be permissive : this means that
``string_use.asciify`` will "asciify" the most characters as possible.

.. code:: python

    >>> from mistool.string_use import asciify
    >>> print(asciify(text = "L'Odyssée de ∏", strict = False))
    L'Odyssee de ∏
    >>> print(asciify("L'Odyssée de ∏"))
    Traceback (most recent call last):
    [...]
    ValueError: ASCII conversion can't be made because of the character << ∏ >>.
    You can use the function ``_ascii_report`` so as to report more precisely
    this fealure with eventually an ascii alternative.

Auto completion
---------------

The class ``string_use.AutoComplete`` gives the auto-completion feature
accessible without using any GUI package.

.. code:: python

    >>> from mistool.string_use import AutoComplete
    >>> myac = AutoComplete(
    ...     words = [
    ...         "article", "artist", "art",
    ...         "when", "who", "whendy",
    ...         "bar", "barbie", "barber", "bar"
    ...     ]
    ... )
    >>> print(myac.matching("art"))
    ['article', 'artist']
    >>> print(myac.matching(""))
    [
        'art', 'article', 'artist',
        'bar', 'barber', 'barbie',
        'when', 'whendy', 'who'
    ]
    >>> print(myac.missing("art", 'article'))
    icle

It is a convention in GUI applications to give auto-completion only for
at least three characters. You can do that by using the optional
argument ``minsize`` which is ``1`` by default.

The module ``term_use``
=======================

Auto-numbering steps
--------------------

For terminal informations, it can be useful to number some important
printed steps. This can be done easily with the class ``term_use.Step``.

.. code:: python

    >>> from mistool.term_use import Step
    >>> mysteps = Step()
    >>> i = 0
    >>> while i <= 12:
    ...     if i % 2:
    ...         mysteps("Action #{0}".format(i))
    ...     i += 1
    ...
    1) Action #1
    2) Action #3
    3) Action #5
    4) Action #7
    5) Action #9
    6) Action #11

The class ``term_use.Step`` has two optional arguments.

1. ``start`` gives the first number which is ``1`` by default.

2. ``textit`` is a function of two variables ``(n, t)`` returning the
   text containing the step number ``n`` and the text ``t``. By default,
   ``textit = lambda n, t: "{0}) {1}".format(n, t)``.

Frame
-----

The function ``term_use.withframe`` puts a text inside an ASCII frame
*(you can choose the alignment and use other kinds of frames if
necessary as it is explained in the docstrings)*.

.. code:: python

    >>> from mistool.term_use import withframe
    >>> text = '''
    ... One small
    ... text
    ... to do tests
    ... '''.strip()
    >>> print(withframe(text))
    ###############
    # One small   #
    # text        #
    # to do tests #
    ###############

ASCII tree views of one directory
---------------------------------

For our examples, we consider a folder with the following structure and
the absolute path ``/Users/projetmbc/dir``.

::

    + dir
        * code_1.py
        * code_2.py
        * file_1.txt
        * file_2.txt
        + doc
            * code_A.py
            * code_B.py
            * slide_A.pdf
            * slide_B.pdf
            + licence
                * doc.pdf
        + emptydir

The preceding ASCII tree view was built easily using the following code
*(``PPath`` is the class defined in ``os_use`` added in ``term_use`` for
you comfort)*.

.. code:: python

    >>> from mistool.term_use import DirView, PPath
    >>> dir     = PPath("/Users/projetmbc/dir")
    >>> dirview = DirView(
    ...     ppath   = dir,
    ...     sorting = "filefirst"
    ... )
    >>> print(dirview.ascii)
    + dir
        * code_1.py
        * code_2.py
        * file_1.txt
        * file_2.txt
        + doc
            * code_A.py
            * code_B.py
            * slide_A.pdf
            * slide_B.pdf
            + licence
                * doc.pdf
        + emptydir

Using the "regpath" concept of the module ``os_use``, we can filter
folders and files shown as in the example above *(we also use the
argument ``display`` so as to customize the output)*.

.. code:: python

    >>> from mistool.term_use import DirView, PPath
    >>> dir     = PPath("/Users/projetmbc/dir")
    >>> dirview = DirView(
    ...     ppath   = dir,
    ...     regpath = "file::**.py",
    ...     display = "main short found"
    ... )
    >>> print(dirview.ascii)
    + dir
        * code_1.py
        * code_2.py
        + doc
            * code_A.py
            * code_B.py

You can also use the following property methods.

1. ``dirview.tree`` is a graphical tree.

2. ``dirview.toc`` gives a minimal tabulated tree.

3. ``dirview.latex`` is for the LaTeX package ``dirtree``.

The module ``python_use``
=========================

A multikeys dictionary
----------------------

The class ``MKOrderedDict`` allows to work easily with multikeys ordered
dictionaries. Here is a complete example of use.

.. code:: python

    >>> from mistool.python_use import MKOrderedDict
    >>> onemkdict = MKOrderedDict()
    >>> onemkdict[(1, 2, 4)] = "1st value"
    >>> onemkdict["key"] = "2nd value"
    >>> onemkdict["key"] = "3rd value"
    >>> print(onemkdict)
    MKOrderedDict([
        ((id=0, key=(1, 2, 4)), value='1st value'),
        ((id=0, key='key')    , value='2nd value'),
        ((id=1, key='key')    , value='3rd value')
    ])
    >>> for k_id, val in onemkdict["key"]:
    ...     print(k_id, val)
    ...
    0 2nd value
    1 3rd value
    >>> print(onemkdict.getitembyid(1, "key"))
    3rd value
    >>> for (k_id, key), val in onemkdict.items():
    ...     print((k_id, key), "===>", val)
    ...
    (0, (1, 2, 4)) ===> 1st value
    (0, 'key') ===> 2nd value
    (1, 'key') ===> 3rd value
    >>> for key, val in onemkdict.items(noid=True):
    ...     print(key, "===>", val)
    ...
    (1, 2, 4) ===> 1st value
    key ===> 2nd value
    key ===> 3rd value
    >>> "key" in onemkdict
    True
    >>> "kaaaay" in onemkdict
    False
    >>> onemkdict.setitembyid(0, "key", "New 2nd value")
    >>> print(onemkdict)
    MKOrderedDict([
        ((id=0, key=(1, 2, 4)), value='1st value'),
        ((id=0, key='key')    , value='New 2nd value'),
        ((id=1, key='key')    , value='3rd value')])

A dictionary defined recursively
--------------------------------

The class ``RecuOrderedDict`` allows to use a list of hashable keys, or
just a single hashable key. Here is a complete example of use.

.. code:: python

    >>> from mistool.python_use import RecuOrderedDict
    >>> onerecudict = RecuOrderedDict()
    >>> onerecudict[[1, 2, 4]] = "1st value"
    >>> onerecudict[(1, 2, 4)] = "2nd value"
    >>> onerecudict["key"] = "3rd value"
    >>> print(onerecudict)
    RecuOrderedDict([
        (
            1,
            RecuOrderedDict([
                (
                    2,
                    RecuOrderedDict([ (4, '1st value') ])
                )
            ])
        ),
        (
            (1, 2, 4),
            '2nd value'
        ),
        (
            'key',
            '3rd value'
        )
    ])
    >>> [1, 2, 4] in onerecudict
    True
    >>> [2, 4] in onerecudict[1]
    True

List of single values of a dictionary
-------------------------------------

If you need to list all the value of one dictionary, the function
``python_use.dictvalues`` is made for you.

.. code:: python

    >>> from mistool.python_use import dictvalues
    >>> onedict = {"a": 1, "b": 2, "c": 1}
    >>> print(dictvalues(onedict))
    [1, 2]
    >>> print(list(onedict.values()))
    [2, 1, 1]

Easy quoted text with the least escaped quote symbols
-----------------------------------------------------

With ``python_use.quote`` you can add without pain quotes around a text.

.. code:: python

    >>> from mistool.python_use import quote
    >>> print(quote('First example.'))
    'First example.'
    >>> print(quote("Same example."))
    'Same example.'
    >>> print(quote('One "small" example.'))
    'One "small" example.'
    >>> print(quote("Another kind of \"example\"."))
    'Another kind of "example".'
    >>> print(quote("An example a 'little' more \"problematic\"."))
    'An example a \'little\' more "problematic".'

The module ``datetime_use``
===========================

Special class ``ddatetime``
---------------------------

The class ``ddatetime`` is an enhanced version of the class
``datetime.datetime`` : see the two sections below. It is very easy to
build an instance of ``ddatetime`` thanks to the very cool function
``build_ddatetime``. The examples above show different ways to define a
date.

.. code:: python

    >>> from mistool.datetime_use import build_ddatetime
    >>> build_ddatetime((2017, 8, 1))
    ddatetime(2017, 8, 1, 0, 0)
    >>> build_ddatetime(2017, 8, 1)
    ddatetime(2017, 8, 1, 0, 0)
    >>> build_ddatetime("2017-08-01")
    ddatetime(2017, 8, 1, 0, 0)
    >>> build_ddatetime("Friday 01 august 2017")
    ddatetime(2017, 8, 1, 0, 0)
    >>> build_ddatetime("Vendredi 1er août 2017", lang = "fr_FR")
    ddatetime(2017, 8, 1, 0, 0)
    >>> build_ddatetime("Vendredi 1er août 2017")
    [...]
    ValueError: Unknown string format
    >>> build_ddatetime("Vendredi 1er août 2017", "fr_FR")
    [...]
    TypeError: an integer is required (got type str)

Note that you **must** define a special language using
``lang = "fr_FR"`` as you can see in the two last commands in the
preceding example.

Next day having a fixed english name
------------------------------------

In some applications you want to know the next monday after a fixing
date.

.. code:: python

    >>> from mistool.datetime_use import ddatetime
    >>> onedate = ddatetime(2017,8, 1)
    >>> print(onedate.strftime("%Y-%m-%d is a %A."))
    2017-08-01 is a Tuesday.
    >>> nextfriday = onedate.nextday(name = "friday")
    >>> print("Next Friday:", nextfriday.strftime("%Y-%m-%d"))
    Next Friday: 2017-08-04

Translating a date
------------------

Thanks to the class ``ddatetime``, it is easy to safely translate all
the names in a date.

.. code:: python

    >>> from mistool.datetime_use import ddatetime
    >>> onedate   = ddatetime(2015, 6, 2)
    >>> oneformat = "%A %d %B %Y"
    >>> print(onedate.translate(strformat = oneformat))
    Tuesday 02 June 2015
    >>> print(onedate.translate(strformat = oneformat, lang = "fr_FR"))
    Mardi 02 juin 2015

Parsing a string to build a date
--------------------------------

The function ``parsedate`` is an international version of the function
``dateutil.parser.parse`` (translations need external contributions :
the job is very easy to do !).

.. code:: python

    >>> from mistool.datetime_use import parsedate
    >>> parsedate("Friday 01 august 2017")
    ddatetime(2017, 8, 1, 0, 0)
    >>> parsedate(timestr = "Vendredi 1er Août 2017", lang = "fr_FR")
    ddatetime(2017, 8, 1, 0, 0)
    >>> parsedate(timestr = "Montag, 11. April 2016", lang = "de_DE")
    [...]
    ValueError: unsupported language ''de_DE''

The module ``url_use``
======================

Looking for dead or bad urls
----------------------------

For the following example, we suppose that we have a working internet
connection.

.. code:: python

    >>> from mistool.url_use import islinked
    >>> islinked("http://www.google.com")
    True
    >>> islinked("http://www.g-o-o-g-l-e.com")
    False

Escaping special characters in urls
-----------------------------------

It is safe to not use non-ASCII characters in a url. Here is one way to
do that.

.. code:: python

    >>> from mistool.url_use import escape
    >>> print(escape("http://www.vivaespaña.com/camión/"))
    http://www.vivaespa%C3%B1a.com/cami%C3%B3n/

The module ``latex_use``
========================

Escaping the special LaTeX characters
-------------------------------------

The function ``latex_use.escape`` will escape all special characters for
you regarding the text or math mode.

.. code:: python

    >>> from mistool.latex_use import escape
    >>> onetext = "\OH/ & ..."
    >>> print(escape(onetext))
    \textbackslash{}OH/ \& ...
    >>> print(escape(text = onetext, mode = "math"))
    \backslash{}OH/ \& ...

Easy LaTeX compilation(s)
-------------------------

The class ``latex_use.Build`` compiles a LaTeX file for you *(for the
moment only the PDF compilation is implemented)*. Let's consider the
following LaTeX file with the absolute path
``/Users/projetmbc/latex/file.tex``.

.. code:: latex

    \documentclass[11pt, oneside]{article}

    \begin{document}

    \section{One little test}

    One basic formula : $E = mc^2$.

    \end{document}

In the following code, we call to the class ``term_use.DirView`` so as
to show the new files made by LaTeX *(the ellipsis ``[...]`` indicates
some lines not reproduced here)*.

.. code:: python

    >>> from mistool.latex_use import Build, PPath
    >>> from mistool.term_use import DirView
    >>> latexdir = PPath("/Users/projetmbc/latex/file.tex")
    >>> print(DirView(latexdir.parent).ascii)
    + latex
        * file.tex
    >>> builder   = Build(latexdir)
    >>> builder.pdf()
    # -- Start of compilation Nb.1 -- #

    This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014) (preloaded
    format=pdflatex)
     restricted \write18 enabled.
    entering extended mode

    [...]

    Output written on file.pdf (1 page, 36666 bytes).
    Transcript written on file.log.

    # -- End of compilation Nb.1 -- #
    >>> print(DirView(latexdir.parent).ascii)
    + latex
        * file.aux
        * file.log
        * file.pdf
        * file.tex

The PDF file has been build by LaTeX but there are also temporary ones.
If you need several compilations, so as to build a table of content for
example, just use the attribut-argument ``repeat``, and if you don't
want to see the LaTeX ouput, just set the attribut-argument
``showinfos`` to ``False``.

Removing the temporary files produced by LaTeX
----------------------------------------------

We keep the same LaTeX example file. The function ``latex_use.clean``
cleans all unuseful temporary files when the compilation has been done.

.. code:: python

    >>> from mistool.latex_use import clean, PPath
    >>> from mistool.term_use import DirView
    >>> latexdir = PPath("/Users/projetmbc/latex")
    >>> print(DirView(latexdir.parent).ascii)
    + latex
        * file.aux
        * file.log
        * file.pdf
        * file.synctex.gz
        * file.tex
    >>> clean(ppath = latexdir, showinfos = True)
    * Cleaning for "/Users/projetmbc/latex/file.tex"
    >>> print(DirView(latexdir.parent).ascii)
    + latex
        * file.pdf
        * file.tex

Automatic installation of personal LaTeX packages
-------------------------------------------------

Let's suppose that we have package named ``lyxam`` stored in a folder
having the absolute path ``/Users/projetmbc/latex/lyxam`` and whose
structure is the following one.

::

    + lyxam
        + change_log
            + 2012
                * 02.txt
                * 03.txt
                * 04.txt
                * 10.txt
            * todo.txt
        * lyxam.sty
        + config
            * settings.tex
            + lang
                * en.tex
                * fr.tex
                + special
                    * fr.config
                + standard
                    * en.config
                    * fr.config
            + style
                * apmep.tex
                * default.tex

To install this package locally in your LaTeX distribution, just do like
in the code above.

.. code:: python

    >>> from mistool.latex_use import install, PPath
    >>> package = PPath("/Users/projetmbc/latex/lyxam")
    >>> install(package)
    Starting installation of the package locally.
        * Deletion of the old << lyxam >> package in the local LaTeX directory.
        * Creation of a new << lyxam >> package in the local LaTeX directory.
            + Adding the new file << lyxam.sty >>
            + Adding the new file << change_log/todo.txt >>
            + Adding the new file << change_log/2012/02.txt >>
            + Adding the new file << change_log/2012/03.txt >>
            + Adding the new file << change_log/2012/04.txt >>
            + Adding the new file << change_log/2012/10.txt >>
            + Adding the new file << config/settings.tex >>
            + Adding the new file << config/lang/en.tex >>
            + Adding the new file << config/lang/fr.tex >>
            + Adding the new file << config/lang/special/fr.config >>
            + Adding the new file << config/lang/standard/en.config >>
            + Adding the new file << config/lang/standard/fr.config >>
            + Adding the new file << config/style/apmep.tex >>
            + Adding the new file << config/style/default.tex >>
        * Refreshing the list of LaTeX packages.

Using the concept of "regpath" of the module ``os_use``, you can for
example choose to not install all the ``TXT`` files.

.. code:: python

    >>> from mistool.latex_use import install, PPath
    >>> package = PPath("/Users/projetmbc/latex/lyxam")
    >>> install(ppath = package, regpath = "file not::**.txt")
    Starting installation of the package locally.
        * Deletion of the old << lyxam >> package in the local LaTeX directory.
        * Creation of a new << lyxam >> package in the local LaTeX directory.
            + Adding the new file << lyxam.sty >>
            + Adding the new file << config/settings.tex >>
            + Adding the new file << config/lang/en.tex >>
            + Adding the new file << config/lang/fr.tex >>
            + Adding the new file << config/lang/special/fr.config >>
            + Adding the new file << config/lang/standard/en.config >>
            + Adding the new file << config/lang/standard/fr.config >>
            + Adding the new file << config/style/apmep.tex >>
            + Adding the new file << config/style/default.tex >>
        * Refreshing the list of LaTeX packages.

Remove a personal LaTeX packages
--------------------------------

Just use ``remove(name)`` where ``name`` is the name of a local LaTeX
package.


