Skip to content

Commit e6818c0

Browse files
authored
Merge pull request #3 from git-mastery/rewrite-logic-flow
Rewrite logic flow
2 parents c46ea0c + 75de5b2 commit e6818c0

File tree

67 files changed

+1422
-338
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1422
-338
lines changed

.github/workflows/publish.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ jobs:
3030
python -m pip install --upgrade pip
3131
pip install -r requirements.txt
3232
33+
- name: Run unit tests
34+
run: |
35+
python -m pytest -s -vv
36+
3337
- name: Build binary
3438
run: |
3539
echo "__version__ = \"${GITHUB_REF_NAME}\"" > src/repo_smith/version.py

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,9 @@ For more use cases of `repo-smith`, refer to:
5353

5454
- [Official specification](/specification.md)
5555
- [Unit tests](./tests/)
56+
57+
## FAQ
58+
59+
### Why don't you assign every error to a constant and unit test against the constant?
60+
61+
Suppose the constant was `X`, and we unit tested that the error value was `X`, we would not capture any nuances if the value of `X` had changed by accident.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ PyYAML
44
types-PyYAML
55
build
66
twine
7+
pytest-cov

src/repo_smith/initialize_repo.py

Lines changed: 4 additions & 244 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import re
32
import shutil
43
import tempfile
54
from contextlib import contextmanager
@@ -8,22 +7,10 @@
87
import yaml
98
from git import Repo
109

11-
import repo_smith.steps.add_step
12-
import repo_smith.steps.bash_step
13-
import repo_smith.steps.branch_delete_step
14-
import repo_smith.steps.branch_rename_step
15-
import repo_smith.steps.branch_step
16-
import repo_smith.steps.checkout_step
17-
import repo_smith.steps.commit_step
18-
import repo_smith.steps.fetch_step
19-
import repo_smith.steps.file_step
20-
import repo_smith.steps.merge_step
21-
import repo_smith.steps.remote_step
2210
import repo_smith.steps.tag_step
2311
from repo_smith.clone_from import CloneFrom
2412
from repo_smith.spec import Spec
25-
from repo_smith.steps.step import Step
26-
from repo_smith.steps.step_type import StepType
13+
from repo_smith.steps.dispatcher import Dispatcher
2714

2815
Hook: TypeAlias = Callable[[Repo], None]
2916

@@ -46,7 +33,7 @@ def initialize(self, existing_path: Optional[str] = None) -> Iterator[Repo]:
4633
if self.__spec.clone_from is not None:
4734
repo = Repo.clone_from(self.__spec.clone_from.repo_url, tmp_dir)
4835
else:
49-
repo = Repo.init(tmp_dir)
36+
repo = Repo.init(tmp_dir, initial_branch="main")
5037

5138
for step in self.__spec.steps:
5239
if step.id in self.__pre_hooks:
@@ -118,8 +105,8 @@ def __get_all_ids(self, spec: Spec) -> Set[str]:
118105
def __parse_spec(self, spec: Any) -> Spec:
119106
steps = []
120107

121-
for step in spec.get("initialization", {}).get("steps", []):
122-
steps.append(self.__parse_step(step))
108+
for step in spec.get("initialization", {}).get("steps", []) or []:
109+
steps.append(Dispatcher.dispatch(step))
123110

124111
clone_from = None
125112
if spec.get("initialization", {}).get("clone-from", None) is not None:
@@ -134,233 +121,6 @@ def __parse_spec(self, spec: Any) -> Spec:
134121
clone_from=clone_from,
135122
)
136123

