Skip to content

Commit b4561ef

Browse files
Merge pull request #2 from Adwaith-Rajesh/mtime
Mtime
2 parents 4e03639 + d8c0e19 commit b4561ef

File tree

6 files changed

+498
-70
lines changed

6 files changed

+498
-70
lines changed

README.md

+28-57
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ And,
2222
It does come with some feature.
2323

2424
- It is just command runner (Does not give an _f_ about the file or it's mod time)
25+
- v0.3.0 onwards, buildme does give an _f_ about the file and it's mod times (times have changed)
2526
- It's just python. Anything that works on python works on buildme script.
2627
- you want a build script that has access to `pandas`, well you can now have it.
2728

@@ -40,34 +41,9 @@ pip3 install buildme
4041
4142
```python
4243
#!/bin/env buildme
43-
from argparse import Namespace # for type hinting purposes only
4444
from buildme import CommandRunner, target
4545

46-
47-
cr = CommandRunner(
48-
shell=False, # prevents invoking 'mystery' programs (default=False)
49-
print_cmd=True, # prints the command that is currently being executed (default=True)
50-
print_cmd_sep=True, # prints a separation line between the commands that are ran (default=True)
51-
exit_non_zero=True, # exit the buildme execution if a command exits non zero (default=True)
52-
)
53-
54-
# you can override the exit_no_zero using the
55-
# method CommandRunner.set_exit_non_zero(vel: bool)
56-
57-
58-
@target(depends=['test'])
59-
def hello(opts: Namespace, _):
60-
print(opts)
61-
code = cr.run('echo Hello World')
62-
print(f'{code=}')
63-
64-
65-
@target()
66-
def test(opts: Namespace, _):
67-
if getattr(opts, 'release', None) is not None and opts.release == '0':
68-
print('release is zero')
69-
print('This from test')
70-
46+
cr = CommandRunner()
7147

7248
@target()
7349
def foo(_, __):
@@ -76,52 +52,47 @@ def foo(_, __):
7652

7753
@target()
7854
def bar(_, __):
79-
print('This is the bar target')
55+
cr.run('echo this is from bar target')
8056

8157

82-
@target(depends=['test'])
58+
@target(depends=['foo', 'bar'])
8359
def all(_, __): pass
8460

8561
```
8662

8763
- Make it executable
8864

8965
```console
90-
chmod +x ./buildme
91-
```
92-
93-
- Have Fun
66+
$ ./buildme foo
67+
This is the foo target
9468

95-
```console
96-
$ ./buildme hello
97-
This from test
98-
Namespace()
69+
$ ./buildme bar
9970
================================================================================
100-
[CMD]: echo Hello World
101-
Hello World
71+
[CMD]: echo this is from bar target
72+
this is from bar target
10273
================================================================================
103-
code=0
104-
```
105-
106-
> Order matters
107-
108-
```console
109-
$ ./buildme foo bar
110-
This is the foo target
111-
This is the bar target
11274

113-
$ ./buildme bar foo
114-
This is the bar target
75+
$ ./buildme all
11576
This is the foo target
116-
77+
================================================================================
78+
[CMD]: echo this is from bar target
79+
this is from bar target
80+
================================================================================
11781
```
11882

119-
- Helper functions
120-
121-
`buildme` has currently two helper functions
122-
123-
```python
124-
from buildme import mkdir, touch, rmdir, rm, get_files_in_dir
125-
```
83+
More docs can be found here: [Docs](/docs/buildme.md)
84+
85+
- [Buildme](/docs/buildme.md#buildme-scripts)
86+
- [Target](/docs/buildme.md#target)
87+
- [Creating a target](/docs/buildme.md#creating-a-target)
88+
- [Target depending on another target](/docs/buildme.md#target-depending-on-another-target)
89+
- [Target depending on files](/docs/buildme.md#target-depending-on-files)
90+
- [Targets that creates files](/docs/buildme.md#targets-that-creates-files)
91+
- [CommandRunner](/docs/buildme.md#commandrunner)
92+
- [Receiving Command line args](/docs/buildme.md#receiving-command-line-args)
93+
- [Getting info about the target itself](/docs/buildme.md#getting-info-about-the-target-itself)
94+
- [Dynamic 'creates'](/docs/buildme.md#dynamic-creates)
95+
- [Combining Ideas](/docs/buildme.md#the-above-two-ides-can-combined-to-do-this)
96+
- [Helper functions](/docs/buildme.md#helper-functions)
12697

12798
# Bye....

buildme/cli.py

-6
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ def _get_buildme_file_contents(filepath: str) -> str:
1212
return f.read()
1313

1414

15-
def _create_build_script_code(old_code: str, targets: list[str], usr_opt_var_name: str) -> str:
16-
return old_code + ''.join(f'\n{t}({usr_opt_var_name})' for t in targets)
17-
18-
1915
def _check_target_exists_map(targets: list[str]) -> list[bool]:
2016
return [_check_target_exists(name) for name in targets]
2117

@@ -35,8 +31,6 @@ def main() -> int:
3531
user_arg_parser.add_argument(a.split('=')[0], type=str)
3632

3733
usr_known_args, _ = user_arg_parser.parse_known_args()
38-
# gets the var name as string
39-
usr_known_args_var_name = f'{usr_known_args=}'.split('=')[0] # noqa: F841
4034

4135
target_globals: dict[str, Any] = {}
4236
exec(_get_buildme_file_contents(args.path), target_globals)

buildme/core.py

+95-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import glob
12
import shlex
23
import sys
34
from argparse import Namespace
5+
from dataclasses import dataclass
46
from functools import wraps
7+
from pathlib import Path
58
from subprocess import Popen
69
from typing import Any
710
from typing import Callable
@@ -39,24 +42,78 @@ def set_exit_non_zero(self, val: bool) -> None:
3942
self.exit_non_zero = val
4043

4144

45+
@dataclass
46+
class TargetDepends:
47+
targets: list[str]
48+
files: list[str]
49+
50+
51+
@dataclass
52+
class TargetCreates:
53+
files: list[str] | None
54+
func: Callable[[str], str] | None
55+
56+
4257
class TargetData(NamedTuple):
4358
name: str
44-
depends: list[str] = []
59+
depends: TargetDepends
60+
creates: TargetCreates
4561

4662

63+
TargetCreateType = list[str] | Callable[[str], str]
4764
TargetFuncType = Callable[[Namespace, TargetData], None]
4865
WrapTargetFuncType = Callable[[Namespace], None]
4966

50-
_targets = {}
67+
_targets: dict[str, TargetData] = {}
68+
69+
70+
def _parse_dependencies(depends: list[str]) -> TargetDepends:
71+
ts = []
72+
fs = []
73+
for d in depends:
74+
if d.startswith('f:'):
75+
fs.extend(glob.glob(d[2:]))
76+
else:
77+
ts.append(d)
78+
return TargetDepends(targets=ts, files=fs)
79+
80+
81+
def _parse_creates(creates: TargetCreateType) -> TargetCreates:
82+
if callable(creates):
83+
return TargetCreates(func=creates, files=None)
84+
85+
if isinstance(creates, list):
86+
files = []
87+
for f in creates:
88+
g = glob.glob(f)
89+
files.extend(g if g else [f]) # the might exist in the future
90+
return TargetCreates(files=files, func=None)
91+
92+
93+
def _gen_create_files(func: Callable[[str], str] | None, depends: list[str]) -> list[str]:
94+
# if a function was provided to crate instead of list of file globs then
95+
# the file name are generated by call the provided function on all the dependencies
96+
if func:
97+
return list(map(func, depends))
98+
return []
5199

52100

53-
def target(depends: list[str] = []) -> Callable[[TargetFuncType], WrapTargetFuncType]:
101+
def target(creates: TargetCreateType = [], depends: list[str] = []) -> Callable[[TargetFuncType], WrapTargetFuncType]:
54102
def target_dec(fn: TargetFuncType) -> WrapTargetFuncType:
55-
_targets[fn.__name__] = TargetData(name=fn.__name__, depends=depends)
103+
_targets[fn.__name__] = TargetData(name=fn.__name__,
104+
creates=_parse_creates(creates), depends=_parse_dependencies(depends))
105+
106+
if _targets[fn.__name__].creates.func is not None:
107+
# gen the file names that target might create
108+
_targets[fn.__name__].creates.files = _gen_create_files(
109+
func=_targets[fn.__name__].creates.func,
110+
depends=_targets[fn.__name__].depends.files,
111+
)
56112

57113
@wraps(fn)
58114
def target_wrap(opts: Namespace) -> None:
59115
fn(opts, _targets[fn.__name__])
116+
60117
return target_wrap
61118
return target_dec
62119

@@ -68,14 +125,46 @@ def _get_target_data(name: str) -> TargetData | None:
68125
def _check_target_exists(name: str) -> bool: return name in _targets
69126

70127

128+
def _decide_target_exec(name: str) -> bool:
129+
# target executes if:
130+
# creates.files is empty
131+
# if ones of the fils in create does not exists
132+
# max mtimes (creates) < min mtimes (depends)
133+
t_data = _get_target_data(name)
134+
if not t_data:
135+
return False
136+
137+
if not t_data.creates.files:
138+
return True
139+
140+
n_creates = [Path(i) for i in t_data.creates.files]
141+
n_depends = [Path(i) for i in t_data.depends.files]
142+
143+
# check whether file exists
144+
if not all(i.exists() for i in n_creates):
145+
return True
146+
147+
if max(i.stat().st_mtime for i in n_creates) < min(i.stat().st_mtime for i in n_depends):
148+
return True
149+
150+
return False
151+
152+
71153
def _exec_target(name: str, opts: Namespace, target_globals: dict[str, Any]) -> None:
72154
if name not in _targets:
73155
print(f'unknown target: {name}', file=sys.stderr)
74156
exit(1)
75157

158+
if not _decide_target_exec(name):
159+
return
160+
76161
if callable(fn := target_globals[name]):
77162
t_data = _get_target_data(name)
78163
if t_data:
79-
for d in t_data.depends:
80-
_exec_target(d, opts, target_globals)
164+
for d in t_data.depends.targets:
165+
if d not in _targets:
166+
print(f'unknown target: {d}', file=sys.stderr)
167+
exit(1)
168+
if _decide_target_exec(d):
169+
_exec_target(d, opts, target_globals)
81170
fn(opts)

buildme/hfuncs.py

+5
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ def get_files_in_dir(dir_pattern: str, recurse: bool = False,
2626
return glob.glob(dir_pattern, recursive=recurse, include_hidden=include_hidden)
2727

2828

29+
def get_file_name(path: str) -> str:
30+
"""gets file name from its path"""
31+
return Path(path).name
32+
33+
2934
create_file = touch

0 commit comments

Comments
 (0)