mirror of
https://github.com/input-output-hk/foliage.git
synced 2024-11-30 01:16:54 +03:00
Finish off pages
- Summary page has the list of all packages available (with details of the latest version) - Timeline page has the list of all entries as they appear in the index Also: - Rebuild sdists is they are missing
This commit is contained in:
parent
db3fe215de
commit
c0714b1b3d
@ -1,4 +1,3 @@
|
|||||||
{-# LANGUAGE DeriveGeneric #-}
|
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
@ -8,41 +7,28 @@ import Codec.Archive.Tar qualified as Tar
|
|||||||
import Codec.Archive.Tar.Entry qualified as Tar
|
import Codec.Archive.Tar.Entry qualified as Tar
|
||||||
import Codec.Compression.GZip qualified as GZip
|
import Codec.Compression.GZip qualified as GZip
|
||||||
import Control.Monad (unless, when)
|
import Control.Monad (unless, when)
|
||||||
import Data.Aeson (object, (.=))
|
|
||||||
import Data.Aeson.Types (ToJSON)
|
|
||||||
import Data.ByteString.Lazy qualified as BL
|
import Data.ByteString.Lazy qualified as BL
|
||||||
import Data.ByteString.Lazy qualified as BSL
|
import Data.ByteString.Lazy qualified as BSL
|
||||||
import Data.Foldable (for_)
|
import Data.Foldable (for_)
|
||||||
import Data.List (sortOn)
|
import Data.List (sortOn)
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.Ord
|
|
||||||
import Data.Text.Lazy.IO qualified as TL
|
|
||||||
import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds)
|
|
||||||
import Data.Traversable (for)
|
import Data.Traversable (for)
|
||||||
import Development.Shake
|
import Development.Shake
|
||||||
import Development.Shake.FilePath
|
import Development.Shake.FilePath
|
||||||
import Distribution.Aeson
|
|
||||||
import Distribution.Package
|
import Distribution.Package
|
||||||
import Distribution.PackageDescription (GenericPackageDescription)
|
|
||||||
import Distribution.Parsec (simpleParsec)
|
import Distribution.Parsec (simpleParsec)
|
||||||
import Distribution.Pretty (prettyShow)
|
import Distribution.Pretty (prettyShow)
|
||||||
import Distribution.Simple.PackageDescription (readGenericPackageDescription)
|
|
||||||
import Distribution.Verbosity qualified as Verbosity
|
|
||||||
import Foliage.HackageSecurity hiding (ToJSON, toJSON)
|
import Foliage.HackageSecurity hiding (ToJSON, toJSON)
|
||||||
import Foliage.Meta
|
import Foliage.Meta
|
||||||
import Foliage.Meta.Aeson ()
|
import Foliage.Meta.Aeson ()
|
||||||
import Foliage.Options
|
import Foliage.Options
|
||||||
import Foliage.Pages (indexPageTemplate, packageVersionPageTemplate)
|
import Foliage.Pages
|
||||||
import Foliage.PrepareSdist
|
import Foliage.PrepareSdist
|
||||||
import Foliage.PrepareSource (addPrepareSourceRule, prepareSource)
|
import Foliage.PrepareSource (addPrepareSourceRule, prepareSource)
|
||||||
import Foliage.RemoteAsset (addFetchRemoteAssetRule)
|
import Foliage.RemoteAsset (addFetchRemoteAssetRule)
|
||||||
import Foliage.Shake
|
import Foliage.Shake
|
||||||
import Foliage.Time qualified as Time
|
import Foliage.Time qualified as Time
|
||||||
import Foliage.Utils.Aeson
|
|
||||||
import GHC.Generics (Generic)
|
|
||||||
import Hackage.Security.Util.Path (castRoot, toFilePath)
|
import Hackage.Security.Util.Path (castRoot, toFilePath)
|
||||||
import System.Directory qualified as IO
|
|
||||||
import Text.Mustache (renderMustache)
|
|
||||||
|
|
||||||
cmdBuild :: BuildOptions -> IO ()
|
cmdBuild :: BuildOptions -> IO ()
|
||||||
cmdBuild buildOptions = do
|
cmdBuild buildOptions = do
|
||||||
@ -97,13 +83,15 @@ buildAction
|
|||||||
putInfo $ "Current time set to " <> Time.iso8601Show t <> "."
|
putInfo $ "Current time set to " <> Time.iso8601Show t <> "."
|
||||||
return t
|
return t
|
||||||
|
|
||||||
packages <- getPackages inputDir
|
packageVersions <- getPackageVersions inputDir
|
||||||
|
|
||||||
makeIndexPage currentTime outputDir packages
|
makeSummaryPage currentTime outputDir packageVersions
|
||||||
|
|
||||||
for_ packages $ makePackageVersionPage inputDir outputDir
|
makeTimelinePage currentTime outputDir packageVersions
|
||||||
|
|
||||||
for_ packages $ \pkgMeta@PackageVersionMeta {pkgId} -> do
|
for_ packageVersions $ makePackageVersionPage inputDir outputDir
|
||||||
|
|
||||||
|
for_ packageVersions $ \pkgMeta@PackageVersionMeta {pkgId} -> do
|
||||||
let PackageIdentifier {pkgName, pkgVersion} = pkgId
|
let PackageIdentifier {pkgName, pkgVersion} = pkgId
|
||||||
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
|
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
|
||||||
copyFileChanged cabalFilePath (outputDir </> "index" </> prettyShow pkgName </> prettyShow pkgVersion </> prettyShow pkgName <.> "cabal")
|
copyFileChanged cabalFilePath (outputDir </> "index" </> prettyShow pkgName </> prettyShow pkgVersion </> prettyShow pkgName <.> "cabal")
|
||||||
@ -129,11 +117,11 @@ buildAction
|
|||||||
|
|
||||||
return $ cf : revcf
|
return $ cf : revcf
|
||||||
)
|
)
|
||||||
packages
|
packageVersions
|
||||||
|
|
||||||
targetKeys <- maybeReadKeysAt "target"
|
targetKeys <- maybeReadKeysAt "target"
|
||||||
metadataEntries <-
|
metadataEntries <-
|
||||||
for packages $ \pkg@PackageVersionMeta {pkgId, pkgSpec} -> do
|
for packageVersions $ \pkg@PackageVersionMeta {pkgId, pkgSpec} -> do
|
||||||
let PackageIdentifier {pkgName, pkgVersion} = pkgId
|
let PackageIdentifier {pkgName, pkgVersion} = pkgId
|
||||||
let PackageVersionSpec {packageVersionTimestamp} = pkgSpec
|
let PackageVersionSpec {packageVersionTimestamp} = pkgSpec
|
||||||
targets <- prepareIndexPkgMetadata expiryTime pkg
|
targets <- prepareIndexPkgMetadata expiryTime pkg
|
||||||
@ -233,66 +221,8 @@ buildAction
|
|||||||
timestampInfoSnapshot = snapshotInfo
|
timestampInfoSnapshot = snapshotInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
data IndexEntry
|
getPackageVersions :: FilePath -> Action [PackageVersionMeta]
|
||||||
= IndexEntryPackage
|
getPackageVersions inputDir = do
|
||||||
{ indexEntryPkgId :: PackageIdentifier,
|
|
||||||
indexEntryTimestamp :: UTCTime,
|
|
||||||
indexEntryTimestampPosix :: POSIXTime,
|
|
||||||
indexEntrySource :: PackageVersionSource
|
|
||||||
}
|
|
||||||
| IndexEntryRevision
|
|
||||||
{ indexEntryPkgId :: PackageIdentifier,
|
|
||||||
indexEntryTimestamp :: UTCTime,
|
|
||||||
indexEntryTimestampPosix :: POSIXTime
|
|
||||||
}
|
|
||||||
deriving stock (Generic)
|
|
||||||
deriving (ToJSON) via MyAesonEncoding IndexEntry
|
|
||||||
|
|
||||||
makeIndexPage :: UTCTime -> FilePath -> [PackageVersionMeta] -> Action ()
|
|
||||||
makeIndexPage currentTime outputDir packages = do
|
|
||||||
let entries =
|
|
||||||
sortOn (Down . indexEntryTimestamp) $
|
|
||||||
foldMap
|
|
||||||
( \PackageVersionMeta {pkgId, pkgSpec = PackageVersionSpec {packageVersionTimestamp, packageVersionRevisions, packageVersionSource}} ->
|
|
||||||
IndexEntryPackage
|
|
||||||
{ indexEntryPkgId = pkgId,
|
|
||||||
indexEntryTimestamp = fromMaybe currentTime packageVersionTimestamp,
|
|
||||||
indexEntryTimestampPosix = utcTimeToPOSIXSeconds (fromMaybe currentTime packageVersionTimestamp),
|
|
||||||
indexEntrySource = packageVersionSource
|
|
||||||
} :
|
|
||||||
map
|
|
||||||
( \RevisionSpec {revisionTimestamp} ->
|
|
||||||
IndexEntryRevision
|
|
||||||
{ indexEntryPkgId = pkgId,
|
|
||||||
indexEntryTimestamp = revisionTimestamp,
|
|
||||||
indexEntryTimestampPosix = utcTimeToPOSIXSeconds revisionTimestamp
|
|
||||||
}
|
|
||||||
)
|
|
||||||
packageVersionRevisions
|
|
||||||
)
|
|
||||||
packages
|
|
||||||
|
|
||||||
liftIO $ do
|
|
||||||
IO.createDirectoryIfMissing True outputDir
|
|
||||||
TL.writeFile (outputDir </> "index.html") $
|
|
||||||
renderMustache indexPageTemplate $
|
|
||||||
object ["entries" .= entries]
|
|
||||||
|
|
||||||
makePackageVersionPage :: FilePath -> FilePath -> PackageVersionMeta -> Action ()
|
|
||||||
makePackageVersionPage inputDir outputDir pkgMeta@PackageVersionMeta {pkgId, pkgSpec} = do
|
|
||||||
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
|
|
||||||
pkgDesc <- readGenericPackageDescription' cabalFilePath
|
|
||||||
liftIO $ do
|
|
||||||
IO.createDirectoryIfMissing True (outputDir </> "package" </> prettyShow pkgId)
|
|
||||||
TL.writeFile (outputDir </> "package" </> prettyShow pkgId </> "index.html") $
|
|
||||||
renderMustache packageVersionPageTemplate $
|
|
||||||
object
|
|
||||||
[ "pkgSpec" .= pkgSpec,
|
|
||||||
"pkgDesc" .= jsonGenericPackageDescription pkgDesc
|
|
||||||
]
|
|
||||||
|
|
||||||
getPackages :: FilePath -> Action [PackageVersionMeta]
|
|
||||||
getPackages inputDir = do
|
|
||||||
metaFiles <- getDirectoryFiles inputDir ["*/*/meta.toml"]
|
metaFiles <- getDirectoryFiles inputDir ["*/*/meta.toml"]
|
||||||
|
|
||||||
when (null metaFiles) $ do
|
when (null metaFiles) $ do
|
||||||
@ -332,11 +262,6 @@ getPackages inputDir = do
|
|||||||
|
|
||||||
return $ PackageVersionMeta pkgId pkgSpec
|
return $ PackageVersionMeta pkgId pkgSpec
|
||||||
|
|
||||||
readGenericPackageDescription' :: FilePath -> Action GenericPackageDescription
|
|
||||||
readGenericPackageDescription' fp = do
|
|
||||||
need [fp]
|
|
||||||
liftIO $ readGenericPackageDescription Verbosity.silent fp
|
|
||||||
|
|
||||||
prepareIndexPkgCabal :: PackageId -> UTCTime -> FilePath -> Action Tar.Entry
|
prepareIndexPkgCabal :: PackageId -> UTCTime -> FilePath -> Action Tar.Entry
|
||||||
prepareIndexPkgCabal pkgId timestamp filePath = do
|
prepareIndexPkgCabal pkgId timestamp filePath = do
|
||||||
need [filePath]
|
need [filePath]
|
||||||
@ -376,16 +301,3 @@ mkTarEntry contents indexFile timestamp =
|
|||||||
anchorPath :: Path Absolute -> (RepoLayout -> RepoPath) -> FilePath
|
anchorPath :: Path Absolute -> (RepoLayout -> RepoPath) -> FilePath
|
||||||
anchorPath outputDirRoot p =
|
anchorPath outputDirRoot p =
|
||||||
toFilePath $ anchorRepoPathLocally outputDirRoot $ p hackageRepoLayout
|
toFilePath $ anchorRepoPathLocally outputDirRoot $ p hackageRepoLayout
|
||||||
|
|
||||||
cabalFileRevisionPath :: FilePath -> PackageIdentifier -> Int -> FilePath
|
|
||||||
cabalFileRevisionPath inputDir PackageIdentifier {pkgName, pkgVersion} revisionNumber =
|
|
||||||
inputDir </> unPackageName pkgName </> prettyShow pkgVersion </> "revisions" </> show revisionNumber <.> "cabal"
|
|
||||||
|
|
||||||
originalCabalFile :: PackageVersionMeta -> Action FilePath
|
|
||||||
originalCabalFile PackageVersionMeta {pkgId, pkgSpec} = do
|
|
||||||
srcDir <- prepareSource pkgId pkgSpec
|
|
||||||
return $ srcDir </> unPackageName (pkgName pkgId) <.> "cabal"
|
|
||||||
|
|
||||||
revisedCabalFile :: FilePath -> PackageVersionMeta -> Maybe FilePath
|
|
||||||
revisedCabalFile inputDir PackageVersionMeta {pkgId, pkgSpec} = do
|
|
||||||
cabalFileRevisionPath inputDir pkgId <$> latestRevisionNumber pkgSpec
|
|
||||||
|
@ -32,6 +32,8 @@ module Foliage.Meta
|
|||||||
UTCTime,
|
UTCTime,
|
||||||
latestRevisionNumber,
|
latestRevisionNumber,
|
||||||
consolidateRanges,
|
consolidateRanges,
|
||||||
|
cabalFileRevisionPath,
|
||||||
|
revisedCabalFile,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
@ -43,32 +45,21 @@ import Data.Ord (Down (Down))
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Data.Time.LocalTime (utc, utcToZonedTime, zonedTimeToUTC)
|
import Data.Time.LocalTime (utc, utcToZonedTime, zonedTimeToUTC)
|
||||||
import Development.Shake.Classes
|
import Development.Shake.Classes (Binary, Hashable, NFData)
|
||||||
( Binary,
|
|
||||||
Hashable,
|
|
||||||
NFData,
|
|
||||||
)
|
|
||||||
import Distribution.Aeson ()
|
import Distribution.Aeson ()
|
||||||
import Distribution.Parsec (simpleParsec)
|
import Distribution.Parsec (simpleParsec)
|
||||||
import Distribution.Pretty (prettyShow)
|
import Distribution.Pretty (prettyShow)
|
||||||
import Distribution.Types.Orphans ()
|
import Distribution.Types.Orphans ()
|
||||||
import Distribution.Types.PackageId (PackageIdentifier)
|
import Distribution.Types.PackageId (PackageIdentifier (..))
|
||||||
|
import Distribution.Types.PackageName (unPackageName)
|
||||||
import Distribution.Types.Version (Version)
|
import Distribution.Types.Version (Version)
|
||||||
import Distribution.Types.VersionRange
|
import Distribution.Types.VersionRange (VersionRange, anyVersion, intersectVersionRanges, notThisVersion)
|
||||||
( VersionRange,
|
import Distribution.Version (isAnyVersion, isNoVersion, simplifyVersionRange)
|
||||||
anyVersion,
|
|
||||||
intersectVersionRanges,
|
|
||||||
notThisVersion,
|
|
||||||
)
|
|
||||||
import Distribution.Version
|
|
||||||
( isAnyVersion,
|
|
||||||
isNoVersion,
|
|
||||||
simplifyVersionRange,
|
|
||||||
)
|
|
||||||
import Foliage.Time (UTCTime)
|
import Foliage.Time (UTCTime)
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Network.URI (URI, parseURI)
|
import Network.URI (URI, parseURI)
|
||||||
import Network.URI.Orphans ()
|
import Network.URI.Orphans ()
|
||||||
|
import System.FilePath ((<.>), (</>))
|
||||||
import Toml (TomlCodec, (.=))
|
import Toml (TomlCodec, (.=))
|
||||||
import Toml qualified
|
import Toml qualified
|
||||||
|
|
||||||
@ -245,4 +236,13 @@ data PackageVersionMeta = PackageVersionMeta
|
|||||||
{ pkgId :: PackageIdentifier,
|
{ pkgId :: PackageIdentifier,
|
||||||
pkgSpec :: PackageVersionSpec
|
pkgSpec :: PackageVersionSpec
|
||||||
}
|
}
|
||||||
|
deriving (Show, Eq)
|
||||||
deriving (Generic)
|
deriving (Generic)
|
||||||
|
|
||||||
|
cabalFileRevisionPath :: FilePath -> PackageIdentifier -> Int -> FilePath
|
||||||
|
cabalFileRevisionPath inputDir PackageIdentifier {pkgName, pkgVersion} revisionNumber =
|
||||||
|
inputDir </> unPackageName pkgName </> prettyShow pkgVersion </> "revisions" </> show revisionNumber <.> "cabal"
|
||||||
|
|
||||||
|
revisedCabalFile :: FilePath -> PackageVersionMeta -> Maybe FilePath
|
||||||
|
revisedCabalFile inputDir PackageVersionMeta {pkgId, pkgSpec} = do
|
||||||
|
cabalFileRevisionPath inputDir pkgId <$> latestRevisionNumber pkgSpec
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE StandaloneDeriving #-}
|
{-# LANGUAGE StandaloneDeriving #-}
|
||||||
{-# OPTIONS_GHC -Wno-orphans #-}
|
{-# OPTIONS_GHC -Wno-orphans #-}
|
||||||
|
{-# LANGUAGE InstanceSigs #-}
|
||||||
|
|
||||||
module Foliage.Meta.Aeson where
|
module Foliage.Meta.Aeson where
|
||||||
|
|
||||||
@ -30,7 +31,9 @@ instance ToJSON PackageVersionSource where
|
|||||||
genericToJSON
|
genericToJSON
|
||||||
defaultOptions
|
defaultOptions
|
||||||
{ sumEncoding = ObjectWithSingleField
|
{ sumEncoding = ObjectWithSingleField
|
||||||
|
, omitNothingFields = True
|
||||||
}
|
}
|
||||||
|
|
||||||
instance ToJSON URI where
|
instance ToJSON URI where
|
||||||
|
toJSON :: URI -> Value
|
||||||
toJSON = toJSON . show
|
toJSON = toJSON . show
|
||||||
|
@ -1,17 +1,140 @@
|
|||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
|
||||||
module Foliage.Pages
|
module Foliage.Pages
|
||||||
( indexPageTemplate,
|
( summaryPageTemplate,
|
||||||
|
timelinePageTemplate,
|
||||||
packageVersionPageTemplate,
|
packageVersionPageTemplate,
|
||||||
|
makeSummaryPage,
|
||||||
|
makePackageVersionPage,
|
||||||
|
makeTimelinePage,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Data.Aeson (KeyValue ((.=)), ToJSON, object)
|
||||||
|
import Data.Function (on)
|
||||||
|
import Data.List (groupBy, sortOn)
|
||||||
|
import Data.Maybe (fromMaybe, listToMaybe)
|
||||||
|
import Data.Ord (Down (Down))
|
||||||
|
import Data.Text.Lazy.IO.Utf8 qualified as TL
|
||||||
|
import Data.Time (UTCTime)
|
||||||
|
import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds)
|
||||||
|
import Development.Shake (Action, liftIO)
|
||||||
|
import Distribution.Aeson (jsonGenericPackageDescription)
|
||||||
|
import Distribution.Package (PackageIdentifier (pkgName, pkgVersion))
|
||||||
|
import Distribution.Pretty (prettyShow)
|
||||||
|
import Foliage.Meta (PackageVersionMeta (..), PackageVersionSource, PackageVersionSpec (PackageVersionSpec, packageVersionRevisions, packageVersionSource, packageVersionTimestamp), RevisionSpec (RevisionSpec, revisionTimestamp), revisedCabalFile)
|
||||||
|
import Foliage.Meta.Aeson ()
|
||||||
|
import Foliage.Shake (originalCabalFile, readGenericPackageDescription')
|
||||||
|
import Foliage.Utils.Aeson (MyAesonEncoding (..))
|
||||||
|
import GHC.Generics (Generic)
|
||||||
|
import System.Directory qualified as IO
|
||||||
|
import System.FilePath ((</>))
|
||||||
import Text.Mustache (Template)
|
import Text.Mustache (Template)
|
||||||
import Text.Mustache.Compile.TH (compileMustacheDir)
|
import Text.Mustache.Compile.TH (compileMustacheDir)
|
||||||
|
import Text.Mustache.Render (renderMustache)
|
||||||
|
|
||||||
indexPageTemplate :: Template
|
data SummaryPageEntry = SummaryPageEntry
|
||||||
indexPageTemplate = $(compileMustacheDir "index" "templates")
|
{ summaryPageEntryPkgId :: PackageIdentifier,
|
||||||
|
summaryPageEntryTimestamp :: UTCTime,
|
||||||
|
summaryPageEntryTimestampPosix :: POSIXTime,
|
||||||
|
summaryPageEntrySource :: PackageVersionSource,
|
||||||
|
summaryPageEntryRevision :: Maybe RevisionSpec
|
||||||
|
}
|
||||||
|
deriving stock (Generic)
|
||||||
|
deriving (ToJSON) via MyAesonEncoding SummaryPageEntry
|
||||||
|
|
||||||
|
makeSummaryPage :: UTCTime -> FilePath -> [PackageVersionMeta] -> Action ()
|
||||||
|
makeSummaryPage currentTime outputDir packageVersions = do
|
||||||
|
let packages =
|
||||||
|
sortOn summaryPageEntryPkgId $
|
||||||
|
map
|
||||||
|
( ( \PackageVersionMeta {pkgId, pkgSpec = PackageVersionSpec {packageVersionTimestamp, packageVersionRevisions, packageVersionSource}} ->
|
||||||
|
SummaryPageEntry
|
||||||
|
{ summaryPageEntryPkgId = pkgId,
|
||||||
|
summaryPageEntryTimestamp = fromMaybe currentTime packageVersionTimestamp,
|
||||||
|
summaryPageEntryTimestampPosix = utcTimeToPOSIXSeconds (fromMaybe currentTime packageVersionTimestamp),
|
||||||
|
summaryPageEntrySource = packageVersionSource,
|
||||||
|
summaryPageEntryRevision = listToMaybe packageVersionRevisions
|
||||||
|
}
|
||||||
|
)
|
||||||
|
. head
|
||||||
|
. sortOn (Down . pkgVersion . pkgId)
|
||||||
|
)
|
||||||
|
$ groupBy ((==) `on` (pkgName . pkgId)) packageVersions
|
||||||
|
liftIO $ do
|
||||||
|
IO.createDirectoryIfMissing True outputDir
|
||||||
|
IO.createDirectoryIfMissing True (outputDir </> "summary")
|
||||||
|
TL.writeFile (outputDir </> "summary" </> "index.html") $
|
||||||
|
renderMustache summaryPageTemplate $
|
||||||
|
object ["packages" .= packages]
|
||||||
|
|
||||||
|
data TimelinePageEntry
|
||||||
|
= TimelinePageEntryPackage
|
||||||
|
{ timelinePageEntryPkgId :: PackageIdentifier,
|
||||||
|
timelinePageEntryTimestamp :: UTCTime,
|
||||||
|
timelinePageEntryTimestampPosix :: POSIXTime,
|
||||||
|
timelinePageEntrySource :: PackageVersionSource
|
||||||
|
}
|
||||||
|
| TimelinePageEntryRevision
|
||||||
|
{ timelinePageEntryPkgId :: PackageIdentifier,
|
||||||
|
timelinePageEntryTimestamp :: UTCTime,
|
||||||
|
timelinePageEntryTimestampPosix :: POSIXTime
|
||||||
|
}
|
||||||
|
deriving stock (Generic)
|
||||||
|
deriving (ToJSON) via MyAesonEncoding TimelinePageEntry
|
||||||
|
|
||||||
|
makeTimelinePage :: UTCTime -> FilePath -> [PackageVersionMeta] -> Action ()
|
||||||
|
makeTimelinePage currentTime outputDir packageVersions = do
|
||||||
|
let entries =
|
||||||
|
sortOn (Down . timelinePageEntryTimestamp) $
|
||||||
|
foldMap
|
||||||
|
( \PackageVersionMeta {pkgId, pkgSpec = PackageVersionSpec {packageVersionTimestamp, packageVersionRevisions, packageVersionSource}} ->
|
||||||
|
TimelinePageEntryPackage
|
||||||
|
{ timelinePageEntryPkgId = pkgId,
|
||||||
|
timelinePageEntryTimestamp = fromMaybe currentTime packageVersionTimestamp,
|
||||||
|
timelinePageEntryTimestampPosix = utcTimeToPOSIXSeconds (fromMaybe currentTime packageVersionTimestamp),
|
||||||
|
timelinePageEntrySource = packageVersionSource
|
||||||
|
} :
|
||||||
|
map
|
||||||
|
( \RevisionSpec {revisionTimestamp} ->
|
||||||
|
TimelinePageEntryRevision
|
||||||
|
{ timelinePageEntryPkgId = pkgId,
|
||||||
|
timelinePageEntryTimestamp = revisionTimestamp,
|
||||||
|
timelinePageEntryTimestampPosix = utcTimeToPOSIXSeconds revisionTimestamp
|
||||||
|
}
|
||||||
|
)
|
||||||
|
packageVersionRevisions
|
||||||
|
)
|
||||||
|
packageVersions
|
||||||
|
|
||||||
|
liftIO $ do
|
||||||
|
IO.createDirectoryIfMissing True outputDir
|
||||||
|
IO.createDirectoryIfMissing True (outputDir </> "timeline")
|
||||||
|
TL.writeFile (outputDir </> "timeline" </> "index.html") $
|
||||||
|
renderMustache timelinePageTemplate $
|
||||||
|
object ["entries" .= entries]
|
||||||
|
|
||||||
|
makePackageVersionPage :: FilePath -> FilePath -> PackageVersionMeta -> Action ()
|
||||||
|
makePackageVersionPage inputDir outputDir pkgMeta@PackageVersionMeta {pkgId, pkgSpec} = do
|
||||||
|
cabalFilePath <- maybe (originalCabalFile pkgMeta) pure (revisedCabalFile inputDir pkgMeta)
|
||||||
|
pkgDesc <- readGenericPackageDescription' cabalFilePath
|
||||||
|
liftIO $ do
|
||||||
|
IO.createDirectoryIfMissing True (outputDir </> "package" </> prettyShow pkgId)
|
||||||
|
TL.writeFile (outputDir </> "package" </> prettyShow pkgId </> "index.html") $
|
||||||
|
renderMustache packageVersionPageTemplate $
|
||||||
|
object
|
||||||
|
[ "pkgSpec" .= pkgSpec,
|
||||||
|
"pkgDesc" .= jsonGenericPackageDescription pkgDesc
|
||||||
|
]
|
||||||
|
|
||||||
|
summaryPageTemplate :: Template
|
||||||
|
summaryPageTemplate = $(compileMustacheDir "summary" "templates")
|
||||||
|
|
||||||
|
timelinePageTemplate :: Template
|
||||||
|
timelinePageTemplate = $(compileMustacheDir "timeline" "templates")
|
||||||
|
|
||||||
packageVersionPageTemplate :: Template
|
packageVersionPageTemplate :: Template
|
||||||
packageVersionPageTemplate = $(compileMustacheDir "packageVersion" "templates")
|
packageVersionPageTemplate = $(compileMustacheDir "packageVersion" "templates")
|
||||||
|
@ -11,15 +11,15 @@ module Foliage.PrepareSdist
|
|||||||
where
|
where
|
||||||
|
|
||||||
import Control.Monad (when)
|
import Control.Monad (when)
|
||||||
|
import Data.Binary qualified as Binary
|
||||||
import Data.ByteString qualified as BS
|
import Data.ByteString qualified as BS
|
||||||
import Data.ByteString.Lazy qualified as BSL
|
import Data.ByteString.Lazy qualified as BSL
|
||||||
import Development.Shake
|
import Development.Shake
|
||||||
import Development.Shake.Classes
|
import Development.Shake.Classes
|
||||||
import Development.Shake.FilePath
|
import Development.Shake.FilePath
|
||||||
import Development.Shake.Rule
|
import Development.Shake.Rule
|
||||||
import Distribution.Client.HashValue (HashValue, hashValue, showHashValue)
|
import Distribution.Client.HashValue (HashValue, hashValue, readFileHashValue, showHashValue)
|
||||||
import Distribution.Client.SrcDist (packageDirToSdist)
|
import Distribution.Client.SrcDist (packageDirToSdist)
|
||||||
import Distribution.Compat.Binary (decode, encode)
|
|
||||||
import Distribution.Package (packageId)
|
import Distribution.Package (packageId)
|
||||||
import Distribution.Simple.PackageDescription (readGenericPackageDescription)
|
import Distribution.Simple.PackageDescription (readGenericPackageDescription)
|
||||||
import Distribution.Verbosity qualified as Verbosity
|
import Distribution.Verbosity qualified as Verbosity
|
||||||
@ -28,6 +28,7 @@ import Foliage.Meta ()
|
|||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Hackage.Security.Util.Path (toFilePath)
|
import Hackage.Security.Util.Path (toFilePath)
|
||||||
import System.Directory qualified as IO
|
import System.Directory qualified as IO
|
||||||
|
import System.IO.Error (tryIOError)
|
||||||
|
|
||||||
newtype PrepareSdistRule = PrepareSdistRule FilePath
|
newtype PrepareSdistRule = PrepareSdistRule FilePath
|
||||||
deriving (Show, Eq, Generic)
|
deriving (Show, Eq, Generic)
|
||||||
@ -42,28 +43,32 @@ addPrepareSdistRule :: Path Absolute -> Rules ()
|
|||||||
addPrepareSdistRule outputDirRoot = addBuiltinRule noLint noIdentity run
|
addPrepareSdistRule outputDirRoot = addBuiltinRule noLint noIdentity run
|
||||||
where
|
where
|
||||||
run :: PrepareSdistRule -> Maybe BS.ByteString -> RunMode -> Action (RunResult FilePath)
|
run :: PrepareSdistRule -> Maybe BS.ByteString -> RunMode -> Action (RunResult FilePath)
|
||||||
run (PrepareSdistRule _srcDir) (Just old) RunDependenciesSame =
|
run (PrepareSdistRule srcDir) (Just old) RunDependenciesSame = do
|
||||||
let (_, path) = load old
|
let (hvExpected, path) = load old
|
||||||
in return $ RunResult ChangedNothing old path
|
|
||||||
run (PrepareSdistRule srcDir) old _ = do
|
-- Check of has of the sdist, if the sdist is still there and it is
|
||||||
cabalFile <- do
|
-- indeed what we expect, signal that nothing changed. Otherwise
|
||||||
getDirectoryFiles srcDir ["*.cabal"] >>= \case
|
-- warn the user and proceed to recompute.
|
||||||
[f] -> pure f
|
ehvExisting <- liftIO $ tryIOError $ readFileHashValue path
|
||||||
fs -> fail $ "Invalid srcDir: " ++ srcDir ++ ". It contains multiple cabal files: " ++ unwords fs
|
case ehvExisting of
|
||||||
(hv, path) <- traced "cabal sdist" $ do
|
Right hvExisting
|
||||||
gpd <- readGenericPackageDescription Verbosity.normal (srcDir </> cabalFile)
|
| hvExisting == hvExpected ->
|
||||||
let pkgId = packageId gpd
|
return RunResult {runChanged = ChangedNothing, runStore = old, runValue = path}
|
||||||
packagePath = repoLayoutPkgTarGz hackageRepoLayout pkgId
|
Right hvExisting -> do
|
||||||
path = toFilePath $ anchorRepoPathLocally outputDirRoot packagePath
|
putWarn $ "Changed " ++ path ++ " (expecting hash " ++ showHashValue hvExpected ++ " found " ++ showHashValue hvExisting ++ "). I will rebuild it."
|
||||||
IO.createDirectoryIfMissing True (takeDirectory path)
|
run (PrepareSdistRule srcDir) (Just old) RunDependenciesChanged
|
||||||
sdist <- packageDirToSdist Verbosity.normal gpd srcDir
|
Left _e -> do
|
||||||
BSL.writeFile path sdist
|
putWarn $ "Unable to read " ++ path ++ ". I will rebuild it."
|
||||||
return (hashValue sdist, path)
|
run (PrepareSdistRule srcDir) (Just old) RunDependenciesChanged
|
||||||
|
run (PrepareSdistRule srcDir) old _mode = do
|
||||||
|
-- create the sdist distribution
|
||||||
|
(hv, path) <- makeSdist srcDir
|
||||||
|
|
||||||
let new = save (hv, path)
|
let new = save (hv, path)
|
||||||
|
|
||||||
let changed = case old of
|
let changed = case fmap ((== hv) . fst . load) old of
|
||||||
Just old' | fst (load old') == hv -> ChangedRecomputeSame
|
Just True -> ChangedRecomputeSame
|
||||||
_ -> ChangedRecomputeDiff
|
_differentOrMissing -> ChangedRecomputeDiff
|
||||||
|
|
||||||
when (changed == ChangedRecomputeSame) $
|
when (changed == ChangedRecomputeSame) $
|
||||||
putInfo $ "Wrote " ++ path ++ " (same hash " ++ showHashValue hv ++ ")"
|
putInfo $ "Wrote " ++ path ++ " (same hash " ++ showHashValue hv ++ ")"
|
||||||
@ -73,8 +78,24 @@ addPrepareSdistRule outputDirRoot = addBuiltinRule noLint noIdentity run
|
|||||||
|
|
||||||
return $ RunResult {runChanged = changed, runStore = new, runValue = path}
|
return $ RunResult {runChanged = changed, runStore = new, runValue = path}
|
||||||
|
|
||||||
|
makeSdist srcDir = do
|
||||||
|
cabalFile <- do
|
||||||
|
getDirectoryFiles srcDir ["*.cabal"] >>= \case
|
||||||
|
[f] -> pure f
|
||||||
|
fs -> fail $ "Invalid srcDir: " ++ srcDir ++ ". It contains multiple cabal files: " ++ unwords fs
|
||||||
|
|
||||||
|
traced "cabal sdist" $ do
|
||||||
|
gpd <- readGenericPackageDescription Verbosity.normal (srcDir </> cabalFile)
|
||||||
|
let pkgId = packageId gpd
|
||||||
|
packagePath = repoLayoutPkgTarGz hackageRepoLayout pkgId
|
||||||
|
path = toFilePath $ anchorRepoPathLocally outputDirRoot packagePath
|
||||||
|
IO.createDirectoryIfMissing True (takeDirectory path)
|
||||||
|
sdist <- packageDirToSdist Verbosity.normal gpd srcDir
|
||||||
|
BSL.writeFile path sdist
|
||||||
|
return (hashValue sdist, path)
|
||||||
|
|
||||||
save :: (HashValue, FilePath) -> BS.ByteString
|
save :: (HashValue, FilePath) -> BS.ByteString
|
||||||
save = BSL.toStrict . encode
|
save = BSL.toStrict . Binary.encode
|
||||||
|
|
||||||
load :: BS.ByteString -> (HashValue, FilePath)
|
load :: BS.ByteString -> (HashValue, FilePath)
|
||||||
load = decode . BSL.fromStrict
|
load = Binary.decode . BSL.fromStrict
|
||||||
|
@ -48,11 +48,11 @@ addFetchRemoteAssetRule cacheDir = addBuiltinRule noLint noIdentity run
|
|||||||
let oldETag = fromMaybe BS.empty old
|
let oldETag = fromMaybe BS.empty old
|
||||||
|
|
||||||
newETag <-
|
newETag <-
|
||||||
withTempFile $ \fp -> do
|
withTempFile $ \fp -> traced "curl" $ do
|
||||||
liftIO $ BS.writeFile fp oldETag
|
BS.writeFile fp oldETag
|
||||||
liftIO $ createDirectoryIfMissing True (takeDirectory path)
|
createDirectoryIfMissing True (takeDirectory path)
|
||||||
cmd_ Shell ["curl", "--silent", "--location", "--etag-compare", fp, "--etag-save", fp, "--output", path, show uri]
|
cmd_ Shell ["curl", "--silent", "--location", "--etag-compare", fp, "--etag-save", fp, "--output", path, show uri]
|
||||||
liftIO $ BS.readFile fp
|
BS.readFile fp
|
||||||
|
|
||||||
let changed = if newETag == oldETag then ChangedRecomputeSame else ChangedRecomputeDiff
|
let changed = if newETag == oldETag then ChangedRecomputeSame else ChangedRecomputeDiff
|
||||||
return $ RunResult {runChanged = changed, runStore = newETag, runValue = path}
|
return $ RunResult {runChanged = changed, runStore = newETag, runValue = path}
|
||||||
|
@ -2,14 +2,22 @@ module Foliage.Shake
|
|||||||
( computeFileInfoSimple',
|
( computeFileInfoSimple',
|
||||||
readKeysAt,
|
readKeysAt,
|
||||||
readPackageVersionSpec',
|
readPackageVersionSpec',
|
||||||
|
readGenericPackageDescription',
|
||||||
|
originalCabalFile,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.Traversable (for)
|
import Data.Traversable (for)
|
||||||
import Development.Shake
|
import Development.Shake
|
||||||
import Development.Shake.FilePath
|
import Development.Shake.FilePath
|
||||||
|
import Distribution.Simple.PackageDescription
|
||||||
|
import Distribution.Types.GenericPackageDescription
|
||||||
|
import Distribution.Types.PackageId
|
||||||
|
import Distribution.Types.PackageName
|
||||||
|
import Distribution.Verbosity qualified as Verbosity
|
||||||
import Foliage.HackageSecurity
|
import Foliage.HackageSecurity
|
||||||
import Foliage.Meta
|
import Foliage.Meta
|
||||||
|
import Foliage.PrepareSource
|
||||||
|
|
||||||
computeFileInfoSimple' :: FilePath -> Action FileInfo
|
computeFileInfoSimple' :: FilePath -> Action FileInfo
|
||||||
computeFileInfoSimple' fp = do
|
computeFileInfoSimple' fp = do
|
||||||
@ -28,3 +36,13 @@ readPackageVersionSpec' :: FilePath -> Action PackageVersionSpec
|
|||||||
readPackageVersionSpec' fp = do
|
readPackageVersionSpec' fp = do
|
||||||
need [fp]
|
need [fp]
|
||||||
liftIO $ readPackageVersionSpec fp
|
liftIO $ readPackageVersionSpec fp
|
||||||
|
|
||||||
|
readGenericPackageDescription' :: FilePath -> Action GenericPackageDescription
|
||||||
|
readGenericPackageDescription' fp = do
|
||||||
|
need [fp]
|
||||||
|
liftIO $ readGenericPackageDescription Verbosity.silent fp
|
||||||
|
|
||||||
|
originalCabalFile :: PackageVersionMeta -> Action FilePath
|
||||||
|
originalCabalFile PackageVersionMeta {pkgId, pkgSpec} = do
|
||||||
|
srcDir <- prepareSource pkgId pkgSpec
|
||||||
|
return $ srcDir </> unPackageName (pkgName pkgId) <.> "cabal"
|
||||||
|
@ -45,6 +45,7 @@ executable foliage
|
|||||||
base >=4.14.3.0 && <4.18,
|
base >=4.14.3.0 && <4.18,
|
||||||
aeson >=2.0.3.0 && <2.2,
|
aeson >=2.0.3.0 && <2.2,
|
||||||
base64 >=0.4.2.3 && <0.5,
|
base64 >=0.4.2.3 && <0.5,
|
||||||
|
binary,
|
||||||
bytestring >=0.10.12.0 && <0.12,
|
bytestring >=0.10.12.0 && <0.12,
|
||||||
Cabal >=3.8 && <3.9,
|
Cabal >=3.8 && <3.9,
|
||||||
Cabal-syntax >=3.8 && <3.9,
|
Cabal-syntax >=3.8 && <3.9,
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
<div class="container px-4 py-5">
|
<div class="container px-4 py-5">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="/">Home</a></li>
|
<li class="breadcrumb-item"><a href="/timeline/">Timeline</a></li>
|
||||||
<li class="breadcrumb-item"><a href="/?q={{name}}">{{name}}</a></li>
|
<li class="breadcrumb-item"><a href="/timeline/?q={{name}}">{{name}}</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{version}}</li>
|
<li class="breadcrumb-item active" aria-current="page">{{version}}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{{#TarballSource}}
|
{{#TarballSource}}
|
||||||
<dt>Tarball</dt>
|
|
||||||
<dd><a href="{{tarballSourceURI}}">{{tarballSourceURI}}</a></dd>
|
<dd><a href="{{tarballSourceURI}}">{{tarballSourceURI}}</a></dd>
|
||||||
{{#subdir}}
|
{{#subdir}}
|
||||||
<dt>Subdir</dt>
|
<dt>Subdir</dt>
|
||||||
@ -7,7 +6,6 @@
|
|||||||
{{/subdir}}
|
{{/subdir}}
|
||||||
{{/TarballSource}}
|
{{/TarballSource}}
|
||||||
{{#GitHubSource}}
|
{{#GitHubSource}}
|
||||||
<dt>GitHub</dt>
|
|
||||||
<dd>
|
<dd>
|
||||||
{{#subdir}}
|
{{#subdir}}
|
||||||
<a href="https://github.com/{{githubRepo}}/tree/{{githubRev}}/{{subdir}}">https://github.com/{{githubRepo}}</a>
|
<a href="https://github.com/{{githubRepo}}/tree/{{githubRev}}/{{subdir}}">https://github.com/{{githubRepo}}</a>
|
||||||
|
@ -12,67 +12,67 @@
|
|||||||
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.css" integrity="sha384-4+3RMUungd+Oh0juS2Jl3yQ7mPlVfsgr10NLSsqltLXIECi9ExY4d1cTtmf9aN8E" crossorigin="anonymous">
|
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.css" integrity="sha384-4+3RMUungd+Oh0juS2Jl3yQ7mPlVfsgr10NLSsqltLXIECi9ExY4d1cTtmf9aN8E" crossorigin="anonymous">
|
||||||
<script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.min.js" integrity="sha384-eU1uLDC5C4YCIouMauJZjbrnSmIiICWWAPdnZjRkNnuDxG+eJFN/EhW5GlKPAIVl" crossorigin="anonymous"></script>
|
<script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.min.js" integrity="sha384-eU1uLDC5C4YCIouMauJZjbrnSmIiICWWAPdnZjRkNnuDxG+eJFN/EhW5GlKPAIVl" crossorigin="anonymous"></script>
|
||||||
<title>
|
<title>
|
||||||
Repository's content
|
Index summary
|
||||||
</title>
|
</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container px-4 py-5">
|
<div class="container px-4 py-5">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item active" aria-current="page"><a href="/">Home</a></li>
|
<li class="breadcrumb-item active" aria-current="page"><a href="/summary/">Summary</a></li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<h1 class="py-5">
|
<h1 class="py-5">
|
||||||
Repository's entries
|
Index summary
|
||||||
</h1>
|
</h1>
|
||||||
<table id="entries" class="table table-hover">
|
<p>
|
||||||
|
This page lists the latest version and revision of all packages in the index.
|
||||||
|
</p>
|
||||||
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Package identifier</th>
|
<th>Package</th>
|
||||||
<th>Type</th>
|
<th>Added on</th>
|
||||||
<th>Timestamp</th>
|
|
||||||
<th>Source</th>
|
<th>Source</th>
|
||||||
|
<th>Revision</th>
|
||||||
</th>
|
</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{#entries}}
|
{{#packages}}
|
||||||
{{#IndexEntryPackage}}
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="col-sm-2"><a href="package/{{indexEntryPkgId}}">{{indexEntryPkgId}}</a></td>
|
<td class="col-sm-2"><a href="/package/{{summaryPageEntryPkgId}}">{{summaryPageEntryPkgId}}</a></td>
|
||||||
<td class="col-sm-1">package</td>
|
<td class="col-sm-3" data-order="{{summaryPageEntryTimestampPosix}}">{{summaryPageEntryTimestamp}}</td>
|
||||||
<td class="col-sm-3" data-order="{{indexEntryTimestampPosix}}">{{indexEntryTimestamp}}</td>
|
|
||||||
<td class="col-sm-6">
|
<td class="col-sm-6">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
{{#indexEntrySource}}
|
{{#summaryPageEntrySource}}
|
||||||
{{> packageVersionSource}}
|
{{> packageVersionSource}}
|
||||||
{{/indexEntrySource}}
|
{{/summaryPageEntrySource}}
|
||||||
</dl>
|
</dl>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="col-sm-3">
|
||||||
|
{{#summaryPageEntryRevision}}
|
||||||
|
Last revision at {{revisionTimestamp}}
|
||||||
|
{{/summaryPageEntryRevision}}
|
||||||
|
{{^summaryPageEntryRevision}}
|
||||||
|
No revision
|
||||||
|
{{/summaryPageEntryRevision}}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/IndexEntryPackage}}
|
{{/packages}}
|
||||||
{{#IndexEntryRevision}}
|
|
||||||
<tr>
|
|
||||||
<td>{{indexEntryPkgId}}</td>
|
|
||||||
<td>revision</td>
|
|
||||||
<td data-order="{{indexEntryTimestampPosix}}">{{indexEntryTimestamp}}</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
{{/IndexEntryRevision}}
|
|
||||||
{{/entries}}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
const urlParams = new URLSearchParams(location.search);
|
const table = $('table').DataTable({
|
||||||
$('#entries').DataTable({
|
order: [[0, 'asc']],
|
||||||
order: [[2, 'desc']],
|
lengthMenu: [ [50, 100, 500, -1], [50, 100, 500, "All"] ]
|
||||||
lengthMenu: [ [50, 100, 500, -1], [50, 100, 500, "All"] ],
|
})
|
||||||
search: {
|
const urlParams = new URLSearchParams(location.search)
|
||||||
search: urlParams.has('q') ? urlParams.get('q') : null,
|
if (urlParams.has('q')) {
|
||||||
|
table.search(urlParams.get('q')).draw()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
83
templates/timeline.mustache
Normal file
83
templates/timeline.mustache
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<!-- Required meta tags -->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<!-- Bootstrap CSS -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.1.3/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||||
|
<!-- DataTables -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/v/bs5/dt-1.12.1/datatables.min.css" integrity="sha384-4+3RMUungd+Oh0juS2Jl3yQ7mPlVfsgr10NLSsqltLXIECi9ExY4d1cTtmf9aN8E" crossorigin="anonymous">
|
||||||
|
<!-- <script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.min.js" integrity="sha384-eU1uLDC5C4YCIouMauJZjbrnSmIiICWWAPdnZjRkNnuDxG+eJFN/EhW5GlKPAIVl" crossorigin="anonymous"></script>
|
||||||
|
-->
|
||||||
|
<script src="https://cdn.datatables.net/v/bs5/jq-3.6.0/dt-1.12.1/datatables.js"></script>
|
||||||
|
<title>
|
||||||
|
Index timeline
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container px-4 py-5">
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item active" aria-current="page"><a href="/timeline/">Timeline</a></li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h1 class="py-5">
|
||||||
|
Index timeline
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
This page lists all package versions and revisions in the index.
|
||||||
|
</p>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Package</th>
|
||||||
|
<th>Version/Revision</th>
|
||||||
|
<th>Added on</th>
|
||||||
|
<th>Source</th>
|
||||||
|
</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#entries}}
|
||||||
|
{{#TimelinePageEntryPackage}}
|
||||||
|
<tr>
|
||||||
|
<td class="col-sm-2"><a href="/package/{{timelinePageEntryPkgId}}">{{timelinePageEntryPkgId}}</a></td>
|
||||||
|
<td class="col-sm-1">Version</td>
|
||||||
|
<td class="col-sm-3" data-order="{{timelinePageEntryTimestampPosix}}">{{timelinePageEntryTimestamp}}</td>
|
||||||
|
<td class="col-sm-6">
|
||||||
|
<dl class="row">
|
||||||
|
{{#timelinePageEntrySource}}
|
||||||
|
{{> packageVersionSource}}
|
||||||
|
{{/timelinePageEntrySource}}
|
||||||
|
</dl>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{/TimelinePageEntryPackage}}
|
||||||
|
{{#TimelinePageEntryRevision}}
|
||||||
|
<tr>
|
||||||
|
<td>{{timelinePageEntryPkgId}}</td>
|
||||||
|
<td>Revision</td>
|
||||||
|
<td data-order="{{timelinePageEntryTimestampPosix}}">{{timelinePageEntryTimestamp}}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{{/TimelinePageEntryRevision}}
|
||||||
|
{{/entries}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
const table = $('table').DataTable({
|
||||||
|
order: [[2, 'desc']],
|
||||||
|
lengthMenu: [ [50, 100, 500, -1], [50, 100, 500, "All"] ]
|
||||||
|
})
|
||||||
|
const urlParams = new URLSearchParams(location.search)
|
||||||
|
if (urlParams.has('q')) {
|
||||||
|
table.search(urlParams.get('q')).draw()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user