137-
def __parse_step(self, step: Any) -> Step:
138-
if "type" not in step:
139-
raise ValueError('Missing "type" field in step.')
140-
141-
name = step.get("name")
142-
description = step.get("description")
143-
step_type = StepType.from_value(step["type"])
144-
id = step.get("id")
145-
146-
if step_type == StepType.COMMIT:
147-
if "message" not in step:
148-
raise ValueError('Missing "message" field in commit step.')
149-
150-
return repo_smith.steps.commit_step.CommitStep(
151-
name=name,
152-
description=description,
153-
step_type=StepType.COMMIT,
154-
id=id,
155-
empty=step.get("empty", False),
156-
message=step["message"],
157-
)
158-
elif step_type == StepType.ADD:
159-
if "files" not in step:
160-
raise ValueError('Missing "files" field in add step.')
161-
162-
if step["files"] is None or step["files"] == []:
163-
raise ValueError('Empty "files" list in add step.')
164-
165-
return repo_smith.steps.add_step.AddStep(
166-
name=name,
167-
description=description,
168-
step_type=StepType.ADD,
169-
id=id,
170-
files=step["files"],
171-
)
172-
elif step_type == StepType.TAG:
173-
if "tag-name" not in step:
174-
raise ValueError('Missing "tag-name" field in tag step.')
175-
176-
if step["tag-name"] is None or step["tag-name"].strip() == "":
177-
raise ValueError('Empty "tag-name" field in tag step.')
178-
179-
tag_name_regex = "^[0-9a-zA-Z-_.]*$"
180-
if re.search(tag_name_regex, step["tag-name"]) is None:
181-
raise ValueError(
182-
'Field "tag-name" can only contain alphanumeric characters, _, -, .'
183-
)
184-
185-
return repo_smith.steps.tag_step.TagStep(
186-
name=name,
187-
description=description,
188-
step_type=StepType.TAG,
189-
id=id,
190-
tag_name=step["tag-name"],
191-
tag_message=step.get("tag-message"),
192-
)
193-
elif step_type == StepType.BASH:
194-
if "runs" not in step:
195-
raise ValueError('Missing "runs" field in bash step.')
196-
197-
if step["runs"] is None or step["runs"].strip() == "":
198-
raise ValueError('Empty "runs" field in file step.')
199-
200-
return repo_smith.steps.bash_step.BashStep(
201-
name=name,
202-
description=description,
203-
step_type=step_type,
204-
id=id,
205-
body=step["runs"],
206-
)
207-
elif step_type == StepType.BRANCH:
208-
if "branch-name" not in step:
209-
raise ValueError('Missing "branch-name" field in branch step.')
210-
211-
if step["branch-name"] is None or step["branch-name"].strip() == "":
212-
raise ValueError('Empty "branch-name" field in branch step.')
213-
214-
return repo_smith.steps.branch_step.BranchStep(
215-
name=name,
216-
description=description,
217-
step_type=step_type,
218-
id=id,
219-
branch_name=step["branch-name"],
220-
)
221-
elif step_type == StepType.BRANCH_RENAME:
222-
if "branch-name" not in step:
223-
raise ValueError('Missing "branch-name" field in branch-rename step.')
224-
225-
if step["branch-name"] is None or step["branch-name"].strip() == "":
226-
raise ValueError('Empty "branch-name" field in branch-rename step.')
227-
228-
if "new-name" not in step:
229-
raise ValueError('Missing "new-name" field in branch-rename step.')
230-
231-
if step["new-name"] is None or step["new-name"].strip() == "":
232-
raise ValueError('Empty "new-name" field in branch-rename step.')
233-
234-
return repo_smith.steps.branch_rename_step.BranchRenameStep(
235-
name=name,
236-
description=description,
237-
step_type=step_type,
238-
id=id,
239-
original_branch_name=step["branch-name"],
240-
target_branch_name=step["new-name"],
241-
)
242-
elif step_type == StepType.BRANCH_DELETE:
243-
if "branch-name" not in step:
244-
raise ValueError('Missing "branch-name" field in branch step.')
245-
246-
if step["branch-name"] is None or step["branch-name"].strip() == "":
247-
raise ValueError('Empty "branch-name" field in branch step.')
248-
249-
return repo_smith.steps.branch_delete_step.BranchDeleteStep(
250-
name=name,
251-
description=description,
252-
step_type=step_type,
253-
id=id,
254-
branch_name=step["branch-name"],
255-
)
256-
elif step_type == StepType.CHECKOUT:
257-
if step.get("branch-name") is None and step.get("commit-hash") is None:
258-
raise ValueError(
259-
'Provide either "branch-name" or "commit-hash" in checkout step.'
260-
)
261-
262-
return repo_smith.steps.checkout_step.CheckoutStep(
263-
name=name,
264-
description=description,
265-
step_type=step_type,
266-
id=id,
267-
branch_name=step.get("branch-name"),
268-
commit_hash=step.get("commit-hash"),
269-
)
270-
elif step_type == StepType.MERGE:
271-
if step.get("branch-name") is None:
272-
raise ValueError('Provide either "branch-name" in merge step.')
273-
274-
return repo_smith.steps.merge_step.MergeStep(
275-
name=name,
276-
description=description,
277-
step_type=step_type,
278-
id=id,
279-
branch_name=step.get("branch-name"),
280-
no_fast_forward=step.get("no-ff", False),
281-
squash=step.get("squash", False),
282-
)
283-
elif step_type == StepType.REMOTE:
284-
if "remote-url" not in step:
285-
raise ValueError('Missing "remote-url" field in remote step.')
286-
287-
if "remote-name" not in step:
288-
raise ValueError('Missing "remote-name" field in remote step.')
289-
290-
return repo_smith.steps.remote_step.RemoteStep(
291-
name=name,
292-
description=description,
293-
id=id,
294-
step_type=step_type,
295-
remote_name=step["remote-name"],
296-
remote_url=step["remote-url"],
297-
)
298-
elif step_type == StepType.FETCH:
299-
if "remote-name" not in step:
300-
raise ValueError('Missing "remote-name" field in fetch step.')
301-
302-
return repo_smith.steps.fetch_step.FetchStep(
303-
name=name,
304-
description=description,
305-
id=id,
306-
step_type=step_type,
307-
remote_name=step["remote-name"],
308-
)
309-
elif step_type in {
310-
StepType.NEW_FILE,
311-
StepType.APPEND_FILE,
312-
StepType.EDIT_FILE,
313-
StepType.DELETE_FILE,
314-
}:
315-
if "filename" not in step:
316-
raise ValueError('Missing "filename" field in file step.')
317-
318-
if step["filename"] is None or step["filename"].strip() == "":
319-
raise ValueError('Empty "filename" field in file step.')
320-
321-
filename = step["filename"]
322-
contents = step.get("contents", "") or ""
323-
324-
match step_type:
325-
case StepType.NEW_FILE:
326-
return repo_smith.steps.file_step.NewFileStep(
327-
name=name,
328-
description=description,
329-
step_type=step_type,
330-
id=id,
331-
filename=filename,
332-
contents=contents,
333-
)
334-
case StepType.EDIT_FILE:
335-
return repo_smith.steps.file_step.EditFileStep(
336-
name=name,
337-
description=description,
338-
step_type=step_type,
339-
id=id,
340-
filename=filename,
341-
contents=contents,
342-
)
343-
case StepType.DELETE_FILE:
344-
return repo_smith.steps.file_step.DeleteFileStep(
345-
name=name,
346-
description=description,
347-
step_type=step_type,
348-
id=id,
349-
filename=filename,
350-
contents=contents,
351-
)
352-
case StepType.APPEND_FILE:
353-
return repo_smith.steps.file_step.AppendFileStep(
354-
name=name,
355-
description=description,
356-
step_type=step_type,
357-
id=id,
358-
filename=filename,
359-
contents=contents,
360-
)
361-
else:
362-
raise ValueError('Improper "type" field in spec.')
363-
364124

