Helpful features about packages

Introduced classes

class MakeRule

nitc :: MakeRule

A rule that goes into a Makefile
private class ManPage

nitc :: ManPage

class NitMakefile

nitc :: NitMakefile

A Makefile for the Nit project
private class NitPackagePhase

nitc :: NitPackagePhase

Redefined classes

redef class IniFile

nitc :: nitpackage $ IniFile

Read and write INI configuration files
redef class MModule

nitc :: nitpackage $ MModule

A Nit module is usually associated with a Nit source file.
redef class MPackage

nitc :: nitpackage $ MPackage

A Nit package, that encompass a product
redef class Sys

nitc :: nitpackage $ Sys

The main class of the program.
redef class ToolContext

nitc :: nitpackage $ ToolContext

Global context for tools

All class definitions

redef class IniFile

nitc :: nitpackage $ IniFile

Read and write INI configuration files
redef class MModule

nitc :: nitpackage $ MModule

A Nit module is usually associated with a Nit source file.
redef class MPackage

nitc :: nitpackage $ MPackage

A Nit package, that encompass a product
class MakeRule

nitc $ MakeRule

A rule that goes into a Makefile
private class ManPage

nitc $ ManPage

class NitMakefile

nitc $ NitMakefile

A Makefile for the Nit project
redef class Sys

nitc :: nitpackage $ Sys

The main class of the program.
redef class ToolContext

nitc :: nitpackage $ ToolContext

Global context for tools
package_diagram nitc::nitpackage nitpackage nitc\>frontend\> frontend nitc::nitpackage->nitc\>frontend\> nitc::commands_main commands_main nitc::nitpackage->nitc::commands_main nitc\>modelize\> modelize nitc\>frontend\>->nitc\>modelize\> gen_nit gen_nit nitc\>frontend\>->gen_nit nitc nitc nitc\>frontend\>->nitc nitc\>semantize\> semantize nitc\>frontend\>->nitc\>semantize\> nitc\>parser\> parser nitc\>frontend\>->nitc\>parser\> nitc::commands_model commands_model nitc::commands_main->nitc::commands_model ...nitc\>modelize\> ... ...nitc\>modelize\>->nitc\>modelize\> ...gen_nit ... ...gen_nit->gen_nit ...nitc ... ...nitc->nitc ...nitc\>semantize\> ... ...nitc\>semantize\>->nitc\>semantize\> ...nitc\>parser\> ... ...nitc\>parser\>->nitc\>parser\> ...nitc::commands_model ... ...nitc::commands_model->nitc::commands_model a_star-m a_star-m a_star-m->nitc::nitpackage

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module abstract_tree

trees :: abstract_tree

Introduce tree structures abstraction
module actors_injection_phase

nitc :: actors_injection_phase

Injects model for the classes annotated with "is actor" so
module annotation

nitc :: annotation

Management and utilities on annotations
module array

core :: array

This module introduces the standard array structure.
module astbuilder

nitc :: astbuilder

Instantiation and transformation of semantic nodes in the AST of expressions and statements
module auto_super_init

nitc :: auto_super_init

Computing of super-constructors that must be implicitly called at the begin of constructors.
module bintree

trees :: bintree

Binary Tree data-structure
module bitset

core :: bitset

Services to handle BitSet
module bktree

trees :: bktree

Implementation of BKTree
module bytes

core :: bytes

Services for byte streams and arrays
module caching

serialization :: caching

Services for caching serialization engines
module check_annotation

nitc :: check_annotation

Check that annotation present in the AST are either primitive or user-declared
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module commands_base

nitc :: commands_base

Documentation commands
module commands_model

nitc :: commands_model

Doc commands about a Model or a MEntity
module console

console :: console

Defines some ANSI Terminal Control Escape Sequences.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module deriving

nitc :: deriving

Injection of automatic method definitions for standard methods, based on the attributes of the classes
module digraph

graph :: digraph

Implementation of directed graphs, also called digraphs.
module div_by_zero

nitc :: div_by_zero

Detection of divisions by zero in obvious cases
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module flow

nitc :: flow

Intraprocedural static flow.
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module glsl_validation

nitc :: glsl_validation

Check shader code within Nit modules using the tool glslangValidator
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module i18n_phase

nitc :: i18n_phase

Basic support of internationalization through the generation of id-to-string tables
module ini

ini :: ini

Read and write INI configuration files
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module lexer

