You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
5.3 KiB
247 lines
5.3 KiB
module Zip exposing
|
|
( Zip
|
|
, fromBytes
|
|
, entries
|
|
, getEntry
|
|
, count
|
|
, isEmpty
|
|
, empty
|
|
, fromEntries
|
|
, insert
|
|
, filter
|
|
, toBytes
|
|
)
|
|
|
|
{-| Work with [Zip archives](https://en.wikipedia.org/wiki/ZIP_file_format).
|
|
|
|
@docs Zip
|
|
|
|
|
|
# Read an archive
|
|
|
|
@docs fromBytes
|
|
|
|
|
|
# Access the content
|
|
|
|
Once you have a `Zip`, you can use it to access its files and directories.
|
|
|
|
Use the [Zip.Entry module](./Zip-Entry#Entry) to do read their content and metadata.
|
|
|
|
@docs entries
|
|
@docs getEntry
|
|
@docs count
|
|
@docs isEmpty
|
|
|
|
|
|
# Build an archive
|
|
|
|
You can alter archives or create your own.
|
|
|
|
Checkout the [Build section](./Zip-Entry#build) of the `Zip.Entry` module to learn how to make your own entries.
|
|
|
|
@docs empty
|
|
@docs fromEntries
|
|
@docs insert
|
|
@docs filter
|
|
|
|
|
|
## ...and when it's ready
|
|
|
|
@docs toBytes
|
|
|
|
-}
|
|
|
|
import Bytes exposing (Bytes)
|
|
import Internal.Decode exposing (readDirectory)
|
|
import Internal.Encode exposing (writeArchive)
|
|
import Internal.Format exposing (Entry)
|
|
import Zip.Entry as Entry
|
|
|
|
|
|
{-| Represents a Zip archive.
|
|
|
|
An archive is comprised of [entries](./Zip-Entry#Entry) which represent files -that may be compressed- and directories.
|
|
|
|
-}
|
|
type Zip
|
|
= Zip (List Entry)
|
|
|
|
|
|
{-| Read a `Zip` from `Bytes`.
|
|
|
|
If you have [an uploaded File](https://package.elm-lang.org/packages/elm/file/latest/File) of an archive,
|
|
you can use [`File.toBytes`](https://package.elm-lang.org/packages/elm/file/latest/File#toBytes) to read it:
|
|
|
|
import File exposing (File)
|
|
import Task exposing (Task)
|
|
import Zip exposing (Zip)
|
|
|
|
type Msg
|
|
= GotZip (Maybe Zip)
|
|
|
|
readArchive : File -> Cmd Msg
|
|
readArchive file =
|
|
file
|
|
|> File.toBytes
|
|
|> Task.map Zip.fromBytes
|
|
|> Task.perform GotZip
|
|
|
|
You can also get `Bytes` from somewhere else, such as [an HTTP request](https://package.elm-lang.org/packages/elm/http/latest/Http#expectBytes),
|
|
or even from [within another archive](./Zip-Entry#toBytes).
|
|
|
|
-}
|
|
fromBytes : Bytes -> Maybe Zip
|
|
fromBytes bytes =
|
|
readDirectory bytes |> Maybe.map Zip
|
|
|
|
|
|
{-| Write a `Zip` to `Bytes`.
|
|
|
|
From here, you can [download the archive](https://package.elm-lang.org/packages/elm/file/latest/File-Download#bytes),
|
|
[upload it to a server](https://package.elm-lang.org/packages/elm/http/latest/Http#bytesBody>), etc.
|
|
|
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
|
update msg model =
|
|
case msg of
|
|
DownloadArchive ->
|
|
( model
|
|
, model.zip
|
|
|> Zip.toBytes
|
|
|> File.Download.bytes "archive.zip" "application/zip"
|
|
)
|
|
|
|
-}
|
|
toBytes : Zip -> Bytes
|
|
toBytes (Zip allEntries) =
|
|
writeArchive allEntries
|
|
|
|
|
|
{-| Get all [entries](./Zip-Entry#Entry) in the archive.
|
|
|
|
allEntries =
|
|
Zip.entries zip
|
|
|
|
Files and directories get their own entries.
|
|
|
|
If you only care about one kind, you can use the [`Zip.Entry.isDirectory`](./Zip-Entry#isDirectory) function to filter them:
|
|
|
|
allFiles =
|
|
zip
|
|
|> Zip.entries
|
|
|> List.filter (not << Entry.isDirectory)
|
|
|
|
-}
|
|
entries : Zip -> List Entry
|
|
entries (Zip allEntries) =
|
|
allEntries
|
|
|
|
|
|
{-| Get an [entry](./Zip-Entry#Entry) by its absolute path.
|
|
|
|
zip |> Zip.getEntry "versions/v1.txt"
|
|
|
|
`Nothing` is returned if no entry matches the path exactly.
|
|
|
|
Directory entries are typically stored in the archive with a slash at the end:
|
|
|
|
zip |> Zip.getEntry "versions" == Nothing
|
|
|
|
zip |> Zip.getEntry "versions/" == Just (Entry(..))
|
|
|
|
-}
|
|
getEntry : String -> Zip -> Maybe Entry
|
|
getEntry path =
|
|
entries >> find (Entry.path >> (==) path)
|
|
|
|
|
|
{-| Count the number of entries in an archive.
|
|
-}
|
|
count : Zip -> Int
|
|
count =
|
|
entries >> List.length
|
|
|
|
|
|
{-| Determine if an archive is empty.
|
|
-}
|
|
isEmpty : Zip -> Bool
|
|
isEmpty =
|
|
entries >> List.isEmpty
|
|
|
|
|
|
{-| An empty archive with no entries.
|
|
|
|
From here, you can use [`insert`](#insert) to add some entries.
|
|
|
|
-}
|
|
empty : Zip
|
|
empty =
|
|
Zip []
|
|
|
|
|
|
{-| Create an archive from a list of entries.
|
|
-}
|
|
fromEntries : List Entry -> Zip
|
|
fromEntries =
|
|
Zip
|
|
|
|
|
|
{-| Add a new entry to the archive.
|
|
|
|
This function replaces entries with the same path. You can conditionally add it by checking existence with the [`getEntry`](#getEntry) function:
|
|
|
|
case zip |> Zip.getEntry path of
|
|
Nothing ->
|
|
-- Entry does not exist, create and add it
|
|
zip |> Zip.insert (createEntry ())
|
|
|
|
Just _ ->
|
|
-- Entry already exists, leave archive as it is
|
|
zip
|
|
|
|
-}
|
|
insert : Entry -> Zip -> Zip
|
|
insert entry (Zip currentEntries) =
|
|
currentEntries
|
|
|> List.filter (Entry.path >> (/=) (Entry.path entry))
|
|
|> (::) entry
|
|
|> Zip
|
|
|
|
|
|
{-| Only keep entries that pass a given test.
|
|
|
|
|
|
### Examples
|
|
|
|
Remove entries by path:
|
|
|
|
filter (\entry -> Entry.path entry /= "sample/version.json") zip
|
|
|
|
Keep all files under 1MB:
|
|
|
|
filter (\entry -> Entry.extractedSize entry < 1048576) zip
|
|
|
|
Keep only `.txt` files:
|
|
|
|
filter (Entry.path >> String.endsWith ".txt") zip
|
|
|
|
-}
|
|
filter : (Entry -> Bool) -> Zip -> Zip
|
|
filter check (Zip currentEntries) =
|
|
currentEntries
|
|
|> List.filter check
|
|
|> Zip
|
|
|
|
|
|
find : (a -> Bool) -> List a -> Maybe a
|
|
find check list =
|
|
case list of
|
|
[] ->
|
|
Nothing
|
|
|
|
item :: tail ->
|
|
if check item then
|
|
Just item
|
|
|
|
else
|
|
find check tail
|
|
|