From: Jean Privat Date: Wed, 3 Jul 2019 18:50:57 +0000 (-0400) Subject: Merge: nitrpg: Move `nitrpg` to its own repository X-Git-Url: http://nitlanguage.org?hp=5dbab8fac6905d57abf78076c58916b0b34b35ce Merge: nitrpg: Move `nitrpg` to its own repository `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 Pull-Request: #2755 --- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a0711b3..70b91e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -77,7 +77,7 @@ test_some: &test_some artifacts: paths: - tests/errlist - - tests/*.xml + - tests/*.xml* when: always reports: junit: tests/*.xml @@ -90,6 +90,7 @@ nitunit_some: - 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* @@ -221,6 +222,7 @@ nitunit_lib: - 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* @@ -238,6 +240,7 @@ nitunit_src: - 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* @@ -329,6 +332,21 @@ build_more_tools: - 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: @@ -340,6 +358,16 @@ build_doc: 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: diff --git a/contrib/nitiwiki/src/wiki_base.nit b/contrib/nitiwiki/src/wiki_base.nit index 2e93a74..f1fb6ae 100644 --- a/contrib/nitiwiki/src/wiki_base.nit +++ b/contrib/nitiwiki/src/wiki_base.nit @@ -612,7 +612,13 @@ end # # 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 @@ -779,8 +785,8 @@ class WikiConfig 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 @@ -834,7 +840,13 @@ 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 diff --git a/lib/config/config.nit b/lib/config/config.nit index da03f7c..f07ec47 100644 --- a/lib/config/config.nit +++ b/lib/config/config.nit @@ -299,7 +299,7 @@ class IniConfig 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") @@ -311,7 +311,7 @@ class IniConfig redef fun parse_options(args) do super - ini = new ConfigTree(config_file) + ini = new IniFile.from_file(config_file) end # Default config file path diff --git a/lib/github/api.nit b/lib/github/api.nit index 4eb9a97..fb6fff9 100644 --- a/lib/github/api.nit +++ b/lib/github/api.nit @@ -102,20 +102,24 @@ class GithubAPI # 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}") @@ -173,10 +177,12 @@ class GithubAPI # # 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 @@ -185,11 +191,13 @@ class GithubAPI # # 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 @@ -201,8 +209,12 @@ class GithubAPI 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. @@ -301,12 +313,14 @@ class GithubAPI # # 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 @@ -339,11 +353,13 @@ class GithubAPI # # 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 @@ -352,11 +368,13 @@ class GithubAPI # # 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 @@ -406,12 +424,14 @@ class GithubAPI # # 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 @@ -420,11 +440,13 @@ class GithubAPI # # 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 @@ -433,11 +455,13 @@ class GithubAPI # # 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 @@ -446,15 +470,17 @@ class GithubAPI # # 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 @@ -463,13 +489,15 @@ class GithubAPI # # 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 @@ -478,13 +506,15 @@ class GithubAPI # # 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 @@ -493,13 +523,15 @@ class GithubAPI # # 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 diff --git a/lib/github/loader.nit b/lib/github/loader.nit index 4ffa501..6a60c5d 100644 --- a/lib/github/loader.nit +++ b/lib/github/loader.nit @@ -104,12 +104,18 @@ class LoaderConfig # 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 @@ -128,15 +134,19 @@ class LoaderConfig # 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 @@ -412,7 +422,7 @@ class Loader 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 diff --git a/lib/github/tests/mock/errors_404.res b/lib/github/tests/mock/errors_404.res new file mode 100644 index 0000000..e0a95cb --- /dev/null +++ b/lib/github/tests/mock/errors_404.res @@ -0,0 +1 @@ +{"message":"Not Found","documentation_url":"https://developer.github.com/v3"} \ No newline at end of file diff --git a/lib/github/tests/mock/repo_branches_master.res b/lib/github/tests/mock/repo_branches_master.res new file mode 100644 index 0000000..dcb088a --- /dev/null +++ b/lib/github/tests/mock/repo_branches_master.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_branches_nit.res b/lib/github/tests/mock/repo_branches_nit.res new file mode 100644 index 0000000..ff67214 --- /dev/null +++ b/lib/github/tests/mock/repo_branches_nit.res @@ -0,0 +1 @@ +[{"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 diff --git a/lib/github/tests/mock/repo_comments_8982707.res b/lib/github/tests/mock/repo_comments_8982707.res new file mode 100644 index 0000000..e8ec2f0 --- /dev/null +++ b/lib/github/tests/mock/repo_comments_8982707.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_commits_64ce1f.res b/lib/github/tests/mock/repo_commits_64ce1f.res new file mode 100644 index 0000000..e90dbfc --- /dev/null +++ b/lib/github/tests/mock/repo_commits_64ce1f.res @@ -0,0 +1 @@ +{"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 ","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 diff --git a/lib/github/tests/mock/repo_issues_1000.res b/lib/github/tests/mock/repo_issues_1000.res new file mode 100644 index 0000000..24294cf --- /dev/null +++ b/lib/github/tests/mock/repo_issues_1000.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_issues_comments_6020149.res b/lib/github/tests/mock/repo_issues_comments_6020149.res new file mode 100644 index 0000000..8b2f691 --- /dev/null +++ b/lib/github/tests/mock/repo_issues_comments_6020149.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_issues_events_199674194.res b/lib/github/tests/mock/repo_issues_events_199674194.res new file mode 100644 index 0000000..5e6b941 --- /dev/null +++ b/lib/github/tests/mock/repo_issues_events_199674194.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_labels_ok_will_merge.res b/lib/github/tests/mock/repo_labels_ok_will_merge.res new file mode 100644 index 0000000..e75432c --- /dev/null +++ b/lib/github/tests/mock/repo_labels_ok_will_merge.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_milestones_4.res b/lib/github/tests/mock/repo_milestones_4.res new file mode 100644 index 0000000..69abdc0 --- /dev/null +++ b/lib/github/tests/mock/repo_milestones_4.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_nit.res b/lib/github/tests/mock/repo_nit.res new file mode 100644 index 0000000..8acf396 --- /dev/null +++ b/lib/github/tests/mock/repo_nit.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_pulls_1000.res b/lib/github/tests/mock/repo_pulls_1000.res new file mode 100644 index 0000000..76f6935 --- /dev/null +++ b/lib/github/tests/mock/repo_pulls_1000.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/repo_pulls_comment_21010363.res b/lib/github/tests/mock/repo_pulls_comment_21010363.res new file mode 100644 index 0000000..c6f62ee --- /dev/null +++ b/lib/github/tests/mock/repo_pulls_comment_21010363.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/mock/user_Morriar.res b/lib/github/tests/mock/user_Morriar.res new file mode 100644 index 0000000..5a491b1 --- /dev/null +++ b/lib/github/tests/mock/user_Morriar.res @@ -0,0 +1 @@ +{"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 diff --git a/lib/github/tests/test_api.nit b/lib/github/tests/test_api.nit new file mode 100644 index 0000000..7176aaa --- /dev/null +++ b/lib/github/tests/test_api.nit @@ -0,0 +1,346 @@ +# 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 diff --git a/lib/ini/README.md b/lib/ini/README.md new file mode 100644 index 0000000..4f9a478 --- /dev/null +++ b/lib/ini/README.md @@ -0,0 +1,316 @@ +# `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." +~~~ diff --git a/lib/ini/ini.nit b/lib/ini/ini.nit index 815995f..e965323 100644 --- a/lib/ini/ini.nit +++ b/lib/ini/ini.nit @@ -12,344 +12,547 @@ # 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 diff --git a/lib/ini/package.ini b/lib/ini/package.ini index 9611f06..07864ce 100644 --- a/lib/ini/package.ini +++ b/lib/ini/package.ini @@ -3,7 +3,7 @@ name=ini tags=format,lib maintainer=Alexandre Terrasa 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 diff --git a/lib/logger/logger.nit b/lib/logger/logger.nit new file mode 100644 index 0000000..974ff9c --- /dev/null +++ b/lib/logger/logger.nit @@ -0,0 +1,402 @@ +# 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 diff --git a/lib/logger/package.ini b/lib/logger/package.ini new file mode 100644 index 0000000..f4ba7bd --- /dev/null +++ b/lib/logger/package.ini @@ -0,0 +1,12 @@ +[package] +name=logger +tags=logging,lib +maintainer=Alexandre Terrasa +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 diff --git a/lib/popcorn/README.md b/lib/popcorn/README.md index f7c6346..50a0de6 100644 --- a/lib/popcorn/README.md +++ b/lib/popcorn/README.md @@ -511,7 +511,7 @@ with the `use_before` method. 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 @@ -584,7 +584,7 @@ Starting with version 0.1, Popcorn provide a set of built-in middleware that can 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). diff --git a/lib/popcorn/pop_logging.nit b/lib/popcorn/pop_logging.nit index ba3418f..6f0d154 100644 --- a/lib/popcorn/pop_logging.nit +++ b/lib/popcorn/pop_logging.nit @@ -17,7 +17,7 @@ module pop_logging import pop_handlers -import console +import logger import realtime # Initialize a clock for the resquest. @@ -30,73 +30,71 @@ class RequestClock 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 diff --git a/lib/popcorn/pop_tracker.nit b/lib/popcorn/pop_tracker.nit index 9ecf5b7..f43b100 100644 --- a/lib/popcorn/pop_tracker.nit +++ b/lib/popcorn/pop_tracker.nit @@ -46,7 +46,6 @@ module pop_tracker import popcorn import popcorn::pop_config -import popcorn::pop_logging import popcorn::pop_json import popcorn::pop_repos @@ -91,7 +90,6 @@ end # Saves logs into a MongoDB collection class PopTracker - super ConsoleLog super TrackerHandler redef fun all(req, res) do diff --git a/misc/docker/ci/Dockerfile b/misc/docker/ci/Dockerfile index e4ba62b..69dbc45 100644 --- a/misc/docker/ci/Dockerfile +++ b/misc/docker/ci/Dockerfile @@ -13,6 +13,7 @@ RUN dpkg --add-architecture i386 \ graphviz \ libunwind-dev \ pkg-config \ + libicu-dev \ # Get the code! git \ ca-certificates \ diff --git a/src/doc/commands/commands_ini.nit b/src/doc/commands/commands_ini.nit index df312c4..d7c3457 100644 --- a/src/doc/commands/commands_ini.nit +++ b/src/doc/commands/commands_ini.nit @@ -21,7 +21,7 @@ abstract class CmdIni super CmdEntity # Ini file - var ini: nullable ConfigTree = null + var ini: nullable IniFile = null redef fun init_command do var res = super diff --git a/src/loader.nit b/src/loader.nit index 8bdfcb2..169d52e 100644 --- a/src/loader.nit +++ b/src/loader.nit @@ -477,7 +477,7 @@ redef class ModelBuilder # 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 @@ -543,7 +543,7 @@ redef class ModelBuilder 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 @@ -1178,7 +1178,7 @@ redef class MPackage # 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 diff --git a/src/nitpackage.nit b/src/nitpackage.nit index a81ded9..7f4c5f2 100644 --- a/src/nitpackage.nit +++ b/src/nitpackage.nit @@ -253,14 +253,14 @@ redef class MPackage 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) @@ -272,24 +272,24 @@ redef class MPackage # 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", "") @@ -304,7 +304,7 @@ redef class MPackage 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 @@ -528,8 +528,8 @@ redef class MModule 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}`") diff --git a/src/nitpm.nit b/src/nitpm.nit index 1e9de9c..0c6ed5f 100644 --- a/src/nitpm.nit +++ b/src/nitpm.nit @@ -78,7 +78,7 @@ class CommandInstall 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." @@ -135,7 +135,7 @@ class CommandInstall 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" @@ -195,7 +195,7 @@ class CommandInstall 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 @@ -320,7 +320,7 @@ class CommandList 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 diff --git a/src/nitunit.nit b/src/nitunit.nit index fe8fb7b..125c514 100644 --- a/src/nitunit.nit +++ b/src/nitunit.nit @@ -84,19 +84,23 @@ for a in args do 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 diff --git a/src/nitweb.nit b/src/nitweb.nit index 9e9d54f..d3c8c63 100644 --- a/src/nitweb.nit +++ b/src/nitweb.nit @@ -99,7 +99,7 @@ private class NitwebPhase 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 diff --git a/tests/sav/nitunit_args1.res b/tests/sav/nitunit_args1.res index f1851ee..7cec735 100644 --- a/tests/sav/nitunit_args1.res +++ b/tests/sav/nitunit_args1.res @@ -35,5 +35,5 @@ Test suites: Classes: 1; Test Cases: 3; Failures: 1 assert !@#$%^&*() var x = new X assert x.foo2 -Runtime error: Assert failed (test_test_nitunit.nit:38) +Runtime error: Assert failed (test_test_nitunit.nit:38) \ No newline at end of file diff --git a/tests/sav/nitunit_args10.res b/tests/sav/nitunit_args10.res index 5fdb314..cbfc0cd 100644 --- a/tests/sav/nitunit_args10.res +++ b/tests/sav/nitunit_args10.res @@ -5,4 +5,4 @@ Docunits: Entities: 4; Documented ones: 0; With nitunits: 0 Test suites: Classes: 1; Test Cases: 2; Failures: 0 [SUCCESS] All 2 tests passed. - \ No newline at end of file + \ No newline at end of file diff --git a/tests/sav/nitunit_args11.res b/tests/sav/nitunit_args11.res index 0d1829d..a640323 100644 --- a/tests/sav/nitunit_args11.res +++ b/tests/sav/nitunit_args11.res @@ -13,4 +13,4 @@ Docunits: Entities: 5; Documented ones: 0; With nitunits: 0 Test suites: Classes: 1; Test Cases: 3; Failures: 3 [FAILURE] 3/3 tests failed. `nitunit.out` is not removed for investigation. - \ No newline at end of file + \ No newline at end of file diff --git a/tests/sav/nitunit_args12.res b/tests/sav/nitunit_args12.res index 9015b4a..4932785 100644 --- a/tests/sav/nitunit_args12.res +++ b/tests/sav/nitunit_args12.res @@ -11,4 +11,4 @@ Docunits: Entities: 5; Documented ones: 0; With nitunits: 0 Test suites: Classes: 1; Test Cases: 3; Failures: 1 [FAILURE] 1/3 tests failed. `nitunit.out` is not removed for investigation. - \ No newline at end of file + \ No newline at end of file diff --git a/tests/sav/nitunit_args13.res b/tests/sav/nitunit_args13.res index 8c8d3f9..84dbc07 100644 --- a/tests/sav/nitunit_args13.res +++ b/tests/sav/nitunit_args13.res @@ -11,4 +11,4 @@ Docunits: Entities: 3; Documented ones: 0; With nitunits: 0 Test suites: Classes: 1; Test Cases: 3; Failures: 1 [FAILURE] 1/3 tests failed. `nitunit.out` is not removed for investigation. - \ No newline at end of file + \ No newline at end of file diff --git a/tests/sav/nitunit_args14.res b/tests/sav/nitunit_args14.res index c0f1e23..3cb2d30 100644 --- a/tests/sav/nitunit_args14.res +++ b/tests/sav/nitunit_args14.res @@ -15,4 +15,4 @@ Docunits: Entities: 7; Documented ones: 0; With nitunits: 0 Test suites: Classes: 1; Test Cases: 7; Failures: 1 [FAILURE] 1/7 tests failed. `nitunit.out` is not removed for investigation. - \ No newline at end of file + \ No newline at end of file diff --git a/tests/sav/nitunit_args9.res b/tests/sav/nitunit_args9.res index 404c270..7682e35 100644 --- a/tests/sav/nitunit_args9.res +++ b/tests/sav/nitunit_args9.res @@ -59,11 +59,11 @@ Docunits: Entities: 22; Documented ones: 0; With nitunits: 0 Test suites: Classes: 3; Test Cases: 8; Failures: 7 [FAILURE] 7/8 tests failed. `nitunit.out` is not removed for investigation. -test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`. +test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`. test_nitunit4/test_bad_comp.nit:25,10--19: Error: method or variable `bad_method` unknown in `TestSuiteBadComp`. -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`. +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`. 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`. -Before Test +Before Test Tested method After Test Runtime assert: <TestTestSuite>.before @@ -82,4 +82,4 @@ After Test Before Test Tested method After Test - \ No newline at end of file + \ No newline at end of file diff --git a/tests/tests.sh b/tests/tests.sh index 721171d..c19a9a8 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -846,6 +846,10 @@ fi echo >>$xml "" +if type junit2html >/dev/null; then + junit2html "$xml" +fi + if [ -n "$nok" ]; then exit 1 else