nitc :: lexer

Lexer and its tokens.
module lexer_work

nitc :: lexer_work

Internal algorithm and data structures for the Nit lexer
module list

core :: list

This module handle double linked lists
module literal

nitc :: literal

Parsing of literal values in the abstract syntax tree.
module loader

nitc :: loader

Loading of Nit source files
module local_var_init

nitc :: local_var_init

Verify that local variables are initialized before their usage
module location

nitc :: location

Nit source-file and locations in source-file
module math

core :: math

Mathematical operations
module mdoc

nitc :: mdoc

Documentation of model entities
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module mmodule

nitc :: mmodule

modules and module hierarchies in the metamodel
module mmodule_data

nitc :: mmodule_data

Define and retrieve data in modules
module model

nitc :: model

Classes, types and properties
module model_base

nitc :: model_base

The abstract concept of model and related common things
module model_collect

nitc :: model_collect

Collect things from the model.
module model_examples

nitc :: model_examples

Examples for Model entities
module model_index

nitc :: model_index

Search things from the Model
module modelbuilder_base

nitc :: modelbuilder_base

Load nit source files and build the associated model
module modelize

nitc :: modelize

Create a model from nit source files
module modelize_class

nitc :: modelize_class

Analysis and verification of class definitions to instantiate model element
module modelize_property

nitc :: modelize_property

Analysis and verification of property definitions to instantiate model element
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module mpackage

nitc :: mpackage

Modelisation of a Nit package
module native

core :: native

Native structures for text and bytes
module nitpm_shared

nitc :: nitpm_shared

Services related to the Nit package manager
module no_warning

nitc :: no_warning

Fill toolcontext information about blacklisting of warnings.
module numeric

core :: numeric

Advanced services for Numeric types
module opts

opts :: opts

Management of options on the command line
module ordered_tree

ordered_tree :: ordered_tree

Manipulation and presentation of ordered trees.
module parallelization_phase

nitc :: parallelization_phase

Phase generating threads for functions annotated with threaded annotation
module parse_annotations

nitc :: parse_annotations

Simple annotation parsing
module parser

nitc :: parser

Parser.
module parser_nodes

nitc :: parser_nodes

AST nodes of the Nit language
module parser_prod

nitc :: parser_prod

Production AST nodes full definition.
module parser_util

nitc :: parser_util

Utils and tools related to parsers and AST
module parser_work

nitc :: parser_work

Internal algorithm and data structures for the Nit parser
module phase

nitc :: phase

Phases of the processing of nit programs
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module rbtree

trees :: rbtree

A red–black tree is a data structure which is a type of self-balancing binary search tree.
module re

core :: re

Regular expression support for all services based on Pattern
module regex_phase

nitc :: regex_phase

Check for error in regular expressions from string literals
module ropes

core :: ropes

Tree-based representation of a String.
module scope

nitc :: scope

Identification and scoping of local variables and labels.
module semantize

nitc :: semantize

Process bodies of methods in regard with the model.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module serialization_model_phase

nitc :: serialization_model_phase

Phase generating methods (model-only) to serialize Nit objects
module simple_misc_analysis

nitc :: simple_misc_analysis

Simple vavious processing on a AST
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module tables

nitc :: tables

Module that interfaces the parsing tables.
module template

template :: template

Basic template system
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module toolcontext

nitc :: toolcontext

Common command-line tool infrastructure than handle options and error messages
module trees

trees :: trees

General module for tree data structures
module trie

trees :: trie

A trie (or prefix tree) is a datastructure used to perform prefix searches.
module typing

nitc :: typing

Intraprocedural resolution of static types and OO-services
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O
module version

nitc :: version

This file was generated by git-gen-version.sh

Parents

module frontend

nitc :: frontend

Collect and orchestration of main frontend phases

Children

module a_star-m

a_star-m

# Helpful features about packages
module nitpackage

import frontend
import doc::commands::commands_main

