From: Jean Privat Date: Fri, 6 Feb 2015 01:14:55 +0000 (+0700) Subject: Merge: Generalize instance creation service so FFI can use it X-Git-Tag: v0.7.2~25 X-Git-Url: http://nitlanguage.org?hp=e0b09defd048208f3a88f7d078a797c0b70b1b04 Merge: Generalize instance creation service so FFI can use it Move up instance creation from ANew to AbstractCompilerVisitor so they can be used from the FFI implementation. Allows to call extern constructors of extern classes from extern code, and fix #1145. Pull-Request: #1150 Reviewed-by: Lucas Bajolet Reviewed-by: Alexandre Terrasa Reviewed-by: Jean Privat --- diff --git a/contrib/github_merge.nit b/contrib/github_merge.nit index c85f4af..8a2c545 100644 --- a/contrib/github_merge.nit +++ b/contrib/github_merge.nit @@ -35,7 +35,12 @@ redef class GithubCurl var statuses = get_and_check("https://api.github.com/repos/privat/nit/statuses/{sha}") prm["statuses"] = statuses print "{prm["title"].to_s}: by {prm["user"].json_as_map["login"].to_s} (# {prm["number"].to_s})" - print "\tmergable: {prm["mergeable"].to_s}" + var mergeable = prm["mergeable"] + if mergeable != null then + print "\tmergeable: {mergeable.to_s}" + else + print "\tmergeable: unknown" + end var st = prm["statuses"].json_as_a if not st.is_empty then print "\tstatus: {st[0].json_as_map["state"].to_s}" @@ -115,12 +120,13 @@ else print "Commit {sha} not in local repository; did you fetch github?" return end - if system("git merge --no-commit {sha}") != 0 then + if system("git merge --no-ff --no-commit {sha}") != 0 then system("cp mergemsg `git rev-parse --git-dir`/MERGE_MSG") print "Problem during merge... Let's do the commit manually." return end system("git commit -F mergemsg") print "The merge is made" + mergemsg.write_to(stdout) end diff --git a/contrib/nitrpg/.gitignore b/contrib/nitrpg/.gitignore new file mode 100644 index 0000000..24c4036 --- /dev/null +++ b/contrib/nitrpg/.gitignore @@ -0,0 +1,4 @@ +.github_data +nitrpg_data +listener +web diff --git a/contrib/nitrpg/Makefile b/contrib/nitrpg/Makefile new file mode 100644 index 0000000..40ebec2 --- /dev/null +++ b/contrib/nitrpg/Makefile @@ -0,0 +1,28 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +NITC=../../bin/nitc + +all: listener web + +listener: + $(NITC) src/listener.nit + +web: + $(NITC) src/web.nit + +clean: + rm listener web diff --git a/contrib/nitrpg/src/game.nit b/contrib/nitrpg/src/game.nit new file mode 100644 index 0000000..9de4887 --- /dev/null +++ b/contrib/nitrpg/src/game.nit @@ -0,0 +1,294 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# `nitrpg` game structures. +# +# Here we define the main game entities: +# +# * `Game` holds all the entities for a game and provides high level services. +# * `Player` represents a `Github::User` which plays the `Game`. +# +# Developpers who wants to extend the game capabilities should look at +# the `GameReactor` abstraction. +module game + +intrude import json::store +import github::events + +# An entity within a `Game`. +# +# All game entities can be saved in a json format. +interface GameEntity + # The game instance containing `self`. + fun game: Game is abstract + + # Uniq key used for data storage. + fun key: String is abstract + + # Save `self` as a json object. + fun save do game.store.store_object(key, to_json) + + # Json representation of `self`. + fun to_json: JsonObject do return new JsonObject + + # Pretty print `self` to be displayed in a terminal. + fun pretty: String is abstract +end + +# Holder for game data and main services. +# +# Game is a `GameEntity` so it can be saved. +class Game + super GameEntity + + redef fun game do return self + + # Returns the repo `full_name`. + # + # Example: `"privat/nit"` + redef fun key do return repo.full_name + + # We need a `GithubAPI` client to load Github data. + var api: GithubAPI + + # A game takes place in a `github::Repo`. + var repo: Repo + + # Directory where game data are stored. + var game_dir: String is lazy do return "nitrpg_data" / repo.full_name + + # Used for data storage. + # + # File are stored in `game_dir`. + var store: JsonStore is lazy do return new JsonStore(game_dir) + + # Init the Game and try to load saved data. + init do if store.has_key(key) then from_json(store.load_object(key)) + + # Init `self` from a JsonObject. + # + # Used to load entities from saved data. + fun from_json(json: JsonObject) do end + + # Create a player from a Github `User`. + # + # Or return the existing one from game data. + fun add_player(user: User): Player do + # check if player already exists + var player = load_player(user.login) + if player != null then return player + # create and store new player + player = new Player(self, user.login) + player.save + return player + end + + # Get a Player from his `name` or null if no player was found. + # + # Looks for the player save file in game data. + # + # Returns `null` if the player cannot be found. + # In this case, the player can be created with `add_player`. + fun load_player(name: String): nullable Player do + var key = "players" / name + if not store.has_key(key) then return null + var json = store.load_object(key) + return new Player.from_json(self, json) + end + + # List known players. + # + # This list is reloaded from game data each time its called. + # + # To add players see `add_player`. + fun load_players: MapRead[String, Player] do + var res = new HashMap[String, Player] + if not store.has_collection("players") then return res + var coll = store.list_collection("players") + for id in coll do + var name = id.to_s + res[name] = load_player(name).as(not null) + end + return res + end + + # Return a list of player name associated to their rank in the game. + fun player_ranking: MapRead[String, Int] do + var arr = load_players.values.to_a + var res = new HashMap[String, Int] + (new PlayerCoinComparator).sort(arr) + var rank = 1 + for player in arr do + res[player.name] = rank + rank += 1 + end + return res + end + + # Erase all saved data for this game. + fun clear do store.clear + + # Verbosity level used fo stdout. + # + # * `-1` quiet + # * `0` error and warnings + # * `1` info + # * `2` debug + var verbose_lvl = 0 is writable + + # Display `msg` if `lvl` >= `verbose_lvl` + fun message(lvl: Int, msg: String) do + if lvl > verbose_lvl then return + print msg + end + + redef fun pretty do + var res = new FlatBuffer + res.append "-------------------------\n" + res.append "{repo.full_name}\n" + res.append "-------------------------\n" + res.append "# {load_players.length} players \n" + return res.write_to_string + end +end + +# Players can battle on nitrpg for nitcoins and glory. +# +# A `Player` is linked to a `Github::User`. +class Player + super GameEntity + + # Key is based on player `name`. + redef var key is lazy do return "players" / name + + redef var game + + # FIXME contructor should be private + + # Player name. + # + # This is the unic key for this player. + # Should be equal to the associated `Github::User::login`. + # + # The name is also used to load the user data lazilly from Github API. + var name: String + + # Player amount of nitcoins. + # + # Nitcoins is the currency used in nitrpg. + # They can be obtained by performing actions on the `Game::Repo`. + var nitcoins: Int = 0 is public writable + + # `Github::User` linked to this player. + var user: User is lazy do + var user = game.api.load_user(name) + assert user isa User + return user + end + + # Init `self` from a `json` object. + # + # Used to load players from saved data. + init from_json(game: Game, json: JsonObject) do + self.game = game + name = json["name"].to_s + nitcoins = json["nitcoins"].as(Int) + end + + redef fun to_json do + var json = super + json["name"] = name + json["nitcoins"] = nitcoins + return json + end + + redef fun pretty do + var res = new FlatBuffer + res.append "-- {name} ({nitcoins} $)\n" + return res.write_to_string + end + + redef fun to_s do return name +end + +redef class User + # The player linked to `self`. + fun player(game: Game): Player is lazy do + var player = game.load_player(login) + if player == null then player = game.add_player(self) + return player + end +end + +# A GameReactor reacts to event sent by a `Github::HookListener`. +# +# Subclasses of `GameReactor` are implemented to handle all kind of +# `GithubEvent`. +# Depending on the received event, the reactor is used to update game data. +# +# Reactors are mostly used with a `Github::HookListener` that dispatchs received +# events from the Github API. +# +# Example: +# +# ~~~ +# import github::hooks +# +# # Reactor that prints received events in console. +# class PrintReactor +# super GameReactor +# +# redef fun react_event(game, e) do print e +# end +# +# # Hook listener that redirect events to reactors. +# class RpgHookListener +# super HookListener +# +# redef fun apply_event(event) do +# var game = new Game(api, event.repo) +# var reactor = new PrintReactor +# reactor.react_event(game, event) +# end +# end +# ~~~ +# +# See module `reactors` and `listener` for more examples. +interface GameReactor + + # Reacts to this `event` and update `game` accordingly. + # + # Concrete `GameReactor` implement this method to update game data + # for each specific GithubEvent. + # + # By default, only logs received events. + fun react_event(game: Game, event: GithubEvent) do + game.message(1, "Received event {event} for {game.repo.full_name}") + end +end + +# utils + +# Sort players by descending number of nitcoins. +# +# The first in the list is the player with the more of nitcoins. +class PlayerCoinComparator + super Comparator + + redef type COMPARED: Player + + redef fun compare(a, b) do return b.nitcoins <=> a.nitcoins +end diff --git a/contrib/nitrpg/src/listener.nit b/contrib/nitrpg/src/listener.nit new file mode 100644 index 0000000..a77ec9f --- /dev/null +++ b/contrib/nitrpg/src/listener.nit @@ -0,0 +1,58 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This tool is runned to listen to `Github::Event` and update the game. +module listener + +import statistics +import reactors +import github::hooks + +# `HookListener` that redirects events to a `Game` instance. +class RpgHookListener + super HookListener + + # Registered reactors list. + var reactors = new Array[GameReactor] + + # Dispatch event to registered `reactors`. + redef fun apply_event(event) do + var game = new Game(api, event.repo) + # TODO handle verbosity with opts + game.verbose_lvl = 1 + for reactor in reactors do reactor.react_event(game, event) + end +end + +if args.length != 2 then + print "Error: missing argument" + print "" + print "Usage:" + print "listener " + exit 1 +end + +var host = args[0] +var port = args[1].to_i + +var api = new GithubAPI(get_github_oauth) + +var listener = new RpgHookListener(api, host, port) +listener.reactors.add new StatisticsReactor +listener.reactors.add new PlayerReactor + +print "Listening events on {host}:{port}" +listener.listen diff --git a/contrib/nitrpg/src/reactors.nit b/contrib/nitrpg/src/reactors.nit new file mode 100644 index 0000000..d1f3b3c --- /dev/null +++ b/contrib/nitrpg/src/reactors.nit @@ -0,0 +1,67 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Various implementations of `GameReactor` can be found here. +# +# TODO This module use a lot of magic numbers for nitcoin rewards. +# This should be extracted from configuration or stored elsewhere. +module reactors + +import game + +# Reacts to event that can affect players (like giving nitcoins). +class PlayerReactor + super GameReactor + + redef fun react_event(game, e) do e.react_player_event(game) +end + +redef class GithubEvent + # Reacts to a player related event. + # + # Called by `PlayerReactor::react_event`. + # No-op by default. + private fun react_player_event(game: Game) do end +end + +redef class PullRequestEvent + + # Rewards player for opened pull requests. + redef fun react_player_event(game) do + if action == "opened" then + var player = pull.user.player(game) + player.nitcoins += 10 + player.save + end + end +end + +redef class IssueCommentEvent + + # Rewards player for review comments. + # + # Actuallty we look if the comment contains the string `"+1"`. + # + # TODO only give nitcoins if reviewers < 2 + redef fun react_player_event(game) do + # FIXME use a more precise way to locate reviews + if comment.body.has("\\+1\\b".to_re) then + var player = comment.user.player(game) + player.nitcoins += 2 + player.save + end + end +end diff --git a/contrib/nitrpg/src/statistics.nit b/contrib/nitrpg/src/statistics.nit new file mode 100644 index 0000000..65d5a69 --- /dev/null +++ b/contrib/nitrpg/src/statistics.nit @@ -0,0 +1,153 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Statistics about the Game. +# +# This module uses `GameReactor` to extract statistics about the game from +# triggered `Github::Event`. +module statistics + +import game +import github::hooks + +redef class Game + + # Statistics for this game instance. + var stats = new GameStats + + redef fun from_json(json) do + super + if json.has_key("statistics") then + stats.from_json(json["statistics"].as(JsonObject)) + end + end + + redef fun to_json do + var obj = super + obj["statistics"] = stats.to_json + return obj + end + + redef fun pretty do + var res = new FlatBuffer + res.append super + res.append "# stats:\n" + res.append stats.pretty + return res.write_to_string + end + + redef fun clear do + super + stats.clear + end +end + +# Game statistics structure that can be saved as a `GameEntity`. +class GameStats + super GameEntity + + redef var key = "statistics" + + # Used internally to stats values. + private var stats = new HashMap[String, Int] + + init do clear + + # Load `self` from saved data + private fun from_json(json: JsonObject) do + for k, v in json do stats[k] = v.as(Int) + end + + redef fun to_json do + var obj = new JsonObject + for k, v in stats do obj[k] = v + return obj + end + + # Retrieves the current value of `key` statistic entry. + fun [](key: String): Int do return stats[key] + + # Increments the value of `key` statistic entry by 1. + fun incr(key: String) do stats[key] += 1 + + # Decrements the value of `key` statistic entry by 1. + fun decr(key: String) do stats[key] -= 1 + + # Reset game stats. + fun clear do + stats["issues"] = 0 + stats["issues_open"] = 0 + stats["pulls"] = 0 + stats["pulls_open"] = 0 + end + + redef fun pretty do + var res = new FlatBuffer + for k, v in stats do + res.append "# {v} {k}\n" + end + return res.write_to_string + end +end + +# `GameReactor` that computes statistics about the game. +class StatisticsReactor + super GameReactor + + redef fun react_event(game, e) do + super # log events + e.react_stats_event(game) + game.save + end +end + +redef class GithubEvent + # Reacts to a statistics related event. + # + # Called by `StatisticsReactor::react_event`. + # No-op by default. + private fun react_stats_event(game: Game) do end +end + +redef class IssuesEvent + + # Count opened and closed issues. + redef fun react_stats_event(game) do + if action == "opened" then + game.stats.incr("issues") + game.stats.incr("issues_open") + else if action == "reopened" then + game.stats.incr("issues_open") + else if action == "closed" then + game.stats.decr("issues_open") + end + end +end + +redef class PullRequestEvent + + # Count opened and closed pull requests. + redef fun react_stats_event(game) do + if action == "opened" then + game.stats.incr("pulls") + game.stats.incr("pulls_open") + else if action == "reopened" then + game.stats.incr("pulls_open") + else if action == "closed" then + game.stats.decr("pulls_open") + end + end +end diff --git a/contrib/nitrpg/src/templates/panels.nit b/contrib/nitrpg/src/templates/panels.nit new file mode 100644 index 0000000..03f3ea2 --- /dev/null +++ b/contrib/nitrpg/src/templates/panels.nit @@ -0,0 +1,305 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Panels templates for `nitpg`. +module panels + +import templates_base + +# A panel can be displayed in a html page. +# +# This display a Bootstrap panel. +class Panel + super Template + + redef fun rendering do + add """
+
+

""" + render_title + add """

+
+
""" + render_body + add """
+
""" + end + + # Render the panel title. + # Betweem `

` tags. + fun render_title do end + + # Render the panel body. + fun render_body do end +end + +# A panel that contain only a table as body. +class TablePanel + super Panel + + redef fun rendering do + add """
+
+

""" + render_title + add """ +

+
""" + render_body + add """
""" + end +end + +# Display an error message within a panel. +class ErrorPanel + super Panel + + redef fun rendering do + add """ +
+
+

""" + render_title + add """ +

+
+
""" + render_body + add """ +
+
+""" + end + + # The error message to display as panel body. + var msg: String + + redef fun render_title do + add "  " + add "Error" + end + + redef fun render_body do + add msg.html_escape + end + +end + +# A panel that display repo statistics. +class GameStatusPanel + super Panel + + # Repo to display. + var game: Game + + redef fun render_title do + add "  " + add "{game.name}" + end + + redef fun render_body do + add "{game.load_players.length}" + add " players
" + add "{game.stats["pulls"]} pull requests" + add " ({game.stats["pulls_open"]} open)
" + add "{game.stats["issues"]} issues" + add " ({game.stats["issues_open"]} open)
" + end +end + +# Player status panel. +class PlayerStatusPanel + super Panel + + # Game instance. + var game: Game + + # Target player. + var player: Player + + redef fun render_title do + add "" + add " \"{player.name}\"" + add "  " + add "{player.name}" + end + + redef fun render_body do + var ranking = game.player_ranking + # TODO player.rank + add "

ranked " + add " # {ranking[player.name]}

" + add "{player.nitcoins} nitcoins
" + end +end + +# A panel that display a list of player in a repo. +class ShortListPlayersPanel + super Panel + + # Game instance. + var game: Game + + redef fun render_title do + add "  " + add "Players" + end + + redef fun render_body do + var players = game.load_players.values.to_a + if players.is_empty then + add "No player yet..." + return + end + (new PlayerCoinComparator).sort(players) + for player in players do + add "" + add player.name + add " ({player.nitcoins})
" + end + end +end + +# A panel that display a list of player in a repo. +class ListPlayersPanel + super TablePanel + + # Game instance. + var game: Game + + redef fun render_title do + add "  " + add "Players" + end + + redef fun render_body do + var players = game.load_players.values.to_a + (new PlayerCoinComparator).sort(players) + if players.is_empty then + add "
" + add "No player yet..." + add "
" + return + end + add """ + + + + + """ + var rank = 1 + for player in players do + add "" + add " " + add " " + add " " + add "" + rank += 1 + end + add "
#PlayerNitcoins
{rank}{player.name}{player.nitcoins}
" + end +end + +# A panel that display the podium. +class PodiumPanel + super Panel + + # Game instance. + var game: Game + + redef fun render_title do + add "  Hall of fame" + end + + redef fun render_body do + var players = game.load_players.values.to_a + (new PlayerCoinComparator).sort(players) + if players.is_empty then + add "No players yet..." + return + end + add """ +
+
""" + var max = players.first.nitcoins + var orders = [3, 1, 0, 2, 4] + for order in orders do + if order >= players.length then continue + var player = players[order] + var size = 0 + if max > 0 then size = player.nitcoins * 300 / max + add """ +
+

+ + {{{player.name}}} + +

+

{{{player.name}}}

+

{{{player.nitcoins}}}

