packages feed

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 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:         +