repo-parser 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023-2024 William Lachance
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.1
2
+ Name: repo-parser
3
+ Version: 0.0.1
4
+ Summary: A library for extracting metadata out of a source repository.
5
+ Author-email: William Lachance <wlach@protonmail.com>
6
+ Description-Content-Type: text/markdown
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Requires-Dist: GitPython
9
+ Project-URL: Home, https://github.com/wlach/repo-parser
10
+
11
+ # repo-parser
12
+
13
+ This is a set of python scripts and tools for extracting metadata and structure
14
+ out of a monorepo, for the purposes of generating a service registry, unified documentation, or other types of data out of the contents.
15
+
16
+ It should be considered an experiment, rather than production software. It is inspired by my experiences in the software industry and being frustrated with current solutions being some combination of:
17
+
18
+ - Expensive
19
+ - Time consuming to configure and maintain
20
+ - Requiring the specification of redundant metadata (which is bound to get out of date)
21
+
22
+ Assumptions:
23
+
24
+ - The files you want to process fit into memory
25
+ - You don't care about history
26
+
27
+ You can see a demo of a documentation site generated from this repo at:
28
+
29
+ https://repo-parser-demo.netlify.app/
30
+
31
+ ## Local development
32
+
33
+ You can experiment with the local demo by running `make example`.
34
+ It should live-reload as you make changes in the `example/repo` directory.
35
+
36
+ For other tasks, look at the (very simple) `Makefile` in the root directory.
37
+
@@ -0,0 +1,26 @@
1
+ # repo-parser
2
+
3
+ This is a set of python scripts and tools for extracting metadata and structure
4
+ out of a monorepo, for the purposes of generating a service registry, unified documentation, or other types of data out of the contents.
5
+
6
+ It should be considered an experiment, rather than production software. It is inspired by my experiences in the software industry and being frustrated with current solutions being some combination of:
7
+
8
+ - Expensive
9
+ - Time consuming to configure and maintain
10
+ - Requiring the specification of redundant metadata (which is bound to get out of date)
11
+
12
+ Assumptions:
13
+
14
+ - The files you want to process fit into memory
15
+ - You don't care about history
16
+
17
+ You can see a demo of a documentation site generated from this repo at:
18
+
19
+ https://repo-parser-demo.netlify.app/
20
+
21
+ ## Local development
22
+
23
+ You can experiment with the local demo by running `make example`.
24
+ It should live-reload as you make changes in the `example/repo` directory.
25
+
26
+ For other tasks, look at the (very simple) `Makefile` in the root directory.
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["flit_core >=3.2,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "repo-parser"
7
+ authors = [{ name = "William Lachance", email = "wlach@protonmail.com" }]
8
+ readme = "README.md"
9
+ license = { file = "LICENSE" }
10
+ classifiers = ["License :: OSI Approved :: MIT License"]
11
+ dynamic = ["version", "description"]
12
+ dependencies = ["GitPython"]
13
+
14
+ [project.urls]
15
+ Home = "https://github.com/wlach/repo-parser"
16
+
17
+ [tool.ruff.lint]
18
+ select = ["E4", "E7", "E9", "F", "B", "I"]
@@ -0,0 +1,11 @@
1
+ """
2
+ A library for extracting metadata out of a source repository.
3
+ """
4
+
5
+ __version__ = "0.0.1"
6
+
7
+ from .filesystem import scan
8
+ from .processor import DEFAULT_PROCESSORS
9
+ from .resource import Resource, get_resources
10
+
11
+ __all__ = ["scan", "get_resources", "Resource", "DEFAULT_PROCESSORS"]
@@ -0,0 +1,70 @@
1
+ import pathlib
2
+ from dataclasses import dataclass
3
+ from pathlib import PurePath
4
+ from typing import List, Optional
5
+
6
+ import git
7
+
8
+ from .processor import Processor
9
+
10
+
11
+ @dataclass
12
+ class File:
13
+ name: str
14
+ src_path: PurePath
15
+ content: Optional[str]
16
+
17
+
18
+ @dataclass
19
+ class Dir:
20
+ path: pathlib.Path
21
+ files: List[File]
22
+ dirs: List["Dir"]
23
+
24
+
25
+ def _scan(path: pathlib.Path, processors: List[Processor], repo: git.Repo) -> Dir:
26
+ dir = Dir(path=pathlib.PurePath(path), files=[], dirs=[])
27
+
28
+ entries = [entry for entry in path.iterdir()]
29
+ if not entries:
30
+ return dir
31
+ # FIXME: At some point we should only ignore .git in the root directory
32
+ ignored = set(repo.ignored(*[entry.name for entry in entries]) + [".git"])
33
+
34
+ for entry in entries:
35
+ if entry.name in ignored:
36
+ continue
37
+ elif entry.is_file():
38
+ for processor in processors:
39
+ if processor.pattern.search(str(entry)):
40
+ content = entry.read_text() if processor.read_content else None
41
+ dir.files.append(
42
+ File(
43
+ name=entry.name,
44
+ src_path=(path / entry.name),
45
+ content=content,
46
+ )
47
+ )
48
+ elif entry.is_dir():
49
+ dir.dirs.append(_scan(path / entry.name, processors, repo))
50
+
51
+ return dir
52
+
53
+
54
+
55
+ def scan(path: pathlib.Path, processors: List[Processor]) -> Dir:
56
+ """
57
+ Scans a GitHub repository for files and subdirectories, returning a data
58
+ structure representing the directory tree.
59
+
60
+ Takes in a list of processors to figure out which files should be included
61
+ in the returned tree.
62
+
63
+ Under the hood, this uses the `git` library to scan the repository, skipping
64
+ files in .gitignore.
65
+
66
+ This step does not do any post-processing of the files, though their
67
+ content is read in if one of their processors requires it.
68
+ """
69
+ repo = git.Repo(path)
70
+ return _scan(path, processors, repo)
@@ -0,0 +1,25 @@
1
+ import re
2
+ from dataclasses import dataclass
3
+
4
+ import frontmatter
5
+
6
+
7
+ @dataclass
8
+ class Processor:
9
+ pattern: re.Pattern
10
+ process: callable
11
+ read_content: bool
12
+
13
+
14
+ def _parse_markdown(file_contents: str):
15
+ metadata, _ = frontmatter.parse(file_contents)
16
+
17
+ filetype = metadata.get("type", "file")
18
+ metadata.pop("type", None) # remove "type" from the metadata
19
+
20
+ return filetype, metadata, file_contents
21
+
22
+
23
+ DEFAULT_PROCESSORS = [
24
+ Processor(re.compile("\.md$"), _parse_markdown, True),
25
+ ]
@@ -0,0 +1,89 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import PurePath
3
+ from typing import List, Optional
4
+
5
+ from .filesystem import Dir
6
+ from .processor import Processor
7
+
8
+
9
+ @dataclass
10
+ class Resource:
11
+ name: str
12
+ src_path: PurePath
13
+ path: PurePath
14
+ type: str
15
+ metadata: dict
16
+ content: Optional[str]
17
+ children: List["Resource"]
18
+
19
+
20
+ def _get_resources(
21
+ dir: Dir, parent_path: PurePath, processors: List[Processor]
22
+ ) -> List[Resource]:
23
+ child_resources: List[Resource] = []
24
+ dir_resource: Optional[Resource] = None
25
+
26
+ # do a first pass to see if we can find a "dir_resource"
27
+ for file in dir.files:
28
+ for processor in processors:
29
+ if processor.pattern.search(file.name):
30
+ filetype, metadata, content = processor.process(file.content)
31
+ if filetype != "file":
32
+ if dir_resource is None:
33
+ dir_resource = Resource(
34
+ name=dir.path.name,
35
+ src_path=dir.path,
36
+ path=PurePath(),
37
+ type=filetype,
38
+ metadata=metadata,
39
+ content=None,
40
+ children=[],
41
+ )
42
+ # parent path is now the dir_resource
43
+ parent_path = dir_resource.path
44
+ else:
45
+ # a previous processor picked this up, augment the metadata
46
+ # FIXME: I think we want to be able to override the name as well
47
+ dir_resource.metadata.update(metadata)
48
+
49
+ # do a second pass to find all the child resources, placing
50
+ # their paths relative to the parent
51
+ for file in dir.files:
52
+ if processor.pattern.search(file.name):
53
+ child_resources.append(
54
+ Resource(
55
+ name=file.name,
56
+ path=parent_path / file.name,
57
+ src_path=file.src_path,
58
+ type="file",
59
+ metadata=metadata,
60
+ content=file.content,
61
+ children=[],
62
+ )
63
+ )
64
+
65
+ for subdir in dir.dirs:
66
+ child_resources.extend(
67
+ _get_resources(subdir, parent_path / subdir.path.name, processors)
68
+ )
69
+
70
+ # if this directory did not define a new resource, append any newly
71
+ # found resources to the parent
72
+ if dir_resource is None:
73
+ return child_resources
74
+
75
+ # otherwise, add the child resources to the dir_resource, and return that
76
+ dir_resource.children = child_resources
77
+ return [dir_resource]
78
+
79
+
80
+ def get_resources(dir: Dir, processors: List[Processor]) -> Resource:
81
+ return Resource(
82
+ name=dir.path.name,
83
+ path=PurePath(),
84
+ src_path=dir.path,
85
+ type="repo",
86
+ metadata={},
87
+ children=_get_resources(dir, PurePath(), processors),
88
+ content=None,
89
+ )