
    c2                     *   d Z dZddlZddlZddlZddlZddlmZmZm	Z	m
Z
mZmZmZ ddlmZ ddlmZmZmZmZmZmZmZ ddlmZmZ ddlmZmZmZ dd	lmZ dd
lm Z!m"Z" ddl#m$Z$ ddl%m&Z& de'de'fdZ(d Z)de'de*fdZ+de'de'fdZ,efdZ- G d de&      Z. G d de/      Z0d Z1d Z2  e$d      e2      Z2d Z3  e$d      e3      Z3d)de'dee'   dee'   fdZ4d)d edee'   dee'   fd!Z5e!e"dfd"e'd#e'd$ee'e'e'e'e'e'e'e'f   d%ee'e'e'e'e'e'f   d&e*ddfd'Z6d*d(Z7y)+a3  File and file-path manipulation utilities.

:group path manipulation: first_level_directory, relative_path, is_binary,get_by_ext, remove_dead_links
:group file manipulation: norm_read, norm_open, lines, stream_lines, lines,write_open_mode, ensure_fs_mode, export
:sort: path manipulation, file manipulation
zrestructuredtext en    N)isabsisdirislinksplitexistsnormpathjoin)abspath)sepmkdirremovelistdirstatchmodwalk)ST_MODES_IWRITE)OptionalListTuple)TextIOWrapper)STD_BLACKLISTIGNORED_EXTENSIONS)callable_deprecated)FileIOpathreturnc                 X    t        |       \  }}|r|rt        |      \  }}|r|r|r|S |S )a  Return the first level directory of a path.

    >>> first_level_directory('home/syt/work')
    'home'
    >>> first_level_directory('/home/syt/work')
    '/'
    >>> first_level_directory('work')
    'work'
    >>>

    :type path: str
    :param path: the path for which we want the first level directory

    :rtype: str
    :return: the first level directory appearing in `path`
    )r   )r   headtails      :/usr/lib/python3/dist-packages/logilab/common/fileutils.pyfirst_level_directoryr"   .   s7    " tJD$
44[
d 4K    c                 h    t        |       } t        |       D cg c]  }t        | |       c}S c c}w )z*Lists path's content using absolute paths.)r
   r   r	   )r   filenames     r!   abspath_listdirr&   H   s*    4=D18?XDx ???s   /r%   c                 r    	 t        j                  |       d   j                  d       S # t        $ r Y yw xY w)a  Return true if filename may be a binary file, according to it's
    extension.

    :type filename: str
    :param filename: the name of the file

    :rtype: bool
    :return:
      true if the file is a binary file (actually if it's mime type
      isn't beginning by text/)
    r   text   )	mimetypes
guess_type
startswithAttributeErrorr%   s    r!   	is_binaryr/   N   s?     ''1!4??GGG s   '* 	66c                     t        |       ryy)zReturn the write mode that should used to open file.

    :type filename: str
    :param filename: the name of the file

    :rtype: str
    :return: the mode that should be use to open the file ('w' or 'wb')
    wbw)r/   r.   s    r!   write_open_moder3   b   s     r#   c                 R    t        |       t           }||z  st        | ||z         yy)a;  Check that the given file has the given mode(s) set, else try to
    set it.

    :type filepath: str
    :param filepath: path of the file

    :type desired_mode: int
    :param desired_mode:
      ORed flags describing the desired mode. Use constants from the
      `stat` module for file permission's modes
    N)r   r   r   )filepathdesired_modemodes      r!   ensure_fs_moder8   p   s.     >'"D,h|+, r#   c                   <    e Zd ZdZdededdfdZd
