git.iko.soy/rowtype-yoga/purescript-docs-search
on master help sign in
Implement sort by package popularity for declarations

Allow searching for modules

commit80d005944ab558ca8f6bd85351657f20a9803aea authorklntsky <klntsky@gmail.com> dateSat 18 Jul 2020, 00:14 (6 years ago) parent17ad192
15 files changed+337−114
src/Docs/Search/App.purs+36−13 src/Docs/Search/App/SearchResults.purs+30−5 src/Docs/Search/App/Sidebar.purs+19−6 src/Docs/Search/BrowserEngine.purs+2 src/Docs/Search/Declarations.purs+2−1 src/Docs/Search/Engine.purs+52−19 src/Docs/Search/IndexBuilder.purs+5−4 src/Docs/Search/Interactive.purs+24−18 src/Docs/Search/ModuleIndex.purs+75−6 src/Docs/Search/NodeEngine.purs+2 src/Docs/Search/PackageIndex.purs+16−41 src/Docs/Search/Score.purs+47 src/Docs/Search/TypeIndex.purs+1−1 test/Main.purs+2 test/Test/ModuleIndex.purs+24
src/Docs/Search/App.purs+36−13
@@ -3,9 +3,12 @@ module Docs.Search.App where
33
44import Prelude
55
6+import Control.Alt (alt)
67import Control.Coroutine as Coroutine
78import Data.Maybe (Maybe(..))
89import Data.Newtype (wrap)
10+import Data.Tuple (Tuple(..))
11+import Data.Tuple.Nested ((/\))
912import Docs.Search.App.SearchField as SearchField
1013import Docs.Search.App.SearchResults as SearchResults
1114import Docs.Search.App.Sidebar as Sidebar
@@ -50,28 +53,29 @@ main = do
5053 , searchResults
5154 , pageContents
5255 , sidebarContainer
53− , realSidebar } -> do
56+ , realSidebar
57+ , isIndexHTML
58+ } -> do
5459
5560 -- Hide real sidebar completely - we are going to recreate it as Halogen component.
5661 ChildNode.remove $ Element.toChildNode realSidebar
5762
5863 HA.runHalogenAff do
5964 packageIndex <- PackageIndex.loadPackageIndex
60− moduleIndex <- ModuleIndex.loadModuleIndex
65+ moduleIndex <- ModuleIndex.unpackModuleIndex <$> ModuleIndex.loadModuleIndex
66+ let scores = PackageIndex.mkScoresFromPackageIndex packageIndex
6167
62− let initialSearchEngineState = { packageIndex: packageIndex
63− , moduleIndex: moduleIndex
68+ let initialSearchEngineState = { packageIndex
69+ , moduleIndex
6470 , index: mempty
6571 , typeIndex: mempty
72+ , scores
6673 }
6774
6875 resultsComponent =
6976 SearchResults.mkComponent initialSearchEngineState pageContents markdownIt
7077
7178 sfio <- runUI SearchField.component unit searchField
72− sbio <- do
73− component <- Sidebar.mkComponent moduleIndex
74− runUI component unit sidebarContainer
7579 srio <- runUI resultsComponent unit searchResults
7680
7781 sfio.subscribe $
@@ -91,6 +95,10 @@ main = do
9195
9296 addEventListener hashchange listener true (Window.toEventTarget win)
9397
98+ sbio <- do
99+ component <- Sidebar.mkComponent moduleIndex.packageModules isIndexHTML
100+ runUI component unit sidebarContainer
101+
94102 -- Subscribe to window focus events
95103 H.liftEffect do
96104
@@ -131,11 +139,14 @@ insertStyle doc = do
131139 /* Add a margin between badge icons and package/module names. */
132140 margin-right: 0.25em;
133141 }
134− .li-package {
142+ .li-package > details > summary {
135143 font-weight: bold;
136144 cursor: pointer;
137145 color: #c4953a;
138146 }
147+ .li-package > details > summary:hover {
148+ color: #7b5904;
149+ }
139150 /* Make spaces narrower in the sidebar */
140151 .li-package > details > ul {
141152 margin-top: auto;
@@ -185,7 +196,8 @@ insertVersionInfo doc = do
185196 void $ Node.appendChild suffix versionInfo
186197
187198
188−-- | Query the DOM for specific elements that should always be present.
199+-- | Query the DOM for specific elements that should always be present and determine if we are on
200+-- | `index.html` or not.
189201getContainers
190202 :: Document.Document
191203 -> Effect (Maybe { searchField :: HTML.HTMLElement
@@ -193,6 +205,7 @@ getContainers
193205 , pageContents :: Element.Element
194206 , sidebarContainer :: HTML.HTMLElement
195207 , realSidebar :: Element.Element
208+ , isIndexHTML :: Sidebar.IsIndexHTML
196209 })
197210getContainers doc = do
198211 let docPN = Document.toParentNode doc
@@ -204,19 +217,29 @@ getContainers doc = do
204217 ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN
205218 mbMainContainer <-
206219 ParentNode.querySelector (wrap ".everything-except-footer > main") docPN
207− mbRealSidebar <-
208− ParentNode.querySelector (wrap ".col--aside") docPN
220+ mbSidebarStatus <-
221+ alt <$> (map (Tuple Sidebar.NotIndexHTML) <$>
222+ ParentNode.querySelector (wrap ".col--aside") docPN)
223+ -- If there's no sidebar, that means we are currently on `index.html`.
224+ <*> (map (Tuple Sidebar.IsIndexHTML) <$>
225+ ParentNode.querySelector (wrap ".col--main") docPN)
209226 case unit of
210227 _ | Just banner <- mbBanner
211228 , Just everything <- mbEverything
212229 , Just pageContents <- mbContainer
213230 , Just mainContainer <- mbMainContainer
214− , Just realSidebar <- mbRealSidebar -> do
231+ , Just (isIndexHTML /\ realSidebar) <- mbSidebarStatus -> do
215232 search <- Document.createElement "div" doc
216233 void $ Node.appendChild (Element.toNode search) (Element.toNode banner)
217234 pure do
218235 searchField <- fromElement search
219236 searchResults <- fromElement everything
220237 sidebarContainer <- fromElement mainContainer
221− pure { searchField, searchResults, pageContents, realSidebar, sidebarContainer }
238+ pure { searchField
239+ , searchResults
240+ , pageContents
241+ , realSidebar
242+ , sidebarContainer
243+ , isIndexHTML
244+ }
222245 | otherwise -> pure Nothing
src/Docs/Search/App/SearchResults.purs+30−5
@@ -6,10 +6,11 @@ import Docs.Search.BrowserEngine (PartialIndex, browserSearchEngine)
66import Docs.Search.Config (config)
77import Docs.Search.Declarations (DeclLevel(..), declLevelToHashAnchor)
88import Docs.Search.DocsJson (DataDeclType(..))
9−import Docs.Search.Extra (homePageFromRepository, (>#>))
10−import Docs.Search.PackageIndex (PackageResult)
119import Docs.Search.Engine (Result(..))
1210import Docs.Search.Engine as Engine
11+import Docs.Search.Extra (homePageFromRepository, (>#>))
12+import Docs.Search.ModuleIndex (ModuleResult)
13+import Docs.Search.PackageIndex (PackageResult)
1314import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
1415import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..), QualifiedName(..), Type(..), TypeArgument(..), joinForAlls, joinRows)
1516import Docs.Search.TypeIndex (TypeIndex)
@@ -230,9 +231,10 @@ renderResult
230231 . MD.MarkdownIt
231232 -> Result
232233 -> Array (HH.HTML a Action)
233−renderResult markdownIt (DeclResult sr) = renderSearchResult markdownIt sr
234−renderResult markdownIt (TypeResult sr) = renderSearchResult markdownIt sr
235−renderResult markdownIt (PackResult sr) = renderPackageResult sr
234+renderResult markdownIt (DeclResult r) = renderSearchResult markdownIt r
235+renderResult markdownIt (TypeResult r) = renderSearchResult markdownIt r
236+renderResult markdownIt (PackResult r) = renderPackageResult r
237+renderResult markdownIt (MdlResult r) = renderModuleResult r
236238
237239
238240renderPackageResult
@@ -263,6 +265,29 @@ renderPackageResult { name, description, repository } =
263265 ]
264266
265267
268+renderModuleResult
269+ :: forall a
270+ . ModuleResult
271+ -> Array (HH.HTML a Action)
272+renderModuleResult { name, package } =
273+ [ HH.div [ HP.class_ (wrap "result") ]
274+ [ HH.h3 [ HP.class_ (wrap "result__title") ]
275+ [ HH.span [ HP.classes [ wrap "result__badge"
276+ , wrap "badge"
277+ , wrap "badge--module" ]
278+ , HP.title "Module"
279+ ]
280+ [ HH.text "M" ]
281+
282+ , HH.a [ HP.class_ (wrap "result__link")
283+ , HP.href $ name <> ".html"
284+ ]
285+ [ HH.text name ]
286+ ]
287+ ]
288+ ]
289+
290+
266291renderSearchResult
267292 :: forall a
268293 . MD.MarkdownIt
src/Docs/Search/App/Sidebar.purs+19−6
@@ -1,7 +1,7 @@
11module Docs.Search.App.Sidebar where
22
33import Docs.Search.Config (config)
4−import Docs.Search.ModuleIndex (ModuleIndex)
4+import Docs.Search.ModuleIndex (PackedModuleIndex)
55import Docs.Search.Types (ModuleName, PackageName)
66
77import Prelude
@@ -36,24 +36,32 @@ data GroupingMode = GroupByPackage | DontGroup
3636
3737derive instance groupingModeEq :: Eq GroupingMode
3838
39+-- | Whether current page is `index.html` or not. `index.html` is special, it
40+-- | has no sidebar, hence the difference must be taken into account.
41+data IsIndexHTML = IsIndexHTML | NotIndexHTML
42+
43+derive instance isIndexHTMLEq :: Eq IsIndexHTML
3944
4045type State = { moduleIndex :: Map PackageName (Set ModuleName)
4146 , groupingMode :: GroupingMode
4247 , moduleNames :: Array ModuleName
48+ , isIndexHTML :: IsIndexHTML
4349 }
4450
4551
4652mkComponent
4753 :: forall i
48− . ModuleIndex
54+ . PackedModuleIndex
55+ -> IsIndexHTML
4956 -> Aff (H.Component HH.HTML Query i Action Aff)
50−mkComponent moduleIndex = do
57+mkComponent moduleIndex isIndexHTML = do
5158 groupingMode <- H.liftEffect loadGroupingModeFromLocalStorage
5259 pure $
5360 H.mkComponent
5461 { initialState: const { moduleIndex
5562 , groupingMode
5663 , moduleNames
64+ , isIndexHTML
5765 }
5866 , render
5967 , eval: H.mkEval $ H.defaultEval { handleAction = handleAction
@@ -96,9 +104,14 @@ render
96104 :: forall m
97105 . State
98106 -> H.ComponentHTML Action () m
99−render { moduleIndex, groupingMode, moduleNames } =
100−
101− HH.div [ HP.classes [ wrap "col", wrap "col--aside" ] ]
107+render { moduleIndex, groupingMode, moduleNames, isIndexHTML } =
108+
109+ HH.div [ HP.classes [ wrap "col"
110+ , wrap $ if isIndexHTML == IsIndexHTML
111+ then "col--main"
112+ else "col--aside"
113+ ]
114+ ]
102115
103116 [ HH.h3_ [ HH.text "Modules" ]
104117 , HH.input [ HP.id_ "group-modules__input"
src/Docs/Search/BrowserEngine.purs+2
@@ -7,6 +7,7 @@ import Docs.Search.Engine (Engine, EngineState, Index)
77import Docs.Search.SearchResult (SearchResult)
88import Docs.Search.TypeIndex (TypeIndex)
99import Docs.Search.TypeIndex as TypeIndex
10+import Docs.Search.ModuleIndex as ModuleIndex
1011
1112import Prelude
1213
@@ -112,6 +113,7 @@ browserSearchEngine =
112113 { queryIndex: query
113114 , queryTypeIndex: TypeIndex.query
114115 , queryPackageIndex
116+ , queryModuleIndex: ModuleIndex.queryModuleIndex
115117 }
116118
117119
src/Docs/Search/Declarations.purs+2−1
@@ -1,12 +1,13 @@
11module Docs.Search.Declarations where
22
33import Docs.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(..), Declaration(..), DocsJson(..), SourceSpan)
4−import Docs.Search.PackageIndex (Scores)
4+import Docs.Search.Score (Scores)
55import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
66import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), Kind, joinForAlls)
77import Docs.Search.Types (ModuleName, PackageName)
88
99import Prelude
10+
1011import Control.Alt ((<|>))
1112import Data.Array ((!!))
1213import Data.Array as Array
src/Docs/Search/Engine.purs+52−19
@@ -1,19 +1,22 @@
11module Docs.Search.Engine where
22
3+import Docs.Search.ModuleIndex (ModuleIndex, ModuleResult)
34import Docs.Search.PackageIndex (PackageIndex, PackageResult)
4−import Docs.Search.ModuleIndex (ModuleIndex)
5+import Docs.Search.Score (Scores)
56import Docs.Search.SearchResult (SearchResult, typeOfResult)
67import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty)
8+import Docs.Search.Types (PackageName, ModuleName)
79
810import Prelude
911
10−import Data.Newtype (unwrap)
1112import Data.Array as Array
1213import Data.Either (hush)
14+import Data.Function (on)
15+import Data.List (List)
1316import Data.Maybe (Maybe(..))
17+import Data.Newtype (unwrap)
1418import Data.Search.Trie (Trie)
1519import Data.String.Common (toLower) as String
16−import Data.List (List)
1720
1821
1922type Index = Trie Char (List SearchResult)
@@ -30,6 +33,8 @@ type Engine m index typeIndex =
3033 :: Query m typeIndex TypeQuery SearchResult
3134 , queryPackageIndex
3235 :: Query m PackageIndex String PackageResult
36+ , queryModuleIndex
37+ :: Scores -> ModuleIndex -> String -> Array ModuleResult
3338 }
3439
3540
@@ -38,6 +43,7 @@ type EngineState index typeIndex
3843 , typeIndex :: typeIndex
3944 , packageIndex :: PackageIndex
4045 , moduleIndex :: ModuleIndex
46+ , scores :: Scores
4147 }
4248
4349
@@ -47,27 +53,51 @@ mkEngineState
4753 -> typeIndex
4854 -> PackageIndex
4955 -> ModuleIndex
56+ -> Scores
5057 -> EngineState index typeIndex
51−mkEngineState index typeIndex packageIndex moduleIndex =
52− { index, typeIndex, packageIndex, moduleIndex }
58+mkEngineState index typeIndex packageIndex moduleIndex scores =
59+ { index, typeIndex, packageIndex, moduleIndex, scores }
5360
5461
5562data Result
5663 = DeclResult SearchResult
5764 | TypeResult SearchResult
5865 | PackResult PackageResult
66+ | MdlResult ModuleResult
67+
68+
69+getResultScore :: Result -> Int
70+getResultScore (DeclResult r) = (unwrap r).score
71+getResultScore (TypeResult r) = (unwrap r).score
72+getResultScore (PackResult r) = r.score
73+getResultScore (MdlResult r) = r.score
5974
6075
61−sortByPopularity :: Array Result -> Array Result
62−sortByPopularity =
63− Array.sortWith
64− \result ->
65− -- Sort by popularity, show packages before
66− -- ordinary definitions.
67− - case result of
68− DeclResult r -> 2 * (unwrap r).score
69− TypeResult r -> 2 * (unwrap r).score
70− PackResult r -> 2 * r.score + 1
76+getResultPackageName :: Result -> PackageName
77+getResultPackageName (DeclResult r) = (unwrap r).packageName
78+getResultPackageName (TypeResult r) = (unwrap r).packageName
79+getResultPackageName (PackResult r) = r.name
80+getResultPackageName (MdlResult r) = r.package
81+
82+
83+getResultModuleName :: Result -> ModuleName
84+getResultModuleName (DeclResult r) = (unwrap r).moduleName
85+getResultModuleName (TypeResult r) = (unwrap r).moduleName
86+getResultModuleName (PackResult r) = ""
87+getResultModuleName (MdlResult r) = r.name
88+
89+
90+sortByPopularity
91+ :: forall index typeIndex
92+ . EngineState index typeIndex
93+ -> Array Result
94+ -> Array Result
95+sortByPopularity { packageIndex } =
96+ Array.sortBy (
97+ compare `on` (getResultScore >>> negate) <>
98+ compare `on` getResultPackageName <>
99+ compare `on` getResultModuleName
100+ )
71101
72102
73103query
@@ -80,13 +110,16 @@ query engine state input =
80110
81111 -- A declaration/package query
82112 Nothing -> do
83− let lower = String.toLower input
113+ let lowerCased = String.toLower input
84114
85− response <- engine.queryIndex state.index lower
86− packageResponse <- engine.queryPackageIndex state.packageIndex lower
115+ response <- engine.queryIndex state.index lowerCased
116+ packageResponse <- engine.queryPackageIndex state.packageIndex lowerCased
117+ let moduleResponse =
118+ engine.queryModuleIndex state.scores state.moduleIndex lowerCased
87119
88− pure { results: sortByPopularity $
120+ pure { results: sortByPopularity state $
89121 (packageResponse.results <#> PackResult) <>
122+ (moduleResponse <#> MdlResult) <>
90123 (response.results <#> DeclResult)
91124
92125 -- No need to update package index (it never changes).
src/Docs/Search/IndexBuilder.purs+5−4
@@ -5,8 +5,9 @@ import Docs.Search.Config (config)
55import Docs.Search.Declarations (Declarations(..), mkDeclarations)
66import Docs.Search.DocsJson (DocsJson)
77import Docs.Search.Extra ((>#>))
8−import Docs.Search.ModuleIndex (ModuleIndex, mkModuleIndex)
9−import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo, mkScores)
8+import Docs.Search.ModuleIndex (PackedModuleIndex, mkPackedModuleIndex)
9+import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo)
10+import Docs.Search.Score (mkScores)
1011import Docs.Search.SearchResult (SearchResult)
1112import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex)
1213
@@ -78,7 +79,7 @@ run' cfg = do
7879 index = mkDeclarations scores docsJsons
7980 typeIndex = mkTypeIndex scores docsJsons
8081 packageInfo = mkPackageInfo packageMetas
81− moduleIndex = mkModuleIndex index
82+ moduleIndex = mkPackedModuleIndex index
8283
8384 createDirectories cfg
8485
@@ -223,7 +224,7 @@ writePackageInfo packageInfo = do
223224 header = "window.DocsSearchPackageIndex = "
224225
225226
226−writeModuleIndex :: ModuleIndex -> Aff Unit
227+writeModuleIndex :: PackedModuleIndex -> Aff Unit
227228writeModuleIndex moduleIndex = do
228229 writeTextFile UTF8 config.moduleIndexPath $
229230 header <> stringify (encodeJson moduleIndex)
src/Docs/Search/Interactive.purs+24−18
@@ -1,36 +1,36 @@
11-- | Definitions for the "search REPL".
22module Docs.Search.Interactive where
33
4+import Prelude
5+
6+import Data.Array as Array
7+import Data.Identity (Identity(..))
8+import Data.Maybe (fromMaybe)
9+import Data.Newtype (un, unwrap, wrap)
10+import Data.Search.Trie as Trie
11+import Data.String (length) as String
12+import Data.String.Common (split, trim) as String
13+import Data.Tuple (fst)
414import Docs.Search.Declarations (Declarations, mkDeclarations)
5−import Docs.Search.ModuleIndex (mkModuleIndex)
615import Docs.Search.DocsJson (DataDeclType(..))
716import Docs.Search.Engine (mkEngineState, Result(..))
817import Docs.Search.Engine as Engine
918import Docs.Search.Extra (listToString, stringToList, (>#>))
1019import Docs.Search.IndexBuilder as IndexBuilder
20+import Docs.Search.ModuleIndex (ModuleResult, mkPackedModuleIndex, unpackModuleIndex)
1121import Docs.Search.NodeEngine (nodeEngine)
12−import Docs.Search.PackageIndex (PackageResult, mkPackageIndex, mkPackageInfo, mkScores)
22+import Docs.Search.PackageIndex (PackageResult, mkPackageIndex, mkPackageInfo)
23+import Docs.Search.Score (mkScores)
1324import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
1425import Docs.Search.Terminal (bold, cyan, green, yellow)
1526import Docs.Search.TypeDecoder (Constraint, FunDeps, Kind, QualifiedName, Type, TypeArgument)
1627import Docs.Search.TypeIndex (resultsWithTypes)
1728import Docs.Search.TypePrinter (keyword, showConstraint, showFunDeps, showKind, showType, showTypeArgument, space, syntax)
18−
19−import Prelude
20−
21−import Data.Identity (Identity(..))
22−import Data.Array as Array
23−import Data.Maybe (fromMaybe)
24−import Data.Search.Trie as Trie
25−import Data.String (length) as String
26−import Data.String.Common (split, trim) as String
27−import Data.Tuple (fst)
2829import Effect (Effect)
2930import Effect.Aff (launchAff_)
3031import Effect.Class (liftEffect)
3132import Effect.Console (log)
3233import Node.ReadLine (createConsoleInterface, question)
33−import Data.Newtype (un, unwrap, wrap)
3434
3535
3636type Config =
@@ -52,8 +52,8 @@ run cfg = launchAff_ $ do
5252 index = mkDeclarations scores docsJsons
5353 typeIndex = docsJsons >>= resultsWithTypes scores
5454 packageIndex = mkPackageIndex $ mkPackageInfo packageMetas
55− moduleIndex = mkModuleIndex index
56− engineState = mkEngineState (unwrap index) typeIndex packageIndex moduleIndex
55+ moduleIndex = unpackModuleIndex $ mkPackedModuleIndex index
56+ engineState = mkEngineState (unwrap index) typeIndex packageIndex moduleIndex scores
5757
5858 let countOfDefinitions = Trie.size $ unwrap index
5959 countOfTypeDefinitions = Array.length typeIndex
@@ -115,9 +115,10 @@ mkCompleter index input = do
115115
116116showResult :: Result -> String
117117showResult = case _ of
118− DeclResult sr -> showSearchResult sr
119− TypeResult sr -> showSearchResult sr
120− PackResult pr -> showPackageResult pr
118+ DeclResult r -> showSearchResult r
119+ TypeResult r -> showSearchResult r
120+ PackResult r -> showPackageResult r
121+ MdlResult r -> showModuleResult r
121122
122123
123124showSearchResult :: SearchResult -> String
@@ -138,6 +139,11 @@ showPackageResult { name, description } =
138139 (description >#> \text -> "\n\n" <> leftShift 3 text <> "\n")
139140
140141
142+showModuleResult :: ModuleResult -> String
143+showModuleResult { name, package } =
144+ bold (cyan "module") <> " " <> bold (green name)
145+
146+
141147showSignature ::
142148 forall rest.
143149 { name :: String
src/Docs/Search/ModuleIndex.purs+75−6
@@ -4,30 +4,92 @@ import Docs.Search.Config (config)
44import Docs.Search.Declarations (Declarations(..))
55import Docs.Search.SearchResult (SearchResult(..))
66import Docs.Search.Types (ModuleName, PackageName)
7+import Docs.Search.Extra (stringToList)
8+import Docs.Search.Score (Scores, normalizePackageName)
79
810import Prelude
11+
12+import Control.Monad.State (execState, modify_)
913import Control.Promise (Promise, toAffE)
1014import Data.Argonaut.Core (Json)
1115import Data.Argonaut.Decode (decodeJson)
16+import Data.Array as Array
1217import Data.Either (hush)
13−import Data.Foldable (foldr)
14−import Data.List (List)
18+import Data.Foldable (foldl)
19+import Data.Lens ((%~))
20+import Data.Lens.Record (prop)
21+import Data.List (List, (:))
1522import Data.Map (Map)
1623import Data.Map as Map
1724import Data.Maybe (fromMaybe)
25+import Data.Search.Trie (Trie)
1826import Data.Search.Trie as Trie
1927import Data.Set (Set)
2028import Data.Set as Set
29+import Data.String.CodeUnits (toCharArray) as String
30+import Data.String.Common (split, toLower) as String
31+import Data.String.Pattern (Pattern(..))
32+import Data.Symbol (SProxy(..))
33+import Data.Traversable (foldr, for_)
34+import Data.Tuple.Nested ((/\))
2135import Effect (Effect)
2236import Effect.Aff (Aff)
2337
2438
25−type ModuleIndex = Map PackageName (Set ModuleName)
39+-- | Module index that is actually stored in a JS file.
40+type PackedModuleIndex = Map PackageName (Set ModuleName)
41+
42+-- | "Expanded" module index that can be queried quickly.
43+type ModuleIndex = { packageModules :: Map PackageName (Set ModuleName)
44+ , modulePackages :: Map ModuleName PackageName
45+ , index :: Trie Char ModuleName
46+ }
47+
48+
49+type ModuleResult
50+ = { name :: ModuleName
51+ , package :: PackageName
52+ , score :: Int
53+ }
54+
55+
56+unpackModuleIndex :: PackedModuleIndex -> ModuleIndex
57+unpackModuleIndex packageModules =
58+ flip execState { packageModules, modulePackages: mempty, index: mempty } do
59+ for_ (Map.toUnfoldableUnordered packageModules :: Array _)
60+ \(packageName /\ moduleNames) -> do
61+ for_ moduleNames \moduleName -> do
62+ modify_ $ _modulePackages %~ Map.insert moduleName packageName
63+ for_ (extractModuleNameParts moduleName) \part -> do
64+ let partPath = Array.toUnfoldable $ String.toCharArray part
65+ modify_ $ _index %~ Trie.insert partPath moduleName
66+
67+
68+-- | E.g. `"Data.Array.ST" -> ["data.array.st", "array.st", "st"]`.
69+extractModuleNameParts :: ModuleName -> List String
70+extractModuleNameParts =
71+ foldl (\acc el -> el : map (_ <> "." <> el) acc) mempty <<< String.split (Pattern ".") <<< String.toLower
72+
73+
74+queryModuleIndex
75+ :: Scores
76+ -> ModuleIndex
77+ -> String
78+ -> Array ModuleResult
79+queryModuleIndex scores { index, modulePackages } query =
80+ let path = stringToList $ String.toLower query in
81+ Trie.queryValues path index #
82+ Array.fromFoldable #
83+ Array.nub <#>
84+ (\name -> do
85+ package <- Map.lookup name modulePackages
86+ pure { name, package, score: fromMaybe 0 $ Map.lookup (normalizePackageName package) scores }) #
87+ Array.catMaybes
2688
2789
2890-- | Constructs a mapping from packages to modules
29−mkModuleIndex :: Declarations -> ModuleIndex
30−mkModuleIndex (Declarations trie) =
91+mkPackedModuleIndex :: Declarations -> PackedModuleIndex
92+mkPackedModuleIndex (Declarations trie) =
3193 foldr (Map.unionWith Set.union) mempty $ extract <$> Trie.values trie
3294 where
3395 extract
@@ -39,7 +101,7 @@ mkModuleIndex (Declarations trie) =
39101 Map.singleton packageName (Set.singleton moduleName)
40102
41103
42−loadModuleIndex :: Aff ModuleIndex
104+loadModuleIndex :: Aff PackedModuleIndex
43105loadModuleIndex = do
44106 json <- toAffE $ load config.moduleIndexLoadPath
45107 pure $ fromMaybe mempty $ hush $ decodeJson json
@@ -48,3 +110,10 @@ loadModuleIndex = do
48110foreign import load
49111 :: String
50112 -> Effect (Promise Json)
113+
114+
115+_modulePackages :: forall a b rest. (a -> b) -> { modulePackages :: a | rest } -> { modulePackages :: b | rest }
116+_modulePackages = prop (SProxy :: SProxy "modulePackages")
117+
118+_index :: forall a b rest. (a -> b) -> { index :: a | rest } -> { index :: b | rest }
119+_index = prop (SProxy :: SProxy "index")
src/Docs/Search/NodeEngine.purs+2
@@ -3,6 +3,7 @@ module Docs.Search.NodeEngine where
33
44import Docs.Search.Extra (stringToList)
55import Docs.Search.PackageIndex as PackageIndex
6+import Docs.Search.ModuleIndex as ModuleIndex
67import Docs.Search.Engine (Engine, Query, Index, sortByDistance)
78import Docs.Search.SearchResult (SearchResult)
89import Docs.Search.TypeQuery (TypeQuery)
@@ -24,6 +25,7 @@ nodeEngine =
2425 { queryIndex
2526 , queryTypeIndex
2627 , queryPackageIndex: PackageIndex.queryPackageIndex
28+ , queryModuleIndex: ModuleIndex.queryModuleIndex
2729 }
2830
2931
src/Docs/Search/PackageIndex.purs+16−41
@@ -2,24 +2,24 @@ module Docs.Search.PackageIndex where
22
33import Docs.Search.Config (config)
44import Docs.Search.Extra (stringToList)
5+import Docs.Search.Score (Scores, getPackageScore, mkScores, normalizePackageName)
56
67import Prelude
78
8−import Data.Either (hush)
9−import Data.Maybe (Maybe, fromMaybe)
10−import Data.Newtype (unwrap, wrap)
11−import Effect (Effect)
12−import Effect.Aff (Aff)
13−import Web.Bower.PackageMeta (Dependencies, PackageMeta(..))
149import Control.Promise (Promise, toAffE)
1510import Data.Argonaut.Core (Json)
1611import Data.Argonaut.Decode (decodeJson)
1712import Data.Array as Array
13+import Data.Either (hush)
1814import Data.Map (Map)
1915import Data.Map as Map
16+import Data.Maybe (Maybe, fromMaybe)
17+import Data.Newtype (unwrap)
2018import Data.Search.Trie (Trie)
2119import Data.Search.Trie as Trie
22−import Data.String.CodeUnits as String
20+import Effect (Effect)
21+import Effect.Aff (Aff)
22+import Web.Bower.PackageMeta (PackageMeta(..))
2323
2424
2525type PackageResult
@@ -30,33 +30,11 @@ type PackageResult
3030 , repository :: Maybe String
3131 }
3232
33−type Scores = Map String Int
34−
3533type PackageIndex = Trie Char PackageResult
3634
3735type PackageInfo = Array PackageResult
3836
3937
40−-- | Construct a mapping from package names to their scores, based on number
41−-- of reverse dependencies.
42−mkScores :: Array PackageMeta -> Scores
43−mkScores =
44− Array.foldr
45− (\pm ->
46− updateScoresFor (unwrap pm).dependencies >>>
47− updateScoresFor (unwrap pm).devDependencies
48− )
49− mempty
50−
51− where
52− updateScoresFor :: Dependencies -> Scores -> Scores
53− updateScoresFor deps scores =
54− Array.foldr
55− (\dep -> Map.insertWith add dep 1)
56− scores
57− (deps # unwrap >>> map (_.packageName))
58−
59−
6038mkPackageInfo :: Array PackageMeta -> PackageInfo
6139mkPackageInfo pms =
6240 Array.fromFoldable $
@@ -66,7 +44,6 @@ mkPackageInfo pms =
6644 where
6745 packageScores = mkScores pms
6846
69−
7047 insert
7148 :: PackageMeta
7249 -> Map String PackageResult
@@ -82,12 +59,17 @@ mkPackageInfo pms =
8259 name
8360 { name
8461 , description: description
85− , score: fromMaybe 0 $ Map.lookup name packageScores
62+ , score: getPackageScore packageScores name
8663 , dependencies: unwrap dependencies <#> (_.packageName)
8764 , repository: repository <#> (_.url)
8865 }
8966
9067
68+mkScoresFromPackageIndex :: PackageIndex -> Scores
69+mkScoresFromPackageIndex =
70+ Trie.values >>> Array.foldr (\ { name, score } -> Map.insert name score) mempty
71+
72+
9173loadPackageIndex :: Aff PackageIndex
9274loadPackageIndex = do
9375 json <- toAffE (load config.packageInfoLoadPath)
@@ -97,17 +79,10 @@ loadPackageIndex = do
9779
9880mkPackageIndex :: PackageInfo -> PackageIndex
9981mkPackageIndex =
100−
10182 Array.foldr
102− (\package -> Trie.insert (shortNamePath package.name) package)
83+ (\package -> Trie.insert (stringToList $ normalizePackageName package.name) package)
10384 mempty
10485
105− where
106− shortNamePath name =
107− stringToList $
108− fromMaybe name $
109− String.stripPrefix (wrap "purescript-") name
110−
11186
11287queryPackageIndex
11388 :: forall m
@@ -117,9 +92,9 @@ queryPackageIndex
11792 -> m { index :: PackageIndex
11893 , results :: Array PackageResult
11994 }
120−queryPackageIndex index q =
95+queryPackageIndex index query =
12196 pure { index
122− , results: Array.fromFoldable $ Trie.queryValues (stringToList q) index
97+ , results: Array.fromFoldable $ Trie.queryValues (stringToList query) index
12398 }
12499
125100
src/Docs/Search/Score.pursnew file+47
@@ -0,0 +1,47 @@
1+module Docs.Search.Score where
2+
3+import Docs.Search.Types (PackageName)
4+
5+import Prelude
6+
7+import Data.Array as Array
8+import Data.Map (Map)
9+import Data.Map as Map
10+import Data.Maybe (fromMaybe)
11+import Data.Newtype (unwrap, wrap)
12+import Data.String.CodeUnits as String
13+import Web.Bower.PackageMeta (Dependencies, PackageMeta)
14+
15+
16+type Scores = Map PackageName Int
17+
18+
19+getPackageScore :: Scores -> PackageName -> Int
20+getPackageScore _ "<builtin>" = 1000000
21+getPackageScore _ "<local package>" = 2000000
22+getPackageScore scores name = fromMaybe 0 $ Map.lookup name scores
23+
24+
25+normalizePackageName :: PackageName -> PackageName
26+normalizePackageName packageName =
27+ fromMaybe packageName $ String.stripPrefix (wrap "purescript-") packageName
28+
29+
30+-- | Construct a mapping from package names to their scores, based on number
31+-- of reverse dependencies.
32+mkScores :: Array PackageMeta -> Scores
33+mkScores =
34+ Array.foldr
35+ (\pm ->
36+ updateScoresFor (unwrap pm).dependencies >>>
37+ updateScoresFor (unwrap pm).devDependencies
38+ )
39+ mempty
40+
41+ where
42+ updateScoresFor :: Dependencies -> Scores -> Scores
43+ updateScoresFor deps scores =
44+ Array.foldr
45+ (\dep -> Map.insertWith add dep 1)
46+ scores
47+ (deps # unwrap >>> map (_.packageName >>> normalizePackageName))
src/Docs/Search/TypeIndex.purs+1−1
@@ -4,7 +4,7 @@ module Docs.Search.TypeIndex where
44import Docs.Search.Config (config)
55import Docs.Search.Declarations (resultsForDeclaration)
66import Docs.Search.DocsJson (DocsJson(..))
7−import Docs.Search.PackageIndex (Scores)
7+import Docs.Search.Score (Scores)
88import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..))
99import Docs.Search.TypeDecoder (Type)
1010import Docs.Search.TypeQuery (TypeQuery)
test/Main.purs+2
@@ -6,6 +6,7 @@ import Docs.Search.TypeDecoder (Constraint(..), FunDep(..), FunDeps(..), Kind(..
66import Test.TypeQuery as TypeQuery
77import Test.IndexBuilder as IndexBuilder
88import Test.Declarations as Declarations
9+import Test.ModuleIndex as ModuleIndex
910
1011import Test.Extra (assertRight)
1112
@@ -28,6 +29,7 @@ mainTest = do
2829 TypeQuery.tests
2930 IndexBuilder.tests
3031 Declarations.tests
32+ ModuleIndex.tests
3133
3234 let mkJson x = unsafePartial $ fromRight $ jsonParser x
3335 suite "FunDeps decoder" do
test/Test/ModuleIndex.pursnew file+24
@@ -0,0 +1,24 @@
1+module Test.ModuleIndex where
2+
3+import Docs.Search.ModuleIndex (extractModuleNameParts)
4+
5+import Prelude
6+
7+import Data.List as List
8+import Test.Unit (TestSuite, suite, test)
9+import Test.Unit.Assert as Assert
10+
11+
12+tests :: TestSuite
13+tests = do
14+ suite "ModuleIndex" do
15+
16+ test "test #0" do
17+ Assert.equal (extractModuleNameParts "Data.Array.ST") (
18+ List.fromFoldable [ "st", "array.st", "data.array.st" ]
19+ )
20+
21+ test "test #1" do
22+ Assert.equal (extractModuleNameParts "Foo") (
23+ List.fromFoldable [ "foo" ]
24+ )