redef class ToolContext

	# nitpackage phase
	var nitpackage_phase: Phase = new NitPackagePhase(self, null)

	# --expand
	var opt_expand = new OptionBool("Move singleton packages to their own directory", "--expand")

	# --check-ini
	var opt_check_ini = new OptionBool("Check package.ini files", "--check-ini")

	# --gen-ini
	var opt_gen_ini = new OptionBool("Generate package.ini files", "--gen-ini")

	# --force
	var opt_force = new OptionBool("Force update of existing files", "-f", "--force")

	# --check-makefile
	var opt_check_makefile = new OptionBool("Check Makefile files", "--check-makefile")

	# --gen-makefile
	var opt_gen_makefile = new OptionBool("Generate Makefile files", "--gen-makefile")

	# --check-man
	var opt_check_man = new OptionBool("Check manpages files", "--check-man")

	# --gen-man
	var opt_gen_man = new OptionBool("Generate manpages files", "--gen-man")

	# --check-readme
	var opt_check_readme = new OptionBool("Check README.md files", "--check-readme")

	redef init do
		super
		option_context.add_option(opt_expand, opt_force)
		option_context.add_option(opt_check_ini, opt_gen_ini)
		option_context.add_option(opt_check_makefile, opt_gen_makefile)
		option_context.add_option(opt_check_man, opt_gen_man)
		option_context.add_option(opt_check_readme)
	end
end

private class NitPackagePhase
	super Phase

	redef fun process_mainmodule(mainmodule, mmodules) do
		var mpackages = extract_mpackages(mmodules)
		for mpackage in mpackages do

			# Fictive and buggy packages are ignored
			if not mpackage.has_source then
				toolcontext.warning(mpackage.location, "no-source",
					"Warning: `{mpackage}` has no source file")
				continue
			end

			# Check package INI files
			if toolcontext.opt_check_ini.value then
				mpackage.check_ini(toolcontext)
				continue
			end

			# Check package Makefiles
			if toolcontext.opt_check_makefile.value then
				mpackage.check_makefile(toolcontext, mainmodule)
				continue
			end

			# Check manpages
			if toolcontext.opt_check_man.value then
				mpackage.check_man(toolcontext, mainmodule)
				continue
			end

			# Check README.md
			if toolcontext.opt_check_readme.value then
				mpackage.check_readme(toolcontext)
				continue
			end

			# Expand packages
			if toolcontext.opt_expand.value and not mpackage.is_expanded then
				var path = mpackage.expand
				toolcontext.info("{mpackage} moved to {path}", 0)
			end
			if not mpackage.is_expanded then
				toolcontext.warning(mpackage.location, "no-dir",
					"Warning: `{mpackage}` has no package directory")
				continue
			end

			# Create INI file
			if toolcontext.opt_gen_ini.value then
				if not mpackage.has_ini or toolcontext.opt_force.value then
					var path = mpackage.gen_ini
					toolcontext.info("generated INI file `{path}`", 0)
				end
			end

			# Create Makefile
			if toolcontext.opt_gen_makefile.value then
				if not mpackage.has_makefile or toolcontext.opt_force.value then
					var path = mpackage.gen_makefile(toolcontext.modelbuilder.model, mainmodule)
					if path != null then
						toolcontext.info("generated Makefile `{path}`", 0)
					end
				end
			end

			# Create manpages
			if toolcontext.opt_gen_man.value then
				mpackage.gen_man(toolcontext, mainmodule)
			end
		end
	end

	# Extract the list of packages from the mmodules passed as arguments
	fun extract_mpackages(mmodules: Collection[MModule]): Collection[MPackage] do
		var mpackages = new ArraySet[MPackage]
		for mmodule in mmodules do
			var mpackage = mmodule.mpackage
			if mpackage == null then continue
			mpackages.add mpackage
		end
		return mpackages.to_a
	end
end

