1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
18 import catalog
::catalog_json
20 redef class NitwebConfig
22 # Catalog to pass to handlers.
23 var catalog
: Catalog is noinit
27 # This method should be called at nitweb startup.
29 self.catalog
= new Catalog(modelbuilder
)
30 self.catalog
.build_catalog
(model
.mpackages
)
37 use
("/catalog/packages/", new APICatalogPackages(config
))
38 use
("/catalog/stats", new APICatalogStats(config
))
40 use
("/catalog/tags", new APICatalogTags(config
))
41 use
("/catalog/tag/:tid", new APICatalogTag(config
))
43 use
("/catalog/person/:pid", new APICatalogPerson(config
))
44 use
("/catalog/person/:pid/maintaining", new APICatalogMaintaining(config
))
45 use
("/catalog/person/:pid/contributing", new APICatalogContributing(config
))
49 abstract class APICatalogHandler
52 # Sorter used to sort packages
54 # Sorting is based on mpackage score.
55 var mpackages_sorter
= new CatalogScoreSorter(config
.catalog
) is lazy
58 # Get all the packages from the catalog using pagination
60 # `GET /packages?p=1&n=10`: get the list of catalog by page
61 class APICatalogPackages
62 super APICatalogHandler
64 redef fun get
(req
, res
) do
65 var page
= req
.int_arg
("p")
66 var limit
= req
.int_arg
("n")
67 var mpackages
= config
.catalog
.mpackages
.values
.to_a
68 mpackages_sorter
.sort
(mpackages
)
69 var response
= new JsonArray.from
(mpackages
)
70 res
.json paginate
(response
, response
.length
, page
, limit
)
74 # Get the catalog statistics
76 # `GET /stats`: return the catalog statistics
78 super APICatalogHandler
80 redef fun get
(req
, res
) do
81 res
.json config
.catalog
.catalog_stats
85 # Get all the tags from the catalog
87 # `GET /tags`: the list of tags associated with their number of packages
89 super APICatalogHandler
91 # Sorter to sort tags alphabetically
92 var tags_sorter
= new CatalogTagsSorter
94 redef fun get
(req
, res
) do
95 var obj
= new JsonObject
97 var tags
= config
.catalog
.tag2proj
.keys
.to_a
98 tags_sorter
.sort
(tags
)
101 if not config
.catalog
.tag2proj
.has_key
(tag
) then continue
102 obj
[tag
] = config
.catalog
.tag2proj
[tag
].length
108 # Get the packages related to a tag
110 # `GET /tag/:tid?p=1&n=10`: return a paginated list of packages
112 super APICatalogHandler
114 redef fun get
(req
, res
) do
115 var page
= req
.int_arg
("p")
116 var limit
= req
.int_arg
("n")
117 var id
= req
.param
("tid")
119 res
.api_error
(400, "Missing tag")
122 id
= id
.from_percent_encoding
123 if not config
.catalog
.tag2proj
.has_key
(id
) then
124 res
.api_error
(404, "Tag not found")
127 var obj
= new JsonObject
129 var mpackages
= config
.catalog
.tag2proj
[id
]
130 mpackages_sorter
.sort
(mpackages
)
131 var response
= new JsonArray.from
(mpackages
)
132 obj
["packages"] = paginate
(response
, response
.length
, page
, limit
)
137 # Get a person existing in the catalog
139 # `GET /person/:pid`: get the person with `pid`
140 class APICatalogPerson
141 super APICatalogHandler
143 # Get the person with `:pid` or throw a 404 error
144 fun get_person
(req
: HttpRequest, res
: HttpResponse): nullable Person do
145 var id
= req
.param
("pid")
147 res
.api_error
(400, "Missing package full_name")
150 id
= id
.from_percent_encoding
151 if not config
.catalog
.name2person
.has_key
(id
) then
152 res
.api_error
(404, "Person not found")
155 return config
.catalog
.name2person
[id
]
158 redef fun get
(req
, res
) do
159 var person
= get_person
(req
, res
)
160 if person
== null then return
165 # Get the list of mpackages maintained by a person
167 # `GET /person/:pid/maintaining?p=1&n=10`: return a paginated list of packages
168 class APICatalogMaintaining
169 super APICatalogPerson
171 redef fun get
(req
, res
) do
172 var person
= get_person
(req
, res
)
173 if person
== null then return
175 var page
= req
.int_arg
("p")
176 var limit
= req
.int_arg
("n")
177 var array
= new Array[MPackage]
178 if config
.catalog
.maint2proj
.has_key
(person
) then
179 array
= config
.catalog
.maint2proj
[person
].to_a
181 mpackages_sorter
.sort
(array
)
182 var response
= new JsonArray.from
(array
)
183 res
.json paginate
(response
, response
.length
, page
, limit
)
187 # Get the list of mpackages contributed by a person
189 # `GET /person/:pid/contributing?p=1&n=10`: return a paginated list of packages
190 class APICatalogContributing
191 super APICatalogPerson
193 redef fun get
(req
, res
) do
194 var person
= get_person
(req
, res
)
195 if person
== null then return
197 var page
= req
.int_arg
("p")
198 var limit
= req
.int_arg
("n")
199 var array
= new Array[MPackage]
200 if config
.catalog
.contrib2proj
.has_key
(person
) then
201 array
= config
.catalog
.contrib2proj
[person
].to_a
203 mpackages_sorter
.sort
(array
)
204 var response
= new JsonArray.from
(array
)
205 res
.json paginate
(response
, response
.length
, page
, limit
)
209 redef class APIEntity
210 redef fun get
(req
, res
) do
211 var mentity
= mentity_from_uri
(req
, res
)
212 if mentity
== null then return
214 # Special case for packages (catalog view)
215 if mentity
isa MPackage then
216 res
.raw_json mentity
.to_full_catalog_json
(plain
=true, config
.mainmodule
, config
.catalog
)
218 res
.raw_json mentity
.to_full_json
(config
.mainmodule
)
223 redef class APISearch
224 super APICatalogHandler
226 redef fun search
(query
, limit
) do
227 var index
= config
.view
.index
229 # lookup by name prefix
230 var matches
= index
.find_by_name_prefix
(query
).uniq
.
231 sort
(lname_sorter
, name_sorter
, kind_sorter
)
232 matches
= matches
.rerank
.sort
(vis_sorter
, score_sorter
)
235 var malus
= matches
.length
236 if config
.catalog
.tag2proj
.has_key
(query
) then
237 for mpackage
in config
.catalog
.tag2proj
[query
] do
238 matches
.add
new IndexMatch(mpackage
, malus
)
241 matches
= matches
.uniq
.rerank
.sort
(vis_sorter
, score_sorter
)
244 # lookup by full_name prefix
245 malus
= matches
.length
246 var full_matches
= new IndexMatches
247 for match
in index
.find_by_full_name_prefix
(query
).
248 sort
(lfname_sorter
, fname_sorter
) do
250 full_matches
.add match
252 matches
= matches
.uniq
254 # lookup by similarity
255 malus
= matches
.length
256 var sim_matches
= new IndexMatches
257 for match
in index
.find_by_similarity
(query
).sort
(score_sorter
, lname_sorter
, name_sorter
) do
258 if match
.score
> query
.length
then break
260 sim_matches
.add match
262 matches
.add_all sim_matches
263 matches
= matches
.uniq
264 return matches
.rerank
.sort
(vis_sorter
, score_sorter
).mentities
267 private var score_sorter
= new ScoreComparator
268 private var vis_sorter
= new VisibilityComparator
269 private var name_sorter
= new NameComparator
270 private var lname_sorter
= new NameLengthComparator
271 private var fname_sorter
= new FullNameComparator
272 private var lfname_sorter
= new FullNameLengthComparator
273 private var kind_sorter
= new MEntityComparator
278 # Build the catalog from `mpackages`
279 fun build_catalog
(mpackages
: Array[MPackage]) do
281 for p
in mpackages
do
284 modelbuilder
.scan_group
(g
)
287 for gg
in p
.mgroups
do for m
in gg
.mmodules
do
288 for im
in m
.in_importation
.direct_greaters
do
290 if ip
== null or ip
== p
then continue
296 for mpackage
in mpackages
do
297 package_page
(mpackage
)
299 mpackage_stats
(mpackage
)
305 # Serialize the full catalog version of `self` to JSON
307 # See: `FullCatalogSerializer`
308 fun to_full_catalog_json
(mainmodule
: MModule, catalog
: Catalog, plain
, pretty
: nullable Bool): String do
309 var stream
= new StringWriter
310 var serializer
= new FullCatalogSerializer(stream
, mainmodule
, catalog
)
311 serializer
.plain_json
= plain
or else false
312 serializer
.pretty_json
= pretty
or else false
313 serializer
.serialize
self
318 redef fun core_serialize_to
(v
) do
320 v
.serialize_attribute
("metadata", metadata
)
321 if v
isa FullCatalogSerializer then
322 v
.serialize_attribute
("stats", v
.catalog
.mpackages_stats
[self])
324 var parents
= v
.catalog
.deps
[self].direct_greaters
.to_a
325 v
.serialize_attribute
("dependencies", v
.deps_to_json
(parents
))
326 var children
= v
.catalog
.deps
[self].direct_smallers
.to_a
327 v
.serialize_attribute
("clients", v
.deps_to_json
(children
))
332 # CatalogSerializer decorate the Package JSON with full catalog metadata
334 # See MEntity::to_full_catalog_json.
335 class FullCatalogSerializer
336 super FullJsonSerializer
338 # Catalog used to decorate the MPackages
341 private fun deps_to_json
(mpackages
: Array[MPackage]): JsonArray do
342 var res
= new JsonArray
343 for mpackage
in mpackages
do
344 res
.add dep_to_json
(mpackage
)
349 private fun dep_to_json
(mpackage
: MPackage): JsonObject do
350 var obj
= new JsonObject
351 obj
["name"] = mpackage
.name
352 var mdoc
= mpackage
.mdoc_or_fallback
354 obj
["synopsis"] = mdoc
.synopsis
.write_to_string