`nitrpg` is broken since a long time. I think https:/api.github.com actually changed twice since it broke. I don't plan on killing it yet but I moved it to its own repository until I worked again on it (or never).
See https://github.com/Morriar/nitrpg.
Signed-off-by: Alexandre Terrasa <alexandre@moz-code.org>
Pull-Request: #2755
artifacts:
paths:
- tests/errlist
- - tests/*.xml
+ - tests/*.xml*
when: always
reports:
junit: tests/*.xml
- git diff --name-only origin/master..HEAD -- "*.nit" "*.res" "README.*" | grep -v "^tests/" > list0.txt || true
- xargs nitls -pP < list0.txt > list.txt
- xargs nitunit < list.txt
+ - junit2html nitunit.xml
artifacts:
paths:
- nitunit.xml*
- xargs nitunit -v < list.txt| tee log.txt
- grep -e KO log.txt > status.txt || true
- tail -3 log.txt >> status.txt
+ - junit2html nitunit.xml
artifacts:
paths:
- nitunit.xml*
- xargs nitunit -v < list.txt| tee log.txt
- grep -e KO log.txt > status.txt || true
- tail -3 log.txt >> status.txt
+ - junit2html nitunit.xml
artifacts:
paths:
- nitunit.xml*
- src/version.nit
- src/nitc_0
+valgrind:
+ stage: more_test
+ dependencies:
+ - build_more_tools
+ script:
+ - mkdir -p valgrind.out
+ - nitc src/nitc.nit # To warm-up the cache
+ - src/valgrind.sh --callgrind-out-file=valgrind.out/nitc.nitc.out nitc src/nitc.nit -vv
+ - callgrind_annotate valgrind.out/nitc.nitc.out > valgrind.out/nitc.nitc.txt
+ - src/valgrind.sh --callgrind-out-file=valgrind.out/niti.niti.out nit -- src/nit.nit tests/base_simple3.nit -vv
+ - callgrind_annotate valgrind.out/niti.niti.out > valgrind.out/niti.niti.txt
+ artifacts:
+ paths:
+ - valgrind.out
+
build_doc:
stage: more_test
dependencies:
paths:
- nitdoc.out
+nitmetrics:
+ stage: more_test
+ dependencies:
+ - build_more_tools
+ script:
+ - nitmetrics --all --log --log-dir nitmetrics.out --dir nitmetrics.out --keep-going lib src
+ artifacts:
+ paths:
+ - nitmetrics.out
+
build_catalog:
stage: more_test
dependencies:
#
# This class provides services that ensure static typing when accessing the `config.ini` file.
class WikiConfig
- super ConfigTree
+ super IniFile
+ autoinit ini_file
+
+ # Path to this file
+ var ini_file: String
+
+ init do load_file(ini_file)
# Returns the config value at `key` or return `default` if no key was found.
protected fun value_or_default(key: String, default: String): String do
var sidebar_blocks: Array[String] is lazy do
var res = new Array[String]
if not has_key("wiki.sidebar.blocks") then return res
- for val in at("wiki.sidebar.blocks").as(not null).values do
- res.add val
+ for val in section("wiki.sidebar.blocks").as(not null).values do
+ res.add val.as(not null)
end
return res
end
# Each section can provide its own config file to customize
# appearance or behavior.
class SectionConfig
- super ConfigTree
+ super IniFile
+ autoinit ini_file
+
+ # Path to this file
+ var ini_file: String
+
+ init do load_file(ini_file)
# Returns the config value at `key` or `null` if no key was found.
private fun value_or_null(key: String): nullable String do
super Config
# Config tree used to store config options
- var ini: ConfigTree is noinit
+ var ini: IniFile is noinit
# Path to app config file
var opt_config = new OptionString("Path to config file", "--config")
redef fun parse_options(args) do
super
- ini = new ConfigTree(config_file)
+ ini = new IniFile.from_file(config_file)
end
# Default config file path
# This method returns raw json data.
# See other `load_*` methods to use more expressive types.
#
- # var api = new GithubAPI(get_github_oauth)
- # var obj = api.get("/repos/nitlang/nit")
- # assert obj isa JsonObject
- # assert obj["name"] == "nit"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var obj = api.get("/repos/nitlang/nit")
+ # assert obj isa JsonObject
+ # assert obj["name"] == "nit"
+ # ~~~
#
# Returns `null` in case of `error`.
#
- # obj = api.get("/foo/bar/baz")
- # assert obj == null
- # assert api.was_error
- # var err = api.last_error
- # assert err isa GithubError
- # assert err.name == "GithubAPIError"
- # assert err.message == "Not Found"
+ # ~~~nitish
+ # obj = api.get("/foo/bar/baz")
+ # assert obj == null
+ # assert api.was_error
+ # var err = api.last_error
+ # assert err isa GithubError
+ # assert err.name == "GithubAPIError"
+ # assert err.message == "Not Found"
+ # ~~~
fun get(path: String): nullable Serializable do
path = sanitize_uri(path)
var res = ghcurl.get_and_parse("{api_url}{path}")
#
# Loads the `User` from the API or returns `null` if the user cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var user = api.load_user("Morriar")
- # print user or else "null"
- # assert user.login == "Morriar"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var user = api.load_user("Morriar")
+ # print user or else "null"
+ # assert user.login == "Morriar"
+ # ~~~
fun load_user(login: String): nullable User do
return load_from_github("/users/{login}").as(nullable User)
end
#
# Loads the `Repo` from the API or returns `null` if the repo cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo.name == "nit"
- # assert repo.owner.login == "nitlang"
- # assert repo.default_branch == "master"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo.name == "nit"
+ # assert repo.owner.login == "nitlang"
+ # assert repo.default_branch == "master"
+ # ~~~
fun load_repo(full_name: String): nullable Repo do
return load_from_github("/repos/{full_name}").as(nullable Repo)
end
var res = new Array[Branch]
if not array isa JsonArray then return res
var deser = deserialize(array.to_json)
- if deser isa Array[Object] then return res # empty array
- return deser.as(Array[Branch])
+ if not deser isa Array[Object] then return res # empty array
+ for branch in deser do
+ if not branch isa Branch then continue
+ res.add branch
+ end
+ return res
end
# List of issues associated with their ids.
#
# Returns `null` if the branch cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var branch = api.load_branch(repo, "master")
- # assert branch.name == "master"
- # assert branch.commit isa Commit
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var branch = api.load_branch(repo, "master")
+ # assert branch.name == "master"
+ # assert branch.commit isa Commit
+ # ~~~
fun load_branch(repo: Repo, name: String): nullable Branch do
return load_from_github("/repos/{repo.full_name}/branches/{name}").as(nullable Branch)
end
#
# Returns `null` if the commit cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var commit = api.load_commit(repo, "64ce1f")
- # assert commit isa Commit
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var commit = api.load_commit(repo, "64ce1f")
+ # assert commit isa Commit
+ # ~~~
fun load_commit(repo: Repo, sha: String): nullable Commit do
return load_from_github("/repos/{repo.full_name}/commits/{sha}").as(nullable Commit)
end
#
# Returns `null` if the issue cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var issue = api.load_issue(repo, 1)
- # assert issue.title == "Doc"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var issue = api.load_issue(repo, 1)
+ # assert issue.title == "Doc"
+ # ~~~
fun load_issue(repo: Repo, number: Int): nullable Issue do
return load_from_github("/repos/{repo.full_name}/issues/{number}").as(nullable Issue)
end
#
# Returns `null` if the pull request cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var pull = api.load_pull(repo, 1)
- # assert pull.title == "Doc"
- # assert pull.user.login == "Morriar"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var pull = api.load_pull(repo, 1)
+ # assert pull.title == "Doc"
+ # assert pull.user.login == "Morriar"
+ # ~~~
fun load_pull(repo: Repo, number: Int): nullable PullRequest do
return load_from_github("/repos/{repo.full_name}/pulls/{number}").as(nullable PullRequest)
end
#
# Returns `null` if the label cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var labl = api.load_label(repo, "ok_will_merge")
- # assert labl != null
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var labl = api.load_label(repo, "ok_will_merge")
+ # assert labl != null
+ # ~~~
fun load_label(repo: Repo, name: String): nullable Label do
return load_from_github("/repos/{repo.full_name}/labels/{name}").as(nullable Label)
end
#
# Returns `null` if the milestone cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var stone = api.load_milestone(repo, 4)
- # assert stone.title == "v1.0prealpha"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var stone = api.load_milestone(repo, 4)
+ # assert stone.title == "v1.0prealpha"
+ # ~~~
fun load_milestone(repo: Repo, id: Int): nullable Milestone do
return load_from_github("/repos/{repo.full_name}/milestones/{id}").as(nullable Milestone)
end
#
# Returns `null` if the event cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo isa Repo
- # var event = api.load_issue_event(repo, 199674194)
- # assert event isa IssueEvent
- # assert event.actor.login == "privat"
- # assert event.event == "labeled"
- # assert event.labl isa Label
- # assert event.labl.name == "need_review"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo isa Repo
+ # var event = api.load_issue_event(repo, 199674194)
+ # assert event isa IssueEvent
+ # assert event.actor.login == "privat"
+ # assert event.event == "labeled"
+ # assert event.labl isa Label
+ # assert event.labl.name == "need_review"
+ # ~~~
fun load_issue_event(repo: Repo, id: Int): nullable IssueEvent do
return load_from_github("/repos/{repo.full_name}/issues/events/{id}").as(nullable IssueEvent)
end
#
# Returns `null` if the comment cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var comment = api.load_commit_comment(repo, 8982707)
- # assert comment.user.login == "Morriar"
- # assert comment.body == "For testing purposes...\n"
- # assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var comment = api.load_commit_comment(repo, 8982707)
+ # assert comment.user.login == "Morriar"
+ # assert comment.body == "For testing purposes...\n"
+ # assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
+ # ~~~
fun load_commit_comment(repo: Repo, id: Int): nullable CommitComment do
return load_from_github("/repos/{repo.full_name}/comments/{id}").as(nullable CommitComment)
end
#
# Returns `null` if the comment cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var comment = api.load_issue_comment(repo, 6020149)
- # assert comment.user.login == "privat"
- # assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
- # assert comment.issue_number == 10
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var comment = api.load_issue_comment(repo, 6020149)
+ # assert comment.user.login == "privat"
+ # assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
+ # assert comment.issue_number == 10
+ # ~~~
fun load_issue_comment(repo: Repo, id: Int): nullable IssueComment do
return load_from_github("/repos/{repo.full_name}/issues/comments/{id}").as(nullable IssueComment)
end
#
# Returns `null` if the comment cannot be found.
#
- # var api = new GithubAPI(get_github_oauth)
- # var repo = api.load_repo("nitlang/nit")
- # assert repo != null
- # var comment = api.load_review_comment(repo, 21010363)
- # assert comment.path == "src/modelize/modelize_property.nit"
- # assert comment.original_position == 26
- # assert comment.pull_number == 945
+ # ~~~nitish
+ # var api = new GithubAPI(get_github_oauth)
+ # var repo = api.load_repo("nitlang/nit")
+ # assert repo != null
+ # var comment = api.load_review_comment(repo, 21010363)
+ # assert comment.path == "src/modelize/modelize_property.nit"
+ # assert comment.original_position == 26
+ # assert comment.pull_number == 945
+ # ~~~
fun load_review_comment(repo: Repo, id: Int): nullable ReviewComment do
return load_from_github("/repos/{repo.full_name}/pulls/comments/{id}").as(nullable ReviewComment)
end
# Github tokens used to access data.
var tokens: Array[String] is lazy do
- var arr = opt_tokens.value
- if arr.is_empty then
- var iarr = ini.at("tokens")
- if iarr != null then arr = iarr.values.to_a
+ var opt_tokens = self.opt_tokens.value
+ if opt_tokens.not_empty then return opt_tokens
+
+ var res = new Array[String]
+ var ini_tokens = ini.section("tokens")
+ if ini_tokens == null then return res
+
+ for token in ini_tokens.values do
+ if token == null then continue
+ res.add token
end
- return arr or else new Array[String]
+ return res
end
# Github tokens wallet\13
# Verbosity level (the higher the more verbose)
fun verbose_level: Int do
var opt = opt_start.value
- if opt > 0 then return opt
+ if opt > 0 then
+ return info_level
+ end
var v = ini["loader.verbose"]
- if v != null then return v.to_i
- return 4
+ if v != null and v.to_i > 0 then
+ return info_level
+ end
+ return warn_level
end
# Logger used to print things
- var logger: ConsoleLog is lazy do
- var logger = new ConsoleLog
+ var logger: PopLogger is lazy do
+ var logger = new PopLogger
logger.level = verbose_level
return logger
end
end
# Logger shortcut
- fun log: ConsoleLog do return config.logger
+ fun log: PopLogger do return config.logger
# Display a error and exit
fun error(msg: String) do
--- /dev/null
+{"message":"Not Found","documentation_url":"https://developer.github.com/v3"}
\ No newline at end of file
--- /dev/null
+{"name":"master","commit":{"sha":"9248f1c81c08b6c0ec7785678dbb1d7440b885d9","node_id":"MDY6Q29tbWl0MzI4NTk3OjkyNDhmMWM4MWMwOGI2YzBlYzc3ODU2NzhkYmIxZDc0NDBiODg1ZDk=","commit":{"author":{"name":"Jean Privat","email":"jean@pryen.org","date":"2019-06-13T14:14:24Z"},"committer":{"name":"Jean Privat","email":"jean@pryen.org","date":"2019-06-13T14:14:24Z"},"message":"Merge: Some more small improvements on gitlab-ci\n\nPull-Request: #2744","tree":{"sha":"1c40bdc143d18c628fb7939b8258fa65f6ada2e7","url":"https://api.github.com/repos/nitlang/nit/git/trees/1c40bdc143d18c628fb7939b8258fa65f6ada2e7"},"url":"https://api.github.com/repos/nitlang/nit/git/commits/9248f1c81c08b6c0ec7785678dbb1d7440b885d9","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/nitlang/nit/commits/9248f1c81c08b6c0ec7785678dbb1d7440b885d9","html_url":"https://github.com/nitlang/nit/commit/9248f1c81c08b6c0ec7785678dbb1d7440b885d9","comments_url":"https://api.github.com/repos/nitlang/nit/commits/9248f1c81c08b6c0ec7785678dbb1d7440b885d9/comments","author":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"committer":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"parents":[{"sha":"afc737008b2b10b9317f0e528dedf27dcf9926ab","url":"https://api.github.com/repos/nitlang/nit/commits/afc737008b2b10b9317f0e528dedf27dcf9926ab","html_url":"https://github.com/nitlang/nit/commit/afc737008b2b10b9317f0e528dedf27dcf9926ab"},{"sha":"61e9c7897630bfe23a2bf6c2a02803c1fc8dd51d","url":"https://api.github.com/repos/nitlang/nit/commits/61e9c7897630bfe23a2bf6c2a02803c1fc8dd51d","html_url":"https://github.com/nitlang/nit/commit/61e9c7897630bfe23a2bf6c2a02803c1fc8dd51d"}]},"_links":{"self":"https://api.github.com/repos/nitlang/nit/branches/master","html":"https://github.com/nitlang/nit/tree/master"},"protected":true,"protection":{"enabled":true,"required_status_checks":{"enforcement_level":"non_admins","contexts":[]}},"protection_url":"https://api.github.com/repos/nitlang/nit/branches/master/protection"}
\ No newline at end of file
--- /dev/null
+[{"name":"master","commit":{"sha":"9248f1c81c08b6c0ec7785678dbb1d7440b885d9","url":"https://api.github.com/repos/nitlang/nit/commits/9248f1c81c08b6c0ec7785678dbb1d7440b885d9"},"protected":true},{"name":"next","commit":{"sha":"9248f1c81c08b6c0ec7785678dbb1d7440b885d9","url":"https://api.github.com/repos/nitlang/nit/commits/9248f1c81c08b6c0ec7785678dbb1d7440b885d9"},"protected":false}]
\ No newline at end of file
--- /dev/null
+{"url":"https://api.github.com/repos/nitlang/nit/comments/8982707","html_url":"https://github.com/nitlang/nit/commit/7eacb86d1e24b7e72bc9ac869bf7182c0300ceca#commitcomment-8982707","id":8982707,"node_id":"MDEzOkNvbW1pdENvbW1lbnQ4OTgyNzA3","user":{"login":"Morriar","id":583144,"node_id":"MDQ6VXNlcjU4MzE0NA==","avatar_url":"https://avatars2.githubusercontent.com/u/583144?v=4","gravatar_id":"","url":"https://api.github.com/users/Morriar","html_url":"https://github.com/Morriar","followers_url":"https://api.github.com/users/Morriar/followers","following_url":"https://api.github.com/users/Morriar/following{/other_user}","gists_url":"https://api.github.com/users/Morriar/gists{/gist_id}","starred_url":"https://api.github.com/users/Morriar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Morriar/subscriptions","organizations_url":"https://api.github.com/users/Morriar/orgs","repos_url":"https://api.github.com/users/Morriar/repos","events_url":"https://api.github.com/users/Morriar/events{/privacy}","received_events_url":"https://api.github.com/users/Morriar/received_events","type":"User","site_admin":false},"position":null,"line":null,"path":null,"commit_id":"7eacb86d1e24b7e72bc9ac869bf7182c0300ceca","created_at":"2014-12-16T00:37:24Z","updated_at":"2014-12-16T00:37:24Z","author_association":"MEMBER","body":"For testing purposes...\n"}
\ No newline at end of file
--- /dev/null
+{"sha":"64ce1f587209024f5de46d06c70526a569ff537f","node_id":"MDY6Q29tbWl0MzI4NTk3OjY0Y2UxZjU4NzIwOTAyNGY1ZGU0NmQwNmM3MDUyNmE1NjlmZjUzN2Y=","commit":{"author":{"name":"Jean Privat","email":"jean@pryen.org","date":"2014-12-03T15:16:13Z"},"committer":{"name":"Jean Privat","email":"jean@pryen.org","date":"2014-12-03T15:33:19Z"},"message":"lib/string: add `chomp`\n\nSigned-off-by: Jean Privat <jean@pryen.org>","tree":{"sha":"66ac4b2bd9247d98afbb5309db97dda06991ed77","url":"https://api.github.com/repos/nitlang/nit/git/trees/66ac4b2bd9247d98afbb5309db97dda06991ed77"},"url":"https://api.github.com/repos/nitlang/nit/git/commits/64ce1f587209024f5de46d06c70526a569ff537f","comment_count":0,"verification":{"verified":false,"reason":"unsigned","signature":null,"payload":null}},"url":"https://api.github.com/repos/nitlang/nit/commits/64ce1f587209024f5de46d06c70526a569ff537f","html_url":"https://github.com/nitlang/nit/commit/64ce1f587209024f5de46d06c70526a569ff537f","comments_url":"https://api.github.com/repos/nitlang/nit/commits/64ce1f587209024f5de46d06c70526a569ff537f/comments","author":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"committer":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"parents":[{"sha":"a882d5602264623f9275698b5abe73d95b127b9f","url":"https://api.github.com/repos/nitlang/nit/commits/a882d5602264623f9275698b5abe73d95b127b9f","html_url":"https://github.com/nitlang/nit/commit/a882d5602264623f9275698b5abe73d95b127b9f"}],"stats":{"total":13,"additions":13,"deletions":0},"files":[{"sha":"664ecbcb0652892b7659a98e5fb24c995211563c","filename":"lib/standard/string.nit","status":"modified","additions":13,"deletions":0,"changes":13,"blob_url":"https://github.com/nitlang/nit/blob/64ce1f587209024f5de46d06c70526a569ff537f/lib/standard/string.nit","raw_url":"https://github.com/nitlang/nit/raw/64ce1f587209024f5de46d06c70526a569ff537f/lib/standard/string.nit","contents_url":"https://api.github.com/repos/nitlang/nit/contents/lib/standard/string.nit?ref=64ce1f587209024f5de46d06c70526a569ff537f","patch":"@@ -385,6 +385,19 @@ abstract class Text\n \t# assert \"\\na\\nb\\tc\\t\".trim == \"a\\nb\\tc\"\n \tfun trim: SELFTYPE do return (self.l_trim).r_trim\n \n+\t# Returns `self` removed from its last `\\n` (if any).\n+\t#\n+\t# assert \"Hello\\n\".chomp == \"Hello\"\n+\t# assert \"Hello\".chomp == \"Hello\"\n+\t# assert \"\\n\\n\\n\".chomp == \"\\n\\n\"\n+\t#\n+\t# This method is mainly used to remove the LINE_FEED character from lines of text.\n+\tfun chomp: SELFTYPE\n+\tdo\n+\t\tif self.chars.last != '\\n' then return self\n+\t\treturn substring(0, length-1)\n+\tend\n+\n \t# Justify a self in a space of `length`\n \t#\n \t# `left` is the space ratio on the left side."}]}
\ No newline at end of file
--- /dev/null
+{"url":"https://api.github.com/repos/nitlang/nit/issues/1000","repository_url":"https://api.github.com/repos/nitlang/nit","labels_url":"https://api.github.com/repos/nitlang/nit/issues/1000/labels{/name}","comments_url":"https://api.github.com/repos/nitlang/nit/issues/1000/comments","events_url":"https://api.github.com/repos/nitlang/nit/issues/1000/events","html_url":"https://github.com/nitlang/nit/pull/1000","id":51639845,"node_id":"MDExOlB1bGxSZXF1ZXN0MjU4NzM0Mzg=","number":1000,"title":"Raise nitc from the dead","user":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"labels":[{"id":81916206,"node_id":"MDU6TGFiZWw4MTkxNjIwNg==","url":"https://api.github.com/repos/nitlang/nit/labels/ok_will_merge","name":"ok_will_merge","color":"009800","default":false}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":7,"created_at":"2014-12-11T02:55:09Z","updated_at":"2014-12-18T14:14:33Z","closed_at":"2014-12-13T15:38:09Z","author_association":"MEMBER","pull_request":{"url":"https://api.github.com/repos/nitlang/nit/pulls/1000","html_url":"https://github.com/nitlang/nit/pull/1000","diff_url":"https://github.com/nitlang/nit/pull/1000.diff","patch_url":"https://github.com/nitlang/nit/pull/1000.patch"},"body":"Raise dead on `nitc`.\nIt's super effective...\n","closed_by":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false}}
\ No newline at end of file
--- /dev/null
+{"url":"https://api.github.com/repos/nitlang/nit/issues/comments/6020149","html_url":"https://github.com/nitlang/nit/pull/10#issuecomment-6020149","issue_url":"https://api.github.com/repos/nitlang/nit/issues/10","id":6020149,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMjAxNDk=","user":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"created_at":"2012-05-30T20:16:54Z","updated_at":"2012-05-30T20:16:54Z","author_association":"MEMBER","body":"Rebased e766cde to drop the ugly github merge commit 0e3a614.\nThe result is 8f221e3.\n"}
\ No newline at end of file
--- /dev/null
+{"id":199674194,"node_id":"MDEyOkxhYmVsZWRFdmVudDE5OTY3NDE5NA==","url":"https://api.github.com/repos/nitlang/nit/issues/events/199674194","actor":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"event":"labeled","commit_id":null,"commit_url":null,"created_at":"2014-11-27T20:32:30Z","label":{"name":"need_review","color":"fbca04"},"issue":{"url":"https://api.github.com/repos/nitlang/nit/issues/945","repository_url":"https://api.github.com/repos/nitlang/nit","labels_url":"https://api.github.com/repos/nitlang/nit/issues/945/labels{/name}","comments_url":"https://api.github.com/repos/nitlang/nit/issues/945/comments","events_url":"https://api.github.com/repos/nitlang/nit/issues/945/events","html_url":"https://github.com/nitlang/nit/pull/945","id":50322007,"node_id":"MDExOlB1bGxSZXF1ZXN0MjUxNjg4ODY=","number":945,"title":"Useless type","user":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"labels":[{"id":81916206,"node_id":"MDU6TGFiZWw4MTkxNjIwNg==","url":"https://api.github.com/repos/nitlang/nit/labels/ok_will_merge","name":"ok_will_merge","color":"009800","default":false}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":1,"created_at":"2014-11-27T20:32:27Z","updated_at":"2014-12-03T20:22:42Z","closed_at":"2014-12-01T13:53:03Z","author_association":"MEMBER","pull_request":{"url":"https://api.github.com/repos/nitlang/nit/pulls/945","html_url":"https://github.com/nitlang/nit/pull/945","diff_url":"https://github.com/nitlang/nit/pull/945.diff","patch_url":"https://github.com/nitlang/nit/pull/945.patch"},"body":"Fix a wrong `useless-type` warning for attributes.\nExtends the `useless-type` warning to local variables.\n"}}
\ No newline at end of file
--- /dev/null
+{"id":81916206,"node_id":"MDU6TGFiZWw4MTkxNjIwNg==","url":"https://api.github.com/repos/nitlang/nit/labels/ok_will_merge","name":"ok_will_merge","color":"009800","default":false}
\ No newline at end of file
--- /dev/null
+{"url":"https://api.github.com/repos/nitlang/nit/milestones/4","html_url":"https://github.com/nitlang/nit/milestone/4","labels_url":"https://api.github.com/repos/nitlang/nit/milestones/4/labels","id":795157,"node_id":"MDk6TWlsZXN0b25lNzk1MTU3","number":4,"title":"v1.0prealpha","description":"The first public version that we are proud off and can be used sanely by non Nit people.","creator":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"open_issues":22,"closed_issues":22,"state":"open","created_at":"2014-09-19T00:16:45Z","updated_at":"2017-06-02T12:43:15Z","due_on":null,"closed_at":null}
\ No newline at end of file
--- /dev/null
+{"id":328597,"node_id":"MDEwOlJlcG9zaXRvcnkzMjg1OTc=","name":"nit","full_name":"nitlang/nit","private":false,"owner":{"login":"nitlang","id":5420298,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0MjAyOTg=","avatar_url":"https://avatars1.githubusercontent.com/u/5420298?v=4","gravatar_id":"","url":"https://api.github.com/users/nitlang","html_url":"https://github.com/nitlang","followers_url":"https://api.github.com/users/nitlang/followers","following_url":"https://api.github.com/users/nitlang/following{/other_user}","gists_url":"https://api.github.com/users/nitlang/gists{/gist_id}","starred_url":"https://api.github.com/users/nitlang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nitlang/subscriptions","organizations_url":"https://api.github.com/users/nitlang/orgs","repos_url":"https://api.github.com/users/nitlang/repos","events_url":"https://api.github.com/users/nitlang/events{/privacy}","received_events_url":"https://api.github.com/users/nitlang/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/nitlang/nit","description":"Nit language","fork":false,"url":"https://api.github.com/repos/nitlang/nit","forks_url":"https://api.github.com/repos/nitlang/nit/forks","keys_url":"https://api.github.com/repos/nitlang/nit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nitlang/nit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nitlang/nit/teams","hooks_url":"https://api.github.com/repos/nitlang/nit/hooks","issue_events_url":"https://api.github.com/repos/nitlang/nit/issues/events{/number}","events_url":"https://api.github.com/repos/nitlang/nit/events","assignees_url":"https://api.github.com/repos/nitlang/nit/assignees{/user}","branches_url":"https://api.github.com/repos/nitlang/nit/branches{/branch}","tags_url":"https://api.github.com/repos/nitlang/nit/tags","blobs_url":"https://api.github.com/repos/nitlang/nit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nitlang/nit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nitlang/nit/git/refs{/sha}","trees_url":"https://api.github.com/repos/nitlang/nit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nitlang/nit/statuses/{sha}","languages_url":"https://api.github.com/repos/nitlang/nit/languages","stargazers_url":"https://api.github.com/repos/nitlang/nit/stargazers","contributors_url":"https://api.github.com/repos/nitlang/nit/contributors","subscribers_url":"https://api.github.com/repos/nitlang/nit/subscribers","subscription_url":"https://api.github.com/repos/nitlang/nit/subscription","commits_url":"https://api.github.com/repos/nitlang/nit/commits{/sha}","git_commits_url":"https://api.github.com/repos/nitlang/nit/git/commits{/sha}","comments_url":"https://api.github.com/repos/nitlang/nit/comments{/number}","issue_comment_url":"https://api.github.com/repos/nitlang/nit/issues/comments{/number}","contents_url":"https://api.github.com/repos/nitlang/nit/contents/{+path}","compare_url":"https://api.github.com/repos/nitlang/nit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nitlang/nit/merges","archive_url":"https://api.github.com/repos/nitlang/nit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nitlang/nit/downloads","issues_url":"https://api.github.com/repos/nitlang/nit/issues{/number}","pulls_url":"https://api.github.com/repos/nitlang/nit/pulls{/number}","milestones_url":"https://api.github.com/repos/nitlang/nit/milestones{/number}","notifications_url":"https://api.github.com/repos/nitlang/nit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nitlang/nit/labels{/name}","releases_url":"https://api.github.com/repos/nitlang/nit/releases{/id}","deployments_url":"https://api.github.com/repos/nitlang/nit/deployments","created_at":"2009-10-06T15:03:00Z","updated_at":"2019-06-17T13:13:03Z","pushed_at":"2019-06-19T00:02:11Z","git_url":"git://github.com/nitlang/nit.git","ssh_url":"git@github.com:nitlang/nit.git","clone_url":"https://github.com/nitlang/nit.git","svn_url":"https://github.com/nitlang/nit","homepage":"http://nitlanguage.org","size":123083,"stargazers_count":187,"watchers_count":187,"language":"C","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":56,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":171,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":56,"open_issues":171,"watchers":187,"default_branch":"master","permissions":{"admin":false,"push":true,"pull":true},"organization":{"login":"nitlang","id":5420298,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0MjAyOTg=","avatar_url":"https://avatars1.githubusercontent.com/u/5420298?v=4","gravatar_id":"","url":"https://api.github.com/users/nitlang","html_url":"https://github.com/nitlang","followers_url":"https://api.github.com/users/nitlang/followers","following_url":"https://api.github.com/users/nitlang/following{/other_user}","gists_url":"https://api.github.com/users/nitlang/gists{/gist_id}","starred_url":"https://api.github.com/users/nitlang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nitlang/subscriptions","organizations_url":"https://api.github.com/users/nitlang/orgs","repos_url":"https://api.github.com/users/nitlang/repos","events_url":"https://api.github.com/users/nitlang/events{/privacy}","received_events_url":"https://api.github.com/users/nitlang/received_events","type":"Organization","site_admin":false},"network_count":56,"subscribers_count":18}
\ No newline at end of file
--- /dev/null
+{"url":"https://api.github.com/repos/nitlang/nit/pulls/1000","id":25873438,"node_id":"MDExOlB1bGxSZXF1ZXN0MjU4NzM0Mzg=","html_url":"https://github.com/nitlang/nit/pull/1000","diff_url":"https://github.com/nitlang/nit/pull/1000.diff","patch_url":"https://github.com/nitlang/nit/pull/1000.patch","issue_url":"https://api.github.com/repos/nitlang/nit/issues/1000","number":1000,"state":"closed","locked":false,"title":"Raise nitc from the dead","user":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"body":"Raise dead on `nitc`.\nIt's super effective...\n","created_at":"2014-12-11T02:55:09Z","updated_at":"2014-12-18T14:14:33Z","closed_at":"2014-12-13T15:38:09Z","merged_at":"2014-12-13T15:38:09Z","merge_commit_sha":"49af656d278987d3a09f8500bcbe019e3c0f6367","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":81916206,"node_id":"MDU6TGFiZWw4MTkxNjIwNg==","url":"https://api.github.com/repos/nitlang/nit/labels/ok_will_merge","name":"ok_will_merge","color":"009800","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/nitlang/nit/pulls/1000/commits","review_comments_url":"https://api.github.com/repos/nitlang/nit/pulls/1000/comments","review_comment_url":"https://api.github.com/repos/nitlang/nit/pulls/comments{/number}","comments_url":"https://api.github.com/repos/nitlang/nit/issues/1000/comments","statuses_url":"https://api.github.com/repos/nitlang/nit/statuses/273b078ecc1a395f260992ec9fb08a31e8c338d9","head":{"label":"nitlang:raise-nitc","ref":"raise-nitc","sha":"273b078ecc1a395f260992ec9fb08a31e8c338d9","user":{"login":"nitlang","id":5420298,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0MjAyOTg=","avatar_url":"https://avatars1.githubusercontent.com/u/5420298?v=4","gravatar_id":"","url":"https://api.github.com/users/nitlang","html_url":"https://github.com/nitlang","followers_url":"https://api.github.com/users/nitlang/followers","following_url":"https://api.github.com/users/nitlang/following{/other_user}","gists_url":"https://api.github.com/users/nitlang/gists{/gist_id}","starred_url":"https://api.github.com/users/nitlang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nitlang/subscriptions","organizations_url":"https://api.github.com/users/nitlang/orgs","repos_url":"https://api.github.com/users/nitlang/repos","events_url":"https://api.github.com/users/nitlang/events{/privacy}","received_events_url":"https://api.github.com/users/nitlang/received_events","type":"Organization","site_admin":false},"repo":{"id":328597,"node_id":"MDEwOlJlcG9zaXRvcnkzMjg1OTc=","name":"nit","full_name":"nitlang/nit","private":false,"owner":{"login":"nitlang","id":5420298,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0MjAyOTg=","avatar_url":"https://avatars1.githubusercontent.com/u/5420298?v=4","gravatar_id":"","url":"https://api.github.com/users/nitlang","html_url":"https://github.com/nitlang","followers_url":"https://api.github.com/users/nitlang/followers","following_url":"https://api.github.com/users/nitlang/following{/other_user}","gists_url":"https://api.github.com/users/nitlang/gists{/gist_id}","starred_url":"https://api.github.com/users/nitlang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nitlang/subscriptions","organizations_url":"https://api.github.com/users/nitlang/orgs","repos_url":"https://api.github.com/users/nitlang/repos","events_url":"https://api.github.com/users/nitlang/events{/privacy}","received_events_url":"https://api.github.com/users/nitlang/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/nitlang/nit","description":"Nit language","fork":false,"url":"https://api.github.com/repos/nitlang/nit","forks_url":"https://api.github.com/repos/nitlang/nit/forks","keys_url":"https://api.github.com/repos/nitlang/nit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nitlang/nit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nitlang/nit/teams","hooks_url":"https://api.github.com/repos/nitlang/nit/hooks","issue_events_url":"https://api.github.com/repos/nitlang/nit/issues/events{/number}","events_url":"https://api.github.com/repos/nitlang/nit/events","assignees_url":"https://api.github.com/repos/nitlang/nit/assignees{/user}","branches_url":"https://api.github.com/repos/nitlang/nit/branches{/branch}","tags_url":"https://api.github.com/repos/nitlang/nit/tags","blobs_url":"https://api.github.com/repos/nitlang/nit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nitlang/nit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nitlang/nit/git/refs{/sha}","trees_url":"https://api.github.com/repos/nitlang/nit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nitlang/nit/statuses/{sha}","languages_url":"https://api.github.com/repos/nitlang/nit/languages","stargazers_url":"https://api.github.com/repos/nitlang/nit/stargazers","contributors_url":"https://api.github.com/repos/nitlang/nit/contributors","subscribers_url":"https://api.github.com/repos/nitlang/nit/subscribers","subscription_url":"https://api.github.com/repos/nitlang/nit/subscription","commits_url":"https://api.github.com/repos/nitlang/nit/commits{/sha}","git_commits_url":"https://api.github.com/repos/nitlang/nit/git/commits{/sha}","comments_url":"https://api.github.com/repos/nitlang/nit/comments{/number}","issue_comment_url":"https://api.github.com/repos/nitlang/nit/issues/comments{/number}","contents_url":"https://api.github.com/repos/nitlang/nit/contents/{+path}","compare_url":"https://api.github.com/repos/nitlang/nit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nitlang/nit/merges","archive_url":"https://api.github.com/repos/nitlang/nit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nitlang/nit/downloads","issues_url":"https://api.github.com/repos/nitlang/nit/issues{/number}","pulls_url":"https://api.github.com/repos/nitlang/nit/pulls{/number}","milestones_url":"https://api.github.com/repos/nitlang/nit/milestones{/number}","notifications_url":"https://api.github.com/repos/nitlang/nit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nitlang/nit/labels{/name}","releases_url":"https://api.github.com/repos/nitlang/nit/releases{/id}","deployments_url":"https://api.github.com/repos/nitlang/nit/deployments","created_at":"2009-10-06T15:03:00Z","updated_at":"2019-06-17T13:13:03Z","pushed_at":"2019-06-19T00:02:11Z","git_url":"git://github.com/nitlang/nit.git","ssh_url":"git@github.com:nitlang/nit.git","clone_url":"https://github.com/nitlang/nit.git","svn_url":"https://github.com/nitlang/nit","homepage":"http://nitlanguage.org","size":123083,"stargazers_count":187,"watchers_count":187,"language":"C","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":56,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":171,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":56,"open_issues":171,"watchers":187,"default_branch":"master"}},"base":{"label":"nitlang:master","ref":"master","sha":"8bd95517ec64090da1356ee1a88af82a9ccf2847","user":{"login":"nitlang","id":5420298,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0MjAyOTg=","avatar_url":"https://avatars1.githubusercontent.com/u/5420298?v=4","gravatar_id":"","url":"https://api.github.com/users/nitlang","html_url":"https://github.com/nitlang","followers_url":"https://api.github.com/users/nitlang/followers","following_url":"https://api.github.com/users/nitlang/following{/other_user}","gists_url":"https://api.github.com/users/nitlang/gists{/gist_id}","starred_url":"https://api.github.com/users/nitlang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nitlang/subscriptions","organizations_url":"https://api.github.com/users/nitlang/orgs","repos_url":"https://api.github.com/users/nitlang/repos","events_url":"https://api.github.com/users/nitlang/events{/privacy}","received_events_url":"https://api.github.com/users/nitlang/received_events","type":"Organization","site_admin":false},"repo":{"id":328597,"node_id":"MDEwOlJlcG9zaXRvcnkzMjg1OTc=","name":"nit","full_name":"nitlang/nit","private":false,"owner":{"login":"nitlang","id":5420298,"node_id":"MDEyOk9yZ2FuaXphdGlvbjU0MjAyOTg=","avatar_url":"https://avatars1.githubusercontent.com/u/5420298?v=4","gravatar_id":"","url":"https://api.github.com/users/nitlang","html_url":"https://github.com/nitlang","followers_url":"https://api.github.com/users/nitlang/followers","following_url":"https://api.github.com/users/nitlang/following{/other_user}","gists_url":"https://api.github.com/users/nitlang/gists{/gist_id}","starred_url":"https://api.github.com/users/nitlang/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nitlang/subscriptions","organizations_url":"https://api.github.com/users/nitlang/orgs","repos_url":"https://api.github.com/users/nitlang/repos","events_url":"https://api.github.com/users/nitlang/events{/privacy}","received_events_url":"https://api.github.com/users/nitlang/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/nitlang/nit","description":"Nit language","fork":false,"url":"https://api.github.com/repos/nitlang/nit","forks_url":"https://api.github.com/repos/nitlang/nit/forks","keys_url":"https://api.github.com/repos/nitlang/nit/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nitlang/nit/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nitlang/nit/teams","hooks_url":"https://api.github.com/repos/nitlang/nit/hooks","issue_events_url":"https://api.github.com/repos/nitlang/nit/issues/events{/number}","events_url":"https://api.github.com/repos/nitlang/nit/events","assignees_url":"https://api.github.com/repos/nitlang/nit/assignees{/user}","branches_url":"https://api.github.com/repos/nitlang/nit/branches{/branch}","tags_url":"https://api.github.com/repos/nitlang/nit/tags","blobs_url":"https://api.github.com/repos/nitlang/nit/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nitlang/nit/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nitlang/nit/git/refs{/sha}","trees_url":"https://api.github.com/repos/nitlang/nit/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nitlang/nit/statuses/{sha}","languages_url":"https://api.github.com/repos/nitlang/nit/languages","stargazers_url":"https://api.github.com/repos/nitlang/nit/stargazers","contributors_url":"https://api.github.com/repos/nitlang/nit/contributors","subscribers_url":"https://api.github.com/repos/nitlang/nit/subscribers","subscription_url":"https://api.github.com/repos/nitlang/nit/subscription","commits_url":"https://api.github.com/repos/nitlang/nit/commits{/sha}","git_commits_url":"https://api.github.com/repos/nitlang/nit/git/commits{/sha}","comments_url":"https://api.github.com/repos/nitlang/nit/comments{/number}","issue_comment_url":"https://api.github.com/repos/nitlang/nit/issues/comments{/number}","contents_url":"https://api.github.com/repos/nitlang/nit/contents/{+path}","compare_url":"https://api.github.com/repos/nitlang/nit/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nitlang/nit/merges","archive_url":"https://api.github.com/repos/nitlang/nit/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nitlang/nit/downloads","issues_url":"https://api.github.com/repos/nitlang/nit/issues{/number}","pulls_url":"https://api.github.com/repos/nitlang/nit/pulls{/number}","milestones_url":"https://api.github.com/repos/nitlang/nit/milestones{/number}","notifications_url":"https://api.github.com/repos/nitlang/nit/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nitlang/nit/labels{/name}","releases_url":"https://api.github.com/repos/nitlang/nit/releases{/id}","deployments_url":"https://api.github.com/repos/nitlang/nit/deployments","created_at":"2009-10-06T15:03:00Z","updated_at":"2019-06-17T13:13:03Z","pushed_at":"2019-06-19T00:02:11Z","git_url":"git://github.com/nitlang/nit.git","ssh_url":"git@github.com:nitlang/nit.git","clone_url":"https://github.com/nitlang/nit.git","svn_url":"https://github.com/nitlang/nit","homepage":"http://nitlanguage.org","size":123083,"stargazers_count":187,"watchers_count":187,"language":"C","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":56,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":171,"license":{"key":"other","name":"Other","spdx_id":"NOASSERTION","url":null,"node_id":"MDc6TGljZW5zZTA="},"forks":56,"open_issues":171,"watchers":187,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/nitlang/nit/pulls/1000"},"html":{"href":"https://github.com/nitlang/nit/pull/1000"},"issue":{"href":"https://api.github.com/repos/nitlang/nit/issues/1000"},"comments":{"href":"https://api.github.com/repos/nitlang/nit/issues/1000/comments"},"review_comments":{"href":"https://api.github.com/repos/nitlang/nit/pulls/1000/comments"},"review_comment":{"href":"https://api.github.com/repos/nitlang/nit/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/nitlang/nit/pulls/1000/commits"},"statuses":{"href":"https://api.github.com/repos/nitlang/nit/statuses/273b078ecc1a395f260992ec9fb08a31e8c338d9"}},"author_association":"MEMBER","merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"privat","id":135828,"node_id":"MDQ6VXNlcjEzNTgyOA==","avatar_url":"https://avatars1.githubusercontent.com/u/135828?v=4","gravatar_id":"","url":"https://api.github.com/users/privat","html_url":"https://github.com/privat","followers_url":"https://api.github.com/users/privat/followers","following_url":"https://api.github.com/users/privat/following{/other_user}","gists_url":"https://api.github.com/users/privat/gists{/gist_id}","starred_url":"https://api.github.com/users/privat/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/privat/subscriptions","organizations_url":"https://api.github.com/users/privat/orgs","repos_url":"https://api.github.com/users/privat/repos","events_url":"https://api.github.com/users/privat/events{/privacy}","received_events_url":"https://api.github.com/users/privat/received_events","type":"User","site_admin":false},"comments":7,"review_comments":0,"maintainer_can_modify":false,"commits":11,"additions":282,"deletions":268,"changed_files":67}
\ No newline at end of file
--- /dev/null
+{"url":"https://api.github.com/repos/nitlang/nit/pulls/comments/21010363","pull_request_review_id":null,"id":21010363,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxMDEwMzYz","diff_hunk":"@@ -981,11 +983,11 @@ redef class AAttrPropdef\n \n \t\t\t\tif mtype == null then return\n \t\t\tend\n-\t\telse if ntype != null then\n+\t\telse if ntype != null and inherited_type == mtype then\n \t\t\tif nexpr isa ANewExpr then\n \t\t\t\tvar xmtype = modelbuilder.resolve_mtype(mmodule, mclassdef, nexpr.n_type)\n \t\t\t\tif xmtype == mtype then\n-\t\t\t\t\tmodelbuilder.advice(ntype, \"useless-type\", \"Warning: useless type definition\")\n+\t\t\t\t\tmodelbuilder.advice(ntype, \"useless-type\", \"Warning: useless type definition {inherited_type or else \"?\"}\")","path":"src/modelize/modelize_property.nit","position":null,"original_position":26,"commit_id":"ce5e187a87ed5c41144ea5637188a0677d840fdc","original_commit_id":"5f0ab1c7f3c560a67867d5eb08f5c3082f251c20","user":{"login":"jcbrinfo","id":6044484,"node_id":"MDQ6VXNlcjYwNDQ0ODQ=","avatar_url":"https://avatars0.githubusercontent.com/u/6044484?v=4","gravatar_id":"","url":"https://api.github.com/users/jcbrinfo","html_url":"https://github.com/jcbrinfo","followers_url":"https://api.github.com/users/jcbrinfo/followers","following_url":"https://api.github.com/users/jcbrinfo/following{/other_user}","gists_url":"https://api.github.com/users/jcbrinfo/gists{/gist_id}","starred_url":"https://api.github.com/users/jcbrinfo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jcbrinfo/subscriptions","organizations_url":"https://api.github.com/users/jcbrinfo/orgs","repos_url":"https://api.github.com/users/jcbrinfo/repos","events_url":"https://api.github.com/users/jcbrinfo/events{/privacy}","received_events_url":"https://api.github.com/users/jcbrinfo/received_events","type":"User","site_admin":false},"body":"Warning: `inherited_type` is always non null here.\n","created_at":"2014-11-27T20:39:29Z","updated_at":"2014-11-28T01:05:12Z","html_url":"https://github.com/nitlang/nit/pull/945#discussion_r21010363","pull_request_url":"https://api.github.com/repos/nitlang/nit/pulls/945","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/nitlang/nit/pulls/comments/21010363"},"html":{"href":"https://github.com/nitlang/nit/pull/945#discussion_r21010363"},"pull_request":{"href":"https://api.github.com/repos/nitlang/nit/pulls/945"}}}
\ No newline at end of file
--- /dev/null
+{"login":"Morriar","id":583144,"node_id":"MDQ6VXNlcjU4MzE0NA==","avatar_url":"https://avatars2.githubusercontent.com/u/583144?v=4","gravatar_id":"","url":"https://api.github.com/users/Morriar","html_url":"https://github.com/Morriar","followers_url":"https://api.github.com/users/Morriar/followers","following_url":"https://api.github.com/users/Morriar/following{/other_user}","gists_url":"https://api.github.com/users/Morriar/gists{/gist_id}","starred_url":"https://api.github.com/users/Morriar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Morriar/subscriptions","organizations_url":"https://api.github.com/users/Morriar/orgs","repos_url":"https://api.github.com/users/Morriar/repos","events_url":"https://api.github.com/users/Morriar/events{/privacy}","received_events_url":"https://api.github.com/users/Morriar/received_events","type":"User","site_admin":false,"name":"Alexandre Terrasa","company":null,"blog":"moz-code.org","location":null,"email":"alexandre@moz-code.org","hireable":null,"bio":null,"public_repos":96,"public_gists":1,"followers":42,"following":10,"created_at":"2011-01-25T17:50:36Z","updated_at":"2019-06-15T01:41:56Z"}
\ No newline at end of file
--- /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.
+
+module test_api is test
+
+intrude import api
+
+# GithubAPI testing
+#
+# To avoid test flakyness we test the GithubAPI against a mock of the real one.
+# For each api request we return a cache file of the real API response body.
+#
+# Cache files can be automatically created and updated by setting
+# `update_responses_cache` to `true` then running `nitunit`.
+class MockGithubCurl
+ super GithubCurl
+
+ # Mock so it returns the response from a file
+ #
+ # See `update_responses_cache`.
+ redef fun get_and_parse(uri) do
+ print uri # for debugging
+
+ var path = uri.replace("https://api.github.com/", "/")
+ assert has_response(path)
+
+ if update_responses_cache then
+ var file = response_file(path)
+ save_actual_response(uri, file)
+ end
+
+ var response = response_string(path).parse_json
+ if response_is_error(path) then
+ var title = "GithubAPIError"
+ var msg = response.as(JsonObject)["message"].as(String)
+ var err = new GithubError(msg, title)
+ err.json["requested_uri"] = uri
+ err.json["status_code"] = response_code(path)
+ return err
+ end
+ return response
+ end
+
+ var test_responses: Map[String, String] do
+ var map = new HashMap[String, String]
+ map["/user"] = "user_Morriar"
+ map["/users/Morriar"] = "user_Morriar"
+ map["/repos/nitlang/nit"] = "repo_nit"
+ map["/repos/nitlang/nit/labels/ok_will_merge"] = "repo_labels_ok_will_merge"
+ map["/repos/nitlang/nit/milestones/4"] = "repo_milestones_4"
+ map["/repos/nitlang/nit/branches"] = "repo_branches_nit"
+ map["/repos/nitlang/nit/branches/master"] = "repo_branches_master"
+ map["/repos/nitlang/nit/issues/1000"] = "repo_issues_1000"
+ map["/repos/nitlang/nit/issues/comments/6020149"] = "repo_issues_comments_6020149"
+ map["/repos/nitlang/nit/issues/events/199674194"] = "repo_issues_events_199674194"
+ map["/repos/nitlang/nit/pulls/1000"] = "repo_pulls_1000"
+ map["/repos/nitlang/nit/commits/64ce1f"] = "repo_commits_64ce1f"
+ map["/repos/nitlang/nit/comments/8982707"] = "repo_comments_8982707"
+ map["/repos/nitlang/nit/pulls/comments/21010363"] = "repo_pulls_comment_21010363"
+ # errors
+ map["/users/not_found/not_found"] = "errors_404"
+ return map
+ end
+
+ # Does `self` have a mock response for Github `path`?
+ fun has_response(path: String): Bool do
+ return test_responses.has_key(path)
+ end
+
+ # Root responses cache directory
+ var responses_dir: String is lazy do
+ var path = "NIT_TESTING_PATH".environ.dirname / "mock"
+ path.mkdir
+ return path
+ end
+
+ # Returns the response file path for a Github `path`
+ fun response_file(path: String): String do
+ assert has_response(path)
+ return "{responses_dir / test_responses[path]}.res"
+ end
+
+ # Returns the response body string for a Github `path`
+ fun response_string(path: String): String do
+ var file = response_file(path)
+ assert file.file_exists
+ return file.to_path.read_all
+ end
+
+ # Is this response a simulated error?
+ fun response_is_error(path: String): Bool do
+ assert has_response(path)
+ return test_responses[path].has_prefix("errors_")
+ end
+
+ # Status code of a simulated error
+ #
+ # See `response_is_error`.
+ fun response_code(path: String): String do
+ assert response_is_error(path)
+ return test_responses[path].split("_").last
+ end
+
+ # Response caching
+
+ # Activate caching
+ #
+ # Change this value to `true` then run nitunit to cache the responses
+ # from the Github API.
+ #
+ # Default is `false`.
+ var update_responses_cache = false
+
+ # Save the actual Github API response body for `uri` to a `file`
+ private fun save_actual_response(uri, file: String) do
+ assert update_responses_cache
+
+ var request = new CurlHTTPRequest(uri)
+ request.user_agent = actual_curl.user_agent
+ request.headers = actual_curl.header
+ var response = request.execute
+
+ if response isa CurlResponseSuccess then
+ response.body_str.write_to_file(file)
+ else if response isa CurlResponseFailed then
+ response.error_msg.write_to_file(file)
+ else abort
+
+ print "Response to `{uri}` saved at `{file}`"
+ end
+
+ # Actual GithubCurl instance used for caching
+ private var actual_curl = new GithubCurl(get_github_oauth, "nitunit")
+end
+
+class TestGithubAPI
+ test
+
+ var mock = new MockGithubCurl("test", "test")
+
+ fun api: GithubAPI do
+ var api = new GithubAPI("test")
+ api.ghcurl = mock
+ return api
+ end
+
+ fun test_deserialize is test do
+ var response = mock.response_string("/users/Morriar")
+ var obj = api.deserialize(response)
+ assert obj isa User
+ assert obj.login == "Morriar"
+ end
+
+ fun test_sanitize_url is test do
+ # TODO better tests
+ assert api.sanitize_uri("/repos/Nit with spaces/") == "/repos/Nit%20with%20spaces/"
+ end
+
+ fun test_get is test do
+ var api = self.api
+ var obj = api.get("/users/Morriar")
+ assert not api.was_error
+ assert api.last_error == null
+ assert obj isa JsonObject
+ assert obj["login"] == "Morriar"
+ end
+
+ fun test_get_404 is test do
+ var api = self.api
+ var res = api.get("/users/not_found/not_found")
+ assert res == null
+ assert api.was_error
+ var err = api.last_error
+ assert err isa GithubError
+ assert err.name == "GithubAPIError"
+ assert err.message == "Not Found"
+ end
+
+ fun test_load_from_github is test do
+ var api = self.api
+ var obj = api.load_from_github("/users/Morriar")
+ assert not api.was_error
+ assert api.last_error == null
+ assert obj isa User
+ assert obj.login == "Morriar"
+ end
+
+ fun test_load_from_github_404 is test do
+ var api = self.api
+ var res = api.load_from_github("/users/not_found/not_found")
+ assert res == null
+ assert api.was_error
+ var err = api.last_error
+ assert err isa GithubError
+ assert err.name == "GithubAPIError"
+ assert err.message == "Not Found"
+ end
+
+ # TODO test more error cases
+
+ fun test_get_auth_user is test do
+ var user = api.load_auth_user
+ assert user isa User
+ assert user.login == "Morriar"
+ assert user.avatar_url == "https://avatars2.githubusercontent.com/u/583144?v=4"
+ assert user.name == "Alexandre Terrasa"
+ assert user.email == "alexandre@moz-code.org"
+ assert user.blog == "moz-code.org"
+ end
+
+ fun test_get_user is test do
+ var user = api.load_user("Morriar")
+ assert user isa User
+ assert user.login == "Morriar"
+ assert user.avatar_url == "https://avatars2.githubusercontent.com/u/583144?v=4"
+ assert user.name == "Alexandre Terrasa"
+ assert user.email == "alexandre@moz-code.org"
+ assert user.blog == "moz-code.org"
+ end
+
+ fun test_get_repo is test do
+ var repo = api.load_repo("nitlang/nit")
+ assert repo isa Repo
+ assert repo.full_name == "nitlang/nit"
+ assert repo.name == "nit"
+ assert repo.owner.login == "nitlang"
+ assert repo.default_branch == "master"
+ end
+
+ private var repo: Repo is lazy do return api.load_repo("nitlang/nit").as(not null)
+
+ fun test_get_branches is test do
+ var branches = api.load_repo_branches(repo)
+ assert branches.length == 2
+ assert branches.first.name == "master"
+ assert branches.last.name == "next"
+ end
+
+ # TODO issues
+ # TODO repo_last_issue
+ # TODO labels
+ # TODO milestones
+ # TODO pulls
+ # TODO contrib_stats
+
+ fun test_get_branch is test do
+ var branch = api.load_branch(repo, "master")
+ assert branch isa Branch
+ assert branch.name == "master"
+ end
+
+ # TODO branch commits
+
+ fun test_get_commit is test do
+ var commit = api.load_commit(repo, "64ce1f")
+ assert commit isa Commit
+ assert commit.sha == "64ce1f587209024f5de46d06c70526a569ff537f"
+ # TODO other fields
+ end
+
+ fun test_get_issue is test do
+ var issue = api.load_issue(repo, 1000)
+ assert issue isa Issue
+ assert issue.number == 1000
+ assert issue.title == "Raise nitc from the dead"
+ assert issue.user.as(User).login == "privat"
+ assert issue.comments == 7
+ assert issue.created_at == "2014-12-11T02:55:09Z"
+ assert issue.closed_at == "2014-12-13T15:38:09Z"
+ assert issue.closed_by.as(User).login == "privat"
+ assert issue.body == "Raise dead on `nitc`.\nIt's super effective...\n"
+ assert issue.is_pull_request
+ end
+
+ # TODO issue comments
+ # TODO issue events
+
+ fun test_get_pull is test do
+ var pull = api.load_pull(repo, 1000)
+ assert pull isa Issue
+ assert pull.number == 1000
+ assert pull.title == "Raise nitc from the dead"
+ assert pull.user.as(User).login == "privat"
+ assert pull.comments == 7
+ assert pull.created_at == "2014-12-11T02:55:09Z"
+ assert pull.closed_at == "2014-12-13T15:38:09Z"
+ assert pull.merged_by.as(User).login == "privat"
+ assert pull.body == "Raise dead on `nitc`.\nIt's super effective...\n"
+ end
+
+ fun test_get_label is test do
+ var labl = api.load_label(repo, "ok_will_merge")
+ assert labl isa Label
+ assert labl.name == "ok_will_merge"
+ end
+
+ fun test_get_milestone is test do
+ var milestone = api.load_milestone(repo, 4)
+ assert milestone isa Milestone
+ assert milestone.title == "v1.0prealpha"
+ # TODO other fields
+ end
+
+ fun test_get_issue_event is test do
+ var event = api.load_issue_event(repo, 199674194)
+ assert event isa IssueEvent
+ assert event.actor.login == "privat"
+ assert event.event == "labeled"
+ assert event.labl.as(Label).name == "need_review"
+ end
+
+ fun test_get_issue_comment is test do
+ var comment = api.load_issue_comment(repo, 6020149)
+ assert comment isa IssueComment
+ assert comment.user.login == "privat"
+ assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
+ assert comment.issue_number == 10
+ end
+
+ fun test_get_comment is test do
+ var comment = api.load_commit_comment(repo, 8982707)
+ assert comment isa CommitComment
+ assert comment.user.login == "Morriar"
+ assert comment.body == "For testing purposes...\n"
+ assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
+ end
+
+ fun test_get_review_comments is test do
+ var comment = api.load_review_comment(repo, 21010363)
+ assert comment isa ReviewComment
+ assert comment.path == "src/modelize/modelize_property.nit"
+ assert comment.original_position == 26
+ assert comment.pull_number == 945
+ end
+end
--- /dev/null
+# `ini` - Read and write INI configuration files
+
+[INI files](https://en.wikipedia.org/wiki/INI_file) are simple text files with
+a basic structure composed of sections, properties and values used to store
+configuration parameters.
+
+Here's an example from the `package.ini` of this package:
+
+~~~
+import ini
+
+var package_ini = """
+[package]
+name=ini
+desc=Read and write INI configuration files.
+[upstream]
+git=https://github.com/nitlang/nit.git
+git.directory=lib/ini/
+"""
+~~~
+
+## Basic usage
+
+`IniFile` is used to parse INI strings and access their content:
+
+~~~
+var ini = new IniFile.from_string(package_ini)
+assert ini["package.name"] == "ini"
+assert ini["upstream.git.directory"] == "lib/ini/"
+assert ini["unknown.unknown"] == null
+~~~
+
+`IniFile` can also load INI configuration from a file:
+
+~~~
+package_ini.write_to_file("my_package.ini")
+
+ini = new IniFile.from_file("my_package.ini")
+assert ini["package.name"] == "ini"
+assert ini["upstream.git.directory"] == "lib/ini/"
+
+"my_package.ini".to_path.delete
+~~~
+
+INI content can be added or edited through the `IniFile` API then written to
+a stream or a file.
+
+~~~
+ini["package.name"] = "new name"
+ini["upstream.git.directory"] = "/dev/null"
+ini["section.key"] = "value"
+
+var stream = new StringWriter
+ini.write_to(stream)
+
+assert stream.to_s == """
+[package]
+name=new name
+desc=Read and write INI configuration files.
+[upstream]
+git=https://github.com/nitlang/nit.git
+git.directory=/dev/null
+[section]
+key=value
+"""
+~~~
+
+## INI content
+
+### Properties
+
+Properties are the basic element of the INI format.
+Every property correspond to a *key* associated to a *value* thanks to the equal (`=`) sign.
+
+~~~
+ini = new IniFile.from_string("""
+key1=value1
+key2=value2
+""")
+assert ini["key1"] == "value1"
+assert ini["key2"] == "value2"
+assert ini.length == 2
+~~~
+
+Accessing an unknown property returns `null`:
+
+~~~
+assert ini["unknown"] == null
+~~~
+
+Properties can be iterated over:
+
+~~~
+var i = 1
+for key, value in ini do
+ assert key == "key{i}"
+ assert value == "value{i}"
+ i += 1
+end
+~~~
+
+Property keys cannot contain the character `=`.
+Values can contain any character.
+Spaces are trimmed.
+
+~~~
+ini = new IniFile.from_string("""
+prop=erty1=value1
+ property2 = value2
+property3=value3 ; with semicolon
+""")
+assert ini[";property1"] == null
+assert ini["prop=erty1"] == null
+assert ini["prop"] == "erty1=value1"
+assert ini["property2"] == "value2"
+assert ini[" property2 "] == "value2"
+assert ini["property3"] == "value3 ; with semicolon"
+~~~
+
+Both keys and values are case sensitive.
+
+~~~
+ini = new IniFile.from_string("""
+Property1=value1
+property2=Value2
+""")
+assert ini["property1"] == null
+assert ini["Property1"] == "value1"
+assert ini["property2"] != "value2"
+assert ini["property2"] == "Value2"
+~~~
+
+### Sections
+
+Properties may be grouped into arbitrary sections.
+The section name appears on a line by itself between square brackets (`[` and `]`).
+
+All keys after the section declaration are associated with that section.
+The is no explicit "end of section" delimiter; sections end at the next section
+declaration or the end of the file.
+Sections cannot be nested.
+
+~~~
+var content = """
+key1=value1
+key2=value2
+[section1]
+key1=value3
+key2=value4
+[section2]
+key1=value5
+"""
+
+ini = new IniFile.from_string(content)
+assert ini["key1"] == "value1"
+assert ini["unknown"] == null
+assert ini["section1.key1"] == "value3"
+assert ini["section1.unknown"] == null
+assert ini["section2.key1"] == "value5"
+~~~
+
+Sections can be iterated over:
+
+~~~
+i = 1
+for section in ini.sections do
+ assert section.name == "section{i}"
+ assert section["key1"].has_prefix("value")
+ i += 1
+end
+~~~
+
+When iterating over a file properties, only properties at root are returned.
+`flatten` can be used to iterate over all properties including the one from
+sections.
+
+~~~
+assert ini.join(", ", ": ") == "key1: value1, key2: value2"
+assert ini.flatten.join(", ", ": ") ==
+ "key1: value1, key2: value2, section1.key1: value3, section1.key2: value4, section2.key1: value5"
+
+i = 0
+for key, value in ini do
+ i += 1
+ assert key == "key{i}" and value == "value{i}"
+end
+assert i == 2
+
+~~~
+
+Sections name may contain any character including brackets (`[` and `]`).
+Spaces are trimmed.
+
+~~~
+ini = new IniFile.from_string("""
+[[section1]]
+key=value1
+[ section 2 ]
+key=value2
+[section1.section3]
+key=value3
+""")
+assert ini.sections.length == 3
+assert ini["[section1].key"] == "value1"
+assert ini["section 2.key"] == "value2"
+assert ini["section1.section3.key"] == "value3"
+assert ini.sections.last.name == "section1.section3"
+~~~
+
+The dot `.` notation is used to create new sections with `[]=`.
+Unknown sections will be created on the fly.
+
+~~~
+ini = new IniFile
+ini["key"] = "value1"
+ini["section1.key"] = "value2"
+ini["section2.key"] = "value3"
+
+stream = new StringWriter
+ini.write_to(stream)
+assert stream.to_s == """
+key=value1
+[section1]
+key=value2
+[section2]
+key=value3
+"""
+~~~
+
+Sections can also be created manually:
+
+~~~
+ini = new IniFile
+ini["key"] = "value1"
+
+var section = new IniSection("section1")
+section["key"] = "value2"
+ini.sections.add section
+
+stream = new StringWriter
+ini.write_to(stream)
+assert stream.to_s == """
+key=value1
+[section1]
+key=value2
+"""
+~~~
+
+### Comments
+
+Comments are indicated by semicolon (`;`) or a number sign (`#`) at the begining
+of the line. Commented lines are ignored as well as empty lines.
+
+~~~
+ini = new IniFile.from_string("""
+; This is a comment.
+; property1=value1
+
+# This is another comment.
+# property2=value2
+""")
+assert ini.is_empty
+~~~
+
+### Unicode support
+
+INI files support Unicode:
+
+~~~
+ini = new IniFile.from_string("""
+property❤=héhé
+""")
+assert ini["property❤"] == "héhé"
+~~~
+
+## Error handling
+
+By default `IniFile` does not stop when it cannot parse a line in a string (loaded
+by `from_string` or `load_string`) or a file (loaded by `from_file` or `load_file`).
+
+~~~
+ini = new IniFile.from_string("""
+key1=value1
+key2
+key3=value3
+""")
+
+assert ini.length == 2
+assert ini["key1"] == "value1"
+assert ini["key2"] == null
+assert ini["key3"] == "value3"
+~~~
+
+
+This behaviour can be modified by setting `stop_on_first_error` to `true`.
+
+~~~
+ini = new IniFile.from_string("""
+key1=value1
+key2
+key3=value3
+""", stop_on_first_error = true)
+
+assert ini.length == 1
+assert ini["key1"] == "value1"
+assert ini["key2"] == null
+assert ini["key3"] == null
+~~~
+
+Wathever the value set for `stop_on_first_error`, errors can be checked thanks
+to the `errors` array:
+
+~~~
+assert ini.errors.length == 1
+assert ini.errors.first.message == "Unexpected string `key2` at line 2."
+~~~
# See the License for the specific language governing permissions and
# limitations under the License.
-# Handle ini config files.
+# Read and write INI configuration files
module ini
-# A configuration tree that can read and store data in ini format
+import core
+intrude import core::collection::hash_collection
+
+# Read and write INI configuration files
+#
+# In an INI file, properties (or keys) are associated to values thanks to the
+# equals symbol (`=`).
+# Properties may be grouped into section marked between brackets (`[` and `]`).
+#
+# ~~~
+# var ini_string = """
+# ; Example INI
+# key=value1
+# [section1]
+# key=value2
+# [section2]
+# key=value3
+# """
+# ~~~
+#
+# The main class, `IniFile`, can be created from an INI string and allows easy
+# access to its content.
+#
+# ~~~
+# # Read INI from string
+# var ini = new IniFile.from_string(ini_string)
+#
+# # Check keys presence
+# assert ini.has_key("key")
+# assert ini.has_key("section1.key")
+# assert not ini.has_key("not.found")
+#
+# # Access values
+# assert ini["key"] == "value1"
+# assert ini["section2.key"] == "value3"
+# assert ini["not.found"] == null
+#
+# # Access sections
+# assert ini.sections.length == 2
+# assert ini.section("section1")["key"] == "value2"
+# ~~~
#
-# Write example:
+# `IniFile` can also be used to create new INI files from scratch, or edit
+# existing ones through its API.
#
-# var config = new ConfigTree("config.ini")
-# config["goo"] = "goo"
-# config["foo.bar"] = "foobar"
-# config["foo.baz"] = "foobaz"
-# config.save
-# assert config.to_map.length == 3
+# ~~~
+# # Create a new INI file and write it to disk
+# ini = new IniFile
+# ini["key"] = "value1"
+# ini["section1.key"] = "value2"
+# ini["section2.key"] = "value3"
+# ini.write_to_file("my_config.ini")
#
-# Read example:
+# # Load the INI file from disk
+# ini = new IniFile.from_file("my_config.ini")
+# assert ini["key"] == "value1"
+# assert ini["section1.key"] == "value2"
+# assert ini["section2.key"] == "value3"
#
-# config = new ConfigTree("config.ini")
-# assert config.has_key("foo.bar")
-# assert config["foo.bar"] == "foobar"
-class ConfigTree
+# "my_config.ini".to_path.delete
+# ~~~
+class IniFile
super Writable
+ super HashMap[String, nullable String]
- # The ini file used to read/store data
- var ini_file: String
-
- init do if ini_file.file_exists then load
+ # Create a IniFile from a `string` content
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # key1=value1
+ # [section1]
+ # key2=value2
+ # """)
+ # assert ini["key1"] == "value1"
+ # assert ini["section1.key2"] == "value2"
+ # ~~~
+ #
+ # See also `stop_on_first_error` and `errors`.
+ init from_string(string: String, stop_on_first_error: nullable Bool) do
+ init stop_on_first_error or else false
+ load_string(string)
+ end
- # Get the config value for `key`
- #
- # var config = new ConfigTree("config.ini")
- # assert config["goo"] == "goo"
- # assert config["foo.bar"] == "foobar"
- # assert config["foo.baz"] == "foobaz"
- # 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
+ # Create a IniFile from a `file` content
+ #
+ # ~~~
+ # """
+ # key1=value1
+ # [section1]
+ # key2=value2
+ # """.write_to_file("my_config.ini")
+ #
+ # var ini = new IniFile.from_file("my_config.ini")
+ # assert ini["key1"] == "value1"
+ # assert ini["section1.key2"] == "value2"
+ #
+ # "my_config.ini".to_path.delete
+ # ~~~
+ #
+ # See also `stop_on_first_error` and `errors`.
+ init from_file(file: String, stop_on_first_error: nullable Bool) do
+ init stop_on_first_error or else false
+ load_file(file)
end
- # Get the config values under `key`
+ # Sections composing this IniFile
#
- # 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")
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # [section1]
+ # key1=value1
+ # [ section 2 ]
+ # key2=value2
+ # """)
+ # assert ini.sections.length == 2
+ # assert ini.sections.first.name == "section1"
+ # assert ini.sections.last.name == "section 2"
+ # ~~~
+ var sections = new Array[IniSection]
+
+ # Get a section by its `name`
#
- # Return null if the key does not exists.
+ # Returns `null` if the section is not found.
#
- # 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]
- for k, child in node.children do
- var value = child.value
- if value == null then continue
- map[k] = value
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # [section1]
+ # key1=value1
+ # [section2]
+ # key2=value2
+ # """)
+ # assert ini.section("section1") isa IniSection
+ # assert ini.section("section2").name == "section2"
+ # assert ini.section("not.found") == null
+ # ~~~
+ fun section(name: String): nullable IniSection do
+ for section in sections do
+ if section.name == name then return section
end
- return map
+ return null
end
- # Set `value` at `key`
- #
- # var config = new ConfigTree("config.ini")
- # assert config["foo.bar"] == "foobar"
- # config["foo.bar"] = "baz"
- # assert config["foo.bar"] == "baz"
- fun []=(key: String, value: nullable String) do
- set_node(key, value)
+ # Does this file contains no properties and no sections?
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("")
+ # assert ini.is_empty
+ #
+ # ini = new IniFile.from_string("""
+ # key=value
+ # """)
+ # assert not ini.is_empty
+ #
+ # ini = new IniFile.from_string("""
+ # [section]
+ # """)
+ # assert not ini.is_empty
+ # ~~~
+ redef fun is_empty do return super and sections.is_empty
+
+ # Is there a property located at `key`?
+ #
+ # Returns `true` if the `key` is not found of if its associated value is `null`.
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # key=value1
+ # [section1]
+ # key=value2
+ # [section2]
+ # key=value3
+ # """)
+ # assert ini.has_key("key")
+ # assert ini.has_key("section1.key")
+ # assert ini.has_key("section2.key")
+ # assert not ini.has_key("section1")
+ # assert not ini.has_key("not.found")
+ # ~~~
+ redef fun has_key(key) do return self[key] != null
+
+ # Get the value associated with a property (`key`)
+ #
+ # Returns `null` if the key is not found.
+ # Section properties can be accessed with the `.` notation.
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # key=value1
+ # [section1]
+ # key=value2
+ # [section2]
+ # key=value3
+ # """)
+ # assert ini["key"] == "value1"
+ # assert ini["section1.key"] == "value2"
+ # assert ini["section2.key"] == "value3"
+ # assert ini["section1"] == null
+ # assert ini["not.found"] == null
+ # ~~~
+ redef fun [](key) do
+ if key == null then return null
+ key = key.to_s.trim
+
+ # Look in root
+ var node = node_at(key)
+ if node != null then return node.value
+
+ # Look in sections
+ for section in sections do
+ # Matched if the section name is a prefix of the key
+ if not key.has_prefix(section.name) then continue
+ var skey = key.substring(section.name.length + 1, key.length)
+ if section.has_key(skey) then return section[skey]
+ end
+ return null
end
- # Is `key` in the config?
- #
- # var config = new ConfigTree("config.ini")
- # assert config.has_key("goo")
- # assert config.has_key("foo.bar")
- # assert not config.has_key("zoo")
- fun has_key(key: String): Bool do
- var parts = key.split(".").reversed
- var node = get_root(parts.pop)
- if node == null then return false
- while not parts.is_empty do
- node = node.get_child(parts.pop)
- if node == null then return false
+ # Set the `value` for the property locaated at `key`
+ #
+ # ~~~
+ # var ini = new IniFile
+ # ini["key"] = "value1"
+ # ini["section1.key"] = "value2"
+ # ini["section2.key"] = "value3"
+ #
+ # assert ini["key"] == "value1"
+ # assert ini["section1.key"] == "value2"
+ # assert ini["section2.key"] == "value3"
+ # assert ini.section("section1").name == "section1"
+ # assert ini.section("section2")["key"] == "value3"
+ # ~~~
+ redef fun []=(key, value) do
+ if value == null then return
+ var parts = key.split_once_on(".")
+
+ # No dot notation, store value in root
+ if parts.length == 1 then
+ super(key.trim, value.trim)
+ return
+ end
+
+ # First part matches a section, store value in it
+ var section = self.section(parts.first.trim)
+ if section != null then
+ section[parts.last.trim] = value.trim
+ return
end
- return true
+
+ # No section matched, create a new one and store value in it
+ section = new IniSection(parts.first.trim)
+ section[parts.last.trim] = value.trim
+ sections.add section
end
- # Get `self` as a Map of `key`, `value`
- #
- # var config = new ConfigTree("config.ini")
- # var map = config.to_map
- # assert map.has_key("goo")
- # assert map.has_key("foo.bar")
- # assert map.has_key("foo.baz")
- # assert map.length == 3
- fun to_map: Map[String, String] do
+ # Flatten `self` and its subsection in a `Map` of keys => values
+ #
+ # Properties from section are prefixed with their section names with the
+ # dot (`.`) notation.
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # key=value1
+ # [section]
+ # key=value2
+ # """)
+ # assert ini.flatten.join(", ", ": ") == "key: value1, section.key: value2"
+ # ~~~
+ fun flatten: Map[String, String] do
var map = new HashMap[String, String]
- for node in leaves do
- var value = node.value
+ for key, value in self do
if value == null then continue
- map[node.key] = value
+ map[key] = value
+ end
+ for section in sections do
+ for key, value in section do
+ if value == null then continue
+ map["{section.name}.{key}"] = value
+ end
end
return map
end
- redef fun to_s do return to_map.join(", ", ":")
-
- # Write `self` in `stream`
- #
- # var config = new ConfigTree("config.ini")
- # var out = new StringWriter
- # config.write_to(out)
- # assert out.to_s == """
- # goo=goo
- # [foo]
- # bar=foobar
- # baz=foobaz
- # """
+ # Write `self` to a `stream`
+ #
+ # Key with `null` values are ignored.
+ # The empty string can be used to represent an empty value.
+ #
+ # ~~~
+ # var ini = new IniFile
+ # ini["key"] = "value1"
+ # ini["key2"] = null
+ # ini["key3"] = ""
+ # ini["section1.key"] = "value2"
+ # ini["section1.key2"] = null
+ # ini["section2.key"] = "value3"
+ #
+ # var stream = new StringWriter
+ # ini.write_to(stream)
+ #
+ # assert stream.to_s == """
+ # key=value1
+ # key3=
+ # [section1]
+ # key=value2
+ # [section2]
+ # key=value3
+ # """
+ # ~~~
redef fun write_to(stream) do
- var todo = new Array[ConfigNode].from(roots.reversed)
- while not todo.is_empty do
- var node = todo.pop
- if node.children.not_empty then
- todo.add_all node.children.values.to_a.reversed
- end
- if node.children.not_empty and node.parent == null then
- stream.write("[{node.name}]\n")
- end
- var value = node.value
+ for key, value in self do
if value == null then continue
- var path = node.path
- if path.length > 1 then path.shift
- stream.write("{path.join(".")}={value}\n")
+ stream.write "{key}={value}\n"
+ end
+ for section in sections do
+ stream.write "[{section.name}]\n"
+ for key, value in section do
+ if value == null then continue
+ stream.write "{key}={value}\n"
+ end
end
end
- # Reload config from file
- # Done automatically at init
- #
- # Example with hierarchical ini file:
- #
- # # init file
- # var str = """
- # foo.bar=foobar
- # foo.baz=foobaz
- # goo=goo"""
- # str.write_to_file("config1.ini")
- # # load file
- # var config = new ConfigTree("config1.ini")
- # assert config["foo.bar"] == "foobar"
- #
- # Example with sections:
- #
- # # init file
- # str = """
- # goo=goo
- # [foo]
- # bar=foobar
- # baz=foobaz
- # [boo]
- # bar=boobar"""
- # str.write_to_file("config2.ini")
- # # load file
- # config = new ConfigTree("config2.ini")
- # assert config["foo.bar"] == "foobar"
- # assert config["boo.bar"] == "boobar"
- #
- # Example with both hierarchy and section:
- #
- # # init file
- # str = """
- # goo=goo
- # [foo]
- # bar.baz=foobarbaz
- # [goo.boo]
- # bar=gooboobar
- # baz.bar=gooboobazbar"""
- # str.write_to_file("config3.ini")
- # # load file
- # config = new ConfigTree("config3.ini")
- # assert config["goo"] == "goo"
- # assert config["foo.bar.baz"] == "foobarbaz"
- # assert config["goo.boo.bar"] == "gooboobar"
- # assert config["goo.boo.baz.bar"] == "gooboobazbar"
- #
- # Using the array notation
- #
- # str = """
- # foo[]=a
- # foo[]=b
- # foo[]=c"""
- # str.write_to_file("config4.ini")
- # # load file
- # config = new ConfigTree("config4.ini")
- # print config.to_map.join(":", ",")
- # assert config["foo.0"] == "a"
- # assert config["foo.1"] == "b"
- # assert config["foo.2"] == "c"
- # assert config.at("foo").values.join(",") == "a,b,c"
- fun load do
- roots.clear
- var stream = new FileReader.open(ini_file)
- var path: nullable String = null
- var line_number = 0
+ # Read INI content from `string`
+ #
+ # ~~~
+ # var ini = new IniFile
+ # ini.load_string("""
+ # section1.key1=value1
+ # section1.key2=value2
+ # [section2]
+ # key=value3
+ # """)
+ # assert ini["section1.key1"] == "value1"
+ # assert ini["section1.key2"] == "value2"
+ # assert ini["section2.key"] == "value3"
+ # ~~~
+ #
+ # Returns `true` if the parsing finished correctly.
+ #
+ # See also `stop_on_first_error` and `errors`.
+ fun load_string(string: String): Bool do
+ var stream = new StringReader(string)
+ var last_section = null
+ var was_error = false
+ var i = 0
while not stream.eof do
- var line = stream.read_line
- line_number += 1
+ i += 1
+ var line = stream.read_line.trim
if line.is_empty then
continue
else if line.has_prefix(";") then
continue
+ else if line.has_prefix("#") then
+ continue
else if line.has_prefix("[") then
- line = line.trim
- var key = line.substring(1, line.length - 2)
- path = key
- set_node(path, null)
+ var section = new IniSection(line.substring(1, line.length - 2).trim)
+ sections.add section
+ last_section = section
+ continue
else
var parts = line.split_once_on("=")
- if parts.length == 1 then
+ if parts.length != 2 then
+ # FIXME silent skip?
+ # we definitely need exceptions...
+ was_error = true
+ errors.add new IniError("Unexpected string `{line}` at line {i}.")
+ if stop_on_first_error then return was_error
continue
end
var key = parts[0].trim
- var val = parts[1].trim
- if path != null then key = "{path}.{key}"
- if key.has_suffix("[]") then
- set_array(key, val)
+ var value = parts[1].trim
+
+ if last_section != null then
+ last_section[key] = value
else
- set_node(key,val)
+ self[key] = value
end
end
end
stream.close
+ return was_error
end
- # Save config to file
- fun save do write_to_file(ini_file)
-
- private var roots = new Array[ConfigNode]
-
- # Append `value` to array at `key`
- private fun set_array(key: String, value: nullable String) do
- key = key.substring(0, key.length - 2)
- var len = 0
- var node = get_node(key)
- if node != null then len = node.children.length
- set_node("{key}.{len.to_s}", value)
- end
-
- private fun set_node(key: String, value: nullable String) do
- var parts = key.split(".").reversed
- var k = parts.pop
- var root = get_root(k)
- if root == null then
- root = new ConfigNode(k)
- if parts.is_empty then
- root.value = value
- end
- roots.add root
- end
- while not parts.is_empty do
- k = parts.pop
- var node = root.get_child(k)
- if node == null then
- node = new ConfigNode(k)
- node.parent = root
- root.children[node.name] = node
- end
- if parts.is_empty then
- node.value = value
- end
- root = node
- end
- end
-
- private fun get_node(key: String): nullable ConfigNode do
- var parts = key.split(".").reversed
- var node = get_root(parts.pop)
- while node != null and not parts.is_empty do
- node = node.get_child(parts.pop)
- end
- return node
- end
+ # Load a `file` content as INI
+ #
+ # New properties will be appended to the `self`, existing properties will be
+ # overwrote by the values contained in `file`.
+ #
+ # ~~~
+ # var ini = new IniFile
+ # ini["key1"] = "value1"
+ # ini["key2"] = "value2"
+ #
+ # """
+ # key2=changed
+ # key3=added
+ # """.write_to_file("load_config.ini")
+ #
+ # ini.load_file("load_config.ini")
+ # assert ini["key1"] == "value1"
+ # assert ini["key2"] == "changed"
+ # assert ini["key3"] == "added"
+ #
+ # "load_config.ini".to_path.delete
+ # ~~~
+ #
+ # The process fails silently if the file does not exist.
+ #
+ # ~~~
+ # ini = new IniFile
+ # ini.load_file("ini_not_found.ini")
+ # assert ini.is_empty
+ # ~~~
+ #
+ # Returns `true` if the parsing finished correctly.
+ #
+ # See also `stop_on_first_error` and `errors`.
+ fun load_file(file: String): Bool do return load_string(file.to_path.read_all)
- private fun get_root(name: String): nullable ConfigNode do
- for root in roots do
- if root.name == name then return root
- end
- return null
- end
+ # Stop parsing on the first error
+ #
+ # By default, `load_string` will skip unparsable properties so the string can
+ # be loaded.
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # key1=value1
+ # key2
+ # key3=value3
+ # """)
+ #
+ # assert ini.length == 2
+ # assert ini["key1"] == "value1"
+ # assert ini["key2"] == null
+ # assert ini["key3"] == "value3"
+ # ~~~
+ #
+ # Set `stop_on_first_error` to `true` to force the parsing to stop.
+ #
+ # ~~~
+ # ini = new IniFile
+ # ini.stop_on_first_error = true
+ # ini.load_string("""
+ # key1=value1
+ # key2
+ # key3=value3
+ # """)
+ #
+ # assert ini.length == 1
+ # assert ini["key1"] == "value1"
+ # assert ini["key2"] == null
+ # assert ini["key3"] == null
+ # ~~~
+ #
+ # See also `errors`.
+ var stop_on_first_error = false is optional, writable
- private fun leaves: Array[ConfigNode] do
- var res = new Array[ConfigNode]
- var todo = new Array[ConfigNode]
- todo.add_all roots
- while not todo.is_empty do
- var node = todo.pop
- if node.children.is_empty then
- res.add node
- else
- todo.add_all node.children.values
- end
- end
- return res
- end
+ # Errors found during parsing
+ #
+ # Wathever the value of `stop_on_first_error`, errors from parsing a string
+ # or a file are logged into `errors`.
+ #
+ # ~~~
+ # var ini = new IniFile.from_string("""
+ # key1=value1
+ # key2
+ # key3=value3
+ # """)
+ #
+ # assert ini.errors.length == 1
+ # assert ini.errors.first.message == "Unexpected string `key2` at line 2."
+ # ~~~
+ #
+ # `errors` is not cleared between two parsing:
+ #
+ # ~~~
+ # ini.load_string("""
+ # key4
+ # key5=value5
+ # """)
+ #
+ # assert ini.errors.length == 2
+ # assert ini.errors.last.message == "Unexpected string `key4` at line 1."
+ # ~~~
+ #
+ # See also `stop_on_first_error`.
+ var errors = new Array[IniError]
end
-private class ConfigNode
-
- var parent: nullable ConfigNode = null
- var children = new HashMap[String, ConfigNode]
- var name: String is writable
- var value: nullable String = null
+# A section in a IniFile
+#
+# Section properties values are strings associated keys.
+# Sections cannot be nested.
+#
+# ~~~
+# var section = new IniSection("section")
+# section["key1"] = "value1"
+# section["key2"] = "value2"
+#
+# assert section.length == 2
+# assert section["key1"] == "value1"
+# assert section["not.found"] == null
+# assert section.join(", ", ": ") == "key1: value1, key2: value2"
+#
+# var i = 0
+# for key, value in section do
+# assert key.has_prefix("key")
+# assert value.has_prefix("value")
+# i += 1
+# end
+# assert i == 2
+# ~~~
+class IniSection
+ super HashMap[String, nullable String]
- fun key: String do
- var parent = self.parent
- if parent == null then
- return name
- end
- return "{parent.key}.{name}"
- end
+ # Section name
+ var name: String
- fun path: Array[String] do
- var parent = self.parent
- if parent == null then
- return [name]
- end
- var res = new Array[String].from(parent.path)
- res.add name
- return res
+ # Get the value associated with `key`
+ #
+ # Returns `null` if the `key` is not found.
+ #
+ # ~~~
+ # var section = new IniSection("section")
+ # section["key"] = "value1"
+ # section["sub.key"] = "value2"
+ #
+ # assert section["key"] == "value1"
+ # assert section["sub.key"] == "value2"
+ # assert section["not.found"] == null
+ # ~~~
+ redef fun [](key) do
+ if not has_key(key) then return null
+ return super
end
+end
- fun get_child(name: String): nullable ConfigNode do
- if children.has_key(name) then
- return children[name]
- end
- return null
- end
+# Error for `IniFile` parsing
+class IniError
+ super Error
end
tags=format,lib
maintainer=Alexandre Terrasa <alexandre@moz-code.org>
license=Apache-2.0
-desc=Handle ini config files
+desc=Read and write INI configuration files
[upstream]
browse=https://github.com/nitlang/nit/tree/master/lib/ini/
git=https://github.com/nitlang/nit.git
--- /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.
+
+# A simple logger for Nit
+#
+# ## Basic Usage
+#
+# Create a new `Logger` with a severity level threshold set to `warn_level`:
+#
+# ~~~
+# var logger = new Logger(warn_level)
+# ~~~
+#
+# Messages with a severity equal or higher than `warn_level` will be displayed:
+#
+# ~~~
+# logger.error "Displays an error."
+# logger.warn "Displays a warning."
+# ~~~
+#
+# Messages with a lower severity are silenced:
+#
+# ~~~
+# logger.info "Displays nothing."
+# ~~~
+#
+# `FileLogger` can be used to output the messages into a file:
+#
+# ~~~
+# var log_file = "my.log"
+#
+# logger = new FileLogger(warn_level, log_file, append = false)
+# logger.error("An error")
+# logger.info("Some info")
+# logger.close
+#
+# assert log_file.to_path.read_all == "An error\n"
+# log_file.to_path.delete
+# ~~~
+#
+# ## Severity levels
+#
+# Each message is associated with a level that indicate its severity.
+# Only messages with a severity equal to or higher than the logger `level`
+# threshold will be displayed.
+#
+# Severity levels from the most severe to the least severe:
+#
+# * `unknown_level`: An unknown message that should always be outputted.
+# * `fatal_level`: An unhandleable error that results in a program crash.
+# * `error_level`: A handleable error condition.
+# * `warn_level`: A warning.
+# * `info_level`: Generic (useful) information about system operation.
+# * `debug_level`: Low-level information for developpers.
+#
+# ## Formatting messages
+#
+# You can create custom formatters by implementing the `Formatter` interface.
+#
+# ~~~
+# class MyFormatter
+# super Formatter
+#
+# redef fun format(level, message) do
+# if level < warn_level then return super
+# return "!!!{message}!!!"
+# end
+# end
+# ~~~
+#
+# See `DefaultFormatter` for a more advanced implementation example.
+#
+# Each Logger can be given a default formatter used to format the every messages
+# before outputting them:
+#
+# ~~~
+# var formatter = new MyFormatter
+# var stderr = new StringWriter
+# var logger = new Logger(warn_level, stderr, formatter)
+#
+# logger.warn("This is a warning.")
+# assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
+# ~~~
+#
+# Optionally, a `Formatter` can be given to replace the `default_formatter`
+# used by default:
+#
+# ~~~
+# # Create a formatter with no default decorator
+# logger = new Logger(warn_level, stderr, null)
+#
+# # Display a message without any formatter
+# logger.warn("This is a warning.")
+# assert stderr.to_s.trim.split("\n").last == "This is a warning."
+#
+# # Display a message with a custom formatter
+# logger.warn("This is a warning.", formatter)
+# assert stderr.to_s.trim.split("\n").last == "!!!This is a warning.!!!"
+# ~~~
+module logger
+
+import console
+
+# A simple logging utility
+#
+# `Logger` provides a simple way to output messages from applications.
+#
+# Each message is associated with a level that indicate its severity.
+# Only messages with a severity equal to or higher than the logger `level`
+# threshold will be displayed.
+#
+# ~~~
+# var logger = new Logger(warn_level)
+# assert logger.unknown("unkown")
+# assert logger.fatal("fatal")
+# assert logger.error("error")
+# assert logger.warn("warn")
+# assert not logger.info("info")
+# assert not logger.debug("debug")
+# ~~~
+class Logger
+
+ # Severity threshold
+ #
+ # Messages with a severity level greater than or equal to `level` will be displayed.
+ # Default is `warn_level`.
+ #
+ # See `unknown_level`, `fatal_level`, error_level``, `warn_level`,
+ # `info_level` and `debug_level`.
+ var level: Int = warn_level is optional, writable
+
+ # Kind of `Writer` used to output messages
+ type OUT: Writer
+
+ # Writer used to output messages
+ #
+ # Default is `stderr`.
+ var out: OUT = stderr is optional
+
+ # Formatter used to format messages before outputting them
+ #
+ # By default no formatter is used.
+ #
+ # See `DefaultFormatter`.
+ var default_formatter: nullable Formatter = null is optional, writable
+
+ # Output a message with `level` severity
+ #
+ # Only output messages with `level` severity greater than of equal to `self.level`.
+ #
+ # ~~~
+ # var stderr = new StringWriter
+ # var logger = new Logger(warn_level, stderr, null)
+ #
+ # # This message will be displayed:
+ # assert logger.warn("This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ #
+ # # This message will not:
+ # assert not logger.info("This is some info.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ # ~~~
+ #
+ # Each logger can be given a default formatter used to format the messages
+ # before outputting them:
+ #
+ # ~~~
+ # var formatter = new DefaultFormatter(no_color = true)
+ # logger = new Logger(warn_level, stderr, formatter)
+ # logger.warn("This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
+ # ~~~
+ #
+ # Optionally, a `Formatter` can be given to replace the `default_formatter`
+ # used by default.
+ #
+ # ~~~
+ # # Create a formatter with no default decorator
+ # logger = new Logger(warn_level, stderr, null)
+ #
+ # # Display a message without any formatter
+ # logger.warn("This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ #
+ # # Display a message with a custom formatter
+ # logger.warn("This is a warning.", formatter)
+ # assert stderr.to_s.trim.split("\n").last == "Warning: This is a warning."
+ # ~~~
+ fun add(level: Int, message: Writable, formatter: nullable Formatter): Bool do
+ var format = formatter or else default_formatter
+ if format == null then
+ return add_raw(level, message)
+ end
+ return add_raw(level, format.format(level, message))
+ end
+
+ # Output a message with `level` severity without formatting it
+ #
+ # Only output messages with `level` severity greater than of equal to `self.level`.
+ #
+ # ~~~
+ # var stderr = new StringWriter
+ # var logger = new Logger(warn_level, stderr, null)
+ #
+ # # This message will be displayed:
+ # assert logger.add_raw(warn_level, "This is a warning.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ #
+ # # This message will not:
+ # assert not logger.add_raw(info_level, "This is some info.")
+ # assert stderr.to_s.trim.split("\n").last == "This is a warning."
+ # ~~~
+ fun add_raw(level: Int, message: Writable): Bool do
+ if level < self.level then return false
+ out.write(message.write_to_string)
+ out.write("\n")
+ return true
+ end
+
+ # Output a message with `unknown_level` severity
+ #
+ # Unkown severity messages are always outputted.
+ fun unknown(message: String, formatter: nullable Formatter): Bool do
+ return add(unknown_level, message, formatter)
+ end
+
+ # Output a message with `fatal_level` severity
+ fun fatal(message: String, formatter: nullable Formatter): Bool do
+ return add(fatal_level, message, formatter)
+ end
+
+ # Output a message with `error_level` severity
+ fun error(message: String, formatter: nullable Formatter): Bool do
+ return add(error_level, message, formatter)
+ end
+
+ # Output a message with `warn_level` severity
+ fun warn(message: String, formatter: nullable Formatter): Bool do
+ return add(warn_level, message, formatter)
+ end
+
+ # Output a message with `info_level` severity
+ fun info(message: String, formatter: nullable Formatter): Bool do
+ return add(info_level, message, formatter)
+ end
+
+ # Output a message with `debug` severity
+ fun debug(message: String, formatter: nullable Formatter): Bool do
+ return add(debug_level, message, formatter)
+ end
+end
+
+# Log messages to a file
+#
+# ~~~
+# var log_file = "my_file.log"
+# var logger = new FileLogger(warn_level, log_file, append = false)
+# logger.error("An error")
+# logger.info("Some info")
+# logger.close
+# assert log_file.to_path.read_all == "An error\n"
+#
+# logger = new FileLogger(warn_level, log_file, append = true)
+# logger.error("Another error")
+# logger.close
+# assert log_file.to_path.read_all == "An error\nAnother error\n"
+#
+# log_file.to_path.delete
+# ~~~
+class FileLogger
+ super Logger
+ autoinit level, file, append, default_formatter
+
+ redef type OUT: FileWriter
+
+ # File where messages will be written
+ var file: String
+
+ # Append messages to `file`
+ #
+ # If `append` is `false`, the `file` will be overwritten.
+ var append: Bool = true is optional
+
+ init do
+ var old = null
+ if append then
+ old = file.to_path.read_all
+ end
+ out = new FileWriter.open(file)
+ out.set_buffering_mode(0, buffer_mode_line)
+ if old != null then
+ out.write(old)
+ end
+ end
+
+ # Close the logger and its `file`
+ fun close do out.close
+end
+
+# Format messages before outputing them
+#
+# A `Logger` can use a `Formatter` to format the messages before outputting them.
+#
+# See `DefaultFormatter`.
+interface Formatter
+
+ # Format `message` depending of its severity `level`
+ fun format(level: Int, message: Writable): Writable do return message
+end
+
+# Default `Logger` formatter
+#
+# The default formatter decorates the messages with severity labels and colors.
+class DefaultFormatter
+ super Formatter
+
+ # Do not decorate messages with colors
+ #
+ # ~~~
+ # var formatter = new DefaultFormatter(no_color = true)
+ # assert formatter.format(error_level, "My message.") == "Error: My message."
+ # ~~~
+ var no_color = false is optional, writable
+
+ redef fun format(level, message) do
+ var string = message.write_to_string
+
+ if level == fatal_level then
+ string = "Fatal: {string}"
+ else if level == error_level then
+ string = "Error: {string}"
+ else if level == warn_level then
+ string = "Warning: {string}"
+ else if level == info_level then
+ string = "Info: {string}"
+ else if level == debug_level then
+ string = "Debug: {string}"
+ end
+
+ if no_color then return string
+
+ if level == fatal_level then
+ return string.red
+ else if level == error_level then
+ return string.red
+ else if level == warn_level then
+ return string.yellow
+ else if level == info_level then
+ return string.purple
+ else if level == debug_level then
+ return string.blue
+ end
+
+ return string
+ end
+end
+
+redef class Sys
+
+ # Unknown severity level
+ #
+ # These messages are always displayed.
+ #
+ # See `Logger`.
+ var unknown_level = 5
+
+ # Fatal severity level
+ #
+ # See `Logger`.
+ var fatal_level = 4
+
+ # Error severity level
+ #
+ # See `Logger`.
+ var error_level = 3
+
+ # Warning severity level
+ #
+ # See `Logger`.
+ var warn_level = 2
+
+ # Info severity level
+ #
+ # See `Logger`.
+ var info_level = 1
+
+ # Debug severity level
+ #
+ # See `Logger`.
+ var debug_level = 0
+end
--- /dev/null
+[package]
+name=logger
+tags=logging,lib
+maintainer=Alexandre Terrasa <alexandre@moz-code.org>
+license=Apache-2.0
+desc=A simple logger for Nit
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/lib/logger/
+git=https://github.com/nitlang/nit.git
+git.directory=lib/logger/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
Next, we’ll create a middleware handler called “LogHandler” that prints the requested
uri, the response status and the time it took to Popcorn to process the request.
-This example gives a simplified version of the `RequestClock` and `ConsoleLog` middlewares.
+This example gives a simplified version of the `RequestClock` and `PopLogger` middlewares.
~~~
import popcorn
be used to develop your app faster.
* `RequestClock`: initializes requests clock.
-* `ConsoleLog`: displays resquest and response status in console (can be used with `RequestClock`).
+* `PopLogger`: displays resquest and response status in console (can be used with `RequestClock`).
* `SessionInit`: initializes requests session (see the `Sessions` section).
* `StaticServer`: serves static files (see the `Serving static files with Popcorn` section).
* `Router`: a mountable mini-app (see the `Mountable routers` section).
module pop_logging
import pop_handlers
-import console
+import logger
import realtime
# Initialize a clock for the resquest.
end
# Display log info about request processing.
-class ConsoleLog
+class PopLogger
+ super Logger
super Handler
- # Logger level
- #
- # * `0`: silent
- # * `1`: errors
- # * `2`: warnings
- # * `3`: info
- # * `4`: debug
- #
- # Request status are always logged, whatever the logger level is.
- var level = 4 is writable
-
# Do we want colors in the console output?
- var no_colors = false
+ var no_color = false is optional
+
+ redef var default_formatter = new PopFormatter(no_color) is optional
redef fun all(req, res) do
var clock = req.clock
if clock != null then
- log "{req.method} {req.url} {status(res)} ({clock.total}s)"
+ add_raw(info_level, "{req.method} {req.url} {status(res)} ({clock.total}s)")
else
- log "{req.method} {req.url} {status(res)}"
+ add_raw(info_level, "{req.method} {req.url} {status(res)}")
end
end
# Colorize the request status.
private fun status(res: HttpResponse): String do
- if no_colors then return res.status_code.to_s
+ if no_color then return res.status_code.to_s
return res.color_status
end
+end
- # Display a `message` with `level`.
- #
- # Message will only be displayed if `level <= self.level`.
- # Colors will be used depending on `colors`.
- #
- # Use `0` for no coloration.
- private fun display(level: Int, message: String) do
- if level > self.level then return
- if no_colors then
- print message
- return
+class PopFormatter
+ super Formatter
+
+ # Do not decorate messages with colors
+ var no_color = false is optional, writable
+
+ redef fun format(level, message) do
+ var string = message.write_to_string
+
+ if level == fatal_level then
+ string = "[FATAL] {string}"
+ else if level == error_level then
+ string = "[ERROR] {string}"
+ else if level == warn_level then
+ string = "[WARN] {string}"
+ else if level == info_level then
+ string = "[INFO] {string}"
+ else if level == debug_level then
+ string = "[DEBUG] {string}"
end
- if level == 0 then print message
- if level == 1 then print message.red
- if level == 2 then print message.yellow
- if level == 3 then print message.blue
- if level == 4 then print message.gray
- end
-
- # Display a message wathever the `level`
- fun log(message: String) do display(0, message)
- # Display a red error `message`.
- fun error(message: String) do display(1, "[ERROR] {message}")
-
- # Display a yellow warning `message`.
- fun warning(message: String) do display(2, "[WARN] {message}")
-
- # Display a blue info `message`.
- fun info(message: String) do display(3, "[INFO] {message}")
+ if no_color then return string
+
+ if level == fatal_level then
+ return string.red
+ else if level == error_level then
+ return string.red
+ else if level == warn_level then
+ return string.yellow
+ else if level == info_level then
+ return string.blue
+ else if level == debug_level then
+ return string.gray
+ end
- # Display a gray debug `message`.
- fun debug(message: String) do display(4, "[DEBUG] {message}")
+ return string
+ end
end
+
redef class HttpRequest
# Time that request was received by the Popcorn app.
var clock: nullable Clock = null
import popcorn
import popcorn::pop_config
-import popcorn::pop_logging
import popcorn::pop_json
import popcorn::pop_repos
# Saves logs into a MongoDB collection
class PopTracker
- super ConsoleLog
super TrackerHandler
redef fun all(req, res) do
graphviz \
libunwind-dev \
pkg-config \
+ libicu-dev \
# Get the code!
git \
ca-certificates \
super CmdEntity
# Ini file
- var ini: nullable ConfigTree = null
+ var ini: nullable IniFile = null
redef fun init_command do
var res = super
# Attach homonymous `ini` file to the package
var inipath = path.dirname / "{pn}.ini"
if inipath.file_exists then
- var ini = new ConfigTree(inipath)
+ var ini = new IniFile.from_file(inipath)
mpackage.ini = ini
end
end
var parent = null
var inipath = dirpath / "package.ini"
if inipath.file_exists then
- ini = new ConfigTree(inipath)
+ ini = new IniFile.from_file(inipath)
end
if ini == null then
# The `ini` file is given as is and might contain invalid or missing information.
#
# Some packages, like stand-alone packages or virtual packages have no `ini` file associated.
- var ini: nullable ConfigTree = null
+ var ini: nullable IniFile = null
# Array of relative source paths excluded according to the `source.exclude` key of the `ini`
var excludes: nullable Array[String] is lazy do
var ini_path = ini_path
if ini_path == null then return
- var ini = new ConfigTree(ini_path)
+ var ini = new IniFile.from_file(ini_path)
- ini.check_key(toolcontext, self, "package.name", name)
- ini.check_key(toolcontext, self, "package.desc")
- ini.check_key(toolcontext, self, "package.tags")
+ 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(toolcontext, self, "package.maintainer")
+ 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)
# ini.check_key(toolcontext, self, "package.more_contributors", contribs.join(", "))
# end
- ini.check_key(toolcontext, self, "package.license", license)
- ini.check_key(toolcontext, self, "upstream.browse", browse_url)
- ini.check_key(toolcontext, self, "upstream.git", git_url)
- ini.check_key(toolcontext, self, "upstream.git.directory", git_dir)
- ini.check_key(toolcontext, self, "upstream.homepage", homepage_url)
- ini.check_key(toolcontext, self, "upstream.issues", issues_url)
+ 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.to_map.keys do
+ 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.ini_file}`")
+ "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 ConfigTree(ini_path)
+ var ini = new IniFile.from_file(ini_path)
ini.update_value("package.name", name)
ini.update_value("package.desc", "")
ini.update_value("upstream.homepage", homepage_url)
ini.update_value("upstream.issues", issues_url)
- ini.save
+ ini.write_to_file(ini_path)
return ini_path
end
end
end
-redef class ConfigTree
- private fun check_key(toolcontext: ToolContext, mpackage: MPackage, key: String, value: nullable String) do
+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}`")
exit 1
end
- var ini = new ConfigTree(ini_path)
+ var ini = new IniFile.from_file(ini_path)
var import_line = ini["package.import"]
if import_line == null then
print_error "The local `package.ini` declares no external dependencies."
print ini_path.to_path.read_all
end
- var ini = new ConfigTree(ini_path)
+ var ini = new IniFile.from_file(ini_path)
var git_repo = ini["upstream.git"]
if git_repo == null then
print_error "Package description invalid, or it does not declare a Git repository"
end
# Recursive install
- var ini = new ConfigTree(target_dir/"package.ini")
+ var ini = new IniFile.from_file(target_dir/"package.ini")
var import_line = ini["package.import"]
if import_line != null then
install_packages import_line
for file in files do
var ini_path = nitpm_lib_dir / file / "package.ini"
if verbose then print "- Reading ini file at {ini_path}"
- var ini = new ConfigTree(ini_path)
+ var ini = new IniFile.from_file(ini_path)
var tags = ini["package.tags"]
name_to_desc[file] = tags
end
# Try to load the file as a markdown document
var mdoc = modelbuilder.load_markdown(a)
- page.add modelbuilder.test_mdoc(mdoc)
+ var ts = modelbuilder.test_mdoc(mdoc)
+ if not ts.children.is_empty then page.add ts
end
for a in module_files do
var g = modelbuilder.identify_group(a)
if g == null then continue
- page.add modelbuilder.test_group(g)
+ var ts = modelbuilder.test_group(g)
+ if not ts.children.is_empty then page.add ts
end
for m in mmodules do
- page.add modelbuilder.test_markdown(m)
- var ts = modelbuilder.test_unit(m)
- if ts != null then page.add ts
+ var ts
+ ts = modelbuilder.test_markdown(m)
+ if not ts.children.is_empty then page.add ts
+ ts = modelbuilder.test_unit(m)
+ if ts != null and not ts.children.is_empty then page.add ts
end
var file = toolcontext.opt_output.value
app.use("/oauth", new GithubOAuthCallBack(config.github_client_id, config.github_client_secret))
app.use("/logout", new GithubLogout)
app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
- app.use_after("/*", new ConsoleLog)
+ app.use_after("/*", new PopLogger(info_level))
app.listen(config.app_host, config.app_port)
end
</system-out></testcase><testcase classname="nitunit.test_nitunit.X" name="foo1" time="0.0"><failure message="Syntax Error: unexpected operator '!'."></failure><system-out>assert !@#$%^&*()
</system-out></testcase><testcase classname="nitunit.test_nitunit.X" name="foo2" time="0.0"><system-err></system-err><system-out>var x = new X
assert x.foo2
-</system-out></testcase></testsuite><testsuite package="test_test_nitunit::test_test_nitunit"></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo1" time="0.0"><error message="Runtime Error in file nitunit.out/gen_test_test_nitunit.nit">Runtime error: Assert failed (test_test_nitunit.nit:38)
+</system-out></testcase></testsuite><testsuite package="test_test_nitunit"><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo1" time="0.0"><error message="Runtime Error in file nitunit.out/gen_test_test_nitunit.nit">Runtime error: Assert failed (test_test_nitunit.nit:38)
</error></testcase><testcase classname="nitunit.test_test_nitunit.TestX" name="test_foo2" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
Docunits: Entities: 4; Documented ones: 0; With nitunits: 0
Test suites: Classes: 1; Test Cases: 2; Failures: 0
[SUCCESS] All 2 tests passed.
-<testsuites><testsuite package="test_nitunit5::test_nitunit5"></testsuite><testsuite package="test_nitunit5"><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_set" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_suite_path" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit5"><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_set" time="0.0"><system-err></system-err></testcase><testcase classname="nitunit.test_nitunit5.TestNitunit5" name="test_path_is_suite_path" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
Test suites: Classes: 1; Test Cases: 3; Failures: 3
[FAILURE] 3/3 tests failed.
`nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit6::test_nitunit6"></testsuite><testsuite package="test_nitunit6"><testcase classname="nitunit.test_nitunit6.TestNitunit6" name="test_foo" time="0.0"><failure message="Nitunit Error: before module test failed"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit6"><testcase classname="nitunit.test_nitunit6.TestNitunit6" name="test_foo" time="0.0"><failure message="Nitunit Error: before module test failed"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
Test suites: Classes: 1; Test Cases: 3; Failures: 1
[FAILURE] 1/3 tests failed.
`nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit7::test_nitunit7"></testsuite><testsuite package="test_nitunit7"><testcase classname="nitunit.test_nitunit7.TestNitunit7" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit7"><testcase classname="nitunit.test_nitunit7.TestNitunit7" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
Test suites: Classes: 1; Test Cases: 3; Failures: 1
[FAILURE] 1/3 tests failed.
`nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit8::test_nitunit8"></testsuite><testsuite package="test_nitunit8"><testcase classname="nitunit.test_nitunit8.TestNitunit8" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit8"><testcase classname="nitunit.test_nitunit8.TestNitunit8" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
Test suites: Classes: 1; Test Cases: 7; Failures: 1
[FAILURE] 1/7 tests failed.
`nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit11::test_nitunit11"></testsuite><testsuite package="test_nitunit11"><testcase classname="nitunit.test_nitunit11.TestNitunit11" name="test_baz" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit11"><testcase classname="nitunit.test_nitunit11.TestNitunit11" name="test_baz" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
Test suites: Classes: 3; Test Cases: 8; Failures: 7
[FAILURE] 7/8 tests failed.
`nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit4>"></testsuite><testsuite package="test_nitunit4::nitunit4"></testsuite><testsuite package="test_nitunit4::test_bad_comp"></testsuite><testsuite package="test_bad_comp"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
+<testsuites><testsuite package="test_bad_comp"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
</failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`.
-</failure></testcase></testsuite><testsuite package="test_nitunit4::test_bad_comp2"></testsuite><testsuite package="test_bad_comp2"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">nitunit.out/gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
+</failure></testcase></testsuite><testsuite package="test_bad_comp2"><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_good" time="0.0"><failure message="Compilation Error">nitunit.out/gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
</failure></testcase><testcase classname="nitunit.test_nitunit4.TestSuiteBadComp" name="test_bad" time="0.0"><failure message="Compilation Error">nitunit.out/gen_test_bad_comp2.nit:11,10--17: Error: expected 1 argument(s) for `test_bad(param: Bool)`; got 0. See introduction at `test_nitunit4::TestSuiteBadComp::test_bad`.
-</failure></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4"></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_foo" time="0.0"><error message="Runtime Error in file nitunit.out/gen_test_nitunit4.nit">Before Test
+</failure></testcase></testsuite><testsuite package="test_nitunit4"><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_foo" time="0.0"><error message="Runtime Error in file nitunit.out/gen_test_nitunit4.nit">Before Test
Tested method
After Test
Runtime assert: <TestTestSuite>.before
</error></testcase><testcase classname="nitunit.test_nitunit4.TestTestSuite" name="test_sav_conflict" time="0.0"><error message="Conflicting expected output: test_nitunit4/test_nitunit4.sav/test_sav_conflict.res, test_nitunit4/sav/test_sav_conflict.res and test_nitunit4/test_sav_conflict.res all exist">Before Test
Tested method
After Test
-</error></testcase></testsuite><testsuite package="test_nitunit4::test_nitunit4_base"></testsuite></testsuites>
\ No newline at end of file
+</error></testcase></testsuite></testsuites>
\ No newline at end of file
echo >>$xml "</testsuite></testsuites>"
+if type junit2html >/dev/null; then
+ junit2html "$xml"
+fi
+
if [ -n "$nok" ]; then
exit 1
else