redef class MPackage

	# Expand `self` in its own directory
	private fun expand: String do
		assert not is_expanded

		var ori_path = package_path.as(not null)
		var new_path = ori_path.dirname / name

		new_path.mkdir
		sys.system "mv {ori_path} {new_path / name}.nit"

		var ini_file = "{new_path}.ini"
		if ini_file.file_exists then
			sys.system "mv {new_path}.ini {new_path}/package.ini"
		end

		return new_path
	end

	private var maintainer: nullable String is lazy do
		return git_exec("git shortlog -esn . | head -n 1 | sed 's/\\s*[0-9]*\\s*//'")
	end

	private var contributors: Array[String] is lazy do
		var contribs = git_exec("git shortlog -esn . | head -n -1 | " +
			"sed 's/\\s*[0-9]*\\s*//'")
		if contribs == null then return new Array[String]
		return contribs.split("\n")
	end

	private var git_url: nullable String is lazy do
		var git = git_exec("git remote get-url origin")
		if git == null then return null
		git = git.replace("git@github.com:", "https://github.com/")
		git = git.replace("git@gitlab.com:", "https://gitlab.com/")
		return git
	end

	private var git_dir: nullable String is lazy do
		return git_exec("git rev-parse --show-prefix")
	end

	private var browse_url: nullable String is lazy do
		var git = git_url
		if git == null then return null
		var browse = git.replace(".git", "")
		var dir = git_dir
		if dir == null or dir.is_empty then return browse
		return "{browse}/tree/master/{dir}"
	end

	private var homepage_url: nullable String is lazy do
		var git = git_url
		if git == null then return null
		# Special case for nit files
		if git.has_suffix("/nit.git") then
			return "http://nitlanguage.org"
		end
		return git.replace(".git", "")
	end

	private var issues_url: nullable String is lazy do
		var git = git_url
		if git == null then return null
		return "{git.replace(".git", "")}/issues"
	end

	private var license: nullable String is lazy do
		var git = git_url
		if git == null then return null
		# Special case for nit files
		if git.has_suffix("/nit.git") then
			return "Apache-2.0"
		end
		return null
	end

	private fun git_exec(cmd: String): nullable String do
		var path = package_path
		if path == null then return null
		if not is_expanded then path = path.dirname
		with pr = new ProcessReader("sh", "-c", "cd {path} && {cmd}") do
			return pr.read_all.trim
		end
	end

	private var allowed_ini_keys = [
		"package.name", "package.desc", "package.tags", "package.license",
		"package.maintainer", "package.more_contributors",
		"upstream.browse", "upstream.git", "upstream.git.directory",
		"upstream.homepage", "upstream.issues", "upstream.apk", "upstream.tryit",
		"source.exclude"
		]

	private fun check_ini(toolcontext: ToolContext) do
		if not has_ini then
			toolcontext.error(location, "No `package.ini` file for `{name}`")
			return
		end

		var pkg_path = package_path
		if pkg_path == null then return

		var ini_path = ini_path
		if ini_path == null then return

		var ini = new IniFile.from_file(ini_path)

		ini.check_key(ini_path, toolcontext, self, "package.name", name)
		ini.check_key(ini_path, toolcontext, self, "package.desc")
		ini.check_key(ini_path, toolcontext, self, "package.tags")

		# FIXME since `git reflog --follow` seems bugged
		ini.check_key(ini_path, toolcontext, self, "package.maintainer")
		# var maint = mpackage.maintainer
		# if maint != null then
			# ini.check_key(toolcontext, self, "package.maintainer", maint)
		# end

		# FIXME since `git reflog --follow` seems bugged
		# var contribs = mpackage.contributors
		# if contribs.not_empty then
			# ini.check_key(toolcontext, self, "package.more_contributors", contribs.join(", "))
		# end

		ini.check_key(ini_path, toolcontext, self, "package.license", license)
		ini.check_key(ini_path, toolcontext, self, "upstream.browse", browse_url)
		ini.check_key(ini_path, toolcontext, self, "upstream.git", git_url)
		ini.check_key(ini_path, toolcontext, self, "upstream.git.directory", git_dir)
		ini.check_key(ini_path, toolcontext, self, "upstream.homepage", homepage_url)
		ini.check_key(ini_path, toolcontext, self, "upstream.issues", issues_url)

		for key in ini.flatten.keys do
			if not allowed_ini_keys.has(key) then
				toolcontext.warning(location, "unknown-ini-key",
					"Warning: ignoring unknown `{key}` key in `{ini_path}`")
			end
		end
	end

	private fun gen_ini: String do
		var ini_path = self.ini_path.as(not null)
		var ini = new IniFile.from_file(ini_path)

		ini.update_value("package.name", name)
		ini.update_value("package.desc", "")
		ini.update_value("package.tags", "")
		ini.update_value("package.maintainer", maintainer)
		ini.update_value("package.more_contributors", contributors.join(","))
		ini.update_value("package.license", license or else "")

		ini.update_value("upstream.browse", browse_url)
		ini.update_value("upstream.git", git_url)
		ini.update_value("upstream.git.directory", git_dir)
		ini.update_value("upstream.homepage", homepage_url)
		ini.update_value("upstream.issues", issues_url)

		ini.write_to_file(ini_path)
		return ini_path
	end

	# Makefile

	# The path to `self` Makefile
	fun makefile_path: nullable String do
		var path = package_path
		if path == null then return null
		if not is_expanded then return null
		return path / "Makefile"
	end

	# Does `self` have a Makefile?
	fun has_makefile: Bool do
		var makefile_path = self.makefile_path
		if makefile_path == null then return false
		return makefile_path.file_exists
	end

	private fun check_makefile(toolcontext: ToolContext, mainmodule: MModule) do
		var model = toolcontext.modelbuilder.model
		var filter = new ModelFilter(accept_example = false, accept_test = false)

		var cmd_bin = new CmdMains(model, filter, mentity = self)
		var res_bin = cmd_bin.init_command
		if not res_bin isa CmdSuccess then return

		for mmodule in cmd_bin.results.as(not null) do
			if not mmodule isa MModule then continue

			if mmodule.makefile_path == null then
				toolcontext.warning(location, "missing-makefile",
					"Warning: no Makefile for executable module `{mmodule.full_name}`")
			end
		end
	end

	private fun gen_makefile(model: Model, mainmodule: MModule): nullable String do
		var filter = new ModelFilter(accept_example = false, accept_test = false)

		var pkg_path = package_path.as(not null)
		var makefile_path = makefile_path.as(not null)

		var bins = new Array[String]
		var cmd_bin = new CmdMains(model, filter, mentity = self)
		var res_bin = cmd_bin.init_command
		if res_bin isa CmdSuccess then
			for mmodule in cmd_bin.results.as(not null) do
				if not mmodule isa MModule then continue
				var mmodule_makefile = mmodule.makefile_path
				if mmodule_makefile != null and mmodule_makefile != makefile_path then continue

				var file = mmodule.location.file
				if file == null then continue
				# Remove package path prefix
				var bin_path = file.filename
				if pkg_path.has_suffix("/") then
					bin_path = bin_path.replace(pkg_path, "")
				else
					bin_path = bin_path.replace("{pkg_path}/", "")
				end
				bins.add bin_path
			end
		end

		if  bins.is_empty then return null

		var make = new NitMakefile(bins)
		make.render.write_to_file(makefile_path)
		return makefile_path
	end

	# Manpages

	# The path to `self` manpage files
	private fun man_path: nullable String do
		var path = package_path
		if path == null then return null
		if not is_expanded then return null
		return path / "man"
	end

	# Does `self` have a manpage files?
	private fun has_man: Bool do
		var man_path = self.man_path
		if man_path == null then return false
		return man_path.file_exists
	end

	private fun check_man(toolcontext: ToolContext, mainmodule: MModule) do
		var model = toolcontext.modelbuilder.model
		var filter = new ModelFilter(accept_example = false, accept_test = false)
		var cmd = new CmdMains(model, filter, mentity = self)
		var res = cmd.init_command
		if not res isa CmdSuccess then return

		for mmodule in cmd.results.as(not null) do
			if not mmodule isa MModule then continue
			mmodule.check_man(toolcontext)
		end
	end

	private fun gen_man(toolcontext: ToolContext, mainmodule: MModule) do
		var model = toolcontext.modelbuilder.model
		var filter = new ModelFilter(accept_example = false, accept_test = false)
		var cmd = new CmdMains(model, filter, mentity = self)
		var res = cmd.init_command
		if not res isa CmdSuccess then return

		var pkg_man = man_path.as(not null)
		for mmodule in cmd.results.as(not null) do
			if not mmodule isa MModule then continue
			if not has_man then pkg_man.mkdir
			mmodule.gen_man(toolcontext)
		end
	end

	# README

	private fun check_readme(toolcontext: ToolContext) do
		if not has_readme then
			toolcontext.error(location, "No `README.md` file for `{name}`")
			return
		end
	end
