lens-regex (empty) → 0.1.0
raw patch · 11 files changed
+461/−0 lines, 11 filesdep +arraydep +basedep +directorysetup-changed
Dependencies added: array, base, directory, doctest, filepath, lens, lens-regex, regex-base, regex-posix, template-haskell
Files
- .gitignore +26/−0
- .travis.yml +55/−0
- CHANGELOG.md +3/−0
- LICENSE +26/−0
- README.md +3/−0
- Setup.hs +2/−0
- lens-regex.cabal +66/−0
- sample/sample.hs +81/−0
- src/Text/Regex/Lens.hs +125/−0
- src/Text/Regex/Quote.hs +45/−0
- tests/doctests.hs +29/−0
+ .gitignore view
@@ -0,0 +1,26 @@+# General+\#*#+.*~+*~+.#*+*.swp+.DS_Store+.gdb_history+TAGS+# Object files+*.a+*.o+*.so+*.hi+*.p_hi+a.out+# autotool+autom4te.cache+stamp-h1+# misc+*.sqlite+Main+dist/+cabal-dev/+cabal.sandbox.config+.cabal-sandbox
+ .travis.yml view
@@ -0,0 +1,55 @@+language: haskell++env:+ matrix:+ - GHCVER=7.4.2+ - GHCVER=7.6.3+ - GHCVER=7.8.4+ - GHCVER=7.10.1+ - GHCVER=head++matrix:+ allow_failures:+ - env: GHCVER=head++before_install:+ - |+ if [ $GHCVER = `ghc --numeric-version` ]; then+ echo "use system ghc: `which ghc` `ghc --numeric-version`"+ else+ travis_retry sudo add-apt-repository -y ppa:hvr/ghc+ travis_retry sudo apt-get update+ travis_retry sudo apt-get install -y --force-yes cabal-install-1.22 ghc-$GHCVER+ export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/1.22/bin:$PATH+ echo "use ppa:hvr/ghc: `which ghc` `ghc --numeric-version`"+ fi++install:+ - export PATH=$HOME/.cabal/bin:$PATH+ - which cabal+ - travis_retry cabal update+ - cabal install --only-dependencies --enable-tests+ - ghc-pkg list++script:+ - which cabal+ - cabal configure --enable-tests -fbuild-samples+ - cabal build+ - cabal test --show-details=always++ # tests source distribution package+ - cabal sdist+ - |+ PKGNAME="$(cabal info . | awk '/^\*/{print $2}')"+ SRCTGZ="${PKGNAME}.tar.gz"+ cd dist/+ if [ -f "$SRCTGZ" ]; then+ tar xvzf "$SRCTGZ"+ cd "$PKGNAME"+ NG=$(git ls-tree HEAD --full-tree -r | while read perm blob hash name; do [ -e "$name" ] || echo NG "$name"; done)+ if [ -n "$NG" ]; then+ echo "Missing files:"+ echo $NG+ exit 1+ fi+ fi
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+## 0.1.0++* Initial release
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (C) 2015, Takahiro Himura++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++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+OWNER 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,3 @@+# lens-regex: Lens powered regular expression #++[](https://travis-ci.org/himura/lens-regex)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lens-regex.cabal view
@@ -0,0 +1,66 @@+name: lens-regex+version: 0.1.0+synopsis: Lens powered regular expression+description: Lens powered regular expression+homepage: https://github.com/himura/lens-regex+license: BSD3+license-file: LICENSE+author: Takahiro HIMURA+maintainer: Takahiro HIMURA <taka@himura.jp>+copyright: Copyright (C) 2015 Takahiro HIMURA+category: Text+build-type: Simple+cabal-version: >=1.10++extra-source-files:+ .gitignore+ .travis.yml+ README.md+ CHANGELOG.md+ sample/*.hs++source-repository head+ type: git+ location: https://github.com/himura/lens-regex.git++flag build-samples+ description: build samples+ default: False++library+ exposed-modules: Text.Regex.Lens+ Text.Regex.Quote+ build-depends: base >= 4.5 && < 5+ , array+ , lens >= 4 && < 5+ , regex-base+ , template-haskell+ hs-source-dirs: src+ ghc-options: -Wall+ default-language: Haskell2010++executable sample+ main-is: sample.hs+ hs-source-dirs: sample++ if !flag(build-samples)+ buildable: False+ else+ build-depends: base >= 4.5 && < 5+ , lens+ , lens-regex+ , regex-posix+ ghc-options: -Wall+ default-language: Haskell2010++test-suite doctests+ type: exitcode-stdio-1.0+ main-is: doctests.hs+ hs-source-dirs: tests++ build-depends: base+ , directory+ , doctest+ , filepath+ , regex-posix+ default-language: Haskell2010
+ sample/sample.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE QuasiQuotes #-}++{-++OUTPUT:++##################+## Target String:+target1 = "hoge00 fuga hoge01 neko hoge02"++## Example: target1 ^.. regex [r|hoge[0-9]+|]+[MatchPart {_matchedString = "hoge00", _captures = []},MatchPart {_matchedString = "hoge01", _captures = []},MatchPart {_matchedString = "hoge02", _captures = []}]++## Example: target1 ^.. regex [r|hoge[0-9]+|] . matchedString+["hoge00","hoge01","hoge02"]++## Example: target1 ^? regex [r|hoge[0-9]+|] . index 1 . matchedString+Just "hoge01"++## Example: target1 ^? regex [r|hoge[0-9]+|] . index 3 . matchedString+Nothing++## Example: target1 & regex [r|hoge[0-9]+|] . matchedString .~ "HOGE"+"HOGE fuga HOGE neko HOGE"++## Example: target1 & regex [r|hoge[0-9]+|] .index 1 . matchedString .~ "HOGE"+"hoge00 fuga HOGE neko hoge02"++## Example: target1 & regex [r|hoge[0-9]+|] .index 1 . matchedString %~ (\s -> "<<" ++ s ++ ">>")+"hoge00 fuga <<hoge01>> neko hoge02"+##################+## Target String:+target2 = "<img src=\"/image/shinku0721.jpg\" alt=\"shinku birthday\"><img src=\"/image/shinku141.jpg\">"++## Example: target2 ^.. regex [r|<img src="([^"]+)"[^>]*>|] . captures . traversed . index 0+["/image/shinku0721.jpg","/image/shinku141.jpg"]++-}++module Main where++import Control.Lens+import Text.Regex.Lens+import Text.Regex.Posix+import Text.Regex.Quote++main :: IO ()+main = do+ let target1 = "hoge00 fuga hoge01 neko hoge02"+ putStrLn "##################"+ putStrLn "## Target String:"+ putStrLn $ "target1 = " ++ show target1++ putStrLn "\n## Example: target1 ^.. regex [r|hoge[0-9]+|]"+ print $ target1 ^.. regex [r|hoge[0-9]+|]++ putStrLn "\n## Example: target1 ^.. regex [r|hoge[0-9]+|] . matchedString"+ print $ target1 ^.. regex [r|hoge[0-9]+|] . matchedString++ putStrLn "\n## Example: target1 ^? regex [r|hoge[0-9]+|] . index 1 . matchedString"+ print $ target1 ^? regex [r|hoge[0-9]+|] . index 1 . matchedString++ putStrLn "\n## Example: target1 ^? regex [r|hoge[0-9]+|] . index 3 . matchedString"+ print $ target1 ^? regex [r|hoge[0-9]+|] . index 3 . matchedString++ putStrLn "\n## Example: target1 & regex [r|hoge[0-9]+|] . matchedString .~ \"HOGE\""+ print $ target1 & regex [r|hoge[0-9]+|] . matchedString .~ "HOGE"++ putStrLn "\n## Example: target1 & regex [r|hoge[0-9]+|] .index 1 . matchedString .~ \"HOGE\""+ print $ target1 & regex [r|hoge[0-9]+|] . index 1 . matchedString .~ "HOGE"++ putStrLn "\n## Example: target1 & regex [r|hoge[0-9]+|] .index 1 . matchedString %~ (\\s -> \"<<\" ++ s ++ \">>\")"+ print $ target1 & regex [r|hoge[0-9]+|] . index 1 . matchedString %~ (\s -> "<<" ++ s ++ ">>")++ let target2 = "<img src=\"/image/shinku0721.jpg\" alt=\"shinku birthday\"><img src=\"/image/shinku141.jpg\">"+ putStrLn "##################"+ putStrLn "## Target String:"+ putStrLn $ "target2 = " ++ show target2++ putStrLn "\n## Example: target2 ^.. regex [r|<img src=\"([^\"]+)\"[^>]*>|] . captures . traversed . index 0"+ print $ target2 ^.. regex [r|<img src="([^"]+)"[^>]*>|] . captures . traversed . index 0
+ src/Text/Regex/Lens.hs view
@@ -0,0 +1,125 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TemplateHaskell #-}++module Text.Regex.Lens+ ( MatchPart(..)+ , matchedString+ , captures++ , regex+ , regex'+ , matched+ , matched'+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative+import Data.Monoid+#endif++import Control.Lens+import qualified Data.Array as A+import Text.Regex.Base++-- $setup+-- >>> import Text.Regex.Quote+-- >>> import Text.Regex.Posix+-- >>> :set -XQuasiQuotes++type RegexResult text = [RegexPartialResult text]+type RegexPartialResult text = Either text (MatchPart text)++data MatchPart text = MatchPart+ { _matchedString :: text+ , _captures :: [text]+ } deriving Show+makeLensesFor [("_matchedString", "matchedString")] ''MatchPart+makeLensesWith (lensRulesFor [("_captures", "captures")] & generateUpdateableOptics .~ False) ''MatchPart++-- | An indexed Traversal for matched part with regexp.+--+-- >>> "foo bar baz" ^? regex [r|b.*r|]+-- Just (MatchPart {_matchedString = "bar", _captures = []})+--+-- >>> "foo bar baz" ^? regex [r|hoge|]+-- Nothing+--+-- You can access to the matched string by using `matchedString`:+--+-- >>> "foo bar baz" ^? regex [r|b.*r|] . matchedString+-- Just "bar"+--+-- Multiple result:+--+-- >>> "foo bar baz" ^.. regex [r|b[^ ]+|] . matchedString+-- ["bar","baz"]+--+-- Replace:+--+-- >>> "foo bar baz" & regex [r|b[^ ]+|] . matchedString .~ "nya"+-- "foo nya nya"+--+-- Indexing:+--+-- >>> "foo bar baz" ^.. regex [r|b[^ ]+|] . index 1 . matchedString+-- ["baz"]+--+-- >>> "foo bar baz" & regex [r|b[^ ]+|] . index 1 . matchedString .~ "nya"+-- "foo bar nya"+--+-- Captures:+--+-- >>> "foo00 bar01 baz02" ^.. regex [r|([a-z]+)([0-9]+)|] . captures+-- [["foo","00"],["bar","01"],["baz","02"]]+--+-- >>> "foo00 bar01 baz02" ^.. regex [r|([a-z]+)([0-9]+)|] . captures . traversed . index 1+-- ["00","01","02"]+--+-- /Note/: This is /not/ a legal Traversal, unless you are very careful not to invalidate the predicate on the target.+-- For example, if you replace the matched part with a string which is not match with the regex, the second 'Traversal' law is violated.+--+-- @+-- let l = regex [r|t.*t|] . matchedString+-- 'Control.Lens.Setter.over' l (++ "peta") '.' 'Control.Lens.Setter.over' l (++ "nya") '/=' 'Control.Lens.Setter.over' l ((++ "peta") . (++ "nya"))+-- 'Control.Lens.Setter.over' l (++ "put") '.' 'Control.Lens.Setter.over' l (++ "hot") '==' 'Control.Lens.Setter.over' l ((++ "put") . (++ "hot"))+-- @+regex :: (RegexLike regex text, Monoid text)+ => regex -- ^ compiled regular expression+ -> IndexedTraversal' Int text (MatchPart text)+regex pat = regex' pat . matched++regex' :: (RegexLike regex text, Monoid text) => regex -> Lens' text (RegexResult text)+regex' pat f target = fromRegexResult <$> f (toRegexResult pat target)++matched :: (Indexable Int p, Applicative f)+ => p (MatchPart text) (f (MatchPart text)) -> RegexResult text -> f (RegexResult text)+matched = conjoined matched' (indexing matched')++matched' :: Traversal' (RegexResult text) (MatchPart text)+matched' f target = go target+ where+ go [] = pure []+ go ((Left x):xs) = ((Left x):) <$> go xs+ go ((Right x):xs) = (:) <$> (Right <$> f x) <*> go xs++toRegexResult :: RegexLike regex text => regex -> text -> (RegexResult text)+toRegexResult pat target = go 0 $ matchAll pat target+ where+ go pos [] = [Left (after pos target)]+ go pos (m:ms) =+ if posDiff > 0+ then Left (extract (pos, posDiff) target) : cont+ else cont+ where+ (pos', len) = m A.! 0+ posDiff = pos' - pos+ (ms0:mss) = map (flip extract target) $ A.elems m+ cont = Right (MatchPart ms0 mss) : go (pos' + len) ms++fromRegexResult :: Monoid text => (RegexResult text) -> text+fromRegexResult = mconcat . map toStr+ where+ toStr (Right (MatchPart s _)) = s+ toStr (Left s) = s
+ src/Text/Regex/Quote.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE TemplateHaskell #-}++module Text.Regex.Quote+ ( r+ )+ where++import Language.Haskell.TH+import Language.Haskell.TH.Quote+import Text.Regex.Base++-- | Generate compiled regular expression.+--+-- This QuasiQuote is shorthand of /makeRegex with type annotations/:+--+-- @+-- [r|hogehoge|] == (makeRegex ("hogehoge" :: String) :: Regex)+-- @+--+-- The /Regex/ type signature in the above example, is the type+-- which is named as /Regex/ in this translation unit.+-- Therefore, you can choose Regex type by changing imports.+--+-- For example, the /exp/ variable in the below example has the type of Text.Regex.Posix.Regex:+--+-- @+-- import Text.Regex.Posix (Regex)+-- exp = [r|hoge|]+-- @+--+-- and, the /exp/ variable in below example has the type of Text.Regex.PCRE.Regex:+--+-- @+-- import Text.Regex.PCRE (Regex)+-- exp = [r|hoge|]+-- @+r :: QuasiQuoter+r = QuasiQuoter+ { quoteExp = \str -> do+ mk <- [|makeRegex|]+ return $ mk `AppE` (LitE (StringL str) `SigE` ConT ''String) `SigE` ConT (mkName "Regex")+ , quotePat = error "quotePat is not defined"+ , quoteType = error "quoteType is not defined"+ , quoteDec = error "quoteDec is not defined"+ }
+ tests/doctests.hs view
@@ -0,0 +1,29 @@+module Main where++import Test.DocTest+import System.Directory+import System.FilePath+import Data.List+import Control.Monad+import Control.Applicative++main :: IO ()+main = do+ sources <- findSources+ doctest $+ [ "-isrc"+ , "-idist/build/autogen"+ , "-optP-include"+ , "-optPdist/build/autogen/cabal_macros.h"+ ]+ ++ sources++findSources :: IO [FilePath]+findSources = filter (isSuffixOf ".hs") <$> go "src"+ where+ go dir = do+ (dirs, files) <- listFiles dir+ (files ++) . concat <$> mapM go dirs+ listFiles dir = do+ entries <- map (dir </>) . filter (`notElem` [".", ".."]) <$> getDirectoryContents dir+ (,) <$> filterM doesDirectoryExist entries <*> filterM doesFileExist entries