PyProd 0.2.0.post1__py3-none-any.whl → 0.3.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
pyprod/prod.py CHANGED
@@ -33,7 +33,7 @@ class NoRuleToMakeTargetError(Exception):
33
33
  pass
34
34
 
35
35
 
36
- class InvalidRuleError(Exception):
36
+ class RuleError(Exception):
37
37
  pass
38
38
 
39
39
 
@@ -109,13 +109,11 @@ def glob(path, dir="."):
109
109
 
110
110
  def rule_to_re(rule):
111
111
  if not isinstance(rule, (str, Path)):
112
- raise InvalidRuleError(rule)
112
+ raise RuleError(rule)
113
113
 
114
114
  srule = str(rule)
115
115
  srule = translate(srule)
116
116
  srule = replace_pattern(srule, "(?P<stem>.*)", maxreplace=1)
117
- if isinstance(rule, Path):
118
- return Path(srule)
119
117
  return srule
120
118
 
121
119
 
@@ -132,38 +130,97 @@ def replace_pattern(rule, replaceto, *, maxreplace=None):
132
130
  if maxreplace is not None:
133
131
  if n > maxreplace:
134
132
  # contains multiple '%'
135
- raise InvalidRuleError(s_rule)
133
+ raise RuleError(f"{s_rule} contains multiple '%'")
134
+
136
135
  return replaceto
137
136
 
138
137
  s_rule = re.sub("%%|%", f, s_rule)
139
- if isinstance(rule, Path):
140
- return Path(s_rule)
141
138
  return s_rule
142
139
 
143
140
 
144
- @dataclass
141
+ def _check_pattern_count(pattern):
142
+ """Counts number of '%' in the pattern"""
143
+ matches = re.finditer(r"%%|%", pattern)
144
+ num = len([m for m in matches if len(m[0]) == 1])
145
+ if num > 1:
146
+ raise RuleError(f"{pattern}: Multiple '%' is not allowed")
147
+ return num
148
+
149
+
150
+ def _check_pattern(pattern):
151
+ matches = re.finditer(r"%%|%", pattern)
152
+ singles = [m for m in matches if len(m[0]) == 1]
153
+ if len(singles) > 1:
154
+ raise RuleError(f"{pattern}: Multiple '%' is not allowed")
155
+ if not len(singles):
156
+ raise RuleError(f"{pattern}: Pattern should contain a '%'.")
157
+
158
+
159
+ def _strip_dot(path):
160
+ if not path:
161
+ return path
162
+ path = Path(path) # ./aaa/ -> aaa
163
+ parts = path.parts
164
+ if ".." in parts:
165
+ raise RuleError(f"{path}: '..' directory is not allowed")
166
+ return str(path)
167
+
168
+
169
+ def _check_wildcard(path):
170
+ if "*" in path:
171
+ raise RuleError(f"{path}: '*' directory is not allowed")
172
+
173
+
145
174
  class Rule:
146
- targets: list
147
- pattern: str | None
148
- depends: list
149
- uses: list | None
150
- builder: str | None
151
-
152
- orig_targets: list = field(init=False)
153
- orig_depends: list = field(init=False)
154
- orig_uses: list = field(init=False)
155
-
156
- def __post_init__(self):
157
- self.orig_targets = self.targets
158
- self.orig_depends = self.depends
159
- self.orig_uses = self.uses
160
-
161
- self.targets = [rule_to_re(r) for r in flatten(self.targets)]
162
- self.depends = list(flatten(self.depends))
163
- if self.pattern:
164
- self.pattern = rule_to_re(self.pattern)
165
- if self.uses:
166
- self.uses = list(flatten(self.uses))
175
+ def __init__(self, targets, pattern, depends, uses, builder=None):
176
+ self.targets = []
177
+ for target in flatten(targets or ()):
178
+ target = str(target)
179
+ _check_pattern_count(target)
180
+ target = _strip_dot(target)
181
+ target = rule_to_re(target)
182
+ self.targets.append(target)
183
+
184
+ self.first_target = None
185
+ for target in flatten(targets or ()):
186
+ target = str(target)
187
+ if not target:
188
+ continue
189
+
190
+ if "*" in target:
191
+ continue
192
+
193
+ if _check_pattern_count(target) == 0:
194
+ # not contain one %
195
+ self.first_target = target
196
+ break
197
+
198
+ if pattern:
199
+ pattern = _strip_dot(pattern)
200
+ if _check_pattern_count(pattern) != 1:
201
+ raise RuleError(f"{pattern}: Pattern should contain a '%'")
202
+
203
+ self.pattern = rule_to_re(pattern)
204
+ else:
205
+ self.pattern = None
206
+
207
+ self.depends = []
208
+ for depend in flatten(depends or ()):
209
+ depend = str(depend)
210
+ _check_pattern_count(depend)
211
+ _check_wildcard(depend)
212
+ depend = _strip_dot(depend)
213
+ self.depends.append(depend)
214
+
215
+ self.uses = []
216
+ for use in flatten(uses or ()):
217
+ use = str(use)
218
+ _check_pattern_count(use)
219
+ _check_wildcard(use)
220
+ use = _strip_dot(use)
221
+ self.uses.append(use)
222
+
223
+ self.builder = builder
167
224
 