end

redef class MModule
	private fun makefile_path: nullable String do
		var file = location.file
		if file == null then return null

		var dir = file.filename.dirname
		var makefile = (dir / "Makefile")
		if not makefile.file_exists then return null

		for line in makefile.to_path.read_lines do
			if line.has_prefix("{name}:") then return makefile
		end
		return null
	end

	private fun man_path: nullable String do
		var mpackage = self.mpackage
		if mpackage == null then return null
		var path = mpackage.man_path
		if path == null then return null
		return path / "{name}.man"
	end

	# Does `self` have a manpage?
	private fun has_man: Bool do
		var man_path = self.man_path
		if man_path == null then return false
		return man_path.file_exists
	end

	private fun make_module(toolcontext: ToolContext): Bool do
		var mpackage = self.mpackage
		if mpackage == null then return false
		if not mpackage.is_expanded then return false

		var pkg_path = mpackage.package_path
		if pkg_path == null then return false

		var pr = new ProcessReader("sh", "-c", "cd {pkg_path} && make -Bs bin/{name}")
		var out = pr.read_all.trim
		pr.close
		pr.wait
		if pr.status > 0 then
			toolcontext.error(location, "unable to compile `{name}`")
			print out
			return false
		end
		return true
	end

	private fun stub_man(toolcontext: ToolContext): nullable String do
		if not make_module(toolcontext) then return null
		var mpackage = self.mpackage
		if mpackage == null then return null
		if not mpackage.is_expanded then return null

		var pkg_path = mpackage.package_path
		if pkg_path == null then return null

		var pr = new ProcessReader("{pkg_path}/bin/{name}", "--stub-man")
		var man = pr.read_all.trim
		pr.close
		pr.wait
		if pr.status > 0 then
			toolcontext.error(location, "unable to run `{pkg_path}/bin/{name} --stub-man`")
			print man
			return null
		end
		return man
	end

	private fun check_man(toolcontext: ToolContext) do
		if not has_man then
			toolcontext.error(location, "No manpage for bin {full_name}")
			return
		end
		var man_path = self.man_path.as(not null)
		var man = stub_man(toolcontext)
		if man == null or man.is_empty then return

		var old_man = new ManPage.from_file(self, man_path)
		var new_man = new ManPage.from_string(self, man)
		old_man.diff(toolcontext, new_man)
	end

	private fun gen_man(toolcontext: ToolContext) do
		var man = stub_man(toolcontext)
		if man == null or man.is_empty then return
		var man_path = self.man_path
		if man_path == null then return
		man.write_to_file(man_path)
		toolcontext.info("created manpage `{man_path}`", 0)
	end
