packages feed

bencoding-lens (empty) → 0.1.0.0

raw patch · 7 files changed

+386/−0 lines, 7 filesdep +basedep +bencodingdep +bencoding-lens

Dependencies added: base, bencoding, bencoding-lens, bytestring, cryptonite, hspec, http-conduit, lens

Files

+ .gitignore view
@@ -0,0 +1,28 @@+.DS_Store+.idea+*.log+tmp/++dist+dist-*+cabal-dev+*.o+*.hi+*.hie+*.chi+*.chs.h+*.dyn_o+*.dyn_hi+.hpc+.hsenv+.cabal-sandbox/+cabal.sandbox.config+*.prof+*.aux+*.hp+*.eventlog+.stack-work/+cabal.project.local+cabal.project.local~+.HTF/+.ghc.environment.*
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for bencoding-lens++## 0.1.0.0 -- 2022-02-22++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) 2021-2022 Joseph Morag+All rights reserved.++Redistribution and use in source and binary forms, with or without modification,+are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+this list of conditions and the following disclaimer in the documentation and/or+other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors+may be used to endorse or promote products derived from this software without+specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,46 @@+# bencoding-lens++Law abiding lenses and traversals for+[bencoding](https://hackage.haskell.org/package/bencoding), ported from+[lens-aeson](https://hackage.haskell.org/package/lens-aeson-1.1.1). Works with+arbitrary bencoded data, although most examples in the wild are torrent files.+++``` haskell+{-# LANGUAGE OverloadedStrings #-}++import Control.Lens+import Data.BEncode+import Data.BEncode.Lens++import Network.HTTP.Simple -- from http-conduit+import Crypto.Hash -- from cryptonite++main = do+  -- Get the ubuntu 20.04 live server torrent file with http-conduit+  torrent <- getResponseBody <$> httpBS "https://releases.ubuntu.com/20.04/ubuntu-20.04.4-live-server-amd64.iso.torrent"+  -- Print some interesting bits of the torrent file++  -- Announce link+  print (torrent ^? key "announce") +  -- Just (BString "https://torrent.ubuntu.com/announce")++  -- Keys of the top level dict+  print (fmap fst (torrent ^@.. members))+  -- ["announce","announce-list","comment","created by","creation date","info"]++  -- Keys of the info dict+  print (fmap fst (torrent ^@.. key "info" . members))+  -- ["length","name","piece length","pieces"]++  -- Piece length+  print (torrent ^? key "info" . key "piece length" . _BInteger)+  -- Just 262144++  -- Compute the info hash (see definition below)+  print (torrent ^?! infoHash)+  -- b44a0e20fa5b7cecb77156333b4268dfd7c30afb+  +infoHash :: (AsBValue t) => Fold t (Digest SHA1)+infoHash = key "info" . to (hashlazy . encode)+```
+ bencoding-lens.cabal view
@@ -0,0 +1,58 @@+cabal-version:      >=1.10+name:               bencoding-lens+version:            0.1.0.0+synopsis:           Lenses for bencoded data.+description:        A port of lens-aeson for bencoding.+homepage:           https://github.com/jmorag/bencoding-lens+bug-reports:        https://github.com/jmorag/bencoding-lens/issues+license:            BSD3+license-file:       LICENSE+author:             Joseph Morag+maintainer:         Joseph Morag <jm@josephmorag.com>+copyright:          (c) 2021-2022, Joseph Morag+category:           Data+extra-source-files:+  .gitignore+  CHANGELOG.md+  README.md++build-type:         Simple+tested-with:+  GHC ==7.10.3+   || ==8.0.2+   || ==8.2.2+   || ==8.4.4+   || ==8.6.5+   || ==8.8.4+   || ==8.10.7+   || ==9.0.2+   || ==9.2.2++source-repository head+  type:     git+  location: https://github.com/jmorag/bencoding-lens++library+  default-language: Haskell2010+  hs-source-dirs:   src+  exposed-modules:  Data.BEncode.Lens+  ghc-options:      -Wall+  build-depends:+      base        >=4.4      && <4.18+    , bencoding   >=0.4.5.4  && <0.5+    , bytestring  >=0.10.4.1 && <0.12+    , lens        >=4        && <5.2++test-suite bencoding-lens-torrent-test+  default-language: Haskell2010+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  main-is: Torrent.hs+  build-depends:+      base        >=4.4      && <4.18+    , lens        >=4        && <5.2+    , bencoding+    , bencoding-lens+    , http-conduit+    , cryptonite+    , hspec
+ src/Data/BEncode/Lens.hs view
@@ -0,0 +1,191 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- |+-- Copyright :  (c) Joseph Morag 2021-2022+-- License   :  BSD3+-- Maintainer:  Joseph Morag <jm@josephmorag.com>+-- Stability :  experimental+-- Portability: non-portable+--+-- This module exports orphan instances for @'Ixed' 'BValue'@, @'Plated'+-- 'BValue'@, @'Ixed' 'BDictMap'@, @'Plated' 'BDictMap'@, @'Traversable'+-- 'BDictMap'@, @'FunctorWithIndex' 'BDictMap'@, @'FoldableWithIndex'+-- 'BDictMap'@, and @'TraversbaleWithIndex' 'BDictMap'@.+module Data.BEncode.Lens+  ( -- * Prisms+    AsBValue (..),++    -- * BDicts and BLists+    members,+    key,+    nth,+    values,+  )+where++import Control.Lens+import Data.BEncode+import Data.BEncode.BDict as BE+import Data.BEncode.Types+import qualified Data.ByteString as Strict+import qualified Data.ByteString.Lazy as Lazy++-- $setup+-- >>> import Control.Lens+-- >>> import Data.ByteString+-- >>> import Data.BEncode.BDict+-- >>> import Data.BEncode.Types+-- >>> :set -XOverloadedStrings++-- | Things that can be treated as a 'BValue'. Instances are provided for strict+-- and lazy 'ByteString' as well as 'BValue's themselves.+class AsBValue t where+  _BValue :: Prism' t BValue++  -- |+  -- >>> ("i3e" :: ByteString) ^? _BInteger+  -- Just 3+  _BInteger :: Prism' t BInteger+  _BInteger = _BValue . prism' BInteger (\case BInteger x -> Just x; _ -> Nothing)+  {-# INLINE _BInteger #-}++  -- |+  -- >>> ("0:" :: ByteString) ^? _BString+  -- Just ""+  --+  -- >>> ("4:spam" :: ByteString) ^? _BString+  -- Just "spam"+  _BString :: Prism' t BString+  _BString = _BValue . prism' BString (\case BString x -> Just x; _ -> Nothing)+  {-# INLINE _BString #-}++  -- |+  -- >>> ("le" :: ByteString) ^? _BList+  -- Just []+  --+  -- >>> ("l4:spam4:eggse" :: ByteString) ^? _BList == Just [BString "spam", BString "eggs"]+  -- True+  _BList :: Prism' t BList+  _BList = _BValue . prism' BList (\case BList x -> Just x; _ -> Nothing)+  {-# INLINE _BList #-}++  -- |+  -- >>> ("de" :: ByteString) ^? _BDict+  -- Just Nil+  --+  -- >>> ("d3:cow3:moo4:spam4:eggse" :: ByteString) ^? _BDict == Just (Cons "cow" (BString "moo") (Cons "spam" (BString "eggs") Nil))+  -- True+  _BDict :: Prism' t BDict+  _BDict = _BValue . prism' BDict (\case BDict x -> Just x; _ -> Nothing)+  {-# INLINE _BDict #-}++instance AsBValue BValue where+  _BValue = id+  {-# INLINE _BValue #-}++instance AsBValue Strict.ByteString where+  _BValue = prism' (view strict . encode) $ either (const Nothing) Just . decode+  {-# INLINE _BValue #-}++instance AsBValue Lazy.ByteString where+  _BValue = prism' encode $ either (const Nothing) Just . decode . view strict+  {-# INLINE _BValue #-}++-- |+-- >>> ("d3:cow3:moo4:spam4:eggse" :: ByteString) ^@.. members+-- [("cow",BString "moo"),("spam",BString "eggs")]+members :: AsBValue t => IndexedTraversal' BKey t BValue+members = _BDict . itraversed+{-# INLINE members #-}++-- |+-- >>> ("d3:cow3:moo4:spam4:eggse" :: ByteString) ^? key "cow"+-- Just (BString "moo")+key :: AsBValue t => BKey -> Traversal' t BValue+key k = _BDict . ix k+{-# INLINE key #-}++-- |+-- >>> ("li0ei1ee" :: ByteString) ^? nth 0+-- Just (BInteger 0)+nth :: AsBValue t => Int -> Traversal' t BValue+nth i = _BList . ix i+{-# INLINE nth #-}++-- |+-- >>> ("ll1:ae3:cow3:moo4:spam4:eggse" :: ByteString) ^.. values+-- [BList [BString "a"],BString "cow",BString "moo",BString "spam",BString "eggs"]+values :: AsBValue t => IndexedTraversal' Int t BValue+values = _BList . traversed+{-# INLINE values #-}++------------------------------------------------------------------------------+-- Orphan instances for lens library interop+------------------------------------------------------------------------------+instance Traversable BDictMap where+  traverse _ Nil = pure Nil+  traverse f (Cons k x xs) = Cons k <$> f x <*> traverse f xs++instance FoldableWithIndex BKey BDictMap++instance FunctorWithIndex BKey BDictMap++instance TraversableWithIndex BKey BDictMap where+  itraverse _ Nil = pure Nil+  itraverse f (Cons k x xs) = Cons k <$> f k x <*> itraverse f xs++type instance Index (BDictMap a) = BKey++type instance IxValue (BDictMap a) = a++type instance Index BValue = BKey++type instance IxValue BValue = BValue++instance At (BDictMap a) where+  at :: BKey -> Lens' (BDictMap a) (Maybe a)+  at k f m =+    f mv <&> \case+      Nothing -> maybe m (const (delete k m)) mv+      Just v' -> insert k v' m+    where+      mv = BE.lookup k m+  {-# INLINE at #-}++-- | Insert a key value pair into a BDictMap. Overwrites the value for an+-- existing key+insert :: BKey -> a -> BDictMap a -> BDictMap a+insert k v Nil = BE.singleton k v+insert k v bd@(Cons k' x xs)+  | k == k' = Cons k v xs+  | k < k' = Cons k v bd+  | otherwise = Cons k' x (insert k v xs)++-- | Delete a key from a BDictMap. Returns the BDictMap unchanged if the key is+-- not present.+delete :: BKey -> BDictMap a -> BDictMap a+delete _ Nil = Nil+delete k bd@(Cons k' x xs)+  | k == k' = xs+  | k > k' = bd+  | otherwise = Cons k' x (delete k xs)++instance Ixed (BDictMap a)++instance Ixed BValue where+  ix i f (BDict o) = BDict <$> ix i f o+  ix _ _ v = pure v+  {-# INLINE ix #-}++instance Plated BValue where+  plate f (BDict o) = BDict <$> traverse f o+  plate f (BList l) = BList <$> traverse f l+  plate _ xs = pure xs+  {-# INLINE plate #-}
+ test/Torrent.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE OverloadedStrings #-}++import Control.Lens+import Crypto.Hash+import Data.BEncode+import Data.BEncode.Lens+import Network.HTTP.Simple+import Test.Hspec++main = do+  -- Get the ubuntu 20.04 live server torrent file with http-conduit+  resp <- httpBS "https://releases.ubuntu.com/20.04/ubuntu-20.04.4-live-server-amd64.iso.torrent"+  let torrent = getResponseBody resp+  hspec do+    it "should get the corrent ubuntu announce string" do+      (torrent ^?! key "announce" . _BString) `shouldBe` "https://torrent.ubuntu.com/announce"+    it "should get the keys of the top level dict" do+      fmap fst (torrent ^@.. members) `shouldBe` ["announce", "announce-list", "comment", "created by", "creation date", "info"]++    it "should get the keys of the info dict" do+      fmap fst (torrent ^@.. key "info" . members) `shouldBe` ["length", "name", "piece length", "pieces"]++    it "should find the piece length" do+      (torrent ^?! key "info" . key "piece length" . _BInteger) `shouldBe` 262144++    it "should compute the info hash (see definition below)" do+      show (torrent ^?! infoHash) `shouldBe` "b44a0e20fa5b7cecb77156333b4268dfd7c30afb"++infoHash :: (AsBValue t) => Fold t (Digest SHA1)+infoHash = key "info" . to (hashlazy . encode)