X-Git-Url: http://nitlanguage.org diff --git a/src/catalog.nit b/src/catalog.nit index 1d6bb2a..92510bc 100644 --- a/src/catalog.nit +++ b/src/catalog.nit @@ -30,7 +30,7 @@ # * [ ] gather git information from the repository # * [ ] gather package information from github # * [ ] gather people information from github -# * [ ] reify people +# * [X] reify people # * [X] separate information gathering from rendering # * [ ] move up information gathering in (existing or new) service modules # * [X] add command line options @@ -65,10 +65,10 @@ redef class MPackage var tags = new Array[String] # The list of maintainers - var maintainers = new Array[String] + var maintainers = new Array[Person] # The list of contributors - var contributors = new Array[String] + var contributors = new Array[Person] # The date of the most recent commit var last_date: nullable String = null @@ -82,6 +82,119 @@ redef class Int fun score: Float do return (self+1).to_f.log end +# A contributor/author/etc. +# +# It comes from git or the metadata +# +# TODO get more things from github by using the email as a key +# "https://api.github.com/search/users?q={email}+in:email" +class Person + # The name. Eg "John Doe" + var name: String is writable + + # The email, Eg "john.doe@example.com" + var email: nullable String is writable + + # Some homepage. Eg "http://example.com/~jdoe" + var page: nullable String is writable + + # Return a full-featured link to a person + fun to_html: String + do + var res = "" + var e = name.html_escape + var page = self.page + if page != null then + res += "" + end + var email = self.email + if email != null then + var md5 = email.md5.to_lower + res += " " + end + res += e + if page != null then res += "" + return res + end + + # The standard representation of a person. + # + # ~~~ + # var jd = new Person("John Doe", "john.doe@example.com", "http://example.com/~jdoe") + # assert jd.to_s == "John Doe (http://example.com/~jdoe)" + # ~~~ + # + # It can be used as the input of `parse`. + # + # ~~~ + # var jd2 = new Person.parse(jd.to_s) + # assert jd2.to_s == jd.to_s + # ~~~ + redef fun to_s + do + var res = name + var email = self.email + if email != null then res += " <{email}>" + var page = self.page + if page != null then res += " ({page})" + return res + end + + # Crete a new person from its standard textual representation. + # + # ~~~ + # var jd = new Person.parse("John Doe (http://example.com/~jdoe)") + # assert jd.name == "John Doe" + # assert jd.email == "john.doe@example.com" + # assert jd.page == "http://example.com/~jdoe" + # ~~~ + # + # Emails and page are optional. + # + # ~~~ + # var jd2 = new Person.parse("John Doe") + # assert jd2.name == "John Doe" + # assert jd2.email == null + # assert jd2.page == null + # ~~~ + init parse(person: String) + do + var name = person + var email = null + var page = null + # Regular expressions are broken, need to investigate. + # So split manually. + # + #var re = "([^<(]*?)(<([^>]*?)>)?(\\((.*)\\))?".to_re + #var m = (person+" ").search(re) + #print "{person}: `{m or else "?"}` `{m[1] or else "?"}` `{m[3] or else "?"}` `{m[5] or else "?"}`" + do + var sp1 = person.split_once_on("<") + if sp1.length < 2 then + break + end + var sp2 = sp1.last.split_once_on(">") + if sp2.length < 2 then + break + end + name = sp1.first.trim + email = sp2.first.trim + var sp3 = sp2.last.split_once_on("(") + if sp3.length < 2 then + break + end + var sp4 = sp3.last.split_once_on(")") + if sp4.length < 2 then + break + end + page = sp4.first.trim + end + + init(name, email, page) + end +end + + # The main class of the calatog generator that has the knowledge class Catalog @@ -96,10 +209,10 @@ class Catalog var cat2proj = new MultiHashMap[String, MPackage] # Packages by maintainer - var maint2proj = new MultiHashMap[String, MPackage] + var maint2proj = new MultiHashMap[Person, MPackage] # Packages by contributors - var contrib2proj = new MultiHashMap[String, MPackage] + var contrib2proj = new MultiHashMap[Person, MPackage] # Dependency between packages var deps = new POSet[MPackage] @@ -116,6 +229,18 @@ class Catalog # 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] @@ -124,11 +249,28 @@ class Catalog # The score is loosely computed using other metrics var score = new Counter[MPackage] + # List of known people + var persons = new HashMap[String, Person] + # Scan, register and add a contributor to a package - fun register_contrib(person: String, mpackage: MPackage) + fun register_contrib(person: String, mpackage: MPackage): Person do - var projs = contrib2proj[person] - if not projs.has(mpackage) then projs.add mpackage + 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.contributors.add p + end + return p end # Compute information for a package @@ -159,9 +301,9 @@ class Catalog var maintainer = mpackage.metadata("package.maintainer") if maintainer != null then score += 5.0 - register_contrib(maintainer, mpackage) - mpackage.maintainers.add maintainer - var projs = maint2proj[maintainer] + var person = register_contrib(maintainer, mpackage) + mpackage.maintainers.add person + var projs = maint2proj[person] if not projs.has(mpackage) then projs.add mpackage end var license = mpackage.metadata("package.license") @@ -204,12 +346,7 @@ class Catalog var more_contributors = mpackage.metadata("package.more_contributors") if more_contributors != null then for c in more_contributors.split(",") do - contributors.add c.trim - end - end - if not contributors.is_empty then - for c in contributors do - register_contrib(c, mpackage) + register_contrib(c.trim, mpackage) end end score += contributors.length.to_f @@ -218,9 +355,27 @@ class Catalog 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 @@ -228,9 +383,23 @@ class Catalog loc += file.line_starts.length - 1 end end + var ms = gs + if m.is_test_suite 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 @@ -241,11 +410,19 @@ class Catalog 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 @@ -282,7 +459,7 @@ class Catalog contributors.inc(s.last) end for c in contributors.sort.reverse_iterator do - mpackage.contributors.add c + register_contrib(c, mpackage) end end