365125
def initialize_repo(spec_path: str) -> RepoInitializer:
366126
if not os.path.isfile(spec_path):

src/repo_smith/steps/add_step.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
1-
from dataclasses import dataclass
2-
from typing import List
1+
from dataclasses import dataclass, field
2+
from typing import Any, List, Optional, Self, Type
33

44
from git import Repo
5-
65
from repo_smith.steps.step import Step
6+
from repo_smith.steps.step_type import StepType
77

88

99
@dataclass
1010
class AddStep(Step):
1111
files: List[str]
1212

13+
step_type: StepType = field(init=False, default=StepType.ADD)
14+
1315
def execute(self, repo: Repo) -> None:
1416
repo.index.add(self.files)
17+
18+
@classmethod
19+
def parse(
20+
cls: Type[Self],
21+
name: Optional[str],
22+
description: Optional[str],
23+
id: Optional[str],
24+
step: Any,
25+
) -> Self:
26+
if "files" not in step:
27+
raise ValueError('Missing "files" field in add step.')
28+
29+
if step["files"] is None or step["files"] == []:
30+
raise ValueError('Empty "files" list in add step.')
31+
32+
return cls(
33+
name=name,
34+
description=description,
35+
id=id,
36+
files=step["files"],
37+
)

src/repo_smith/steps/bash_step.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,38 @@
11
import subprocess
2-
3-
from dataclasses import dataclass
2+
from dataclasses import dataclass, field
3+
from typing import Any, Optional, Self, Type
44

55
from git import Repo
6-
76
from repo_smith.steps.step import Step
7+
from repo_smith.steps.step_type import StepType
88

99

1010
@dataclass
1111
class BashStep(Step):
1212
body: str
1313

14-
def execute(self, repo: Repo) -> None: # type: ignore
14+
step_type: StepType = field(init=False, default=StepType.BASH)
15+
16+
def execute(self, repo: Repo) -> None:
1517
subprocess.check_call(self.body.strip(), shell=True, cwd=repo.working_dir)
18+
19+
@classmethod
20+
def parse(
21+
cls: Type[Self],
22+
name: Optional[str],
23+
description: Optional[str],
24+
id: Optional[str],
25+
step: Any,
26+
) -> Self:
27+
if "runs" not in step:
28+
raise ValueError('Missing "runs" field in bash step.')
29+
30+
if step["runs"] is None or step["runs"].strip() == "":
31+
raise ValueError('Empty "runs" field in bash step.')
32+
33+
return cls(
34+
name=name,
35+
description=description,
36+
id=id,
37+
body=step["runs"],
38+
)

0 commit comments

Comments
 (0)