inflections 0.1.0.5 → 0.1.0.6
raw patch · 14 files changed
+224/−120 lines, 14 files
Files
- Text/Inflections.hs +75/−24
- Text/Inflections/Camelize.hs +19/−28
- Text/Inflections/Dasherize.hs +5/−21
- Text/Inflections/Data.hs +2/−2
- Text/Inflections/Humanize.hs +23/−0
- Text/Inflections/Parameterize.hs +2/−2
- Text/Inflections/Parse/CamelCase.hs +12/−9
- Text/Inflections/Parse/SnakeCase.hs +11/−8
- Text/Inflections/Parse/Types.hs +9/−2
- Text/Inflections/Titleize.hs +17/−0
- Text/Inflections/Transliterate.hs +31/−0
- Text/Inflections/Underscore.hs +5/−19
- inflections.cabal +8/−4
- test/Suite.hs +5/−1
Text/Inflections.hs view
@@ -12,6 +12,62 @@ "Inflections" library found in Rails: <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>++While many of the functions in this library are the same as in implementations+in Rails' ActiveSupport, the philosophy of this library is fundamentally+different. Where Rails tries to be as permissive as possible, and return a+String when given any input, this library tries to output strings that make+sense according to the function that is called.++When you look closely at many of the functions in Rails' inflections+library, you will notice that many of them are partial. That is, they only+have well-defined output for some of the possible inputs to the function allowed+by the type system. As an example, let's take the @underscore@ function. In+Rails, it works like this:++>>> "fooBar".underscore+"foo_bar"++Looks ok so far. However, it's also easy to produce less expected results:++>>> "foo bar".underscore+"foo bar"++The output isn't underscored - it contains a space! It turns out that some of+the functions from Inflections in ActiveSupport are /partial/. Ie., the outputs+are really only specified for a certain range of the inputs allowed by the+String type.++In the Haskell inflections library, we aim to deliver more predictable results+by separating the parsing of strings into tokens from the application of+transformations. Let's see an example.++First, we tokenize an underscored String using 'parseSnakeCase':++>>> parseSnakeCase [] "foo_bar"+Right [Word "foo",Word "bar"]++We can chain together the tokenization of the input String and the+transformation to CamelCase by using 'Control.Monad.LiftM':++>>> import Control.Monad (liftM)+>>> liftM camelize $ parseSnakeCase "foo_bar"++By separating out the tokenization from the application of inflections, we also+end up with useful libraries for validating input which can be used+independently:++>>> parseSnakeCase [] "fooBar"+Left "(unknown)" (line 1, column 4):+unexpected 'B'+expecting lowercase letter, "_" or end of input++This library is still a work-in-progress, and contributions are welcome for+missing pieces and to fix bugs. Please see the Github page to contribute with+code or bug reports:++<https://github.com/stackbuilders/inflections-hs>+ -} module Text.Inflections@@ -19,11 +75,13 @@ , camelizeCustom , dasherize- , dasherizeCustom + , humanize+ , underscore- , underscoreCustom + , titleize+ , defaultMap , parameterize@@ -34,41 +92,34 @@ , ordinal , ordinalize++ , parseSnakeCase+ , parseCamelCase+ , Transliterations ) where -import Data.Char (isAscii)-import qualified Data.Map as Map -import Text.Inflections.Data (defaultMap)+import Text.Inflections.Data ( defaultMap ) import Text.Inflections.Parameterize ( Transliterations , parameterize , parameterizeCustom ) -import Text.Inflections.Underscore ( underscore, underscoreCustom )+import Text.Inflections.Underscore ( underscore ) import Text.Inflections.Camelize ( camelize, camelizeCustom ) -import Text.Inflections.Dasherize ( dasherize, dasherizeCustom )+import Text.Inflections.Humanize ( humanize ) +import Text.Inflections.Titleize ( titleize )++import Text.Inflections.Transliterate ( transliterate, transliterateCustom )++import Text.Inflections.Dasherize ( dasherize )+ import Text.Inflections.Ordinal ( ordinal, ordinalize ) --- |Returns a String after default approximations for changing Unicode characters--- to a valid ASCII range are applied. If you want to supplement the default--- approximations with your own, you should use the transliterateCustom--- function instead of transliterate.-transliterate :: String -> String-transliterate = transliterateCustom "?" defaultMap+import Text.Inflections.Parse.SnakeCase ( parseSnakeCase ) --- |Returns a String after default approximations for changing Unicode characters--- to a valid ASCII range are applied.-transliterateCustom :: String -> Transliterations -> String -> String-transliterateCustom replacement ts = concatMap lookupCharTransliteration- where lookupCharTransliteration c =- if isAscii c then -- Don't bother looking up Chars in ASCII range- [c]- else- case Map.lookup c ts of- Nothing -> replacement- Just val -> val+import Text.Inflections.Parse.CamelCase ( parseCamelCase )
Text/Inflections/Camelize.hs view
@@ -1,38 +1,29 @@-module Text.Inflections.Camelize- ( camelize- , camelizeCustom )-where+module Text.Inflections.Camelize ( camelize, camelizeCustom ) where -import Text.Inflections.Parse.SnakeCase (Word(..), parser)-import Text.Parsec (ParseError, parse)-import Data.Char (toUpper)+import Text.Inflections.Parse.Types (Word(..)) --- |Turns a String in snake_case into CamelCase. Returns the CamelCase string,--- or a ParseError if the input String is not in valid snake_case.+import Data.Char (toUpper, toLower)++-- |Turns a an input Word List in into CamelCase. Returns the CamelCase String. camelize- :: String -- ^ The input string, in snake_case- -> Either ParseError String-camelize s = camelizeCustom [] True s+ :: [Word] -- ^ Input Words to separate with underscores+ -> String -- ^ The camelized String+camelize ws = camelizeCustom True ws --- |Turns an input String in snake_case into CamelCase. Returns the CamelCase--- String, or a ParseError if the input String is not in valid snake_case.--- Accepts arguments to control parsing and output format.+-- |Turns an input Word List into a CamelCase String. camelizeCustom- :: [String] -- ^ A list of acronyms that will be kept whole and accepted as valid input- -> Bool -- ^ Whether to capitalize the first character in the output String- -> String -- ^ The input string, in snake_case- -> Either ParseError String-camelizeCustom acronyms isFirstCap s =- case parse (parser acronyms) "(unknown)" s of- Left errs -> Left errs- Right res -> Right $ concatMap (caseForWord isFirstCap) $ isFirstList res+ :: Bool -- ^ Whether to capitalize the first character in the output String+ -> [Word] -- ^ The input Words+ -> String -- ^ The camelized String+camelizeCustom isFirstCap = concatMap (caseForWord isFirstCap) . isFirstList + caseForWord :: Bool -> (Word, Bool) -> String-caseForWord True (Word (c:cs), True) = toUpper c : cs-caseForWord False (Word s, True) = s-caseForWord _ (Word (c:cs), _) = toUpper c : cs-caseForWord _ (Word [], _) = []-caseForWord _ (Acronym s, _) = s+caseForWord True (Word (c:cs), True) = toUpper c : cs+caseForWord False (Word (c:cs), True) = toLower c : cs+caseForWord _ (Word (c:cs), _) = toUpper c : cs+caseForWord _ (Word [], _) = []+caseForWord _ (Acronym s, _) = s -- |Returns list with Bool indicating if an element is first. isFirstList :: [a] -> [(a, Bool)]
Text/Inflections/Dasherize.hs view
@@ -1,30 +1,14 @@-module Text.Inflections.Dasherize- ( dasherize- , dasherizeCustom )-where+module Text.Inflections.Dasherize ( dasherize ) where -import Text.Inflections.Parse.SnakeCase (Word(..), parser)-import Text.Parsec (ParseError, parse)+import Text.Inflections.Parse.Types (Word(..)) import Data.List (intercalate) -- |Replaces underscores in a snake_cased string with dashes (hyphens). dasherize- :: String -- ^ The input string, in snake_case- -> Either ParseError String-dasherize s = dasherizeCustom [] s---- |Turns a snake_case string into dasherized form. Returns a ParseError if--- the input string is not in proper snake_case.-dasherizeCustom- :: [String] -- ^ A list of acronyms that will be kept whole and accepted as valid input- -> String -- ^ The input string, in snake_case- -> Either ParseError String-dasherizeCustom acronyms s =- case parse (parser acronyms) "(unknown)" s of- Left errs -> Left errs- Right res -> Right $ intercalate "-" $ map toString res-+ :: [Word] -- ^ Input Words to separate with dashes+ -> String -- ^ The dasherized String+dasherize ws = intercalate "-" $ map toString ws toString :: Word -> String toString (Acronym s) = s
Text/Inflections/Data.hs view
@@ -2,8 +2,8 @@ import Data.Map (Map, fromList) --- |These default transliterations stolen from the Ruby i18n library ---- https://github.com/svenfuchs/i18n/blob/master/lib/i18n/backend/transliterator.rb#L41:L69+-- |These default transliterations stolen from the Ruby i18n library - see+-- <https://github.com/svenfuchs/i18n/blob/master/lib/i18n/backend/transliterator.rb#L41:L69>. defaultMap :: Map Char String defaultMap = fromList [ ('À', "A"), ('Á', "A"), ('Â', "A"), ('Ã', "A"), ('Ä', "A"), ('Å', "A"),
+ Text/Inflections/Humanize.hs view
@@ -0,0 +1,23 @@+module Text.Inflections.Humanize (humanize) where++import Text.Inflections.Parse.Types (Word(..))++import Data.List (intercalate)+import Data.Char (toUpper)++-- |Capitalizes the first word and turns underscores into spaces Like titleize,+-- this is meant for creating pretty output.+humanize+ :: [Word] -- ^ List of Words, first of which will be capitalized+ -> String -- ^ The humanized output+humanize s = intercalate " " $ map caseForWord $ isFirstList s++-- |Returns list with Bool indicating if an element is first.+isFirstList :: [a] -> [(a, Bool)]+isFirstList xs = zip xs $ True : repeat False++caseForWord :: (Word, Bool) -> String+caseForWord (Word (c:cs), True) = toUpper c : cs+caseForWord (Word s, False) = s+caseForWord (Word [], _) = []+caseForWord (Acronym s, _) = s -- Acronyms are left intact
Text/Inflections/Parameterize.hs view
@@ -18,8 +18,8 @@ , parser , isValidParamChar ) --- |A Map containing mappings from international characters to sequences--- approximating these characters within the ASCII range.+-- |A 'Data.Map.Map' containing mappings from international characters to+-- sequences approximating these characters within the ASCII range. type Transliterations = Map.Map Char String -- |Replaces special characters in a string so that it may be used as part of a
Text/Inflections/Parse/CamelCase.hs view
@@ -1,23 +1,26 @@ {-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-} -module Text.Inflections.Parse.CamelCase ( parser )+module Text.Inflections.Parse.CamelCase ( parseCamelCase ) where -import qualified Text.ParserCombinators.Parsec.Char as C-import qualified Text.Parsec as P+import Text.Parsec import Text.Inflections.Parse.Types (Word(..)) import Text.Inflections.Parse.Acronym (acronym) +parseCamelCase :: [String] -> String -> Either ParseError [Word]+parseCamelCase acronyms = parse (parser acronyms) "(unknown)"++ -- |Recognizes an input String in CamelCase.-parser :: P.Stream s m Char => [String] -> P.ParsecT s u m [Word]+parser :: Stream s m Char => [String] -> ParsecT s u m [Word] parser acronyms = do- ws <- P.many $ P.choice [ acronym acronyms, word ]- P.eof+ ws <- many $ choice [ acronym acronyms, word ]+ eof return ws -word :: P.Stream s m Char => P.ParsecT s u m Word+word :: Stream s m Char => ParsecT s u m Word word = do- firstChar <- C.upper P.<|> C.lower- restChars <- P.many C.lower+ firstChar <- upper <|> lower+ restChars <- many lower return $ Word $ firstChar : restChars
Text/Inflections/Parse/SnakeCase.hs view
@@ -1,20 +1,23 @@ {-# LANGUAGE FlexibleContexts, NoMonomorphismRestriction #-} -module Text.Inflections.Parse.SnakeCase ( Word(..), parser )+module Text.Inflections.Parse.SnakeCase ( parseSnakeCase ) where -import qualified Text.ParserCombinators.Parsec.Char as C-import qualified Text.Parsec as P import Control.Applicative ((<$>))+import Text.Parsec import Text.Inflections.Parse.Types (Word(..)) import Text.Inflections.Parse.Acronym (acronym) -word :: P.Stream s m Char => P.ParsecT s u m Word-word = Word <$> P.many1 C.lower+parseSnakeCase :: [String] -> String -> Either ParseError [Word]+parseSnakeCase acronyms = parse (parser acronyms) "(unknown)" -parser :: P.Stream s m Char => [String] -> P.ParsecT s u m [Word]++parser :: Stream s m Char => [String] -> ParsecT s u m [Word] parser acronyms = do- ws <- (acronym acronyms P.<|> word) `P.sepBy` (C.char '_')- P.eof+ ws <- (acronym acronyms <|> word) `sepBy` (char '_')+ eof return ws++word :: Stream s m Char => ParsecT s u m Word+word = Word <$> ((many1 lower) <|> (many1 digit))
Text/Inflections/Parse/Types.hs view
@@ -1,5 +1,12 @@ module Text.Inflections.Parse.Types ( Word(..) ) where -data Word = Word String- | Acronym String deriving (Show, Eq)+-- |A 'String' that should be kept whole through applied inflections+data Word + -- | A word that may be transformed by inflection+ = Word String++ -- | A word that may not be transformed by inflections+ | Acronym String++ deriving (Show, Eq)
+ Text/Inflections/Titleize.hs view
@@ -0,0 +1,17 @@+module Text.Inflections.Titleize (titleize) where++import Text.Inflections.Parse.Types (Word(..))++import Data.List (intercalate)+import Data.Char (toUpper)++-- |Capitalizes Capitalizes all the words.+titleize+ :: [Word] -- ^ List of Words, first of which will be capitalized+ -> String -- ^ The titleized String+titleize s = intercalate " " $ map upperCaseWord s++upperCaseWord :: Word -> String+upperCaseWord (Word (c:cs)) = toUpper c : cs+upperCaseWord (Word []) = []+upperCaseWord (Acronym s) = s -- Acronyms are left intact
+ Text/Inflections/Transliterate.hs view
@@ -0,0 +1,31 @@+module Text.Inflections.Transliterate+ ( transliterate+ , transliterateCustom+ )+where++import Text.Inflections.Parameterize ( Transliterations )+import Text.Inflections.Data (defaultMap)++import Data.Char (isAscii)++import qualified Data.Map as Map++-- |Returns a String after default approximations for changing Unicode characters+-- to a valid ASCII range are applied. If you want to supplement the default+-- approximations with your own, you should use the transliterateCustom+-- function instead of transliterate.+transliterate :: String -> String+transliterate = transliterateCustom "?" defaultMap++-- |Returns a String after default approximations for changing Unicode characters+-- to a valid ASCII range are applied.+transliterateCustom :: String -> Transliterations -> String -> String+transliterateCustom replacement ts = concatMap lookupCharTransliteration+ where lookupCharTransliteration c =+ if isAscii c then -- Don't bother looking up Chars in ASCII range+ [c]+ else+ case Map.lookup c ts of+ Nothing -> replacement+ Just val -> val
Text/Inflections/Underscore.hs view
@@ -1,31 +1,17 @@-module Text.Inflections.Underscore (underscore, underscoreCustom) where+module Text.Inflections.Underscore ( underscore ) where -import Text.Inflections.Parse.CamelCase (parser) import Text.Inflections.Parse.Types (Word(..)) -import Text.Parsec (ParseError, parse) import Data.Char (toLower) import Data.List (intercalate) -- |Turns a CamelCase string into an underscore_separated String. underscore- :: String -- ^ A String in CamelCase- -> Either ParseError String-underscore s = underscoreCustom [] s---- |Changes an input String in CamelCase into a String of Words separated by--- underscores. Accepts options for customization.-underscoreCustom- :: [String] -- ^ A list of acronyms that will be kept whole and accepted as valid input- -> String -- ^ A String in CamelCase- -> Either ParseError String-underscoreCustom acronyms s =- case parse (parser acronyms) "(unknown)" s of- Left errs -> Left errs- Right res -> Right $ intercalate "_" $ map toDowncasedString res+ :: [Word] -- ^ Input Words to separate with underscores+ -> String -- ^ The underscored String+underscore ws = intercalate "_" $ map toDowncasedString ws toDowncasedString :: Word -> String toDowncasedString (Acronym s) = map toLower s-toDowncasedString (Word s) = map toLower s-+toDowncasedString (Word s) = map toLower s
inflections.cabal view
@@ -1,5 +1,5 @@ name: inflections-version: 0.1.0.5+version: 0.1.0.6 synopsis: Inflections library for Haskell description: Inflections provides methods for singularization, pluralization,@@ -21,20 +21,24 @@ library exposed-modules: Text.Inflections+ , Text.Inflections.Parse.Types + other-modules: Text.Inflections.Data , Text.Inflections.Parameterize+ , Text.Inflections.Humanize , Text.Inflections.Underscore , Text.Inflections.Camelize , Text.Inflections.Dasherize , Text.Inflections.Ordinal+ , Text.Inflections.Titleize+ , Text.Inflections.Transliterate , Text.Inflections.Parse.Acronym- , Text.Inflections.Parse.SnakeCase , Text.Inflections.Parse.Parameterizable- , Text.Inflections.Parse.CamelCase- , Text.Inflections.Parse.Types + , Text.Inflections.Parse.SnakeCase+ , Text.Inflections.Parse.CamelCase ghc-options: -Wall build-depends: base >=4.5 && <4.7, parsec, containers
test/Suite.hs view
@@ -5,8 +5,12 @@ import qualified Text.Inflections.Tests import qualified Text.Inflections.UnderscoreTest import qualified Text.Inflections.OrdinalTest+import qualified Text.Inflections.HumanizeTest+import qualified Text.Inflections.TitleizeTest main :: IO () main = defaultMain $ Text.Inflections.Tests.tests ++ Text.Inflections.UnderscoreTest.tests ++- Text.Inflections.OrdinalTest.tests+ Text.Inflections.OrdinalTest.tests +++ Text.Inflections.HumanizeTest.tests +++ Text.Inflections.TitleizeTest.tests