Now that projects can have metadata, let's just provide them.
Commits are grouped by maintainer and directory so that selfish maintainers can review only their.
Fell free to propose better tagging or metadata. See #1655 and http://nitlanguage.org/nep/project_metadata.html for current state of the discussion.
Note that the proof on concept of a catalog is available at http://nitlanguage.org/catalog
Pull-Request: #1675
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Christophe Gigax <christophe.gigax@viacesi.fr>
Julien Pagès <julien.projet@gmail.com>
+
+Mehdi Ait Younes <overpex@gmail.com>
+
+Hervé Matysiak <herve.matysiak@viacesi.fr>
@echo '***************************************************************'
@echo '* Generate doc for NIT standard library *'
@echo '***************************************************************'
- bin/nitdoc $$(bin/nitls lib -rs --path) -d doc/stdlib \
+ bin/nitdoc lib -d doc/stdlib \
--custom-title "Nit Standard Library" \
--custom-brand "<a href=\"http://nitlanguage.org/\">Nitlanguage.org</a>" \
--custom-overview-text "<p>Documentation for the standard library of Nit<br/>Version $$(git describe)<br/>Date: $$(git show --format="%cd" | head -1)</p>" \
--piwik-site-id "2" \
doc/nitc/index.html: bin/nitdoc bin/nitls
- bin/nitdoc $$(bin/nitls lib -rs --path) src/nit*.nit src/test_*.nit -d doc/nitc \
+ bin/nitdoc lib src/nit*.nit src/test_*.nit -d doc/nitc \
--private \
--custom-title "Nit Compilers and Tools" \
--custom-brand "<a href=\"http://nitlanguage.org/\">Nitlanguage.org</a>" \
# Generate email and fill the attributes `email_content` and `email_title`
fun generate_email(beer_events: BeerEvents)
do
- email_title = beer_events.to_email_title
+ email_title = "Benitlux {street.capitalized}{beer_events.to_email_title}"
email_content = beer_events.to_email_content
end
var mail = new Mail("Benitlux <benitlux@xymus.net>", email_title, content)
mail.to.add email
mail.header["Content-Type"] = "text/html; charset=\"UTF-8\""
+ mail.header["List-Unsubscribe"] = unsub_link
+ mail.header["Precedence"] = "bulk"
mail.encrypt_with_base64
mail.send
# Get a pretty and short version of `self`
fun to_email_title: String
do
- var title = "Benelux Beer Menu"
-
# New beers
var new_beers_name = new Array[String]
for beer in self.new_beers do new_beers_name.add beer.name
if not new_beers_name.is_empty then
- title += " (+ {new_beers_name.join(", ")})"
+ return " (+ {new_beers_name.join(", ")})"
end
- return title
+ return ""
end
end
module moles_android
-import mnit_android
+import mnit::android
import android::portrait
import moles
module moles_linux
-import mnit_linux
+import mnit::linux
import moles
import effects
-Chainz of Friendz
-
-A puzzle game
+Chainz of Friendz, a multi-platform puzzle game
# Objectives
module friendz_android
import friendz
-import mnit_android
+import mnit::android
import android::landscape
redef class App
module friendz_linux
import friendz
-import mnit_linux
+import mnit::linux
redef class Display
redef fun wanted_width do return app.screen_width
var res = new Array[String]
for l in logins do
var u = get_and_check("https://api.github.com/users/{l}").json_as_map
- if not u.has_key("name") then
- print "No public name for user {l}"
+ if not u.has_key("name") or u["name"] == null or not u.has_key("email")or u["email"] == null then
+ print "No public name/email for user {l}"
continue
end
var r = "{u["name"].to_s} <{u["email"].to_s}>"
# limitations under the License.
import s2pn
-import mnit_android
+import mnit::android
# limitations under the License.
import s2pn
-import mnit_linux
+import mnit::linux
android_manifest """<uses-permission android:name="android.permission.VIBRATE" />"""
end
-import mnit_android
+import mnit::android
import android::portrait
import serialization
module simple_linux
import simple
-import mnit_linux
+import mnit::linux
import test_data_store
# Returns the config value at `key` or return `default` if no key was found.
private fun value_or_default(key: String, default: String): String do
- if not has_key(key) then return default
- return self[key]
+ return self[key] or else default
end
# Site name displayed.
src/objc_parser.nit
src/objc_test_parser.nit
tests/MyClass.nit
+tests/nsarray.nit
+tests/nsalert.nit
objc.ast.dot
-all: bin/objcwrapper
+all: bin/objcwrapper bin/header_static
../nitcc/src/nitcc:
make -C ../nitcc
../../bin/nitpick tests/MyClass.nit
# Test on classes of libgnustep-base-dev
-check-gnustep:
+check-gnustep: bin/objcwrapper bin/header_static
gcc -E /usr/include/GNUstep/Foundation/NSArray.h -I /usr/include/GNUstep/ -Wno-deprecated \
| ../header_keeper/bin/header_keeper /usr/include/GNUstep/Foundation/NSArray.h \
| bin/header_static > tests/NSArray.pre.h
- bin/objcwrapper tests/NSArray.pre.h
- ../../bin/nitpick NSArray.nit
+ bin/objcwrapper tests/NSArray.pre.h -o tests/nsarray.nit
+ ../../bin/nitpick tests/nsarray.nit
# Test on classes of the Apple Foundation framework
-check-apple:
+check-apple: bin/objcwrapper bin/header_static
gcc -E /System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSArray.h \
| ../header_keeper/bin/header_keeper /System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSArray.h \
| bin/header_static > tests/NSArray.pre.h
- bin/objcwrapper tests/NSArray.pre.h
- ../../bin/nitpick NSArray.nit
+ bin/objcwrapper tests/NSArray.pre.h -o tests/nsarray.nit
+ ../../bin/nitpick tests/nsarray.nit
-bin/header_static:
+ gcc -E /System/Library/Frameworks/AppKit.framework/Headers/NSAlert.h \
+ | ../header_keeper/bin/header_keeper NSAlert.h \
+ | bin/header_static > tests/NSAlert.pre.h
+ bin/objcwrapper tests/NSAlert.pre.h -o tests/nsalert.nit
+ ../../bin/nitpick tests/nsalert.nit
+
+bin/header_static: $(shell ../../bin/nitls -M src/header_static.nit)
../../bin/nitc --dir bin src/header_static.nit
--- /dev/null
+Generator of Nit extern classes to wrap Objective-C services.
+
+# Description
+
+_objcwrapper_ is a tool to help access Objective-C classes and methods from Nit.
+It generates Nit code composed of extern classes and extern methods from the Objective-C FFI.
+The code should be valid Nit, but it may need some tweaking by hand before use.
+
+_objcwrapper_ takes as input preprocessed Objective-C header files.
+This preprocessing is usually done by combinig (or piping) calls to:
+`gcc -E`, `header_keeper` and `header_static`.
+See the check rules in the Makefile for example preprocessing.
+
+# Usage
+
+1. Compile _objcwrapper_ with: `make`
+
+2. Compile the wrapper `NSArray.nit` from the preprocessed header `NSArray.h` with:
+
+ ~~~
+ bin/objcwrapper -o NSArray.h NSArray.h
+ ~~~
+
+3. Import the generated module as usual from any Nit program.
+ It is not recommended to modify the generated file directly,
+ but you can redef the generated classes from other modules.
+
+# See also
+
+_jwrapper_ is the inspiration for this tool.
+It generate wrappers to access Java services from Nit.
{instance:} '-';
parameter =
- {named:} [left:]term ':' lpar parameter_type rpar attribute? [right:]term |
+ {named:} [left:]term ':' parameter_type_in_par? attribute? [right:]term |
{single:} term |
{comma:} comma '...' |
{macro:} macro_name;
+ parameter_type_in_par = lpar parameter_type rpar;
+
parameter_type =
{normal:} type |
{anonymous:} type anonymous |
'long long int' |
'float' |
'double' |
- 'long double';
+ 'long double' |
+ 'size_t';
classe =
{class:} class |
while not input.eof do
var line = input.read_line
- if line.to_s.has("static") then static_target = true
+
+ if line.has("typedef struct") then continue
+
+ if line.has("static") then static_target = true
if static_target then
if line.to_s.has("__attribute__") then static_attribute_target = true
import objc_model
redef class Sys
+
# Path to the output file
var opt_output = new OptionString("Output file", "-o")
-end
-class CodeGenerator
- # Merge the calls to `alloc` and `init...` in a single constructor?
+ # Shall `init` methods/constructors be wrapped as methods?
#
- # If `true`, also the default behavior, initializing an extern Objective-C object looks like:
+ # By default, these methods/constructors are wrapped as extern constructors.
+ # So initializing an extern Objective-C object looks like:
# ~~~nitish
# var o = new NSArray.init_with_array(some_other_array)
# ~~~
#
- # If `false`, the object must first be allocated and then initialized.
+ # If this option is set, the object must first be allocated and then initialized.
# This is closer to the Objective-C behavior:
# ~~~nitish
# var o = new NSArray
# o.init_with_array(some_other_array)
# ~~~
- var init_with_alloc = true is writable
+ var opt_init_as_methods = new OptionBool(
+ "Wrap `init...` constructors as Nit methods instead of Nit constructors",
+ "--init-as-methods")
+
+ private var objc_to_nit_types: Map[String, String] is lazy do
+ var types = new HashMap[String, String]
+ types["char"] = "Byte"
+ types["short"] = "Int"
+ types["short int"] = "Int"
+ types["int"] = "Int"
+ types["long"] = "Int"
+ types["long int"] = "Int"
+ types["long long"] = "Int"
+ types["long long int"] = "Int"
+ types["float"] = "Float"
+ types["double"] = "Float"
+ types["long double"] = "Float"
+
+ types["NSUInteger"] = "Int"
+ types["BOOL"] = "Bool"
+ types["id"] = "NSObject"
+ types["constid"] = "NSObject"
+ types["SEL"] = "NSObject"
+ types["void"] = "Pointer"
+
+ return types
+ end
+end
+
+redef class ObjcModel
+ redef fun knows_type(objc_type) do return super or
+ objc_to_nit_types.keys.has(objc_type)
+end
+
+# Wrapper generator
+class CodeGenerator
+
+ # `ObjcModel` to wrap
+ var model: ObjcModel
# Generate Nit code to wrap `classes`
- fun generate(classes: Array[ObjcClass])
+ fun generate
do
+ var classes = model.classes
+
# Open specified path or stdin
var file
var path = opt_output.value
if path != null then file.close
end
- private fun type_convertor(type_word: String): String
+ private fun write_class(classe: ObjcClass, file: Writer)
do
- var types = new HashMap[String, String]
- types["char"] = "Byte"
- types["short"] = "Int"
- types["short int"] = "Int"
- types["int"] = "Int"
- types["long"] = "Int"
- types["long int"] = "Int"
- types["long long"] = "Int"
- types["long long int"] = "Int"
- types["float"] = "Float"
- types["double"] = "Float"
- types["long double"] = "Float"
+ # Class header
+ file.write """
- types["NSUInteger"] = "Int"
- types["BOOL"] = "Bool"
- types["id"] = "NSObject"
+extern class {{{classe.name}}} in "ObjC" `{ {{{classe.name}}} * `}
+"""
- if types.has_key(type_word) then
- return types[type_word]
- else
- return type_word
- end
- end
+ # Supers
+ for super_name in classe.super_names do file.write """
+ super {{{super_name}}}
+"""
+ if classe.super_names.is_empty then file.write """
+ super NSObject
+"""
- private fun write_class(classe: ObjcClass, file: Writer)
- do
- var commented_methods = new Array[ObjcMethod]
- file.write "extern class " + classe.name + """ in "ObjC" `{ """ + classe.name + """ * `}\n"""
- for super_name in classe.super_names do
- file.write """ super """ + super_name + "\n"
- end
- if classe.super_names.is_empty then file.write """ super NSObject\n"""
- write_constructor(classe, file)
file.write "\n"
+
+ # Constructor or constructors
+ write_constructors(classe, file)
+
+ # Attributes
for attribute in classe.attributes do
write_attribute(attribute, file)
end
+
+ # Methods
for method in classe.methods do
- if method.is_commented then
- commented_methods.add(method)
- else
- if init_with_alloc and method.params.first.name.has("init") then continue
- file.write """ """
- write_method(method, file)
- file.write """ in "ObjC" `{\n """
- write_objc_method_call(method, file)
- file.write """ `}"""
- if method != classe.methods.last then file.write "\n\n"
- end
- end
- for commented_method in commented_methods do
- if commented_method == commented_methods.first then file.write "\n"
- file.write """ #"""
- write_method(commented_method, file)
- if commented_method != commented_methods.last then file.write "\n"
+ if not model.knows_all_types(method) then method.is_commented = true
+
+ if not opt_init_as_methods.value and method.is_init then continue
+
+ write_method_signature(method, file)
+ write_objc_method_call(method, file)
end
- file.write "\nend\n"
+
+ file.write """
+end
+"""
end
- private fun write_constructor(classe: ObjcClass, file: Writer)
+ private fun write_constructors(classe: ObjcClass, file: Writer)
do
- if init_with_alloc then
- for method in classe.methods do
- if method.params.first.name.has("init") and not method.is_commented then
- file.write """\n """
- if method.params.first.name == "init" then
- file.write "new"
- else
- write_method(method, file)
- end
- file.write """ in "ObjC" `{\n"""
- write_objc_init_call(classe.name, method, file)
- file.write """ `}\n"""
- end
- end
- else
- file.write """\n new in "ObjC"`{\n"""
- file.write """ return [""" + classe.name + " alloc];\n"
- file.write """ `}\n"""
+ if opt_init_as_methods.value then
+ # A single constructor for `alloc`
+ file.write """
+ new in "ObjC" `{
+ return [{{{classe.name}}} alloc];
+ `}
+
+"""
+ return
+ end
+
+ # A constructor per `init...` method
+ for method in classe.methods do
+ if not method.is_init then continue
+
+ if not model.knows_all_types(method) then method.is_commented = true
+
+ write_method_signature(method, file)
+
+ write_objc_init_call(classe.name, method, file)
end
end
private fun write_attribute(attribute: ObjcAttribute, file: Writer)
do
+ if not model.knows_type(attribute.return_type) then attribute.is_commented = true
+
write_attribute_getter(attribute, file)
# TODO write_attribute_setter if there is no `readonly` annotation
- file.write "\n"
end
private fun write_attribute_getter(attribute: ObjcAttribute, file: Writer)
do
- file.write """ fun """ + attribute.name.to_snake_case + ": " + type_convertor(attribute.return_type)
- file.write """ in "ObjC" `{\n"""
- file.write """ return [self """ + attribute.name + "];\n"
- file.write """ `}\n"""
+ var nit_attr_name = attribute.name.to_snake_case
+ var nit_attr_type = attribute.return_type.objc_to_nit_type
+
+ var c = attribute.comment_str
+
+ file.write """
+{{{c}}} fun {{{nit_attr_name}}}: {{{nit_attr_type}}} in "ObjC" `{
+{{{c}}} return [self {{{attribute.name}}}];
+{{{c}}} `}
+
+"""
end
private fun write_attribute_setter(attribute: ObjcAttribute, file: Writer)
do
- file.write """ fun """ + attribute.name.to_snake_case + "=(value: " + type_convertor(attribute.return_type) + ")"
- file.write " in \"ObjC\" `\{\n"
- file.write """ self.""" + attribute.name + " = value;\n"
- file.write """ `}\n"""
+ var nit_attr_name = attribute.name.to_snake_case
+ var nit_attr_type = attribute.return_type.objc_to_nit_type
+
+ var c = attribute.comment_str
+
+ file.write """
+{{{c}}} fun {{{nit_attr_name}}}=(value: {{{nit_attr_type}}}) in "ObjC" `{
+{{{c}}} return self.{{{attribute.name}}} = value;
+{{{c}}} `}
+
+"""
end
- private fun write_method(method: ObjcMethod, file: Writer)
+ private fun write_method_signature(method: ObjcMethod, file: Writer)
do
+ var c = method.comment_str
+
+ # Build Nit method name
var name = ""
for param in method.params do
name += param.name[0].to_upper.to_s + param.name.substring_from(1)
- name = name.to_snake_case
end
- if name.has("init") and init_with_alloc then
- file.write "new "
- else
- if not init_with_alloc and name == "init" then name = "init_0"
- file.write "fun "
+ name = name.to_snake_case
+
+ if name == "init" then name = ""
+
+ # Kind of method
+ var fun_keyword = "fun"
+ if not opt_init_as_methods.value and method.is_init then
+ fun_keyword = "new"
end
- file.write name
+
+ # Params
+ var params = new Array[String]
for param in method.params do
- if param == method.params.first and not param.is_single then
- file.write "(" + param.variable_name + ": " + type_convertor(param.return_type)
- end
- if param != method.params.first and not param.is_single then
- file.write ", " + param.variable_name + ": " + type_convertor(param.return_type)
- end
- if param == method.params.last and not param.is_single then
- file.write ")"
- end
+ if param.is_single then break
+ params.add "{param.variable_name}: {param.return_type.objc_to_nit_type}"
end
- if method.return_type != "void" and not method.params.first.name.has("init") then
- file.write ": " + type_convertor(method.return_type)
+
+ var params_with_par = ""
+ if params.not_empty then params_with_par = "({params.join(", ")})"
+
+ # Return
+ var ret = ""
+ if method.return_type != "void" and fun_keyword != "new" then
+ ret = ": {method.return_type.objc_to_nit_type}"
end
+
+ file.write """
+{{{c}}} {{{fun_keyword}}} {{{name}}}{{{params_with_par}}}{{{ret}}} in "ObjC" `{
+"""
end
- private fun write_objc_init_call(classe_name: String, method: ObjcMethod, file: Writer)
+ # Write a combined call to alloc and to a constructor/method
+ private fun write_objc_init_call(class_name: String, method: ObjcMethod, file: Writer)
do
- file.write """ return [[""" + classe_name + " alloc] "
+ # Method name and other params
+ var params = new Array[String]
for param in method.params do
if not param.is_single then
- file.write param.name + ":" + param.variable_name
- if not param == method.params.last then file.write " "
- else
- file.write param.name
- end
+ params.add "{param.name}: {param.variable_name}"
+ else params.add param.name
end
- file.write "];\n"
+
+ var c = method.comment_str
+
+ file.write """
+{{{c}}} return [[{{{class_name}}} alloc] {{{params.join(" ")}}}];
+{{{c}}} `}
+
+"""
end
private fun write_objc_method_call(method: ObjcMethod, file: Writer)
do
- if method.return_type != "void" then file.write "return "
- file.write "[self "
+ # Is there a value to return?
+ var ret = ""
+ if method.return_type != "void" then ret = "return "
+
+ # Method name and other params
+ var params = new Array[String]
for param in method.params do
if not param.is_single then
- file.write param.name + ":" + param.variable_name
- if not param == method.params.last then file.write " "
- else
- file.write param.name
- end
+ params.add "{param.name}: {param.variable_name}"
+ else params.add param.name
end
- file.write "];\n"
+
+ var c = method.comment_str
+
+ file.write """
+{{{c}}} {{{ret}}}[self {{{params.join(" ")}}}];
+{{{c}}} `}
+
+"""
end
end
+
+redef class Text
+ # Nit equivalent to this type
+ private fun objc_to_nit_type: String
+ do
+ var types = sys.objc_to_nit_types
+
+ if types.has_key(self) then
+ return types[self]
+ else
+ return to_s
+ end
+ end
+end
+
+redef class Property
+ private fun comment_str: String do if is_commented then
+ return "#"
+ else return ""
+end
class ObjcModel
# All analyzed classes
var classes = new Array[ObjcClass]
+
+ # Is `objc_type` known by this model?
+ fun knows_type(objc_type: Text): Bool
+ do
+ for c in classes do
+ if c.name == objc_type then return true
+ end
+
+ return imported_types.has(objc_type)
+ end
+
+ # Are all types in the signature or `method` known by this model?
+ fun knows_all_types(method: ObjcMethod): Bool
+ do
+ for param in method.params do
+ if param.is_single then break
+ if not knows_type(param.return_type) then return false
+ end
+
+ var r = method.return_type
+ return r == "void" or r == "id" or knows_type(r)
+ end
+
+ # Objective-C types available in imported modules
+ #
+ # TODO seach in existing wrappers
+ private var imported_types: Array[String] = ["NSObject", "NSString"]
end
# Objective-C class
# Return type as a `String`
var return_type: String is noinit, writable
+
+ # Does this method look like a constructor/method?
+ fun is_init: Bool do return params.first.name.has_prefix("init")
end
# Attribute of an `ObjcClass`
var param = new Param
param.variable_name = n_right.collect_text
param.name = n_left.collect_text
- param.return_type = n_parameter_type.to_type
+
+ var n_type = n_parameter_type_in_par
+ param.return_type = if n_type != null then
+ n_type.n_parameter_type.to_type
+ else "NSObject"
+
return param
end
end
var opt_help = new OptionBool("Show this help message", "-h", "--help")
var opts = new OptionContext
-opts.add_option(opt_help, opt_output)
+opts.add_option(opt_help, opt_output, opt_init_as_methods)
opts.parse(args)
if opts.errors.not_empty or opts.rest.is_empty or opt_help.value then
end
var v = new ObjcVisitor
-var g = new CodeGenerator
for arg in opts.rest do
# Read input
v.enter_visit root
end
-g.generate v.model.classes
+var g = new CodeGenerator(v.model)
+g.generate
android_api_target 10
end
-import mnit_android
+import mnit::android
import android::audio
import android::vibration
import android::landscape
# GNU/Linux client with config saved to `config.json`
module linux_client
-import mnit_linux
+import mnit::linux
import linux::audio
import json::serialization
# See the License for the specific language governing permissions and
# limitations under the License.
-# Implementation of circular lists
+# Example of an implementation of circular lists
# This example shows the usage of generics and somewhat a specialisation of collections.
module circular_list
# See the License for the specific language governing permissions and
# limitations under the License.
-# This module provide a simple wall clock.
-# It is an example of getters and setters.
+# Example of a simple wall clock class with generalized getters and setters.
+#
# A beefed-up module is available in clock_more
module clock
# See the License for the specific language governing permissions and
# limitations under the License.
-# This module beef up the clock module by allowing a clock to be comparable.
-# It show the usage of class refinement
+# A beefed up example of the `clock` module that show class refinement.
+#
+# It refines clock to make them comparable.
module clock_more
import clock
# See the License for the specific language governing permissions and
# limitations under the License.
-# A simple exemple of refinement where a method is added to the integer class.
+# Simple example of refinement where a method is added to the integer class.
module fibonacci
redef class Int
module game_logic
-import mnit_android
+import mnit::android
import android::sensors
class Ball
import dino
-import mnit_android
+import mnit::android
import android::portrait
import android::vibration
import dino
-import mnit_linux
+import mnit::linux
redef class ImageSet
redef fun start_over_path do return "images/play_again.png"
+++ /dev/null
-all:
- mkdir -p bin/
- ../../bin/nitc --dir bin src/nitcorn_hello_world.nit src/file_server_on_port_80.nit
-
-xymus.net:
- mkdir -p bin/
- ../../bin/nitc --dir bin/ -I ../../contrib/tnitter/src/ -I ../../contrib/benitlux/src/ -I ../../contrib/opportunity/src/ src/xymus_net.nit
# See the License for the specific language governing permissions and
# limitations under the License.
-# How to print arguments of the command line.
+# Example of how to print the arguments of the command line.
module print_arguments
for a in args do
# See the License for the specific language governing permissions and
# limitations under the License.
-# A procedural program (without explicit class definition).
+# Example of a procedural program (without explicit class definition).
# This program manipulates arrays of integers.
module procedural_array
-# Rosetta Code
+# Various tasks from Rosetta Code
This are Nit programs that solve some tasks from the [Rosetta Code] project.
--- /dev/null
+A multi-platform shoot'em up in space
+
+This unfinished game is an example of the use of `mnit` and `scene2d` to develop a simple multi-platform game.
module shoot_android
import shoot
-import mnit_android
+import mnit::android
super
module shoot_linux
import shoot
-import mnit_linux
+import mnit::linux
super
-Simple toolkit for artificial intelligence
+Simple library for basic artificial intelligence algorithms
This library provides some frameworks for basic algorithms used for artificial intelligence.
It offers simple generic classes to extends.
# Pointer and hardware key events
module input_events
-import mnit_input
+import mnit::input
import android
in "C header" `{
# You are allowed to redistribute it and sell it, alone or is a part of
# another product.
-# Cartesian products on heterogeneous collections.
+# Memory-efficient Cartesian products on heterogeneous collections.
#
# This module is a proof-of-concept to propose memory-efficient views on collections.
#
#
# However, in Nit, there in no native *tuple* type.
# So we need a first building block, a pair.
+module cartesian
# A simple read-only pair of two elements `e` and `f`.
class Pair[E, F]
# You are allowed to redistribute it and sell it, alone or is a part of
# another product.
-# Cartesian products, combinations and permutation on collections.
+# Memory-efficient Cartesian products, combinations and permutation on collections.
#
# This module offers memory-efficient views on combinatoric collections.
# Methods of the views create objects only when needed.
-# Nit Core Library
+# Nit common library of core classes and methods
Core classes and methods used by default by Nit programs and libraries.
# Returns `self` as a String of `length`.
fun to_s_with_length(length: Int): String is abstract
+
+ # Returns `self` as a String with `bytelen` and `length` set
+ #
+ # SEE: `abstract_text::Text` for more infos on the difference
+ # between `Text::bytelen` and `Text::length`
+ fun to_s_full(bytelen, unilen: Int): String is abstract
end
redef class NativeArray[E]
do
written = true
if bytelen == 0 then items = new NativeString(1)
- return new FlatString.with_infos(items, bytelen, 0, bytelen - 1)
+ return new FlatString.full(items, bytelen, 0, bytelen - 1, length)
end
redef fun to_cstring
redef fun times(repeats)
do
- var x = new FlatString.with_infos(items, bytelen, 0, bytelen - 1)
+ var x = new FlatString.full(items, bytelen, 0, bytelen - 1, length)
for i in [1 .. repeats[ do
append(x)
end
return str
end
+ redef fun to_s_full(bytelen, unilen) do
+ return new FlatString.full(self, bytelen, 0, bytelen - 1, unilen)
+ end
+
# Returns `self` as a new String.
redef fun to_s_with_copy: FlatString
do
var ns = new NativeString(nlen + 1)
mits.copy_to(ns, mlen, mifrom, 0)
sits.copy_to(ns, slen, sifrom, mlen)
- return ns.to_s_with_length(nlen)
+ return new FlatString.full(ns, nlen, 0, nlen - 1, length + s.length)
else if s isa Concat then
var sl = s.left
var sllen = sl.bytelen
# You are allowed to redistribute it and sell it, alone or is a part of
# another product.
-# Automatic derivable implementation of standard basic methods.
+# Automatic derivable implementations of standard basic methods.
#
# This module introduce `Derivable` as the main interface to implement (or auto-implement) and
# provides additional mixin-interfaces with specific default behavior of standard basic methods based
import glesv2
import egl
-import mnit_linux::sdl
+import sdl
import x11
if "NIT_TESTING".environ == "true" then exit(0)
# Get the config value for `key`
#
- # REQUIRE: `has_key(key)`
- #
# var config = new ConfigTree("config.ini")
# assert config["goo"] == "goo"
# assert config["foo.bar"] == "foobar"
# assert config["foo.baz"] == "foobaz"
- fun [](key: String): String do
- if not has_key(key) then
- print "error: config key `{key}` not found"
- abort
- end
- var node = get_node(key).as(not null)
- if node.value == null then
- print "error: config key `{key}` has no value"
- abort
- end
- return node.value.as(not null)
+ # assert config["fail.fail"] == null
+ fun [](key: String): nullable String do
+ var node = get_node(key)
+ if node == null then return null
+ return node.value
end
# Get the config values under `key`
#
- # REQUIRE: `has_key(key)`
- #
# var config = new ConfigTree("config.ini")
# var values = config.at("foo")
# assert values.has_key("bar")
# assert values.has_key("baz")
# assert not values.has_key("goo")
- fun at(key: String): Map[String, String] do
- if not has_key(key) then
- print "error: config key `{key}` not found"
- abort
- end
+ #
+ # Return null if the key does not exists.
+ #
+ # assert config.at("fail.fail") == null
+ fun at(key: String): nullable Map[String, String] do
+ var node = get_node(key)
+ if node == null then return null
var map = new HashMap[String, String]
- var node = get_node(key).as(not null)
for k, child in node.children do
if child.value == null then continue
map[k] = child.value.to_s
private fun get_node(key: String): nullable ConfigNode do
var parts = key.split(".").reversed
var node = get_root(parts.pop)
- while not parts.is_empty do
+ while node != null and not parts.is_empty do
node = node.get_child(parts.pop)
end
return node
module audio
import app::audio
-import mnit_linux::linux_assets
+import mnit::linux::linux_assets
# Simple audio asset
redef class Sound
# limitations under the License.
# Android support for MNit
-module mnit_android
+module android
import android_app
import android_opengles1
"""
import mnit
-import android
import mnit::opengles1
+import ::android
intrude import ::android::input_events
in "C" `{
module android_opengles1 is ldflags "-lEGL -lGLESv1_CM"
import android_app
-import android
in "C" `{
#include <android_native_app_glue.h>
module assets
import mnit_app
-import mnit_display
+import mnit::display
# General asset
interface Asset
# limitations under the License.
# Defines abstract display classes
-module mnit_display
+module display
-import mnit_input
+import mnit::input
# Any class with a size
interface Sized
# Defines abstract classes for user and general inputs to the application.
# Implemented independantly for each platforms and technologies.
-module mnit_input
+module input
# Input to the App, propagated through `App::input`.
interface InputEvent
# limitations under the License.
# Linux support for MNit
-module mnit_linux
+module linux
import linux_app
import linux_opengles1
import mnit
import sdl
import linux_opengles1
-import linux
+import ::linux
in "C" `{
#include <EGL/egl.h>
# See the License for the specific language governing permissions and
# limitations under the License.
-# General module for cross-compatibility between multiple platforms
+# Multiplatform game framework for Nit
module mnit
import mnit_app
module mnit_app
import ::app
-import mnit_display
+import mnit::display
# An App instance serves as base to every Mnit projects.
#
# OpenGL ES1 general support (most of it)
module opengles1 is pkgconfig("glesv1_cm", "egl")
-import mnit_display
+import mnit::display
in "C header" `{
#include <EGL/egl.h>
# Manage images that are tileset or glyphset (for bitmap fonts)
module tileset
-import mnit_display
+import mnit::display
# Efficiently retrieve tiles in a big image
class TileSet
--- /dev/null
+all:
+ mkdir -p bin/
+ ../../bin/nitc --dir bin src/nitcorn_hello_world.nit src/file_server_on_port_80.nit
+
+xymus.net:
+ mkdir -p bin/
+ ../../bin/nitc --dir bin/ src/xymus_net.nit
# Use actions defined by contribs
import tnitter
-import benitlux_controller
-import opportunity_controller
+import benitlux::benitlux_controller
+import opportunity::opportunity_controller
# Header for the whole site
class MasterHeader
# See the License for the specific language governing permissions and
# limitations under the License.
-# management and display of ordered trees
+# Manipulation and presentation of ordered trees.
module ordered_tree
# Generic structure to manage and display an ordered tree
# See the License for the specific language governing permissions and
# limitations under the License.
-# 2D management of game elements
+# Framework for 2D management of game elements
#
# TODO: collision framework (with quad tree?)
module scene2d
ldflags(exec("sdl-config", "--libs"), "-lSDL_image -lSDL_ttf")
end
-import mnit_display
+import mnit::display
import c
in "C header" `{
# You are allowed to redistribute it and sell it, alone or is a part of
# another product.
-# Symbol classes
-# FIXME: Should be deprecated soon
+# Library for simple interning of strings
module symbol
redef class String
module astbuilder
intrude import semantize::typing
-intrude import semantize::literal
-intrude import semantize::parser
+intrude import literal
+intrude import parser
intrude import semantize::scope
# General factory to build semantic nodes in the AST of expressions
var native_mtype = mmodule.native_string_type
var nat = self.new_var(native_mtype)
self.add("{nat} = \"{string.escape_to_c}\";")
- var length = self.int_instance(string.bytelen)
- self.add("{res} = {self.send(self.get_property("to_s_with_length", native_mtype), [nat, length]).as(not null)};")
+ var bytelen = self.int_instance(string.bytelen)
+ var unilen = self.int_instance(string.length)
+ self.add("{res} = {self.send(self.get_property("to_s_full", native_mtype), [nat, bytelen, unilen]).as(not null)};")
self.add("{name} = {res};")
self.add("\}")
return res
fun string_instance(txt: String): Instance
do
var nat = native_string_instance(txt)
- var res = self.send(self.force_get_primitive_method("to_s_with_length", nat.mtype), [nat, self.int_instance(txt.bytelen)])
+ var res = self.send(self.force_get_primitive_method("to_s_full", nat.mtype), [nat, self.int_instance(txt.bytelen), self.int_instance(txt.length)])
assert res != null
return res
end
return res.first
end
+ # Search groups named `name` from paths `lookpaths`.
+ private fun search_group_in_paths(name: String, lookpaths: Collection[String]): ArraySet[MGroup]
+ do
+ var res = new ArraySet[MGroup]
+ for dirname in lookpaths do
+ # try a single group directory
+ var mg = get_mgroup(dirname/name)
+ if mg != null then
+ res.add mg
+ end
+ end
+ return res
+ end
+
# Cache for `identify_file` by realpath
private var identified_files_by_path = new HashMap[String, nullable ModulePath]
var mgroup
if parent == null then
# no parent, thus new project
- var namekey = "project.name"
- if ini != null and ini.has_key(namekey) then pn = ini[namekey]
+ if ini != null then pn = ini["project.name"] or else pn
var mproject = new MProject(pn, model)
mgroup = new MGroup(pn, mproject, null) # same name for the root group
mproject.root = mgroup
# This method handles qualified names as used in `AModuleName`.
fun seach_module_by_amodule_name(n_name: AModuleName, mgroup: nullable MGroup): nullable ModulePath
do
- if n_name.n_quad != null then mgroup = null # Start from top level
- for grp in n_name.n_path do
- var path = search_mmodule_by_name(grp, mgroup, grp.text)
- if path == null then return null # Forward error
- mgroup = path.mgroup
- end
var mod_name = n_name.n_id.text
- return search_mmodule_by_name(n_name, mgroup, mod_name)
+
+ # If a quad is given, we ignore the starting group (go from path)
+ if n_name.n_quad != null then mgroup = null
+
+ # If name not qualified, just search the name
+ if n_name.n_path.is_empty then
+ # Fast search if no n_path
+ return search_mmodule_by_name(n_name, mgroup, mod_name)
+ end
+
+ # If qualified and in a group
+ if mgroup != null then
+ # First search in the project
+ var r = mgroup.mproject.root
+ assert r != null
+ scan_group(r)
+ # Get all modules with the final name
+ var res = r.mmodule_paths_by_name(mod_name)
+ # Filter out the name that does not match the qualifiers
+ res = [for x in res do if match_amodulename(n_name, x) then x]
+ if res.not_empty then
+ if res.length > 1 then
+ error(n_name, "Error: conflicting module files for `{mod_name}`: `{res.join(",")}`")
+ end
+ return res.first
+ end
+ end
+
+ # If no module yet, then assume that the first element of the path
+ # Is to be searched in the path.
+ var root_name = n_name.n_path.first.text
+ var roots = search_group_in_paths(root_name, paths)
+ if roots.is_empty then
+ error(n_name, "Error: cannot find `{root_name}`. Tried: {paths.join(", ")}.")
+ return null
+ end
+
+ var res = new ArraySet[ModulePath]
+ for r in roots do
+ # Then, for each root, collect modules that matches the qualifiers
+ scan_group(r)
+ var root_res = r.mmodule_paths_by_name(mod_name)
+ for x in root_res do if match_amodulename(n_name, x) then res.add x
+ end
+ if res.not_empty then
+ if res.length > 1 then
+ error(n_name, "Error: conflicting module files for `{mod_name}`: `{res.join(",")}`")
+ end
+ return res.first
+ end
+ # If still nothing, just call a basic search that will fail and will produce an error message
+ error(n_name, "Error: cannot find module `{mod_name}` from `{root_name}`. Tried: {paths.join(", ")}.")
+ return null
+ end
+
+ # Is elements of `n_name` correspond to the group nesting of `m`?
+ #
+ # Basically it check that `bar::foo` matches `bar/foo.nit` and `bar/baz/foo.nit`
+ # but not `baz/foo.nit` nor `foo/bar.nit`
+ #
+ # Is used by `seach_module_by_amodule_name` to validate qualified names.
+ private fun match_amodulename(n_name: AModuleName, m: ModulePath): Bool
+ do
+ var g: nullable MGroup = m.mgroup
+ for grp in n_name.n_path.reverse_iterator do
+ while g != null and grp.text != g.name do
+ g = g.parent
+ end
+ end
+ return g != null
end
# Analyze the module importation and fill the module_importation_hierarchy
--- /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.
+
+# Basic catalog generator for Nit projects
+#
+# See: <http://nitlanguage.org/catalog/>
+#
+# The tool scans projects and generates the HTML files of a catalog.
+#
+# ## Features
+#
+# * [X] scan projects and their `.ini`
+# * [X] generate lists of projects
+# * [X] generate a page per project with the readme and most metadata
+# * [ ] link/include/be included in the documentation
+# * [ ] propose `related projects`
+# * [ ] show directory content (a la nitls)
+# * [X] gather git information from the working directory
+# * [ ] gather git information from the repository
+# * [ ] gather project information from github
+# * [ ] gather people information from github
+# * [ ] reify people
+# * [ ] separate information gathering from rendering
+# * [ ] move up information gathering in (existing or new) service modules
+# * [ ] add command line options
+# * [ ] harden HTML (escaping, path injection, etc)
+# * [ ] nitcorn server with RESTful API
+#
+# ## Issues and limitations
+#
+# The tool works likee the other tools and expects to find valid Nit source code in the directories
+#
+# * cruft and temporary files will be collected
+# * missing source file (e.g. not yet generated by nitcc) will make information
+# incomplete (e.g. invalid module thus partial dependency and metrics)
+#
+# How to use the tool as the basis of a Nit code archive on the web usable with a package manager is not clear.
+module nitcatalog
+
+import loader # Scan&load projects, groups and modules
+import doc::doc_down # Display mdoc
+import md5 # To get gravatar images
+import counter # For statistics
+import modelize # To process and count classes and methods
+
+redef class MProject
+ # Return the associated metadata from the `ini`, if any
+ fun metadata(key: String): nullable String
+ do
+ var ini = self.ini
+ if ini == null then return null
+ return ini[key]
+ end
+
+ # The list of maintainers
+ var maintainers = new Array[String]
+
+ # The list of contributors
+ var contributors = new Array[String]
+
+ # The date of the most recent commit
+ var last_date: nullable String = null
+
+ # The date of the oldest commit
+ var first_date: nullable String = null
+end
+
+# A HTML page in a catalog
+#
+# This is just a template with the header pre-filled and the footer injected at rendering.
+# Therefore, once instantiated, the content can just be added to it.
+class CatalogPage
+ super Template
+
+ # Placeholder to include additional things before the `</head>`.
+ var more_head = new Template
+
+ redef init
+ do
+ add """
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
+ <link rel="stylesheet" media="all" href="style.css">
+"""
+ add more_head
+
+ add """
+</head>
+<body>
+<div class='container-fluid'>
+ <div class='row'>
+ <nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>
+ <div class='container-fluid'>
+ <div class='navbar-header'>
+ <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='#topmenu-collapse'>
+ <span class='sr-only'>Toggle menu</span>
+ <span class='icon-bar'></span>
+ <span class='icon-bar'></span>
+ <span class='icon-bar'></span>
+ </button>
+ <span class='navbar-brand'><a href="http://nitlanguage.org/">Nitlanguage.org</a></span>
+ </div>
+ <div class='collapse navbar-collapse' id='topmenu-collapse'>
+ <ul class='nav navbar-nav'>
+ <li><a href="index.html">Catalog</a></li>
+ </ul>
+ </div>
+ </div>
+ </nav>
+ </div>
+"""
+ end
+
+ redef fun rendering
+ do
+ add """
+</div> <!-- container-fluid -->
+<script src='https://code.jquery.com/jquery-latest.min.js'></script>
+<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js'></script>
+<script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js'></script>
+</body>
+</html>
+"""
+ end
+end
+
+redef class Int
+ # Returns `log(self+1)`. Used to compute score of projects
+ fun score: Float do return (self+1).to_f.log
+end
+
+# The main class of the calatog generator that has the knowledge
+class Catalog
+
+ # The modelbuilder
+ # used to access the files and count source lines of code
+ var modelbuilder: ModelBuilder
+
+ # Projects by tag
+ var tag2proj = new MultiHashMap[String, MProject]
+
+ # Projects by category
+ var cat2proj = new MultiHashMap[String, MProject]
+
+ # Projects by maintainer
+ var maint2proj = new MultiHashMap[String, MProject]
+
+ # Projects by contributors
+ var contrib2proj = new MultiHashMap[String, MProject]
+
+ # Dependency between projects
+ var deps = new POSet[MProject]
+
+ # Number of modules by project
+ var mmodules = new Counter[MProject]
+
+ # Number of classes by project
+ var mclasses = new Counter[MProject]
+
+ # Number of methods by project
+ var mmethods = new Counter[MProject]
+
+ # Number of line of code by project
+ var loc = new Counter[MProject]
+
+ # Number of commits by project
+ var commits = new Counter[MProject]
+
+ # Score by project
+ #
+ # The score is loosely computed using other metrics
+ var score = new Counter[MProject]
+
+ # Scan, register and add a contributor to a project
+ fun add_contrib(person: String, mproject: MProject, res: Template)
+ do
+ var projs = contrib2proj[person]
+ if not projs.has(mproject) then projs.add mproject
+ var name = person
+ var email = null
+ var page = null
+
+ # Regular expressions are broken, need to investigate.
+ # So split manually.
+ #
+ #var re = "([^<(]*?)(<([^>]*?)>)?(\\((.*)\\))?".to_re
+ #var m = (person+" ").search(re)
+ #print "{person}: `{m or else "?"}` `{m[1] or else "?"}` `{m[3] or else "?"}` `{m[5] or else "?"}`"
+ do
+ var sp1 = person.split_once_on("<")
+ if sp1.length < 2 then
+ break
+ end
+ var sp2 = sp1.last.split_once_on(">")
+ if sp2.length < 2 then
+ break
+ end
+ name = sp1.first.trim
+ email = sp2.first.trim
+ var sp3 = sp2.last.split_once_on("(")
+ if sp3.length < 2 then
+ break
+ end
+ var sp4 = sp3.last.split_once_on(")")
+ if sp4.length < 2 then
+ break
+ end
+ page = sp4.first.trim
+ end
+
+ var e = name.html_escape
+ res.add "<li>"
+ if page != null then
+ res.add "<a href=\"{page.html_escape}\">"
+ end
+ if email != null then
+ # TODO get more things from github by using the email as a key
+ # "https://api.github.com/search/users?q={email}+in:email"
+ var md5 = email.md5.to_lower
+ res.add "<img src=\"https://secure.gravatar.com/avatar/{md5}?size=20&default=retro\"> "
+ end
+ res.add "{e}"
+ if page != null then res.add "</a>"
+ res.add "</li>"
+ end
+
+
+ # Compute information and generate a full HTML page for a project
+ fun project_page(mproject: MProject): Writable
+ do
+ var res = new CatalogPage
+ var score = score[mproject].to_f
+ var name = mproject.name.html_escape
+ res.more_head.add """<title>{{{name}}}</title>"""
+
+ res.add """
+<div class="content">
+<h1 class="package-name">{{{name}}}</h1>
+"""
+ var mdoc = mproject.mdoc_or_fallback
+ if mdoc != null then
+ score += 100.0
+ res.add mdoc.html_documentation
+ score += mdoc.content.length.score
+ end
+ res.add """
+</div>
+<div class="sidebar">
+<ul class="box">
+"""
+ var homepage = mproject.metadata("upstream.homepage")
+ if homepage != null then
+ score += 5.0
+ var e = homepage.html_escape
+ res.add "<li><a href=\"{e}\">{e}</a></li>\n"
+ end
+ var maintainer = mproject.metadata("project.maintainer")
+ if maintainer != null then
+ score += 5.0
+ add_contrib(maintainer, mproject, res)
+ mproject.maintainers.add maintainer
+ var projs = maint2proj[maintainer]
+ if not projs.has(mproject) then projs.add mproject
+ end
+ var license = mproject.metadata("project.license")
+ if license != null then
+ score += 5.0
+ var e = license.html_escape
+ res.add "<li><a href=\"http://opensource.org/licenses/{e}\">{e}</a> license</li>\n"
+ end
+ res.add "</ul>\n"
+
+ res.add "<h3>Source Code</h3>\n<ul class=\"box\">\n"
+ var browse = mproject.metadata("upstream.browse")
+ if browse != null then
+ score += 5.0
+ var e = browse.html_escape
+ res.add "<li><a href=\"{e}\">{e}</a></li>\n"
+ end
+ var git = mproject.metadata("upstream.git")
+ if git != null then
+ var e = git.html_escape
+ res.add "<li><tt>{e}</tt></li>\n"
+ end
+ var last_date = mproject.last_date
+ if last_date != null then
+ var e = last_date.html_escape
+ res.add "<li>most recent commit: {e}</li>\n"
+ end
+ var first_date = mproject.first_date
+ if first_date != null then
+ var e = first_date.html_escape
+ res.add "<li>oldest commit: {e}</li>\n"
+ end
+ var commits = commits[mproject]
+ if commits != 0 then
+ res.add "<li>{commits} commits</li>\n"
+ end
+ res.add "</ul>\n"
+
+ res.add "<h3>Tags</h3>\n"
+ var tags = mproject.metadata("project.tags")
+ var ts2 = new Array[String]
+ var cat = null
+ if tags != null then
+ var ts = tags.split(",")
+ for t in ts do
+ t = t.trim
+ if t == "" then continue
+ if cat == null then cat = t
+ tag2proj[t].add mproject
+ t = t.html_escape
+ ts2.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+ end
+ res.add_list(ts2, ", ", ", ")
+ end
+ if ts2.is_empty then
+ var t = "none"
+ cat = t
+ tag2proj[t].add mproject
+ res.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+ end
+ if cat != null then cat2proj[cat].add mproject
+ score += ts2.length.score
+
+ var reqs = deps[mproject].greaters.to_a
+ reqs.remove(mproject)
+ alpha_comparator.sort(reqs)
+ res.add "<h3>Requirements</h3>\n"
+ if reqs.is_empty then
+ res.add "none"
+ else
+ var list = new Array[String]
+ for r in reqs do
+ var direct = deps.has_direct_edge(mproject, r)
+ var s = "<a href=\"{r}.html\">"
+ if direct then s += "<strong>"
+ s += r.to_s
+ if direct then s += "</strong>"
+ s += "</a>"
+ list.add s
+ end
+ res.add_list(list, ", ", " and ")
+ end
+
+ reqs = deps[mproject].smallers.to_a
+ reqs.remove(mproject)
+ alpha_comparator.sort(reqs)
+ res.add "<h3>Clients</h3>\n"
+ if reqs.is_empty then
+ res.add "none"
+ else
+ var list = new Array[String]
+ for r in reqs do
+ var direct = deps.has_direct_edge(r, mproject)
+ var s = "<a href=\"{r}.html\">"
+ if direct then s += "<strong>"
+ s += r.to_s
+ if direct then s += "</strong>"
+ s += "</a>"
+ list.add s
+ end
+ res.add_list(list, ", ", " and ")
+ end
+
+ score += deps[mproject].greaters.length.score
+ score += deps[mproject].direct_greaters.length.score
+ score += deps[mproject].smallers.length.score
+ score += deps[mproject].direct_smallers.length.score
+
+ var contributors = mproject.contributors
+ if not contributors.is_empty then
+ res.add "<h3>Contributors</h3>\n<ul class=\"box\">"
+ for c in contributors do
+ add_contrib(c, mproject, res)
+ end
+ res.add "</ul>"
+ end
+ score += contributors.length.to_f
+
+ var mmodules = 0
+ var mclasses = 0
+ var mmethods = 0
+ var loc = 0
+ for g in mproject.mgroups do
+ mmodules += g.module_paths.length
+ for m in g.mmodules do
+ var am = modelbuilder.mmodule2node(m)
+ if am != null then
+ var file = am.location.file
+ if file != null then
+ loc += file.line_starts.length - 1
+ end
+ end
+ for cd in m.mclassdefs do
+ mclasses += 1
+ for pd in cd.mpropdefs do
+ if not pd isa MMethodDef then continue
+ mmethods += 1
+ end
+ end
+ end
+ end
+ self.mmodules[mproject] = mmodules
+ self.mclasses[mproject] = mclasses
+ self.mmethods[mproject] = mmethods
+ self.loc[mproject] = loc
+
+ #score += mmodules.score
+ score += mclasses.score
+ score += mmethods.score
+ score += loc.score
+
+ res.add """
+<h3>Stats</h3>
+<ul class="box">
+<li>{{{mmodules}}} modules</li>
+<li>{{{mclasses}}} classes</li>
+<li>{{{mmethods}}} methods</li>
+<li>{{{loc}}} lines of code</li>
+</ul>
+"""
+
+ res.add """
+</div>
+"""
+ self.score[mproject] = score.to_i
+
+ return res
+ end
+
+ # Return a short HTML sequence for a project
+ #
+ # Intended to use in lists.
+ fun li_project(p: MProject): String
+ do
+ var res = ""
+ var f = "{p.name}.html"
+ res += "<a href=\"{f}\">{p}</a>"
+ var d = p.mdoc_or_fallback
+ if d != null then res += " - {d.html_synopsis.write_to_string}"
+ return res
+ end
+
+ # List projects by group.
+ #
+ # For each key of the `map` a `<h3>` is generated.
+ # Each project is then listed.
+ #
+ # The list of keys is generated first to allow fast access to the correct `<h3>`.
+ # `id_prefix` is used to give an id to the `<h3>` element.
+ fun list_by(map: MultiHashMap[String, MProject], id_prefix: String): Template
+ do
+ var res = new Template
+ var keys = map.keys.to_a
+ alpha_comparator.sort(keys)
+ var list = [for x in keys do "<a href=\"#{id_prefix}{x.html_escape}\">{x.html_escape}</a>"]
+ res.add_list(list, ", ", " and ")
+
+ for k in keys do
+ var projs = map[k].to_a
+ alpha_comparator.sort(projs)
+ var e = k.html_escape
+ res.add "<h3 id=\"{id_prefix}{e}\">{e} ({projs.length})</h3>\n<ul>\n"
+ for p in projs do
+ res.add "<li>"
+ res.add li_project(p)
+ res.add "</li>"
+ end
+ res.add "</ul>"
+ end
+ return res
+ end
+
+ # List the 10 best projects from `cpt`
+ fun list_best(cpt: Counter[MProject]): Template
+ do
+ var res = new Template
+ res.add "<ul>"
+ var best = cpt.sort
+ for i in [1..10] do
+ if i > best.length then break
+ var p = best[best.length-i]
+ res.add "<li>"
+ res.add li_project(p)
+ # res.add " ({cpt[p]})"
+ res.add "</li>"
+ end
+ res.add "</ul>"
+ return res
+ end
+
+ # Collect more information on a project using the `git` tool.
+ fun git_info(mproject: MProject)
+ do
+ var ini = mproject.ini
+ if ini == null then return
+
+ # TODO use real git info
+ #var repo = ini.get_or_null("upstream.git")
+ #var branch = ini.get_or_null("upstream.git.branch")
+ #var directory = ini.get_or_null("upstream.git.directory")
+
+ var dirpath = mproject.root.filepath
+ if dirpath == null then return
+
+ # Collect commits info
+ var res = git_run("log", "--no-merges", "--follow", "--pretty=tformat:%ad;%aN <%aE>", "--", dirpath)
+ var contributors = new Counter[String]
+ var commits = res.split("\n")
+ if commits.not_empty and commits.last == "" then commits.pop
+ self.commits[mproject] = commits.length
+ for l in commits do
+ var s = l.split_once_on(';')
+ if s.length != 2 or s.last == "" then continue
+
+ # Collect date of last and first commit
+ if mproject.last_date == null then mproject.last_date = s.first
+ mproject.first_date = s.first
+
+ # Count contributors
+ contributors.inc(s.last)
+ end
+ for c in contributors.sort.reverse_iterator do
+ mproject.contributors.add c
+ end
+
+ end
+
+ # Produce a HTML table containig information on the projects
+ #
+ # `project_page` must have been called before so that information is computed.
+ fun table_projects(mprojects: Array[MProject]): Template
+ do
+ alpha_comparator.sort(mprojects)
+ var res = new Template
+ res.add "<table data-toggle=\"table\" data-sort-name=\"name\" data-sort-order=\"desc\" width=\"100%\">\n"
+ res.add "<thead><tr>\n"
+ res.add "<th data-field=\"name\" data-sortable=\"true\">name</th>\n"
+ res.add "<th data-field=\"maint\" data-sortable=\"true\">maint</th>\n"
+ res.add "<th data-field=\"contrib\" data-sortable=\"true\">contrib</th>\n"
+ res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
+ res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
+ res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
+ res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
+ res.add "<th data-field=\"mod\" data-sortable=\"true\">modules</th>\n"
+ res.add "<th data-field=\"cla\" data-sortable=\"true\">classes</th>\n"
+ res.add "<th data-field=\"met\" data-sortable=\"true\">methods</th>\n"
+ res.add "<th data-field=\"loc\" data-sortable=\"true\">lines</th>\n"
+ res.add "<th data-field=\"score\" data-sortable=\"true\">score</th>\n"
+ res.add "</tr></thead>"
+ for p in mprojects do
+ res.add "<tr>"
+ res.add "<td><a href=\"{p.name}.html\">{p.name}</a></td>"
+ var maint = "?"
+ if p.maintainers.not_empty then maint = p.maintainers.first
+ res.add "<td>{maint}</td>"
+ res.add "<td>{p.contributors.length}</td>"
+ res.add "<td>{deps[p].greaters.length-1}</td>"
+ res.add "<td>{deps[p].direct_greaters.length}</td>"
+ res.add "<td>{deps[p].smallers.length-1}</td>"
+ res.add "<td>{deps[p].direct_smallers.length}</td>"
+ res.add "<td>{mmodules[p]}</td>"
+ res.add "<td>{mclasses[p]}</td>"
+ res.add "<td>{mmethods[p]}</td>"
+ res.add "<td>{loc[p]}</td>"
+ res.add "<td>{score[p]}</td>"
+ res.add "</tr>\n"
+ end
+ res.add "</table>\n"
+ return res
+ end
+end
+
+# Execute a git command and return the result
+fun git_run(command: String...): String
+do
+ # print "git {command.join(" ")}"
+ var p = new ProcessReader("git", command...)
+ var res = p.read_all
+ p.close
+ p.wait
+ return res
+end
+
+var model = new Model
+var tc = new ToolContext
+
+tc.process_options(sys.args)
+tc.keep_going = true
+
+var modelbuilder = new ModelBuilder(model, tc)
+var catalog = new Catalog(modelbuilder)
+
+# Get files or groups
+for a in tc.option_context.rest do
+ modelbuilder.get_mgroup(a)
+ modelbuilder.identify_file(a)
+end
+
+# Scan projects and compute information
+for p in model.mprojects do
+ var g = p.root
+ assert g != null
+ modelbuilder.scan_group(g)
+
+ # Load the module to process importation information
+ modelbuilder.parse_group(g)
+
+ catalog.deps.add_node(p)
+ for gg in p.mgroups do for m in gg.mmodules do
+ for im in m.in_importation.direct_greaters do
+ var ip = im.mproject
+ if ip == null or ip == p then continue
+ catalog.deps.add_edge(p, ip)
+ end
+ end
+
+ catalog.git_info(p)
+end
+
+# Run phases to modelize classes and properties (so we can count them)
+#modelbuilder.run_phases
+
+var out = "out"
+out.mkdir
+
+# Generate the css (hard coded)
+var css = """
+body {
+margin-top: 15px;
+background-color: #f8f8f8;
+}
+
+a {
+color: #0D8921;
+text-decoration: none;
+}
+
+a:hover {
+color: #333;
+text-decoration: none;
+}
+
+h1 {
+font-weight: bold;
+color: #0D8921;
+font-size: 22px;
+}
+
+h2 {
+color: #6C6C6C;
+font-size: 18px;
+border-bottom: solid 3px #CCC;
+}
+
+h3 {
+color: #6C6C6C;
+font-size: 15px;
+border-bottom: solid 1px #CCC;
+}
+
+ul {
+list-style-type: square;
+}
+
+dd {
+color: #6C6C6C;
+margin-top: 1em;
+margin-bottom: 1em;
+}
+
+pre {
+border: 1px solid #CCC;
+font-family: Monospace;
+color: #2d5003;
+background-color: rgb(250, 250, 250);
+}
+
+code {
+font-family: Monospace;
+color: #2d5003;
+}
+
+footer {
+margin-top: 20px;
+}
+
+.container {
+margin: 0 auto;
+padding: 0 20px;
+}
+
+.content {
+float: left;
+margin-top: 40px;
+width: 65%;
+}
+
+.sidebar {
+float: right;
+margin-top: 40px;
+width: 30%
+}
+
+.sidebar h3 {
+color: #0D8921;
+font-size: 18px;
+border-bottom: 0px;
+}
+
+.box {
+margin: 0;
+padding: 0;
+}
+
+.box li {
+line-height: 2.5;
+white-space: nowrap;
+overflow: hidden;
+text-overflow: ellipsis;
+padding-right: 10px;
+border-bottom: 1px solid rgba(0,0,0,0.2);
+}
+"""
+css.write_to_file(out/"style.css")
+
+# PAGES
+
+for p in model.mprojects do
+ # print p
+ var f = "{p.name}.html"
+ catalog.project_page(p).write_to_file(out/f)
+end
+
+# INDEX
+
+var index = new CatalogPage
+index.more_head.add "<title>Projects in Nit</title>"
+
+index.add """
+<div class="content">
+<h1>Projects in Nit</h1>
+"""
+
+index.add "<h2>Highlighted Projects</h2>\n"
+index.add catalog.list_best(catalog.score)
+
+index.add "<h2>Most Required</h2>\n"
+var reqs = new Counter[MProject]
+for p in model.mprojects do
+ reqs[p] = catalog.deps[p].smallers.length - 1
+end
+index.add catalog.list_best(reqs)
+
+index.add "<h2>By First Tag</h2>\n"
+index.add catalog.list_by(catalog.cat2proj, "cat_")
+
+index.add "<h2>By Any Tag</h2>\n"
+index.add catalog.list_by(catalog.tag2proj, "tag_")
+
+index.add """
+</div>
+<div class="sidebar">
+<h3>Stats</h3>
+<ul class="box">
+<li>{{{model.mprojects.length}}} projects</li>
+<li>{{{catalog.maint2proj.length}}} maintainers</li>
+<li>{{{catalog.contrib2proj.length}}} contributors</li>
+<li>{{{catalog.tag2proj.length}}} tags</li>
+<li>{{{catalog.mmodules.sum}}} modules</li>
+<li>{{{catalog.mclasses.sum}}} classes</li>
+<li>{{{catalog.mmethods.sum}}} methods</li>
+<li>{{{catalog.loc.sum}}} lines of code</li>
+</ul>
+</div>
+"""
+
+index.write_to_file(out/"index.html")
+
+# PEOPLE
+
+var page = new CatalogPage
+page.more_head.add "<title>People of Nit</title>"
+page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
+page.add "<h2>By Maintainer</h2>\n"
+page.add catalog.list_by(catalog.maint2proj, "maint_")
+page.add "<h2>By Contributor</h2>\n"
+page.add catalog.list_by(catalog.contrib2proj, "contrib_")
+page.add "</div>\n"
+page.write_to_file(out/"people.html")
+
+# TABLE
+
+page = new CatalogPage
+page.more_head.add "<title>Projets of Nit</title>"
+page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
+page.add "<h2>Table of Projets</h2>\n"
+page.add catalog.table_projects(model.mprojects)
+page.add "</div>\n"
+page.write_to_file(out/"table.html")
private var parent: ANode
private var items = new Array[E]
redef fun iterator do return items.iterator
+ redef fun reverse_iterator do return items.reverse_iterator
redef fun length do return items.length
redef fun is_empty do return items.is_empty
redef fun push(e)
do
var native = v.analysis.mainmodule.native_string_type
v.add_type(native)
- var prop = v.get_method(native, "to_s_with_length")
+ var prop = v.get_method(native, "to_s_full")
v.add_monomorphic_send(native, prop)
end
end
-import project1 #alt1# import project1::module1 #alt2# import project1::module2 #alt3# import project1::fail #alt4# import project1::module4 #alt5# import project1::project2
+# 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.
+
+import project1 #alt1-6#
+#alt1#import project1::module1
+#alt2#import project1::module2
+#alt3#import project1::fail
+#alt4#import project1::module4
+#alt5#import project1::project2
+#alt6#import fail::module1
foo
--- /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.
+
+import module_0 #alt1-3#
+#alt1#import project1::module_0
+#alt2#import project1::module_01
+#alt3#import project1::module_02
--- /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.
+
+import kernel
+
+1.output
+0.output
--- /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.
+
+import module_0
--- /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.
+
+import ::module_0
--- /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.
+
+import kernel
+
+1.output
+'s'.output
+0.output
--- /dev/null
+alt/base_import2_alt1.nit:16,8--25: Error: conflicting module files for `module_0`: `./project1/module_0.nit,./project1/subdir/module_0.nit`
-alt/base_import_alt3.nit:1,8--21: Error: cannot find module `fail` from `project1`. Tried: ., ../lib/core, ../lib/core/collection, alt, ../lib, ../contrib.
+alt/base_import_alt3.nit:18,8--21: Error: cannot find module `fail` from `project1`. Tried: ., ../lib/core, ../lib/core/collection, alt, ../lib, ../contrib.
-alt/base_import_alt5.nit:1,8--25: Error: cannot find module `project2` from `project1`. Tried: ., ../lib/core, ../lib/core/collection, alt, ../lib, ../contrib.
+alt/base_import_alt5.nit:20,8--25: Error: cannot find module `project2` from `project1`. Tried: ., ../lib/core, ../lib/core/collection, alt, ../lib, ../contrib.
--- /dev/null
+alt/base_import_alt6.nit:21,8--20: Error: cannot find `fail`. Tried: ., ../lib/core, ../lib/core/collection, alt, ../lib, ../contrib.
-../lib/mnit_linux/linux_app.nit:29,16--31: Redef Error: a virtual type cannot be refined.
-../lib/mnit_linux/linux_app.nit:30,16--29: Redef Error: a virtual type cannot be refined.
+../lib/mnit/linux/linux_app.nit:29,16--31: Redef Error: a virtual type cannot be refined.
+../lib/mnit/linux/linux_app.nit:30,16--29: Redef Error: a virtual type cannot be refined.
-alt/error_needed_method_alt3.nit:48,9--13: Fatal Error: `NativeString` must have a property named `to_s_with_length`.
+alt/error_needed_method_alt3.nit:48,9--13: Fatal Error: `NativeString` must have a property named `to_s_full`.
--- /dev/null
+Usage: [OPTION]... [ARG]...
+Use --help for help
-alt/error_needed_method_alt4.nit:49,10--14: Fatal Error: `NativeString` must have a property named `to_s_with_length`.
+alt/error_needed_method_alt4.nit:49,10--14: Fatal Error: `NativeString` must have a property named `to_s_full`.
|--\e[1mmodule1\e[m (\e[33mproject1/module1.nit\e[m)
|--\e[1mmodule2\e[m (\e[33mproject1/module2.nit\e[m)
|--\e[1mmodule3\e[m (\e[33mproject1/module3.nit\e[m)
+|--\e[1mmodule_0\e[m (\e[33mproject1/module_0.nit\e[m)
+|--\e[1mmodule_01\e[m (\e[33mproject1/module_01.nit\e[m)
+|--\e[1mmodule_02\e[m (\e[33mproject1/module_02.nit\e[m)
|--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
`--subdir (\e[33mproject1/subdir\e[m)
- `--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+ |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+ `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
\e[1mproject2\e[m (\e[33mproject1/project2/project2.nit\e[m)
|--\e[1mmodule1\e[m (\e[33mproject1/module1.nit\e[m)
|--\e[1mmodule2\e[m (\e[33mproject1/module2.nit\e[m)
|--\e[1mmodule3\e[m (\e[33mproject1/module3.nit\e[m)
+|--\e[1mmodule_0\e[m (\e[33mproject1/module_0.nit\e[m)
+|--\e[1mmodule_01\e[m (\e[33mproject1/module_01.nit\e[m)
+|--\e[1mmodule_02\e[m (\e[33mproject1/module_02.nit\e[m)
|--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
`--subdir (\e[33mproject1/subdir\e[m)
- `--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+ |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+ `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
\e[1mproject2\e[m (\e[33mproject1/project2/project2.nit\e[m)
|--\e[1mmodule1\e[m (\e[33mproject1/module1.nit\e[m)
|--\e[1mmodule2\e[m (\e[33mproject1/module2.nit\e[m)
|--\e[1mmodule3\e[m (\e[33mproject1/module3.nit\e[m)
+|--\e[1mmodule_0\e[m (\e[33mproject1/module_0.nit\e[m)
+|--\e[1mmodule_01\e[m (\e[33mproject1/module_01.nit\e[m)
+|--\e[1mmodule_02\e[m (\e[33mproject1/module_02.nit\e[m)
|--\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
`--subdir (\e[33mproject1/subdir\e[m)
- `--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+ |--\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+ `--\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
project2 (\e[33mproject1/project2\e[m)
|--\e[1mfoo\e[m (\e[33mproject1/project2/foo.nit\e[m)
`--\e[1mproject2\e[m (\e[33mproject1/project2/project2.nit\e[m)
project1/\e[1mmodule2\e[m (\e[33mproject1/module2.nit\e[m)
project1/\e[1mmodule3\e[m (\e[33mproject1/module3.nit\e[m)
project1/subdir/\e[1mmodule4\e[m (\e[33mproject1/subdir/module4.nit\e[m)
+project1/\e[1mmodule_0\e[m (\e[33mproject1/module_0.nit\e[m)
+project1/subdir/\e[1mmodule_0\e[m (\e[33mproject1/subdir/module_0.nit\e[m)
+project1/\e[1mmodule_01\e[m (\e[33mproject1/module_01.nit\e[m)
+project1/\e[1mmodule_02\e[m (\e[33mproject1/module_02.nit\e[m)
project1/\e[1mproject1\e[m (\e[33mproject1/project1.nit\e[m)
project2/\e[1mproject2\e[m (\e[33mproject1/project2/project2.nit\e[m)
-../lib/mnit_linux/linux_app.nit:29,16--31: Redef Error: a virtual type cannot be refined.
-../lib/mnit_linux/linux_app.nit:30,16--29: Redef Error: a virtual type cannot be refined.
+../lib/mnit/linux/linux_app.nit:29,16--31: Redef Error: a virtual type cannot be refined.
+../lib/mnit/linux/linux_app.nit:30,16--29: Redef Error: a virtual type cannot be refined.
-../examples/nitcorn/src/xymus_net.nit:25,8--26: Error: cannot find module `benitlux_controller` from `src`. Tried: alt, ../lib, ../contrib, ../examples.
-../examples/nitcorn/src/xymus_net.nit:26,8--29: Error: cannot find module `opportunity_controller` from `src`. Tried: alt, ../lib, ../contrib, ../examples.