Property definitions

nitc $ Catalog :: defaultinit
# The main class of the calatog generator that has the knowledge
class Catalog

	# The modelbuilder
	# used to access the files and count source lines of code
	var modelbuilder: ModelBuilder

	# List of all packages by their names
	var mpackages = new HashMap[String, MPackage]

	# Packages by tag
	var tag2proj = new MultiHashMap[String, MPackage]

	# Packages by category
	var cat2proj = new MultiHashMap[String, MPackage]

	# Packages by maintainer
	var maint2proj = new MultiHashMap[Person, MPackage]

	# Packages by contributors
	var contrib2proj = new MultiHashMap[Person, MPackage]

	# Dependency between packages
	fun deps: HashDigraph[MPackage] do return modelbuilder.model.mpackage_importation_graph

	# Number of modules by package
	var mmodules = new Counter[MPackage]

	# Number of classes by package
	var mclasses = new Counter[MPackage]

	# Number of methods by package
	var mmethods = new Counter[MPackage]

	# Number of line of code by package
	var loc = new Counter[MPackage]

	# Number of errors
	var errors = new Counter[MPackage]

	# Number of warnings and advices
	var warnings = new Counter[MPackage]

	# Number of warnings per 1000 lines of code (w/kloc)
	var warnings_per_kloc = new Counter[MPackage]

	# Documentation score (between 0 and 100)
	var documentation_score = new Counter[MPackage]

	# Number of commits by package
	var commits = new Counter[MPackage]

	# Score by package
	#
	# The score is loosely computed using other metrics
	var score = new Counter[MPackage]

	# List of known people by their git string
	var persons = new HashMap[String, Person]

	# Map person short names to person objects
	var name2person = new HashMap[String, Person]

	# Package statistics cache
	var mpackages_stats = new HashMap[MPackage, MPackageStats]

	# Scan, register and add a contributor to a package
	fun register_contrib(person: String, mpackage: MPackage): Person
	do
		var p = persons.get_or_null(person)
		if p == null then
			var new_p = new Person.parse(person)
			# Maybe, we already have this person in fact?
			p = persons.get_or_null(new_p.to_s)
			if p == null then
				p = new_p
				persons[p.to_s] = p
			end
		end
		var projs = contrib2proj[p]
		if not projs.has(mpackage) then
			projs.add mpackage
			mpackage.metadata.contributors.add p
		end
		name2person[p.name] = p
		return p
	end

	# Compute information for a package
	fun package_page(mpackage: MPackage)
	do
		mpackages[mpackage.full_name] = mpackage

		var score = score[mpackage].to_f

		var mdoc = mpackage.mdoc_or_fallback
		if mdoc != null then
			score += 100.0
			score += mdoc.content.length.score
		end
		var metadata = mpackage.metadata

		var tryit = metadata.tryit
		if tryit != null then
			score += 1.0
		end
		var apk = metadata.apk
		if apk != null then
			score += 1.0
		end
		var homepage = metadata.homepage
		if homepage != null then
			score += 5.0
		end
		var maintainer = metadata.maintainer
		if maintainer != null then
			score += 5.0
			var person = register_contrib(maintainer, mpackage)
			mpackage.metadata.maintainers.add person
			var projs = maint2proj[person]
			if not projs.has(mpackage) then projs.add mpackage
		end
		var license = metadata.license
		if license != null then
			score += 5.0
		end
		var browse = metadata.browse
		if browse != null then
			score += 5.0
		end
		var tags = metadata.tags
		for tag in tags do
			tag2proj[tag].add mpackage
		end
		if tags.not_empty then
			var cat = tags.first
			cat2proj[cat].add mpackage
			score += tags.length.score
		end
		if deps.has_vertex(mpackage) then
			score += deps.predecessors(mpackage).length.score
			score += deps.get_all_predecessors(mpackage).length.score
			score += deps.successors(mpackage).length.score
			score += deps.get_all_successors(mpackage).length.score
		end

		var contributors = mpackage.metadata.contributors
		var more_contributors = metadata.more_contributors
		for c in more_contributors do
			register_contrib(c, mpackage)
		end
		score += contributors.length.to_f
		var mmodules = 0
		var mclasses = 0
		var mmethods = 0
		var loc = 0
		var errors = 0
		var warnings = 0
		# The documentation value of each entity is ad hoc.
		var entity_score = 0.0
		var doc_score = 0.0
		for g in mpackage.mgroups do
			mmodules += g.mmodules.length
			var gs = 1.0
			entity_score += gs
			if g.mdoc != null then doc_score += gs
			for m in g.mmodules do
				var source = m.location.file
				if source != null then
					for msg in source.messages do
						if msg.level == 2 then
							errors += 1
						else
							warnings += 1
						end
					end
				end
				var am = modelbuilder.mmodule2node(m)
				if am != null then
					var file = am.location.file
					if file != null then
						loc += file.line_starts.length - 1
					end
				end
				var ms = gs
				if m.is_test then ms /= 100.0
				entity_score += ms
				if m.mdoc != null then doc_score += ms else ms /= 10.0
				for cd in m.mclassdefs do
					var cs = ms * 0.2
					if not cd.is_intro then cs /= 100.0
					if not cd.mclass.visibility <= private_visibility then cs /= 100.0
					entity_score += cs
					if cd.mdoc != null then doc_score += cs
					mclasses += 1
					for pd in cd.mpropdefs do
						var ps = ms * 0.1
						if not pd.is_intro then ps /= 100.0
						if not pd.mproperty.visibility <= private_visibility then ps /= 100.0
						entity_score += ps
						if pd.mdoc != null then doc_score += ps
						if not pd isa MMethodDef then continue
						mmethods += 1
					end
				end
			end
		end
		self.mmodules[mpackage] = mmodules
		self.mclasses[mpackage] = mclasses
		self.mmethods[mpackage] = mmethods
		self.loc[mpackage] = loc
		self.errors[mpackage] = errors
		self.warnings[mpackage] = warnings
		if loc > 0 then
			self.warnings_per_kloc[mpackage] = warnings * 1000 / loc
		end
		var documentation_score =  (100.0 * doc_score / entity_score).to_i
		self.documentation_score[mpackage] = documentation_score
		#score += mmodules.score
		score += mclasses.score
		score += mmethods.score
		score += loc.score
		score += documentation_score.score

		self.score[mpackage] = score.to_i
	end

	# Collect more information on a package using the `git` tool.
	fun git_info(mpackage: MPackage)
	do
		var ini = mpackage.ini
		if ini == null then return

		var root = mpackage.root
		if root == null then return

		# TODO use real git info
		#var repo = ini.get_or_null("upstream.git")
		#var branch = ini.get_or_null("upstream.git.branch")
		#var directory = ini.get_or_null("upstream.git.directory")

		var dirpath = root.filepath
		if dirpath == null then return

		# Collect commits info
		var res = git_run("log", "--no-merges", "--follow", "--pretty=tformat:%ad;%aN <%aE>", "--", dirpath)
		var contributors = new Counter[String]
		var commits = res.split("\n")
		if commits.not_empty and commits.last == "" then commits.pop
		self.commits[mpackage] = commits.length
		for l in commits do
			var s = l.split_once_on(';')
			if s.length != 2 or s.last == "" then continue

			# Collect date of last and first commit
			if mpackage.metadata.last_date == null then mpackage.metadata.last_date = s.first
			mpackage.metadata.first_date = s.first

			# Count contributors
			contributors.inc(s.last)
		end
		for c in contributors.sort.reverse_iterator do
			register_contrib(c, mpackage)
		end
	end

	# Compose package stats
	fun mpackage_stats(mpackage: MPackage): MPackageStats do
		var stats = new MPackageStats
		stats.mmodules = mmodules[mpackage]
		stats.mclasses = mclasses[mpackage]
		stats.mmethods = mmethods[mpackage]
		stats.loc = loc[mpackage]
		stats.errors = errors[mpackage]
		stats.warnings = warnings[mpackage]
		stats.warnings_per_kloc = warnings_per_kloc[mpackage]
		stats.documentation_score = documentation_score[mpackage]
		stats.commits = commits[mpackage]
		stats.score = score[mpackage]

		mpackages_stats[mpackage] = stats
		return stats
	end

	# Compose catalog stats
	var catalog_stats: CatalogStats is lazy do
		var stats = new CatalogStats
		stats.packages = mpackages.length
		stats.maintainers = maint2proj.length
		stats.contributors = contrib2proj.length
		stats.tags = tag2proj.length
		stats.modules = mmodules.sum
		stats.classes = mclasses.sum
		stats.methods = mmethods.sum
		stats.loc = loc.sum
		return stats
	end
end
src/catalog/catalog.nit:246,1--543,3