trajectory (empty) → 0.1.0.0
raw patch · 9 files changed
+841/−0 lines, 9 filesdep +aesondep +attoparsecdep +basesetup-changed
Dependencies added: aeson, attoparsec, base, bytestring, cmdargs, containers, http-enumerator, http-types, regexpr, text, unordered-containers, uri
Files
- InitTj.hs +44/−0
- LICENSE +30/−0
- LsStory.hs +288/−0
- README.md +64/−0
- Setup.hs +2/−0
- Trajectory/API.hs +142/−0
- Trajectory/Private/Config.hs +70/−0
- Trajectory/Types.hs +63/−0
- trajectory.cabal +138/−0
+ InitTj.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Main where++import System.Console.CmdArgs+import Data.Data++import Text.RegexPR (matchRegexPR)+import Data.Maybe (isJust)+import System.IO (hFlush, stdout)++import Trajectory.Private.Config (writeKey)++main = do+ args <- cmdArgs initTjArgDefinition+ key <- getKey+ writeKey (profileName args) key+ return ()++getKey = promptWhile isBlank "API key: " ++data InitTjArg = InitTjArg {+ profileName :: String+} deriving (Show, Data, Typeable)++initTjArgDefinition = InitTjArg {+ profileName = "default"+ &= explicit+ &= name "profile"+ &= help "The profile name to use [default]"+} &= program "initrj"++-- generally useful functions below; maybe they exist elsewhere:++promptWhile p prompt = loop+ where+ loop = do+ putStr prompt+ hFlush stdout+ result <- getLine+ if p result+ then loop+ else return result++isBlank s = isJust $ matchRegexPR "^\\s*$" s
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Mike Burns++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.++ * Neither the name of Mike Burns nor the names of other+ 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+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.
+ LsStory.hs view
@@ -0,0 +1,288 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Main where++import System.Console.CmdArgs+import Data.Data++import Data.List (intercalate)+import Data.Maybe (fromMaybe, isNothing)+import Control.Applicative( (<*>) )+import Data.Monoid (mconcat)++import Trajectory.Private.Config (withKey)+import Trajectory.API (getStories, Story(..), Iteration(..))++main = do+ args <- cmdArgs lsStoryArgDefinition+ withKey (profileName args) $ \key -> do+ stories <- getStories key (accountName args) (projectName args)+ putStrLn $ either (\error -> "Error: " ++ show error)+ (handle args)+ stories++handle :: LsStoryArg -> ([Story],[Iteration]) -> String+handle args (stories,iterations) =+ let filters = buildFilters args iterations+ renderer = buildRenderer args in+ renderer $ filters `pipe` stories+ where+ pipe :: [ a -> a ] -> (a -> a)+ pipe = foldr (.) id++buildRenderer args [] = ""+buildRenderer args stories+ | onlyNext args && null nonMilestones = ""+ | detailedOutput args && onlyNext args =+ head $ map detailedFormatter nonMilestones+ | detailedOutput args =+ intercalate "\n\n" $ map detailedFormatter stories+ | onlyNext args =+ head $ map simpleFormatter nonMilestones+ | otherwise =+ intercalate "\n" $ map simpleFormatter stories+ where+ nonMilestones = skipMilestones stories+ skipMilestones = filter $ ("Milestone" /=) . storyTaskType++detailedFormatter story =+ title ++ "\n" ++ origins ++ "\n" ++ overall+ where+ title = milestonePrefix story ++ storyTitle story+ overall = intercalate "\t" $ [+ "id: " ++ (show $ storyId story)+ ,"points: " ++ (show $ storyPoints story)+ ,"comments: " ++ (show $ storyCommentsCount story)+ ]+ origins = intercalate "\t" $ [+ (fromMaybe "unassigned" $ storyAssigneeName story)+ ,(maybe "free-floating" ("idea: " ++) $ storyIdeaSubject story)+ ]++simpleFormatter story = milestonePrefix story ++ storyTitle story++milestonePrefix story+ | storyTaskType story == "Milestone" = "^^^ "+ | otherwise = ""++buildFilters :: LsStoryArg -> [Iteration] -> [ [Story] -> [Story] ]+buildFilters args iterations = filters <*> [args]+ where filters = [+ ideaFilter+ ,beforeMilestoneFilter+ ,estimationFilter+ ,assignmentFilter+ ,stateFilter+ ,neededFilter+ ,iterationFilter iterations+ ,deletionsFilter+ ]++ideaFilter args+ | null summaries && not limitedToNullIdeas = id+ | null summaries && limitedToNullIdeas =+ filter (isNothing . storyIdeaSubject)+ | otherwise = filter ideaElements+ where+ summaries = ideaSummaries args+ limitedToNullIdeas = showNullIdeas args+ ideaElements story =+ maybe limitedToNullIdeas+ (\summary -> summary `elem` summaries)+ (storyIdeaSubject story)++beforeMilestoneFilter args+ | isNothing $ beforeMilestone args = id+ | otherwise = takeWhile notTheMilestone+ where+ (Just milestoneTitle) = beforeMilestone args+ notTheMilestone story =+ (storyTaskType story /= "Milestone") &&+ (storyTitle story /= milestoneTitle)++estimationFilter args+ | null (showEstimate args) && not (showUnestimated args) = id+ | null $ showEstimate args =+ filter (\story -> (storyPoints story) == 0)+ | not $ showUnestimated args =+ filter (\story -> (storyPoints story) `elem` (showEstimate args))+ | otherwise =+ filter (\story -> (storyPoints story) `elem` [0]++(showEstimate args))++assignmentFilter args+ | null assignments && not limitedToUnassigned = id+ | null assignments = filter (isNothing . storyAssigneeName)+ | otherwise = filter assignmentElements+ where+ limitedToUnassigned = showUnassigned args+ assignments = showAssigned args+ assignmentElements story =+ maybe limitedToUnassigned+ (\assignment -> assignment `elem` assignments)+ (storyAssigneeName story)++stateFilter args =+ filter (\story -> (storyState story) `elem` states)+ where+ allStates = + ["unstarted", "started", "finished", "done", "accepted", "rejected"]+ stateBits = [(showUnstarted args)+ ,(showStarted args)+ ,(showFinished args)+ ,(showDone args)+ ,(showAccepted args)+ ,(showRejected args)]+ states =+ if or stateBits+ then map fst $ filter snd $ zip allStates stateBits+ else allStates++neededFilter args+ | showDesignNeeded args && showDevelopmentNeeded args =+ filter (\story -> storyDesignNeeded story && storyDevelopmentNeeded story)+ | showDesignNeeded args = filter storyDesignNeeded+ | showDevelopmentNeeded args = filter storyDevelopmentNeeded+ | otherwise = id++iterationFilter iterations args+ | showAllIterations args = id+ | showCurrentIteration args && (present $ showIteration args) =+ filter (storyInIterations $ [currentIteration] ++ desiredIterations)+ | present $ showIteration args = filter (storyInIterations desiredIterations)+ | otherwise = filter (storyInIterations [currentIteration])+ where+ currentIteration = head $ filter iterationIsCurrent iterations+ present = not . null+ desiredIterations = filter (\iteration ->+ (iterationStartsOn iteration) `elem` (showIteration args)+ ) iterations+ storyInIterations its story =+ (storyIterationId story) `elem` (map iterationId its)++deletionsFilter args = filter (not . storyDeleted)++data LsStoryArg = LsStoryArg {+ profileName :: String++ ,ideaSummaries :: [String]+ ,showNullIdeas :: Bool+ ,beforeMilestone :: Maybe String+ ,showEstimate :: [Int]+ ,showUnestimated :: Bool+ ,showAssigned :: [String]+ ,showUnassigned :: Bool+ ,showDesignNeeded :: Bool+ ,showDevelopmentNeeded :: Bool++ ,showUnstarted :: Bool+ ,showStarted :: Bool+ ,showFinished :: Bool+ ,showDone :: Bool+ ,showAccepted :: Bool+ ,showRejected :: Bool++ ,showAllIterations :: Bool+ ,showCurrentIteration :: Bool+ ,showIteration :: [String]++ ,detailedOutput :: Bool+ ,onlyNext :: Bool++ ,accountName :: String+ ,projectName :: String+} deriving (Show, Data, Typeable)++lsStoryArgDefinition = LsStoryArg {+ profileName = "default"+ &= explicit+ &= name "profile"+ &= groupname "Common flags"+ &= help "The profile name to use [default]"+ ,ideaSummaries = def+ &= explicit+ &= name "idea"+ &= groupname "Filtering"+ &= help "Stories matching this idea"+ ,showNullIdeas = def+ &= explicit+ &= name "unset-idea"+ &= help "Stories without an idea"+ ,beforeMilestone = def+ &= explicit+ &= name "before-milestone"+ &= help "Stories before the given milestone"+ ,showUnestimated = def+ &= explicit+ &= name "unestimated"+ &= help "Unestimated stories"+ ,showEstimate = def+ &= explicit+ &= name "estimate"+ &= help "Stories with the given estimate"+ ,showUnassigned = def+ &= explicit+ &= name "unassigned"+ &= help "Unassigned stories"+ ,showAssigned = def+ &= explicit+ &= name "assigned"+ &= help "Stories assigned to the named person"+ ,showDesignNeeded = def+ &= explicit+ &= name "design"+ &= help "Stories that need design"+ ,showDevelopmentNeeded = def+ &= explicit+ &= name "development"+ &= help "Stories that need development"+ ,showUnstarted = def+ &= explicit+ &= name "unstarted"+ &= groupname "Filtering on state"+ &= help "Unstarted stories"+ ,showStarted = def+ &= explicit+ &= name "started"+ &= help "Started stories"+ ,showFinished = def+ &= explicit+ &= name "finished"+ &= help "Finished stories"+ ,showDone = def+ &= explicit+ &= name "done"+ &= help "Stories in the done state"+ ,showAccepted = def+ &= explicit+ &= name "accepted"+ &= help "Stories which have been accepted"+ ,showRejected = def+ &= explicit+ &= name "rejected"+ &= help "Stories which have been rejected"+ ,showAllIterations = def+ &= explicit+ &= name "all-iterations"+ &= groupname "Iteration filtering"+ &= help "Stories from all iterations"+ ,showCurrentIteration = def+ &= explicit+ &= name "current-iteration"+ &= help "Stories from the current iteration"+ ,showIteration = def+ &= explicit+ &= name "iteration"+ &= help "Stories from the named iteration"+ ,onlyNext = def+ &= explicit+ &= name "next"+ &= groupname "Formatting"+ &= help "Only show the next matching story"+ ,detailedOutput = def+ &= explicit+ &= name "long"+ &= name "l"+ &= groupname "Formatting"+ &= help "Show story details"+ ,accountName = def &= argPos 0+ ,projectName = def &= argPos 1+} &= program "lsstory" &= helpArg [groupname "Common flags"]
+ README.md view
@@ -0,0 +1,64 @@+Trajectory+----------++Tools and a library for [Trajectory](http://apptrajectory.com/).++Installation+============++ % cabal install trajectory+ % export PATH=~/.cabal/bin+ % inittj++You can find your API key in [the Trajectory settings](https://www.apptrajectory.com/profile/edit).++The account name and project name come from the URL:++ https://www.apptrajectory.com/ACCOUNTNAME/PROJECTNAME/stories++It probably makes a ton of sense to use a shell alias:++ % alias lsos='lsstory thoughtbot opensource'++Synopsis+========++Done:++ % inittj+ % inittj --profile thoughtbot-support++ % lsstory accountname projectname+ % lsstory accountname projectname --idea "Spam is out of control"+ % lsstory accountname projectname --next+ % lsstory accountname projectname --unstarted+ % lsstory accountname projectname --unestimated+ % lsstory accountname projectname --unstarted --next --unassigned --incomplete+ % lsstory accountname projectname --within-milestone "Fully Backboned"+ % lsstory accountname projectname --profile thoughtbot-support --project "Trajectory"+ % lsstory accountname projectname --completed++ % lsstory accountname projectname --development+ % lsstory accountname projectname --design+ % lsstory accountname projectname --design --development++ % lsstory accountname projectname --all-iterations+ % lsstory accountname projectname --current-iteration+ % lsstory accountname projectname --iteration 2012-01-02++To do:++ % lsidea+ % lsmilestone+ % lsmilestone --unstarted --next++ % mkstory "As a user I want to always be signed in so I don't have to care"+ % mkstory "As an admin I want to delete comments so I can remove spam" --idea "Spam is out of control"+ % mkstory "As a visitor I am not able to see private messages so I can maintain security" -m -a screenshot.png --top++Copyright+=========++Copyright 2011 Mike Burns++Trajectory is copyright 2011 thoughtbot
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Trajectory/API.hs view
@@ -0,0 +1,142 @@+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}++-- | The Trajectory API, or a subset of it at least. This mirrors the+-- underlying implementation, which ties stories to iterations.++module Trajectory.API (+ getStories+,module Trajectory.Types+) where++import Data.Data+import Data.Aeson+import Control.Applicative ( (<$>), (<*>) )++import Data.List (intercalate)+import Data.Attoparsec.ByteString.Lazy+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as LBS+import qualified Network.HTTP.Types as Types+import Network.HTTP.Enumerator+import Text.URI+import qualified Control.Exception as E+import Data.Maybe (fromMaybe)++import Trajectory.Types++-- | Get all the incomplete stories and iterations for a given user key,+-- account name, and project name. Since stories and iterations are tied+-- together in the underlying API, this produces them as a pair.+--+-- It produces an IO of either an error or the stories/iterations pair. The+-- error can come from the HTTP, or from non-JSON input, or from a change to+-- the JSON.+--+-- > do+-- > possibleStories <- getStories "abcdefg" "thoughtbot" "opensource"+-- > case possibleStories of+-- > (Left error) -> putStrLn $ "got the error: " ++ show error+-- > (Right (stories,iterations)) ->+-- > putStrLn $ intercalate "\n" $+-- > (map formatStory stories) ++ (map formatIteration iterations)+getStories :: String -> String -> String -> IO (Either Error ([Story], [Iteration]))+getStories key accountName projectName = do+ let url = buildUrl [key, "accounts", accountName, "projects", projectName, "stories.json"]+ result <- doHttps (BS.pack "GET") url Nothing+ return $ either (Left . HTTPConnectionError)+ (extractStories . parseJson . responseBody)+ result+ where+ extractStories :: (Either Error Stories) -> (Either Error ([Story],[Iteration]))+ extractStories (Left l) = Left l+ extractStories (Right (Stories stories iterations)) = Right (stories, iterations)+++data Stories = Stories [Story] [Iteration]+ deriving (Show, Eq, Typeable, Data)++instance FromJSON Story where+ parseJSON (Object o) =+ Story <$> o .: "archived"+ <*> o .:? "assignee_id"+ <*> o .:? "branch"+ <*> o .: "created_at"+ <*> o .: "deleted"+ <*> o .: "design_needed"+ <*> o .: "development_needed"+ <*> o .: "id"+ <*> o .:? "idea_id"+ <*> o .: "iteration_id"+ <*> o .: "points"+ <*> o .: "position"+ <*> o .: "state"+ <*> o .: "task_type"+ <*> o .: "title"+ <*> o .: "updated_at"+ <*> o .: "user_id"+ <*> o .: "comments_count"+ <*> o .:? "assignee_name"+ <*> o .: "user_name"+ <*> o .: "state_events"+ <*> o .:? "idea_subject"+ parseJSON _ = fail "Could not build a Story"++instance FromJSON Iteration where+ parseJSON (Object o) =+ Iteration <$> o .: "accepted_points"+ <*> o .: "complete"+ <*> o .: "created_at"+ <*> o .: "estimated_points"+ <*> o .: "estimated_velocity"+ <*> o .: "id"+ <*> o .: "starts_on"+ <*> o .: "stories_count"+ <*> o .: "team_strength"+ <*> o .: "updated_at"+ <*> o .: "percent_complete"+ <*> o .: "current?"+ <*> o .: "unstarted_stories_count"+ <*> o .: "accepted_stories_count"+ <*> o .: "started_stories_count"+ <*> o .: "delivered_stories_count"+ <*> o .: "comments_count"+ parseJSON _ = fail "Could not build an Iteration"++instance FromJSON Stories where+ parseJSON (Object o) =+ Stories <$> o .: "stories" <*> o .: "iterations"+ parseJSON _ = fail "Could not build Stories"+++buildUrl :: [String] -> String+buildUrl paths = "https://www.apptrajectory.com/api/" ++ intercalate "/" paths+++doHttps :: BS.ByteString -> String -> Maybe (RequestBody IO) -> IO (Either E.IOException Response)+doHttps method url body = do+ let (Just uri) = parseURI url+ (Just host) = uriRegName uri+ requestBody = fromMaybe (RequestBodyBS $ BS.pack "") body+ queryString = Types.parseQuery $ BS.pack $ fromMaybe "" $ uriQuery uri+ request = def { method = method+ , secure = True+ , host = BS.pack host+ , port = 443+ , path = BS.pack $ uriPath uri+ , requestBody = requestBody+ , queryString = queryString+ }++ (getResponse request >>= return . Right) `catch` (return . Left)+ where+ getResponse request = withManager $ \manager -> httpLbs request manager++parseJson :: (FromJSON b, Show b) => LBS.ByteString -> Either Error b+parseJson jsonString =+ let parsed = parse (fromJSON <$> json) jsonString in+ case parsed of+ Data.Attoparsec.ByteString.Lazy.Done _ jsonResult -> do+ case jsonResult of+ (Success s) -> Right s+ (Error e) -> Left $ JsonError $ e ++ " on the JSON: " ++ LBS.unpack jsonString+ (Fail _ _ e) -> Left $ ParseError e
+ Trajectory/Private/Config.hs view
@@ -0,0 +1,70 @@+module Trajectory.Private.Config where++import qualified Data.ByteString.Char8 as BS (readFile, ByteString)+import qualified Data.ByteString.Lazy as LBS+import System.Environment (getEnv)+import qualified Data.HashMap.Lazy as M+import qualified Data.Text as T (pack, unpack)+import Data.Aeson (json, Value(..), encode, toJSON)+import Data.Attoparsec (parse, IResult(..))+import Data.Text as T++withKey :: String -> (String -> IO ()) -> IO ()+withKey profileName doThis = do+ maybeKey <- getKey profileName+ maybe showError doThis maybeKey+ where showError = putStrLn $ "Unknown profile name: " ++ profileName++getKey profileName = do+ possibleConfig <- decodeConfig+ return $ maybe Nothing+ (maybe Nothing (Just . extractKey) . lookupKey)+ possibleConfig+ where+ extractKey (String k) = T.unpack k+ lookupKey = M.lookup (T.pack profileName)++readConfig :: IO (Maybe BS.ByteString)+readConfig = getConfigFileName >>= readConfigFrom++readConfigFrom :: String -> IO (Maybe BS.ByteString)+readConfigFrom configFileName =+ (BS.readFile configFileName >>= return . Just) `catch` (const $ return Nothing)++decodeConfig :: IO (Maybe (M.HashMap T.Text Value))+decodeConfig = getConfigFileName >>= decodeConfigFrom++decodeConfigFrom :: String -> IO (Maybe (M.HashMap T.Text Value))+decodeConfigFrom configFileName = do+ possibleJsonString <- readConfigFrom configFileName+ case possibleJsonString of+ Nothing -> return Nothing+ (Just jsonString) -> do+ return $ Just mapping+ where+ (Done _ config) = parse json jsonString+ (Object mapping) = config++writeKey :: String -> String -> IO ()+writeKey profileName key = do+ configFileName <- getConfigFileName+ possiblePriorConfig <- decodeConfigFrom configFileName+ let newConfig = maybe (M.singleton encodedProfileName encodedKey)+ (M.insert encodedProfileName encodedKey)+ possiblePriorConfig+ LBS.writeFile configFileName $ encode $ toJSON newConfig+ where+ encodedProfileName = T.pack profileName+ encodedKey = String $ T.pack key++getConfigFileName = do+ getEnv "TRAJECTORY_CONFIG_FILE"+ `catch`+ (const (getEnv "HOME" >>= return . withHomeDir))+ `catch`+ (const (getEnv "USER" >>= return . withUserName))+ `catch`+ (const (return "/.trajectory"))+ where+ withHomeDir homeDir = homeDir ++ "/.trajectory"+ withUserName userName = "/usr/"++userName++"/.trajectory"
+ Trajectory/Types.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Trajectory.Types where++import Control.Exception (IOException)+import Data.Data (Typeable, Data)++-- | Errors have been tagged according to their source, so you can more easily+-- dispatch and handle them.+data Error =+ HTTPConnectionError IOException -- ^ A HTTP error occurred. The actual caught error is included, if available.+ | ParseError String -- ^ An error in the parser itself.+ | JsonError String -- ^ The JSON is malformed or unexpected.+ | UserError String -- ^ Incorrect input.+ deriving (Show, Eq)++-- | A Trajectory story.+data Story = Story {+ storyArchived :: Bool+ ,storyAssigneeId :: Maybe Int+ ,storyBranch :: Maybe String+ ,storyCreatedAt :: String+ ,storyDeleted :: Bool+ ,storyDesignNeeded :: Bool+ ,storyDevelopmentNeeded :: Bool+ ,storyId :: Int+ ,storyIdeaId :: Maybe Int+ ,storyIterationId :: Int+ ,storyPoints :: Int+ ,storyPosition :: Int+ ,storyState :: String+ ,storyTaskType :: String+ ,storyTitle :: String+ ,storyUpdatedAt :: String+ ,storyUserId :: Int+ ,storyCommentsCount :: Int+ ,storyAssigneeName :: Maybe String+ ,storyUserName :: String+ ,storyStateEvents :: [String]+ ,storyIdeaSubject :: Maybe String+} deriving (Show, Eq, Typeable, Data)++-- | An iteration in Trajectory. The iterationStartsOn is the most+-- user-identifying string, though it changes with time. The @storyIterationId@+-- is the same as the @iterationId@.+data Iteration = Iteration {+ iterationAcceptedPoints :: Int+ ,iterationIsComplete :: Bool+ ,iterationCreatedAt :: String+ ,iterationEstimatedPoints :: Int+ ,iterationEstimatedVelocity :: Int+ ,iterationId :: Int+ ,iterationStartsOn :: String+ ,iterationStoriesCount :: Int+ ,iterationTeamStrength :: Int+ ,iterationUpdatedAt :: String+ ,iterationPercentComplete :: Int+ ,iterationIsCurrent :: Bool+ ,iterationUnstartedStoriesCount :: Int+ ,iterationAcceptedStoriesCount :: Int+ ,iterationStartedStoriesCount :: Int+ ,iterationDeliveredStoriesCount :: Int+ ,iterationCommentsCount :: Int+} deriving (Show, Eq, Typeable, Data)
+ trajectory.cabal view
@@ -0,0 +1,138 @@+-- trajectory.cabal auto-generated by cabal init. For additional+-- options, see+-- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.+-- The name of the package.+Name: trajectory++-- The package version. See the Haskell package versioning policy+-- (http://www.haskell.org/haskellwiki/Package_versioning_policy) for+-- standards guiding when and how versions should be incremented.+Version: 0.1.0.0++-- A short (one-line) description of the package.+Synopsis: Tools and a library for working with Trajectory.++-- A longer description of the package.+Description: Trajectory <http://apptrajectory.com/> is a project+ estimation and management tool for project managers,+ product managers, developers, and designers. It is heavily+ inspired by, but improves upon, Basecamp and Pivotal+ Tracker. Like Basecamp, it has a place to organize ideas+ and exchange free-form feedback; like Pivotal Tracker it+ has the concept of stories with points instead of time+ estimations, and it calculates time estimations based on+ past performance. It also weds the two concepts,+ connecting stories with ideas.+ .+ This is a collection of tools and libraries for interacting with Trajectory.+ .+ It provides the @lsstory@ tool, a command-line app that+ can list stories, filtering by source idea, milestone,+ point, assignment, whether design or development is+ needed; it can filter on the state (unstarted, started,+ finished, done); it can filter on the iteration. It can+ also be used simply to tell you the next unstarted,+ unassigned development story to work on.+ .+ To build more with this package, look into the+ @Trajectory.API@ module.+ .+ Trajectory is made by thoughtbot <http://thoughtbot.com/>.++-- The license under which the package is released.+License: BSD3++-- The file containing the license text.+License-file: LICENSE++-- The package author(s).+Author: Mike Burns++-- An email address to which users can send suggestions, bug reports,+-- and patches.+Maintainer: mike@mike-burns.com++Homepage: https://github.com/mike-burns/trajectory++-- A copyright notice.+Copyright: Copyright 2011 Mike Burns++Category: Network APIs++Build-type: Simple++-- Extra files to be distributed with the package, such as examples or+-- a README.+Extra-source-files: README.md, LICENSE++-- Constraint on the version of Cabal needed to build this package.+Cabal-version: >=1.6++source-repository head+ type: git+ location: git://github.com/mike-burns/trajectory.git+++Executable inittj+ -- .hs or .lhs file containing the Main module.+ Main-is: InitTj.hs+ + -- Packages needed in order to build this package.+ Build-depends: base >= 4.0 && < 5.0+ ,regexpr+ ,aeson == 0.5.0.0+ ,attoparsec == 0.10.1.0+ ,bytestring+ ,text+ ,containers+ ,cmdargs+ ,unordered-containers == 0.1.4.3+ + -- Modules not exported by this package.+ Other-modules: Trajectory.Private.Config+ + -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+ -- Build-tools: + +Executable lsstory+ -- .hs or .lhs file containing the Main module.+ Main-is: LsStory.hs+ + -- Packages needed in order to build this package.+ Build-depends: base >= 4.0 && < 5.0+ ,uri+ ,http-enumerator == 0.7.2.1+ ,bytestring+ ,aeson == 0.5.0.0+ ,attoparsec == 0.10.1.0+ ,http-types+ ,text+ ,containers+ ,cmdargs+ ,unordered-containers == 0.1.4.3+ + -- Modules not exported by this package.+ Other-modules: Trajectory.Private.Config+ + -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+ -- Build-tools: ++Library+ -- Modules exported by the library.+ Exposed-modules: Trajectory.Types, Trajectory.API++ -- Packages needed in order to build this package.+ Build-depends: base >= 4.0 && < 5.0+ ,aeson == 0.5.0.0+ ,attoparsec == 0.10.1.0+ ,bytestring+ ,http-enumerator == 0.7.2.1+ ,uri+ ,http-types+ + -- Modules not exported by this package.+ -- Other-modules: + + -- Extra tools (e.g. alex, hsc2hs, ...) needed to build the source.+ -- Build-tools: +