{"action":"created","comment":{"url":"https://api.github.com/repos/guykisel/inline-plz/pulls/comments/217553648","pull_request_review_id":155296359,"id":217553648,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxNzU1MzY0OA==","diff_hunk":"@@ -650,268 +645,285 @@ def vendored_path(path):\n }\n \n \n-def run_command(command, log_on_fail=False, log_all=False, timeout=120):\n-    print('Running: \"{}\"'.format(\" \".join(command)))\n-    shell = False\n-    if os.name == \"nt\":\n-        shell = True\n-    popen_kwargs = {\n-        \"args\": command,\n-        \"stdin\": subprocess.PIPE,\n-        \"stdout\": subprocess.PIPE,\n-        \"stderr\": subprocess.PIPE,\n-        \"shell\": shell,\n-        \"env\": os.environ,\n-        \"universal_newlines\": True,\n-        \"timeout\": timeout,\n-    }\n-    if sys.version_info[0] >= 3 and sys.version_info[1] >= 6:\n-        popen_kwargs[\"encoding\"] = \"utf-8\"\n-    try:\n-        proc = subprocess.run(**popen_kwargs)\n-    except subprocess.TimeoutExpired:\n-        print(\"Timeout: {}\".format(command))\n-        return 0, \"\"\n-\n-    stdout, stderr = proc.stdout, proc.stderr\n-    output = \"{}\\n{}\".format(stdout, stderr).strip()\n-    if output and ((log_on_fail and proc.returncode) or log_all):\n-        print(output)\n-        sys.stdout.flush()\n-    return proc.returncode, output\n-\n+class LinterRunner:\n+    def __init__(\n+        self,\n+        install=False,\n+        autorun=False,\n+        ignore_paths=None,\n+        config_dir=None,\n+        enabled_linters=None,\n+        disabled_linters=None,\n+        trusted=False\n+    ):\n+        # TODO: Break this class down with composition or something\n+        self.install = install\n+        self.autorun = autorun\n+        self.ignore_paths = ignore_paths\n+        self.config_dir = config_dir\n+        self.enabled_linters = enabled_linters\n+        self.disabled_linters = disabled_linters\n+        self.trusted = trusted\n+\n+        # Keep track of the parsed messages from all of the linters we're running\n+        self.messages = message.Messages()\n+\n+        # track commands we've already run so that we don't re-run them\n+        self.previous_install_commands = []\n+\n+        # Default event loop is SelectorEventLoop, which on Windows does not support subprocesses\n+        if sys.platform == \"win32\":\n+            self.event_loop = asyncio.ProactorEventLoop()\n+            asyncio.set_event_loop(self.event_loop)\n+        else:\n+            self.event_loop = asyncio.get_event_loop()\n \n-def performance_hacks():\n-    # https://github.com/npm/npm/issues/11283\n-    # npm's progress bar makes npm installs much slower\n-    try:\n-        run_command([\"npm\", \"set\", \"progress=false\"])\n-    except Exception:\n-        pass\n+        # Limit the number of concurrent subprocesses we can run to something close to the number of CPUs we have\n+        self.process_limiter = asyncio.Semaphore(value=multiprocessing.cpu_count(), loop=self.event_loop)\n \n+    async def run_command(self, command, timeout=120):\n+        print('Running command: {}'.format(\" \".join(command)))\n+        sys.stdout.flush()\n \n-def cleanup():\n-    \"\"\"Delete standard installation directories.\"\"\"\n-    for install_dir in INSTALL_DIRS:\n-        try:\n-            shutil.rmtree(install_dir, ignore_errors=True)\n-        except Exception:\n-            print(traceback.format_exc())\n-            print(\"Failed to delete {}\".format(install_dir))\n-\n-\n-def should_ignore_path(path, ignore_paths):\n-    for ignore_path in ignore_paths:\n-        if (\n-            os.path.relpath(path).startswith(ignore_path)\n-            or path.startswith(ignore_path)\n-            or fnmatch.fnmatch(path, ignore_path)\n-            or ignore_path in path.split(os.path.sep)\n-        ):\n-            return True\n-\n-    return False\n-\n-\n-def run_per_file(config, ignore_paths=None, path=None, config_dir=None):\n-    ignore_paths = ignore_paths or []\n-    path = path or os.getcwd()\n-    cmd = run_config(config, config_dir)\n-    run_cmds = []\n-    patterns = PATTERNS.get(config.get(\"language\"))\n-    concurrency = config.get(\"concurrency\")\n-    paths = all_filenames_in_dir(path=path, ignore_paths=ignore_paths)\n-    for pattern in patterns:\n-        for filepath in fnmatch.filter(paths, pattern):\n-            if \"text\" in identify.tags_from_path(filepath):\n-                run_cmds.append(cmd + [filepath])\n-    pool = Pool(processes=concurrency)\n-\n-    def result(run_cmd):\n-        _, out = run_command(run_cmd, timeout=5)\n-        return run_cmd[-1], out.strip()\n-\n-    output = pool.map(result, run_cmds)\n-    return output\n-\n-\n-def linters_to_run(\n-    autorun=False, ignore_paths=None, enabled_linters=None, disabled_linters=None\n-):\n-    linters = set()\n-    enabled_linters = enabled_linters or []\n-    disabled_linters = disabled_linters or []\n-    try:\n-        enabled_linters.extend(enabled_linters[0].split(\",\"))\n-    except (IndexError, AttributeError):\n-        pass\n-    try:\n-        disabled_linters.extend(disabled_linters[0].split(\",\"))\n-    except (IndexError, AttributeError):\n-        pass\n-    if not autorun:\n-        for linter, config in LINTERS.items():\n-            if linter in enabled_linters:\n-                linters.add(linter)\n-    else:\n-        dotfilefound = {}\n-        for linter, config in LINTERS.items():\n-            if dotfiles_exist(config):\n-                dotfilefound[config.get(\"language\")] = True\n-                if (\n-                    config.get(\"run_if_dotfile_in_root\")\n-                    and linter not in disabled_linters\n-                ):\n-                    linters.add(linter)\n-            if dotfilefound.get(config.get(\"language\")) and config.get(\"autorun\"):\n-                if linter not in disabled_linters:\n-                    linters.add(linter)\n-        filenames = all_filenames_in_dir(path=os.getcwd(), ignore_paths=ignore_paths)\n-        for linter, config in LINTERS.items():\n-            if linter in enabled_linters or (\n-                not dotfilefound.get(config.get(\"language\"))\n-                and should_autorun(config, filenames)\n-            ):\n-                if linter not in disabled_linters:\n-                    linters.add(linter)\n-    return linters\n+        popen_kwargs = {\n+            \"stdin\": asyncio.subprocess.PIPE,\n+            \"stdout\": asyncio.subprocess.PIPE,\n+            \"stderr\": asyncio.subprocess.PIPE,\n+            \"env\": os.environ\n+        }\n+        # TODO: Figure out why we don't always get utf-8 back, even on 3.6.X without universal_newlines=True\n+        if sys.version_info[0] >= 3 and sys.version_info[1] >= 6:\n+            popen_kwargs[\"encoding\"] = \"utf-8\"\n+\n+        # TODO: Figure out the max buffer size for subprocesses\n+        async with self.process_limiter:\n+            if sys.platform == \"win32\":\n+                proc = await asyncio.wait_for(asyncio.create_subprocess_shell(\" \".join(command), **popen_kwargs), timeout)\n+            else:\n+                proc = await asyncio.wait_for(asyncio.create_subprocess_exec(*command, **popen_kwargs), timeout)\n \n+        stdout, stderr = await proc.communicate()\n+        output = \"{}\\n{}\".format(\n+            stdout.decode(\"utf-8\", \"backslashreplace\"),\n+            stderr.decode(\"utf-8\", \"backslashreplace\")).strip()\n+        return proc.returncode, output\n \n-def all_filenames_in_dir(path=None, ignore_paths=None):\n-    path = path or os.getcwd()\n-    # http://stackoverflow.com/a/2186565\n-    paths = set()\n-    for root, dirnames, filenames in os.walk(path, topdown=True):\n+    async def performance_hacks(self):\n+        # https://github.com/npm/npm/issues/11283\n+        # npm's progress bar makes npm installs much slower\n         try:\n-            for ignore in ignore_paths:\n-                dirnames.remove(ignore)\n-        except ValueError:\n+            await self.run_command([\"npm\", \"set\", \"progress=false\"])\n+        except Exception as e:\n             pass\n-        if should_ignore_path(root, ignore_paths):\n-            continue\n \n-        for filename in filenames:\n-            full_path = os.path.join(root, filename)\n-            if \"text\" in identify.tags_from_path(full_path):\n-                paths.add(full_path)\n-    return paths\n+    def cleanup(self):\n+        \"\"\"Delete standard installation directories.\"\"\"\n+        for install_dir in INSTALL_DIRS:\n+            try:\n+                shutil.rmtree(install_dir, ignore_errors=True)\n+            except Exception:\n+                print(traceback.format_exc())\n+                print(\"Failed to delete {}\".format(install_dir))\n+\n+    def should_ignore_path(self, path):\n+        for ignore_path in self.ignore_paths:\n+            if (\n+                os.path.relpath(path).startswith(ignore_path)\n+                or path.startswith(ignore_path)\n+                or fnmatch.fnmatch(path, ignore_path)\n+                or ignore_path in path.split(os.path.sep)\n+            ):\n+                return True\n \n+        return False\n \n-def should_autorun(config, filenames):\n-    patterns = PATTERNS.get(config.get(\"language\"))\n-    if config.get(\"autorun\"):\n+    async def run_per_file(self, config, path=None):\n+        path = path or os.getcwd()\n+        cmd = self.run_config(config, self.config_dir)\n+        run_cmds = []\n+        file_tasks = []\n+        patterns = PATTERNS.get(config.get(\"language\"))\n+        # TODO: Remove concurrency setting\n+        paths = self.all_filenames_in_dir(path=path)\n         for pattern in patterns:\n-            if fnmatch.filter(filenames, pattern):\n-                return True\n+            for filepath in fnmatch.filter(paths, pattern):\n+                if \"text\" in identify.tags_from_path(filepath):\n+                    run_cmds.append(cmd + [filepath])\n+                    file_tasks.append(self.event_loop.create_task(self.run_command(run_cmds[-1], timeout=5)))\n \n-    return False\n+        output = []\n+        for run_cmd, file_task in zip(run_cmds, file_tasks):\n+            _, out = await file_task\n \n+            # Return the filename and the output\n+            output.append((run_cmd[-1], out.strip()))\n \n-def dotfiles_exist(config, path=None):\n-    path = path or os.getcwd()\n-    return any(\n-        dotfile.strip() in os.listdir(path) for dotfile in config.get(\"dotfiles\")\n-    )\n+        return output\n \n+    def linters_to_run(self):\n+        linters = set()\n+        enabled_linters = self.enabled_linters or []\n+        disabled_linters = self.disabled_linters or []\n+        try:\n+            enabled_linters.extend(enabled_linters[0].split(\",\"))\n+        except (IndexError, AttributeError):\n+            pass\n+        try:\n+            disabled_linters.extend(disabled_linters[0].split(\",\"))\n+        except (IndexError, AttributeError):\n+            pass\n+        if not self.autorun:\n+            for linter, config in LINTERS.items():\n+                if linter in enabled_linters:\n+                    linters.add(linter)\n+        else:\n+            dotfilefound = {}\n+            for linter, config in LINTERS.items():\n+                if self.dotfiles_exist(config):\n+                    dotfilefound[config.get(\"language\")] = True\n+                    if (\n+                        config.get(\"run_if_dotfile_in_root\")\n+                        and linter not in disabled_linters\n+                    ):\n+                        linters.add(linter)\n+                if dotfilefound.get(config.get(\"language\")) and config.get(\"autorun\"):\n+                    if linter not in disabled_linters:\n+                        linters.add(linter)\n+            filenames = self.all_filenames_in_dir()\n+            for linter, config in LINTERS.items():\n+                if linter in enabled_linters or (\n+                    not dotfilefound.get(config.get(\"language\"))\n+                    and self.should_autorun(config, filenames)\n+                ):\n+                    if linter not in disabled_linters:\n+                        linters.add(linter)\n+        return linters\n+\n+    def all_filenames_in_dir(self, path=None):\n+        path = path or os.getcwd()\n+        # http://stackoverflow.com/a/2186565\n+        paths = set()\n+        for root, dirnames, filenames in os.walk(path, topdown=True):\n+            try:\n+                for ignore in self.ignore_paths:\n+                    dirnames.remove(ignore)\n+            except ValueError:","path":"inlineplz/linters/__init__.py","position":360,"original_position":360,"commit_id":"c76e0c5b6c86a42c8c4d385fdb568596e7726a9b","original_commit_id":"c76e0c5b6c86a42c8c4d385fdb568596e7726a9b","user":{"login":"guykisel","id":6760519,"node_id":"MDQ6VXNlcjY3NjA1MTk=","avatar_url":"https://avatars0.githubusercontent.com/u/6760519?v=4","gravatar_id":"","url":"https://api.github.com/users/guykisel","html_url":"https://github.com/guykisel","followers_url":"https://api.github.com/users/guykisel/followers","following_url":"https://api.github.com/users/guykisel/following{/other_user}","gists_url":"https://api.github.com/users/guykisel/gists{/gist_id}","starred_url":"https://api.github.com/users/guykisel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/guykisel/subscriptions","organizations_url":"https://api.github.com/users/guykisel/orgs","repos_url":"https://api.github.com/users/guykisel/repos","events_url":"https://api.github.com/users/guykisel/events{/privacy}","received_events_url":"https://api.github.com/users/guykisel/received_events","type":"User","site_admin":false},"body":"[inline-plz]: `prospector: mccabe: LinterRunner.linters_to_run is too complex (16) (MC0001)`","created_at":"2018-09-13T22:29:02Z","updated_at":"2018-09-13T22:29:02Z","html_url":"https://github.com/guykisel/inline-plz/pull/360#discussion_r217553648","pull_request_url":"https://api.github.com/repos/guykisel/inline-plz/pulls/360","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/guykisel/inline-plz/pulls/comments/217553648"},"html":{"href":"https://github.com/guykisel/inline-plz/pull/360#discussion_r217553648"},"pull_request":{"href":"https://api.github.com/repos/guykisel/inline-plz/pulls/360"}}},"pull_request":{"url":"https://api.github.com/repos/guykisel/inline-plz/pulls/360","id":215432493,"node_id":"MDExOlB1bGxSZXF1ZXN0MjE1NDMyNDkz","html_url":"https://github.com/guykisel/inline-plz/pull/360","diff_url":"https://github.com/guykisel/inline-plz/pull/360.diff","patch_url":"https://github.com/guykisel/inline-plz/pull/360.patch","issue_url":"https://api.github.com/repos/guykisel/inline-plz/issues/360","number":360,"state":"open","locked":false,"title":"Asyncio","user":{"login":"xelaadryth","id":6239387,"node_id":"MDQ6VXNlcjYyMzkzODc=","avatar_url":"https://avatars3.githubusercontent.com/u/6239387?v=4","gravatar_id":"","url":"https://api.github.com/users/xelaadryth","html_url":"https://github.com/xelaadryth","followers_url":"https://api.github.com/users/xelaadryth/followers","following_url":"https://api.github.com/users/xelaadryth/following{/other_user}","gists_url":"https://api.github.com/users/xelaadryth/gists{/gist_id}","starred_url":"https://api.github.com/users/xelaadryth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/xelaadryth/subscriptions","organizations_url":"https://api.github.com/users/xelaadryth/orgs","repos_url":"https://api.github.com/users/xelaadryth/repos","events_url":"https://api.github.com/users/xelaadryth/events{/privacy}","received_events_url":"https://api.github.com/users/xelaadryth/received_events","type":"User","site_admin":false},"body":"Most of the changes are in the linters init, for the parsers I just added a debug log prefix so you could tell which parser was failing on invalid messages.\r\n\r\nRefactors the free-floating functions into a pretty big class (probably still needs additional breakdown), and uses asyncio subprocesses under a processor-count-limited semaphore for each command to be executed.\r\n\r\nStill need to fix unit tests and pretty up and standardize the print output.","created_at":"2018-09-13T22:21:13Z","updated_at":"2018-09-13T22:29:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"83c3eb1b038b3c8aeb62bce365d44ec2ef2d370e","assignee":null,"assignees":[],"requested_reviewers":[{"login":"kyleburton","id":69799,"node_id":"MDQ6VXNlcjY5Nzk5","avatar_url":"https://avatars0.githubusercontent.com/u/69799?v=4","gravatar_id":"","url":"https://api.github.com/users/kyleburton","html_url":"https://github.com/kyleburton","followers_url":"https://api.github.com/users/kyleburton/followers","following_url":"https://api.github.com/users/kyleburton/following{/other_user}","gists_url":"https://api.github.com/users/kyleburton/gists{/gist_id}","starred_url":"https://api.github.com/users/kyleburton/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kyleburton/subscriptions","organizations_url":"https://api.github.com/users/kyleburton/orgs","repos_url":"https://api.github.com/users/kyleburton/repos","events_url":"https://api.github.com/users/kyleburton/events{/privacy}","received_events_url":"https://api.github.com/users/kyleburton/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/guykisel/inline-plz/pulls/360/commits","review_comments_url":"https://api.github.com/repos/guykisel/inline-plz/pulls/360/comments","review_comment_url":"https://api.github.com/repos/guykisel/inline-plz/pulls/comments{/number}","comments_url":"https://api.github.com/repos/guykisel/inline-plz/issues/360/comments","statuses_url":"https://api.github.com/repos/guykisel/inline-plz/statuses/c76e0c5b6c86a42c8c4d385fdb568596e7726a9b","head":{"label":"guykisel:asyncio","ref":"asyncio","sha":"c76e0c5b6c86a42c8c4d385fdb568596e7726a9b","user":{"login":"guykisel","id":6760519,"node_id":"MDQ6VXNlcjY3NjA1MTk=","avatar_url":"https://avatars0.githubusercontent.com/u/6760519?v=4","gravatar_id":"","url":"https://api.github.com/users/guykisel","html_url":"https://github.com/guykisel","followers_url":"https://api.github.com/users/guykisel/followers","following_url":"https://api.github.com/users/guykisel/following{/other_user}","gists_url":"https://api.github.com/users/guykisel/gists{/gist_id}","starred_url":"https://api.github.com/users/guykisel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/guykisel/subscriptions","organizations_url":"https://api.github.com/users/guykisel/orgs","repos_url":"https://api.github.com/users/guykisel/repos","events_url":"https://api.github.com/users/guykisel/events{/privacy}","received_events_url":"https://api.github.com/users/guykisel/received_events","type":"User","site_admin":false},"repo":{"id":49332088,"node_id":"MDEwOlJlcG9zaXRvcnk0OTMzMjA4OA==","name":"inline-plz","full_name":"guykisel/inline-plz","owner":{"login":"guykisel","id":6760519,"node_id":"MDQ6VXNlcjY3NjA1MTk=","avatar_url":"https://avatars0.githubusercontent.com/u/6760519?v=4","gravatar_id":"","url":"https://api.github.com/users/guykisel","html_url":"https://github.com/guykisel","followers_url":"https://api.github.com/users/guykisel/followers","following_url":"https://api.github.com/users/guykisel/following{/other_user}","gists_url":"https://api.github.com/users/guykisel/gists{/gist_id}","starred_url":"https://api.github.com/users/guykisel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/guykisel/subscriptions","organizations_url":"https://api.github.com/users/guykisel/orgs","repos_url":"https://api.github.com/users/guykisel/repos","events_url":"https://api.github.com/users/guykisel/events{/privacy}","received_events_url":"https://api.github.com/users/guykisel/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/guykisel/inline-plz","description":"Inline your lint messages","fork":false,"url":"https://api.github.com/repos/guykisel/inline-plz","forks_url":"https://api.github.com/repos/guykisel/inline-plz/forks","keys_url":"https://api.github.com/repos/guykisel/inline-plz/keys{/key_id}","collaborators_url":"https://api.github.com/repos/guykisel/inline-plz/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/guykisel/inline-plz/teams","hooks_url":"https://api.github.com/repos/guykisel/inline-plz/hooks","issue_events_url":"https://api.github.com/repos/guykisel/inline-plz/issues/events{/number}","events_url":"https://api.github.com/repos/guykisel/inline-plz/events","assignees_url":"https://api.github.com/repos/guykisel/inline-plz/assignees{/user}","branches_url":"https://api.github.com/repos/guykisel/inline-plz/branches{/branch}","tags_url":"https://api.github.com/repos/guykisel/inline-plz/tags","blobs_url":"https://api.github.com/repos/guykisel/inline-plz/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/guykisel/inline-plz/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/guykisel/inline-plz/git/refs{/sha}","trees_url":"https://api.github.com/repos/guykisel/inline-plz/git/trees{/sha}","statuses_url":"https://api.github.com/repos/guykisel/inline-plz/statuses/{sha}","languages_url":"https://api.github.com/repos/guykisel/inline-plz/languages","stargazers_url":"https://api.github.com/repos/guykisel/inline-plz/stargazers","contributors_url":"https://api.github.com/repos/guykisel/inline-plz/contributors","subscribers_url":"https://api.github.com/repos/guykisel/inline-plz/subscribers","subscription_url":"https://api.github.com/repos/guykisel/inline-plz/subscription","commits_url":"https://api.github.com/repos/guykisel/inline-plz/commits{/sha}","git_commits_url":"https://api.github.com/repos/guykisel/inline-plz/git/commits{/sha}","comments_url":"https://api.github.com/repos/guykisel/inline-plz/comments{/number}","issue_comment_url":"https://api.github.com/repos/guykisel/inline-plz/issues/comments{/number}","contents_url":"https://api.github.com/repos/guykisel/inline-plz/contents/{+path}","compare_url":"https://api.github.com/repos/guykisel/inline-plz/compare/{base}...{head}","merges_url":"https://api.github.com/repos/guykisel/inline-plz/merges","archive_url":"https://api.github.com/repos/guykisel/inline-plz/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/guykisel/inline-plz/downloads","issues_url":"https://api.github.com/repos/guykisel/inline-plz/issues{/number}","pulls_url":"https://api.github.com/repos/guykisel/inline-plz/pulls{/number}","milestones_url":"https://api.github.com/repos/guykisel/inline-plz/milestones{/number}","notifications_url":"https://api.github.com/repos/guykisel/inline-plz/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/guykisel/inline-plz/labels{/name}","releases_url":"https://api.github.com/repos/guykisel/inline-plz/releases{/id}","deployments_url":"https://api.github.com/repos/guykisel/inline-plz/deployments","created_at":"2016-01-09T16:37:10Z","updated_at":"2018-09-13T17:06:41Z","pushed_at":"2018-09-13T22:26:21Z","git_url":"git://github.com/guykisel/inline-plz.git","ssh_url":"git@github.com:guykisel/inline-plz.git","clone_url":"https://github.com/guykisel/inline-plz.git","svn_url":"https://github.com/guykisel/inline-plz","homepage":null,"size":57883,"stargazers_count":17,"watchers_count":17,"language":"XSLT","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"open_issues_count":60,"license":{"key":"isc","name":"ISC License","spdx_id":"ISC","url":"https://api.github.com/licenses/isc","node_id":"MDc6TGljZW5zZTEw"},"forks":11,"open_issues":60,"watchers":17,"default_branch":"master"}},"base":{"label":"guykisel:master","ref":"master","sha":"2dd87f1f939a6e8a1ed2028f78e3e53c4ddf632b","user":{"login":"guykisel","id":6760519,"node_id":"MDQ6VXNlcjY3NjA1MTk=","avatar_url":"https://avatars0.githubusercontent.com/u/6760519?v=4","gravatar_id":"","url":"https://api.github.com/users/guykisel","html_url":"https://github.com/guykisel","followers_url":"https://api.github.com/users/guykisel/followers","following_url":"https://api.github.com/users/guykisel/following{/other_user}","gists_url":"https://api.github.com/users/guykisel/gists{/gist_id}","starred_url":"https://api.github.com/users/guykisel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/guykisel/subscriptions","organizations_url":"https://api.github.com/users/guykisel/orgs","repos_url":"https://api.github.com/users/guykisel/repos","events_url":"https://api.github.com/users/guykisel/events{/privacy}","received_events_url":"https://api.github.com/users/guykisel/received_events","type":"User","site_admin":false},"repo":{"id":49332088,"node_id":"MDEwOlJlcG9zaXRvcnk0OTMzMjA4OA==","name":"inline-plz","full_name":"guykisel/inline-plz","owner":{"login":"guykisel","id":6760519,"node_id":"MDQ6VXNlcjY3NjA1MTk=","avatar_url":"https://avatars0.githubusercontent.com/u/6760519?v=4","gravatar_id":"","url":"https://api.github.com/users/guykisel","html_url":"https://github.com/guykisel","followers_url":"https://api.github.com/users/guykisel/followers","following_url":"https://api.github.com/users/guykisel/following{/other_user}","gists_url":"https://api.github.com/users/guykisel/gists{/gist_id}","starred_url":"https://api.github.com/users/guykisel/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/guykisel/subscriptions","organizations_url":"https://api.github.com/users/guykisel/orgs","repos_url":"https://api.github.com/users/guykisel/repos","events_url":"https://api.github.com/users/guykisel/events{/privacy}","received_events_url":"https://api.github.com/users/guykisel/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/guykisel/inline-plz","description":"Inline your lint messages","fork":false,"url":"https://api.github.com/repos/guykisel/inline-plz","forks_url":"https://api.github.com/repos/guykisel/inline-plz/forks","keys_url":"https://api.github.com/repos/guykisel/inline-plz/keys{/key_id}","collaborators_url":"https://api.github.com/repos/guykisel/inline-plz/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/guykisel/inline-plz/teams","hooks_url":"https://api.github.com/repos/guykisel/inline-plz/hooks","issue_events_url":"https://api.github.com/repos/guykisel/inline-plz/issues/events{/number}","events_url":"https://api.github.com/repos/guykisel/inline-plz/events","assignees_url":"https://api.github.com/repos/guykisel/inline-plz/assignees{/user}","branches_url":"https://api.github.com/repos/guykisel/inline-plz/branches{/branch}","tags_url":"https://api.github.com/repos/guykisel/inline-plz/tags","blobs_url":"https://api.github.com/repos/guykisel/inline-plz/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/guykisel/inline-plz/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/guykisel/inline-plz/git/refs{/sha}","trees_url":"https://api.github.com/repos/guykisel/inline-plz/git/trees{/sha}","statuses_url":"https://api.github.com/repos/guykisel/inline-plz/statuses/{sha}","languages_url":"https://api.github.com/repos/guykisel/inline-plz/languages","stargazers_url":"https://api.github.com/repos/guykisel/inline-plz/stargazers","contributors_url":"https://api.github.com/repos/guykisel/inline-plz/contributors","subscribers_url":"https://api.github.com/repos/guykisel/inline-plz/subscribers","subscription_url":"https://api.github.com/repos/guykisel/inline-plz/subscription","commits_url":"https://api.github.com/repos/guykisel/inline-plz/commits{/sha}","git_commits_url":"https://api.github.com/repos/guykisel/inline-plz/git/commits{/sha}","comments_url":"https://api.github.com/repos/guykisel/inline-plz/comments{/number}","issue_comment_url":"https://api.github.com/repos/guykisel/inline-plz/issues/comments{/number}","contents_url":"https://api.github.com/repos/guykisel/inline-plz/contents/{+path}","compare_url":"https://api.github.com/repos/guykisel/inline-plz/compare/{base}...{head}","merges_url":"https://api.github.com/repos/guykisel/inline-plz/merges","archive_url":"https://api.github.com/repos/guykisel/inline-plz/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/guykisel/inline-plz/downloads","issues_url":"https://api.github.com/repos/guykisel/inline-plz/issues{/number}","pulls_url":"https://api.github.com/repos/guykisel/inline-plz/pulls{/number}","milestones_url":"https://api.github.com/repos/guykisel/inline-plz/milestones{/number}","notifications_url":"https://api.github.com/repos/guykisel/inline-plz/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/guykisel/inline-plz/labels{/name}","releases_url":"https://api.github.com/repos/guykisel/inline-plz/releases{/id}","deployments_url":"https://api.github.com/repos/guykisel/inline-plz/deployments","created_at":"2016-01-09T16:37:10Z","updated_at":"2018-09-13T17:06:41Z","pushed_at":"2018-09-13T22:26:21Z","git_url":"git://github.com/guykisel/inline-plz.git","ssh_url":"git@github.com:guykisel/inline-plz.git","clone_url":"https://github.com/guykisel/inline-plz.git","svn_url":"https://github.com/guykisel/inline-plz","homepage":null,"size":57883,"stargazers_count":17,"watchers_count":17,"language":"XSLT","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":11,"mirror_url":null,"archived":false,"open_issues_count":60,"license":{"key":"isc","name":"ISC License","spdx_id":"ISC","url":"https://api.github.com/licenses/isc","node_id":"MDc6TGljZW5zZTEw"},"forks":11,"open_issues":60,"watchers":17,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/guykisel/inline-plz/pulls/360"},"html":{"href":"https://github.com/guykisel/inline-plz/pull/360"},"issue":{"href":"https://api.github.com/repos/guykisel/inline-plz/issues/360"},"comments":{"href":"https://api.github.com/repos/guykisel/inline-plz/issues/360/comments"},"review_comments":{"href":"https://api.github.com/repos/guykisel/inline-plz/pulls/360/comments"},"review_comment":{"href":"https://api.github.com/repos/guykisel/inline-plz/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/guykisel/inline-plz/pulls/360/commits"},"statuses":{"href":"https://api.github.com/repos/guykisel/inline-plz/statuses/c76e0c5b6c86a42c8c4d385fdb568596e7726a9b"}},"author_association":"COLLABORATOR"}}
{"actor":{"display_login":"guykisel"}}