Add mode option for file creation#271
Conversation
|
@dibby-dibby, thanks for this PR but couldn't you achieve the same thing using the command |
|
Sorry for the late response, but I added it to be able to apply specific mode for each file I add in my extension's "define". for example, I want to add 2 bash scripts to the project, and I want them to be already created with execution permission. |
|
Ok, got it, thanks. Could you fix the unit tests and add new ones that test your PR? Ignore the Python 3.5 test for now. |
|
Guys, if we are changing the API, would it be possible to document it in https://github.com/pyscaffold/pyscaffold/blob/master/docs/extensions.rst ? |
|
Good point. It should be documented also we could use this chance to rethink the code a bit. Things like |
So, I think the problem here is that the original API I proposed some time ago is starting to present a genuine symptom of Primitive Obsession with those flags... They are deep intricate dependencies of the core, while a more flexible pattern would be inverting the conditional checks and use some king of strategy pattern. Indeed to choice of primitive data structures (dicts, tuples, int/string flags) was deliberate: I wanted to have something not disruptive from the original implementation in PyScaffold v2, and that was easy to explain to a newcomer. While we could implement a However I could implement a compromise: I mentioned that a strategy pattern could replace the flag. If we consider the single responsibility principle, a strategy pattern can be basically reduced to a callable object. And plain old functions are callable objects. So instead of a flag we could accept a function. In the best spirit of mypy (if recursive types were allowed): from pathlib import Path
from typing import Callable, Dict, Protocol, Text, Tuple, Union
PyScaffoldOpts = dict
FileContent = Union[None, Text]
FileOperation = Callable[[Path, FileContent, PyScaffoldOpts], None]
# ^ The `Stategy` callable.
# It does not return anything but can create a file as side-effect
FileWithOperation = Tuple[FileContent, FileOperation]
FileNode = Union[FileContent, FileWithOperation]
DirTree = Dict[str, Union[FileNode, "DirTree"]]
ProjectStructure = DirTreeParticularly, I would implement NO_OVERWRITE or NO_CREATE as class Create:
...
def __call__(path: Path, content: FileContent, _opts: dict):
if content:
path.write_text(content)
class NoOverwrite:
def __init__(self, wrapped: Optional[FileOperation] = None):
self.wrapped = wrapped or Create()
def __call__(path: Path, content: FileContent, opts: dict):
if opts["force"] or not path.exists():
self.wrapped(path, content, opts)
class SkipOnUpdate: # the NO_CREATE name is confusing because it does create something...
def __init__(self, wrapped: Optional[FileOperation] = None):
self.wrapped = wrapped or Create()
def __call__(path: Path, content: FileContent, opts: dict):
if opts["force"] or not opts["update"]:
self.wrapped(path, content, opts)
# All the classes here could be functions/factories but I like the aesthetics of CamelCase 😝. Alternatively:
def create(path, content, _opts): ...
def no_overwrite(wrapped: FileOperation = create):
def __call__(path: Path, content: FileContent, opts: dict):
if opts["force"] or not path.exists():
wrapped(path, content, opts)
return __call__
def skip_on_update(wrapped: FileOperation = create):
def __call__(path: Path, content: FileContent, opts: dict):
if opts["force"] or not opts["update"]:
wrapped(path, content, opts)
return __call__This changes would simplify the # INSTEAD OF
def modify(
struct: ProjectStructure,
path: Path,
modifier: Callable[[FileContents], FileContents],
update_rule: Literal[None, NO_CREATE, NO_UPDATE]): ...
# WOULD BE
def modify(
struct: ProjectStructure,
path: Path,
modifier: Callable[[FileContents, FileOperation], FileWithOperation]): ...
# The `update_rule` would be absorbed by the modifier callback, which would allow using the factories directly |
This is an redesign attempt to solve the limitations explained in pyscaffold#271. Currently the API is suffering from primitive obsession (a part of it was a deliberate design choice to make the extension mechanism easily understandable even for beginners). The changes introduced here improve a bit, making the design more flexible by replacing `FileOp` integer flags with functions. Most of the primitive obsession is kept due to the same design decisions that motivated the first implementation. The following commits should ensure no errors when running PyScaffold and add documentation. Closes pyscaffold#271.
This is an redesign attempt to solve the limitations explained in pyscaffold#271. Currently the API is suffering from primitive obsession (a part of it was a deliberate design choice to make the extension mechanism easily understandable even for beginners). The changes introduced here improve a bit, making the design more flexible by replacing `FileOp` integer flags with functions. Most of the primitive obsession is kept due to the same design decisions that motivated the first implementation. The following commits should ensure no errors when running PyScaffold and add documentation. Closes pyscaffold#271.
This is an redesign attempt to solve the limitations explained in pyscaffold#271. Currently the API is suffering from primitive obsession (a part of it was a deliberate design choice to make the extension mechanism easily understandable even for beginners). The changes introduced here improve a bit, making the design more flexible by replacing `FileOp` integer flags with functions. Most of the primitive obsession is kept due to the same design decisions that motivated the first implementation. The following commits should ensure no errors when running PyScaffold and add documentation. Closes pyscaffold#271.
This is an redesign attempt to solve the limitations explained in pyscaffold#271. Currently the API is suffering from primitive obsession (a part of it was a deliberate design choice to make the extension mechanism easily understandable even for beginners). The changes introduced here improve a bit, making the design more flexible by replacing `FileOp` integer flags with functions. Most of the primitive obsession is kept due to the same design decisions that motivated the first implementation. The following commits should ensure no errors when running PyScaffold and add documentation. Closes pyscaffold#271.
This is an redesign attempt to solve the limitations explained in pyscaffold#271. Currently the API is suffering from primitive obsession (a part of it was a deliberate design choice to make the extension mechanism easily understandable even for beginners). The changes introduced here improve a bit, making the design more flexible by replacing `FileOp` integer flags with functions. Most of the primitive obsession is kept due to the same design decisions that motivated the first implementation. The following commits should ensure no errors when running PyScaffold and add documentation. Closes pyscaffold#271.
This is an redesign attempt to solve the limitations explained in #271. Currently the API is suffering from primitive obsession (a part of it was a deliberate design choice to make the extension mechanism easily understandable even for beginners). The changes introduced here improve a bit, making the design more flexible by replacing `FileOp` integer flags with functions. Most of the primitive obsession is kept due to the same design decisions that motivated the first implementation. The following commits should ensure no errors when running PyScaffold and add documentation. Closes #271.
|
#294 solves this issue |
Add mode option for file creation