168
225
  def __call__(self, f):
169
226
  self.builder = f
@@ -211,12 +268,15 @@ class Rules:
211
268
  stem = d
212
269
  elif dep.pattern:
213
270
  m = re.fullmatch(str(dep.pattern), name)
214
- d = m.groupdict().get("stem", None)
215
- if d is not None:
216
- stem = d
271
+ if m:
272
+ stem = m.groupdict().get("stem", None)
217
273
 
218
- depends = [replace_pattern(r, stem) for r in dep.depends]
219
- uses = [replace_pattern(r, stem) for r in dep.uses]
274
+ if stem is not None:
275
+ depends = [replace_pattern(r, stem) for r in dep.depends]
276
+ uses = [replace_pattern(r, stem) for r in dep.uses]
277
+ else:
278
+ depends = dep.depends[:]
279
+ uses = dep.uses[:]
220
280
 
221
281
  yield depends, uses, dep
222
282
  break
@@ -237,14 +297,8 @@ class Rules:
237
297
 
238
298
  def select_first_target(self):
239
299
  for dep in self.rules:
240
- for target in dep.orig_targets:
241
- s_target = str(target)
242
- # pattern?
243
- matches = re.finditer(r"%%|%", s_target)
244
- if any((len(m[0]) == 1) for m in matches):
245
- # has %
246
- continue
247
- return target
300
+ if dep.first_target:
301
+ return dep.first_target
248
302
 
249
303
  def select_builder(self, name):
250
304
  for depends, uses, dep in self.iter_rule(name):
@@ -356,6 +410,30 @@ class Envs:
356
410
  return os.environ.get(name, default=default)
357
411
 
358
412
 
413
+ def read(filename):
414
+ with open(filename, "r") as f:
415
+ return f.read()
416
+
417
+
418
+ def write(filename, s, append=False):
419
+ mode = "a" if append else "w"
420
+ with open(filename, mode) as f:
421
+ f.write(s)
422
+
423
+
424
+ def quote(s):
425
+ return shlex.quote(str(s))
426
+
427
+
428
+ def squote(*s):
429
+ ret = [shlex.quote(str(x)) for x in flatten(s)]
430
+ return ret
431
+
432
+
433
+ def makedirs(path):
434
+ os.makedirs(path, exist_ok=True)
435
+
436
+
359
437
  class Prod:
360
438
  def __init__(self, modulefile, njobs=1, params=None):
361
439
  self.modulefile = Path(modulefile)
@@ -372,18 +450,25 @@ class Prod:
372
450
 
373
451
  def get_module_globals(self):
374
452
  globals = {
453
+ "capture": capture,
454
+ "check": self.checkers.check,
455
+ "environ": Envs(),
456
+ "glob": glob,
457
+ "makedirs": makedirs,
458
+ "os": os,
459
+ "params": self.params,
375
460
  "pip": pip,
461
+ "quote": quote,
462
+ "q": quote,
463
+ "squote": squote,
464
+ "sq": squote,
465
+ "read": read,
376
466
  "rule": self.rules.rule,
377
- "check": self.checkers.check,
378
- "Path": Path,
379
467
  "run": run,
380
- "capture": capture,
381
- "glob": glob,
382
- "MAX_TS": MAX_TS,
383
- "environ": Envs(),
384
468
  "shutil": shutil,
385
- "quote": shlex.quote,
386
- "params": self.params,
469
+ "write": write,
470
+ "MAX_TS": MAX_TS,
471
+ "Path": Path,
387
472
  }
388
473
  return globals
389
474
 
@@ -432,13 +517,13 @@ class Prod:
432
517
  if isinstance(obj, str | Path):
433
518
  builds.append(obj)
434
519
  elif isinstance(obj, Rule):
435
- raise InvalidRuleError(obj)
520
+ raise RuleError(obj)
436
521
  elif callable(obj):
437
522
  self.built += 1
438
523
  task = asyncio.create_task(self.run_in_executor(obj))
439
524
  waitings.append(task)
440
525
  else:
441
- raise InvalidRuleError(obj)
526
+ raise RuleError(obj)
442
527
 
443
528
  await self.build(builds)
444
529
  await asyncio.gather(*waitings)
@@ -497,11 +582,11 @@ class Prod:
497
582
  return Exists(name, True, ret)
498
583
 
499
584
  async def run(self, name): # -> Any | int:
