Safe Haskell | None |
---|---|
Language | Haskell2010 |
See documentation for Shh.
Synopsis
- initInteractive :: IO ()
- data Failure = Failure {}
- class Shell f where
- runProc :: HasCallStack => Proc a -> f a
- buildProc :: Shell f => (Handle -> Handle -> Handle -> IO a) -> f a
- pipe :: Shell f => Proc a -> Proc b -> f (a, b)
- pipeErr :: Shell f => Proc a -> Proc b -> f (a, b)
- (|>) :: Shell f => Proc a -> Proc b -> f b
- (|!>) :: Shell f => Proc a -> Proc b -> f b
- (&>) :: Shell f => Proc a -> Stream -> f a
- (&!>) :: Shell f => Proc a -> Stream -> f a
- nativeProc :: (Shell f, NFData a) => (Handle -> Handle -> Handle -> IO a) -> f a
- (<|) :: Shell f => Proc a -> Proc b -> f a
- withPipe :: (Handle -> Handle -> IO a) -> IO a
- writeOutput :: (ExecArg a, Shell io) => a -> io ()
- writeError :: (ExecArg a, Shell io) => a -> io ()
- readInput :: (NFData a, Shell io) => (ByteString -> IO a) -> io a
- unlines :: [ByteString] -> ByteString
- readInputEndBy :: (NFData a, Shell io) => ByteString -> ([ByteString] -> IO a) -> io a
- readInputEndBy0 :: (NFData a, Shell io) => ([ByteString] -> IO a) -> io a
- readInputLines :: (NFData a, Shell io) => ([ByteString] -> IO a) -> io a
- pureProc :: Shell io => (ByteString -> ByteString) -> io ()
- prefixLines :: Shell io => ByteString -> io ()
- writeProc :: Shell io => Proc a -> ByteString -> io a
- withRead :: (Shell f, NFData b) => Proc a -> (ByteString -> IO b) -> f b
- data Stream
- devNull :: Stream
- newtype Proc a = Proc (Handle -> Handle -> Handle -> IO a)
- runProc' :: Handle -> Handle -> Handle -> Proc a -> IO a
- mkProc' :: HasCallStack => Bool -> ByteString -> [ByteString] -> Proc ()
- mkProc :: HasCallStack => ByteString -> [ByteString] -> Proc ()
- capture :: Shell io => io ByteString
- captureTrim :: Shell io => io ByteString
- captureEndBy :: Shell io => ByteString -> io [ByteString]
- captureEndBy0 :: Shell io => io [ByteString]
- captureLines :: Shell io => io [ByteString]
- captureWords :: Shell io => io [ByteString]
- captureRead :: (Shell io, Read a, NFData a) => io a
- apply :: (ExecArg a, Shell io) => Proc v -> a -> io ByteString
- (>>>) :: Shell io => ByteString -> Proc a -> io a
- (<<<) :: Shell io => Proc a -> ByteString -> io a
- waitProc :: HasCallStack => ByteString -> [ByteString] -> ProcessHandle -> IO ()
- dropWhileEnd :: (Char -> Bool) -> ByteString -> ByteString
- trim :: ByteString -> ByteString
- tryFailure :: Shell m => Proc a -> m (Either Failure a)
- tryFailureJust :: Shell m => (Failure -> Maybe b) -> Proc a -> m (Either b a)
- catchFailure :: Shell m => Proc a -> (Failure -> Proc a) -> m a
- catchFailureJust :: Shell m => (Failure -> Maybe b) -> Proc a -> (b -> Proc a) -> m a
- translateCode' :: Shell m => (Int -> Maybe b) -> Proc a -> m (Either b a)
- translateCode :: Shell m => (Int -> Maybe a) -> Proc a -> m a
- failWithStdErr :: Shell io => Proc a -> io a
- ignoreFailure :: (Functor m, Shell m) => Proc a -> m ()
- exitCode :: (Functor m, Shell m) => Proc a -> m Int
- ignoreCode :: (Monad m, Shell m) => Int -> Proc a -> m ()
- class ExecArg a where
- asArg :: a -> [ByteString]
- asArgFromList :: [a] -> [ByteString]
- class Command a where
- toArgs :: HasCallStack => [ByteString] -> a
- type Cmd = HasCallStack => forall a. Command a => a
- displayCommand :: Cmd -> [ByteString]
- pathBins :: IO [FilePath]
- pathBinsAbs :: IO [FilePath]
- findBinsIn :: [FilePath] -> IO [FilePath]
- exe :: (Command a, ExecArg str, HasCallStack) => str -> a
- loadExe :: ExecReference -> String -> Q [Dec]
- data ExecReference
- rawExe :: String -> String -> Q [Dec]
- loadExeAs :: ExecReference -> String -> String -> Q [Dec]
- encodeIdentifier :: String -> String
- loadEnv :: ExecReference -> Q [Dec]
- checkExecutable :: FilePath -> IO Bool
- load :: ExecReference -> [FilePath] -> Q [Dec]
- loadAnnotated :: ExecReference -> (String -> String) -> [FilePath] -> Q [Dec]
- loadAnnotatedEnv :: ExecReference -> (String -> String) -> Q [Dec]
- endBy :: ByteString -> ByteString -> [ByteString]
- loadFromDirs :: [FilePath] -> Q [Dec]
- loadFromBins :: [FilePath] -> Q [Dec]
- loadAnnotatedFromDirs :: [FilePath] -> (String -> String) -> Q [Dec]
- endBy0 :: ByteString -> [ByteString]
- cd' :: FilePath -> IO ()
- class Cd a where
- cd :: a
- xargs1 :: (NFData a, Monoid a) => ByteString -> (ByteString -> Proc a) -> Proc a
- readInputP :: (NFData a, Shell io) => (ByteString -> Proc a) -> io a
- readInputEndByP :: (NFData a, Shell io) => ByteString -> ([ByteString] -> Proc a) -> io a
- readInputEndBy0P :: (NFData a, Shell io) => ([ByteString] -> Proc a) -> io a
- readInputLinesP :: (NFData a, Shell io) => ([ByteString] -> Proc a) -> io a
- withNullInput :: (Handle -> IO a) -> IO a
- withDuplicate :: Handle -> (Handle -> IO a) -> IO a
- withDuplicates :: Handle -> Handle -> Handle -> (Handle -> Handle -> Handle -> IO a) -> IO a
- withDuplicateNullInput :: Handle -> Handle -> (Handle -> Handle -> Handle -> IO a) -> IO a
- hDup :: Handle -> IO Handle
- dupHandleShh :: FilePath -> Handle -> Maybe (MVar Handle__) -> Handle__ -> Maybe HandleFinalizer -> IO Handle
- dupHandleShh_ :: (IODevice dev, BufferedIO dev, Typeable dev) => dev -> FilePath -> Maybe (MVar Handle__) -> Handle__ -> Maybe HandleFinalizer -> IO Handle
Documentation
For doc-tests. Not sure I can use TH in doc tests. >>> :seti -XOverloadedStrings >>> import Data.Monoid >>> import Data.ByteString.Lazy.Char8 (lines) >>> let cat = exe "cat" >>> let echo = exe "echo" >>> let false = exe "false" >>> let head = exe "head" >>> let md5sum = exe "md5sum" >>> let printf = exe "printf" >>> let sleep = exe "sleep" >>> let true = exe "true" >>> let wc = exe "wc" >>> let xargs = exe "xargs" >>> let yes = exe "yes" >>> let some_command = writeOutput "this is stdout" >> (writeOutput "this is stderr" &> StdErr)
initInteractive :: IO () Source #
This function needs to be called in order to use the library successfully
from GHCi. If you use the formatPrompt
function from the shh-extras
package, this will be automatically called for you.
When a process exits with a non-zero exit code
we throw this Failure
exception.
The only exception to this is when a process is terminated
by SIGPIPE
in a pipeline, in which case we ignore it.
Failure | |
|
Instances
Show Failure Source # | |
Exception Failure Source # | |
Defined in Shh.Internal toException :: Failure -> SomeException # fromException :: SomeException -> Maybe Failure # displayException :: Failure -> String # |
This class is used to allow most of the operators in Shh to be
polymorphic in their return value. This makes using them in an IO
context
easier (we can avoid having to prepend everything with a runProc
).
runProc :: HasCallStack => Proc a -> f a Source #
buildProc :: Shell f => (Handle -> Handle -> Handle -> IO a) -> f a Source #
Helper function that creates and potentially executes a Proc
(|>) :: Shell f => Proc a -> Proc b -> f b infixl 1 Source #
Use this to send the output of on process into the input of another. This is just like a shell's `|` operator.
The result is polymorphic in its output, and can result in either another `Proc a` or an `IO a` depending on the context in which it is used.
If any intermediate process throws an exception, the whole pipeline is canceled.
The result of the last process in the chain is the result returned by the pipeline.
>>>
echo "Hello" |> wc
1 1 6
(|!>) :: Shell f => Proc a -> Proc b -> f b infixl 1 Source #
Similar to |!>
except that it connects stderr to stdin of the
next process in the chain.
NB: The next command to be |>
on will recapture the stdout of
both preceding processes, because they are both going to the same
handle!
See the &>
and &!>
operators for redirection.
>>>
echo "Ignored" |!> wc "-c"
Ignored 0
(&>) :: Shell f => Proc a -> Stream -> f a infixl 9 Source #
Redirect stdout of this process to another location
>>>
echo "Ignore me" &> Append "/dev/null"
(&!>) :: Shell f => Proc a -> Stream -> f a infixl 9 Source #
Redirect stderr of this process to another location
>>>
echo "Shh" &!> StdOut
Shh
(<|) :: Shell f => Proc a -> Proc b -> f a infixr 1 Source #
Flipped version of |>
with lower precedence.
>>>
captureTrim <| (echo "Hello" |> wc "-c")
"6"
withPipe :: (Handle -> Handle -> IO a) -> IO a Source #
Create a pipe, and close both ends on exception. The first argument is the read end, the second is the write end.
>>>
withPipe $ \r w -> hPutStr w "test" >> hClose w >> hGetLine r
"test"
writeOutput :: (ExecArg a, Shell io) => a -> io () Source #
Simple
that writes its argument to its Proc
stdout
. This behaves
very much like the standard printf
utility, except that there is no
restriction as to what can be in the argument.
NB: String
arguments are encoded as UTF8, while ByteString
is passed
through. Be aware if you are using OverloadedStrings
that you will get
wrong results if using unicode in your string literal and it inferes
anything other than String
.
>>>
writeOutput "Hello"
Hello
writeError :: (ExecArg a, Shell io) => a -> io () Source #
Simple
that writes its argument to its Proc
stderr
.
See also
.writeOutput
>>>
writeError "Hello" &> devNull
Hello
unlines :: [ByteString] -> ByteString Source #
Join a list of ByteString
s with newline characters, terminating it
with a newline.
readInputEndBy :: (NFData a, Shell io) => ByteString -> ([ByteString] -> IO a) -> io a Source #
readInputEndBy0 :: (NFData a, Shell io) => ([ByteString] -> IO a) -> io a Source #
readInputLines :: (NFData a, Shell io) => ([ByteString] -> IO a) -> io a Source #
pureProc :: Shell io => (ByteString -> ByteString) -> io () Source #
Creates a pure
that simple transforms the Proc
stdin
and writes
it to stdout
. The input can be infinite.
>>>
yes |> pureProc (BS.take 4) |> capture
"y\ny\n"
prefixLines :: Shell io => ByteString -> io () Source #
Captures the stdout of a process and prefixes all the lines with the given string.
>>>
some_command |> prefixLines "stdout: " |!> prefixLines "stderr: " &> StdErr
stdout: this is stdout stderr: this is stderr
writeProc :: Shell io => Proc a -> ByteString -> io a Source #
Provide the stdin of a Proc
from a ByteString
Same as writeOutput
s |> p
withRead :: (Shell f, NFData b) => Proc a -> (ByteString -> IO b) -> f b Source #
Run a process and capture its output lazily. Once the continuation
is completed, the handles are closed. However, the process is run
until it naturally terminates in order to capture the correct exit
code. Most utilities behave correctly with this (e.g. cat
will
terminate if you close the handle).
Same as p |> readInput f
Type representing a series or pipeline (or both) of shell commands.
Proc
's can communicate to each other via stdin
, stdout
and stderr
and can communicate to Haskell via their parameterised return type, or by
throwing an exception.
Instances
Monad Proc Source # | |
Functor Proc Source # | |
Applicative Proc Source # | |
MonadIO Proc Source # | |
Defined in Shh.Internal | |
Shell Proc Source # | |
Defined in Shh.Internal | |
Semigroup (Proc a) Source # | The |
a ~ () => Monoid (Proc a) Source # | |
a ~ () => Command (Proc a) Source # | |
Defined in Shh.Internal toArgs :: [ByteString] -> Proc a Source # |
mkProc' :: HasCallStack => Bool -> ByteString -> [ByteString] -> Proc () Source #
mkProc :: HasCallStack => ByteString -> [ByteString] -> Proc () Source #
Create a Proc
from a command and a list of arguments. Does not delegate
control-c handling.
capture :: Shell io => io ByteString Source #
A special Proc
which captures its stdin and presents it as a ByteString
to Haskell.
>>>
printf "Hello" |> md5sum |> capture
"8b1a9953c4611296a827abf8c47804d7 -\n"
This is just
. Note that it is not lazy, and will read
the entire readInput
pureByteString
into memory.
captureTrim :: Shell io => io ByteString Source #
captureEndBy :: Shell io => ByteString -> io [ByteString] Source #
captureEndBy0 :: Shell io => io [ByteString] Source #
Same as
.captureEndBy
"\0"
captureLines :: Shell io => io [ByteString] Source #
Same as
.captureSplit
"\n"
captureWords :: Shell io => io [ByteString] Source #
Capture stdout, splitting it into words.
apply :: (ExecArg a, Shell io) => Proc v -> a -> io ByteString Source #
Apply a Proc
to a ByteString
. That is, feed the bytestring to
the stdin
of the process and read the stdout
.
> apply md5sum "Hello"
"8b1a9953c4611296a827abf8c47804d7 -n"
waitProc :: HasCallStack => ByteString -> [ByteString] -> ProcessHandle -> IO () Source #
Wait on a given ProcessHandle
, and throw an exception of
type Failure
if its exit code is non-zero (ignoring SIGPIPE)
dropWhileEnd :: (Char -> Bool) -> ByteString -> ByteString Source #
Drop trailing characters from a ByteString
while the given predicate
matches.
>>>
dropWhileEnd isSpace "a line \n"
"a line"
trim :: ByteString -> ByteString Source #
Trim leading and tailing whitespace.
>>>
trim " a string \n"
"a string"
tryFailureJust :: Shell m => (Failure -> Maybe b) -> Proc a -> m (Either b a) Source #
Like
except that it takes an exception predicate which
selects which exceptions to catch. Any exception not matching the predicate
(returning tryFailure
Nothing
) is re-thrown.
catchFailure :: Shell m => Proc a -> (Failure -> Proc a) -> m a Source #
Run a Proc
with an action to take if an exception is thrown.
catchFailureJust :: Shell m => (Failure -> Maybe b) -> Proc a -> (b -> Proc a) -> m a Source #
Like
except that it takes an exception predicate
which selects which exceptions to catch. Any exceptions not matching the
predicate (returning catchFailureJust
Nothing
) are re-thrown.
translateCode' :: Shell m => (Int -> Maybe b) -> Proc a -> m (Either b a) Source #
Apply a function that translates non-0 exit codes to results. Any code
that returns a Nothing
will be thrown as a
.Failure
translateCode :: Shell m => (Int -> Maybe a) -> Proc a -> m a Source #
Apply a function to non-0 exit codes to extract a result. If Nothing
is produced, the
is thrown.Failure
failWithStdErr :: Shell io => Proc a -> io a Source #
Capture the stderr of the proc, and attach it to any
exceptions that are thrown. The stderr is also forwarded to downstream
processes, or the inherited stderr handle. Note that capturing stderr
inherently requires that the stderr is accumulated in memory, so be
careful about processes that dump a lot of information.Failure
ignoreFailure :: (Functor m, Shell m) => Proc a -> m () Source #
Run a Proc
action, ignoring any Failure
exceptions.
This can be used to prevent a process from interrupting a whole pipeline.
>>>
false |> (sleep "0.1" >> echo 1)
*** Exception: Command `false` failed [exit 1] at CallStack (from HasCallStack): ...
>>>
(ignoreFailure false) |> (sleep "0.1" >> echo 1)
1
exitCode :: (Functor m, Shell m) => Proc a -> m Int Source #
Run a Proc
action returning the exit code of the process instead of
throwing an exception.
>>>
exitCode false
1
class ExecArg a where Source #
A class for things that can be converted to arguments on the command
line. The default implementation is to use show
.
Nothing
asArg :: a -> [ByteString] Source #
default asArg :: Show a => a -> [ByteString] Source #
asArgFromList :: [a] -> [ByteString] Source #
default asArgFromList :: Show a => [a] -> [ByteString] Source #
Instances
class Command a where Source #
A class for building up a command.
toArgs :: HasCallStack => [ByteString] -> a Source #
Instances
Command [ByteString] Source # | |
Defined in Shh.Internal toArgs :: [ByteString] -> [ByteString] Source # | |
Command [ByteString] Source # | |
Defined in Shh.Internal toArgs :: [ByteString0] -> [ByteString] Source # | |
a ~ () => Command (IO a) Source # | Commands can be executed directly in IO |
Defined in Shh.Internal toArgs :: [ByteString] -> IO a Source # | |
a ~ () => Command (Proc a) Source # | |
Defined in Shh.Internal toArgs :: [ByteString] -> Proc a Source # | |
(ExecArg b, Command a) => Command (b -> a) Source # | |
Defined in Shh.Internal toArgs :: [ByteString] -> b -> a Source # |
type Cmd = HasCallStack => forall a. Command a => a Source #
displayCommand :: Cmd -> [ByteString] Source #
This function turns a Cmd
into a list of
s.ByteString
>>>
displayCommand $ echo "Hello, world!"
["echo","Hello, world!"]
pathBinsAbs :: IO [FilePath] Source #
Get all uniquely named executables on your `$PATH` as absolute file names. The uniqueness is determined by the filename, and not the whole path. First one found wins.
findBinsIn :: [FilePath] -> IO [FilePath] Source #
Get all uniquely named executables from the list of directories. Returns a list of absolute file names.
exe :: (Command a, ExecArg str, HasCallStack) => str -> a Source #
Execute the given command. Further arguments can be passed in.
exe "ls" "-l"
NB: It is recommended that you use the template haskell functions to load
executables from your path. If you do it manually, it is recommended to
use withFrozenCallStack
from GHC.Stack
echo :: Cmd echo = withFrozenCallStack (exe "echo")
data ExecReference Source #
Specify how executables should be referenced.
Absolute | Find executables on PATH, but store their absolute path |
SearchPath | Always search on PATH |
rawExe :: String -> String -> Q [Dec] Source #
Template Haskell function to create a function from a path that will be called. This does not check for executability at compile time.
loadExeAs :: ExecReference -> String -> String -> Q [Dec] Source #
$(loadExeAs ref fnName executable)
defines a function called fnName
which executes the path in executable
. If executable
is an absolute path
it is used directly. If it is just an executable name, then it is searched
for in the PATH environment variable. If ref
is SearchPath
, the short
name is retained, and your PATH will be searched at runtime. If ref
is Absolute
, a executable name will be turned into an absolute path, which
will be used at runtime.
encodeIdentifier :: String -> String Source #
Takes a string, and makes a Haskell identifier out of it. If the string
is a path, the filename portion is used. The exact transformation is that
alphanumeric characters are unchanged, -
becomes _
, and '
is used to
escape all other characters. _
becomes '_
, .
becomes ''
and
anthing else is becomes a hex encoded number surrounded by '
characters.
Justification for changing -
to _
is that -
appears far more commonly
in executable names than _
does, and so we give it the more ergonomic
encoding.
>>>
encodeIdentifier "nix-shell"
"nix_shell"
>>>
encodeIdentifier "R"
"_R"
>>>
encodeIdentifier "x86_64-unknown-linux-gnu-gcc"
"x86'_64_unknown_linux_gnu_gcc"
>>>
encodeIdentifier "release.sh"
"release''sh"
loadEnv :: ExecReference -> Q [Dec] Source #
Scans your '$PATH' environment variable and creates a function for each
executable found. Binaries that would not create valid Haskell identifiers
are encoded using the
function.encodeIdentifier
checkExecutable :: FilePath -> IO Bool Source #
Test to see if an executable can be found either on the $PATH or absolute.
load :: ExecReference -> [FilePath] -> Q [Dec] Source #
Load the given executables into the program, checking their executability
and creating a function missingExecutables
to do a runtime check for their
availability. Uses the
function to create function
names.encodeIdentifier
loadAnnotated :: ExecReference -> (String -> String) -> [FilePath] -> Q [Dec] Source #
Same as load
, but allows you to modify the function names.
loadAnnotatedEnv :: ExecReference -> (String -> String) -> Q [Dec] Source #
Like loadEnv
, but allows you to modify the function name that would
be generated.
endBy :: ByteString -> ByteString -> [ByteString] Source #
Split a string separated by the provided separator. A trailing separator
is ignored, and does not produce an empty string. Compatible with the
output of most CLI programs, such as find -print0
.
>>>
endBy "\n" "a\nb\n"
["a","b"]
>>>
endBy "\n" "a\nb"
["a","b"]
>>>
endBy "\n" "a\nb\n\n"
["a","b",""]
loadFromBins :: [FilePath] -> Q [Dec] Source #
Load executables from the given directories appended with "/bin"
.
Useful for use with Nix.
loadAnnotatedFromDirs :: [FilePath] -> (String -> String) -> Q [Dec] Source #
Load executables from the given dirs, applying the given transformation to the filenames.
endBy0 :: ByteString -> [ByteString] Source #
Function that splits '0' separated list of strings. Useful in conjunction
with find . "-print0"
.
Helper class for variable number of arguments to cd
builtin.
Mimics the shell builtin "cd". Be careful using this function in a program, as it doesn't play well with multiple threads. Best to just use it in an interactive shell or for very simple transliterations of shell scripts.
xargs1 :: (NFData a, Monoid a) => ByteString -> (ByteString -> Proc a) -> Proc a Source #
xargs1 n f
runs f
for each item in the input separated by n
. Similar
to the standard xargs
utility, but you get to choose the separator, and it
only does one argument per command. Compare the following two lines, which
do the same thing.
>>>
printf "a\\0b" |> xargs "--null" "-L1" "echo" |> cat
a b>>>
printf "a\\0b" |> xargs1 "\0" echo |> cat
a b
One benefit of this method over the standard xargs
is that we can run
Haskell functions as well.
>>>
yes |> head "-n" 5 |> xargs1 "\n" (const $ pure $ Sum 1)
Sum {getSum = 5}
readInputP :: (NFData a, Shell io) => (ByteString -> Proc a) -> io a Source #
readInputEndByP :: (NFData a, Shell io) => ByteString -> ([ByteString] -> Proc a) -> io a Source #
Like
, but splits the input.readInputP
readInputEndBy0P :: (NFData a, Shell io) => ([ByteString] -> Proc a) -> io a Source #
Like
, but splits the input on 0 bytes.readInputP
readInputLinesP :: (NFData a, Shell io) => ([ByteString] -> Proc a) -> io a Source #
Like
, but splits the input on new lines.readInputP
withDuplicates :: Handle -> Handle -> Handle -> (Handle -> Handle -> Handle -> IO a) -> IO a Source #
Bracket three
shDup
withDuplicateNullInput :: Handle -> Handle -> (Handle -> Handle -> Handle -> IO a) -> IO a Source #
Bracket two
s and provide a null input handle.hDup
hDup :: Handle -> IO Handle Source #
Duplicate a
without trying to flush buffers. Only works on Handle
s.FileHandle
hDuplicate tries to "flush" read buffers by seeking backwards, which doesn't
work for streams/pipes. Since we are simulating a fork + exec
in
,
losing the buffers is actually the expected behaviour. (System.Process doesn't
attempt to flush the buffers).nativeProc
NB: An alternate solution that we could implement (even for System.Process forks) is to create a fresh pipe and spawn an async task to forward buffered content from the original handle if there is something in the buffer. My concern would be that it might be a performance hit that people aren't expecting.
Code basically copied from http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.Handle.html#hDuplicate with minor modifications.
dupHandleShh :: FilePath -> Handle -> Maybe (MVar Handle__) -> Handle__ -> Maybe HandleFinalizer -> IO Handle Source #
Helper function for duplicating a Handle
dupHandleShh_ :: (IODevice dev, BufferedIO dev, Typeable dev) => dev -> FilePath -> Maybe (MVar Handle__) -> Handle__ -> Maybe HandleFinalizer -> IO Handle Source #
Helper function for duplicating a Handle