Ticket #576: reverse_dependencies.patch

File reverse_dependencies.patch, 49.1 KB (added by Roel van Dijk, 3 years ago)

Implements reverse dependencies

Line 
1Fri Sep 25 00:27:01 CEST 2009  vandijk.roel@gmail.com
2  * Implemented reverse dependencies (#576)
3
4New patches:
5
6[Implemented reverse dependencies (#576)
7vandijk.roel@gmail.com**20090924222701
8 Ignore-this: f9e5e358a4c8db9d5ae17c47a1a49565
9] {
10hunk ./Locations.hs 63
11 pkgScriptURL :: URL
12 pkgScriptURL = "/package"
13 
14+-- URL of the CGI script to show reverse dependencies of a package
15+pkgRevDepsURL :: URL
16+pkgRevDepsURL = "/revdeps"
17+
18 -- URL of the list of recent additions to the database
19 recentAdditionsURL :: URL
20 recentAdditionsURL = "/packages/archive/recent.html"
21hunk ./Makefile 4
22 SHELL  = /bin/sh
23 HC     = ghc
24 HCFLAGS        = -O -Wall
25-CGI_PROGS = upload-pkg check-pkg package search
26-AUX_PROGS = pkg-list recent-adds rss-feed latest-versions splitDistroMap
27+CGI_PROGS = upload-pkg check-pkg package revdeps search
28+AUX_PROGS = pkg-list pkg-rev-deps recent-adds rss-feed latest-versions splitDistroMap
29 GEN_PROGS = $(CGI_PROGS) $(AUX_PROGS)
30 PROGS  = $(GEN_PROGS) post-upload-hook preferred-versions
31 
32hunk ./Makefile 29
33 install: $(PROGS)
34        scp -C $(PROGS) $(HACKAGE_HOST):$(TMPDIR)
35        ssh $(HACKAGE_HOST) mv $(TMPDIR)/package $(CGIDIR)
36+       ssh $(HACKAGE_HOST) mv $(TMPDIR)/revdeps $(CGIDIR)
37        ssh $(HACKAGE_HOST) mv $(TMPDIR)/check-pkg $(CGIDIR)
38        ssh $(HACKAGE_HOST) mv $(TMPDIR)/upload-pkg $(CGIDIR)/protected
39        ssh $(HACKAGE_HOST) mv $(TMPDIR)/search $(CGIDIR)
40hunk ./Makefile 35
41        ssh $(HACKAGE_HOST) mv $(TMPDIR)/post-upload-hook $(BINDIR)
42        ssh $(HACKAGE_HOST) mv $(TMPDIR)/pkg-list $(BINDIR)
43+       ssh $(HACKAGE_HOST) mv $(TMPDIR)/pkg-rev-deps $(BINDIR)
44        ssh $(HACKAGE_HOST) mv $(TMPDIR)/recent-adds $(BINDIR)
45        ssh $(HACKAGE_HOST) mv $(TMPDIR)/rss-feed $(BINDIR)
46        ssh $(HACKAGE_HOST) mv $(TMPDIR)/latest-versions $(BINDIR)
47hunk ./Makefile 56
48        $(HC) --make $(HCFLAGS) -o $@ package.hs
49        strip $@
50 
51+revdeps: revdeps.hs Locations.hs Util.hs RevDepsPage.hs
52+       $(HC) --make $(HCFLAGS) -o $@ revdeps.hs
53+       strip $@
54+
55 search: search.hs SearchAlgorithm.hs SearchMatchingFunctions.hs SearchUtils.hs SearchHackage.hs SearchTypes.hs Locations.hs Util.hs
56        $(HC) --make $(HCFLAGS) -o $@ search.hs
57        strip $@
58hunk ./Makefile 70
59        $(HC) --make $(HCFLAGS) -o $@ pkg-list.hs
60        strip $@
61 
62+pkg-rev-deps: pkg-rev-deps.hs RevDepMap.hs Locations.hs Util.hs
63+       $(HC) --make $(HCFLAGS) -o $@ pkg-rev-deps.hs
64+       strip $@
65+
66 recent-adds: recent-adds.hs Locations.hs Util.hs
67        $(HC) --make $(HCFLAGS) -o $@ recent-adds.hs
68        strip $@
69hunk ./PackagePage.hs 7
70 import Control.Monad           ( guard, liftM2 )
71 import qualified Data.Foldable as Foldable
72 import Data.Char               ( toLower, toUpper, isSpace )
73-import Data.List               ( delete, intersperse, isPrefixOf,
74+import Data.List               ( intersperse, isPrefixOf,
75                                  partition, sort, sortBy )
76 import Data.Maybe
77 import Data.Map                        ( Map )
78hunk ./PackagePage.hs 28
79 import Util                    ( cabalFile, packageFile,
80                                  dirContents, getDependencies,
81                                  availableVersions, maybeLast, packageURL,
82-                                 packageDir, splitOn, catLabel )
83+                                 packageDir, splitOn, catLabel,
84+                                  getReverseDependencies, revDepsURL
85+                                )
86 import TagMap
87 import DistroInfo
88 
89hunk ./PackagePage.hs 53
90        , pdDependencies :: Map PackageName [Version]
91                -- ^ dependent packages from 'pdDescription', each paired
92                -- with available versions of that package (if any).
93+        , pdRevDeps :: [(PackageIdentifier, Int, Int, Bool)]
94+                -- ^ List of packages that depend on this package, the number
95+                -- of direct reverse dependencies per package, the number of
96+                -- indirect reverse dependencies per package and a flag
97+                -- indicating whether the package directly depends on the
98+                -- package in question. The first item in this list contains
99+                -- information about the original package.
100        , pdDocURL      :: Maybe URL
101                -- ^ URL of Haddock documentation for this version of the
102                -- package (if available).
103hunk ./PackagePage.hs 89
104        vs <- availableVersions (pkgName pkgId)
105 
106        deps <- getDependencies pkg
107+        revDeps <- getReverseDependencies pkgId
108 
109        let htmldir = pkgDir `slash` "doc" `slash` "html"
110        docExists <- doesDirectoryExist $ localFile htmldir
111hunk ./PackagePage.hs 106
112                , pdTags = tags tag_map
113                , pdAllVersions = vs
114                , pdDependencies = deps
115+                , pdRevDeps = revDeps
116                , pdDocURL = mbHtmlURL
117                , pdBuilds = builds
118                , pdBuildFailures = failures
119hunk ./PackagePage.hs 185
120                            (bold << display pversion) :
121                            map linkVers later_vs))] ++
122                [("Dependencies", html_deps_list)] ++
123+                [("Reverse deps.", html_rev_deps)] ++
124                [(fname, f_value) |
125                        (fname, htmlField) <- showFields,
126                        let f_value = htmlField pkg,
127hunk ./PackagePage.hs 214
128                intersperse (toHtml " " +++ bold (toHtml "or") +++ br) $
129                map (showDependencies vmap) deps_list
130        deps_list = flatDependencies (pdDescription pd)
131+        html_rev_deps | null revDeps = toHtml "No reverse dependencies"
132+                      | otherwise    = concatHtml [ anchor ! [href $ revDepsURL pkgId ++ "#direct"] <<
133+                                                      (showStrong numD +++ toHtml " direct")
134+                                                  , toHtml " and "
135+                                                  , anchor ! [href $ revDepsURL pkgId ++ "#indirect"] <<
136+                                                      (showStrong numI +++ toHtml " indirect")
137+                                                  , toHtml " dependencies on "
138+                                                  , anchor ! [href $ revDepsURL pkgId] <<
139+                                                      toHtml (display pkgId)
140+                                                  ]
141+            where ((_,numD,numI,_):_) = revDeps
142+        revDeps = pdRevDeps pd
143        successes = Map.keys (pdBuilds pd)
144        failures = Map.toList (Map.difference (pdBuildFailures pd) (pdBuilds pd))
145        showLog (desc, url) =
146hunk ./PackagePage.hs 242
147         dispTagVal "superseded by" v = anchor ! [href (packageURL (PackageIdentifier (PackageName v) (Version [] [])))] << v
148         dispTagVal _ v = toHtml v
149 
150+showStrong :: Show a => a -> Html
151+showStrong = strong . toHtml . show
152+
153 tabulate :: [(String, Html)] -> Html
154 -- tabulate items = dlist << concat [[dterm << t, ddef << d] | (t, d) <- items]
155 tabulate items = table ! [theclass "properties"] <<
156addfile ./RevDepMap.hs
157hunk ./RevDepMap.hs 1
158+{-# LANGUAGE ScopedTypeVariables #-}
159+
160+module RevDepMap ( RevDepMap
161+                 , calcRevDeps
162+                 , lookup
163+                 , latestVersions
164+                 , totalRevDeps
165+                 , totalRevDeps_st
166+                 ) where
167+
168+import Control.Monad                   ( filterM, fmap, foldM, mapM )
169+import Control.Monad.ST                ( ST, runST )
170+import Control.Monad.State.Lazy        ( State, evalState, get, put )
171+import Data.Function                   ( on )
172+import Data.List                       ( nub, groupBy )
173+import Data.Maybe                      ( fromMaybe )
174+import Data.STRef                      ( STRef, newSTRef
175+                                       , readSTRef, writeSTRef
176+                                       )
177+import Distribution.Package            ( Dependency(..)
178+                                       , PackageIdentifier(..)
179+                                       , PackageId
180+                                       , PackageName(..)
181+                                       )
182+import Distribution.Version            ( withinRange )
183+import Prelude                         hiding ( lookup )
184+
185+import qualified Data.Map as Mp
186+import qualified Data.Set as S
187+
188+
189+-------------------------------------------------------------------------------
190+-- Types
191+
192+-- |A RevDepMap maps a package 'x' to a set of packages which depend
193+-- on 'x' in some way.
194+type RevDepMap = Mp.Map PackageId (S.Set PackageId)
195+
196+-------------------------------------------------------------------------------
197+
198+-- |Transforms a reverse dependency map so that it contains only the last
199+-- versions of every reverse dependency.
200+latestVersions :: RevDepMap -> RevDepMap
201+latestVersions = fmap ( S.fromDistinctAscList
202+                      . map maximum
203+                      . groupBy ((==) `on` pkgName)
204+                      . S.toAscList
205+                      )
206+
207+-------------------------------------------------------------------------------
208+-- Direct reverse dependencies
209+
210+-- |Calculates the direct reverse dependencies of every package in the
211+-- input list.
212+calcRevDeps :: [(PackageId, [Dependency])] -> RevDepMap
213+calcRevDeps pkgs =
214+    foldr (\(pkg, deps) ->
215+            -- Per package on which this package depends, add this package as a
216+            -- reverse dependency.
217+            foldr2 (foldr2 (addRevDep pkg) . depToPkgs)
218+                   (nub deps)
219+          )
220+          emptyRevDepMap
221+          pkgs
222+    where
223+      -- A variant of foldr with the list and the default value flipped.
224+      foldr2 :: (a -> b -> b) -> [a] -> b -> b
225+      foldr2 = flip . foldr
226+
227+      {-
228+      -- If emptyRevDepMap is set to initRevDeps then the result of calcRevDeps
229+      -- will also contain packages with 0 reverse dependencies.
230+      initRevDeps :: RevDepMap
231+      initRevDeps = Mp.fromList [(pkg, S.empty) | (pkg, _) <- pkgs]
232+      -}
233+
234+      emptyRevDepMap = Mp.empty -- initRevDeps
235+
236+      addRevDep :: PackageId -> PackageId -> (RevDepMap -> RevDepMap)
237+      addRevDep src pkg = Mp.insertWith (const $ S.insert src)
238+                                        pkg
239+                                        $ S.singleton src
240+
241+      -- List of packages matching the dependency.
242+      depToPkgs :: Dependency -> [PackageId]
243+      depToPkgs (Dependency pkg vrange) =
244+          maybe []
245+                (filter (\depPkg -> withinRange (pkgVersion depPkg) vrange))
246+                $ Mp.lookup pkg nameToPkg
247+
248+      -- Maps a package name to ALL versions of the package in the database.
249+      nameToPkg :: Mp.Map PackageName [PackageId]
250+      nameToPkg = foldr ( \(pkg, _) -> Mp.insertWith (const (pkg :))
251+                                                     (pkgName pkg)
252+                                                     [pkg]
253+                        )
254+                        Mp.empty
255+                        pkgs
256+
257+-------------------------------------------------------------------------------
258+-- Total reverse dependencies (using Sets)
259+
260+-- |Transforms a direct reverse dependencies map into a total reverse
261+-- dependencies map.
262+totalRevDeps :: RevDepMap -> RevDepMap
263+totalRevDeps mp = Mp.mapWithKey (const . lookupAll mp) mp
264+
265+lookup :: RevDepMap -> PackageId -> S.Set PackageId
266+lookup mp k = fromMaybe S.empty $ Mp.lookup k mp
267+
268+lookupAll :: RevDepMap -> PackageId -> S.Set PackageId
269+lookupAll = dfs . lookup
270+
271+
272+-- |Depth first search
273+-- Given a graph and a starting node this function finds all nodes reachable
274+-- from that starting node. The graph is represented by a function from a node
275+-- to a set of nodes reachable from that node.
276+dfs :: forall a. Ord a => (a -> S.Set a) -> a -> S.Set a
277+dfs g r = evalState (search r) $ S.singleton r
278+    where search :: a -> State (S.Set a) (S.Set a)
279+          search n = do visited <- get
280+                        let new = S.difference (g n) visited
281+                        put $ S.union visited new
282+                        deep <- mapM search $ S.toList new
283+                        return $ S.union (S.unions deep) new
284+
285+
286+-------------------------------------------------------------------------------
287+-- Total reverse dependencies (using the ST monad)
288+
289+-- |Transforms a direct reverse dependencies map into a total reverse
290+-- dependencies map. It does this using an algorithm which is much more
291+-- efficient than the one using Sets.
292+totalRevDeps_st :: [PackageId] -> RevDepMap -> RevDepMap
293+totalRevDeps_st universe mp = runST go
294+    where
295+      go :: forall s. ST s RevDepMap
296+      go = do -- refMap :: Mp.Map PackageId (STRef s Bool)
297+              refMap <- fmap Mp.fromList
298+                             $ mapM (tagNode False) universe
299+
300+              -- Like the original RevDepMap, except the collection of reverse
301+              -- dependencies is represented by a list and associated with a
302+              -- tag.
303+              let tagged :: Mp.Map PackageId [(PackageId, STRef s Bool)]
304+                  tagged = Mp.map (map (\x -> (x, refMap Mp.! x)) . S.toList)
305+                                  mp
306+
307+              -- Calculate the total reverse dependencies by performing a depth
308+              -- first search per package and inserting the results in a newly
309+              -- constructed map.
310+              totalMp <- foldM (\mp' pkgId -> do
311+                                  -- Mark the initial node.
312+                                  writeSTRef (refMap Mp.! pkgId) True
313+                                  -- Calculate total reverse dependencies for current pkgId.
314+                                  revDeps <- dfs_st (\n -> fromMaybe [] $ Mp.lookup n tagged) pkgId
315+                                  -- Reset all tags for the next package.
316+                                  mapM_ (\r -> writeSTRef r False) $ Mp.elems refMap
317+                                  return $ Mp.insert pkgId (S.fromList revDeps) mp'
318+                               )
319+                               Mp.empty
320+                               $ Mp.keys mp
321+
322+              return totalMp
323+
324+      tagNode :: t -> n -> ST s (n, STRef s t)
325+      tagNode t n = newSTRef t >>= \r -> return (n, r)
326+
327+
328+-- |Depth first search
329+-- Given a graph and a starting node this function finds all nodes reachable
330+-- from that starting node. The graph is represented by a function from a node
331+-- to a list of nodes reachable from that node. Each node in that list is
332+-- associated with a mutable variable which marks whether that node was visited
333+-- before.
334+-- Precondition: All nodes are unmarked.
335+dfs_st :: forall a s. (a -> [(a, STRef s Bool)]) -> a -> ST s [a]
336+dfs_st g r = search r
337+    where search :: a -> ST s [a]
338+          search n = do new <- fmap (map fst) . filterM checkAndMark $ g n
339+                        deep <- mapM search new
340+                        return $ new ++ concat deep
341+
342+          checkAndMark :: (a, STRef s Bool) -> ST s Bool
343+          checkAndMark (_, ref) = do b <- readSTRef ref
344+                                     if b
345+                                       then return False
346+                                       else writeSTRef ref True >> return True
347addfile ./RevDepsPage.hs
348hunk ./RevDepsPage.hs 1
349+-- Body of the HTML page for the reverse dependencies of a package
350+module RevDepsPage (getPkgRevDepsBody) where
351+
352+import Data.Function        ( on )
353+import Data.List            ( intersperse, sortBy, takeWhile, dropWhile )
354+import Distribution.Package ( PackageIdentifier(..), PackageId )
355+import Distribution.Text    ( display )
356+import Distribution.Version ( Version )
357+import Text.XHtml
358+import Util                 ( availableVersions, packageURL, revDepsURL
359+                            , getReverseDependencies
360+                            )
361+
362+
363+getPkgRevDepsBody :: PackageId -> IO [Html]
364+getPkgRevDepsBody pkgId = do vs <- availableVersions (pkgName pkgId)
365+                             revDeps <- getReverseDependencies pkgId
366+                             return $ pkgRevDepsBody pkgId vs revDeps
367+
368+pkgRevDepsBody :: PackageId -> [Version] -> [(PackageId, Int, Int, Bool)] -> [Html]
369+pkgRevDepsBody pkgId vs revDeps = (h2 << pageTitle)
370+                                  : paragraph <<
371+                                      anchor ! [href $ packageURL pkgId] <<
372+                                        toHtml "Package description"
373+                                  : go revDeps
374+    where
375+      pageTitle = "Reverse Dependencies of " ++ display (pkgName pkgId)
376+
377+      go []     = noRevDeps
378+      go [_]    = noRevDeps
379+      go (x:xs) = withRevDeps x $ sortDeps xs
380+
381+      noRevDeps = [toHtml "No reverse dependencies"]
382+
383+      withRevDeps x xs =  overviewSection x vs
384+                       :  directRevDepSection   pkgId xs
385+                       ++ indirectRevDepSection pkgId xs
386+
387+      sortDeps = sortBy (flip compare `on` (\(_,numD,numI,_) -> numD + numI))
388+
389+
390+overviewSection :: (PackageId, Int, Int, Bool) -> [Version] -> Html
391+overviewSection (pkgId, numD, numI, _) vs =
392+    table <<
393+          [ tr ! [theclass "odd"] <<
394+                   [ th ! [theclass "horizontal"] <<
395+                        if null earlier_vs && null later_vs then "Version" else "Versions"
396+                   , td << commaList (map linkVers earlier_vs
397+                               ++ ((strong << display pversion) : map linkVers later_vs))
398+                   ]
399+          , tr ! [theclass "even"] <<
400+                   [ th ! [theclass "horizontal"] << "Direct reverse deps."
401+                   , td << anchor ! [href "#direct"] << toHtml (show numD)
402+                   ]
403+          , tr ! [theclass "odd"] <<
404+                   [ th ! [theclass "horizontal"] << "Indirect reverse deps."
405+                   , td << anchor ! [href "#indirect"] << toHtml (show numI)
406+                   ]
407+          ]
408+    where
409+        pversion = pkgVersion pkgId
410+       pname = pkgName pkgId
411+
412+       earlier_vs = takeWhile (< pversion) vs
413+       later_vs = dropWhile (<= pversion) vs
414+
415+       linkVers v = anchor ! [href (revDepsURL (PackageIdentifier pname v))] <<
416+                      display v
417+
418+directRevDepSection :: PackageId -> [(PackageId, Int, Int, Bool)] -> [Html]
419+directRevDepSection pkgId xs =
420+    revDepSection xs
421+                  True
422+                  "direct"
423+                  "Direct reverse dependencies"
424+                  $ concatHtml [ toHtml "A package is a direct reverse dependency of "
425+                               , packageLink pkgId
426+                               , toHtml " if it directly depend on "
427+                               , packageLink pkgId
428+                               ]
429+
430+indirectRevDepSection :: PackageId -> [(PackageId, Int, Int, Bool)] -> [Html]
431+indirectRevDepSection pkgId xs =
432+    revDepSection xs
433+                  False
434+                  "indirect"
435+                  "Indirect reverse dependencies"
436+                  $ concatHtml [ toHtml "A package is an indirect reverse dependency of "
437+                               , packageLink pkgId
438+                               , toHtml " if it depends on either a direct reverse dependency or an indirect reverse dependency of "
439+                               , packageLink pkgId
440+                               ]
441+
442+revDepSection :: [(PackageId, Int, Int, Bool)] -> Bool -> String -> String -> Html -> [Html]
443+revDepSection revDeps direct sname sectionTitle desc
444+    | null filteredRevDeps = [sectionAnchor]
445+    | otherwise  = [ sectionAnchor
446+                   , h3 << sectionTitle
447+                   , paragraph << desc
448+                   , revDepList filteredRevDeps
449+                   ]
450+    where
451+      filteredRevDeps = filterOnDirect direct revDeps
452+
453+      filterOnDirect :: Bool -> [(PackageId, Int, Int, Bool)] -> [(PackageId, Int, Int)]
454+      filterOnDirect x = map (\(pkgId, numD, numI, _) -> (pkgId, numD, numI))
455+                       . filter (\(_,_,_,isD) -> isD == x)
456+
457+      sectionAnchor = anchor ! [name sname] << noHtml
458+
459+revDepList :: [(PackageId, Int, Int)] -> Html
460+revDepList deps =
461+    table <<
462+      ( tr << [ th << stringToHtml "Package"
463+              , th << stringToHtml "Direct"
464+              , th << stringToHtml "Indirect"
465+              ]
466+      : [ tr ! [theclass $ rowClass row] <<
467+          [ td << packageLink depPkgId
468+          , td << revDepsLink depPkgId "#direct"   (toHtml $ show numD)
469+          , td << revDepsLink depPkgId "#indirect" (toHtml $ show numI)
470+          ]
471+        | (row, (depPkgId, numD, numI)) <- zip [1..] deps
472+        ]
473+      )
474+
475+packageLink :: PackageId -> Html
476+packageLink pkgId = anchor ! [href $ packageURL pkgId] <<
477+                      toHtml (display pkgId)
478+
479+revDepsLink :: PackageId -> String -> Html -> Html
480+revDepsLink pkgId extra contents = anchor ! [href $ revDepsURL pkgId ++ extra] << contents
481+
482+rowClass :: Int -> String
483+rowClass n | odd n     = "odd"
484+           | otherwise = "even"
485+
486+-- Copied from PackagePage
487+commaList :: [Html] -> Html
488+commaList = concatHtml . intersperse (toHtml ", ")
489hunk ./Util.hs 4
490 module Util where
491 
492 import Control.Exception       ( bracket )
493-import Control.Monad           ( unless, filterM, liftM )
494+import Control.Monad           ( unless, filterM, liftM, guard )
495 import Data.Bits               ( (.&.), complement )
496 import Data.Char               ( isSpace, toLower )
497 import Data.List               ( (\\), sort )
498hunk ./Util.hs 10
499 import Data.Map                        ( Map )
500 import qualified Data.Map as Map
501-import Data.Maybe              ( listToMaybe )
502+import Data.Maybe              ( listToMaybe, fromMaybe )
503 import qualified Data.Set as Set ( fromList, toList )
504 import Distribution.Compat.ReadP( readP_to_S )
505 import Distribution.Package    ( PackageName(..), PackageIdentifier(..),
506hunk ./Util.hs 31
507 import Text.XHtml              ( URL )
508 
509 import PublicFile
510-import Locations               ( archiveDir, pkgScriptURL )
511+import Locations               ( archiveDir, pkgScriptURL, pkgRevDepsURL )
512 
513 -- | Registered top-level nodes in the class hierarchy.
514 allocatedTopLevelNodes :: [String]
515hunk ./Util.hs 48
516 packageNameURL :: PackageName -> URL
517 packageNameURL pkg = pkgScriptURL ++ "/" ++ display pkg
518 
519+-- | URL describing the reverse dependencies of a package, including version.
520+revDepsURL :: PackageIdentifier -> URL
521+revDepsURL pkgId = pkgRevDepsURL ++ "/" ++ display pkgId
522+
523+-- | URL describing the cached reverse dependencies of a package.
524+revDepsFile :: PackageIdentifier -> PublicFile
525+revDepsFile pkgId = packageDir pkgId `slash` "revdeps.csv"
526+
527 -- package utilities
528 
529 -- | Available versions (if any) for each package mentioned in this one.
530hunk ./Util.hs 76
531                vs <- availableVersions n
532                return (n, vs)
533 
534+-- | List of packages that depend on this one.
535+getReverseDependencies :: PackageIdentifier -> IO [(PackageIdentifier, Int, Int, Bool)]
536+getReverseDependencies pkgId = do hasRevDeps <- doesFileExist fp
537+                                  if hasRevDeps
538+                                    then do revDepFile <- readFile fp
539+                                            let xs = lines revDepFile
540+                                            return . fromMaybe [] . sequence $ map parseLine xs
541+                                    else return []
542+    where
543+      -- This could be made much simpler by using functions from the "split" package.
544+      parseLine :: String -> Maybe (PackageIdentifier, Int, Int, Bool)
545+      parseLine l = do let (pname, rest1) = spanTillComma l
546+                       guard (not $ null rest1)
547+                       let (direct, rest2) = spanTillComma (tail rest1)
548+                       guard (not $ null rest2)
549+                       let (indirect, rest3) = spanTillComma (tail rest2)
550+                       guard (not $ null rest3)
551+                       let isDirect = tail rest3
552+                       guard (isDirect `elem` ["D", "I"])
553+                       let xs = readP_to_S parse pname
554+                       guard ( not (null xs)
555+                             && length direct > 0
556+                             && length indirect > 0
557+                             )
558+                       return ( fst $ last xs
559+                              , read direct
560+                              , read indirect
561+                              , isDirect == "D"
562+                              )
563+      spanTillComma = span (/= ',')
564+      fp = localFile $ revDepsFile pkgId
565+
566 -- | All package names available in the archive
567 availablePackages :: IO [PackageName]
568 availablePackages = do
569hunk ./hackage-scripts.cabal 30
570 other-modules:  HaddockLex HaddockParse HaddockHtml HackagePage
571                 Locations ModuleForest PackagePage Unpack Util
572 
573+executable:     revdeps
574+main-is:        revdeps.hs
575+other-modules:  Locations RevDepsPage Util
576+
577 executable:     search
578 main-is:        search.hs
579 other-modules:  SearchAlgorithm SearchMatchingFunctions SearchUtils
580hunk ./hackage-scripts.cabal 50
581 executable:     rss-feed
582 main-is:        rss-feed.hs
583 other-modules:  Locations Util
584+
585+executable:     pkg-rev-deps
586+main-is:        pkg-rev-deps.hs
587+other-modules:  Util
588addfile ./pkg-rev-deps.hs
589hunk ./pkg-rev-deps.hs 1
590+-- Generate a .csv file in every package directory containing the
591+-- reverse dependencies of that package.
592+
593+module Main where
594+
595+import Control.Monad                   ( fmap, mapM  )
596+import Data.List                       ( intercalate )
597+import Distribution.Package            ( Dependency(..)
598+                                       , PackageIdentifier(..)
599+                                       , PackageId
600+                                       , PackageName(..)
601+                                       )
602+import Distribution.PackageDescription ( PackageDescription(..) )
603+import Distribution.PackageDescription.Configuration
604+                                       ( flattenPackageDescription )
605+import Distribution.Text               ( display )
606+import PublicFile                      ( PublicFile, localFile )
607+import System.IO                       ( writeFile )
608+import Util                            ( availablePackages
609+                                       , availableVersions
610+                                       , loadPackageDescription
611+                                       , revDepsFile
612+                                       )
613+
614+import qualified Data.Map  as Mp
615+import qualified Data.Set  as S
616+import qualified RevDepMap as RD
617+
618+-------------------------------------------------------------------------------
619+-- Main
620+
621+main :: IO ()
622+main = do pkgs <- allPackages
623+          let revDepMap     = RD.calcRevDeps pkgs
624+              directRevDeps = RD.latestVersions revDepMap
625+              -- Reference implementation, but much slower than the ST monad version.
626+              -- allRevDeps   = RD.latestVersions $ RD.totalRevDeps revDepMap
627+              allRevDeps   = RD.latestVersions $ RD.totalRevDeps_st (map fst pkgs) revDepMap
628+          writeRevDepFiles directRevDeps allRevDeps
629+
630+-------------------------------------------------------------------------------
631+
632+allPackages :: IO [(PackageId, [Dependency])]
633+allPackages = do pkgNames <- availablePackages
634+                 pkgDescs <- mapM availablePkgs pkgNames
635+                 return $ concat pkgDescs
636+
637+availablePkgIds :: PackageName -> IO [PackageId]
638+availablePkgIds p = fmap (map $ PackageIdentifier p) $ availableVersions p
639+
640+availablePkgs :: PackageName -> IO [(PackageId, [Dependency])]
641+availablePkgs p = mapM perPkgId =<< availablePkgIds p
642+    where perPkgId pkgId = do
643+            genDesc <- loadPackageDescription pkgId
644+            let desc   = flattenPackageDescription genDesc
645+                deps   = buildDepends desc
646+                result = pkgId `seq` deps `seq` (pkgId, deps)
647+            -- Strict evaluation of PackageId and [Dependency] so that the now
648+            -- useless PackageDescription is no longer retained.
649+            result `seq` return result
650+
651+-------------------------------------------------------------------------------
652+
653+-- |Stores the reverse dependency information of all packages in the database.
654+writeRevDepFiles :: RD.RevDepMap -> RD.RevDepMap -> IO ()
655+writeRevDepFiles direct total = mapM_ (uncurry $ writeRevDepFile direct total)
656+                                      $ Mp.assocs total
657+
658+{-| Stores the reverse dependency information of a package in the database.
659+
660+The first line in the file contains information about the package itself. The
661+following lines pertain to its reverse dependencies.
662+
663+Each line consists of 4 fields, separated by ',':
664+
665+1. The PackageIdentifier
666+2. Number of direct reverse dependencies
667+3. Number of indirect reverse dependencies (total - direct)
668+4. Flag indicating whether this package is a direct reverse dependency or an
669+   indirect one ('D' or 'I').
670+-}
671+writeRevDepFile :: RD.RevDepMap -> RD.RevDepMap -> PackageId -> S.Set PackageId -> IO ()
672+writeRevDepFile direct total pkgId totalRevDeps =
673+      writeFile (localFile $ revDepsFile pkgId) fileContents
674+    where
675+      directDeps :: S.Set PackageId
676+      directDeps = RD.lookup direct pkgId
677+
678+      fileContents :: String
679+      fileContents = unlines
680+                   . map (\dep -> let numDirect = S.size $ RD.lookup direct dep
681+                                      numTotal  = S.size $ RD.lookup total  dep
682+                                      numIndirect = numTotal - numDirect
683+                                  in intercalate ","
684+                                       [ display dep
685+                                       , show numDirect
686+                                       , show numIndirect
687+                                       , if S.member dep directDeps
688+                                         then "D" -- (D)irect
689+                                         else "I" -- (I)ndirect
690+                                       ]
691+                         )
692+                         $ pkgId : S.toAscList totalRevDeps
693addfile ./revdeps.hs
694hunk ./revdeps.hs 1
695+-- CGI program listing package reverse dependencies
696+module Main (main) where
697+
698+import Distribution.Package     ( PackageIdentifier(..) )
699+import Distribution.Version     ( Version(..) )
700+import Distribution.Text       ( display )
701+import HackagePage              ( hackagePage )
702+import Locations               ( pkgListURL )
703+import Network.CGI             ( CGI, CGIResult, runCGI, handleErrors
704+                                , liftIO, output, outputNotFound
705+                                , pathInfo, redirect
706+                                )
707+import PublicFile               ( localFile )
708+import RevDepsPage              ( getPkgRevDepsBody )
709+import System.Directory         ( doesFileExist )
710+import Text.XHtml              ( renderHtml )
711+import Util                    ( splitOn, availableVersions
712+                                , cabalFile, maybeLast
713+                                , readPackageId, revDepsURL
714+                                )
715+
716+main :: IO ()
717+main = runCGI . handleErrors $ do
718+        path <- pathInfo
719+       let pathParts = filter (not . null) $ splitOn '/' path
720+       case pathParts of
721+           [] -> redirect pkgListURL
722+           [pname] ->
723+               case readPackageId pname of
724+                   Just pkgId -> showRevDeps pkgId
725+                   Nothing -> outputNotFound "malformed package identifier"
726+           _ -> outputNotFound "malformed URL"
727+
728+showRevDeps :: PackageIdentifier -> CGI CGIResult
729+showRevDeps (PackageIdentifier pname (Version [] [])) = do
730+    -- name only: get the most recent version
731+    vs <- liftIO (availableVersions pname)
732+    case maybeLast vs of
733+      Just v -> redirect (revDepsURL (PackageIdentifier pname v))
734+      Nothing -> outputNotFound $
735+                "no such package '" ++ display pname ++ "'"
736+showRevDeps pkgId = do e <- liftIO $ doesFileExist (localFile $ cabalFile pkgId)
737+                       if e
738+                         then do page <- liftIO $ getPkgRevDepsBody pkgId
739+                                 output . renderHtml
740+                                        . hackagePage pkgIdStr
741+                                        $ page
742+                         else outputNotFound ("no such package '" ++ pkgIdStr ++ "'")
743+  where pkgIdStr = display pkgId
744+
745}
746
747Context:
748
749[compile with -O
750Ross Paterson <ross@soi.city.ac.uk>**20090824150826
751 Ignore-this: 67ea1327df7bd8df059694d45a16dc0e
752]
753[tweak locations of distromaps
754Ross Paterson <ross@soi.city.ac.uk>**20090824150257
755 Ignore-this: d642276f3667e3a90065e57005089bd6
756]
757[Mention splitDistroMap in README
758Joachim Breitner <mail@joachim-breitner.de>**20090822165007
759 Ignore-this: 4af14467aa9dc0267dde9b6b886de4dc
760]
761[Avoid compiler warnings in code added by me
762Joachim Breitner <mail@joachim-breitner.de>**20090822164654
763 Ignore-this: c144ce4997793fefc1bcfb006c21abca
764]
765[Add splitDistroMap to Makefile
766Joachim Breitner <mail@joachim-breitner.de>**20090822164644
767 Ignore-this: 11d7c96d0158487d03285cb6cb6a366
768]
769[Use the new per-package DistroInfo API in PackagePage
770Joachim Breitner <mail@joachim-breitner.de>**20090822164057
771 Ignore-this: 918119c885ae61604bbeb7e8806a5642
772]
773[Binary program to split the distro map
774Joachim Breitner <mail@joachim-breitner.de>**20090822163821
775 Ignore-this: 6c0d2733d78e772582f8a8f142ebbee4
776]
777[DistroInfo.splitDistroInfo
778Joachim Breitner <mail@joachim-breitner.de>**20090822163712
779 Ignore-this: 7340701ff507210abfe06cb3048d4935
780 
781 Add a function to DistroInfo to split the all-archive-distro-maps per package,
782 and write out short files per PackageName (not package+version) with only the
783 information for that package, to reduce parsing time.
784]
785[New function Util.unversionedPackageDir
786Joachim Breitner <mail@joachim-breitner.de>**20090822163609
787 Ignore-this: bf246d5c43642614884da3a26bbd001f
788 
789 This is like packageDir, but takes a PackageName instead of a PackageIdentifier
790 and thus returns the parent directory of packageDir. This new function is then
791 also used in packageDir.
792]
793[Implement DistroInfo
794Joachim Breitner <mail@joachim-breitner.de>**20090726132613
795 Ignore-this: 1e2fee85bbad474dc2eef2377e50a1e1
796]
797[Add a note in the README about the files
798Joachim Breitner <mail@joachim-breitner.de>**20090726131937
799 Ignore-this: 7d581e418b9c82a24d1367946ac81f50
800]
801[Set up DistroInfo module
802Joachim Breitner <mail@joachim-breitner.de>**20090726113015
803 Ignore-this: 4c4e6a6fe6fa5a9044363097b5005159
804]
805[fix haddock lexer (fixes #569)
806Ross Paterson <ross@soi.city.ac.uk>**20090801141552
807 Ignore-this: 561b885aea712bf59fedac5e0928740a
808]
809[show source repository
810Ross Paterson <ross@soi.city.ac.uk>**20090706170551
811 Ignore-this: 234c01bf707eaec9ac4efc8ca33ae7e6
812 
813 Only shows the development repo, and may be a bit simplistic.
814]
815[shorter package URL
816Ross Paterson <ross@soi.city.ac.uk>**20090611154034
817 Ignore-this: c812b49a1ca575e9c952d768860f3e2a
818]
819[canonicalize category links
820Ross Paterson <ross@soi.city.ac.uk>**20090611154013
821 Ignore-this: 4b320ba32d1aee8b92ca9c96e76bf112
822]
823[use display instead of show on license field
824Ross Paterson <ross@soi.city.ac.uk>**20090119134457]
825[handle WildcardVersion
826Ross Paterson <ross@soi.city.ac.uk>**20081225024413]
827[explicit imports to avoid clash with Cabal-1.7
828Ross Paterson <ross@soi.city.ac.uk>**20081222124657]
829[add bug tracker link (#415)
830Ross Paterson <ross@soi.city.ac.uk>**20081126232839]
831[non-semantic changes to sync lexer and parser with versions in GHC
832Ross Paterson <ross@soi.city.ac.uk>**20081115170018]
833[update Haddock parser to 0.9 (fixes #406)
834Ross Paterson <ross@soi.city.ac.uk>**20081114235827]
835[record times in UTC
836Ross Paterson <ross@soi.city.ac.uk>**20081109151646]
837[show build failures and sucesses on different versions of GHC
838Ross Paterson <ross@soi.city.ac.uk>**20081109135425]
839[Read the .cabal file as UTF8 when unpacking (from Duncan Coutts)
840Ross Paterson <ross@soi.city.ac.uk>**20081101154633
841 Previously the upload check preview displayed incorrect encoding of
842 people's names and did not validate incorrect UTF8 encodings.
843]
844[use sparklines generated on an upload
845Ross Paterson <ross@soi.city.ac.uk>**20081024122101]
846[add graph to Recent additions page
847Ross Paterson <ross@soi.city.ac.uk>**20081024001245]
848[uniform layout whether or not haddock present, with italics for non-module nodes
849Ross Paterson <ross@soi.city.ac.uk>**20081014100238]
850[reformat the module list to look a bit more like Haddock's
851Ross Paterson <ross@soi.city.ac.uk>**20081014083632]
852[Render exposed modules as a nested list (#308)
853Ross Paterson <ross@soi.city.ac.uk>**20081014074946
854 
855 Written by Bas van Dijk.
856]
857[Marking packages deprecated
858Chry Cheng <chrycheng@gmail.com>**20080828145516
859 Fixes ticket no. 261 as discussed in its annotations.  Packages with "deprecated" "true" are excluded from the package list.  Packages with "superseded by" tags provide links to their superseding packages in the package page.
860]
861[Update deps to require Cabal-1.6
862Duncan Coutts <duncan@haskell.org>**20081011002506]
863[Update install procedure to install the preferred-versions
864Duncan Coutts <duncan@haskell.org>**20081010000322]
865[Add the initial preferred-versions list
866Duncan Coutts <duncan@haskell.org>**20081010000306]
867[Add the preferred-versions file into the 00-index.tar.gz file
868Duncan Coutts <duncan@haskell.org>**20081009235419
869 Old clients should ignore it.
870]
871[update files using mv to avoid corruption
872Ross Paterson <ross@soi.city.ac.uk>**20080907114910]
873[put synopsis in header, show version in context
874Ross Paterson <ross@soi.city.ac.uk>**20080905162925]
875[fix warning
876Ross Paterson <ross@soi.city.ac.uk>**20080905162858]
877[eliminate redundant dependencies
878Ross Paterson <ross@soi.city.ac.uk>**20080902001615]
879[update for Cabal 1.5
880Ross Paterson <ross@soi.city.ac.uk>**20080827020703]
881[use "none" for unmaintained packages
882Ross Paterson <ross@soi.city.ac.uk>**20080723224421]
883[add Hayoo to menu bar
884Ross Paterson <ross@soi.city.ac.uk>**20080723214433]
885[use a single methods to get the package info for display
886Ross Paterson <ross@soi.city.ac.uk>**20080628231737]
887[don't build index asynchronously
888Ross Paterson <ross@soi.city.ac.uk>**20080628231704]
889[flag null maintainer field on package page
890Ross Paterson <ross@soi.city.ac.uk>**20080628231424]
891[use non-versioned links in the package index (#271, #278)
892Ross Paterson <ross@soi.city.ac.uk>**20080510234607]
893[if a package has built, ignore any failures
894Ross Paterson <ross@soi.city.ac.uk>**20080425154935]
895[track changes to checkPackage
896Ross Paterson <ross@soi.city.ac.uk>**20080424003335]
897[add a meta tag declaring the charset as ISO-8859-1, as that's what Text.XHtml generates
898Ross Paterson <ross@soi.city.ac.uk>**20080328154601]
899[make parse warnings fatal
900Ross Paterson <ross@soi.city.ac.uk>**20080328121040]
901[update to Cabal 1.3.9
902Ross Paterson <ross@soi.city.ac.uk>**20080328120711]
903[disallow updating an existing package
904Ross Paterson <ross@soi.city.ac.uk>**20080328120600]
905[generate 00-index.tar.gz asynchronously
906Ross Paterson <ross@soi.city.ac.uk>**20080302105639]
907[prune the search for cabal files to make it go faster
908Ross Paterson <ross@soi.city.ac.uk>**20080302105432]
909[add small latest-versions lister
910Ross Paterson <ross@soi.city.ac.uk>**20080221134229]
911[use absolute filenames when looking for packages
912Ross Paterson <ross@soi.city.ac.uk>**20080221124040]
913[longer label on the search button
914Ross Paterson <ross@soi.city.ac.uk>**20080221123956]
915[simple implementation of tags, starting with upload info
916Ross Paterson <ross@soi.city.ac.uk>**20080216021131]
917[swap arguments to extraChecks
918Ross Paterson <ross@soi.city.ac.uk>**20080214182808]
919[fix warning
920Ross Paterson <ross@soi.city.ac.uk>**20080214182753]
921[Update to latest Cabal lib API and use new package checking code
922Duncan Coutts <duncan@haskell.org>**20080213201347
923 Compiles but otherwise totally untested.
924]
925[remove private copies of functions now in ghc 6.8
926Ross Paterson <ross@soi.city.ac.uk>**20080212011430]
927[introduce PublicFile for files visible through the web
928Ross Paterson <ross@soi.city.ac.uk>**20080212005643]
929[refactoring of upload and check scripts
930Ross Paterson <ross@soi.city.ac.uk>**20080211131109]
931[unpack the whole directory, not just the .cabal file
932Ross Paterson <ross@soi.city.ac.uk>**20080211131009]
933[add a Google search box to the package list page
934Ross Paterson <ross@soi.city.ac.uk>**20080203021954]
935[swap depends-on and required-by maps
936Ross Paterson <ross@soi.city.ac.uk>**20080202012507]
937[list successful and unsuccessful builds, with logs
938Ross Paterson <ross@soi.city.ac.uk>**20080126013443]
939[blacklist Application, Tool and Type categories
940Ross Paterson <ross@soi.city.ac.uk>**20071213074800]
941[add author
942Ross Paterson <ross@soi.city.ac.uk>**20071213074632]
943[correct and simplify the library and programs test
944Ross Paterson <ross@soi.city.ac.uk>**20071210104009]
945[capitalize category names
946Ross Paterson <ross@soi.city.ac.uk>**20071130143321]
947[merge category names that differ only in case
948Ross Paterson <ross@soi.city.ac.uk>**20071130142342]
949[render equality constraints concisely
950Ross Paterson <ross@soi.city.ac.uk>**20071128005652]
951[ignore case when sorting lists of dependent packages
952Ross Paterson <ross@soi.city.ac.uk>**20071120123549]
953[display dependencies in disjunctive normal form
954Ross Paterson <ross@soi.city.ac.uk>**20071024225216]
955[Oops, need the flattened package description to get exposed modules and executables
956Ross Paterson <ross@soi.city.ac.uk>**20071024070848]
957[use GenericPackageDescription instead of PackageDescription
958Ross Paterson <ross@soi.city.ac.uk>**20071024064933]
959[now need Cabal >= 1.2.1
960Ross Paterson <ross@soi.city.ac.uk>**20071021155643]
961[update for Cabal 1.2.1
962Ross Paterson <ross@soi.city.ac.uk>**20071021155322]
963[cabal packaging (based on work of Trevor Elliott)
964Ross Paterson <ross@soi.city.ac.uk>**20071019124210]
965[also install search
966Ross Paterson <ross@soi.city.ac.uk>**20070909185526]
967[Added search functionality (by Sascha Böhme), but not yet in main menu
968Ross Paterson <ross@soi.city.ac.uk>**20070909140603]
969[update for Cabal-1.2
970Ross Paterson <ross@soi.city.ac.uk>**20070907234506]
971[ensure the package list contains latest versions, plus some refacting
972Ross Paterson <ross@soi.city.ac.uk>**20070723204248]
973[add link to build log to package page
974Ross Paterson <ross@soi.city.ac.uk>**20070720124702]
975[minor build updates
976Ross Paterson <ross@soi.city.ac.uk>**20070617213315]
977[fix rendering of identifiers
978Ross Paterson <ross@soi.city.ac.uk>**20070617213249]
979[add missing file
980Ross Paterson <ross@soi.city.ac.uk>**20070530140019]
981[bugfix for previous commit
982Ross Paterson <ross@soi.city.ac.uk>**20070510142803]
983[fix error in building index
984Ross Paterson <ross@soi.city.ac.uk>**20070508165611]
985[include a package in the list only if it has a Cabal file
986Ross Paterson <ross@soi.city.ac.uk>**20070506180938]
987[change the directory layout of the HackageDB data
988Ross Paterson <ross@soi.city.ac.uk>**20070506151844
989 
990 Put version in a separate directory (to simplify future expansion).
991 This will not affect users of the web interface, but will be a breaking
992 change for those who reference the files directly, notably cabal-install.
993 
994 Here's how the positions of the files of the binary package change
995 (-> denotes a symbolic link):
996 
997 Old layout                              New layout
998 --------------------------------------------------------------------
999 binary/binary-0.2.cabal                 binary/0.2/binary.cabal
1000 binary/binary-0.2.tar.gz                binary/0.2/binary-0.2.tar.gz
1001 binary/binary-0.2.misc/doc/html/        binary/0.2/doc/html/
1002 binary/binary-0.3.cabal                 binary/0.3/binary.cabal
1003 binary/binary-0.3.tar.gz                binary/0.3/binary-0.3.tar.gz
1004 binary/binary-0.3.misc/doc/html/        binary/0.3/doc/html/
1005 binary/latest.misc -> binary-0.3.misc   binary/latest -> 0.3
1006]
1007[catch "Unclassified" category
1008Ross Paterson <ross@soi.city.ac.uk>**20070307011355]
1009[use local copy of Cabal logo
1010Ross Paterson <ross@soi.city.ac.uk>**20070225125801]
1011[make "recent additions" a generated page instead of a CGI script
1012Ross Paterson <ross@soi.city.ac.uk>**20070220235702]
1013[tweaks to package list
1014Ross Paterson <ross@soi.city.ac.uk>**20070220235601]
1015[generate RSS feed of recent updates
1016Ross Paterson <ross@soi.city.ac.uk>**20070214185538]
1017[bug fix: show preview even if no warnings
1018Ross Paterson <ross@soi.city.ac.uk>**20070213180708]
1019[check-pkg also returns plain text if requested
1020Ross Paterson <ross@soi.city.ac.uk>**20070213001039]
1021[for plain text clients, return only the warnings
1022Ross Paterson <ross@soi.city.ac.uk>**20070211171633]
1023[use CGI type synonym
1024Ross Paterson <ross@soi.city.ac.uk>**20070210152353]
1025[refactor pkgBody arguments as a record
1026Ross Paterson <ross@soi.city.ac.uk>**20070208194243]
1027[strip executables
1028Ross Paterson <ross@soi.city.ac.uk>**20070207233429]
1029[mark property table
1030Ross Paterson <ross@soi.city.ac.uk>**20070207233402]
1031[point to accounts page
1032Ross Paterson <ross@soi.city.ac.uk>**20070207230638]
1033[install upload-pkg in the correct place
1034Ross Paterson <ross@soi.city.ac.uk>**20070206001525]
1035[new location <pkgid>.misc/doc for docs
1036Ross Paterson <ross@soi.city.ac.uk>**20070205004623]
1037[versioned haddock documentation
1038Ross Paterson <ross@soi.city.ac.uk>**20070203202526]
1039[if PACKAGE/doc/html exists, module names are links into it
1040Ross Paterson <ross@soi.city.ac.uk>**20070203170215
1041 (Generation of haddock documentation is not yet automated, though)
1042]
1043[minor refactoring
1044Ross Paterson <ross@soi.city.ac.uk>**20070202121300]
1045[change package links from pkg/vers to pkg-vers
1046Ross Paterson <ross@soi.city.ac.uk>**20070201143905]
1047[package-ids are unambiguous
1048Ross Paterson <ross@soi.city.ac.uk>**20070201132112]
1049[allow version tags (they seem harmless)
1050Ross Paterson <ross@soi.city.ac.uk>**20070201131251]
1051[trim long synopses in package list
1052Ross Paterson <ross@soi.city.ac.uk>**20070201100052]
1053[tag category list
1054Ross Paterson <ross@soi.city.ac.uk>**20070131121652]
1055[some re-arrangement
1056Ross Paterson <ross@soi.city.ac.uk>**20070131003654]
1057[add category index to package list
1058Ross Paterson <ross@soi.city.ac.uk>**20070130180021]
1059[tweak Cabal logo
1060Ross Paterson <ross@soi.city.ac.uk>**20070129130941]
1061[add Cabal branding to package page
1062Ross Paterson <ross@soi.city.ac.uk>**20070129122133]
1063[mark package lists with an element class
1064Ross Paterson <ross@soi.city.ac.uk>**20070129092724]
1065[revert to textual indication of package type
1066Ross Paterson <ross@soi.city.ac.uk>**20070129080416]
1067[cleanup
1068Ross Paterson <ross@soi.city.ac.uk>**20070129025127]
1069[more compact presentation of package list
1070Ross Paterson <ross@soi.city.ac.uk>**20070129021339]
1071[show other versions on package page
1072Ross Paterson <ross@soi.city.ac.uk>**20070129020229]
1073[move cabalFile to Util
1074Ross Paterson <ross@soi.city.ac.uk>**20070129014325]
1075[pkg-list: only read the most recent Cabal file for each package
1076Ross Paterson <ross@soi.city.ac.uk>**20070129012719]
1077[trim unused functions
1078Ross Paterson <ross@soi.city.ac.uk>**20070129012355]
1079[add export lists for Main modules
1080Ross Paterson <ross@soi.city.ac.uk>**20070129012315]
1081[fix dependency
1082Ross Paterson <ross@soi.city.ac.uk>**20070129005924]
1083[refactoring of version search
1084Ross Paterson <ross@soi.city.ac.uk>**20070129005845]
1085[switch to Text.XHtml
1086Ross Paterson <ross@soi.city.ac.uk>**20070129001802]
1087[more graceful error on missing package
1088Ross Paterson <ross@soi.city.ac.uk>**20070127171227]
1089[minor simplification
1090Ross Paterson <ross@soi.city.ac.uk>**20070127164406]
1091[minor refactoring
1092Ross Paterson <ross@soi.city.ac.uk>**20070127143750]
1093[shuffle stuff between modules
1094Ross Paterson <ross@soi.city.ac.uk>**20070127142603]
1095[change install to scp + mv
1096Ross Paterson <ross@soi.city.ac.uk>**20070127131030]
1097[reword check output
1098Ross Paterson <ross@soi.city.ac.uk>**20070127130746]
1099[add upload.html
1100Ross Paterson <ross@soi.city.ac.uk>**20070126210510]
1101[re-order things on package page
1102Ross Paterson <ross@soi.city.ac.uk>**20070126204017]
1103[minor refactoring
1104Ross Paterson <ross@soi.city.ac.uk>**20070126202141]
1105[tabulate fields
1106Ross Paterson <ross@soi.city.ac.uk>**20070126201050]
1107[remove superfluous thehtml
1108Ross Paterson <ross@soi.city.ac.uk>**20070126174820]
1109[ensure that showPackageId produces the original pkg-id
1110Ross Paterson <ross@soi.city.ac.uk>**20070126150901
1111 
1112 Rule out version numbers like "1.00" -> [1,0] -> "1.0".  These ought to be
1113 rejected by the parser.
1114]
1115[add boilerplate header to each page
1116Ross Paterson <ross@soi.city.ac.uk>**20070126143901]
1117[remove old form-based parameters to package script
1118Ross Paterson <ross@soi.city.ac.uk>**20070126115030]
1119[tweak category headings
1120Ross Paterson <ross@soi.city.ac.uk>**20070126112054]
1121[make package URLs a bit shorter
1122Ross Paterson <ross@soi.city.ac.uk>**20070125105147
1123 
1124 * remove .cgi from CGI scripts in cgi-bin
1125 
1126 * use PATHINFO instead of form data to identify packages.
1127 
1128 So now its .../package/<pkg>/<version> or just .../package/<pkg>
1129]
1130[some refactoring
1131Ross Paterson <ross@soi.city.ac.uk>**20070118102430]
1132[append a preview of the package page to the check-pkg output
1133Ross Paterson <ross@soi.city.ac.uk>**20070118003003]
1134[add a README file with overview and install instructions
1135Ross Paterson <ross@soi.city.ac.uk>**20070116153445]
1136[clean up locations a bit
1137Ross Paterson <ross@soi.city.ac.uk>**20070116123020]
1138[parse and markup package descriptions with code stolen from Haddock
1139Ross Paterson <ross@soi.city.ac.uk>**20070116105520]
1140[temporarily downgrade repeated-upload check to a warning
1141Ross Paterson <ross@soi.city.ac.uk>**20070116073009
1142 
1143 The check seems like a good idea in the longer term, but
1144 It's a bit cumbersome while we're all still experimenting.
1145]
1146[more compact log lines
1147Ross Paterson <ross@soi.city.ac.uk>**20070113010328]
1148[fix previous commit
1149Ross Paterson <ross@soi.city.ac.uk>**20070112155632]
1150[use errorOutput for error cases
1151Ross Paterson <ross@soi.city.ac.uk>**20070112151251]
1152[basename: strip to slash or backslash (which IE includes in filenames)
1153Ross Paterson <ross@soi.city.ac.uk>**20070112143846]
1154[use a different tmp directory for each process
1155Ross Paterson <ross@soi.city.ac.uk>**20070111010943]
1156[don't accept "Foreign binding" as a category
1157Ross Paterson <ross@soi.city.ac.uk>**20070110233221]
1158[fix stylesheet location
1159Ross Paterson <ross@soi.city.ac.uk>**20070110182307]
1160[change locations to match install on hackage.haskell.org
1161Ross Paterson <ross@soi.city.ac.uk>**20070110170036]
1162[repair post-upload-hook
1163Ross Paterson <ross@soi.city.ac.uk>**20070109224215]
1164[fallback for category: top-level of module hierarchy
1165Ross Paterson <ross@soi.city.ac.uk>**20070109224115]
1166[initial import
1167Ross Paterson <ross@soi.city.ac.uk>**20070109004703
1168 
1169 This is a crude first cut at an interface to the Hackage package database:
1170 * generating a package list (pkg-list)
1171 * page describing a package (package.cgi)
1172 * apply basic checks to a Cabal package (check-pkg.cgi)
1173 * upload a Cabal package to the database (upload-pkg.cgi)
1174]
1175Patch bundle hash:
1176f05a1fe98f4435a882637311727ab5bf0759f891