500
- s_name = str(name)
585
+ name = str(name)
501
586
 
502
- self.rules.build_tree(s_name)
503
- deps, uses = self.rules.get_dep_names(s_name)
504
- selected = self.rules.select_builder(s_name)
587
+ self.rules.build_tree(name)
588
+ deps, uses = self.rules.get_dep_names(name)
589
+ selected = self.rules.select_builder(name)
505
590
  if selected:
506
591
  build_deps, build_uses, builder = selected
507
592
  deps = deps + build_deps
@@ -513,19 +598,19 @@ class Prod:
513
598
  if uses:
514
599
  await self.build(uses)
515
600
 
516
- exists = await self.is_exists(s_name)
601
+ exists = await self.is_exists(name)
517
602
 
518
603
  if not exists.exists:
519
- logger.debug("%r does not exists", str(s_name))
604
+ logger.debug("%r does not exists", name)
520
605
  elif (ts >= MAX_TS) or (exists.ts < ts):
521
- logger.debug("%r should be updated", str(s_name))
606
+ logger.debug("%r should be updated", name)
522
607
  else:
523
- logger.debug("%r already exists", str(s_name))
608
+ logger.debug("%r already exists", name)
524
609
 
525
610
  if not exists.exists and not selected:
526
- raise NoRuleToMakeTargetError(s_name)
611
+ raise NoRuleToMakeTargetError(name)
527
612
  elif selected and ((not exists.exists) or (ts >= MAX_TS) or (exists.ts < ts)):
528
- logger.warning("building: %r", s_name)
613
+ logger.warning("building: %r", name)
529
614
  await self.run_in_executor(builder.builder, name, *build_deps)
530
615
  self.built += 1
531
616
  return MAX_TS
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyProd
3
- Version: 0.2.0.post1
3
+ Version: 0.3.0
4
4
  Summary: PyProd: More Makeable than Make
5
5
  Project-URL: Homepage, https://github.com/atsuoishimoto/pyprod
6
6
  Project-URL: Documentation, https://pyprod.readthedocs.io/en/latest/
@@ -0,0 +1,11 @@
1
+ pyprod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pyprod/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
3
+ pyprod/main.py,sha256=nS0mcLy3Ix7ydnX1Pu5g7ceLzhh6DndwJWO5sjO_qWg,2580
4
+ pyprod/prod.py,sha256=r2e-JSHiTw2SOcv-tYFx4aRpVkQbOb5j9vEVrjT0meE,16694
5
+ pyprod/utils.py,sha256=oiiUkSbeqTazbtJ6gz7ZKqG1OvAeV-nV9u_3Y0DCOOM,401
6
+ pyprod/venv.py,sha256=_riw56YQvUOSd55u_1m9ElsqPdjM5qVvIZP6dr9Fzt4,1051
7
+ pyprod-0.3.0.dist-info/METADATA,sha256=t9TMYeBveYE1LFZNpcYJAtOdUo7gPARFHmIZ9p0bnag,2477
8
+ pyprod-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ pyprod-0.3.0.dist-info/entry_points.txt,sha256=zFycf8BYSMRDTiI0jftmcvtkf9XM4MZ4BL3JaIer_ZM,44
10
+ pyprod-0.3.0.dist-info/licenses/LICENSE,sha256=OtPgwnlLrsVEYPnTraun5AqftAT5vUv4rIan-qYj7nE,1071
11
+ pyprod-0.3.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- pyprod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- pyprod/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
3
- pyprod/main.py,sha256=nS0mcLy3Ix7ydnX1Pu5g7ceLzhh6DndwJWO5sjO_qWg,2580
4
- pyprod/prod.py,sha256=poLehE5Gs_vEZvKrSdMicF016tQeaMMAnFc76ssPQQY,14586
5
- pyprod/utils.py,sha256=oiiUkSbeqTazbtJ6gz7ZKqG1OvAeV-nV9u_3Y0DCOOM,401
6
- pyprod/venv.py,sha256=_riw56YQvUOSd55u_1m9ElsqPdjM5qVvIZP6dr9Fzt4,1051
7
- pyprod-0.2.0.post1.dist-info/METADATA,sha256=WnpzS2zWCnRZxR9cHjrfkI7yUd742pp5idOzdNyJ7Fk,2483
8
- pyprod-0.2.0.post1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- pyprod-0.2.0.post1.dist-info/entry_points.txt,sha256=zFycf8BYSMRDTiI0jftmcvtkf9XM4MZ4BL3JaIer_ZM,44
10
- pyprod-0.2.0.post1.dist-info/licenses/LICENSE,sha256=OtPgwnlLrsVEYPnTraun5AqftAT5vUv4rIan-qYj7nE,1071
11
- pyprod-0.2.0.post1.dist-info/RECORD,,