casing 0.1.2.1 → 0.1.3.0
raw patch · 4 files changed
+447/−166 lines, 4 filesdep +casingdep +tastydep +tasty-hunitPVP ok
version bump matches the API change (PVP)
Dependencies added: casing, tasty, tasty-hunit
API changes (from Hackage documentation)
+ Text.Casing: instance GHC.Classes.Eq a => GHC.Classes.Eq (Text.Casing.Identifier a)
Files
- Text/Casing.hs +0/−144
- casing.cabal +36/−22
- src/Text/Casing.hs +161/−0
- tests/Spec.hs +250/−0
− Text/Casing.hs
@@ -1,144 +0,0 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE DeriveTraversable #-}--- | Conversions between several common identifier casing conventions:------ - @PascalCase@ - no spacing between words, first letter in word is--- uppercase, all others are lowercase.--- - @camelCase@ - like @PascalCase@, but the very first letter is lowercase.--- - @kebab-case@ - everything lowercase, dash delimits words.--- - @snake_Case@ - underscores delimit words, case is unrestricted.--- - @quiet_snake_case@ - underscores delimit words, everything lowercase.--- - @SCREAMING_SNAKE_CASE@ - underscores delimit words, everything uppercase.-module Text.Casing-(--- * Types-Identifier (..)--- * Parsing-, fromHumps-, fromKebab-, fromSnake-, fromWords-, fromAny--- * Generating-, toCamel-, toPascal-, toSnake-, toQuietSnake-, toScreamingSnake-, toKebab-, toWords--- * Shorthand functions-, pascal-, camel-, snake-, quietSnake-, screamingSnake-, kebab-, wordify--- * Miscellaneous-, dropPrefix-)-where--import Data.Char-import Data.List (intersperse)-import Data.List.Split (wordsBy)-import Control.Applicative---- | An opaque type that represents a parsed identifier.-newtype Identifier a = Identifier { unIdentifier :: [a] }- deriving (Monad, Functor, Applicative, Show, Foldable, Traversable)--wordCase :: String -> String-wordCase "" = ""-wordCase (x:xs) = toUpper x : map toLower xs---- | Convert from "humped" casing (@camelCase@ or @PascalCase@)-fromHumps :: String -> Identifier String-fromHumps = Identifier . go- where- go "" = [""]- go (x:[]) = [x:[]]- go (x:y:xs)- | (not $ isUpper x) && (isUpper y) =- let (z:zs) = go (y:xs)- in [x]:(z:zs)- | otherwise =- let (z:zs) = go (y:xs)- in (x:z):zs--fromWords :: String -> Identifier String-fromWords = Identifier . words---- | Convert from @kebab-cased-identifiers@-fromKebab :: String -> Identifier String-fromKebab = Identifier . wordsBy (== '-')---- | Convert from @snake_cased@ (either flavor)-fromSnake :: String -> Identifier String-fromSnake = Identifier . wordsBy (== '_')---- | Convert from anything, including mixed casing.-fromAny :: String -> Identifier String-fromAny str = fromHumps str >>= fromKebab >>= fromSnake >>= fromWords---- | To @PascalCase@-toPascal :: Identifier String -> String-toPascal = concat . map wordCase . unIdentifier---- | To @camelCase@-toCamel :: Identifier String -> String-toCamel (Identifier (x:xs)) = concat $ map toLower x:map wordCase xs---- | To @kebab-case@-toKebab :: Identifier String -> String-toKebab = concat . intersperse "-" . map (map toLower) . unIdentifier---- | To @snake_Case@-toSnake :: Identifier String -> String-toSnake = concat . intersperse "_" . unIdentifier---- | To @quiet_snake_case@-toQuietSnake :: Identifier String -> String-toQuietSnake = map toLower . toSnake---- | To @SCREAMING_SNAKE_CASE@-toScreamingSnake :: Identifier String -> String-toScreamingSnake = map toUpper . toSnake---- | To @word Case@-toWords :: Identifier String -> String-toWords = unwords . unIdentifier---- | Directly convert to @PascalCase@ through 'fromAny'-pascal :: String -> String-pascal = toPascal . fromAny---- | Directly convert to @camelCase@ through 'fromAny'-camel :: String -> String-camel = toCamel . fromAny---- | Directly convert to @snake_Case@ through 'fromAny'-snake :: String -> String-snake = toSnake . fromAny---- | Directly convert to @quiet_snake_case@ through 'fromAny'-quietSnake :: String -> String-quietSnake = toQuietSnake . fromAny---- | Directly convert to @SCREAMING_SNAKE_CASE@ through 'fromAny'-screamingSnake :: String -> String-screamingSnake = toScreamingSnake . fromAny---- | Directly convert to @kebab-case@ through 'fromAny'-kebab :: String -> String-kebab = toKebab . fromAny---- | Directly convert to @word Case@ through 'fromAny'-wordify :: String -> String-wordify = toWords . fromAny---- | Drop the first word from a parsed identifier. Typical usage is between--- parsing and writing, e.g.: @toKebab . dropPrefix . fromAny $ "strHelloWorld" == "hello-world"@-dropPrefix :: Identifier String -> Identifier String-dropPrefix = Identifier . drop 1 . unIdentifier
casing.cabal view
@@ -1,26 +1,40 @@--- Initial casing.cabal generated by cabal init. For further +-- Initial casing.cabal generated by cabal init. For further -- documentation, see http://haskell.org/cabal/users-guide/ -name: casing-version: 0.1.2.1-synopsis: Convert between various source code casing conventions-description: Converts between camelCase, PascalCase, kebab-case, and- three flavors of snake_case.-license: MIT-license-file: LICENSE-author: Tobias Dammers-maintainer: tdammers@gmail.com--- copyright: -category: Text-build-type: Simple--- extra-source-files: -cabal-version: >=1.10+name: casing+version: 0.1.3.0+synopsis: Convert between various source code casing conventions+description: Converts between camelCase, PascalCase, kebab-case, and three+ flavors of snake_case.+license: MIT+license-file: LICENSE+author: Tobias Dammers+maintainer: tdammers@gmail.com+-- copyright: +category: Text+build-type: Simple+-- extra-source-files: +cabal-version: >=1.10 +source-repository head+ type: git+ location: https://bitbucket.org/tdammers/casing+ library- exposed-modules: Text.Casing- -- other-modules: - -- other-extensions: - build-depends: base >=4.8 && <5- , split- -- hs-source-dirs: - default-language: Haskell2010+ exposed-modules: Text.Casing+ -- other-modules: + -- other-extensions: + build-depends: base >=4.8 && <5+ , split+ hs-source-dirs: src+ default-language: Haskell2010++test-suite tests+ main-is: Spec.hs+ hs-source-dirs: tests+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ build-depends: base >=4.8 && <5+ , casing+ , tasty+ , tasty-hunit
+ src/Text/Casing.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE DeriveTraversable #-}+-- | Conversions between several common identifier casing conventions:+--+-- - @PascalCase@ - no spacing between words, first letter in word is+-- uppercase, all others are lowercase.+-- - @camelCase@ - like @PascalCase@, but the very first letter is lowercase.+-- - @kebab-case@ - everything lowercase, dash delimits words.+-- - @snake_Case@ - underscores delimit words, case is unrestricted.+-- - @quiet_snake_case@ - underscores delimit words, everything lowercase.+-- - @SCREAMING_SNAKE_CASE@ - underscores delimit words, everything uppercase.+module Text.Casing+(+-- * Types+Identifier (..)+-- * Parsing+, fromHumps+, fromKebab+, fromSnake+, fromWords+, fromAny+-- * Generating+, toCamel+, toPascal+, toSnake+, toQuietSnake+, toScreamingSnake+, toKebab+, toWords+-- * Shorthand functions+, pascal+, camel+, snake+, quietSnake+, screamingSnake+, kebab+, wordify+-- * Miscellaneous+, dropPrefix+)+where++import Data.Char+import Data.List (intersperse)+import Data.List.Split (wordsBy)+import Control.Applicative++-- | An opaque type that represents a parsed identifier.+newtype Identifier a = Identifier { unIdentifier :: [a] }+ deriving (Monad, Functor, Applicative, Show, Foldable, Traversable, Eq)++wordCase :: String -> String+wordCase "" = ""+wordCase (x:xs) = toUpper x : map toLower xs++-- | Convert from "humped" casing (@camelCase@ or @PascalCase@)+fromHumps :: String -> Identifier String+fromHumps = Identifier . go+ where+ go "" = [""]+ go (x:[]) = [x:[]]+ go xxs@(x:xs)+ | isUpper x =+ case xs of+ "" ->+ [[x]]+ (y:_) ->+ if isUpper y then+ [x]:go xs+ else+ let cur = x:takeWhile (not . isUpper) xs+ rem = dropWhile (not . isUpper) xs+ in + if null rem then+ [cur]+ else+ cur:go rem+ | otherwise =+ let cur = takeWhile (not . isUpper) xxs+ rem = dropWhile (not . isUpper) xxs+ in+ if null rem then+ [cur]+ else+ cur:go rem++fromWords :: String -> Identifier String+fromWords = Identifier . words++-- | Convert from @kebab-cased-identifiers@+fromKebab :: String -> Identifier String+fromKebab = Identifier . wordsBy (== '-')++-- | Convert from @snake_cased@ (either flavor)+fromSnake :: String -> Identifier String+fromSnake = Identifier . wordsBy (== '_')++-- | Convert from anything, including mixed casing.+fromAny :: String -> Identifier String+fromAny str = fromHumps str >>= fromKebab >>= fromSnake >>= fromWords++-- | To @PascalCase@+toPascal :: Identifier String -> String+toPascal = concat . map wordCase . unIdentifier++-- | To @camelCase@+toCamel :: Identifier String -> String+toCamel (Identifier (x:xs)) = concat $ map toLower x:map wordCase xs++-- | To @kebab-case@+toKebab :: Identifier String -> String+toKebab = concat . intersperse "-" . map (map toLower) . unIdentifier++-- | To @snake_Case@+toSnake :: Identifier String -> String+toSnake = concat . intersperse "_" . unIdentifier++-- | To @quiet_snake_case@+toQuietSnake :: Identifier String -> String+toQuietSnake = map toLower . toSnake++-- | To @SCREAMING_SNAKE_CASE@+toScreamingSnake :: Identifier String -> String+toScreamingSnake = map toUpper . toSnake++-- | To @word Case@+toWords :: Identifier String -> String+toWords = unwords . unIdentifier++-- | Directly convert to @PascalCase@ through 'fromAny'+pascal :: String -> String+pascal = toPascal . fromAny++-- | Directly convert to @camelCase@ through 'fromAny'+camel :: String -> String+camel = toCamel . fromAny++-- | Directly convert to @snake_Case@ through 'fromAny'+snake :: String -> String+snake = toSnake . fromAny++-- | Directly convert to @quiet_snake_case@ through 'fromAny'+quietSnake :: String -> String+quietSnake = toQuietSnake . fromAny++-- | Directly convert to @SCREAMING_SNAKE_CASE@ through 'fromAny'+screamingSnake :: String -> String+screamingSnake = toScreamingSnake . fromAny++-- | Directly convert to @kebab-case@ through 'fromAny'+kebab :: String -> String+kebab = toKebab . fromAny++-- | Directly convert to @word Case@ through 'fromAny'+wordify :: String -> String+wordify = toWords . fromAny++-- | Drop the first word from a parsed identifier. Typical usage is between+-- parsing and writing, e.g.: @toKebab . dropPrefix . fromAny $ "strHelloWorld" == "hello-world"@+dropPrefix :: Identifier String -> Identifier String+dropPrefix = Identifier . drop 1 . unIdentifier
+ tests/Spec.hs view
@@ -0,0 +1,250 @@+module Main where++import Test.Tasty+import Test.Tasty.HUnit+import Text.Casing++main :: IO ()+main = defaultMain tests++tests :: TestTree+tests = testGroup "tests"+ [ testGroup "parsing"+ [ testGroup "fromHumps"+ [ testCase "no splits 1" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromHumps "hello")+ , testCase "no splits 2" $ do+ assertEqual ""+ (Identifier ["Hello"])+ (fromHumps "Hello")+ , testCase "non-alpha, no split" $ do+ assertEqual ""+ (Identifier ["-hello"])+ (fromHumps "-hello")+ , testCase "non-alpha, split" $ do+ assertEqual ""+ (Identifier ["-hello-", "World"])+ (fromHumps "-hello-World")+ , testCase "simple split camel" $ do+ assertEqual ""+ (Identifier ["hello", "World"])+ (fromHumps "helloWorld")+ , testCase "simple split pascal" $ do+ assertEqual ""+ (Identifier ["Hello", "World"])+ (fromHumps "HelloWorld")+ , testCase "single letter abbrev split" $ do+ assertEqual ""+ (Identifier ["Hello", "A", "World"])+ (fromHumps "HelloAWorld")+ , testCase "multi letter abbrev split" $ do+ assertEqual ""+ (Identifier ["Hello", "X", "M", "L", "World"])+ (fromHumps "HelloXMLWorld")+ , testCase "single letter upper" $ do+ assertEqual ""+ (Identifier ["A"])+ (fromHumps "A")+ , testCase "single letter lower" $ do+ assertEqual ""+ (Identifier ["a"])+ (fromHumps "a")+ ]+ , testGroup "fromKebab"+ [ testCase "no splits" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromKebab "hello")+ , testCase "single split" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromKebab "hello-world")+ , testCase "leading split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromKebab "-world")+ , testCase "trailing split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromKebab "world-")+ , testCase "multiple dashes" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromKebab "hello---world")+ ]+ , testGroup "fromSnake"+ [ testCase "no splits" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromSnake "hello")+ , testCase "single split" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromSnake "hello_world")+ , testCase "leading split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromSnake "_world")+ , testCase "trailing split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromSnake "world_")+ , testCase "multiple dashes" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromSnake "hello___world")+ ]+ , testGroup "fromWords"+ [ testCase "no splits" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromWords "hello")+ , testCase "single split" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromWords "hello world")+ , testCase "leading split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromWords " world")+ , testCase "trailing split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromWords "world ")+ , testCase "multiple dashes" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromWords "hello world")+ ]+ , testGroup "fromAny"+ [ testCase "no splits 1" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromAny "hello")+ , testCase "no splits 2" $ do+ assertEqual ""+ (Identifier ["Hello"])+ (fromAny "Hello")+ , testCase "simple split camel" $ do+ assertEqual ""+ (Identifier ["hello", "World"])+ (fromAny "helloWorld")+ , testCase "simple split pascal" $ do+ assertEqual ""+ (Identifier ["Hello", "World"])+ (fromAny "HelloWorld")+ , testCase "single letter abbrev split" $ do+ assertEqual ""+ (Identifier ["Hello", "A", "World"])+ (fromAny "HelloAWorld")+ , testCase "multi letter abbrev split" $ do+ assertEqual ""+ (Identifier ["Hello", "X", "M", "L", "World"])+ (fromAny "HelloXMLWorld")+ , testCase "single letter upper" $ do+ assertEqual ""+ (Identifier ["A"])+ (fromAny "A")+ , testCase "single letter lower" $ do+ assertEqual ""+ (Identifier ["a"])+ (fromAny "a")+ , testCase "no splits" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromAny "hello")+ , testCase "single split" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromAny "hello-world")+ , testCase "leading split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromAny "-world")+ , testCase "trailing split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromAny "world-")+ , testCase "multiple dashes" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromAny "hello---world")+ , testCase "no splits" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromAny "hello")+ , testCase "single split" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromAny "hello_world")+ , testCase "leading split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromAny "_world")+ , testCase "trailing split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromAny "world_")+ , testCase "multiple dashes" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromAny "hello___world")+ , testCase "no splits" $ do+ assertEqual ""+ (Identifier ["hello"])+ (fromAny "hello")+ , testCase "single split" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromAny "hello world")+ , testCase "leading split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromAny " world")+ , testCase "trailing split" $ do+ assertEqual ""+ (Identifier ["world"])+ (fromAny "world ")+ , testCase "multiple dashes" $ do+ assertEqual ""+ (Identifier ["hello", "world"])+ (fromAny "hello world")+ ]+ ]+ , testGroup "writing"+ [ testGroup "toPascal"+ [ testCase "toPascal simple" $ do+ assertEqual ""+ "HelloWorld"+ (toPascal $ Identifier ["hello", "world"])+ ]+ , testGroup "toCamel"+ [ testCase "toCamel simple" $ do+ assertEqual ""+ "helloWorld"+ (toCamel $ Identifier ["hello", "world"])+ ]+ , testGroup "toKebab"+ [ testCase "toKebab simple" $ do+ assertEqual ""+ "hello-world"+ (toKebab $ Identifier ["hello", "world"])+ ]+ , testGroup "toSnake"+ [ testCase "toSnake simple" $ do+ assertEqual ""+ "hello_world"+ (toSnake $ Identifier ["hello", "world"])+ , testCase "toScreamingSnake simple" $ do+ assertEqual ""+ "HELLO_WORLD"+ (toScreamingSnake $ Identifier ["hello", "world"])+ , testCase "toQuietSnake simple" $ do+ assertEqual ""+ "hello_world"+ (toQuietSnake $ Identifier ["Hello", "World"])+ ]+ ]+ ]