end

redef class IniFile
	private fun check_key(ini_file: String, toolcontext: ToolContext, mpackage: MPackage, key: String, value: nullable String) do
		if not has_key(key) then
			toolcontext.warning(mpackage.location, "missing-ini-key",
				"Warning: missing `{key}` key in `{ini_file}`")
			return
		end
		if self[key].as(not null).is_empty then
			toolcontext.warning(mpackage.location, "missing-ini-value",
				"Warning: empty `{key}` key in `{ini_file}`")
			return
		end
		if value != null and self[key] != value then
			toolcontext.warning(mpackage.location, "wrong-ini-value",
				"Warning: wrong value for `{key}` in `{ini_file}`. " +
				"Expected `{value}`, got `{self[key] or else ""}`")
		end
	end

	private fun update_value(key: String, value: nullable String) do
		if value == null then return
		if not has_key(key) then
			self[key] = value
		else
			var old_value = self[key]
			if not value.is_empty and old_value != value then
				self[key] = value
			end
		end
	end
end

# A Makefile for the Nit project
class NitMakefile

	# Nit files to compile
	var nit_files: Array[String]

	# List of rules to add in the Makefile
	fun rules: Array[MakeRule] do
		var rules = new Array[MakeRule]

		var rule_all = new MakeRule("all", is_phony = true)
		rules.add rule_all

		for file in nit_files do
			var bin = file.basename.strip_extension

			rule_all.deps.add "bin/{bin}"

			var rule = new MakeRule("bin/{bin}")
			rule.deps.add "$(shell $(NITLS) -M {file})"
			rule.lines.add "mkdir -p bin/"
			rule.lines.add "$(NITC) {file} -o bin/{bin}"
			rules.add rule
		end

		var rule_check = new MakeRule("check", is_phony = true)
		rule_check.lines.add "$(NITUNIT) ."
		rules.add rule_check

		var rule_doc = new MakeRule("doc", is_phony = true)
		rule_doc.lines.add "$(NITDOC) . -o doc/"
		rules.add rule_doc

		var rule_clean = new MakeRule("clean", is_phony = true)
		if nit_files.not_empty then
			rule_clean.lines.add "rm -rf bin/"
		end
		rule_clean.lines.add "rm -rf doc/"
		rules.add rule_clean

		return rules
	end

	# Render `self`
	fun render: Writable do
		var tpl = new Template
		tpl.addn """
# 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.\n"""

		if nit_files.not_empty then
			tpl.addn "NITC ?= nitc"
			tpl.addn "NITLS ?= nitls"
		end
		tpl.addn "NITUNIT ?= nitunit"
		tpl.addn "NITDOC ?= nitdoc"

		for rule in rules do
			tpl.add "\n{rule.render.write_to_string}"
		end

		return tpl
	end
