pfdo 6.0.0__py3-none-any.whl
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.
- pfdo/__init__.py +37 -0
- pfdo/__main__.py +331 -0
- pfdo/models.py +75 -0
- pfdo/pfdo.py +200 -0
- pfdo-6.0.0.dist-info/METADATA +109 -0
- pfdo-6.0.0.dist-info/RECORD +10 -0
- pfdo-6.0.0.dist-info/WHEEL +5 -0
- pfdo-6.0.0.dist-info/entry_points.txt +2 -0
- pfdo-6.0.0.dist-info/licenses/LICENSE +201 -0
- pfdo-6.0.0.dist-info/top_level.txt +1 -0
pfdo/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""pfdo: walk a directory tree with pftree and do something in every directory.
|
|
2
|
+
|
|
3
|
+
The base class of a family of tools, each doing one thing per directory of an input
|
|
4
|
+
tree and writing results to the same place in an output tree. ``Pfdo`` is a
|
|
5
|
+
``Pftree`` whose callbacks are meant to be overridden, plus the family's shared
|
|
6
|
+
``--analyzeFileIndex`` choice of which files the analyze step gets.
|
|
7
|
+
|
|
8
|
+
Key components:
|
|
9
|
+
- Pfdo: the base class
|
|
10
|
+
- PfdoOptions: pftree's options plus pfdo's
|
|
11
|
+
- fileIndex_select: the ``--analyzeFileIndex`` rule
|
|
12
|
+
- __main__: the ``pfdo`` command line, its manual, and the parts a package
|
|
13
|
+
built on pfdo assembles its own from
|
|
14
|
+
|
|
15
|
+
Typical usage:
|
|
16
|
+
from pfdo import Pfdo, PfdoOptions
|
|
17
|
+
Pfdo(PfdoOptions(inputDir=Path("/in"), outputDir=Path("/out"), testRun=True)).run()
|
|
18
|
+
|
|
19
|
+
Dependencies:
|
|
20
|
+
- pftree
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
__version__: str = "6.0.0"
|
|
26
|
+
|
|
27
|
+
from pfdo.models import PfdoOptions, fileIndex_validate
|
|
28
|
+
from pfdo.pfdo import ANALYZED_PREFIX, Pfdo, fileIndex_select
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ANALYZED_PREFIX",
|
|
32
|
+
"Pfdo",
|
|
33
|
+
"PfdoOptions",
|
|
34
|
+
"__version__",
|
|
35
|
+
"fileIndex_select",
|
|
36
|
+
"fileIndex_validate",
|
|
37
|
+
]
|
pfdo/__main__.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""The ``pfdo`` command line and its manual.
|
|
2
|
+
|
|
3
|
+
``pfdo`` on its own walks a tree, applies the filters, and with ``--test`` runs the
|
|
4
|
+
base class's demonstration callbacks. Its parser and manual are assembled from
|
|
5
|
+
pftree's parts plus pfdo's own, and are themselves exposed in parts so a package
|
|
6
|
+
built on pfdo can assemble its own.
|
|
7
|
+
|
|
8
|
+
Key components:
|
|
9
|
+
- CLI_SELF, ARGS_SYNOPSIS_SELF, EXAMPLES: pfdo's manual pieces
|
|
10
|
+
- CLI_FULL, CLI_DS, ARGS_SYNOPSIS_FULL, ARGS_SYNOPSIS_DS: the assembled pieces,
|
|
11
|
+
standalone and ChRIS-DS style
|
|
12
|
+
- synopsis: the manual text for a command built on pfdo
|
|
13
|
+
- manual_build: the page, rendered through ``pftree.manual``
|
|
14
|
+
- parserSelf_build / parser_build: the argument parsers
|
|
15
|
+
- main: the entry point, honouring an explicit ``argv``
|
|
16
|
+
|
|
17
|
+
Typical usage:
|
|
18
|
+
pfdo --inputDir /data --outputDir /tmp/out --fileFilter jpg --test --json
|
|
19
|
+
pfdo --man
|
|
20
|
+
|
|
21
|
+
Dependencies:
|
|
22
|
+
- pftree (its CLI parts and manual renderer)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import json
|
|
29
|
+
import sys
|
|
30
|
+
from collections.abc import Sequence
|
|
31
|
+
|
|
32
|
+
from pftree.__main__ import (
|
|
33
|
+
ARGS_SYNOPSIS_CORE,
|
|
34
|
+
ARGS_SYNOPSIS_DS,
|
|
35
|
+
ARGS_SYNOPSIS_IO,
|
|
36
|
+
CLI_CORE,
|
|
37
|
+
CLI_DS_IO,
|
|
38
|
+
CLI_IO_CORE,
|
|
39
|
+
logging_configure,
|
|
40
|
+
parserCore_build,
|
|
41
|
+
parserIO_build,
|
|
42
|
+
)
|
|
43
|
+
from pftree.manual import Manual, manual_print
|
|
44
|
+
from pftree.models import RunResult
|
|
45
|
+
|
|
46
|
+
from pfdo import __version__
|
|
47
|
+
from pfdo.models import PfdoOptions
|
|
48
|
+
from pfdo.pfdo import Pfdo
|
|
49
|
+
|
|
50
|
+
BANNER_ART: str = r"""
|
|
51
|
+
|
|
52
|
+
__ _
|
|
53
|
+
/ _| | |
|
|
54
|
+
_ __ | |_ __| | ___
|
|
55
|
+
| '_ \ | _| / _` | / _ \
|
|
56
|
+
| |_) || | | (_| || (_) |
|
|
57
|
+
| .__/ |_| \__,_| \___/
|
|
58
|
+
| |
|
|
59
|
+
|_|
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
BANNER_TITLE: str = "Path-File Do (something)"
|
|
64
|
+
|
|
65
|
+
BANNER_TEXT: str = """
|
|
66
|
+
Path-File Do (something)
|
|
67
|
+
|
|
68
|
+
Recursively walk down a directory tree and perform some operation
|
|
69
|
+
on files in each directory (optionally filtered by some simple
|
|
70
|
+
expression). Results of each operation are saved in output tree
|
|
71
|
+
that preserves the input directory structure.
|
|
72
|
+
|
|
73
|
+
-- version {version} --
|
|
74
|
+
|
|
75
|
+
'pfdo' is the base infrastructure app/class for walking down some
|
|
76
|
+
dir tree, optionally finding and tagging files that conform to
|
|
77
|
+
a simple filter, and running (something) on the files. This class
|
|
78
|
+
provides the basic mechanism for using pftree in this manner and the
|
|
79
|
+
(something) operation should be provided by a subclass of this base.
|
|
80
|
+
|
|
81
|
+
As part of the "pf*" suite of applications, it is geared to IO as
|
|
82
|
+
directories. Nested directory trees within some input directory
|
|
83
|
+
are reconstructed in an output directory, preserving directory
|
|
84
|
+
structure.
|
|
85
|
+
|
|
86
|
+
In many ways, this module is a slight variation on 'pftree' on which
|
|
87
|
+
it builds. It can be thought of as less of a descendent and more of a
|
|
88
|
+
sibling.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
CLI_SELF: str = """
|
|
93
|
+
[--analyzeFileIndex <a|f|l|m|N>] \\
|
|
94
|
+
[--test] \\"""
|
|
95
|
+
|
|
96
|
+
ARGS_SYNOPSIS_SELF: str = """
|
|
97
|
+
[--test]
|
|
98
|
+
If specified, run the "dummy" internal callback loop triad. The test
|
|
99
|
+
flow simply tags files in some inputDir tree and "touches" them to a
|
|
100
|
+
reconstituted tree in the output directory, prefixed with the text
|
|
101
|
+
"analyzed-". An output file that already exists stops that directory
|
|
102
|
+
unless --overwrite is given.
|
|
103
|
+
|
|
104
|
+
[--analyzeFileIndex <a|f|l|m|N>]
|
|
105
|
+
An optional string to further "filter" the list of files that have
|
|
106
|
+
been detected to analyze. This is useful for certain operations
|
|
107
|
+
that might need only one file in an input directory to start but
|
|
108
|
+
then once started will self consume multiple files independently
|
|
109
|
+
of the external controller. The default is "a" which implies
|
|
110
|
+
*ALL* files in a given directory. The set of valid values:
|
|
111
|
+
|
|
112
|
+
"a": "all" files.
|
|
113
|
+
"m": only the "middle" file in the file list
|
|
114
|
+
"f": only the "first" file in the file list
|
|
115
|
+
"l": only the "last" file in the file list
|
|
116
|
+
"<N>": only the file at index N in the file list. This index
|
|
117
|
+
can "wrap" in the positive and negative direction and
|
|
118
|
+
can exceed the actual list length (in which case it is
|
|
119
|
+
mapped back into the original list).
|
|
120
|
+
|
|
121
|
+
Pedantically, in the negative direction:
|
|
122
|
+
'-1' will mean the last file, while
|
|
123
|
+
-<listLen> will mean the first file again
|
|
124
|
+
-<listLen+1> will mean the last file again
|
|
125
|
+
etc...
|
|
126
|
+
and in the positive direction:
|
|
127
|
+
<listLen> will mean the first file
|
|
128
|
+
<listLen>*2 will mean the first file
|
|
129
|
+
etc...
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
EXAMPLES: str = """
|
|
133
|
+
|
|
134
|
+
EXAMPLES
|
|
135
|
+
|
|
136
|
+
Perform a `pfdo` down some input directory:
|
|
137
|
+
|
|
138
|
+
pfdo \\
|
|
139
|
+
--inputDir /var/www/html/data \\
|
|
140
|
+
--fileFilter jpg \\
|
|
141
|
+
--outputDir /tmp/jpg \\
|
|
142
|
+
--test --json \\
|
|
143
|
+
--threads 0 --printElapsedTime
|
|
144
|
+
|
|
145
|
+
The above will find all files in the tree structure rooted at
|
|
146
|
+
/var/www/html/data that also contain the string "jpg" anywhere
|
|
147
|
+
in the filename. For each file found, a corresponding file will
|
|
148
|
+
be touched in the output directory, in the same tree location as
|
|
149
|
+
the original input. This touched file will be prefixed with the
|
|
150
|
+
string "analyzed-".
|
|
151
|
+
|
|
152
|
+
pfdo \\
|
|
153
|
+
--inputDir $PWD/raw \\
|
|
154
|
+
--dirFilter 100307 \\
|
|
155
|
+
--outputDir $PWD/out --test --json \\
|
|
156
|
+
--analyzeFileIndex m \\
|
|
157
|
+
--threads 0 --printElapsedTime
|
|
158
|
+
|
|
159
|
+
Here, only the middle file in (all) directories whose path contains
|
|
160
|
+
the string ``100307`` will be targetted.
|
|
161
|
+
|
|
162
|
+
Finally the elapsed time and a JSON output are printed.
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
CLI_FULL: str = CLI_IO_CORE + CLI_SELF + CLI_CORE
|
|
167
|
+
ARGS_SYNOPSIS_FULL: str = ARGS_SYNOPSIS_IO + ARGS_SYNOPSIS_SELF + ARGS_SYNOPSIS_CORE
|
|
168
|
+
CLI_DS: str = CLI_DS_IO + CLI_SELF + CLI_CORE
|
|
169
|
+
ARGS_SYNOPSIS_DS: str = ARGS_SYNOPSIS_DS + ARGS_SYNOPSIS_SELF + ARGS_SYNOPSIS_CORE
|
|
170
|
+
|
|
171
|
+
DESCRIPTION: str = """
|
|
172
|
+
DESCRIPTION
|
|
173
|
+
|
|
174
|
+
``pfdo`` provides the base mechanism for navigating some arbitrary
|
|
175
|
+
tree, providing the base hooks for operating on (possibly filtered)
|
|
176
|
+
files in each directory, and saving results in an output tree that
|
|
177
|
+
reflects the input tree topology.
|
|
178
|
+
|
|
179
|
+
A subclass overrides the inputAnalyze_callback and outputSave_callback
|
|
180
|
+
methods (each receives the DirNode for a directory and returns a
|
|
181
|
+
StageResult) and sets processAlways; ``pfdo`` itself only runs its
|
|
182
|
+
demonstration callbacks under --test.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def synopsis(shortOnly: bool = False, command: str = "pfdo") -> str:
|
|
187
|
+
"""Assemble the manual text.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
shortOnly: NAME, SYNOPSIS and a brief example only.
|
|
191
|
+
command: the command name shown, for a package built on pfdo.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The manual text, without the banner.
|
|
195
|
+
"""
|
|
196
|
+
shortSynopsis: str = (
|
|
197
|
+
f"""
|
|
198
|
+
NAME
|
|
199
|
+
|
|
200
|
+
{command}
|
|
201
|
+
|
|
202
|
+
SYNOPSIS
|
|
203
|
+
|
|
204
|
+
{command} \\"""
|
|
205
|
+
+ CLI_FULL
|
|
206
|
+
+ f"""
|
|
207
|
+
|
|
208
|
+
BRIEF EXAMPLE
|
|
209
|
+
|
|
210
|
+
{command} \\
|
|
211
|
+
--inputDir /var/www/html/data \\
|
|
212
|
+
--fileFilter jpg \\
|
|
213
|
+
--outputDir /var/www/html/jpg \\
|
|
214
|
+
--threads 0 --printElapsedTime --test
|
|
215
|
+
"""
|
|
216
|
+
)
|
|
217
|
+
if shortOnly:
|
|
218
|
+
return shortSynopsis
|
|
219
|
+
return shortSynopsis + DESCRIPTION + "\n ARGS " + ARGS_SYNOPSIS_FULL + EXAMPLES
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def manual_build() -> Manual:
|
|
223
|
+
"""The pfdo manual page.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
The page, with both synopses assembled.
|
|
227
|
+
"""
|
|
228
|
+
return Manual(
|
|
229
|
+
command="pfdo",
|
|
230
|
+
art=BANNER_ART,
|
|
231
|
+
title=BANNER_TITLE,
|
|
232
|
+
text=BANNER_TEXT,
|
|
233
|
+
version=__version__,
|
|
234
|
+
synopsisShort=synopsis(shortOnly=True),
|
|
235
|
+
synopsisFull=synopsis(shortOnly=False),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def parserSelf_build() -> argparse.ArgumentParser:
|
|
240
|
+
"""pfdo's own options.
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
A parent parser with ``--test`` and ``--analyzeFileIndex``.
|
|
244
|
+
"""
|
|
245
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(add_help=False)
|
|
246
|
+
parser.add_argument("--test", dest="testRun", action="store_true", help="run the demonstration callbacks")
|
|
247
|
+
parser.add_argument(
|
|
248
|
+
"--analyzeFileIndex",
|
|
249
|
+
dest="analyzeFileIndex",
|
|
250
|
+
default="a",
|
|
251
|
+
help="which files per directory the analyze step gets: a (all), f, l, m, or an index",
|
|
252
|
+
)
|
|
253
|
+
return parser
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def parser_build(standalone: bool = True) -> argparse.ArgumentParser:
|
|
257
|
+
"""Assemble the full parser.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
standalone: include ``--inputDir`` / ``--outputDir``. A ChRIS DS plugin
|
|
261
|
+
passes False and adds its own positional directories.
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
The parser; ``--help`` is the quick reference and points at ``--man``.
|
|
265
|
+
"""
|
|
266
|
+
parents: list[argparse.ArgumentParser] = [parserCore_build("pfdo", __version__), parserSelf_build()]
|
|
267
|
+
if standalone:
|
|
268
|
+
parents.insert(0, parserIO_build())
|
|
269
|
+
return argparse.ArgumentParser(
|
|
270
|
+
prog="pfdo",
|
|
271
|
+
description="Walk a directory tree and do something in every directory. Full manual: pfdo --man",
|
|
272
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
273
|
+
parents=parents,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def options_fromArgs(args: argparse.Namespace) -> PfdoOptions:
|
|
278
|
+
"""Turn parsed arguments into options.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
args: the parser's Namespace.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
PfdoOptions with every known field applied.
|
|
285
|
+
"""
|
|
286
|
+
return PfdoOptions.fromDict(vars(args))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def result_report(args: argparse.Namespace, result: RunResult) -> None:
|
|
290
|
+
"""Print what the arguments asked for.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
args: the parsed arguments.
|
|
294
|
+
result: the run's result.
|
|
295
|
+
"""
|
|
296
|
+
if args.json:
|
|
297
|
+
print(json.dumps(result.toDict(), indent=4, sort_keys=True))
|
|
298
|
+
if args.printElapsedTime:
|
|
299
|
+
print(f"Elapsed time = {result.runTime:.6f} seconds")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
303
|
+
"""Run the command.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
argv: arguments without the program name; ``None`` reads ``sys.argv``.
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
The exit code: 0 on success, 1 when the run failed, 2 on bad arguments.
|
|
310
|
+
"""
|
|
311
|
+
parser: argparse.ArgumentParser = parser_build()
|
|
312
|
+
args: argparse.Namespace = parser.parse_args(argv)
|
|
313
|
+
if args.man or args.synopsis:
|
|
314
|
+
manual_print(manual_build(), shortOnly=not args.man)
|
|
315
|
+
return 0
|
|
316
|
+
if not args.inputDir:
|
|
317
|
+
parser.error("--inputDir is required")
|
|
318
|
+
try:
|
|
319
|
+
options: PfdoOptions = options_fromArgs(args)
|
|
320
|
+
except ValueError as err:
|
|
321
|
+
parser.error(str(err))
|
|
322
|
+
logging_configure(options.verbosity)
|
|
323
|
+
result: RunResult = Pfdo(options).run()
|
|
324
|
+
result_report(args, result)
|
|
325
|
+
if result.error:
|
|
326
|
+
print(result.error, file=sys.stderr)
|
|
327
|
+
return 0 if result.status else 1
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
if __name__ == "__main__":
|
|
331
|
+
sys.exit(main())
|
pfdo/models.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""The data models of pfdo.
|
|
2
|
+
|
|
3
|
+
pfdo adds two options to pftree's: which file(s) in each directory the analyze
|
|
4
|
+
callback should work on, and whether the base class's demonstration callbacks run.
|
|
5
|
+
Everything else, from the input and output directories to the filters and threads,
|
|
6
|
+
is pftree's ``TreeOptions``.
|
|
7
|
+
|
|
8
|
+
Key components:
|
|
9
|
+
- PfdoOptions: ``TreeOptions`` plus ``analyzeFileIndex`` and ``testRun``
|
|
10
|
+
- fileIndex_validate: the grammar of ``--analyzeFileIndex``
|
|
11
|
+
|
|
12
|
+
Typical usage:
|
|
13
|
+
options = PfdoOptions.fromDict(vars(args))
|
|
14
|
+
Pfdo(options).run()
|
|
15
|
+
|
|
16
|
+
Dependencies:
|
|
17
|
+
- pftree.models
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
|
|
24
|
+
from pftree.models import TreeOptions
|
|
25
|
+
|
|
26
|
+
FILE_INDEX_ALL: str = "a"
|
|
27
|
+
FILE_INDEX_FIRST: str = "f"
|
|
28
|
+
FILE_INDEX_LAST: str = "l"
|
|
29
|
+
FILE_INDEX_MIDDLE: str = "m"
|
|
30
|
+
FILE_INDEX_WORDS: tuple[str, ...] = (FILE_INDEX_ALL, FILE_INDEX_FIRST, FILE_INDEX_LAST, FILE_INDEX_MIDDLE)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def fileIndex_validate(spec: str) -> None:
|
|
34
|
+
"""Check an ``--analyzeFileIndex`` value.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
spec: ``a``, ``f``, ``l``, ``m`` or an integer, possibly negative.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: for anything else.
|
|
41
|
+
"""
|
|
42
|
+
if spec in FILE_INDEX_WORDS:
|
|
43
|
+
return
|
|
44
|
+
try:
|
|
45
|
+
int(spec)
|
|
46
|
+
except ValueError as err:
|
|
47
|
+
raise ValueError(f"analyzeFileIndex must be a, f, l, m or an integer, not {spec!r}") from err
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class PfdoOptions(TreeOptions):
|
|
52
|
+
"""pftree's options plus pfdo's own.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
analyzeFileIndex: which of a directory's files the analyze callback gets:
|
|
56
|
+
``a`` all (the default), ``f`` the first, ``l`` the last, ``m`` the
|
|
57
|
+
middle, or an integer index that wraps in either direction.
|
|
58
|
+
testRun: run the base class's demonstration callbacks over the tree.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> PfdoOptions.fromDict({"inputDir": "/in", "analyzeFileIndex": "-1"}).analyzeFileIndex
|
|
62
|
+
'-1'
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
analyzeFileIndex: str = FILE_INDEX_ALL
|
|
66
|
+
testRun: bool = False
|
|
67
|
+
|
|
68
|
+
def __post_init__(self) -> None:
|
|
69
|
+
"""Validate pftree's options and then ``analyzeFileIndex``.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: on a bad filter, leaf template or file index.
|
|
73
|
+
"""
|
|
74
|
+
super().__post_init__()
|
|
75
|
+
fileIndex_validate(self.analyzeFileIndex)
|
pfdo/pfdo.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""pfdo: walk a tree with pftree and do something in every directory.
|
|
2
|
+
|
|
3
|
+
``Pfdo`` is the base class of a family of tools (pfdo_run, pfdo_med2image and
|
|
4
|
+
others) that each do one thing per directory of an input tree and write their
|
|
5
|
+
results to the same place in an output tree. It is a ``Pftree`` whose three
|
|
6
|
+
callbacks are meant to be overridden, plus one convenience the family shares: the
|
|
7
|
+
``--analyzeFileIndex`` choice of which of a directory's files the analyze step gets.
|
|
8
|
+
|
|
9
|
+
The base class's own callbacks are a demonstration: read lists the files, analyze
|
|
10
|
+
picks by index and prefixes each name with ``analyzed-``, save touches those names
|
|
11
|
+
in the output directory. ``pfdo --test`` runs them.
|
|
12
|
+
|
|
13
|
+
Key components:
|
|
14
|
+
- Pfdo: the base class; override ``inputAnalyze_callback`` and
|
|
15
|
+
``outputSave_callback``, set ``processAlways`` to True
|
|
16
|
+
- fileIndex_select: the ``--analyzeFileIndex`` rule, as a pure function
|
|
17
|
+
|
|
18
|
+
Typical usage:
|
|
19
|
+
class Mine(Pfdo):
|
|
20
|
+
processAlways = True
|
|
21
|
+
|
|
22
|
+
def inputAnalyze_callback(self, node: DirNode, read: StageResult) -> StageResult:
|
|
23
|
+
for name in self.filesToAnalyze(node):
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
Mine(PfdoOptions.fromDict(vars(args))).run()
|
|
27
|
+
|
|
28
|
+
Dependencies:
|
|
29
|
+
- pftree
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import logging
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
from pftree.models import DirNode, ProcessResult, RunResult, StageResult
|
|
38
|
+
from pftree.tree import Pftree
|
|
39
|
+
|
|
40
|
+
from pfdo.models import (
|
|
41
|
+
FILE_INDEX_ALL,
|
|
42
|
+
FILE_INDEX_FIRST,
|
|
43
|
+
FILE_INDEX_LAST,
|
|
44
|
+
FILE_INDEX_MIDDLE,
|
|
45
|
+
PfdoOptions,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
logger: logging.Logger = logging.getLogger("pfdo")
|
|
49
|
+
|
|
50
|
+
ANALYZED_PREFIX: str = "analyzed-"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def fileIndex_select(files: list[str], spec: str) -> list[str]:
|
|
54
|
+
"""Pick the files ``--analyzeFileIndex`` names out of a directory's list.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
files: the directory's file names, in order.
|
|
58
|
+
spec: ``a`` for all, ``f`` first, ``l`` last, ``m`` middle, or an integer.
|
|
59
|
+
An integer wraps: ``-1`` is the last file, ``len(files)`` the first again.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The chosen files, in their original order; empty when ``files`` is empty.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: if ``spec`` is none of the above.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
>>> fileIndex_select(["a", "b", "c"], "m")
|
|
69
|
+
['b']
|
|
70
|
+
>>> fileIndex_select(["a", "b", "c"], "-1")
|
|
71
|
+
['c']
|
|
72
|
+
"""
|
|
73
|
+
if not files:
|
|
74
|
+
return []
|
|
75
|
+
match spec:
|
|
76
|
+
case s if s == FILE_INDEX_ALL:
|
|
77
|
+
return list(files)
|
|
78
|
+
case s if s == FILE_INDEX_FIRST:
|
|
79
|
+
return [files[0]]
|
|
80
|
+
case s if s == FILE_INDEX_LAST:
|
|
81
|
+
return [files[-1]]
|
|
82
|
+
case s if s == FILE_INDEX_MIDDLE:
|
|
83
|
+
return [files[len(files) // 2]]
|
|
84
|
+
case _:
|
|
85
|
+
try:
|
|
86
|
+
index: int = int(spec)
|
|
87
|
+
except ValueError as err:
|
|
88
|
+
raise ValueError(f"analyzeFileIndex must be a, f, l, m or an integer, not {spec!r}") from err
|
|
89
|
+
return [files[index % len(files)]]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Pfdo(Pftree):
|
|
93
|
+
"""Walk an input tree and run a per-directory read / analyze / save sequence.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
options: the PfdoOptions in force.
|
|
97
|
+
processAlways: a subclass sets this True so ``run`` always runs the
|
|
98
|
+
callbacks; the base class runs them only under ``--test``.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> tool = Pfdo(PfdoOptions(inputDir=Path("."), outputDir=Path("/tmp/out"), verbosity=0))
|
|
102
|
+
>>> tool.run().status
|
|
103
|
+
True
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
processAlways: bool = False
|
|
107
|
+
|
|
108
|
+
def __init__(self, options: PfdoOptions) -> None:
|
|
109
|
+
super().__init__(options)
|
|
110
|
+
self.options: PfdoOptions = options
|
|
111
|
+
|
|
112
|
+
def filesToAnalyze(self, node: DirNode) -> list[str]:
|
|
113
|
+
"""The files of a directory that ``--analyzeFileIndex`` selects.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
node: the directory.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The selected names, in order.
|
|
120
|
+
"""
|
|
121
|
+
return fileIndex_select(node.files, self.options.analyzeFileIndex)
|
|
122
|
+
|
|
123
|
+
def inputRead_callback(self, node: DirNode) -> StageResult:
|
|
124
|
+
"""Demonstration read: list the directory's files without opening them.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
node: the directory.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Success with the files; failure if there are none.
|
|
131
|
+
"""
|
|
132
|
+
logger.debug("reading %s: %d files", node.relPath, len(node.files))
|
|
133
|
+
return StageResult(status=bool(node.files), files=list(node.files))
|
|
134
|
+
|
|
135
|
+
def inputAnalyze_callback(self, node: DirNode, read: StageResult) -> StageResult:
|
|
136
|
+
"""Demonstration analyze: select by index and tag each name.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
node: the directory.
|
|
140
|
+
read: the read stage's result.
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Success with ``analyzed-<name>`` for every selected file; failure when
|
|
144
|
+
the index selects nothing.
|
|
145
|
+
"""
|
|
146
|
+
selected: list[str] = fileIndex_select(read.files, self.options.analyzeFileIndex)
|
|
147
|
+
logger.debug("analyzing %s: %s", node.relPath, selected)
|
|
148
|
+
return StageResult(status=bool(selected), files=[ANALYZED_PREFIX + name for name in selected])
|
|
149
|
+
|
|
150
|
+
def outputSave_callback(self, node: DirNode, analyze: StageResult) -> StageResult:
|
|
151
|
+
"""Demonstration save: touch each analyzed name in the output directory.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
node: the directory; its ``outputPath`` is created.
|
|
155
|
+
analyze: the analyze stage's result.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Success with the names written. An existing file stops the directory
|
|
159
|
+
with a failure unless ``overwrite`` is set.
|
|
160
|
+
"""
|
|
161
|
+
node.outputPath.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
saved: list[str] = []
|
|
163
|
+
for name in analyze.files:
|
|
164
|
+
target: Path = node.outputPath / name
|
|
165
|
+
if target.exists() and not self.options.overwrite:
|
|
166
|
+
logger.error("%s exists; run with --overwrite to replace it", target)
|
|
167
|
+
return StageResult(status=False, files=saved)
|
|
168
|
+
target.touch()
|
|
169
|
+
saved.append(name)
|
|
170
|
+
logger.debug("saved %d files in %s", len(saved), node.outputPath)
|
|
171
|
+
return StageResult(status=True, files=saved)
|
|
172
|
+
|
|
173
|
+
def callbacks_run(self) -> ProcessResult:
|
|
174
|
+
"""Run the three callbacks over the tree."""
|
|
175
|
+
return self.tree_process(
|
|
176
|
+
inputRead_callback=self.inputRead_callback,
|
|
177
|
+
inputAnalyze_callback=self.inputAnalyze_callback,
|
|
178
|
+
outputWrite_callback=self.outputSave_callback,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def run(self) -> RunResult:
|
|
182
|
+
"""Probe, filter, then process when the class or ``--test`` asks for it.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
The run's result; ``status`` is False with ``error`` set when the input
|
|
186
|
+
directory is missing, or when processing failed in every directory.
|
|
187
|
+
"""
|
|
188
|
+
error: str = self.env_check()
|
|
189
|
+
if error:
|
|
190
|
+
logger.error(error)
|
|
191
|
+
return RunResult(status=False, error=error, runTime=self.runTime_get())
|
|
192
|
+
self.tree_construct(self.tree_probe())
|
|
193
|
+
if self.fileTokens or self.dirTokens:
|
|
194
|
+
self.tree_filter()
|
|
195
|
+
process: ProcessResult | None = None
|
|
196
|
+
status: bool = True
|
|
197
|
+
if self.processAlways or self.options.testRun:
|
|
198
|
+
process = self.callbacks_run()
|
|
199
|
+
status = process.status
|
|
200
|
+
return RunResult(status=status, tree=dict(self.tree), process=process, runTime=self.runTime_get())
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pfdo
|
|
3
|
+
Version: 6.0.0
|
|
4
|
+
Summary: Walk a directory tree with pftree and do something in every directory: the base class of the pfdo family
|
|
5
|
+
Author-email: FNNDSC <dev@babyMRI.org>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/FNNDSC/pfdo
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pftree>=4.1.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
pfdo
|
|
25
|
+
====
|
|
26
|
+
|
|
27
|
+
.. image:: https://github.com/FNNDSC/pfdo/actions/workflows/ci.yml/badge.svg
|
|
28
|
+
:target: https://github.com/FNNDSC/pfdo/actions/workflows/ci.yml
|
|
29
|
+
|
|
30
|
+
.. image:: https://img.shields.io/pypi/v/pfdo.svg
|
|
31
|
+
:target: https://pypi.org/project/pfdo/
|
|
32
|
+
|
|
33
|
+
.. image:: https://img.shields.io/badge/python-3.11%2B-blue.svg
|
|
34
|
+
:target: https://pypi.org/project/pfdo/
|
|
35
|
+
|
|
36
|
+
Walk a directory tree with ``pftree`` and do something in every directory: the base class of the pfdo family.
|
|
37
|
+
|
|
38
|
+
.. contents:: Table of Contents
|
|
39
|
+
|
|
40
|
+
Overview
|
|
41
|
+
--------
|
|
42
|
+
|
|
43
|
+
``pfdo`` is the base class of a family of tools (``pfdo_run``, ``pfdo_med2image``, ``pfdo_mgz2image`` and others) that each do one thing per directory of an input tree and write their results to the same place in an output tree. It is a ``pftree.Pftree`` whose three callbacks, read, analyze and save, are meant to be overridden, plus one convenience the family shares: ``--analyzeFileIndex``, the choice of which of a directory's files the analyze step gets.
|
|
44
|
+
|
|
45
|
+
On its own, ``pfdo`` walks a tree, applies ``pftree``'s filters, and with ``--test`` runs its demonstration callbacks: read lists each directory's files, analyze selects by index and prefixes every name with ``analyzed-``, save touches those names in the output directory.
|
|
46
|
+
|
|
47
|
+
Installation
|
|
48
|
+
------------
|
|
49
|
+
|
|
50
|
+
.. code:: bash
|
|
51
|
+
|
|
52
|
+
pip install pfdo
|
|
53
|
+
|
|
54
|
+
Python 3.11 or newer; depends on ``pftree``.
|
|
55
|
+
|
|
56
|
+
Command line
|
|
57
|
+
------------
|
|
58
|
+
|
|
59
|
+
.. code:: bash
|
|
60
|
+
|
|
61
|
+
pfdo --inputDir /data --outputDir /tmp/out --fileFilter jpg --test --json
|
|
62
|
+
pfdo --inputDir /data --outputDir /tmp/out --dirFilter 100307 --analyzeFileIndex m --test
|
|
63
|
+
pfdo --man
|
|
64
|
+
|
|
65
|
+
``pfdo --man`` prints the full manual, ``--synopsis`` its first page, ``--help`` the quick reference. All of ``pftree``'s options apply (``--fileFilter``, ``--dirFilter`` and their logic, ``--maxDepth``, ``--outputLeafDir``, ``--threads``, ``--relativeDir``, ``--followLinks``, ``--overwrite``, ``--json``, ``--printElapsedTime``); ``pfdo`` adds:
|
|
66
|
+
|
|
67
|
+
``--analyzeFileIndex <a|f|l|m|N>``
|
|
68
|
+
Which of a directory's files the analyze step receives: ``a`` all (default), ``f`` first, ``l`` last, ``m`` middle, or an integer index that wraps in either direction (``-1`` is the last file, ``len`` the first again).
|
|
69
|
+
|
|
70
|
+
``--test``
|
|
71
|
+
Run the demonstration callbacks. An output file that already exists stops that directory unless ``--overwrite`` is given.
|
|
72
|
+
|
|
73
|
+
Library
|
|
74
|
+
-------
|
|
75
|
+
|
|
76
|
+
.. code:: python
|
|
77
|
+
|
|
78
|
+
from pathlib import Path
|
|
79
|
+
from pftree.models import DirNode, StageResult
|
|
80
|
+
from pfdo import Pfdo, PfdoOptions
|
|
81
|
+
|
|
82
|
+
class Counter(Pfdo):
|
|
83
|
+
processAlways = True
|
|
84
|
+
|
|
85
|
+
def inputAnalyze_callback(self, node: DirNode, read: StageResult) -> StageResult:
|
|
86
|
+
chosen = self.filesToAnalyze(node)
|
|
87
|
+
return StageResult(status=True, files=chosen, payload={"count": len(chosen)})
|
|
88
|
+
|
|
89
|
+
def outputSave_callback(self, node: DirNode, analyze: StageResult) -> StageResult:
|
|
90
|
+
node.outputPath.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
(node.outputPath / "count.txt").write_text(f"{analyze.payload['count']}\n")
|
|
92
|
+
return StageResult(status=True, files=["count.txt"])
|
|
93
|
+
|
|
94
|
+
result = Counter(PfdoOptions(inputDir=Path("/data"), outputDir=Path("/out"), threads=4)).run()
|
|
95
|
+
|
|
96
|
+
``PfdoOptions`` is ``pftree.TreeOptions`` plus ``analyzeFileIndex`` and ``testRun``; ``PfdoOptions.fromDict(vars(args))`` builds it from an argparse ``Namespace`` and ignores keys it does not know. A subclass sets ``processAlways = True`` so ``run()`` always runs the callbacks; it calls ``self.filesToAnalyze(node)`` to honour ``--analyzeFileIndex``. ``run()`` returns ``pftree``'s ``RunResult`` (``status``, ``tree``, ``process``, ``runTime``).
|
|
97
|
+
|
|
98
|
+
For its command line, a package built on pfdo composes ``pfdo.__main__``'s parts: ``parserSelf_build()`` and ``pftree``'s ``parserCore_build()`` / ``parserIO_build()`` for the parser, ``synopsis(command=...)`` or the ``CLI_*`` and ``ARGS_SYNOPSIS_*`` texts for the manual, and ``pftree.manual.Manual`` with ``manual_print`` to render it.
|
|
99
|
+
|
|
100
|
+
Changes in 6.0
|
|
101
|
+
--------------
|
|
102
|
+
|
|
103
|
+
- Built on ``pftree`` 4; ``pfmisc`` and ``pudb`` are no longer dependencies.
|
|
104
|
+
- The callbacks are ``inputRead_callback``, ``inputAnalyze_callback`` and ``outputSave_callback``, taking a ``DirNode`` and returning a ``StageResult``, not tuples and dicts.
|
|
105
|
+
- ``--analyzeFileIndex m`` picks the true middle file (index ``len // 2``); 5.x picked one past it for odd-length lists.
|
|
106
|
+
- An existing output file fails that directory and the run continues; 5.x stopped the loop and left the directory half written.
|
|
107
|
+
- ``--test`` maps to ``testRun``; ``pftree``'s own ``--test <seconds>`` is not exposed.
|
|
108
|
+
- ``main(argv)`` honours ``argv``; ``--version``, ``--man`` and ``--synopsis`` exit 0.
|
|
109
|
+
- Packaging is ``pyproject.toml`` only, published from CI on a ``v*`` tag.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pfdo/__init__.py,sha256=0EsMWOqP_UED0Py4sGGz2oRZG3UAGffB52QsUkhsq-M,1155
|
|
2
|
+
pfdo/__main__.py,sha256=Wtt9AfTr3NDMKU4uY6BXf2YaJrWKHXcL9OY461WyguU,11891
|
|
3
|
+
pfdo/models.py,sha256=dFUrH0pig7PH-VtYzRsrf5tlKUnXOjYE54l4NzkO-Hs,2262
|
|
4
|
+
pfdo/pfdo.py,sha256=uQ3pflXjjigs7KLG-MWl5hTNEnCq7GNqZHVQ_nIW1dU,7268
|
|
5
|
+
pfdo-6.0.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
+
pfdo-6.0.0.dist-info/METADATA,sha256=Mg_b52rRbCqJr7_iRgvVs9N0qCwDK3cDcGjVHekkyEQ,5617
|
|
7
|
+
pfdo-6.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
pfdo-6.0.0.dist-info/entry_points.txt,sha256=hybk6kcGUEbZEyns6EGrrG3gjNWOUikazp2OTdwiPEQ,44
|
|
9
|
+
pfdo-6.0.0.dist-info/top_level.txt,sha256=shX2HON2eRe6QKssvfYouSWv7aH5e61HqAWqN8D2vgk,5
|
|
10
|
+
pfdo-6.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pfdo
|