+
+
""" + end + add """ +
+
""" + end +end + +# A `Panel` that displays the list of PR to review for a `Player`. +class PlayerReviewsPanel + super Panel + + # Repo to display. + var game: Game + + # Player to display customized list for. + var player: Player + + redef fun render_title do + add "  " + add "Review pull requests to gain nitcoins!" + end + + redef fun render_body do + var q = "is:open label:need_review sort:updated-asc " + + "-involves:{player.name}" + + var issues = game.repo.search_issues(q) + if issues.is_empty then + add "No pull request to review yet..." + return + end + for issue in issues do + add """""" + end + end +end diff --git a/contrib/nitrpg/src/templates/templates.nit b/contrib/nitrpg/src/templates/templates.nit new file mode 100644 index 0000000..f1affb9 --- /dev/null +++ b/contrib/nitrpg/src/templates/templates.nit @@ -0,0 +1,110 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Templates that compose the `nitrpg` site. +module templates + +import panels + +# A page in the nitrp site. +class NitRpgPage + super Template + + # URL used as prefix for all the links generated in this page. + var root_url: String + + # Breadcrumbs to this page if any. + var breadcrumbs: nullable Breadcrumbs = null is public writable + + # Panels to display in the sidebar. + var side_panels = new Array[Panel] + + # Panels to display in the page main container. + var flow_panels = new Array[Panel] + + redef fun rendering do + render_header + render_footer + end + + # Render the header shared by all pages. + fun render_header do + add """ + + + + + Github RPG + + + + + +
+
""" + if not side_panels.is_empty then + add """
""" + for panel in side_panels do add panel + add """
+
""" + else + add """
""" + end + for panel in flow_panels do add panel + add """
+
+
+""" + end + + # Render the footer shared by all pages. + fun render_footer do + add """ + + + + +""" + end +end + +# A Bootstrap breadcrumbs component. +class Breadcrumbs + super Template + + # Items to display in this breadcrumb. + var entries = new Array[String] + + redef fun rendering do + add "
    " + for entry in entries do + add "
  1. {entry}
  2. " + end + add "
" + end + + # Add a link to the breadcrumbs. + fun add_link(href, name: String) do + entries.add "{name}" + end +end diff --git a/contrib/nitrpg/src/templates/templates_base.nit b/contrib/nitrpg/src/templates/templates_base.nit new file mode 100644 index 0000000..adac329 --- /dev/null +++ b/contrib/nitrpg/src/templates/templates_base.nit @@ -0,0 +1,39 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Base HTML rendering templates for `nitpg`. +module templates_base + +import statistics + +redef class GameEntity + + # URL to this game entity page. + fun url: String do return game.url / key +end + +redef class Game + + # Root URL ise used as a prefix for `url`. + # + # This must be set before any access to `url`. + var root_url: String is noinit, writable + + redef fun url do return "{root_url}/games" / key + + # Displayed name. + fun name: String do return repo.full_name +end diff --git a/contrib/nitrpg/src/web.nit b/contrib/nitrpg/src/web.nit new file mode 100644 index 0000000..80f7821 --- /dev/null +++ b/contrib/nitrpg/src/web.nit @@ -0,0 +1,168 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2014-2015 Alexandre Terrasa +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Display `nitrpg` data as a website. +module web + +import nitcorn +import templates + +# A custom action forn `nitrpg`. +class RpgAction + super Action + + # Root URL is used as a prefix for all URL generated by the actions. + var root_url: String + + # Github oauth token used for GithubAPI. + var auth: String is lazy do return get_github_oauth + + # API client used to import data from Github. + var api: GithubAPI is lazy do + var api = new GithubAPI(auth) + return api + end + + init do + super + if auth.is_empty then + print "Error: Invalid Github oauth token!" + exit 1 + end + end + + # Return an Error reponse page. + fun bad_request(msg: String): HttpResponse do + var rsp = new HttpResponse(400) + var page = new NitRpgPage(root_url) + var error = new ErrorPanel(msg) + page.flow_panels.add error + rsp.body = page.write_to_string + return rsp + end +end + +# An action that require a game. +class GameAction + super RpgAction + + # Response page stub. + var page: NitRpgPage is noinit + + # Target game. + var game: Game is noinit + + redef fun answer(request, url) is abstract + + # Check errors and prepare response. + private fun prepare_response(request: HttpRequest, url: String): HttpResponse do + var owner = request.param("owner") + var repo_name = request.param("repo") + if owner == null or repo_name == null then + var msg = "Bad request: should look like /repos/:owner/:repo." + return bad_request(msg) + end + var repo = new Repo(api, "{owner}/{repo_name}") + game = new Game(api, repo) + game.root_url = root_url + if api.was_error then + var msg = api.last_error.message + return bad_request("Repo Error: {msg}") + end + var response = new HttpResponse(200) + page = new NitRpgPage(root_url) + page.side_panels.add new GameStatusPanel(game) + page.breadcrumbs = new Breadcrumbs + page.breadcrumbs.add_link(game.url, game.name) + return response + end +end + +# Repo overview page. +class RepoHome + super GameAction + + redef fun answer(request, url) do + var rsp = prepare_response(request, url) + page.side_panels.add new ShortListPlayersPanel(game) + page.flow_panels.add new PodiumPanel(game) + rsp.body = page.write_to_string + return rsp + end +end + +# Repo players list. +class ListPlayers + super GameAction + + redef fun answer(request, url) do + var rsp = prepare_response(request, url) + page.breadcrumbs.add_link(game.url / "players", "players") + page.flow_panels.add new ListPlayersPanel(game) + rsp.body = page.write_to_string + return rsp + end +end + +# Player details page. +class PlayerHome + super GameAction + + redef fun answer(request, url) do + var rsp = prepare_response(request, url) + var name = request.param("player") + if name == null then + var msg = "Bad request: should look like /:owner/:repo/:players/:name." + return bad_request(msg) + end + var player = game.load_player(name) + if player == null then + return bad_request("Request Error: unknown player {name}.") + end + page.breadcrumbs.add_link(game.url / "players", "players") + page.breadcrumbs.add_link(player.url, name) + page.side_panels.clear + page.side_panels.add new PlayerStatusPanel(game, player) + page.flow_panels.add new PlayerReviewsPanel(game, player) + rsp.body = page.write_to_string + return rsp + end +end + +if args.length != 3 then + print "Error: missing argument" + print "" + print "Usage:" + print "web " + exit 1 +end + +var host = args[0] +var port = args[1] +var root = args[2] + +var iface = "{host}:{port}" +var vh = new VirtualHost(iface) +vh.routes.add new Route("/styles/", new FileServer("www/styles")) +vh.routes.add new Route("/games/:owner/:repo/players/:player", new PlayerHome(root)) +vh.routes.add new Route("/games/:owner/:repo/players", new ListPlayers(root)) +vh.routes.add new Route("/games/:owner/:repo", new RepoHome(root)) + +var fac = new HttpFactory.and_libevent +fac.config.virtual_hosts.add vh + +print "Launching server on http://{iface}/" +fac.run diff --git a/contrib/nitrpg/www/styles/main.css b/contrib/nitrpg/www/styles/main.css new file mode 100644 index 0000000..865123e --- /dev/null +++ b/contrib/nitrpg/www/styles/main.css @@ -0,0 +1,74 @@ +body { padding-top: 70px; } + +.navbar .breadcrumb { + background-color: transparent; + margin-bottom: 0; + margin-top: 0.5em; +} + +/* columns of same height styles */ +.container-xs-height { + display:table; + padding-left:0px; + padding-right:0px; +} +.row-xs-height { + display:table-row; +} +.col-xs-height { + display:table-cell; + float:none; +} +@media (min-width: 768px) { + .container-sm-height { + display:table; + padding-left:0px; + padding-right:0px; + } + .row-sm-height { + display:table-row; + } + .col-sm-height { + display:table-cell; + float:none; + } +} +@media (min-width: 992px) { + .container-md-height { + display:table; + padding-left:0px; + padding-right:0px; + } + .row-md-height { + display:table-row; + } + .col-md-height { + display:table-cell; + float:none; + } +} +@media (min-width: 1200px) { + .container-lg-height { + display:table; + padding-left:0px; + padding-right:0px; + } + .row-lg-height { + display:table-row; + } + .col-lg-height { + display:table-cell; + float:none; + } +} + +/* vertical alignment styles */ +.col-top { + vertical-align:top; +} +.col-middle { + vertical-align:middle; +} +.col-bottom { + vertical-align:bottom; +} diff --git a/lib/standard/string.nit b/lib/standard/string.nit index 0bf9560..1af06b7 100644 --- a/lib/standard/string.nit +++ b/lib/standard/string.nit @@ -1909,15 +1909,22 @@ redef class Int end end + # C function to calculate the length of the `NativeString` to receive `self` + private fun int_to_s_len: Int is extern "native_int_length_str" + # C function to convert an nit Int to a NativeString (char*) - private fun native_int_to_s: NativeString is extern "native_int_to_s" + private fun native_int_to_s(nstr: NativeString, strlen: Int) is extern "native_int_to_s" # return displayable int in base 10 and signed # # assert 1.to_s == "1" # assert (-123).to_s == "-123" redef fun to_s do - return native_int_to_s.to_s + var nslen = int_to_s_len + var ns = new NativeString(nslen + 1) + ns[nslen] = '\0' + native_int_to_s(ns, nslen + 1) + return ns.to_s_with_length(nslen) end # return displayable int in hexadecimal @@ -2009,23 +2016,6 @@ redef class Float return p1 + "." + p2 end - - # `self` representation with `nb` digits after the '.'. - # - # assert 12.345.to_precision_native(1) == "12.3" - # assert 12.345.to_precision_native(2) == "12.35" - # assert 12.345.to_precision_native(3) == "12.345" - # assert 12.345.to_precision_native(4) == "12.3450" - fun to_precision_native(nb: Int): String import NativeString.to_s `{ - int size; - char *str; - - size = snprintf(NULL, 0, "%.*f", (int)nb, recv); - str = malloc(size + 1); - sprintf(str, "%.*f", (int)nb, recv ); - - return NativeString_to_s( str ); - `} end redef class Char diff --git a/lib/standard/string_nit.c b/lib/standard/string_nit.c index 1a11e99..686d6be 100644 --- a/lib/standard/string_nit.c +++ b/lib/standard/string_nit.c @@ -11,10 +11,12 @@ #include "string_nit.h" +// Returns the length of `recv` as a `char*` (excluding the null character) +long native_int_length_str(long recv){ + return snprintf(NULL, 0, "%ld", recv); +} + // Integer to NativeString method -char* native_int_to_s(long recv){ - int len = snprintf(NULL, 0, "%ld", recv); - char* str = malloc(len+1); - sprintf(str, "%ld", recv); - return str; +void native_int_to_s(long recv, char* str, long buflen){ + snprintf(str, buflen, "%ld", recv); } diff --git a/lib/standard/string_nit.h b/lib/standard/string_nit.h index 311041d..f33580f 100644 --- a/lib/standard/string_nit.h +++ b/lib/standard/string_nit.h @@ -13,6 +13,7 @@ * another product. */ -char* native_int_to_s(long recv); +long native_int_length_str(long recv); +void native_int_to_s(long recv, char* str, long buflen); #endif diff --git a/lib/standard/time.nit b/lib/standard/time.nit index 09e8389..e0b3867 100644 --- a/lib/standard/time.nit +++ b/lib/standard/time.nit @@ -135,7 +135,9 @@ extern class Tm `{struct tm *`} c_format = String_to_cstring(format); res = strftime(buf, 100, c_format, recv); - return NativeString_to_s(buf); + String s = NativeString_to_s_with_copy(buf); + free(buf); + return s; `} redef fun to_s do return asctime.replace("\n", "") diff --git a/src/Makefile b/src/Makefile index 8c9742c..eb02ada 100644 --- a/src/Makefile +++ b/src/Makefile @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -NITCOPT= +NITCOPT=--semi-global OLDNITCOPT= OBJS=nitc nitpick nit nitdoc nitls nitunit nitpretty nitmetrics nitx nitlight nitdbg_client nitserial SRCS=$(patsubst %,%.nit,$(OBJS)) diff --git a/src/compiler/abstract_compiler.nit b/src/compiler/abstract_compiler.nit index 07a8ede..f286f02 100644 --- a/src/compiler/abstract_compiler.nit +++ b/src/compiler/abstract_compiler.nit @@ -856,6 +856,12 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref ); v.add("return 0;") v.add("\}") + + for m in mainmodule.in_importation.greaters do + var f = "FILE_"+m.c_name + v.add "const char {f}[] = \"{m.location.file.filename.escape_to_c}\";" + provide_declaration(f, "extern const char {f}[];") + end end # Copile all C functions related to the [incr|decr]_ref features of the FFI @@ -1543,8 +1549,11 @@ abstract class AbstractCompilerVisitor fun add_raw_abort do - if self.current_node != null and self.current_node.location.file != null then - self.add("PRINT_ERROR(\" (%s:%d)\\n\", \"{self.current_node.location.file.filename.escape_to_c}\", {current_node.location.line_start});") + if self.current_node != null and self.current_node.location.file != null and + self.current_node.location.file.mmodule != null then + var f = "FILE_{self.current_node.location.file.mmodule.c_name}" + self.require_declaration(f) + self.add("PRINT_ERROR(\" (%s:%d)\\n\", {f}, {current_node.location.line_start});") else self.add("PRINT_ERROR(\"\\n\");") end diff --git a/src/doc/doc.nit b/src/doc/doc.nit index 19443b4..a6fb971 100644 --- a/src/doc/doc.nit +++ b/src/doc/doc.nit @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Nitdoc page generation +# Nitdoc generation framework. module doc -import doc_pages - +import doc_base +import doc_phases diff --git a/src/doc/doc_base.nit b/src/doc/doc_base.nit new file mode 100644 index 0000000..56b57c2 --- /dev/null +++ b/src/doc/doc_base.nit @@ -0,0 +1,219 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Base entities shared by all the nitdoc code. +module doc_base + +import toolcontext +import doc_model # FIXME maintain compatibility with old templates. + +# The model of a Nitdoc documentation. +# +# `DocModel` contains the list of the `DocPage` to be generated. +# +# The model is populated through `DocPhase` to be constructed. +# It is a placeholder to share data between each phase. +class DocModel + + # `DocPage` composing the documentation. + # + # This is where `DocPhase` store and access pages to produce documentation. + var pages = new Array[DocPage] + + # Nit `Model` from which we extract the documentation. + var model: Model is writable + + # The entry point of the `model`. + var mainmodule: MModule is writable +end + +# A documentation page abstraction. +# +# The page contains a link to the `root` of the `DocComposite` that compose the +# the page. +class DocPage + + # Title of this page. + var title: String is writable + + # Root element of the page. + # + # `DocPhase` access the structure of the page from the `DocRoot`. + var root = new DocRoot + + redef fun to_s do return title +end + +# `DocPage` elements that can be nested in another. +# +# `DocComposite` is an abstraction for everything that go in a `DocPage` like +# sections, articles, images, lists, graphs... +# +# It provides base services for nesting mechanisms following the +# *Composite pattern*. +# The composed structure is a tree from a `DocRoot` that can be manipulated +# recursively. +abstract class DocComposite + + # Parent element. + var parent: nullable DocComposite = null + + # Does `self` have a `parent`? + fun is_root: Bool do return parent == null + + # Children elements contained in `self`. + # + # Children are ordered, this order can be changed by the `DocPhase`. + var children = new Array[DocComposite] + + # Does `self` have `children`? + fun is_empty: Bool do return children.is_empty + + # Add a `child` to `self`. + # + # Shortcut for `children.add`. + fun add(child: DocComposite) do children.add child +end + +# The `DocComposite` element that contains all the other. +# +# The root uses a specific subclass to provide different a different behavior +# than other `DocComposite` elements. +class DocRoot + super DocComposite + + # No op for `RootSection`. + redef fun parent=(p) do end +end + +# Base page elements. + +# `DocSection` are used to break the documentation page into meaningfull parts. +# +# The content of the documentation summary is based on the section structure +# contained in the DocComposite tree. +class DocSection + super DocComposite +end + +# `DocArticle` are pieces of documentation. +# +# They maintains the content (text, list, image...) of a documentation page. +class DocArticle + super DocComposite +end + +# A DocPhase is a step in the production of a Nitdoc documentation. +# +# Phases work from a `DocModel`. +# Specific phases are used to populate, organize, enhance and render the content +# of the documentation pages. +# +# See `doc_phases` for available DocPhase. +class DocPhase + + # Link to the ToolContext to access Nitdoc tool options. + var ctx: ToolContext + + # `DocModel` used by this phase to work. + var doc: DocModel + + # Starting point of a `DocPhase`. + # + # This is where the behavior of the phase is implemented. + # Phases can populate, edit or render the `doc` from here. + fun apply is abstract +end + +redef class ToolContext + + # Directory where the Nitdoc is rendered. + var opt_dir = new OptionString("output directory", "-d", "--dir") + + # Shortcut for `opt_dir.value` with default "doc". + var output_dir: String is lazy do return opt_dir.value or else "doc" + + redef init do + super + option_context.add_option(opt_dir) + end +end + +# Catalog properties by kind. +class PropertiesByKind + # The virtual types. + var virtual_types = new PropertyGroup[MVirtualTypeProp]("Virtual types") + + # The constructors. + var constructors = new PropertyGroup[MMethod]("Contructors") + + # The attributes. + var attributes = new PropertyGroup[MAttribute]("Attributes") + + # The methods. + var methods = new PropertyGroup[MMethod]("Methods") + + # The inner classes. + var inner_classes = new PropertyGroup[MInnerClass]("Inner classes") + + # All the groups. + # + # Sorted in the order they are displayed to the user. + var groups: SequenceRead[PropertyGroup[MProperty]] = [ + virtual_types, + constructors, + attributes, + methods, + inner_classes: PropertyGroup[MProperty]] + + # Add each the specified property to the appropriate list. + init with_elements(properties: Collection[MProperty]) do add_all(properties) + + # Add the specified property to the appropriate list. + fun add(property: MProperty) do + if property isa MMethod then + if property.is_init then + constructors.add property + else + methods.add property + end + else if property isa MVirtualTypeProp then + virtual_types.add property + else if property isa MAttribute then + attributes.add property + else if property isa MInnerClass then + inner_classes.add property + else + abort + end + end + + # Add each the specified property to the appropriate list. + fun add_all(properties: Collection[MProperty]) do + for p in properties do add(p) + end + + # Sort each group with the specified comparator. + fun sort_groups(comparator: Comparator) do + for g in groups do comparator.sort(g) + end +end + +# An ordered list of properties of the same kind. +class PropertyGroup[E: MProperty] + super Array[E] + + # The title of the group, as displayed to the user. + var title: String +end diff --git a/src/doc/doc_model.nit b/src/doc/doc_model.nit index 2c80ffb..99691d1 100644 --- a/src/doc/doc_model.nit +++ b/src/doc/doc_model.nit @@ -111,7 +111,7 @@ redef class MEntity return new TplListItem.with_content(lnk) end - fun tpl_css_classes: Array[String] is abstract + var tpl_css_classes = new Array[String] # Box title for this mentity fun tpl_title: Template do @@ -257,6 +257,7 @@ redef class MClass redef fun mdoc_or_fallback do return intro.mdoc redef fun tpl_declaration do return intro.tpl_declaration + redef fun tpl_definition do return intro.tpl_definition redef fun tpl_namespace do var tpl = new Template @@ -365,7 +366,6 @@ redef class MClassDef redef fun tpl_definition do var tpl = new TplClassDefinition - tpl.namespace = tpl_namespace var mdoc = mdoc_or_fallback if mdoc != null then tpl.comment = mdoc.tpl_comment @@ -449,7 +449,6 @@ redef class MPropDef redef fun tpl_definition do var tpl = new TplDefinition - tpl.namespace = mclassdef.tpl_namespace var mdoc = mdoc_or_fallback if mdoc != null then tpl.comment = mdoc.tpl_comment @@ -556,6 +555,7 @@ end redef class MVirtualTypeDef redef fun tpl_signature do var tpl = new Template + if bound == null then return tpl tpl.add ": " tpl.add bound.tpl_signature return tpl @@ -705,7 +705,6 @@ redef class MInnerClassDef redef fun tpl_definition do var tpl = new TplClassDefinition - tpl.namespace = mclassdef.tpl_namespace var mdoc = mdoc_or_fallback if mdoc != null then tpl.comment = mdoc.tpl_comment diff --git a/src/doc/doc_pages.nit b/src/doc/doc_pages.nit deleted file mode 100644 index 38e6cdf..0000000 --- a/src/doc/doc_pages.nit +++ /dev/null @@ -1,1603 +0,0 @@ -# This file is part of NIT ( http://www.nitlanguage.org ). -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Nitdoc page generation -module doc_pages - -import toolcontext -import doc_model -private import json::static - -redef class ToolContext - private var opt_dir = new OptionString("output directory", "-d", "--dir") - private var opt_source = new OptionString("link for source (%f for filename, %l for first line, %L for last line)", "--source") - private var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir") - private var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl") - private var opt_no_attributes = new OptionBool("ignore the attributes", - "--no-attributes") - private var opt_nodot = new OptionBool("do not generate graphes with graphviz", "--no-dot") - private var opt_private = new OptionBool("also generate private API", "--private") - - private var opt_custom_title = new OptionString("custom title for homepage", "--custom-title") - private var opt_custom_brand = new OptionString("custom link to external site", "--custom-brand") - private var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text") - private var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text") - - private var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream") - private var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1") - private var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir") - - private var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker") - private var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id") - - private var output_dir: String - private var min_visibility: MVisibility - - redef init do - super - - var opts = option_context - opts.add_option(opt_dir, opt_source, opt_sharedir, opt_shareurl, - opt_no_attributes, opt_nodot, opt_private) - opts.add_option(opt_custom_title, opt_custom_footer, opt_custom_intro, opt_custom_brand) - opts.add_option(opt_github_upstream, opt_github_base_sha1, opt_github_gitdir) - opts.add_option(opt_piwik_tracker, opt_piwik_site_id) - - var tpl = new Template - tpl.add "Usage: nitdoc [OPTION]... ...\n" - tpl.add "Generates HTML pages of API documentation from Nit source files." - tooldescription = tpl.write_to_string - end - - redef fun process_options(args) do - super - - # output dir - var output_dir = opt_dir.value - if output_dir == null then - output_dir = "doc" - end - self.output_dir = output_dir - # min visibility - if opt_private.value then - min_visibility = none_visibility - else - min_visibility = protected_visibility - end - # github urls - var gh_upstream = opt_github_upstream.value - var gh_base_sha = opt_github_base_sha1.value - var gh_gitdir = opt_github_gitdir.value - if not gh_upstream == null or not gh_base_sha == null or not gh_gitdir == null then - if gh_upstream == null or gh_base_sha == null or gh_gitdir == null then - print "Error: Options {opt_github_upstream.names.first}, {opt_github_base_sha1.names.first} and {opt_github_gitdir.names.first} are required to enable the GitHub plugin" - abort - end - end - end - - # Filter the entity based on the options specified by the user. - # - # Return `true` if the specified entity has to be included in the generated - # documentation - private fun filter_mclass(mclass: MClass): Bool do - return mclass.visibility >= min_visibility - end - - # Filter the entity based on the options specified by the user. - # - # Return `true` if the specified entity has to be included in the generated - # documentation - private fun filter_mproperty(mproperty: MProperty): Bool do - return mproperty.visibility >= min_visibility and - not (opt_no_attributes.value and mproperty isa MAttribute) - end -end - -# The Nitdoc class explores the model and generate pages for each mentities found -class Nitdoc - var ctx: ToolContext - var model: Model - var mainmodule: MModule - - fun generate do - init_output_dir - overview - search - groups - modules - classes - properties - quicksearch_list - end - - private fun init_output_dir do - # create destination dir if it's necessary - var output_dir = ctx.output_dir - if not output_dir.file_exists then output_dir.mkdir - # locate share dir - var sharedir = ctx.opt_sharedir.value - if sharedir == null then - var dir = ctx.nit_dir - sharedir = dir/"share/nitdoc" - if not sharedir.file_exists then - print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR" - abort - end - end - # copy shared files - if ctx.opt_shareurl.value == null then - sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/") - else - sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/") - end - - end - - private fun overview do - var page = new NitdocOverview(ctx, model, mainmodule) - page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}") - end - - private fun search do - var page = new NitdocSearch(ctx, model, mainmodule) - page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}") - end - - private fun groups do - for mproject in model.mprojects do - for mgroup in mproject.mgroups.to_a do - var page = new NitdocGroup(ctx, model, mainmodule, mgroup) - page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}") - end - end - end - - private fun modules do - for mmodule in model.mmodules do - if mmodule.is_fictive or mmodule.is_test_suite then continue - var page = new NitdocModule(ctx, model, mainmodule, mmodule) - page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}") - end - end - - private fun classes do - for mclass in model.mclasses do - if not ctx.filter_mclass(mclass) then continue - var page = new NitdocClass(ctx, model, mainmodule, mclass) - page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}") - end - end - - private fun properties do - for mproperty in model.mproperties do - if not ctx.filter_mproperty(mproperty) then continue - if mproperty isa MInnerClass then continue - var page = new NitdocProperty(ctx, model, mainmodule, mproperty) - page.render.write_to_file("{ctx.output_dir.to_s}/{page.page_url}") - end - end - - private fun quicksearch_list do - var quicksearch = new QuickSearch(ctx, model) - quicksearch.render.write_to_file("{ctx.output_dir.to_s}/quicksearch-list.js") - end -end - -# Nitdoc QuickSearch list generator -# -# Create a JSON object containing links to: -# * modules -# * mclasses -# * mpropdefs -# All entities are grouped by name to make the research easier. -class QuickSearch - - private var table = new QuickSearchTable - - var ctx: ToolContext - var model: Model - - init do - for mmodule in model.mmodules do - if mmodule.is_fictive or mmodule.is_test_suite then continue - add_result_for(mmodule.name, mmodule.full_name, mmodule.nitdoc_url) - end - for mclass in model.mclasses do - if not ctx.filter_mclass(mclass) then continue - add_result_for(mclass.name, mclass.full_name, mclass.nitdoc_url) - end - for mproperty in model.mproperties do - if not ctx.filter_mproperty(mproperty) then continue - for mpropdef in mproperty.mpropdefs do - var full_name = mpropdef.mclassdef.mclass.full_name - var cls_url = mpropdef.mclassdef.mclass.nitdoc_url - var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}" - add_result_for(mproperty.name, full_name, def_url) - end - end - end - - private fun add_result_for(query: String, txt: String, url: String) do - table[query].add new QuickSearchResult(txt, url) - end - - fun render: Template do - var tpl = new Template - var buffer = new RopeBuffer - tpl.add buffer - buffer.append "var nitdocQuickSearchRawList=" - table.append_json buffer - buffer.append ";" - return tpl - end -end - -# The result map for QuickSearch. -private class QuickSearchTable - super JsonMapRead[String, QuickSearchResultList] - super HashMap[String, QuickSearchResultList] - - redef fun provide_default_value(key) do - var v = new QuickSearchResultList - self[key] = v - return v - end -end - -# A QuickSearch result list. -private class QuickSearchResultList - super JsonSequenceRead[QuickSearchResult] - super Array[QuickSearchResult] -end - -# A QuickSearch result. -private class QuickSearchResult - super Jsonable - - # The text of the link. - var txt: String - - # The destination of the link. - var url: String - - redef fun to_json do - return "\{\"txt\":{txt.to_json},\"url\":{url.to_json}\}" - end -end - -# Nitdoc base page -# Define page structure and properties -abstract class NitdocPage - - private var ctx: ToolContext - private var model: Model - private var mainmodule: MModule - private var name_sorter = new MEntityNameSorter - - # Render the page as a html template - fun render: Template do - var shareurl = "." - if ctx.opt_shareurl.value != null then - shareurl = ctx.opt_shareurl.value.as(not null) - end - - # build page - var tpl = tpl_page - tpl.title = tpl_title - tpl.url = page_url - tpl.shareurl = shareurl - tpl.topmenu = tpl_topmenu - tpl_content - tpl.footer = ctx.opt_custom_footer.value - tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl)) - tpl.sidebar = tpl_sidebar - - # piwik tracking - var tracker_url = ctx.opt_piwik_tracker.value - var site_id = ctx.opt_piwik_site_id.value - if tracker_url != null and site_id != null then - tpl.scripts.add new TplPiwikScript(tracker_url, site_id) - end - return tpl - end - - # URL to this page. - fun page_url: String is abstract - - # Build page template - fun tpl_page: TplPage is abstract - - # Build page sidebar if any - fun tpl_sidebar: nullable TplSidebar do return null - - # Build page title string - fun tpl_title: String do - if ctx.opt_custom_title.value != null then - return ctx.opt_custom_title.value.to_s - end - return "Nitdoc" - end - - # Build top menu template - fun tpl_topmenu: TplTopMenu do - var topmenu = new TplTopMenu(page_url) - var brand = ctx.opt_custom_brand.value - if brand != null then - var tpl = new Template - tpl.add "" - tpl.add brand - tpl.add "" - topmenu.brand = tpl - end - topmenu.add_link new TplLink("index.html", "Overview") - topmenu.add_link new TplLink("search.html", "Index") - return topmenu - end - - # Build page content template - fun tpl_content is abstract - - # Clickable graphviz image using dot format - # return null if no graph for this page - fun tpl_graph(dot: Buffer, name: String, title: nullable String): nullable TplArticle do - if ctx.opt_nodot.value then return null - var output_dir = ctx.output_dir - var path = output_dir / name - var path_sh = path.escape_to_sh - var file = new OFStream.open("{path}.dot") - file.write(dot) - file.close - sys.system("\{ test -f {path_sh}.png && test -f {path_sh}.s.dot && diff -- {path_sh}.dot {path_sh}.s.dot >/dev/null 2>&1 ; \} || \{ cp -- {path_sh}.dot {path_sh}.s.dot && dot -Tpng -o{path_sh}.png -Tcmapx -o{path_sh}.map {path_sh}.s.dot ; \}") - var fmap = new IFStream.open("{path}.map") - var map = fmap.read_all - fmap.close - - var article = new TplArticle("graph") - var alt = "" - if title != null then - article.title = title - alt = "alt='{title.html_escape}'" - end - article.css_classes.add "text-center" - var content = new Template - var name_html = name.html_escape - content.add "" - content.add map - article.content = content - return article - end - - # A (source) link template for a given location - fun tpl_showsource(location: nullable Location): nullable String - do - if location == null then return null - var source = ctx.opt_source.value - if source == null then - var url = location.file.filename.simplify_path - return "View Source" - end - # THIS IS JUST UGLY ! (but there is no replace yet) - var x = source.split_with("%f") - source = x.join(location.file.filename.simplify_path) - x = source.split_with("%l") - source = x.join(location.line_start.to_s) - x = source.split_with("%L") - source = x.join(location.line_end.to_s) - source = source.simplify_path - return "View Source" - end - - # MProject description template - fun tpl_mproject_article(mproject: MProject): TplArticle do - var article = mproject.tpl_article - article.subtitle = mproject.tpl_declaration - article.content = mproject.tpl_definition - var mdoc = mproject.mdoc_or_fallback - if mdoc != null then - article.content = mdoc.tpl_short_comment - end - return article - end - - # MGroup description template - fun tpl_mgroup_article(mgroup: MGroup): TplArticle do - var article = mgroup.tpl_article - article.subtitle = mgroup.tpl_declaration - article.content = mgroup.tpl_definition - return article - end - - # MModule description template - fun tpl_mmodule_article(mmodule: MModule): TplArticle do - var article = mmodule.tpl_article - article.subtitle = mmodule.tpl_declaration - article.content = mmodule.tpl_definition - # mclassdefs list - var intros = mmodule.intro_mclassdefs(ctx.min_visibility).to_a - if not intros.is_empty then - mainmodule.linearize_mclassdefs(intros) - var intros_art = new TplArticle.with_title("{mmodule.nitdoc_id}.intros", "Introduces") - var intros_lst = new TplList.with_classes(["list-unstyled", "list-labeled"]) - for mclassdef in intros do - intros_lst.add_li mclassdef.tpl_list_item - end - if not intros_lst.is_empty then - intros_art.content = intros_lst - article.add_child intros_art - end - end - var redefs = mmodule.redef_mclassdefs(ctx.min_visibility).to_a - if not redefs.is_empty then - mainmodule.linearize_mclassdefs(redefs) - var redefs_art = new TplArticle.with_title("{mmodule.nitdoc_id}.redefs", "Redefines") - var redefs_lst = new TplList.with_classes(["list-unstyled", "list-labeled"]) - for mclassdef in redefs do - redefs_lst.add_li mclassdef.tpl_list_item - end - if not redefs_lst.is_empty then - redefs_art.content = redefs_lst - article.add_child redefs_art - end - end - return article - end - - # MClassDef description template - fun tpl_mclass_article(mclass: MClass, mclassdefs: Array[MClassDef]): TplArticle do - var article = mclass.tpl_article - if not mclassdefs.has(mclass.intro) then - # add intro synopsys - var intro_article = mclass.intro.tpl_short_article - intro_article.source_link = tpl_showsource(mclass.intro.location) - article.add_child intro_article - end - mainmodule.linearize_mclassdefs(mclassdefs) - for mclassdef in mclassdefs do - # add mclassdef full description - var redef_article = mclassdef.tpl_article - redef_article.source_link = tpl_showsource(mclassdef.location) - article.add_child redef_article - # mpropdefs list - var intros = new TplArticle.with_title("{mclassdef.nitdoc_id}.intros", "Introduces") - var intros_lst = new TplList.with_classes(["list-unstyled", "list-labeled"]) - for mpropdef in mclassdef.collect_intro_mpropdefs(ctx.min_visibility) do - intros_lst.add_li mpropdef.tpl_list_item - end - if not intros_lst.is_empty then - intros.content = intros_lst - redef_article.add_child intros - end - var redefs = new TplArticle.with_title("{mclassdef.nitdoc_id}.redefs", "Redefines") - var redefs_lst = new TplList.with_classes(["list-unstyled", "list-labeled"]) - for mpropdef in mclassdef.collect_redef_mpropdefs(ctx.min_visibility) do - redefs_lst.add_li mpropdef.tpl_list_item - end - if not redefs_lst.is_empty then - redefs.content = redefs_lst - redef_article.add_child redefs - end - end - return article - end - - # MClassDef description template - fun tpl_mclassdef_article(mclassdef: MClassDef): TplArticle do - var article = mclassdef.tpl_article - if mclassdef.is_intro then article.content = null - article.source_link = tpl_showsource(mclassdef.location) - return article - end - - # MProp description template - # - # `main_mpropdef`: The most important mpropdef to display - # `local_mpropdefs`: List of other locally defined mpropdefs to display - # `lin`: full linearization from local_mpropdefs to intro (displayed in redef tree) - fun tpl_mprop_article(main_mpropdef: MPropDef, local_mpropdefs: Array[MPropDef], - lin: Array[MPropDef]): TplArticle do - var mprop = main_mpropdef.mproperty - var article = new TplArticle(mprop.nitdoc_id) - var title = new Template - title.add mprop.tpl_icon - title.add "" - if main_mpropdef.is_intro then - title.add mprop.tpl_link - title.add mprop.intro.tpl_signature - else - var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url - var def_url = "{cls_url}#{mprop.nitdoc_id}" - var lnk = new TplLink.with_title(def_url, mprop.nitdoc_name, - "Go to introduction") - title.add "redef " - title.add lnk - end - article.title = title - article.title_classes.add "signature" - article.summary_title = "{mprop.nitdoc_name}" - article.subtitle = main_mpropdef.tpl_namespace - if main_mpropdef.mdoc_or_fallback != null then - article.content = main_mpropdef.mdoc_or_fallback.tpl_comment - end - var subarticle = new TplArticle("{main_mpropdef.nitdoc_id}.redefs") - # Add redef in same `MClass` - if local_mpropdefs.length > 1 then - for mpropdef in local_mpropdefs do - if mpropdef == main_mpropdef then continue - var redef_article = new TplArticle("{mpropdef.nitdoc_id}") - var redef_title = new Template - redef_title.add "also redef in " - redef_title.add mpropdef.tpl_namespace - redef_article.title = redef_title - redef_article.title_classes.add "signature info" - redef_article.css_classes.add "nospace" - var redef_content = new Template - if mpropdef.mdoc != null then - redef_content.add mpropdef.mdoc.tpl_comment - end - redef_article.content = redef_content - subarticle.add_child redef_article - end - end - # Add linearization - if lin.length > 1 then - var lin_article = new TplArticle("{main_mpropdef.nitdoc_id}.lin") - lin_article.title = "Inheritance" - var lst = new TplList.with_classes(["list-unstyled", "list-labeled"]) - for mpropdef in lin do - lst.add_li mpropdef.tpl_inheritance_item - end - lin_article.content = lst - subarticle.add_child lin_article - end - article.add_child subarticle - return article - end - - # MProperty description template - fun tpl_mpropdef_article(mpropdef: MPropDef): TplArticle do - var article = mpropdef.tpl_article - article.source_link = tpl_showsource(mpropdef.location) - return article - end -end - -# The overview page -# Display a list of modules contained in program -class NitdocOverview - super NitdocPage - - private var page = new TplPage - redef fun tpl_page do return page - - private var sidebar = new TplSidebar - redef fun tpl_sidebar do return sidebar - - redef fun tpl_title do - if ctx.opt_custom_title.value != null then - return ctx.opt_custom_title.value.to_s - else - return "Overview" - end - end - - redef fun page_url do return "index.html" - - # intro text - private fun tpl_intro: TplSection do - var section = new TplSection.with_title("overview", tpl_title) - var article = new TplArticle("intro") - if ctx.opt_custom_intro.value != null then - article.content = ctx.opt_custom_intro.value.to_s - end - section.add_child article - return section - end - - # projects list - private fun tpl_projects(section: TplSection) do - # Projects list - var mprojects = model.mprojects.to_a - var sorter = new MConcernRankSorter - sorter.sort mprojects - var ssection = new TplSection.with_title("projects", "Projects") - for mproject in mprojects do - ssection.add_child tpl_mproject_article(mproject) - end - section.add_child ssection - end - - redef fun tpl_content do - var top = tpl_intro - tpl_projects(top) - tpl_page.add_section top - end -end - -# The search page -# Display a list of modules, classes and properties -class NitdocSearch - super NitdocPage - - private var page = new TplPage - redef fun tpl_page do return page - - redef fun tpl_title do return "Index" - - redef fun page_url do return "search.html" - - redef fun tpl_content do - var tpl = new TplSearchPage("search_all") - var section = new TplSection("search") - # title - tpl.title = "Index" - # modules list - for mmodule in modules_list do - tpl.modules.add mmodule.tpl_link - end - # classes list - for mclass in classes_list do - tpl.classes.add mclass.tpl_link - end - # properties list - for mproperty in mprops_list do - var m = new Template - m.add mproperty.intro.tpl_link - m.add " (" - m.add mproperty.intro.mclassdef.mclass.tpl_link - m.add ")" - tpl.props.add m - end - section.add_child tpl - tpl_page.add_section section - end - - # Extract mmodule list to display (sorted by name) - private fun modules_list: Array[MModule] do - var sorted = new Array[MModule] - for mmodule in model.mmodule_importation_hierarchy do - if mmodule.is_fictive or mmodule.is_test_suite then continue - sorted.add mmodule - end - name_sorter.sort(sorted) - return sorted - end - - # Extract mclass list to display (sorted by name) - private fun classes_list: Array[MClass] do - var sorted = new Array[MClass] - for mclass in model.mclasses do - if not ctx.filter_mclass(mclass) then continue - sorted.add mclass - end - name_sorter.sort(sorted) - return sorted - end - - # Extract mproperty list to display (sorted by name) - private fun mprops_list: Array[MProperty] do - var sorted = new Array[MProperty] - for mproperty in model.mproperties do - if ctx.filter_mproperty(mproperty) then sorted.add mproperty - end - name_sorter.sort(sorted) - return sorted - end -end - -# A group page -# Display a flattened view of the group -class NitdocGroup - super NitdocPage - - private var mgroup: MGroup - - private var concerns: ConcernsTree is noinit - private var intros: Set[MClass] is noinit - private var redefs: Set[MClass] is noinit - - init do - self.concerns = model.concerns_tree(mgroup.collect_mmodules) - self.concerns.sort_with(new MConcernRankSorter) - self.intros = mgroup.in_nesting_intro_mclasses(ctx.min_visibility) - var redefs = new HashSet[MClass] - for rdef in mgroup.in_nesting_redef_mclasses(ctx.min_visibility) do - if intros.has(rdef) then continue - redefs.add rdef - end - self.redefs = redefs - end - - private var page = new TplPage - redef fun tpl_page do return page - - private var sidebar = new TplSidebar - redef fun tpl_sidebar do return sidebar - - redef fun tpl_title do return mgroup.nitdoc_name - - redef fun page_url do return mgroup.nitdoc_url - - redef fun tpl_topmenu do - var topmenu = super - var mproject = mgroup.mproject - if not mgroup.is_root then - topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name) - end - topmenu.add_link new TplLink(page_url, mproject.nitdoc_name) - return topmenu - end - - # Class list to display in sidebar - fun tpl_sidebar_mclasses do - var mclasses = new HashSet[MClass] - mclasses.add_all intros - mclasses.add_all redefs - if mclasses.is_empty then return - var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) - - var sorted = mclasses.to_a - name_sorter.sort(sorted) - for mclass in sorted do - list.add_li tpl_sidebar_item(mclass) - end - tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list) - end - - private fun tpl_sidebar_item(def: MClass): TplListItem do - var classes = def.intro.tpl_css_classes.to_a - if intros.has(def) then - classes.add "intro" - else - classes.add "redef" - end - var lnk = new Template - lnk.add new TplLabel.with_classes(classes) - lnk.add def.tpl_link - return new TplListItem.with_content(lnk) - end - - # intro text - private fun tpl_intro: TplSection do - var section = new TplSection.with_title("top", tpl_title) - var article = new TplArticle("intro") - - if mgroup.is_root then - section.subtitle = mgroup.mproject.tpl_declaration - article.content = mgroup.mproject.tpl_definition - else - section.subtitle = mgroup.tpl_declaration - article.content = mgroup.tpl_definition - end - section.add_child article - return section - end - - private fun tpl_concerns(section: TplSection) do - if concerns.is_empty then return - section.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl) - end - - private fun tpl_groups(parent: TplSection) do - var lst = concerns.to_a - var section = parent - for mentity in lst do - if mentity isa MProject then - section.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MGroup then - section.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MModule then - section.add_child tpl_mmodule_article(mentity) - end - end - end - - redef fun tpl_content do - tpl_sidebar_mclasses - var top = tpl_intro - tpl_concerns(top) - tpl_groups(top) - tpl_page.add_section top - end - - private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do - var map = new HashMap[MClass, Set[MClassDef]] - for mclassdef in mclassdefs do - var mclass = mclassdef.mclass - if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef] - map[mclass].add mclassdef - end - return map - end -end - -# A module page -# Display the list of introduced and redefined classes in module -class NitdocModule - super NitdocPage - - private var mmodule: MModule - private var concerns: ConcernsTree is noinit - private var mclasses2mdefs: Map[MClass, Set[MClassDef]] is noinit - private var mmodules2mclasses: Map[MModule, Set[MClass]] is noinit - - - init do - var mclassdefs = new HashSet[MClassDef] - mclassdefs.add_all mmodule.intro_mclassdefs(ctx.min_visibility) - mclassdefs.add_all mmodule.redef_mclassdefs(ctx.min_visibility) - self.mclasses2mdefs = sort_by_mclass(mclassdefs) - self.mmodules2mclasses = group_by_mmodule(mclasses2mdefs.keys) - self.concerns = model.concerns_tree(mmodules2mclasses.keys) - # rank concerns - mmodule.mgroup.mproject.booster_rank = -1000 - mmodule.mgroup.booster_rank = -1000 - mmodule.booster_rank = -1000 - self.concerns.sort_with(new MConcernRankSorter) - mmodule.mgroup.mproject.booster_rank = 0 - mmodule.mgroup.booster_rank = 0 - mmodule.booster_rank = 0 - end - - private var page = new TplPage - redef fun tpl_page do return page - - private var sidebar = new TplSidebar - redef fun tpl_sidebar do return sidebar - - redef fun tpl_title do return mmodule.nitdoc_name - redef fun page_url do return mmodule.nitdoc_url - - redef fun tpl_topmenu do - var topmenu = super - var mproject = mmodule.mgroup.mproject - topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name) - topmenu.add_link new TplLink(page_url, mmodule.nitdoc_name) - return topmenu - end - - # Class list to display in sidebar - fun tpl_sidebar_mclasses do - var mclasses = new HashSet[MClass] - mclasses.add_all mmodule.filter_intro_mclasses(ctx.min_visibility) - mclasses.add_all mmodule.filter_redef_mclasses(ctx.min_visibility) - if mclasses.is_empty then return - var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) - - var sorted = mclasses.to_a - name_sorter.sort(sorted) - for mclass in sorted do - list.add_li tpl_sidebar_item(mclass) - end - tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list) - end - - private fun tpl_sidebar_item(def: MClass): TplListItem do - var classes = def.intro.tpl_css_classes.to_a - if def.intro_mmodule == mmodule then - classes.add "intro" - else - classes.add "redef" - end - var lnk = new Template - lnk.add new TplLabel.with_classes(classes) - lnk.add def.tpl_link - return new TplListItem.with_content(lnk) - end - - # intro text - private fun tpl_intro: TplSection do - var section = new TplSection.with_title("top", tpl_title) - section.subtitle = mmodule.tpl_declaration - - var article = new TplArticle("intro") - var def = mmodule.tpl_definition - var location = mmodule.location - article.source_link = tpl_showsource(location) - article.content = def - section.add_child article - return section - end - - # inheritance section - private fun tpl_inheritance(parent: TplSection) do - # Extract relevent modules - var imports = mmodule.in_importation.greaters - if imports.length > 10 then imports = mmodule.in_importation.direct_greaters - var clients = mmodule.in_importation.smallers - if clients.length > 10 then clients = mmodule.in_importation.direct_smallers - - # Display lists - var section = new TplSection.with_title("dependencies", "Dependencies") - - # Graph - var mmodules = new HashSet[MModule] - mmodules.add_all mmodule.nested_mmodules - mmodules.add_all imports - if clients.length < 10 then mmodules.add_all clients - mmodules.add mmodule - var graph = tpl_dot(mmodules) - if graph != null then section.add_child graph - - # Imports - var lst = new Array[MModule] - for dep in imports do - if dep.is_fictive or dep.is_test_suite then continue - if dep == mmodule then continue - lst.add(dep) - end - if not lst.is_empty then - name_sorter.sort lst - section.add_child tpl_list("imports", "Imports", lst) - end - - # Clients - lst = new Array[MModule] - for dep in clients do - if dep.is_fictive or dep.is_test_suite then continue - if dep == mmodule then continue - lst.add(dep) - end - if not lst.is_empty then - name_sorter.sort lst - section.add_child tpl_list("clients", "Clients", lst) - end - - parent.add_child section - end - - private fun tpl_list(id: String, title: String, mmodules: Array[MModule]): TplArticle do - var article = new TplArticle.with_title(id, title) - var list = new TplList.with_classes(["list-unstyled", "list-definition"]) - for mmodule in mmodules do list.elts.add mmodule.tpl_list_item - article.content = list - return article - end - - private fun tpl_concerns(parent: TplSection) do - if concerns.is_empty then return - parent.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl) - end - - private fun tpl_mclasses(parent: TplSection) do - for mentity in concerns do - if mentity isa MProject then - parent.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MGroup then - parent.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MModule then - var section = new TplSection(mentity.nitdoc_id) - var title = new Template - if mentity == mmodule then - title.add "in " - section.summary_title = "in {mentity.nitdoc_name}" - else - title.add "from " - section.summary_title = "from {mentity.nitdoc_name}" - end - title.add mentity.tpl_namespace - section.title = title - - var mclasses = mmodules2mclasses[mentity].to_a - name_sorter.sort(mclasses) - for mclass in mclasses do - section.add_child tpl_mclass_article(mclass, mclasses2mdefs[mclass].to_a) - end - parent.add_child section - end - end - end - - private fun group_by_mmodule(mclasses: Collection[MClass]): Map[MModule, Set[MClass]] do - var res = new HashMap[MModule, Set[MClass]] - for mclass in mclasses do - var mmodule = mclass.intro_mmodule - if not res.has_key(mmodule) then - res[mmodule] = new HashSet[MClass] - end - res[mmodule].add(mclass) - end - return res - end - - redef fun tpl_content do - tpl_sidebar_mclasses - var top = tpl_intro - tpl_inheritance(top) - tpl_concerns(top) - tpl_mclasses(top) - tpl_page.add_section top - end - - # Genrate dot hierarchy for class inheritance - fun tpl_dot(mmodules: Collection[MModule]): nullable TplArticle do - var poset = new POSet[MModule] - for mmodule in mmodules do - if mmodule.is_fictive or mmodule.is_test_suite then continue - poset.add_node mmodule - for omodule in mmodules do - if omodule.is_fictive or omodule.is_test_suite then continue - poset.add_node mmodule - if mmodule.in_importation < omodule then - poset.add_edge(mmodule, omodule) - end - end - end - # build graph - var op = new RopeBuffer - var name = "dep_module_{mmodule.nitdoc_id}" - op.append("digraph \"{name.escape_to_dot}\" \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") - for mmodule in poset do - if mmodule == self.mmodule then - op.append("\"{mmodule.name.escape_to_dot}\"[shape=box,margin=0.03];\n") - else - op.append("\"{mmodule.name.escape_to_dot}\"[URL=\"{mmodule.nitdoc_url.escape_to_dot}\"];\n") - end - for omodule in poset[mmodule].direct_greaters do - op.append("\"{mmodule.name.escape_to_dot}\"->\"{omodule.name.escape_to_dot}\";\n") - end - end - op.append("\}\n") - return tpl_graph(op, name, null) - end - - private fun sort_by_mclass(mclassdefs: Collection[MClassDef]): Map[MClass, Set[MClassDef]] do - var map = new HashMap[MClass, Set[MClassDef]] - for mclassdef in mclassdefs do - var mclass = mclassdef.mclass - if not map.has_key(mclass) then map[mclass] = new HashSet[MClassDef] - map[mclass].add mclassdef - end - return map - end -end - -# A class page -# Display a list properties defined or redefined for this class -class NitdocClass - super NitdocPage - - private var mclass: MClass - private var concerns: ConcernsTree is noinit - private var mprops2mdefs: Map[MProperty, Set[MPropDef]] is noinit - private var mmodules2mprops: Map[MModule, Set[MProperty]] is noinit - - init do - var mpropdefs = new HashSet[MPropDef] - mpropdefs.add_all mclass.intro_mpropdefs(ctx.min_visibility) - mpropdefs.add_all mclass.redef_mpropdefs(ctx.min_visibility) - self.mprops2mdefs = sort_by_mproperty(mpropdefs) - self.mmodules2mprops = sort_by_mmodule(mprops2mdefs.keys) - self.concerns = model.concerns_tree(mmodules2mprops.keys) - self.concerns.sort_with(new MConcernRankSorter) - end - - private var page = new TplPage - redef fun tpl_page do return page - - private var sidebar = new TplSidebar - redef fun tpl_sidebar do return sidebar - - redef fun tpl_title do return "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}" - redef fun page_url do return mclass.nitdoc_url - - redef fun tpl_topmenu do - var topmenu = super - var mproject = mclass.intro_mmodule.mgroup.mproject - topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}") - topmenu.add_link new TplLink(page_url, mclass.nitdoc_name) - return topmenu - end - - # Property list to display in sidebar - fun tpl_sidebar_properties do - var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops) - var summary = new TplList.with_classes(["list-unstyled"]) - - by_kind.sort_groups(name_sorter) - for g in by_kind.groups do tpl_sidebar_list(g, summary) - tpl_sidebar.boxes.add new TplSideBox.with_content("All properties", summary) - end - - private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: TplList) do - if mprops.is_empty then return - var entry = new TplListItem.with_content(mprops.title) - var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) - for mprop in mprops do - list.add_li tpl_sidebar_item(mprop) - end - entry.append list - summary.elts.add entry - end - - private fun tpl_sidebar_item(mprop: MProperty): TplListItem do - var classes = mprop.intro.tpl_css_classes.to_a - if not mprops2mdefs.has_key(mprop) then - classes.add "inherit" - var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url - var def_url = "{cls_url}#{mprop.nitdoc_id}" - var lnk = new TplLink(def_url, mprop.nitdoc_name) - var mdoc = mprop.intro.mdoc_or_fallback - if mdoc != null then lnk.title = mdoc.short_comment - var item = new Template - item.add new TplLabel.with_classes(classes) - item.add lnk - return new TplListItem.with_content(item) - end - var defs = mprops2mdefs[mprop] - if defs.has(mprop.intro) then - classes.add "intro" - else - classes.add "redef" - end - var lnk = new Template - lnk.add new TplLabel.with_classes(classes) - lnk.add mprop.tpl_anchor - return new TplListItem.with_content(lnk) - end - - private fun tpl_intro: TplSection do - var section = new TplSection.with_title("top", tpl_title) - section.subtitle = mclass.intro.tpl_declaration - var article = new TplArticle("comment") - var mdoc = mclass.mdoc_or_fallback - if mdoc != null then - article.content = mdoc.tpl_comment - end - section.add_child article - return section - end - - private fun tpl_concerns(parent: TplSection) do - # intro title - var section = new TplSection.with_title("intro", "Introduction") - section.summary_title = "Introduction" - section.add_child tpl_mclassdef_article(mclass.intro) - parent.add_child section - # concerns - if concerns.is_empty then return - parent.add_child new TplArticle.with_content("concerns", "Concerns", concerns.to_tpl) - end - - private fun tpl_inheritance(parent: TplSection) do - # parents - var hparents = new HashSet[MClass] - for c in mclass.in_hierarchy(mainmodule).direct_greaters do - if ctx.filter_mclass(c) then hparents.add c - end - - # ancestors - var hancestors = new HashSet[MClass] - for c in mclass.in_hierarchy(mainmodule).greaters do - if c == mclass then continue - if not ctx.filter_mclass(c) then continue - if hparents.has(c) then continue - hancestors.add c - end - - # children - var hchildren = new HashSet[MClass] - for c in mclass.in_hierarchy(mainmodule).direct_smallers do - if ctx.filter_mclass(c) then hchildren.add c - end - - # descendants - var hdescendants = new HashSet[MClass] - for c in mclass.in_hierarchy(mainmodule).smallers do - if c == mclass then continue - if not ctx.filter_mclass(c) then continue - if hchildren.has(c) then continue - hdescendants.add c - end - - # Display lists - var section = new TplSection.with_title("inheritance", "Inheritance") - - # Graph - var mclasses = new HashSet[MClass] - mclasses.add_all hancestors - mclasses.add_all hparents - mclasses.add_all hchildren - mclasses.add_all hdescendants - mclasses.add mclass - var graph = tpl_dot(mclasses) - if graph != null then section.add_child graph - - # parents - if not hparents.is_empty then - var lst = hparents.to_a - name_sorter.sort lst - section.add_child tpl_list("parents", "Parents", lst) - end - - # ancestors - if not hancestors.is_empty then - var lst = hancestors.to_a - name_sorter.sort lst - section.add_child tpl_list("ancestors", "Ancestors", lst) - end - - # children - if not hchildren.is_empty then - var lst = hchildren.to_a - name_sorter.sort lst - section.add_child tpl_list("children", "Children", lst) - end - - # descendants - if not hdescendants.is_empty then - var lst = hdescendants.to_a - name_sorter.sort lst - section.add_child tpl_list("descendants", "Descendants", lst) - end - - parent.add_child section - end - - private fun tpl_list(id: String, title: String, elts: Array[MClass]): TplArticle do - var article = new TplArticle.with_title(id, title) - if elts.length > 20 then - var tpl = new Template - for e in elts do - tpl.add e.tpl_link - if e != elts.last then tpl.add ", " - end - article.content = tpl - else - var list = new TplList.with_classes(["list-unstyled", "list-definition"]) - for elt in elts do list.elts.add elt.tpl_list_item - article.content = list - end - return article - end - - private fun tpl_properties(parent: TplSection) do - var lst = concerns.to_a - for mentity in lst do - if mentity isa MProject then - parent.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MGroup then - parent.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MModule then - var section = new TplSection(mentity.nitdoc_id) - var title = new Template - title.add "in " - title.add mentity.tpl_namespace - section.title = title - section.summary_title = "in {mentity.nitdoc_name}" - - # properties - var mprops = mmodules2mprops[mentity] - var by_kind = new PropertiesByKind.with_elements(mprops) - - for g in by_kind.groups do - for article in tpl_mproperty_articles(g) do - section.add_child article - end - end - parent.add_child section - end - end - end - - private fun tpl_mproperty_articles(elts: Collection[MProperty]): - Sequence[TplArticle] do - var articles = new List[TplArticle] - for elt in elts do - var local_defs = mprops2mdefs[elt] - # var all_defs = elt.mpropdefs - var all_defs = new HashSet[MPropDef] - for local_def in local_defs do - all_defs.add local_def - var mpropdef = local_def - while not mpropdef.is_intro do - mpropdef = mpropdef.lookup_next_definition(mainmodule, mpropdef.mclassdef.bound_mtype) - all_defs.add mpropdef - end - end - var loc_lin = local_defs.to_a - mainmodule.linearize_mpropdefs(loc_lin) - var all_lin = all_defs.to_a - mainmodule.linearize_mpropdefs(all_lin) - articles.add tpl_mprop_article(loc_lin.first, loc_lin, all_lin) - end - return articles - end - - redef fun tpl_content do - tpl_sidebar_properties - var top = tpl_intro - tpl_inheritance(top) - tpl_concerns(top) - tpl_properties(top) - tpl_page.add_section top - end - - private fun sort_by_mproperty(mpropdefs: Collection[MPropDef]): Map[MProperty, Set[MPropDef]] do - var map = new HashMap[MProperty, Set[MPropDef]] - for mpropdef in mpropdefs do - var mproperty = mpropdef.mproperty - if not map.has_key(mproperty) then map[mproperty] = new HashSet[MPropDef] - map[mproperty].add mpropdef - end - return map - end - - private fun sort_by_mmodule(mprops: Collection[MProperty]): Map[MModule, Set[MProperty]] do - var map = new HashMap[MModule, Set[MProperty]] - for mprop in mprops do - var mpropdefs = mprops2mdefs[mprop].to_a - mainmodule.linearize_mpropdefs(mpropdefs) - var mmodule = mpropdefs.first.mclassdef.mmodule - if not map.has_key(mmodule) then map[mmodule] = new HashSet[MProperty] - map[mmodule].add mprop - end - return map - end - - private fun mclass_inherited_mprops: Set[MProperty] do - var res = new HashSet[MProperty] - var local = mclass.local_mproperties(ctx.min_visibility) - for mprop in mclass.inherited_mproperties(mainmodule, ctx.min_visibility) do - if local.has(mprop) then continue - #if mprop isa MMethod and mprop.is_init then continue - if mprop.intro.mclassdef.mclass.name == "Object" and - (mprop.visibility == protected_visibility or - mprop.intro.mclassdef.mmodule.name != "kernel") then continue - res.add mprop - end - res.add_all local - return res - end - - private fun collect_mmodules(mprops: Collection[MProperty]): Set[MModule] do - var res = new HashSet[MModule] - for mprop in mprops do - if mprops2mdefs.has_key(mprop) then - for mpropdef in mprops2mdefs[mprop] do res.add mpropdef.mclassdef.mmodule - end - end - return res - end - - # Generate dot hierarchy for classes - fun tpl_dot(mclasses: Collection[MClass]): nullable TplArticle do - var poset = new POSet[MClass] - - for mclass in mclasses do - poset.add_node mclass - for oclass in mclasses do - if mclass == oclass then continue - poset.add_node oclass - if mclass.in_hierarchy(mainmodule) < oclass then - poset.add_edge(mclass, oclass) - end - end - end - - var op = new RopeBuffer - var name = "dep_class_{mclass.nitdoc_id}" - op.append("digraph \"{name.escape_to_dot}\" \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") - var classes = poset.to_a - var todo = new Array[MClass] - var done = new HashSet[MClass] - mainmodule.linearize_mclasses(classes) - if not classes.is_empty then todo.add classes.first - while not todo.is_empty do - var c = todo.shift - if done.has(c) then continue - done.add c - if c == mclass then - op.append("\"{c.name.escape_to_dot}\"[shape=box,margin=0.03];\n") - else - op.append("\"{c.name.escape_to_dot}\"[URL=\"{c.nitdoc_url.escape_to_dot}\"];\n") - end - var smallers = poset[c].direct_smallers - if smallers.length < 10 then - for c2 in smallers do - op.append("\"{c2.name.escape_to_dot}\"->\"{c.name.escape_to_dot}\";\n") - end - todo.add_all smallers - else - op.append("\"...\"->\"{c.name.escape_to_dot}\";\n") - end - end - op.append("\}\n") - return tpl_graph(op, name, null) - end -end - -# Groups properties by kind. -private class PropertiesByKind - # The virtual types. - var virtual_types = new PropertyGroup[MVirtualTypeProp]("Virtual types") - - # The constructors. - var constructors = new PropertyGroup[MMethod]("Contructors") - - # The attributes. - var attributes = new PropertyGroup[MAttribute]("Attributes") - - # The methods. - var methods = new PropertyGroup[MMethod]("Methods") - - # The inner classes. - var inner_classes = new PropertyGroup[MInnerClass]("Inner classes") - - # All the groups. - # - # Sorted in the order they are displayed to the user. - var groups: SequenceRead[PropertyGroup[MProperty]] = [ - virtual_types, - constructors, - attributes, - methods, - inner_classes: PropertyGroup[MProperty]] - - # Add each the specified property to the appropriate list. - init with_elements(properties: Collection[MProperty]) do add_all(properties) - - # Add the specified property to the appropriate list. - fun add(property: MProperty) do - if property isa MMethod then - if property.is_init then - constructors.add property - else - methods.add property - end - else if property isa MVirtualTypeProp then - virtual_types.add property - else if property isa MAttribute then - attributes.add property - else if property isa MInnerClass then - inner_classes.add property - else - abort - end - end - - # Add each the specified property to the appropriate list. - fun add_all(properties: Collection[MProperty]) do - for p in properties do add(p) - end - - # Sort each group with the specified comparator. - fun sort_groups(comparator: Comparator) do - for g in groups do comparator.sort(g) - end -end - -# A Group of properties of the same kind. -private class PropertyGroup[E: MProperty] - super Array[E] - - # The title of the group, as displayed to the user. - var title: String -end - -# A MProperty page -class NitdocProperty - super NitdocPage - - private var mproperty: MProperty - private var concerns: ConcernsTree is noinit - private var mmodules2mdefs: Map[MModule, Set[MPropDef]] is noinit - - init do - self.mproperty = mproperty - self.mmodules2mdefs = sort_by_mmodule(collect_mpropdefs) - self.concerns = model.concerns_tree(mmodules2mdefs.keys) - self.concerns.sort_with(new MConcernRankSorter) - end - - private fun collect_mpropdefs: Set[MPropDef] do - var res = new HashSet[MPropDef] - for mpropdef in mproperty.mpropdefs do - if not mpropdef.is_intro then res.add mpropdef - end - return res - end - - private var page = new TplPage - redef fun tpl_page do return page - - private var sidebar = new TplSidebar - redef fun tpl_sidebar do return sidebar - - redef fun tpl_title do - return "{mproperty.nitdoc_name}{mproperty.tpl_signature.write_to_string}" - end - - redef fun page_url do return mproperty.nitdoc_url - - redef fun tpl_topmenu do - var topmenu = super - var mmodule = mproperty.intro_mclassdef.mmodule - var mproject = mmodule.mgroup.mproject - var mclass = mproperty.intro_mclassdef.mclass - topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}") - topmenu.add_link new TplLink("{mclass.nitdoc_url}", "{mclass.nitdoc_name}") - topmenu.add_link new TplLink(page_url, mproperty.nitdoc_name) - return topmenu - end - - private fun tpl_intro: TplSection do - var title = new Template - title.add mproperty.nitdoc_name - title.add mproperty.intro.tpl_signature - var section = new TplSection.with_title("top", title) - section.subtitle = mproperty.tpl_namespace - section.summary_title = mproperty.nitdoc_name - return section - end - - private fun tpl_properties(parent: TplSection) do - # intro title - var ns = mproperty.intro.mclassdef.mmodule.tpl_namespace - var section = new TplSection("intro") - var title = new Template - title.add "Introduction in " - title.add ns - section.title = title - section.summary_title = "Introduction" - section.add_child tpl_mpropdef_article(mproperty.intro) - parent.add_child section - - # concerns - if concerns.is_empty then return - parent.add_child new TplArticle.with_content("Concerns", "Concerns", concerns.to_tpl) - - # redef list - var lst = concerns.to_a - for mentity in lst do - if mentity isa MProject then - parent.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MGroup then - parent.add_child new TplSection(mentity.nitdoc_id) - else if mentity isa MModule then - var ssection = new TplSection(mentity.nitdoc_id) - title = new Template - title.add "in " - title.add mentity.tpl_namespace - ssection.title = title - ssection.summary_title = "in {mentity.nitdoc_name}" - - # properties - var mpropdefs = mmodules2mdefs[mentity].to_a - name_sorter.sort(mpropdefs) - for mpropdef in mpropdefs do - ssection.add_child tpl_mpropdef_article(mpropdef) - end - parent.add_child ssection - end - end - end - - redef fun tpl_content do - var top = tpl_intro - tpl_properties(top) - tpl_page.add_section top - end - - private fun sort_by_mmodule(mpropdefs: Collection[MPropDef]): Map[MModule, Set[MPropDef]] do - var map = new HashMap[MModule, Set[MPropDef]] - for mpropdef in mpropdefs do - var mmodule = mpropdef.mclassdef.mmodule - if not map.has_key(mmodule) then map[mmodule] = new HashSet[MPropDef] - map[mmodule].add mpropdef - end - return map - end -end - diff --git a/src/doc/doc_phases/doc_concerns.nit b/src/doc/doc_phases/doc_concerns.nit new file mode 100644 index 0000000..d1ada76 --- /dev/null +++ b/src/doc/doc_phases/doc_concerns.nit @@ -0,0 +1,149 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Concerns computation. +module doc_concerns + +import doc_pages + +# ConcernsPhase computes the ConcernsTree used for each page layout. +class ConcernsPhase + super DocPhase + + # Populates the given DocModel. + redef fun apply do + for page in doc.pages do page.build_concerns(doc) + end +end + +redef class DocPage + + # Build the `concerns` tree for this page. + # + # Since only `MEntityPage`, this method is a no-op for everything else. + private fun build_concerns(doc: DocModel) do end +end + +redef class MEntityPage + + # Concerns to display in this page. + var concerns: nullable ConcernsTree = null +end + +# TODO ConcernsTrees are a PITA, following redef should not be needed here... +# The bad, so baaaadddd, ConcernsTree interface induces a lot of useless code +# in all phases. + +redef class MGroupPage + + # Introduced classes in `mentity` that should appear in this page. + var intros = new HashSet[MClass] + + # Refined classes in `mentity` that should appear in this page. + var redefs = new HashSet[MClass] + + redef fun build_concerns(doc) do + var mmodules = new HashSet[MModule] + for mmodule in mentity.collect_mmodules do + if doc.mmodules.has(mmodule) then mmodules.add mmodule + # collect mclasses + for mclass in mmodule.intro_mclasses do + if doc.mclasses.has(mclass) then intros.add mclass + end + for mclass in mmodule.redef_mclasses do + if doc.mclasses.has(mclass) then redefs.add mclass + end + end + concerns = doc.model.concerns_tree(mmodules) + end +end + +redef class MModulePage + + # MClasses defined in `mentity` to display in this page. + var mclasses = new HashSet[MClass] + + # MClassDefs located in `mentity` to display in this page. + var mclassdefs = new HashSet[MClassDef] + + redef fun build_concerns(doc) do + # extract mclassdefs in mmodule + for mclassdef in mentity.mclassdefs do + if doc.mclassdefs.has(mclassdef) then mclassdefs.add mclassdef + end + # extract mclasses in mmodule + for mclassdef in mclassdefs do + var mclass = mclassdef.mclass + if doc.mclasses.has(mclass) then mclasses.add mclass + end + # extract concerns + var mods = new HashSet[MModule] + for mclass in mclasses do + var mod = mclass.intro_mmodule + if doc.mmodules.has(mod) then mods.add mod + end + concerns = doc.model.concerns_tree(mods) + end +end + +redef class MClassPage + + # MClassDefs to display in this page. + var mclassdefs = new HashSet[MClassDef] + + # MPropdefs to display in this page. + var mpropdefs = new HashSet[MPropDef] + + redef fun build_concerns(doc) do + # collect mclassdefs + for mclassdef in mentity.mclassdefs do + if doc.mclassdefs.has(mclassdef) then mclassdefs.add mclassdef + end + # collect mpropdefs + for mclassdef in mclassdefs do + for mpropdef in mclassdef.mpropdefs do + if doc.mpropdefs.has(mpropdef) then mpropdefs.add mpropdef + end + end + # collect concerns + var mods = new HashSet[MModule] + for mpropdef in mpropdefs do + var mod = mpropdef.mclassdef.mmodule + if doc.mmodules.has(mod) then mods.add mod + end + concerns = doc.model.concerns_tree(mods) + end +end + +redef class MPropertyPage + + # MPropdefs to display in this page. + var mpropdefs = new HashSet[MPropDef] + + redef fun build_concerns(doc) do + # collect mpropdefs + for mpropdef in mentity.mpropdefs do + # FIXME diff hack + if mpropdef.is_intro then continue + if doc.mpropdefs.has(mpropdef) then mpropdefs.add mpropdef + end + # collect concerns + var mods = new HashSet[MModule] + for mpropdef in mpropdefs do + var mod = mpropdef.mclassdef.mmodule + if doc.mmodules.has(mod) then mods.add mod + end + concerns = doc.model.concerns_tree(mods) + end +end diff --git a/src/doc/doc_phases/doc_extract.nit b/src/doc/doc_phases/doc_extract.nit new file mode 100644 index 0000000..53adeac --- /dev/null +++ b/src/doc/doc_phases/doc_extract.nit @@ -0,0 +1,149 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Extract data mentities of Model that will be documented. +# +# ExtractionPhase populates the DocModel that is the base for all other phases. +# No DocPages are created at this level. +# +# TODO build a model instead? +module doc_extract + +import doc_base + +redef class ToolContext + + # Do not generate documentation for attributes. + var opt_no_attributes = new OptionBool("ignore the attributes", "--no-attributes") + + # Do not generate documentation for private properties. + var opt_private = new OptionBool("also generate private API", "--private") + + redef init do + super + option_context.add_option(opt_no_attributes, opt_private) + end + + # Minimum visibility displayed. + # + # See `opt_private`. + var min_visibility: MVisibility is lazy do + if opt_private.value then return none_visibility + return protected_visibility + end +end + +# ExtractionPhase populates the DocModel. +class ExtractionPhase + super DocPhase + + private var new_model: Model is noinit + + # Populates the given DocModel. + redef fun apply do + doc.populate(self) + end + + # Should we exclude this `mproject` from the documentation? + fun ignore_mentity(mentity: MEntity): Bool do + if mentity isa MModule then + return mentity.is_fictive or mentity.is_test_suite + else if mentity isa MClass then + return mentity.visibility < ctx.min_visibility + else if mentity isa MClassDef then + return ignore_mentity(mentity.mclass) + else if mentity isa MProperty then + return ignore_mentity(mentity.intro_mclassdef) or + mentity.visibility < ctx.min_visibility or + (ctx.opt_no_attributes.value and mentity isa MAttribute) or + mentity isa MInnerClass + else if mentity isa MPropDef then + return ignore_mentity(mentity.mclassdef) or + ignore_mentity(mentity.mproperty) + end + return false + end +end + +# TODO Should I rebuild a new Model from filtered data? +redef class DocModel + + # MProjects that will be documented. + var mprojects = new HashSet[MProject] + + # MGroups that will be documented. + var mgroups = new HashSet[MGroup] + + # MModules that will be documented. + var mmodules = new HashSet[MModule] + + # MClasses that will be documented. + var mclasses = new HashSet[MClass] + + # MClassDefs that will be documented. + var mclassdefs = new HashSet[MClassDef] + + # MProperties that will be documented. + var mproperties = new HashSet[MProperty] + + # MPropdefs that will be documented. + var mpropdefs = new HashSet[MPropDef] + + # Populate `self` from internal `model`. + fun populate(v: ExtractionPhase) do + populate_mprojects(v) + populate_mclasses(v) + populate_mproperties(v) + end + + # Populates the `mprojects` set. + private fun populate_mprojects(v: ExtractionPhase) do + for mproject in model.mprojects do + if v.ignore_mentity(mproject) then continue + self.mprojects.add mproject + for mgroup in mproject.mgroups do + if v.ignore_mentity(mgroup) then continue + self.mgroups.add mgroup + for mmodule in mgroup.mmodules do + if v.ignore_mentity(mmodule) then continue + self.mmodules.add mmodule + end + end + end + end + + # Populates the `mclasses` set. + private fun populate_mclasses(v: ExtractionPhase) do + for mclass in model.mclasses do + if v.ignore_mentity(mclass) then continue + self.mclasses.add mclass + for mclassdef in mclass.mclassdefs do + if v.ignore_mentity(mclassdef) then continue + self.mclassdefs.add mclassdef + end + end + end + + # Populates the `mproperties` set. + private fun populate_mproperties(v: ExtractionPhase) do + for mproperty in model.mproperties do + if v.ignore_mentity(mproperty) then continue + self.mproperties.add mproperty + for mpropdef in mproperty.mpropdefs do + if v.ignore_mentity(mpropdef) then continue + self.mpropdefs.add mpropdef + end + end + end +end diff --git a/src/doc/doc_phases/doc_graphs.nit b/src/doc/doc_phases/doc_graphs.nit new file mode 100644 index 0000000..6013088 --- /dev/null +++ b/src/doc/doc_phases/doc_graphs.nit @@ -0,0 +1,123 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Adds importation and class hierarchy graphs. +module doc_graphs + +import doc_poset + +redef class ToolContext + + # Do not generate `graphviz` diagrams. + var opt_nodot = new OptionBool("do not generate graphes with graphviz", "--no-dot") + + redef init do + super + option_context.add_option(opt_nodot) + end +end + +# This phase insert importation and inheritance graphs into pages. +class GraphPhase + super DocPhase + + redef fun apply do + if ctx.opt_nodot.value then return + for page in doc.pages do + var article = page.build_graph(self, doc) + if article == null then continue + # FIXME avoid diff + # page.root.add article + page.root.children[1].children.insert(article, 0) + end + end +end + +redef class DocPage + # Build dot graph articles from `mmodules` list. + # + # Since only `MEntity pages` contain a graph, this method returns null in all + # other cases. + private fun build_graph(v: GraphPhase, doc: DocModel): nullable GraphArticle do return null +end + +# TODO graph generation can be factorized in POSet. + +redef class MModulePage + redef fun build_graph(v, doc) do + var op = new FlatBuffer + var name = "dep_module_{mentity.nitdoc_id}" + op.append("digraph \"{name.escape_to_dot}\" \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") + for mmodule in poset do + if mmodule == self.mentity then + op.append("\"{mmodule.name.escape_to_dot}\"[shape=box,margin=0.03];\n") + else + op.append("\"{mmodule.name.escape_to_dot}\"[URL=\"{mmodule.nitdoc_url.escape_to_dot}\"];\n") + end + for omodule in poset[mmodule].direct_greaters do + op.append("\"{mmodule.name.escape_to_dot}\"->\"{omodule.name.escape_to_dot}\";\n") + end + end + op.append("\}\n") + return new GraphArticle(name, op) + end +end + +redef class MClassPage + redef fun build_graph(v, doc) do + var op = new FlatBuffer + var name = "dep_class_{mentity.nitdoc_id}" + op.append("digraph \"{name.escape_to_dot}\" \{ rankdir=BT; node[shape=none,margin=0,width=0,height=0,fontsize=10]; edge[dir=none,color=gray]; ranksep=0.2; nodesep=0.1;\n") + var classes = poset.to_a + var todo = new Array[MClass] + var done = new HashSet[MClass] + doc.mainmodule.linearize_mclasses(classes) + if not classes.is_empty then todo.add classes.first + while not todo.is_empty do + var c = todo.shift + if done.has(c) then continue + done.add c + if c == self.mentity then + op.append("\"{c.name.escape_to_dot}\"[shape=box,margin=0.03];\n") + else + op.append("\"{c.name.escape_to_dot}\"[URL=\"{c.nitdoc_url.escape_to_dot}\"];\n") + end + var smallers = poset[c].direct_smallers + if smallers.length < 10 then + for c2 in smallers do + op.append("\"{c2.name.escape_to_dot}\"->\"{c.name.escape_to_dot}\";\n") + end + todo.add_all smallers + else + op.append("\"...\"->\"{c.name.escape_to_dot}\";\n") + end + end + op.append("\}\n") + return new GraphArticle(name, op) + end +end + +# An article that display an importation or inheritance graph. +# +# The graph is stored in dot format. +# The final output is delayed untill rendering. +class GraphArticle + super DocComposite + + # Graph ID (used for outputing file with names). + var id: String + + # Dot script of the graph. + var dot: Text +end diff --git a/src/doc/doc_phases/doc_hierarchies.nit b/src/doc/doc_phases/doc_hierarchies.nit new file mode 100644 index 0000000..ba98ee4 --- /dev/null +++ b/src/doc/doc_phases/doc_hierarchies.nit @@ -0,0 +1,92 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Computes importation and class hierarchy lists. +module doc_hierarchies + +import doc_structure +import doc_poset + +# Insert inheritance / importation lists in the page. +class InheritanceListsPhase + super DocPhase + + # Used to sort list by name. + var name_sorter = new MEntityNameSorter + + redef fun apply do + for page in doc.pages do + if page isa MEntityPage then page.build_inh_list(self, doc) + end + end +end + +redef class MEntityPage + + # Build importation / inheritance list for this page. + fun build_inh_list(v: InheritanceListsPhase, doc: DocModel) do end +end + +redef class MModulePage + redef fun build_inh_list(v, doc) do + var section = new ImportationListSection + var imports = self.imports.to_a + v.name_sorter.sort(imports) + section.children.add new HierarchyListArticle(mentity, "Imports", imports) + var clients = self.clients.to_a + v.name_sorter.sort(clients) + section.children.add new HierarchyListArticle(mentity, "Clients", clients) + root.children.insert(section, 1) + end +end + +redef class MClassPage + redef fun build_inh_list(v, doc) do + var section = new InheritanceListSection + var parents = self.parents.to_a + v.name_sorter.sort(parents) + section.children.add new HierarchyListArticle(mentity, "Parents", parents) + var ancestors = self.ancestors.to_a + v.name_sorter.sort(ancestors) + section.children.add new HierarchyListArticle(mentity, "Ancestors", ancestors) + var children = self.children.to_a + v.name_sorter.sort(children) + section.children.add new HierarchyListArticle(mentity, "Children", children) + var descendants = self.descendants.to_a + v.name_sorter.sort(descendants) + section.children.add new HierarchyListArticle(mentity, "Descendants", descendants) + root.children.insert(section, 1) + end +end + +# FIXME diff hack +class ImportationListSection + super DocSection +end + +# FIXME diff hack +class InheritanceListSection + super DocSection +end + +# Dislay a hierarchical list of mentities. +class HierarchyListArticle + super MEntityArticle + + # Title displayed in the top of this list. + var list_title: String + + # MEntities to display in this list. + var mentities: Array[MEntity] +end diff --git a/src/doc/doc_phases/doc_html.nit b/src/doc/doc_phases/doc_html.nit new file mode 100644 index 0000000..a4bdeb0 --- /dev/null +++ b/src/doc/doc_phases/doc_html.nit @@ -0,0 +1,844 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Render the DocModel pages as HTML pages. +# +# FIXME this module is all f*cked up to maintain compatibility with +# the original `doc_templates` and `doc_model` modules. +# This will change in further refactorings. +module doc_html + +import doc_structure +import doc_hierarchies +import doc_intros_redefs +import doc_graphs + +redef class ToolContext + + # File pattern used to link documentation to source code. + var opt_source = new OptionString("link for source (%f for filename, " + + "%l for first line, %L for last line)", "--source") + + # Directory where the CSS and JS is stored. + var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir") + + # Use a shareurl instead of copy shared files. + # + # This is usefull if you don't want to store the Nitdoc templates with your + # documentation. + var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl") + + # Use a custom title for the homepage. + var opt_custom_title = new OptionString("custom title for homepage", "--custom-title") + + # Display a custom brand or logo in the documentation top menu. + var opt_custom_brand = new OptionString("custom link to external site", "--custom-brand") + + # Display a custom introduction text before the projects overview. + var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text") + # Display a custom footer on each documentation page. + # + # Generally used to display the documentation or product version. + var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text") + + # Piwik tracker URL. + # + # If you want to monitor your visitors. + var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker") + + # Piwik tracker site id. + var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id") + + # These options are not currently used in Nitdoc. + + # FIXME redo the plugin + var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream") + # FIXME redo the plugin + var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1") + # FIXME redo the plugin + var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir") + + redef init do + super + + option_context.add_option( + opt_source, opt_sharedir, opt_shareurl, opt_custom_title, + opt_custom_footer, opt_custom_intro, opt_custom_brand, + opt_github_upstream, opt_github_base_sha1, opt_github_gitdir, + opt_piwik_tracker, opt_piwik_site_id) + end + + redef fun process_options(args) do + super + var upstream = opt_github_upstream + var base_sha = opt_github_base_sha1 + var git_dir = opt_github_gitdir + var opts = [upstream.value, base_sha.value, git_dir.value] + if not opts.has_only(null) and opts.has(null) then + print "Error: Options {upstream.names.first}, " + + "{base_sha.names.first} and {git_dir.names.first} " + + "are required to enable the GitHub plugin" + exit 1 + end + end +end + +# Render the Nitdoc as a HTML website. +class RenderHTMLPhase + super DocPhase + + # Used to sort sidebar elements by name. + var name_sorter = new MEntityNameSorter + + redef fun apply do + init_output_dir + for page in doc.pages do + page.render(self, doc).write_to_file("{ctx.output_dir.to_s}/{page.page_url}") + end + end + + # Creates the output directory and imports assets files form `resources/`. + fun init_output_dir do + # create destination dir if it's necessary + var output_dir = ctx.output_dir + if not output_dir.file_exists then output_dir.mkdir + # locate share dir + var sharedir = ctx.opt_sharedir.value + if sharedir == null then + var dir = ctx.nit_dir + sharedir = dir/"share/nitdoc" + if not sharedir.file_exists then + print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR" + abort + end + end + # copy shared files + if ctx.opt_shareurl.value == null then + sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/") + else + sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/") + end + + end + + # A source link template for a given location + fun tpl_showsource(location: nullable Location): nullable String + do + if location == null then return null + var source = ctx.opt_source.value + if source == null then + var url = location.file.filename.simplify_path + return "View Source" + end + # THIS IS JUST UGLY ! (but there is no replace yet) + var x = source.split_with("%f") + source = x.join(location.file.filename.simplify_path) + x = source.split_with("%l") + source = x.join(location.line_start.to_s) + x = source.split_with("%L") + source = x.join(location.line_end.to_s) + source = source.simplify_path + return "View Source" + end +end + +redef class DocPage + + # Render the page as a html template. + private fun render(v: RenderHTMLPhase, doc: DocModel): TplPage do + var shareurl = "." + if v.ctx.opt_shareurl.value != null then + shareurl = v.ctx.opt_shareurl.value.as(not null) + end + + # build page + var tpl = new TplPage + tpl.title = tpl_title(v, doc) + tpl.url = page_url + tpl.shareurl = shareurl + tpl.topmenu = tpl_topmenu(v, doc) + tpl.add_section tpl_content(v, doc) + tpl.footer = v.ctx.opt_custom_footer.value + tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl)) + tpl.sidebar = tpl_sidebar(v, doc) + + # piwik tracking + var tracker_url = v.ctx.opt_piwik_tracker.value + var site_id = v.ctx.opt_piwik_site_id.value + if tracker_url != null and site_id != null then + tpl.scripts.add new TplPiwikScript(tracker_url, site_id) + end + return tpl + end + + # FIXME diff hack + # all properties below are roughly copied from `doc_pages` + + # URL to this page. + fun page_url: String is abstract + + # Build page sidebar if any + fun tpl_sidebar(v: RenderHTMLPhase, doc: DocModel): nullable TplSidebar do return null + + # Build page title string + fun tpl_title(v: RenderHTMLPhase, doc: DocModel): String do + if v.ctx.opt_custom_title.value != null then + return v.ctx.opt_custom_title.value.to_s + end + return "Nitdoc" + end + + # Build top menu template + fun tpl_topmenu(v: RenderHTMLPhase, doc: DocModel): TplTopMenu do + var topmenu = new TplTopMenu(page_url) + var brand = v.ctx.opt_custom_brand.value + if brand != null then + var tpl = new Template + tpl.add "" + tpl.add brand + tpl.add "" + topmenu.brand = tpl + end + topmenu.add_link new TplLink("index.html", "Overview") + topmenu.add_link new TplLink("search.html", "Index") + return topmenu + end + + # Build page content template + fun tpl_content(v: RenderHTMLPhase, doc: DocModel): TplSection is abstract +end + +redef class OverviewPage + redef fun page_url do return "index.html" + + redef fun tpl_title(v, doc) do + if v.ctx.opt_custom_title.value != null then + return v.ctx.opt_custom_title.value.to_s + else + return "Overview" + end + end + + # TODO this should be done in StructurePhase. + redef fun tpl_content(v, doc) do + # intro text + var section = new TplSection.with_title("overview", tpl_title(v, doc)) + var article = new TplArticle("intro") + if v.ctx.opt_custom_intro.value != null then + article.content = v.ctx.opt_custom_intro.value.to_s + end + section.add_child article + # Projects list + var mprojects = doc.model.mprojects.to_a + var sorter = new MConcernRankSorter + sorter.sort mprojects + var ssection = new TplSection.with_title("projects", "Projects") + for mproject in mprojects do + var sarticle = mproject.tpl_article + sarticle.subtitle = mproject.tpl_declaration + sarticle.content = mproject.tpl_definition + var mdoc = mproject.mdoc_or_fallback + if mdoc != null then + sarticle.content = mdoc.tpl_short_comment + end + ssection.add_child sarticle + end + section.add_child ssection + return section + end + + redef fun tpl_sidebar(v, doc) do return new TplSidebar +end + +redef class SearchPage + redef fun page_url do return "search.html" + redef fun tpl_title(v, doc) do return "Index" + + # TODO this should be done in StructurePhase. + redef fun tpl_content(v, doc) do + var tpl = new TplSearchPage("search_all") + var section = new TplSection("search") + # title + tpl.title = "Index" + # modules list + for mmodule in modules_list(v, doc) do + tpl.modules.add mmodule.tpl_link + end + # classes list + for mclass in classes_list(v, doc) do + tpl.classes.add mclass.tpl_link + end + # properties list + for mproperty in mprops_list(v, doc) do + var m = new Template + m.add mproperty.intro.tpl_link + m.add " (" + m.add mproperty.intro.mclassdef.mclass.tpl_link + m.add ")" + tpl.props.add m + end + section.add_child tpl + return section + end + + # Extract mmodule list to display (sorted by name) + private fun modules_list(v: RenderHTMLPhase, doc: DocModel): Array[MModule] do + var sorted = new Array[MModule] + for mmodule in doc.model.mmodule_importation_hierarchy do + if mmodule.is_fictive or mmodule.is_test_suite then continue + sorted.add mmodule + end + v.name_sorter.sort(sorted) + return sorted + end + + # Extract mclass list to display (sorted by name) + private fun classes_list(v: RenderHTMLPhase, doc: DocModel): Array[MClass] do + var sorted = doc.mclasses.to_a + v.name_sorter.sort(sorted) + return sorted + end + + # Extract mproperty list to display (sorted by name) + private fun mprops_list(v: RenderHTMLPhase, doc: DocModel): Array[MProperty] do + var sorted = doc.mproperties.to_a + v.name_sorter.sort(sorted) + return sorted + end +end + +redef class MEntityPage + redef fun page_url do return mentity.nitdoc_url + redef fun tpl_title(v, doc) do return mentity.nitdoc_name + redef fun tpl_content(v, doc) do return root.start_rendering(v, doc, self) +end + +# FIXME all clases below are roughly copied from `doc_pages` and adapted to new +# doc phases. This is to preserve the compatibility with the current +# `doc_templates` module. + +redef class MGroupPage + redef fun tpl_topmenu(v, doc) do + var topmenu = super + var mproject = mentity.mproject + if not mentity.is_root then + topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name) + end + topmenu.add_link new TplLink(page_url, mproject.nitdoc_name) + return topmenu + end + + redef fun tpl_sidebar(v, doc) do + var sidebar = new TplSidebar + var mclasses = new HashSet[MClass] + mclasses.add_all intros + mclasses.add_all redefs + if mclasses.is_empty then return sidebar + var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) + + var sorted = mclasses.to_a + v.name_sorter.sort(sorted) + for mclass in sorted do + list.add_li tpl_sidebar_item(mclass) + end + sidebar.boxes.add new TplSideBox.with_content("All classes", list) + return sidebar + end + + private fun tpl_sidebar_item(def: MClass): TplListItem do + var classes = def.intro.tpl_css_classes.to_a + if intros.has(def) then + classes.add "intro" + else + classes.add "redef" + end + var lnk = new Template + lnk.add new TplLabel.with_classes(classes) + lnk.add def.tpl_link + return new TplListItem.with_content(lnk) + end +end + +redef class MModulePage + redef fun tpl_topmenu(v, doc) do + var topmenu = super + var mproject = mentity.mproject + topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name) + topmenu.add_link new TplLink(mentity.nitdoc_url, mentity.nitdoc_name) + return topmenu + end + + # Class list to display in sidebar + redef fun tpl_sidebar(v, doc) do + # TODO filter here? + var sidebar = new TplSidebar + var mclasses = new HashSet[MClass] + mclasses.add_all mentity.filter_intro_mclasses(v.ctx.min_visibility) + mclasses.add_all mentity.filter_redef_mclasses(v.ctx.min_visibility) + if mclasses.is_empty then return sidebar + var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) + + var sorted = mclasses.to_a + v.name_sorter.sort(sorted) + for mclass in sorted do + list.add_li tpl_sidebar_item(mclass) + end + sidebar.boxes.add new TplSideBox.with_content("All classes", list) + return sidebar + end + + private fun tpl_sidebar_item(def: MClass): TplListItem do + var classes = def.intro.tpl_css_classes.to_a + if def.intro_mmodule == self.mentity then + classes.add "intro" + else + classes.add "redef" + end + var lnk = new Template + lnk.add new TplLabel.with_classes(classes) + lnk.add def.tpl_link + return new TplListItem.with_content(lnk) + end +end + +redef class MClassPage + + redef fun tpl_title(v, doc) do + return "{mentity.nitdoc_name}{mentity.tpl_signature.write_to_string}" + end + + redef fun tpl_topmenu(v, doc) do + var topmenu = super + var mproject = mentity.intro_mmodule.mgroup.mproject + topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}") + topmenu.add_link new TplLink(page_url, mentity.nitdoc_name) + return topmenu + end + + redef fun tpl_sidebar(v, doc) do + var sidebar = new TplSidebar + var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops(v, doc)) + var summary = new TplList.with_classes(["list-unstyled"]) + + by_kind.sort_groups(v.name_sorter) + for g in by_kind.groups do tpl_sidebar_list(g, summary) + sidebar.boxes.add new TplSideBox.with_content("All properties", summary) + return sidebar + end + + private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: TplList) do + if mprops.is_empty then return + var entry = new TplListItem.with_content(mprops.title) + var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) + for mprop in mprops do + list.add_li tpl_sidebar_item(mprop) + end + entry.append list + summary.elts.add entry + end + + private fun tpl_sidebar_item(mprop: MProperty): TplListItem do + var classes = mprop.intro.tpl_css_classes.to_a + if not mprop_is_local(mprop) then + classes.add "inherit" + var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url + var def_url = "{cls_url}#{mprop.nitdoc_id}" + var lnk = new TplLink(def_url, mprop.nitdoc_name) + var mdoc = mprop.intro.mdoc_or_fallback + if mdoc != null then lnk.title = mdoc.short_comment + var item = new Template + item.add new TplLabel.with_classes(classes) + item.add lnk + return new TplListItem.with_content(item) + end + if mpropdefs.has(mprop.intro) then + classes.add "intro" + else + classes.add "redef" + end + var lnk = new Template + lnk.add new TplLabel.with_classes(classes) + lnk.add mprop.tpl_anchor + return new TplListItem.with_content(lnk) + end + + private fun mclass_inherited_mprops(v: RenderHTMLPhase, doc: DocModel): Set[MProperty] do + var res = new HashSet[MProperty] + var local = mentity.local_mproperties(v.ctx.min_visibility) + for mprop in mentity.inherited_mproperties(doc.mainmodule, v.ctx.min_visibility) do + if local.has(mprop) then continue + #if mprop isa MMethod and mprop.is_init then continue + if mprop.intro.mclassdef.mclass.name == "Object" and + (mprop.visibility == protected_visibility or + mprop.intro.mclassdef.mmodule.name != "kernel") then continue + res.add mprop + end + res.add_all local + return res + end + + private fun mprop_is_local(mprop: MProperty): Bool do + for mpropdef in mprop.mpropdefs do + if self.mpropdefs.has(mpropdef) then return true + end + return false + end +end + +redef class MPropertyPage + redef fun tpl_topmenu(v, doc) do + var topmenu = super + var mmodule = mentity.intro_mclassdef.mmodule + var mproject = mmodule.mgroup.mproject + var mclass = mentity.intro_mclassdef.mclass + topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}") + topmenu.add_link new TplLink("{mclass.nitdoc_url}", "{mclass.nitdoc_name}") + topmenu.add_link new TplLink(page_url, mentity.nitdoc_name) + return topmenu + end + + redef fun tpl_title(v, doc) do + return "{mentity.nitdoc_name}{mentity.tpl_signature.write_to_string}" + end + + redef fun tpl_sidebar(v, doc) do return new TplSidebar +end + +redef class DocComposite + # Render this DocComposite as HTML. + # + # FIXME needed to maintain TplSection compatibility. + fun render(v: RenderHTMLPhase, doc: DocModel, page: MEntityPage, parent: TplSectionElt) is abstract +end + +redef class DocRoot + + # Start the rendering from root. + # + # FIXME needed to maintain TplSection compatibility. + fun start_rendering(v: RenderHTMLPhase, doc: DocModel, page: MEntityPage): TplSection do + var section = new TplSection("top") + var mentity = page.mentity + section.title = mentity.nitdoc_name + section.subtitle = mentity.tpl_declaration + # FIXME ugly hack to avoid diff + if mentity isa MGroup and mentity.is_root then + section.title = mentity.mproject.nitdoc_name + section.subtitle = mentity.mproject.tpl_declaration + else if mentity isa MClass then + section.title = "{mentity.nitdoc_name}{mentity.tpl_signature.write_to_string}" + else if mentity isa MProperty then + section.title = "{mentity.nitdoc_name}{mentity.intro.tpl_signature.write_to_string}" + section.subtitle = mentity.tpl_namespace + section.summary_title = mentity.nitdoc_name + end + render(v, doc, page, section) + return section + end + + redef fun render(v, doc, page, parent) do + for child in children do + child.render(v, doc, page, parent) + end + end +end + +redef class ConcernSection + redef fun render(v, doc, page, parent) do + var section = new TplSection(mentity.nitdoc_id) + var mentity = self.mentity + # FIXME hideous hacks to avoid diff + if page.mentity isa MModule and mentity isa MModule then + render_concern_mmodule(page, section, mentity) + else if page.mentity isa MClass and mentity isa MModule then + render_concern_other(page, section, mentity) + else if page.mentity isa MProperty and mentity isa MModule then + render_concern_other(page, section, mentity) + end + for child in children do + child.render(v, doc, page, section) + end + parent.add_child section + end + + private fun render_concern_mmodule(page: MEntityPage, section: TplSection, mmodule: MModule) do + var title = new Template + if mmodule == page.mentity then + title.add "in " + section.summary_title = "in {mmodule.nitdoc_name}" + else + title.add "from " + section.summary_title = "from {mmodule.nitdoc_name}" + end + title.add mmodule.tpl_namespace + section.title = title + end + + private fun render_concern_other(page: MEntityPage, section: TplSection, mmodule: MModule) do + var title = new Template + title.add "in " + title.add mmodule.tpl_namespace + section.title = title + section.summary_title = "in {mmodule.nitdoc_name}" + end +end + +redef class IntroArticle + redef fun render(v, doc, page, parent) do + var article = new TplArticle("intro") + var mentity = self.mentity + if mentity isa MModule then + article.source_link = v.tpl_showsource(mentity.location) + else if mentity isa MClassDef then + article.source_link = v.tpl_showsource(mentity.location) + else if mentity isa MPropDef then + article.source_link = v.tpl_showsource(mentity.location) + end + # article.subtitle = mentity.tpl_declaration + # FIXME diff hack + if mentity isa MProperty then + # intro title + var ns = mentity.intro.mclassdef.mmodule.tpl_namespace + var section = new TplSection("intro") + var title = new Template + title.add "Introduction in " + title.add ns + section.title = title + section.summary_title = "Introduction" + var intro = mentity.intro.tpl_article + intro.source_link = v.tpl_showsource(mentity.intro.location) + section.add_child intro + parent.add_child section + else + article.content = mentity.tpl_definition + parent.add_child article + end + end +end + +redef class ConcernsArticle + redef fun render(v, doc, page, parent) do + # FIXME diff hack + var title = "concerns" + if page.mentity isa MProperty then title = "Concerns" + parent.add_child new TplArticle. + with_content(title, "Concerns", concerns.to_tpl) + end +end + +redef class DefinitionArticle + redef fun render(v, doc, page, parent) do + var article: TplArticle + var mentity = self.mentity + # FIXME hideous hacks... + if mentity isa MModule then + article = mentity.tpl_article + article.subtitle = mentity.tpl_declaration + article.content = mentity.tpl_definition + else if mentity isa MClass then + article = make_mclass_article(v, page) + else if mentity isa MClassDef then + article = make_mclassdef_article(v, page) + article.source_link = v.tpl_showsource(mentity.location) + else if mentity isa MPropDef and page.mentity isa MClass then + article = make_mpropdef_article(v, doc, page) + else + article = mentity.tpl_article + article.subtitle = mentity.tpl_declaration + if mentity isa MPropDef then + article.source_link = v.tpl_showsource(mentity.location) + end + if not mentity isa MVirtualTypeProp then + # article.content = mentity.tpl_comment + end + end + for child in children do + child.render(v, doc, page, article) + end + parent.add_child article + end + + # FIXME avoid diff while preserving TplArticle compatibility. + + private fun make_mclass_article(v: RenderHTMLPhase, page: MEntityPage): TplArticle do + var article = mentity.tpl_article + article.subtitle = mentity.tpl_namespace + article.content = null + return article + end + + private fun make_mclassdef_article(v: RenderHTMLPhase, page: MEntityPage): TplArticle do + var mclassdef = mentity.as(MClassDef) + var article = mentity.tpl_article + if mclassdef.is_intro and mclassdef.mmodule != page.mentity then + article = mentity.tpl_short_article + end + var title = new Template + title.add "in " + title.add mclassdef.mmodule.tpl_namespace + article.subtitle = title + return article + end + + private fun make_mpropdef_article(v: RenderHTMLPhase, doc: DocModel, page: MEntityPage): TplArticle + do + var mpropdef = mentity.as(MPropDef) + var mprop = mpropdef.mproperty + var article = new TplArticle(mprop.nitdoc_id) + var title = new Template + title.add mprop.tpl_icon + title.add "" + if mpropdef.is_intro then + title.add mprop.tpl_link + title.add mprop.intro.tpl_signature + else + var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url + var def_url = "{cls_url}#{mprop.nitdoc_id}" + var lnk = new TplLink.with_title(def_url, mprop.nitdoc_name, + "Go to introduction") + title.add "redef " + title.add lnk + end + article.title = title + article.title_classes.add "signature" + article.summary_title = "{mprop.nitdoc_name}" + article.subtitle = mpropdef.tpl_namespace + if mpropdef.mdoc_or_fallback != null then + article.content = mpropdef.mdoc_or_fallback.tpl_comment + end + # TODO move in its own phase? let's see after doc_template refactoring. + # Add linearization + var all_defs = new HashSet[MPropDef] + for local_def in local_defs(page.as(MClassPage), mprop) do + all_defs.add local_def + var smpropdef = local_def + while not smpropdef.is_intro do + smpropdef = smpropdef.lookup_next_definition( + doc.mainmodule, smpropdef.mclassdef.bound_mtype) + all_defs.add smpropdef + end + end + var lin = all_defs.to_a + doc.mainmodule.linearize_mpropdefs(lin) + if lin.length > 1 then + var lin_article = new TplArticle("{mpropdef.nitdoc_id}.lin") + lin_article.title = "Inheritance" + var lst = new TplList.with_classes(["list-unstyled", "list-labeled"]) + for smpropdef in lin do + lst.add_li smpropdef.tpl_inheritance_item + end + lin_article.content = lst + article.add_child lin_article + end + return article + end + + # Filter `page.mpropdefs` for this `mpropertie`. + # + # FIXME compatability with current templates. + private fun local_defs(page: MClassPage, mproperty: MProperty): HashSet[MPropDef] do + var mpropdefs = new HashSet[MPropDef] + for mpropdef in page.mpropdefs do + if mpropdef.mproperty == mproperty then + mpropdefs.add mpropdef + end + end + return mpropdefs + end +end + +redef class IntrosRedefsListArticle + redef fun render(v, doc, page, parent) do + if mentities.is_empty then return + var title = list_title + # FIXME diff hack + var id = "intros" + if title == "Redefines" then id = "redefs" + var article = new TplArticle.with_title("{mentity.nitdoc_id}.{id}", title) + var list = new TplList.with_classes(["list-unstyled", "list-labeled"]) + for mentity in mentities do + list.add_li mentity.tpl_list_item + end + article.content = list + parent.add_child article + end +end + +# FIXME compatibility with doc_templates. +redef class ImportationListSection + redef fun render(v, doc, page, parent) do + var section = new TplSection.with_title("dependencies", "Dependencies") + for child in children do + child.render(v, doc, page, section) + end + parent.add_child section + end +end + +# FIXME compatibility with doc_templates. +redef class InheritanceListSection + redef fun render(v, doc, page, parent) do + var section = new TplSection.with_title("inheritance", "Inheritance") + for child in children do + child.render(v, doc, page, section) + end + parent.add_child section + end +end + +# FIXME compatibility with doc_templates. +redef class HierarchyListArticle + redef fun render(v, doc, page, parent) do + if mentities.is_empty then return + var title = list_title + var id = list_title.to_lower + var article = new TplArticle.with_title(id, title) + var list = new TplList.with_classes(["list-unstyled", "list-definition"]) + for mentity in mentities do + list.elts.add mentity.tpl_list_item + end + article.content = list + parent.add_child article + end +end + +redef class GraphArticle + redef fun render(v, doc, page, parent) do + var output_dir = v.ctx.output_dir + var path = output_dir / id + var path_sh = path.escape_to_sh + var file = new OFStream.open("{path}.dot") + file.write(dot) + file.close + sys.system("\{ test -f {path_sh}.png && test -f {path_sh}.s.dot && diff -- {path_sh}.dot {path_sh}.s.dot >/dev/null 2>&1 ; \} || \{ cp -- {path_sh}.dot {path_sh}.s.dot && dot -Tpng -o{path_sh}.png -Tcmapx -o{path_sh}.map {path_sh}.s.dot ; \}") + var fmap = new IFStream.open("{path}.map") + var map = fmap.read_all + fmap.close + + var article = new TplArticle("graph") + var alt = "" + # FIXME diff hack + # if title != null then + # article.title = title + # alt = "alt='{title.html_escape}'" + # end + article.css_classes.add "text-center" + var content = new Template + var name_html = id.html_escape + content.add "" + content.add map + article.content = content + parent.add_child article + end +end diff --git a/src/doc/doc_phases/doc_indexing.nit b/src/doc/doc_phases/doc_indexing.nit new file mode 100644 index 0000000..9e9ef3c --- /dev/null +++ b/src/doc/doc_phases/doc_indexing.nit @@ -0,0 +1,106 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Manage indexing of Nit model for Nitdoc QuickSearch. +module doc_indexing + +import doc_extract +private import json::static + +# Generate the index for then Nitdoc QuickSearch field. +# +# Create a JSON object containing links to: +# * modules +# * mclasses +# * mpropdefs +# All entities are grouped by name to make the research easier. +# +# TODO Add a way to change the output and use it from Vim or whatever. +class IndexingPhase + super DocPhase + + redef fun apply do + for mmodule in doc.mmodules do + add_result_for(mmodule.name, mmodule.full_name, mmodule.nitdoc_url) + end + for mclass in doc.mclasses do + add_result_for(mclass.name, mclass.full_name, mclass.nitdoc_url) + end + for mproperty in doc.mproperties do + for mpropdef in mproperty.mpropdefs do + if not doc.mpropdefs.has(mpropdef) then continue + var full_name = mpropdef.mclassdef.mclass.full_name + var cls_url = mpropdef.mclassdef.mclass.nitdoc_url + var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}" + add_result_for(mproperty.name, full_name, def_url) + end + end + # FIXME hack, generation should be done by the render phase + # create destination dir if it's necessary + var output_dir = ctx.output_dir + if not output_dir.file_exists then output_dir.mkdir + + render.write_to_file("{ctx.output_dir.to_s}/quicksearch-list.js") + end + + private var table = new QuickSearchTable + + private fun add_result_for(query: String, txt: String, url: String) do + table[query].add new QuickSearchResult(txt, url) + end + + # Render the index content. + fun render: Template do + var tpl = new Template + var buffer = new RopeBuffer + tpl.add buffer + buffer.append "var nitdocQuickSearchRawList=" + table.append_json buffer + buffer.append ";" + return tpl + end +end + +# The result map for QuickSearch. +private class QuickSearchTable + super JsonMapRead[String, QuickSearchResultList] + super HashMap[String, QuickSearchResultList] + + redef fun provide_default_value(key) do + var v = new QuickSearchResultList + self[key] = v + return v + end +end + +# A QuickSearch result list. +private class QuickSearchResultList + super JsonSequenceRead[QuickSearchResult] + super Array[QuickSearchResult] +end + +# A QuickSearch result. +private class QuickSearchResult + super Jsonable + + # The text of the link. + var txt: String + + # The destination of the link. + var url: String + + redef fun to_json do + return "\{\"txt\":{txt.to_json},\"url\":{url.to_json}\}" + end +end diff --git a/src/doc/doc_phases/doc_intros_redefs.nit b/src/doc/doc_phases/doc_intros_redefs.nit new file mode 100644 index 0000000..85c2c80 --- /dev/null +++ b/src/doc/doc_phases/doc_intros_redefs.nit @@ -0,0 +1,91 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generates lists about intros/redefs in MEntity. +# +# Actually, this works only for MModules and MclassDefs. +module doc_intros_redefs + +import doc_structure + +# Computes intro / redef mentity list for each DefinitionArticle. +class IntroRedefListPhase + super DocPhase + + redef fun apply do + for page in doc.pages do + if not page isa MEntityPage then continue + page.root.build_intro_redef_list(self, doc, page) + end + end +end + +redef class DocComposite + + # Computes intro / redef lists for this page. + # + # See `IntroRedefListPhase`. + fun build_intro_redef_list(v: IntroRedefListPhase, doc: DocModel, page: MEntityPage) do + for child in children do child.build_intro_redef_list(v, doc, page) + end +end + +redef class DefinitionArticle + redef fun build_intro_redef_list(v, doc, page) do + var mentity = self.mentity + if mentity isa MModule then + build_mmodule_list(v, doc, mentity) + else if mentity isa MClassDef and mentity.mmodule == page.mentity then + build_mclassdef_list(v, doc, mentity) + end + super + end + + # TODO this should move to MEntity? + private fun build_mmodule_list(v: IntroRedefListPhase, doc: DocModel, mmodule: MModule) do + var intros = mmodule.intro_mclassdefs(v.ctx.min_visibility).to_a + doc.mainmodule.linearize_mclassdefs(intros) + children.add new IntrosRedefsListArticle(mentity, "Introduces", intros) + var redefs = mmodule.redef_mclassdefs(v.ctx.min_visibility).to_a + doc.mainmodule.linearize_mclassdefs(redefs) + children.add new IntrosRedefsListArticle(mentity, "Redefines", redefs) + end + + # TODO this should move to MEntity? + private fun build_mclassdef_list(v: IntroRedefListPhase, doc: DocModel, mclassdef: MClassDef) do + var intros = mclassdef.collect_intro_mpropdefs(v.ctx.min_visibility).to_a + # FIXME avoid diff changes + # v.ctx.mainmodule.linearize_mpropdefs(intros) + children.add new IntrosRedefsListArticle(mentity, "Introduces", intros) + var redefs = mclassdef.collect_redef_mpropdefs(v.ctx.min_visibility).to_a + # FIXME avoid diff changes + # v.ctx.mainmodule.linearize_mpropdefs(redefs) + children.add new IntrosRedefsListArticle(mentity, "Redefines", redefs) + end + +end + +# An article that displays a list of introduced / refined mentities. +# +# FIXME diff hack +# This can merged with InheritanceListArticle in a more generic class. +class IntrosRedefsListArticle + super MEntityArticle + + # Title displayed as header of the list. + var list_title: String + + # Intro mentities to list. + var mentities: Array[MEntity] +end diff --git a/src/doc/doc_phases/doc_pages.nit b/src/doc/doc_phases/doc_pages.nit new file mode 100644 index 0000000..2594d4b --- /dev/null +++ b/src/doc/doc_phases/doc_pages.nit @@ -0,0 +1,90 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Create DocPage instances for each documentated Mentity. +module doc_pages + +import doc_extract + +# ExtractionPhase populates the DocModel with DocPage. +class MakePagePhase + super DocPhase + + # Instanciates documentation pages for the given DocModel. + redef fun apply do + doc.pages.add new OverviewPage("Overview") + doc.pages.add new SearchPage("Index") + for mgroup in doc.mgroups do + doc.pages.add new MGroupPage(mgroup.nitdoc_id, mgroup) + end + for mmodule in doc.mmodules do + doc.pages.add new MModulePage(mmodule.nitdoc_id, mmodule) + end + for mclass in doc.mclasses do + doc.pages.add new MClassPage(mclass.nitdoc_id, mclass) + end + for mproperty in doc.mproperties do + doc.pages.add new MPropertyPage(mproperty.nitdoc_id, mproperty) + end + end +end + +# The Nitdoc overview page. +class OverviewPage + super DocPage +end + +# The Nidoc full index page. +class SearchPage + super DocPage +end + +# A DocPage documenting a MEntity. +class MEntityPage + super DocPage + + # Type of MEntity documented by this page. + type MENTITY: MEntity + + # MEntity documented by this page. + var mentity: MENTITY +end + +# A documentation page about a MGroup. +class MGroupPage + super MEntityPage + + redef type MENTITY: MGroup +end + +# A documentation page about a MModule. +class MModulePage + super MEntityPage + + redef type MENTITY: MModule +end + +# A documentation page about a MClass. +class MClassPage + super MEntityPage + + redef type MENTITY: MClass +end + +# A documentation page about a MProperty. +class MPropertyPage + super MEntityPage + + redef type MENTITY: MProperty +end diff --git a/src/doc/doc_phases/doc_phases.nit b/src/doc/doc_phases/doc_phases.nit new file mode 100644 index 0000000..fefd1ac --- /dev/null +++ b/src/doc/doc_phases/doc_phases.nit @@ -0,0 +1,21 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Phases represent the *steps* of the NitDoc generation process. +# +# See `DocPhase`. +module doc_phases + +import doc_html +import doc_indexing diff --git a/src/doc/doc_phases/doc_poset.nit b/src/doc/doc_phases/doc_poset.nit new file mode 100644 index 0000000..fd38e0f --- /dev/null +++ b/src/doc/doc_phases/doc_poset.nit @@ -0,0 +1,178 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Importation and inheritance POSet for pages. +module doc_poset + +import doc_pages + +# This phase computes importation and inheritance POSet for pages. +class POSetPhase + super DocPhase + + # Populates the given DocModel. + redef fun apply do + for page in doc.pages do + if page isa MEntityPage then page.build_poset(self, doc) + end + end +end + +redef class MEntityPage + + # The poset associated with this page. + # + # FIXME should be defined in subclasses + # as the POSet can contains other types than `SELF` + var poset = new POSet[MENTITY] + + # Build the POSet for this page. + private fun build_poset(v: POSetPhase, doc: DocModel) do end +end + +redef class MModulePage + + # Imported modules that should appear in the documentation. + var imports = new HashSet[MModule] + + # Clients modules that shjould appear in the documentation. + var clients = new HashSet[MModule] + + redef fun build_poset(v, doc) do + # collect importation + for dep in mentity.in_importation.greaters do + if dep == mentity then continue + if not doc.mmodules.has(dep) then continue + imports.add dep + end + # FIXME avoid diff + #if imports.length > 10 then + if mentity.in_importation.greaters.length > 10 then + imports.clear + for dep in mentity.in_importation.direct_greaters do + if dep == mentity then continue + if not doc.mmodules.has(dep) then continue + imports.add dep + end + end + # collect clients + for dep in mentity.in_importation.smallers do + if dep == mentity then continue + if not doc.mmodules.has(dep) then continue + clients.add dep + end + if clients.length > 10 then + clients.clear + for dep in mentity.in_importation.direct_smallers do + if dep == mentity then continue + if not doc.mmodules.has(dep) then continue + clients.add dep + end + end + # make poset + var mmodules = new HashSet[MModule] + mmodules.add_all mentity.nested_mmodules + mmodules.add_all imports + if clients.length < 10 then mmodules.add_all clients + mmodules.add mentity + build_importation_poset(doc, mmodules) + end + + # Build the POSet of importation from a list of `mmodules`. + private fun build_importation_poset(doc: DocModel, mmodules: Set[MModule]): POSet[MModule] do + for mmodule in mmodules do + if not doc.mmodules.has(mmodule) then continue + poset.add_node mmodule + for omodule in mmodules do + if not doc.mmodules.has(omodule) then continue + poset.add_node mmodule + if mmodule.in_importation < omodule then + poset.add_edge(mmodule, omodule) + end + end + end + return poset + end +end + +redef class MClassPage + + # Direct parents classes to document. + var parents = new HashSet[MClass] + + # Transitive ancestors classes to document. + # + # Does not contain the direct ancestors. + # See `parents` for that. + var ancestors = new HashSet[MClass] + + # Direct children classes to document. + var children = new HashSet[MClass] + + # All descendants classes to document. + # + # Does not contain the direct children. + # See `children` for that. + var descendants = new HashSet[MClass] + + redef fun build_poset(v, doc) do + poset.add_node mentity + + var h = mentity.in_hierarchy(doc.mainmodule) + # parents + for mclass in h.direct_greaters do + if doc.mclasses.has(mclass) then parents.add mclass + end + # ancestors + for mclass in h.greaters do + if mclass == mentity then continue + if not doc.mclasses.has(mclass) then continue + if parents.has(mclass) then continue + ancestors.add mclass + end + # children + for mclass in h.direct_smallers do + if doc.mclasses.has(mclass) then children.add mclass + end + # descendants + for mclass in h.smallers do + if mclass == mentity then continue + if not doc.mclasses.has(mclass) then continue + if children.has(mclass) then continue + descendants.add mclass + end + # poset + var mclasses = new HashSet[MClass] + mclasses.add_all ancestors + mclasses.add_all parents + mclasses.add_all children + mclasses.add_all descendants + mclasses.add mentity + build_inheritance_poset(v, doc, mclasses) + end + + private fun build_inheritance_poset(v: POSetPhase, doc: DocModel, mclasses: Set[MClass]): POSet[MClass] do + for mclass in mclasses do + poset.add_node mclass + for oclass in mclasses do + if mclass == oclass then continue + poset.add_node oclass + if mclass.in_hierarchy(doc.mainmodule) < oclass then + poset.add_edge(mclass, oclass) + end + end + end + return poset + end +end diff --git a/src/doc/doc_phases/doc_structure.nit b/src/doc/doc_phases/doc_structure.nit new file mode 100644 index 0000000..6fc7468 --- /dev/null +++ b/src/doc/doc_phases/doc_structure.nit @@ -0,0 +1,280 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Composes the DocComposite tree of a DocPage and organizes its content. +module doc_structure + +import doc_concerns + +# StructurePhase populates the DocPage content with section and article. +# +# This phase only applies structure. +# The content of the structure is choosen by the rendering phases. +class StructurePhase + super DocPhase + + # Used to sort ConcernsTree by rank. + private var concerns_sorter = new MConcernRankSorter + + # Used to sort ConcernsTree by name. + private var name_sorter = new MEntityNameSorter + + # Populates the given DocModel. + redef fun apply do + for page in doc.pages do + if page isa MEntityPage then page.apply_structure(self, doc) + end + end + + # TODO index and search page should also be structured here +end + +redef class MEntityPage + + # Populates `self` with structure elements like DocComposite ones. + # + # See `StructurePhase`. + fun apply_structure(v: StructurePhase, doc: DocModel) do end +end + +redef class MGroupPage + redef fun apply_structure(v, doc) do + if mentity.is_root then + root.add new IntroArticle(mentity.mproject) + else + root.add new IntroArticle(mentity) + end + var concerns = self.concerns + if concerns == null or concerns.is_empty then return + # FIXME avoid diff + mentity.mproject.booster_rank = -1000 + mentity.booster_rank = -1000 + concerns.sort_with(v.concerns_sorter) + mentity.mproject.booster_rank = 0 + mentity.booster_rank = 0 + root.add new ConcernsArticle(mentity, concerns) + for mentity in concerns do + if mentity isa MModule then + root.add new DefinitionArticle(mentity) + else + root.add new ConcernSection(mentity) + end + end + end +end + +redef class MModulePage + redef fun apply_structure(v, doc) do + root.add new IntroArticle(mentity) + var concerns = self.concerns + if concerns == null or concerns.is_empty then return + # FIXME avoid diff + mentity.mgroup.mproject.booster_rank = -1000 + mentity.mgroup.booster_rank = -1000 + mentity.booster_rank = -1000 + concerns.sort_with(v.concerns_sorter) + mentity.mgroup.mproject.booster_rank = 0 + mentity.mgroup.booster_rank = 0 + mentity.booster_rank = 0 + root.add new ConcernsArticle(mentity, concerns) + # reference list + for mentity in concerns do + var section = new ConcernSection(mentity) + if mentity isa MModule then + var mclasses = mclasses_for_mmodule(mentity).to_a + v.name_sorter.sort(mclasses) + for mclass in mclasses do + var article = new DefinitionArticle(mclass) + var mclassdefs = mclassdefs_for(mclass).to_a + if not mclassdefs.has(mclass.intro) then + article.add(new DefinitionArticle(mclass.intro)) + end + doc.mainmodule.linearize_mclassdefs(mclassdefs) + for mclassdef in mclassdefs do + article.add(new DefinitionArticle(mclassdef)) + end + section.add article + end + end + root.add section + end + end + + # Filters `self.mclassses` by intro `mmodule`. + private fun mclasses_for_mmodule(mmodule: MModule): Set[MClass] do + var mclasses = new HashSet[MClass] + for mclass in self.mclasses do + if mclass.intro.mmodule == mmodule then + mclasses.add mclass + end + end + return mclasses + end + + # Filters `self.mclassdefs` by `mclass`. + private fun mclassdefs_for(mclass: MClass): Set[MClassDef] do + var mclassdefs = new HashSet[MClassDef] + for mclassdef in self.mclassdefs do + if mclassdef.mclass == mclass then + mclassdefs.add mclassdef + end + end + return mclassdefs + end +end + +redef class MClassPage + redef fun apply_structure(v, doc) do + root.add new IntroArticle(mentity) + var concerns = self.concerns + if concerns == null or concerns.is_empty then return + # FIXME diff hack + mentity.intro_mmodule.mgroup.mproject.booster_rank = -1000 + mentity.intro_mmodule.mgroup.booster_rank = -1000 + mentity.intro_mmodule.booster_rank = -1000 + concerns.sort_with(v.concerns_sorter) + mentity.intro_mmodule.mgroup.mproject.booster_rank = 0 + mentity.intro_mmodule.mgroup.booster_rank = 0 + mentity.intro_mmodule.booster_rank = 0 + root.add new ConcernsArticle(mentity, concerns) + for mentity in concerns do + var section = new ConcernSection(mentity) + if mentity isa MModule then + var mprops = mproperties_for(mentity) + var by_kind = new PropertiesByKind.with_elements(mprops) + for group in by_kind.groups do + v.name_sorter.sort(group) + for mprop in group do + for mpropdef in mpropdefs_for(mprop, mentity) do + section.add new DefinitionArticle(mpropdef) + end + end + end + end + root.add section + end + end + + # Filters `self.mpropdefs` by `mmodule`. + # + # FIXME diff hack + private fun mproperties_for(mmodule: MModule): Set[MProperty] do + var mprops = new HashSet[MProperty] + for mpropdef in self.mpropdefs do + if mpropdef.mclassdef.mmodule == mmodule then + mprops.add mpropdef.mproperty + end + end + return mprops + end + + # Filters `self.mpropdefs` by `mproperty`. + # + # FIXME diff hack + private fun mpropdefs_for(mproperty: MProperty, mmodule: MModule): Set[MPropDef] do + var mpropdefs = new HashSet[MPropDef] + for mpropdef in self.mpropdefs do + if mpropdef.mproperty == mproperty and + mpropdef.mclassdef.mmodule == mmodule then + mpropdefs.add mpropdef + end + end + return mpropdefs + end +end + +redef class MPropertyPage + redef fun apply_structure(v, doc) do + root.add new IntroArticle(mentity) + var concerns = self.concerns + if concerns == null or concerns.is_empty then return + # FIXME diff hack + mentity.intro.mclassdef.mmodule.mgroup.mproject.booster_rank = -1000 + mentity.intro.mclassdef.mmodule.mgroup.booster_rank = -1000 + mentity.intro.mclassdef.mmodule.booster_rank = -1000 + concerns.sort_with(v.concerns_sorter) + mentity.intro.mclassdef.mmodule.mgroup.mproject.booster_rank = 0 + mentity.intro.mclassdef.mmodule.mgroup.booster_rank = 0 + mentity.intro.mclassdef.mmodule.booster_rank = 0 + root.add new ConcernsArticle(mentity, concerns) + for mentity in concerns do + var section = new ConcernSection(mentity) + if mentity isa MModule then + # Add mproperties + var mpropdefs = mpropdefs_for(mentity).to_a + v.name_sorter.sort(mpropdefs) + for mpropdef in mpropdefs do + section.add new DefinitionArticle(mpropdef) + end + end + root.add section + end + end + + # Filters `self.mpropdefs` by `mmodule`. + private fun mpropdefs_for(mmodule: MModule): Set[MPropDef] do + var mpropdefs = new HashSet[MPropDef] + for mpropdef in self.mpropdefs do + if mpropdef.mclassdef.mmodule == mmodule then + mpropdefs.add mpropdef + end + end + return mpropdefs + end +end + +# A DocComposite element about a MEntity. +class MEntityComposite + super DocComposite + + # MEntity documented by this page element. + var mentity: MEntity +end + +# A Section about a Concern. +# +# Those sections are used to build the page summary. +class ConcernSection + super MEntityComposite + super DocSection +end + +# An article about a Mentity. +# +# Used to display textual content about a MEntity. +abstract class MEntityArticle + super MEntityComposite + super DocArticle +end + +# An introduction article about a MEntity. +# +# Used at the top of a documentation page to introduce the documented MEntity. +class IntroArticle + super MEntityComposite + super DocArticle +end + +# An article that display a ConcernsTreee as a list. +class ConcernsArticle + super MEntityArticle + + # Concerns to list in this article. + var concerns: ConcernsTree +end + +# An article that display the definition text of a MEntity. +class DefinitionArticle + super MEntityArticle +end diff --git a/src/interpreter/naive_interpreter.nit b/src/interpreter/naive_interpreter.nit index e297f9c..bb0b3b3 100644 --- a/src/interpreter/naive_interpreter.nit +++ b/src/interpreter/naive_interpreter.nit @@ -857,8 +857,15 @@ redef class AMethPropdef return v.int_instance(args[0].to_i.bin_xor(args[1].to_i)) else if pname == "bin_not" then return v.int_instance(args[0].to_i.bin_not) + else if pname == "int_to_s_len" then + return v.int_instance(recvval.to_s.length) else if pname == "native_int_to_s" then - return v.native_string_instance(recvval.to_s) + var s = recvval.to_s + var srecv = args[1].val.as(Buffer) + srecv.clear + srecv.append(s) + srecv.add('\0') + return null else if pname == "strerror_ext" then return v.native_string_instance(recvval.strerror) end @@ -963,13 +970,13 @@ redef class AMethPropdef if fromval < 0 then debug("Illegal access on {recvval} for element {fromval}/{recvval.length}") end - if fromval + lenval >= recvval.length then + if fromval + lenval > recvval.length then debug("Illegal access on {recvval} for element {fromval}+{lenval}/{recvval.length}") end if toval < 0 then debug("Illegal access on {destval} for element {toval}/{destval.length}") end - if toval + lenval >= destval.length then + if toval + lenval > destval.length then debug("Illegal access on {destval} for element {toval}+{lenval}/{destval.length}") end recvval.as(FlatBuffer).copy(fromval, lenval, destval, toval) diff --git a/src/loader.nit b/src/loader.nit index a046815..517deab 100644 --- a/src/loader.nit +++ b/src/loader.nit @@ -330,8 +330,21 @@ redef class ModelBuilder # Return the mgroup associated to a directory path. # If the directory is not a group null is returned. + # + # Note: `paths` is also used to look for mgroups fun get_mgroup(dirpath: String): nullable MGroup do + if not dirpath.file_exists then do + for p in paths do + var try = p / dirpath + if try.file_exists then + dirpath = try + break label + end + end + return null + end label + var rdp = module_absolute_path(dirpath) if mgroups.has_key(rdp) then return mgroups[rdp] @@ -527,6 +540,12 @@ redef class ModelBuilder nmodules.add(nmodule) self.mmodule2nmodule[mmodule] = nmodule + var source = nmodule.location.file + if source != null then + assert source.mmodule == null + source.mmodule = mmodule + end + if decl != null then # Extract documentation var ndoc = decl.n_doc @@ -673,6 +692,11 @@ redef class MGroup end +redef class SourceFile + # Associated mmodule, once created + var mmodule: nullable MModule = null +end + redef class AStdImport # The imported module once determined var mmodule: nullable MModule = null diff --git a/src/nitdoc.nit b/src/nitdoc.nit index c1a4851..333fa96 100644 --- a/src/nitdoc.nit +++ b/src/nitdoc.nit @@ -13,6 +13,7 @@ # limitations under the License. # Documentation generator for the nit language. +# # Generate API documentation in HTML format from nit source code. module nitdoc @@ -20,21 +21,44 @@ import modelbuilder import doc redef class ToolContext - var docphase: Phase = new NitdocPhase(self, null) + # Nitdoc generation phase. + var docphase: Phase = new Nitdoc(self, null) end -private class NitdocPhase +# Nitdoc phase explores the model and generate pages for each mentities found +private class Nitdoc super Phase redef fun process_mainmodule(mainmodule, mmodules) do - # generate doc - var nitdoc = new Nitdoc(toolcontext, mainmodule.model, mainmodule) - nitdoc.generate + var doc = new DocModel(mainmodule.model, mainmodule) + + var phases = [ + new ExtractionPhase(toolcontext, doc), + new IndexingPhase(toolcontext, doc), + new MakePagePhase(toolcontext, doc), + new POSetPhase(toolcontext, doc), + new ConcernsPhase(toolcontext, doc), + new StructurePhase(toolcontext, doc), + new InheritanceListsPhase(toolcontext, doc), + new IntroRedefListPhase(toolcontext, doc), + new GraphPhase(toolcontext, doc), + new RenderHTMLPhase(toolcontext, doc): DocPhase] + + for phase in phases do + toolcontext.info("# {phase.class_name}", 1) + phase.apply + end end end -# process options +# build toolcontext var toolcontext = new ToolContext +var tpl = new Template +tpl.add "Usage: nitdoc [OPTION]... ...\n" +tpl.add "Generates HTML pages of API documentation from Nit source files." +toolcontext.tooldescription = tpl.write_to_string + +# process options toolcontext.process_options(args) var arguments = toolcontext.option_context.rest @@ -43,6 +67,7 @@ var model = new Model var mbuilder = new ModelBuilder(model, toolcontext) var mmodules = mbuilder.parse_full(arguments) +# process if mmodules.is_empty then return mbuilder.run_phases toolcontext.run_global_phases(mmodules)