dZd
dZd
d	Zy)ProtectedFileaK  A special file-object class that automatically does a 'chmod +w' when
    needed.

    XXX: for now, the way it is done allows 'normal file-objects' to be
    created during the ProtectedFile object lifetime.
    One way to circumvent this would be to chmod / unchmod on each
    write operation.

    One other way would be to :

    - catch the IOError in the __init__

    - if IOError, then create a StringIO object

    - each write operation writes in this StringIO object

    - on close()/del(), write/append the StringIO content to the file and
      do the chmod only once
    r5   r7   r   Nc                     t        |      t           | _        d| _        |dv r7| j                  t        z  s$t        || j                  t        z         d| _        t        j                  | ||       y )NF)r2   ar1   abT)r   r   original_modemode_changedr   r   r   __init__)selfr5   r7   s      r!   r@   zProtectedFile.__init__   s_    !(^G4!))%%0h 2 2X =>$(!h-r#   c                 l    | j                   r(t        | j                  | j                         d| _         yy)z$restores the original mode if neededFN)r?   r   namer>   rA   s    r!   _restore_modezProtectedFile._restore_mode   s,    $))T//0 %D r#   c                 N    | j                          t        j                  |        y)zrestore mode before closingN)rE   r   closerD   s    r!   rG   zProtectedFile.close   s    Tr#   c                 >    | j                   s| j                          y y N)closedrG   rD   s    r!   __del__zProtectedFile.__del__   s    {{JJL r#   )r   N)	__name__
__module____qualname____doc__strr@   rE   rG   rK    r#   r!   r:   r:      s0    (. .C .D .&
r#   r:   c                       e Zd ZdZy)UnresolvableErrorzgException raised by relative path when it's unable to compute relative
    path between two paths.
    N)rL   rM   rN   rO   rQ   r#   r!   rS   rS      s    r#   rS   c                    t        |       } t        |      }| |k(  ryt        |      rt        |       s|S t        |       r
t               | j                  t              }|j                  t              }d}g }t        |      dkD  r]|j                  d      }|r(t        |      dkD  r||d   k(  r|j                  d       nd}|j                  d       t        |      dkD  r]||z  }t	        j                  |      S )aj  Try to get a relative path from `from_file` to `to_file`
    (path will be absolute if to_file is an absolute file). This function
    is useful to create link in `from_file` to `to_file`. This typical use
    case is used in this function description.

    If both files are relative, they're expected to be relative to the same
    directory.

    >>> relative_path( from_file='toto/index.html', to_file='index.html')
    '../index.html'
    >>> relative_path( from_file='index.html', to_file='toto/index.html')
    'toto/index.html'
    >>> relative_path( from_file='tutu/index.html', to_file='toto/index.html')
    '../toto/index.html'
    >>> relative_path( from_file='toto/index.html', to_file='/index.html')
    '/index.html'
    >>> relative_path( from_file='/toto/index.html', to_file='/index.html')
    '../index.html'
    >>> relative_path( from_file='/toto/index.html', to_file='/toto/summary.html')
    'summary.html'
    >>> relative_path( from_file='index.html', to_file='index.html')
    ''
    >>> relative_path( from_file='/index.html', to_file='toto/index.html')
    Traceback (most recent call last):
      File "<string>", line 1, in ?
      File "<stdin>", line 37, in relative_path
    UnresolvableError
    >>> relative_path( from_file='/index.html', to_file='/index.html')
    ''
    >>>

    :type from_file: str
    :param from_file: source file (where links will be inserted)

    :type to_file: str
    :param to_file: target file (on which links point)

    :raise UnresolvableError: if it has been unable to guess a correct path

    :rtype: str
    :return: the relative path of `to_file` from `from_file`
     r)   r   z..)	r   r   rS   r   r   lenpopappendr	   )	from_fileto_file
