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 <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Jean Privat <jean@pryen.org>
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}"
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
--- /dev/null
+.github_data
+nitrpg_data
+listener
+web
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+NITC=../../bin/nitc
+
+all: listener web
+
+listener:
+ $(NITC) src/listener.nit
+
+web:
+ $(NITC) src/web.nit
+
+clean:
+ rm listener web
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# `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
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# 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 <host> <port>"
+ 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
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# 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
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# 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
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# 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 """<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">"""
+ render_title
+ add """ </h3>
+ </div>
+ <div class="panel-body">"""
+ render_body
+ add """</div>
+ </div>"""
+ end
+
+ # Render the panel title.
+ # Betweem `<h4>` 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 """<div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">"""
+ render_title
+ add """
+ </h3>
+ </div>"""
+ render_body
+ add """</div>"""
+ end
+end
+
+# Display an error message within a panel.
+class ErrorPanel
+ super Panel
+
+ redef fun rendering do
+ add """
+<div class="panel panel-danger">
+ <div class="panel-heading">
+ <h3 class="panel-title">"""
+ render_title
+ add """
+ </h3>
+ </div>
+ <div class="panel-body">"""
+ render_body
+ add """
+ </div>
+</div>
+"""
+ end
+
+ # The error message to display as panel body.
+ var msg: String
+
+ redef fun render_title do
+ add "<span class=\"glyphicon glyphicon-warning-sign\"></span> "
+ 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 "<span class=\"glyphicon glyphicon-home\"></span> "
+ add "<a href=\"{game.url}\">{game.name}</a>"
+ end
+
+ redef fun render_body do
+ add "<strong class=\"text-success\">{game.load_players.length}</strong>"
+ add " <a href=\"{game.url}/players\">players</a><br>"
+ add "<strong class=\"text-success\">{game.stats["pulls"]}</strong> pull requests"
+ add " (<strong>{game.stats["pulls_open"]}</strong> open)<br>"
+ add "<strong class=\"text-success\">{game.stats["issues"]}</strong> issues"
+ add " (<strong>{game.stats["issues_open"]}</strong> open)<br>"
+ 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 "<a href=\"{player.url}\">"
+ add " <img class=\"img-circle\" style=\"width: 30px\""
+ add " src=\"{player.user.avatar_url}\" alt=\"{player.name}\">"
+ add "</a> "
+ add "<a href=\"{player.url}\">{player.name}</a>"
+ end
+
+ redef fun render_body do
+ var ranking = game.player_ranking
+ # TODO player.rank
+ add "<p class=\"lead\">ranked "
+ add " <span class=\"text-success\"># {ranking[player.name]}</span></p>"
+ add "<strong class=\"text-success\">{player.nitcoins}</strong> nitcoins<br>"
+ 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 "<span class=\"glyphicon glyphicon-user\"></span> "
+ add "<a href=\"{game.url}/players\">Players</a>"
+ end
+
+ redef fun render_body do
+ var players = game.load_players.values.to_a
+ if players.is_empty then
+ add "<em>No player yet...</em>"
+ return
+ end
+ (new PlayerCoinComparator).sort(players)
+ for player in players do
+ add "<a href=\"{player.url}\">"
+ add player.name
+ add "</a> ({player.nitcoins})<br>"
+ 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 "<span class=\"glyphicon glyphicon-user\"></span> "
+ add "<a href=\"{game.url}/players\">Players</a>"
+ end
+
+ redef fun render_body do
+ var players = game.load_players.values.to_a
+ (new PlayerCoinComparator).sort(players)
+ if players.is_empty then
+ add "<div class=\"panel-body\">"
+ add "<em>No player yet...</em>"
+ add "</div>"
+ return
+ end
+ add """<table class="table table-striped table-hover">
+ <tr>
+ <th>#</th>
+ <th>Player</th>
+ <th>Nitcoins</th>
+ </tr>"""
+ var rank = 1
+ for player in players do
+ add "<tr>"
+ add " <td>{rank}</td>"
+ add " <td><a href=\"{player.url}\">{player.name}</a></td>"
+ add " <td>{player.nitcoins}</td>"
+ add "</tr>"
+ rank += 1
+ end
+ add "</table>"
+ end
+end
+
+# A panel that display the podium.
+class PodiumPanel
+ super Panel
+
+ # Game instance.
+ var game: Game
+
+ redef fun render_title do
+ add "<span class=\"glyphicon glyphicon-stats\"></span> 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 "<em>No players yet...</em>"
+ return
+ end
+ add """
+ <div class="container-fluid">
+ <div id="podium" class="row row-sm-height">"""
+ 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 """
+ <div class="col-xs-2 col-xs-height col-xs-offset-{{{order}}} col-bottom"
+ style="text-align: center;">
+ <p>
+ <a href="{{{player.url}}}">
+ <img class="img-circle" style="width: 80px"
+ src="{{{player.user.avatar_url}}}" alt="{{{player.name}}}">
+ </a>
+ </p>
+ <p><a href="{{{player.url}}}">{{{player.name}}}</a></p>
+ <p>{{{player.nitcoins}}}</p>
+ <div class=" progress-bar-warning progress-bar-striped"
+ style="height: {{{size}}}px;"></div>
+ </div>"""
+ end
+ add """
+ </div>
+ </div>"""
+ 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 "<span class=\"glyphicon glyphicon-check\"></span> "
+ 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 "<em>No pull request to review yet...</em>"
+ return
+ end
+ for issue in issues do
+ add """<div class="media">
+ <a class="media-left" href="{{{player.url}}}">
+ <img class=\"img-circle\" style="width:50px"
+ src="{{{issue.user.avatar_url}}}" alt="{{{issue.user.login}}}">
+ </a>
+ <div class="media-body">
+ <h4 class="media-heading">
+ <a href="{{{issue.html_url}}}">#{{{issue.number}}} {{{issue.title}}}</a></h4>
+ <span class="text-muted">opened by </span>
+ <a href="{{{player.url}}}">{{{issue.user.login}}}</a>
+ </div>
+ </div>"""
+ end
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# 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 """
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <title>Github RPG</title>
+ <link rel="stylesheet"
+ href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
+ <link rel="stylesheet" href="{{{root_url}}}/styles/main.css">
+ </head>
+ <body>
+ <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
+ <a class="navbar-brand" href="/">Github RPG</a>"""
+ if not breadcrumbs == null then
+ add breadcrumbs.as(not null)
+ end
+ add """
+ </nav>
+ <div class="container-fluid">
+ <div class="row">"""
+ if not side_panels.is_empty then
+ add """<div class="col-xs-3" id="side">"""
+ for panel in side_panels do add panel
+ add """</div>
+ <div class="col-xs-9" id="flow">"""
+ else
+ add """<div class="col-xs-12" id="flow">"""
+ end
+ for panel in flow_panels do add panel
+ add """ </div>
+ </div>
+ </div>
+"""
+ end
+
+ # Render the footer shared by all pages.
+ fun render_footer do
+ add """
+ <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
+ </body>
+</html>
+"""
+ 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 "<ol class=\"breadcrumb\">"
+ for entry in entries do
+ add "<li>{entry}</li>"
+ end
+ add "</ol>"
+ end
+
+ # Add a link to the breadcrumbs.
+ fun add_link(href, name: String) do
+ entries.add "<a href=\"{href}\">{name}</a>"
+ end
+end
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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 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
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2014-2015 Alexandre Terrasa <alexandre@moz-code.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.
+
+# 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 <host> <port> <root_url>"
+ 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
--- /dev/null
+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;
+}
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
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
#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);
}
* 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
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", "")
# 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))
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
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
# 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
--- /dev/null
+# 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
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
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
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
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
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
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
+++ /dev/null
-# 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]... <file.nit>...\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 "<span class='navbar-brand'>"
- tpl.add brand
- tpl.add "</span>"
- 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 "<img src='{name_html}.png' usemap='#{name_html}' style='margin:auto' {alt}/>"
- 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 "<a target='_blank' title='Show source' href=\"{url.html_escape}\">View Source</a>"
- 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 "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\">View Source</a>"
- 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 "<span id='{main_mpropdef.nitdoc_id}'></span>"
- 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
-
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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 "<a target='_blank' title='Show source' href=\"{url.html_escape}\">View Source</a>"
+ 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 "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\">View Source</a>"
+ 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 "<span class='navbar-brand'>"
+ tpl.add brand
+ tpl.add "</span>"
+ 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 "<span id='{mpropdef.nitdoc_id}'></span>"
+ 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 "<img src='{name_html}.png' usemap='#{name_html}' style='margin:auto' {alt}/>"
+ content.add map
+ article.content = content
+ parent.add_child article
+ end
+end
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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
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
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)
# 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]
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
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
# limitations under the License.
# Documentation generator for the nit language.
+#
# Generate API documentation in HTML format from nit source code.
module nitdoc
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]... <file.nit>...\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
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)