end

# A rule that goes into a Makefile
class MakeRule

	# Rule name
	var name: String

	# Is this rule a `.PHONY` one?
	var is_phony: Bool = false is optional

	# Rule dependencies
	var deps = new Array[String]

	# Rule lines
	var lines = new Array[String]

	# Render `self`
	fun render: Writable do
		var tpl = new Template
		if is_phony then
			tpl.addn ".PHONY: {name}"
		end
		tpl.add "{name}:"
		if deps.not_empty then
			tpl.add " {deps.join(" ")}"
		end
		tpl.add "\n"
		for line in lines do
			tpl.addn "\t{line}"
		end
		return tpl
	end
end

private class ManPage
	var mmodule: MModule
	var name: nullable String is noinit
	var synopsis: nullable String is noinit
	var options = new HashMap[Array[String], String]

	init from_file(mmodule: MModule, file: String) do
		from_lines(mmodule, file.to_path.read_lines)
	end

	init from_string(mmodule: MModule, string: String) do
		from_lines(mmodule, string.split("\n"))
	end

	init from_lines(mmodule: MModule, lines: Array[String]) do
		init mmodule

		var section = null
		for i in [0..lines.length[ do
			var line = lines[i]
			if line.is_empty then continue

			if line == "# NAME" then
				section = "name"
				continue
			end
			if line == "# SYNOPSIS" then
				section = "synopsis"
				continue
			end
			if line == "# OPTIONS" then
				section = "options"
				continue
			end

			if section == "name" and name == null then
				name = line.trim
			end
			if section == "synopsis" and synopsis == null then
				synopsis = line.trim
			end
			if section == "options" and line.has_prefix("###") then
				var opts = new Array[String]
				for opt in line.substring(3, line.length).trim.replace("`", "").split(",") do
					opts.add opt.trim
				end
				var desc = ""
				if i < lines.length - 1 then
					desc = lines[i + 1].trim
				end
				options[opts] = desc
			end
		end
	end

	fun diff(toolcontext: ToolContext, ref: ManPage) do
		if name != ref.name then
			toolcontext.warning(mmodule.location, "diff-man",
				"Warning: outdated man description. " +
				"Expected `{ref.name or else ""}` got `{name or else ""}`.")
		end
		if synopsis != ref.synopsis then
			toolcontext.warning(mmodule.location, "diff-man",
				"Warning: outdated man synopsis. " +
				"Expected `{ref.synopsis or else ""}` got `{synopsis or else ""}`.")
		end
		for name, desc in options do
			if not ref.options.has_key(name) then
				toolcontext.warning(mmodule.location, "diff-man",
					"Warning: unknown man option `{name}`.`")
				continue
			end
			var ref_desc = ref.options[name]
			if desc != ref_desc then
				toolcontext.warning(mmodule.location, "diff-man",
					"Warning: outdated man option description. Expected `{ref_desc}` got `{desc}`.")
			end
		end
		for ref_name, ref_desc in ref.options do
			if not options.has_key(ref_name) then
				toolcontext.warning(mmodule.location, "diff-man",
					"Warning: missing man option `{ref_name}`.`")
			end
		end
	end

	redef fun to_s do
		var tpl = new Template
		tpl.addn "# NAME"
		tpl.addn name or else ""
		tpl.addn "# SYNOPSIS"
		tpl.addn synopsis or else ""
		tpl.addn "# OPTIONS"
		for name, desc in options do
			tpl.addn " * {name}: {desc}"
		end
		return tpl.write_to_string
	end
end

# build toolcontext
var toolcontext = new ToolContext
var tpl = new Template
tpl.add "Usage: nitpackage [OPTION]... <file.nit>...\n"
tpl.add "Helpful features about packages."
toolcontext.tooldescription = tpl.write_to_string

# process options
toolcontext.process_options(args)
var arguments = toolcontext.option_context.rest

# build model
var model = new Model
var mbuilder = new ModelBuilder(model, toolcontext)
var mmodules = mbuilder.parse_full(arguments)

# process
if mmodules.is_empty then return
mbuilder.run_phases
toolcontext.run_global_phases(mmodules)
src/nitpackage.nit:15,1--791,39