{"action":"created","comment":{"url":"https://api.github.com/repos/translate/pootle/pulls/comments/62728744","id":62728744,"diff_hunk":"@@ -0,0 +1,182 @@\n+# -*- coding: utf-8 -*-\n+#\n+# Copyright (C) Pootle contributors.\n+#\n+# This file is a part of the Pootle project. It is distributed under the GPL3\n+# or later license. See the LICENSE file for a copy of the license and the\n+# AUTHORS file for copyright and authorship information.\n+\n+import fnmatch\n+import os\n+import re\n+\n+import scandir\n+\n+from django.utils.functional import cached_property\n+from django.utils.lru_cache import lru_cache\n+\n+\n+PATH_MAPPING = (\n+    (\".\", \"\\.\"),\n+    (\"<language_code>\", \"(?P<language_code>[\\w\\-\\.]*)\"),\n+    (\"<filename>\", \"(?P<filename>[\\w\\-\\.]*)\"),\n+    (\"/<dir_path>/\", \"/<dir_path>\"),\n+    (\"<dir_path>\", \"(?P<dir_path>[\\w\\/\\-]*?)\"))\n+\n+DEFAULT_EXTS = (\"po\", \"pot\")\n+\n+\n+class TranslationFileFinder(object):\n+\n+    path_mapping = PATH_MAPPING\n+\n+    def __init__(self, translation_path, path_filters=None, ext=None):\n+        self.translation_path = translation_path\n+        self.ext = ext or DEFAULT_EXTS\n+        self.path_filters = path_filters\n+        TranslationPathValidator(translation_path).validate()\n+\n+    @cached_property\n+    def regex(self):\n+        return re.compile(self._parse_path())\n+\n+    @cached_property\n+    def file_root(self):\n+        file_root = self.translation_path.split(\"<\")[0]\n+        if not file_root.endswith(\"/\"):\n+            file_root = \"/\".join(file_root.split(\"/\")[:-1])\n+        return file_root.rstrip(\"/\")\n+\n+    def match_filepath(self, file_path):\n+        filename = os.path.basename(file_path)\n+        match = self.match(file_path)\n+        if not match:\n+            return\n+        matched = match.groupdict()\n+        matched[\"dir_path\"] = (\n+            matched.get(\"dir_path\", \"\").strip(\"/\"))\n+        if not matched.get(\"filename\"):\n+            matched[\"filename\"] = os.path.splitext(\n+                os.path.basename(filename))[0]\n+        return file_path, matched\n+\n+    def walk(self):\n+        # print(\"Walking the FS: %s\" % self.file_root)\n+        for root, dirs, files in scandir.walk(self.file_root):\n+            for filename in files:\n+                yield os.path.join(root, filename)\n+\n+    def find(self):\n+        for filepath in self.walk():\n+            match = self.match_filepath(filepath)\n+            if match:\n+                yield match\n+\n+    def match(self, file_path):\n+        return self.regex.match(file_path)\n+\n+    @lru_cache(maxsize=None)\n+    def reverse_match(self, language_code, filename=None, ext=None, dir_path=None):\n+        if not filename:\n+            filename = language_code\n+        if ext is None:\n+            ext = self.ext[0]\n+        if ext not in self.ext:\n+            raise ValueError(\"ext must be in the list of possible extensions\")\n+        ext = ext.strip(\".\")\n+        path = (\n+            \"%s.%s\"\n+            % (os.path.splitext(self.translation_path)[0], ext))\n+        if dir_path and \"<dir_path>\" not in path:\n+            return\n+        path = (path.replace(\"<language_code>\", language_code)\n+                    .replace(\"<filename>\", filename))\n+        if \"<dir_path>\" in path:\n+            if dir_path and dir_path.strip(\"/\"):\n+                path = path.replace(\n+                    \"<dir_path>\", \"/%s/\" % dir_path.strip(\"/\"))\n+            else:\n+                path = path.replace(\"<dir_path>\", \"\")\n+        local_path = path.replace(self.file_root, \"\")\n+        if \"//\" in local_path:\n+            path = os.path.join(\n+                self.file_root,\n+                local_path.replace(\"//\", \"/\").lstrip(\"/\"))\n+        return path\n+\n+    def _ext_re(self):\n+        return (\n+            r\".(?P<ext>(%s))\"\n+            % \"|\".join(\n+                (\"%s$\" % x)\n+                for x in set(self.ext)))\n+\n+    def _parse_path_regex(self):\n+        path = self.translation_path\n+        for k, v in self.path_mapping:\n+            path = path.replace(k, v)\n+        return \"%s%s$\" % (\n+            os.path.splitext(path)[0],\n+            self._ext_re())\n+\n+    def _parse_path(self):\n+        regex = self._parse_path_regex()\n+        if not self.path_filters:\n+            return regex\n+        regex = r\"(?=%s)\" % regex\n+        filter_regex = \"\".join(\n+            [(\"(?=^%s)\" % fnmatch.translate(path))\n+             for path in self.path_filters])\n+        return \"%s%s\" % (regex, filter_regex)\n+\n+\n+class TranslationPathValidator(object):\n+\n+    validators = (\n+        \"no_dots\", \"lang_code\", \"ext\", \"match_tags\", \"path\")\n+\n+    def __init__(self, path):\n+        self.path = path\n+\n+    def validate_no_dots(self):\n+        links_to_parent = (\n+            self.path == \"..\"\n+            or self.path.startswith(\"../\")\n+            or \"/../\" in self.path\n+            or self.path.endswith(\"/..\"))","path":"pootle/apps/pootle_fs/finder.py","position":146,"original_position":146,"commit_id":"6834a83d6675a4d84e26ebbf3f26acea86075d0e","original_commit_id":"8eee428779fd686826eb53c2c8b0596ee9239750","user":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"body":"hmm @unho as it requires absolute paths, im wondering if a better check is\r\n\r\n```self.path == os.path.abspath(self.path)```\r\n","created_at":"2016-05-10T18:44:06Z","updated_at":"2016-05-10T18:44:06Z","html_url":"https://github.com/translate/pootle/pull/4720#discussion_r62728744","pull_request_url":"https://api.github.com/repos/translate/pootle/pulls/4720","_links":{"self":{"href":"https://api.github.com/repos/translate/pootle/pulls/comments/62728744"},"html":{"href":"https://github.com/translate/pootle/pull/4720#discussion_r62728744"},"pull_request":{"href":"https://api.github.com/repos/translate/pootle/pulls/4720"}}},"pull_request":{"url":"https://api.github.com/repos/translate/pootle/pulls/4720","id":69350240,"html_url":"https://github.com/translate/pootle/pull/4720","diff_url":"https://github.com/translate/pootle/pull/4720.diff","patch_url":"https://github.com/translate/pootle/pull/4720.patch","issue_url":"https://api.github.com/repos/translate/pootle/issues/4720","number":4720,"state":"open","locked":false,"title":"Pootle fs finder","user":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"body":"Add a TranslationFileFinder for pootle_fs\r\n\r\nhttps://github.com/translate/pootle/wiki/DocsTranslationFileFinder\r\n\r\n\r\nmore recent docs here\r\n\r\nhttps://github.com/translate/pootle/pull/4716","created_at":"2016-05-09T14:04:35Z","updated_at":"2016-05-10T18:44:06Z","closed_at":null,"merged_at":null,"merge_commit_sha":"715464955ee792cb68ece0bfb9e86bc0b3a59a5d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/translate/pootle/pulls/4720/commits","review_comments_url":"https://api.github.com/repos/translate/pootle/pulls/4720/comments","review_comment_url":"https://api.github.com/repos/translate/pootle/pulls/comments{/number}","comments_url":"https://api.github.com/repos/translate/pootle/issues/4720/comments","statuses_url":"https://api.github.com/repos/translate/pootle/statuses/6834a83d6675a4d84e26ebbf3f26acea86075d0e","head":{"label":"phlax:pootle_fs_finder","ref":"pootle_fs_finder","sha":"6834a83d6675a4d84e26ebbf3f26acea86075d0e","user":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"repo":{"id":39886550,"name":"pootle","full_name":"phlax/pootle","owner":{"login":"phlax","id":454682,"avatar_url":"https://avatars.githubusercontent.com/u/454682?v=3","gravatar_id":"","url":"https://api.github.com/users/phlax","html_url":"https://github.com/phlax","followers_url":"https://api.github.com/users/phlax/followers","following_url":"https://api.github.com/users/phlax/following{/other_user}","gists_url":"https://api.github.com/users/phlax/gists{/gist_id}","starred_url":"https://api.github.com/users/phlax/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phlax/subscriptions","organizations_url":"https://api.github.com/users/phlax/orgs","repos_url":"https://api.github.com/users/phlax/repos","events_url":"https://api.github.com/users/phlax/events{/privacy}","received_events_url":"https://api.github.com/users/phlax/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/phlax/pootle","description":"Online translation tool","fork":true,"url":"https://api.github.com/repos/phlax/pootle","forks_url":"https://api.github.com/repos/phlax/pootle/forks","keys_url":"https://api.github.com/repos/phlax/pootle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/phlax/pootle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/phlax/pootle/teams","hooks_url":"https://api.github.com/repos/phlax/pootle/hooks","issue_events_url":"https://api.github.com/repos/phlax/pootle/issues/events{/number}","events_url":"https://api.github.com/repos/phlax/pootle/events","assignees_url":"https://api.github.com/repos/phlax/pootle/assignees{/user}","branches_url":"https://api.github.com/repos/phlax/pootle/branches{/branch}","tags_url":"https://api.github.com/repos/phlax/pootle/tags","blobs_url":"https://api.github.com/repos/phlax/pootle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/phlax/pootle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/phlax/pootle/git/refs{/sha}","trees_url":"https://api.github.com/repos/phlax/pootle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/phlax/pootle/statuses/{sha}","languages_url":"https://api.github.com/repos/phlax/pootle/languages","stargazers_url":"https://api.github.com/repos/phlax/pootle/stargazers","contributors_url":"https://api.github.com/repos/phlax/pootle/contributors","subscribers_url":"https://api.github.com/repos/phlax/pootle/subscribers","subscription_url":"https://api.github.com/repos/phlax/pootle/subscription","commits_url":"https://api.github.com/repos/phlax/pootle/commits{/sha}","git_commits_url":"https://api.github.com/repos/phlax/pootle/git/commits{/sha}","comments_url":"https://api.github.com/repos/phlax/pootle/comments{/number}","issue_comment_url":"https://api.github.com/repos/phlax/pootle/issues/comments{/number}","contents_url":"https://api.github.com/repos/phlax/pootle/contents/{+path}","compare_url":"https://api.github.com/repos/phlax/pootle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/phlax/pootle/merges","archive_url":"https://api.github.com/repos/phlax/pootle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/phlax/pootle/downloads","issues_url":"https://api.github.com/repos/phlax/pootle/issues{/number}","pulls_url":"https://api.github.com/repos/phlax/pootle/pulls{/number}","milestones_url":"https://api.github.com/repos/phlax/pootle/milestones{/number}","notifications_url":"https://api.github.com/repos/phlax/pootle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/phlax/pootle/labels{/name}","releases_url":"https://api.github.com/repos/phlax/pootle/releases{/id}","deployments_url":"https://api.github.com/repos/phlax/pootle/deployments","created_at":"2015-07-29T10:00:35Z","updated_at":"2016-01-14T21:46:37Z","pushed_at":"2016-05-10T18:06:28Z","git_url":"git://github.com/phlax/pootle.git","ssh_url":"git@github.com:phlax/pootle.git","clone_url":"https://github.com/phlax/pootle.git","svn_url":"https://github.com/phlax/pootle","homepage":"http://pootle.translatehouse.org","size":83286,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"translate:master","ref":"master","sha":"279542e65e9877ec7e969485fec00a817bc85de1","user":{"login":"translate","id":1538178,"avatar_url":"https://avatars.githubusercontent.com/u/1538178?v=3","gravatar_id":"","url":"https://api.github.com/users/translate","html_url":"https://github.com/translate","followers_url":"https://api.github.com/users/translate/followers","following_url":"https://api.github.com/users/translate/following{/other_user}","gists_url":"https://api.github.com/users/translate/gists{/gist_id}","starred_url":"https://api.github.com/users/translate/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/translate/subscriptions","organizations_url":"https://api.github.com/users/translate/orgs","repos_url":"https://api.github.com/users/translate/repos","events_url":"https://api.github.com/users/translate/events{/privacy}","received_events_url":"https://api.github.com/users/translate/received_events","type":"Organization","site_admin":false},"repo":{"id":3769702,"name":"pootle","full_name":"translate/pootle","owner":{"login":"translate","id":1538178,"avatar_url":"https://avatars.githubusercontent.com/u/1538178?v=3","gravatar_id":"","url":"https://api.github.com/users/translate","html_url":"https://github.com/translate","followers_url":"https://api.github.com/users/translate/followers","following_url":"https://api.github.com/users/translate/following{/other_user}","gists_url":"https://api.github.com/users/translate/gists{/gist_id}","starred_url":"https://api.github.com/users/translate/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/translate/subscriptions","organizations_url":"https://api.github.com/users/translate/orgs","repos_url":"https://api.github.com/users/translate/repos","events_url":"https://api.github.com/users/translate/events{/privacy}","received_events_url":"https://api.github.com/users/translate/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/translate/pootle","description":"Online translation tool","fork":false,"url":"https://api.github.com/repos/translate/pootle","forks_url":"https://api.github.com/repos/translate/pootle/forks","keys_url":"https://api.github.com/repos/translate/pootle/keys{/key_id}","collaborators_url":"https://api.github.com/repos/translate/pootle/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/translate/pootle/teams","hooks_url":"https://api.github.com/repos/translate/pootle/hooks","issue_events_url":"https://api.github.com/repos/translate/pootle/issues/events{/number}","events_url":"https://api.github.com/repos/translate/pootle/events","assignees_url":"https://api.github.com/repos/translate/pootle/assignees{/user}","branches_url":"https://api.github.com/repos/translate/pootle/branches{/branch}","tags_url":"https://api.github.com/repos/translate/pootle/tags","blobs_url":"https://api.github.com/repos/translate/pootle/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/translate/pootle/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/translate/pootle/git/refs{/sha}","trees_url":"https://api.github.com/repos/translate/pootle/git/trees{/sha}","statuses_url":"https://api.github.com/repos/translate/pootle/statuses/{sha}","languages_url":"https://api.github.com/repos/translate/pootle/languages","stargazers_url":"https://api.github.com/repos/translate/pootle/stargazers","contributors_url":"https://api.github.com/repos/translate/pootle/contributors","subscribers_url":"https://api.github.com/repos/translate/pootle/subscribers","subscription_url":"https://api.github.com/repos/translate/pootle/subscription","commits_url":"https://api.github.com/repos/translate/pootle/commits{/sha}","git_commits_url":"https://api.github.com/repos/translate/pootle/git/commits{/sha}","comments_url":"https://api.github.com/repos/translate/pootle/comments{/number}","issue_comment_url":"https://api.github.com/repos/translate/pootle/issues/comments{/number}","contents_url":"https://api.github.com/repos/translate/pootle/contents/{+path}","compare_url":"https://api.github.com/repos/translate/pootle/compare/{base}...{head}","merges_url":"https://api.github.com/repos/translate/pootle/merges","archive_url":"https://api.github.com/repos/translate/pootle/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/translate/pootle/downloads","issues_url":"https://api.github.com/repos/translate/pootle/issues{/number}","pulls_url":"https://api.github.com/repos/translate/pootle/pulls{/number}","milestones_url":"https://api.github.com/repos/translate/pootle/milestones{/number}","notifications_url":"https://api.github.com/repos/translate/pootle/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/translate/pootle/labels{/name}","releases_url":"https://api.github.com/repos/translate/pootle/releases{/id}","deployments_url":"https://api.github.com/repos/translate/pootle/deployments","created_at":"2012-03-19T23:06:03Z","updated_at":"2016-05-09T17:55:54Z","pushed_at":"2016-05-10T18:12:38Z","git_url":"git://github.com/translate/pootle.git","ssh_url":"git@github.com:translate/pootle.git","clone_url":"https://github.com/translate/pootle.git","svn_url":"https://github.com/translate/pootle","homepage":"http://pootle.translatehouse.org","size":87986,"stargazers_count":630,"watchers_count":630,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":187,"mirror_url":null,"open_issues_count":338,"forks":187,"open_issues":338,"watchers":630,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/translate/pootle/pulls/4720"},"html":{"href":"https://github.com/translate/pootle/pull/4720"},"issue":{"href":"https://api.github.com/repos/translate/pootle/issues/4720"},"comments":{"href":"https://api.github.com/repos/translate/pootle/issues/4720/comments"},"review_comments":{"href":"https://api.github.com/repos/translate/pootle/pulls/4720/comments"},"review_comment":{"href":"https://api.github.com/repos/translate/pootle/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/translate/pootle/pulls/4720/commits"},"statuses":{"href":"https://api.github.com/repos/translate/pootle/statuses/6834a83d6675a4d84e26ebbf3f26acea86075d0e"}}}}