from_partsto_partsidemresultdirnames          r!   relative_pathr`      s    V #IwGGW~YN	y	!!%J}}S!HDF
j/A
..#CMA%'Xa[*@LLODMM$ j/A
 hF88Fr#   c                 6    t        | d      j                         S )zReturn the content of the file with normalized line feeds.

    :type path: str
    :param path: path to the file to read

    :rtype: str
    :return: the content of the file with normalized line feeds
    U)openreadr   s    r!   	norm_readrf      s     c?!!r#   zuse "open(path, 'U').read()"c                     t        | d      S )zReturn a stream for a file with content with normalized line feeds.

    :type path: str
    :param path: path to the file to open

    :rtype: file or StringIO
    :return: the opened file with normalized line feeds
    rb   )rc   re   s    r!   	norm_openrh   	  s     c?r#   zuse "open(path, 'U')"commentsc                 p    t        j                  |       5 }t        ||      cddd       S # 1 sw Y   yxY w)a  Return a list of non empty lines in the file located at `path`.

    :type path: str
    :param path: path to the file

    :type comments: str or None
    :param comments:
      optional string which can be used to comment a line in the file
      (i.e. lines starting with this string won't be returned)

    :rtype: list
    :return:
      a list of stripped line in the file, without empty and commented
      lines

    :warning: at some point this function will probably return an iterator
    N)iorc   stream_lines)r   ri   streams      r!   linesrn     s0    $ 
 .&FH-. . .s   ,5rm   c                     	 | j                   }g } |       D ]:  }|j                         }|s||j	                  |      r*|j                  |       < |S # t        $ r | j                  }Y _w xY w)a  Return a list of non empty lines in the given `stream`.

    :type stream: object implementing 'xreadlines' or 'readlines'
    :param stream: file like object

    :type comments: str or None
    :param comments:
      optional string which can be used to comment a line in the file
      (i.e. lines starting with this string won't be returned)

    :rtype: list
    :return:
      a list of stripped line in the file, without empty and commented
      lines

    :warning: at some point this function will probably return an iterator
    )
xreadlinesr-   	readlinesstripr,   rX   )rm   ri   rq   r^   lines        r!   rl   rl   .  sw    $%%%	 F  zz|X%T__X-FMM$  M  %$$	%s   A A.-A.from_dirto_dir	blacklist
ignore_extverbosec           
         	 t        |       t        |       D ]  \  }}}|D ]  }	 |j                  |        |D ]B  }	t        ||	      }
||
t        |       d z   }t        |
      s,t        |      r8t        |       D |D ]  }t        |D cg c]  }|j                  |       c}      r,t        ||      }
||
t        |       d z   }|rt        |
d|t        j                         t        |      rt        |       t        j                  |
|         y# t        $ r Y w xY w# t        $ r Y w xY wc c}w )a  Make a mirror of `from_dir` in `to_dir`, omitting directories and
    files listed in the black list or ending with one of the given
    extensions.

    :type from_dir: str
    :param from_dir: directory to export

    :type to_dir: str
    :param to_dir: destination directory

    :type blacklist: list or tuple
    :param blacklist:
      list of files or directories to ignore, default to the content of
      `BASE_BLACKLIST`

    :type ignore_ext: list or tuple
    :param ignore_ext:
      list of extensions to ignore, default to  the content of
      `IGNORED_EXTENSIONS`

    :type verbose: bool
    :param verbose:
      flag indicating whether information about exported files should be
      printed to stderr, default to False
    Nz->)file)r   OSErrorr   r   
ValueErrorr	   rV   r   r   anyendswithprintsysstderrshutilcopy2)rt   ru   rv   rw   rx   	directorydirnames	filenamesnorecursr_   srcdestr%   exts                 r!   exportr   L  sD   @f +/x. $&	8Y! 	H)	
   	 Gy'*CCH00DSzd|$K	  " 	$H jAsH%%c*ABy(+CCH00Dc4CJJ7d|tLLd#	$$     Bs(   D D+D;	D('D(+	D87D8c                     t        |       D ]M  \  }}}||z   D ]?  }t        ||      }t        |      st        |      r'|rt	        d|       t        |       A O y)a"  Recursively traverse directory and remove all dead links.

    :type directory: str
    :param directory: directory to cleanup

    :type verbose: bool
    :param verbose:
      flag indicating whether information about deleted links should be
      printed to stderr, default to False
    zremove dead linkN)r   r	   r   r   r   r   )r   rx   dirpathr   r   r%   r   s          r!   remove_dead_linksr     s`     )-Y $9 9, 	Hw)Cc{6#;,c2s	r#   rI   )r   )8rO   __docformat__rk   r   r   r*   os.pathr   r   r   r   r   r   r	   r
   osr   r   r   r   r   r   r   r   r   typingr   r   r   _ior   logilab.commonr   BASE_BLACKLISTr   logilab.common.deprecationr   logilab.common.compatr   rP   r"   r&   intr/   r3   r8   r:   	ExceptionrS   r`   rf   rh   rn   rl   r   r   rQ   r#   r!   <module>r      s  $ & 	 
   G G G  = = = " ( (  N : (  4@  (c c  +3 -$,F ,^	 @F	" B @A)L		 ; 9:9E	. .x} .S	 ., (3- 4PS9 B @N6H<$<$<$ S#sCc3;<<$ c3S#s23	<$
 <$ 
<$~r#   