marmalade-upload 0.6 → 0.7
raw patch · 10 files changed
+61/−406 lines, 10 filesdep −deepseqdep −processdep −unixPVP ok
version bump matches the API change (PVP)
Dependencies removed: deepseq, process, unix
API changes (from Hackage documentation)
- Web.Marmalade: verifyPackage :: String -> Marmalade ()
- Web.Marmalade.Magic: data MagicException
- Web.Marmalade.Magic: guessMimeType :: FilePath -> IO String
- Web.Marmalade.Magic: instance Exception MagicException
- Web.Marmalade.Magic: instance Show MagicException
- Web.Marmalade.Magic: instance Typeable MagicException
Files
- CHANGES.md +6/−0
- main.hs +28/−35
- marmalade-upload.cabal +3/−27
- src/Web/Marmalade.hs +4/−23
- src/Web/Marmalade/Magic.hs +0/−121
- src/Web/Marmalade/Magic/Native.hsc +0/−50
- test/magic-tests.hs +0/−60
- test/marmalade-tests.hs +20/−40
- test/resources/foo.el +0/−50
- test/resources/foo.tar binary
CHANGES.md view
@@ -1,3 +1,9 @@+0.7 (Apr 28, 2014)+==================++- Improve the help output and the semantics of `-V`/`--version`+- Remove mimetype verification+ 0.6 (Apr 21, 2014) ==================
main.hs view
@@ -30,10 +30,10 @@ import Control.Monad.IO.Class (liftIO) import Data.Version (showVersion) import Options.Applicative (Parser,execParser,- info,fullDesc,progDesc,- helper,argument,flag',long,short,str,metavar,help,- (<|>),(<$>),(<>),(<*>))-import System.Exit (ExitCode(ExitFailure),exitWith,exitSuccess)+ info,fullDesc,progDesc,infoOption,+ helper,argument,long,short,str,metavar,help,+ (<$>),(<>),(<*>))+import System.Exit (ExitCode(ExitFailure),exitWith) import System.IO (hPutStrLn,stderr) import Text.Printf (printf) @@ -112,40 +112,33 @@ exitException :: SomeException -> IO () exitException = exitFailure.show -data UploadArguments = UploadArguments { argUsername :: String- , argPackageFile :: String }--data Arguments = ShowVersion |- UploadPackage UploadArguments+data Arguments = Arguments { argUsername :: String+ , argPackageFile :: String } -arguments :: Parser Arguments-arguments = flag' ShowVersion (long "version" <>- short 'V' <>- help "Show version number and exit") <|>- (UploadPackage <$> uploadArguments)+parser :: Parser Arguments+parser =+ versionInfo <*> arguments where- uploadArguments = UploadArguments <$>- argument str (metavar "USERNAME" <> help "Marmalade username") <*>- argument str (metavar "PACKAGE" <> help "Package file")+ versionInfo = infoOption versionMessage (long "version" <>+ short 'V' <>+ help "Show version number and exit")+ arguments = Arguments <$>+ argument str (metavar "USERNAME" <> help "Marmalade username") <*>+ argument str (metavar "PACKAGE" <> help "Package file")+ versionMessage = unlines [(appName ++ " " ++ appVersion)+ ,"Copyright (C) 2014 Sebastian Wiesner."+ ,"You may redistribute marmalade-upload"+ ,"under the terms of the MIT/X11 license."] main :: IO () main = do- args <- execParser (info (helper <*> arguments)+ args <- execParser (info (helper <*> parser) (fullDesc <> progDesc "Upload a package to Marmalade"))- processArguments args- where- processArguments ShowVersion = putVersion >> exitSuccess- processArguments (UploadPackage uploadArgs) = do- (shallSaveToken, auth) <- getAuth (argUsername uploadArgs)- handle exitException $ runMarmalade appUserAgent auth $ do- (Username username, Token token) <- login- -- Save the token now- when shallSaveToken $- liftIO (K.setPassword (K.Service appService) (K.Username username) (K.Password token))- upload <- uploadPackage (argPackageFile uploadArgs)- liftIO (putStrLn (uploadMessage upload))- putVersion = do- putStrLn (appName ++ " " ++ appVersion)- putStrLn "Copyright (C) 2014 Sebastian Wiesner."- putStrLn "You may redistribute marmalade-upload"- putStrLn "under the terms of the MIT/X11 license."+ (shallSaveToken, auth) <- getAuth (argUsername args)+ handle exitException $ runMarmalade appUserAgent auth $ do+ (Username username, Token token) <- login+ -- Save the token now+ when shallSaveToken $+ liftIO (K.setPassword (K.Service appService) (K.Username username) (K.Password token))+ upload <- uploadPackage (argPackageFile args)+ liftIO (putStrLn (uploadMessage upload))
marmalade-upload.cabal view
@@ -1,5 +1,5 @@ name: marmalade-upload-version: 0.6+version: 0.7 synopsis: Upload packages to Marmalade description: Upload Emacs packages to the <http://marmalade-repo.org/ Marmalade> ELPA@@ -8,9 +8,7 @@ license: MIT license-file: LICENSE extra-source-files: README.md,- CHANGES.md,- test/resources/foo.el,- test/resources/foo.tar+ CHANGES.md author: Sebastian Wiesner maintainer: lunaryorn@gmail.com copyright: (C) 2014 Sebastian Wiesner@@ -27,7 +25,7 @@ source-repository this type: git location: https://github.com/lunaryorn/marmalade-upload.git- tag: 0.6+ tag: 0.7 flag LibMagic description: Use libmagic to determine the mimetypes of packages@@ -36,7 +34,6 @@ library hs-source-dirs: src/ exposed-modules: Web.Marmalade- Web.Marmalade.Magic ghc-options: -Wall build-depends: base >=4.6 && <4.8, mtl >=2.1 && <2.2,@@ -50,16 +47,6 @@ http-client >=0.3 && <0.4 default-language: Haskell2010 - if flag(LibMagic)- build-tools: hsc2hs- build-depends: unix >=2.6 && <2.8- extra-libraries: magic- cpp-options: -DWITH_LIBMAGIC- other-modules: Web.Marmalade.Magic.Native- else- build-depends: process >=1.1 && <1.3,- deepseq >=1.3 && <1.4- executable marmalade-upload main-is: main.hs ghc-options: -Wall@@ -69,17 +56,6 @@ keyring >=0.1 && <0.2, marmalade-upload default-language: Haskell2010--test-suite magic- type: exitcode-stdio-1.0- main-is: magic-tests.hs- hs-source-dirs: test/- build-depends: base >=4.6 && <4.8,- tasty >=0.8 && <0.9,- tasty-hunit >= 0.8 && <0.9,- marmalade-upload- default-language: Haskell2010- ghc-options: -Wall test-suite marmalade type: exitcode-stdio-1.0
src/Web/Marmalade.hs view
@@ -36,12 +36,10 @@ -- * Generic types , Message(..) -- * Package uploads- , verifyPackage,uploadPackage,Upload(..)+ , Upload(..), uploadPackage ) where -import qualified Web.Marmalade.Magic as Magic- import qualified Data.Aeson as JSON import qualified Data.ByteString.UTF8 as UTF8 import qualified Network as N@@ -49,7 +47,7 @@ import Control.Applicative (Applicative,(<$>)) import Control.Exception (Exception)-import Control.Monad (liftM,mzero,unless)+import Control.Monad (liftM,mzero) import Control.Monad.Catch (MonadThrow,MonadCatch,throwM) import Control.Monad.IO.Class (MonadIO,liftIO) import Control.Monad.State (StateT,MonadState,evalStateT,get,gets,put)@@ -233,29 +231,12 @@ status = C.responseStatus response message = fmap messageContents (JSON.decode' body) --- |Permitted package mimetypes.-packageMimeTypes :: [String]-packageMimeTypes = ["application/x-tar", "text/x-lisp"]---- |@'verifyPackage' package@ checks whether @package@ is a valid package--- object.------ Throw an error if @package@ does not exist, or is not a valid package.-verifyPackage :: String -> Marmalade ()-verifyPackage packageFile = do- -- Force early failure if the package doesn't exist- mimeType <- liftIO (Magic.guessMimeType packageFile)- unless (mimeType `elem` packageMimeTypes)- (throwM (MarmaladeInvalidPackage packageFile- (printf "invalid mimetype %s" mimeType)))- -- |@'uploadPackage' package@ uploads a @package@ file to Marmalade. ----- Return the result of the upload, or throw an error if @package@ is not a--- valid package, or if Marmalade refused to accept the upload.+-- Return the result of the upload, or throw an error if Marmalade refused to+-- accept the upload. uploadPackage :: FilePath -> Marmalade Upload uploadPackage packageFile = do- verifyPackage packageFile (Username username, Token token) <- login manager <- gets marmaladeManager request <- makeRequest "/v1/packages" >>=
− src/Web/Marmalade/Magic.hs
@@ -1,121 +0,0 @@--- Copyright (c) 2014 Sebastian Wiesner <lunaryorn@gmail.com>---- Permission is hereby granted, free of charge, to any person obtaining a copy--- of this software and associated documentation files (the "Software"), to deal--- in the Software without restriction, including without limitation the rights--- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell--- copies of the Software, and to permit persons to whom the Software is--- furnished to do so, subject to the following conditions:---- The above copyright notice and this permission notice shall be included in--- all copies or substantial portions of the Software.---- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR--- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,--- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE--- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER--- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,--- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN--- THE SOFTWARE.--{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}--module Web.Marmalade.Magic- ( MagicException- , guessMimeType)- where--import Control.Exception (Exception,throwIO,bracket)-import Data.Typeable (Typeable)--#ifdef WITH_LIBMAGIC-import qualified Web.Marmalade.Magic.Native as N-import Control.Monad (when)-import Data.Bits ((.|.))-import Foreign.C (CInt,peekCString)-import Foreign.ForeignPtr (ForeignPtr,newForeignPtr,withForeignPtr)-import Foreign.Ptr (nullPtr)-import System.IO.Error (catchIOError)-import System.Posix.IO (OpenMode(ReadOnly),openFd,closeFd,defaultFileFlags)-import System.Posix.Types (Fd(Fd))-#else-import Control.DeepSeq (rnf)-import Control.Exception (finally,evaluate)-import System.Exit(ExitCode(..))-import System.IO (IOMode(ReadMode),Handle,withBinaryFile,hGetContents,hClose)-import System.Process (CreateProcess(std_out,std_err,std_in),- StdStream(CreatePipe,UseHandle),- proc,createProcess,waitForProcess)-#endif--newtype MagicException = MagicException String- deriving Typeable--instance Show MagicException where- show (MagicException message) = message--instance Exception MagicException--#ifdef WITH_LIBMAGIC--type Magic = ForeignPtr ()--magicOpen :: CInt -> IO Magic-magicOpen flags = do- raw <- N.magic_open flags- when (raw == nullPtr) (throwIO (MagicException "Failed to allocate cookie"))- newForeignPtr N.magic_close raw--throwCurrentMagicError :: Magic -> IO a-throwCurrentMagicError magic = do- message <- withForeignPtr magic N.magic_error- if message == nullPtr- then throwIO (MagicException "Unknown error")- else peekCString message >>= throwIO.MagicException--magicDescription :: Magic -> Fd -> IO String-magicDescription magic (Fd fd) = do- buffer <- withForeignPtr magic $ \ptr -> N.magic_descriptor ptr fd- when (buffer == nullPtr) (throwCurrentMagicError magic)- peekCString buffer--withBinaryFileFd :: FilePath -> OpenMode -> (Fd -> IO a) -> IO a-withBinaryFileFd fileName mode =- bracket (openFd fileName mode Nothing defaultFileFlags) closeFdSafe- where- closeFdSafe fd = catchIOError (closeFd fd) (const $ return ())--guessMimeType :: FilePath -> IO String-guessMimeType fileName = do- cookie <- magicOpen (N.magicSymlink .|. N.magicMimeType .|. N.magicError)- withForeignPtr cookie ((flip N.magic_load) nullPtr)- withBinaryFileFd fileName ReadOnly (magicDescription cookie)--#else--hGuessMimeType :: Handle -> IO String-hGuessMimeType handle =- bracket (createProcess process) closeHandles $ \(_, Just oh, Just eh, proch) -> do- stdout <- hGetContents oh- stderr <- hGetContents eh- -- Force reading of the process handles- evaluate $ rnf stdout- evaluate $ rnf stderr- exitStatus <- waitForProcess proch- case exitStatus of- ExitSuccess -> return (head (lines stdout))- ExitFailure _ -> throwIO (MagicException (stdout ++ stderr))- where closeHandles (_, Just outh, Just errh, _) =- finally (hClose outh) (hClose errh)- closeHandles _ = return ()- process =- (proc "file" ["--brief", "--mime-type", "-"]) {- std_out = CreatePipe,- std_err = CreatePipe,- std_in = UseHandle handle }--guessMimeType :: FilePath -> IO String-guessMimeType fileName = withBinaryFile fileName ReadMode hGuessMimeType--#endif
− src/Web/Marmalade/Magic/Native.hsc
@@ -1,50 +0,0 @@--- Copyright (c) 2014 Sebastian Wiesner <lunaryorn@gmail.com>---- Permission is hereby granted, free of charge, to any person obtaining a copy--- of this software and associated documentation files (the "Software"), to deal--- in the Software without restriction, including without limitation the rights--- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell--- copies of the Software, and to permit persons to whom the Software is--- furnished to do so, subject to the following conditions:---- The above copyright notice and this permission notice shall be included in--- all copies or substantial portions of the Software.---- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR--- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,--- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE--- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER--- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,--- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN--- THE SOFTWARE.--{-# LANGUAGE ForeignFunctionInterface #-}--module Web.Marmalade.Magic.Native where--#include <magic.h>--import Foreign.C (CInt(..),CString)-import Foreign.Ptr (Ptr,FunPtr)--#{enum CInt, ,- magicSymlink = MAGIC_SYMLINK,- magicMimeType = MAGIC_MIME_TYPE,- magicError = MAGIC_ERROR}--type Magic = Ptr ()--foreign import ccall unsafe "magic.h magic_open"- magic_open :: CInt -> IO Magic--foreign import ccall unsafe "magic.h magic_load"- magic_load :: Magic -> CString -> IO ()--foreign import ccall unsafe "magic.h &magic_close"- magic_close :: FunPtr (Magic -> IO ())--foreign import ccall unsafe "magic.h magic_error"- magic_error :: Magic -> IO CString--foreign import ccall unsafe "magic.h magic_descriptor"- magic_descriptor :: Magic -> CInt -> IO CString
− test/magic-tests.hs
@@ -1,60 +0,0 @@--- Copyright (c) 2014 Sebastian Wiesner <lunaryorn@gmail.com>---- Permission is hereby granted, free of charge, to any person obtaining a copy--- of this software and associated documentation files (the "Software"), to deal--- in the Software without restriction, including without limitation the rights--- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell--- copies of the Software, and to permit persons to whom the Software is--- furnished to do so, subject to the following conditions:---- The above copyright notice and this permission notice shall be included in--- all copies or substantial portions of the Software.---- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR--- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,--- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE--- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER--- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,--- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN--- THE SOFTWARE.--import Web.Marmalade.Magic (guessMimeType)--import Control.Exception (handleJust)-import Control.Monad (guard)-import System.IO.Error (isDoesNotExistError)-import Test.Tasty-import Test.Tasty.HUnit--mimeTypeOfEmacsLispFile :: TestTree-mimeTypeOfEmacsLispFile = testCase "Emacs Lisp file" $ do- mimeType <- guessMimeType "test/resources/foo.el"- mimeType @?= "text/x-lisp"--mimeTypeOfTarFile :: TestTree-mimeTypeOfTarFile = testCase "TAR file" $ do- mimeType <- guessMimeType "test/resources/foo.tar"- mimeType @?= "application/x-tar"--mimeTypeOfTextFile :: TestTree-mimeTypeOfTextFile = testCase "Text file" $ do- mimeType <- guessMimeType "README.md"- mimeType @?= "text/plain"--fileDoesNotExist :: TestTree-fileDoesNotExist = testCase "File does not exist" $- handleJust (guard.isDoesNotExistError) (const $ return ()) $ do- _ <- guessMimeType "thisFileDoesNotExist"- assertFailure "Expected IO error not thrown"--tests :: TestTree-tests = testGroup "Mimetype guessing"- [- mimeTypeOfEmacsLispFile- , mimeTypeOfTarFile- , mimeTypeOfTextFile- , fileDoesNotExist- ]--main :: IO ()-main = defaultMain tests
test/marmalade-tests.hs view
@@ -24,10 +24,9 @@ import Web.Marmalade import qualified Data.Aeson as JSON-import Control.Exception (handleJust)-import Control.Monad (guard)+import Control.Exception (handle,handleJust) import Data.Version (showVersion)-import System.IO.Error (isDoesNotExistError)+import System.IO.Error (isDoesNotExistError,ioeGetFileName) import Test.Tasty import Test.Tasty.HUnit@@ -36,37 +35,11 @@ testUserAgent = "marmalade-upload-tests/" ++ showVersion version testAuth :: Auth-testAuth = BasicAuth (Username "marmalade-upload-test-user") (return "invalid password")+testAuth = TokenAuth (Username "marmalade-upload-test-user") (Token "test-token") runMarmaladeTest :: Marmalade a -> IO a runMarmaladeTest = runMarmalade testUserAgent testAuth -verifyEmacsLispPackage :: TestTree-verifyEmacsLispPackage = testCase "Emacs Lisp package" $ runMarmaladeTest $- verifyPackage "test/resources/foo.el"--verifyTarPackage :: TestTree-verifyTarPackage = testCase "Tar package" $ runMarmaladeTest $- verifyPackage "test/resources/foo.tar"--verifyNonExistingPackage :: TestTree-verifyNonExistingPackage =- testCase "Package file does not exist" $- handleJust (guard.isDoesNotExistError) (const $ return ()) $ do- runMarmaladeTest (verifyPackage "thisFileDoesNotExist")- assertFailure "Expected IO error not thrown"--verifyInvalidPackage :: TestTree-verifyInvalidPackage =- testCase "Invalid package file" $- handleJust invalidPackageMessage assertInvalidPackageMessage $ do- runMarmaladeTest (verifyPackage "README.md")- assertFailure "Expected MarmaladeError not thrown"- where- invalidPackageMessage (MarmaladeInvalidPackage _ msg) = Just msg- invalidPackageMessage _ = Nothing- assertInvalidPackageMessage msg = msg @?= "invalid mimetype text/plain"- decodeToken :: TestTree decodeToken = testCase "Decode Token JSON" $ case JSON.decode' "{\"token\": \"fooBar\"}" of@@ -87,36 +60,43 @@ loginWithToken :: TestTree loginWithToken = testCase "Login with token" $ do- (Username username, Token token) <- getLogin+ (Username username, Token token) <- runMarmaladeTest login username @?= "marmalade-upload-test-user" token @?= "test-token"- where- auth = TokenAuth (Username "marmalade-upload-test-user") (Token "test-token")- getLogin = runMarmalade testUserAgent auth login invalidUsernameAndPassword :: TestTree invalidUsernameAndPassword = testCase "Invalid username and password" $ handleJust badRequestWithMessage assertMessage $ do- _ <- runMarmaladeTest login+ _ <- runMarmalade testUserAgent auth login assertFailure "Expected MarmaladeBadRequest not thrown" where+ auth = BasicAuth (Username "marmalade-upload-test-user") (return "test-password") badRequestWithMessage (MarmaladeBadRequest msg) = msg badRequestWithMessage _ = Nothing assertMessage msg = msg @?= "Username or password invalid" +uploadNonExistingPackage :: TestTree+uploadNonExistingPackage =+ testCase "Package file does not exist" $+ handle assertDoesNotExistError $ do+ _ <- runMarmaladeTest (uploadPackage fileName)+ assertFailure "Expected IOError not thrown"+ where+ fileName = "/this-file-does-not-exist"+ assertDoesNotExistError err = do+ isDoesNotExistError err @? ("Unexpected error type: " ++ show err)+ ioeGetFileName err @?= Just fileName+ tests :: TestTree tests = testGroup "Marmalade API" [- testGroup "verifyPackage" [ verifyEmacsLispPackage- , verifyTarPackage- , verifyNonExistingPackage- , verifyInvalidPackage ]- , testGroup "JSON decoding" [ decodeToken+ testGroup "JSON decoding" [ decodeToken , decodeMessage , decodeUpload ] , testGroup "login" [ loginWithToken , invalidUsernameAndPassword ]+ , testGroup "upload" [ uploadNonExistingPackage ] ] main :: IO ()
− test/resources/foo.el
@@ -1,50 +0,0 @@-;;; foo.el --- marmalade-upload test dummy -*- lexical-binding: t; -*---;; Copyright (C) 2014 Sebastian Wiesner--;; Author: Sebastian Wiesner <lunaryorn@gmail.com>-;; Package-Version: 0.1--;; This file is not part of GNU Emacs.--;; Permission is hereby granted, free of charge, to any person obtaining a copy-;; of this software and associated documentation files (the "Software"), to deal-;; in the Software without restriction, including without limitation the rights-;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell-;; copies of the Software, and to permit persons to whom the Software is-;; furnished to do so, subject to the following conditions:--;; The above copyright notice and this permission notice shall be included in-;; all copies or substantial portions of the Software.--;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR-;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,-;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE-;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER-;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,-;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE-;; SOFTWARE.--;; This program is free software; you can redistribute it and/or modify-;; it under the terms of the GNU General Public License as published by-;; the Free Software Foundation, either version 3 of the License, or-;; (at your option) any later version.--;; This program is distributed in the hope that it will be useful,-;; but WITHOUT ANY WARRANTY; without even the implied warranty of-;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-;; GNU General Public License for more details.--;; You should have received a copy of the GNU General Public License-;; along with this program. If not, see <http://www.gnu.org/licenses/>.--;;; Commentary:--;;; Code:--(defun foo ()- (message "FOO"))--(provide 'foo)--;;; foo.el ends here
− test/resources/foo.tar
binary file changed (4096 → absent bytes)