dploy 0.1.1__tar.gz → 0.1.3__tar.gz

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.
Files changed (75) hide show
  1. dploy-0.1.3/PKG-INFO +78 -0
  2. dploy-0.1.3/README.md +63 -0
  3. dploy-0.1.3/dploy/__init__.py +35 -0
  4. {dploy-0.1.1 → dploy-0.1.3}/dploy/__main__.py +17 -18
  5. {dploy-0.1.1 → dploy-0.1.3}/dploy/actions.py +98 -28
  6. dploy-0.1.3/dploy/cli.py +114 -0
  7. dploy-0.1.3/dploy/error.py +163 -0
  8. dploy-0.1.3/dploy/ignore.py +67 -0
  9. dploy-0.1.3/dploy/linkcmd.py +81 -0
  10. dploy-0.1.3/dploy/main.py +125 -0
  11. dploy-0.1.3/dploy/stowcmd.py +442 -0
  12. {dploy-0.1.1 → dploy-0.1.3}/dploy/utils.py +123 -98
  13. dploy-0.1.3/dploy/version.py +5 -0
  14. dploy-0.1.3/pyproject.toml +36 -0
  15. dploy-0.1.1/.pylintrc +0 -7
  16. dploy-0.1.1/CHANGELOG.md +0 -116
  17. dploy-0.1.1/MANIFEST.in +0 -12
  18. dploy-0.1.1/PKG-INFO +0 -36
  19. dploy-0.1.1/README.md +0 -15
  20. dploy-0.1.1/conftest.py +0 -0
  21. dploy-0.1.1/dploy/__init__.py +0 -30
  22. dploy-0.1.1/dploy/__init__.pyc +0 -0
  23. dploy-0.1.1/dploy/__pycache__/__init__.cpython-33.pyc +0 -0
  24. dploy-0.1.1/dploy/__pycache__/__init__.cpython-35.pyc +0 -0
  25. dploy-0.1.1/dploy/__pycache__/__main__.cpython-33.pyc +0 -0
  26. dploy-0.1.1/dploy/__pycache__/__main__.cpython-35.pyc +0 -0
  27. dploy-0.1.1/dploy/__pycache__/actions.cpython-33.pyc +0 -0
  28. dploy-0.1.1/dploy/__pycache__/actions.cpython-35.pyc +0 -0
  29. dploy-0.1.1/dploy/__pycache__/cli.cpython-33.pyc +0 -0
  30. dploy-0.1.1/dploy/__pycache__/cli.cpython-35.pyc +0 -0
  31. dploy-0.1.1/dploy/__pycache__/errors.cpython-33.pyc +0 -0
  32. dploy-0.1.1/dploy/__pycache__/errors.cpython-35.pyc +0 -0
  33. dploy-0.1.1/dploy/__pycache__/main.cpython-33.pyc +0 -0
  34. dploy-0.1.1/dploy/__pycache__/main.cpython-35.pyc +0 -0
  35. dploy-0.1.1/dploy/__pycache__/utils.cpython-33.pyc +0 -0
  36. dploy-0.1.1/dploy/__pycache__/utils.cpython-35.pyc +0 -0
  37. dploy-0.1.1/dploy/__pycache__/version.cpython-33.pyc +0 -0
  38. dploy-0.1.1/dploy/__pycache__/version.cpython-35.pyc +0 -0
  39. dploy-0.1.1/dploy/cli.py +0 -99
  40. dploy-0.1.1/dploy/errors.py +0 -86
  41. dploy-0.1.1/dploy/main.py +0 -579
  42. dploy-0.1.1/dploy/version.py +0 -18
  43. dploy-0.1.1/dploy.egg-info/PKG-INFO +0 -36
  44. dploy-0.1.1/dploy.egg-info/SOURCES.txt +0 -63
  45. dploy-0.1.1/dploy.egg-info/dependency_links.txt +0 -1
  46. dploy-0.1.1/dploy.egg-info/entry_points.txt +0 -3
  47. dploy-0.1.1/dploy.egg-info/top_level.txt +0 -1
  48. dploy-0.1.1/invoke.yaml +0 -2
  49. dploy-0.1.1/requirements.txt +0 -11
  50. dploy-0.1.1/setup.cfg +0 -8
  51. dploy-0.1.1/setup.py +0 -49
  52. dploy-0.1.1/tasks.py +0 -78
  53. dploy-0.1.1/tests/__pycache__/conftest.cpython-33-PYTEST.pyc +0 -0
  54. dploy-0.1.1/tests/__pycache__/conftest.cpython-33.pyc +0 -0
  55. dploy-0.1.1/tests/__pycache__/conftest.cpython-35.pyc +0 -0
  56. dploy-0.1.1/tests/__pycache__/test_cli.cpython-33-PYTEST.pyc +0 -0
  57. dploy-0.1.1/tests/__pycache__/test_cli.cpython-35-PYTEST.pyc +0 -0
  58. dploy-0.1.1/tests/__pycache__/test_ignore.cpython-33-PYTEST.pyc +0 -0
  59. dploy-0.1.1/tests/__pycache__/test_ignore.cpython-35-PYTEST.pyc +0 -0
  60. dploy-0.1.1/tests/__pycache__/test_link.cpython-33-PYTEST.pyc +0 -0
  61. dploy-0.1.1/tests/__pycache__/test_link.cpython-35-PYTEST.pyc +0 -0
  62. dploy-0.1.1/tests/__pycache__/test_stow.cpython-33-PYTEST.pyc +0 -0
  63. dploy-0.1.1/tests/__pycache__/test_stow.cpython-35-PYTEST.pyc +0 -0
  64. dploy-0.1.1/tests/__pycache__/test_unstow.cpython-33-PYTEST.pyc +0 -0
  65. dploy-0.1.1/tests/__pycache__/test_unstow.cpython-35-PYTEST.pyc +0 -0
  66. dploy-0.1.1/tests/__pycache__/utils.cpython-33.pyc +0 -0
  67. dploy-0.1.1/tests/__pycache__/utils.cpython-35.pyc +0 -0
  68. dploy-0.1.1/tests/conftest.py +0 -173
  69. dploy-0.1.1/tests/test_cli.py +0 -78
  70. dploy-0.1.1/tests/test_ignore.py +0 -34
  71. dploy-0.1.1/tests/test_link.py +0 -63
  72. dploy-0.1.1/tests/test_stow.py +0 -216
  73. dploy-0.1.1/tests/test_unstow.py +0 -193
  74. dploy-0.1.1/tests/utils.py +0 -100
  75. {dploy-0.1.1 → dploy-0.1.3}/LICENSE.txt +0 -0
dploy-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: dploy
3
+ Version: 0.1.3
4
+ Summary: Provides functionality similar to GNU Stow as a cross platform CLI tool and Python 3 module
5
+ License-Expression: MIT
6
+ License-File: LICENSE.txt
7
+ Author: Ryan Carney
8
+ Author-email: arecarn@gmail.com
9
+ Requires-Python: >=3.10
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Topic :: Utilities
12
+ Project-URL: Homepage, https://github.com/arecarn/dploy
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Dploy
16
+
17
+ Dploy is a tool for creating symbolic links similarly to [GNU
18
+ Stow](https://www.gnu.org/software/stow/). It is provided as a CLI tool and
19
+ Python 3.10+ module and supports Windows, Linux, and OSX.
20
+
21
+ Dploy's command `stow` creates symbolic links to the contents of source
22
+ directories or packages in a specified destination directory. Repeating the
23
+ `stow` command with the same arguments will confirm that the contents of the
24
+ package have been symbolically linked.
25
+
26
+ Dploy's command `unstow` removes symbolic links that resulted from `stow`
27
+ commands. Repeating the `unstow` command with the same arguments will confirm
28
+ that the links to stowed packages have been removed.
29
+
30
+ ## Installation
31
+
32
+ * Latest Release: `pip install dploy`
33
+ * Development Version: `pip install git+https://github.com/arecarn/dploy.git`
34
+
35
+ ## Basic CLI Usage
36
+
37
+ * `dploy stow <source-directory>... <destination-directory>`
38
+ * `dploy unstow <source-directory>... <destination-directory>`
39
+ * `dploy clean <source-directory>... <destination-directory>`
40
+ * `dploy --help`
41
+
42
+ ## Rationale
43
+
44
+ Dploy started out as simple Python script to create symbolic links to my
45
+ dotfiles for Windows, Mac, and Linux. Over time I keep improving and tweaking my
46
+ script to suit my needs, but I was running into a problem. Keeping all the
47
+ files I wanted to link in a config file was becoming a real pain in the neck.
48
+
49
+ I started looking for another solution to solve my problem, and found many
50
+ alternatives but none of them seemed to be a good fit. The solution that seemed
51
+ the most promising was using GNU Stow. It seemed like the most simple elegant
52
+ solution to the problem. The only issue was that it didn't support Windows.
53
+
54
+ Then I thought to myself, why can't I just create my own version of Stow that
55
+ work on Windows, Linux and OSX. So after that my I started morphing
56
+ simple python script into what would become Dploy and learned a lot more about
57
+ python in the process.
58
+
59
+ ## How does it compare with GNU Stow?
60
+
61
+ Below are just a few few major points of comparison between GNU stow and Dploy.
62
+
63
+ * Like GNU Stow Dploy runs in two passes. First by collecting the actions
64
+ required to complete the command and verifying that the command can
65
+ completed without any issues. If no issues are detected then the second
66
+ pass executes these actions are execute to complete the command. Otherwise
67
+ Dploy will exit and indicate why the command can not be completed. This way a
68
+ stow or unstow operation is atomic and never partially done.
69
+
70
+ * Like Stow, Dploy supports tree folding and tree unfolding.
71
+
72
+ * Unlike Stow, Dploy requires an explicit source(s) and a destination
73
+ directory.
74
+
75
+ * Unlike Stow, Dploy does not have any concept of ownership, but will only
76
+ operate on symbolic links and the creation or removal of directories for these
77
+ symbolic links.
78
+
dploy-0.1.3/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Dploy
2
+
3
+ Dploy is a tool for creating symbolic links similarly to [GNU
4
+ Stow](https://www.gnu.org/software/stow/). It is provided as a CLI tool and
5
+ Python 3.10+ module and supports Windows, Linux, and OSX.
6
+
7
+ Dploy's command `stow` creates symbolic links to the contents of source
8
+ directories or packages in a specified destination directory. Repeating the
9
+ `stow` command with the same arguments will confirm that the contents of the
10
+ package have been symbolically linked.
11
+
12
+ Dploy's command `unstow` removes symbolic links that resulted from `stow`
13
+ commands. Repeating the `unstow` command with the same arguments will confirm
14
+ that the links to stowed packages have been removed.
15
+
16
+ ## Installation
17
+
18
+ * Latest Release: `pip install dploy`
19
+ * Development Version: `pip install git+https://github.com/arecarn/dploy.git`
20
+
21
+ ## Basic CLI Usage
22
+
23
+ * `dploy stow <source-directory>... <destination-directory>`
24
+ * `dploy unstow <source-directory>... <destination-directory>`
25
+ * `dploy clean <source-directory>... <destination-directory>`
26
+ * `dploy --help`
27
+
28
+ ## Rationale
29
+
30
+ Dploy started out as simple Python script to create symbolic links to my
31
+ dotfiles for Windows, Mac, and Linux. Over time I keep improving and tweaking my
32
+ script to suit my needs, but I was running into a problem. Keeping all the
33
+ files I wanted to link in a config file was becoming a real pain in the neck.
34
+
35
+ I started looking for another solution to solve my problem, and found many
36
+ alternatives but none of them seemed to be a good fit. The solution that seemed
37
+ the most promising was using GNU Stow. It seemed like the most simple elegant
38
+ solution to the problem. The only issue was that it didn't support Windows.
39
+
40
+ Then I thought to myself, why can't I just create my own version of Stow that
41
+ work on Windows, Linux and OSX. So after that my I started morphing
42
+ simple python script into what would become Dploy and learned a lot more about
43
+ python in the process.
44
+
45
+ ## How does it compare with GNU Stow?
46
+
47
+ Below are just a few few major points of comparison between GNU stow and Dploy.
48
+
49
+ * Like GNU Stow Dploy runs in two passes. First by collecting the actions
50
+ required to complete the command and verifying that the command can
51
+ completed without any issues. If no issues are detected then the second
52
+ pass executes these actions are execute to complete the command. Otherwise
53
+ Dploy will exit and indicate why the command can not be completed. This way a
54
+ stow or unstow operation is atomic and never partially done.
55
+
56
+ * Like Stow, Dploy supports tree folding and tree unfolding.
57
+
58
+ * Unlike Stow, Dploy requires an explicit source(s) and a destination
59
+ directory.
60
+
61
+ * Unlike Stow, Dploy does not have any concept of ownership, but will only
62
+ operate on symbolic links and the creation or removal of directories for these
63
+ symbolic links.
@@ -0,0 +1,35 @@
1
+ """
2
+ dploy script is an attempt at creating a clone of GNU stow that will work on
3
+ Windows as well as *nix
4
+ """
5
+
6
+ from dploy import linkcmd
7
+ from dploy import stowcmd
8
+
9
+
10
+ def stow(sources, dest, is_silent=True, is_dry_run=False, ignore_patterns=None):
11
+ """
12
+ sub command stow
13
+ """
14
+ stowcmd.Stow(sources, dest, is_silent, is_dry_run, ignore_patterns)
15
+
16
+
17
+ def unstow(sources, dest, is_silent=True, is_dry_run=False, ignore_patterns=None):
18
+ """
19
+ sub command unstow
20
+ """
21
+ stowcmd.UnStow(sources, dest, is_silent, is_dry_run, ignore_patterns)
22
+
23
+
24
+ def clean(sources, dest, is_silent=True, is_dry_run=False, ignore_patterns=None):
25
+ """
26
+ sub command clean
27
+ """
28
+ stowcmd.Clean(sources, dest, is_silent, is_dry_run, ignore_patterns)
29
+
30
+
31
+ def link(source, dest, is_silent=True, is_dry_run=False, ignore_patterns=None):
32
+ """
33
+ sub command link
34
+ """
35
+ linkcmd.Link(source, dest, is_silent, is_dry_run, ignore_patterns)
@@ -1,18 +1,17 @@
1
- #!/usr/bin/env python3
2
-
3
- """
4
- The entry point when dploy is called as a module
5
- """
6
-
7
- import sys
8
- assert sys.version_info >= (3, 3), "Requires Python 3.3 or Greater"
9
- import dploy.cli as cli
10
-
11
- def main():
12
- """
13
- main entry point when using dploy from the command line
14
- """
15
- cli.run()
16
-
17
- if __name__ == '__main__':
18
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ The entry point when dploy is called as a module
4
+ """
5
+
6
+ from dploy import cli
7
+
8
+
9
+ def main():
10
+ """
11
+ main entry point when using dploy from the command line
12
+ """
13
+ cli.run()
14
+
15
+
16
+ if __name__ == "__main__":
17
+ main()
@@ -3,25 +3,86 @@ This module contains the actions that are combined to perform dploy's sub
3
3
  commands
4
4
  """
5
5
 
6
- import dploy.utils as utils
6
+ from collections import defaultdict
7
+ from dploy import utils
7
8
 
8
9
 
9
- class AbstractBaseAction():
10
+ class Actions:
11
+ """
12
+ A class that collects and executes action objects
13
+ """
14
+
15
+ def __init__(self, is_silent, is_dry_run):
16
+ self.actions = []
17
+ self.is_silent = is_silent
18
+ self.is_dry_run = is_dry_run
19
+
20
+ def add(self, action):
21
+ """
22
+ Adds an action
23
+ """
24
+ self.actions.append(action)
25
+
26
+ def execute(self):
27
+ """
28
+ Prints and executes actions
29
+ """
30
+ for action in self.actions:
31
+ if not self.is_silent:
32
+ print(action)
33
+ if not self.is_dry_run:
34
+ action.execute()
35
+
36
+ def get_unlink_actions(self):
37
+ """
38
+ get the current Unlink() actions from the self.actions
39
+ """
40
+ return [a for a in self.actions if isinstance(a, UnLink)]
41
+
42
+ def get_unlink_target_parents(self):
43
+ """
44
+ Get list of the parents for the current Unlink() actions from
45
+ self.actions
46
+ """
47
+ unlink_actions = self.get_unlink_actions()
48
+ # sort for deterministic output
49
+ return sorted(set([a.target.parent for a in unlink_actions]))
50
+
51
+ def get_unlink_targets(self):
52
+ """
53
+ Get list of the targets for the current Unlink() actions from
54
+ self.actions
55
+ """
56
+ unlink_actions = self.get_unlink_actions()
57
+ return [a.target for a in unlink_actions]
58
+
59
+ def get_duplicates(self):
60
+ """
61
+ return a tuple containing tuples with the following structure
62
+ (link destination, [indices of duplicates])
63
+ """
64
+ tally = defaultdict(list)
65
+ for index, action in enumerate(self.actions):
66
+ if isinstance(action, SymbolicLink):
67
+ tally[action.dest].append(index)
68
+ # sort for deterministic output
69
+ return sorted([indices for _, indices in tally.items() if len(indices) > 1])
70
+
71
+
72
+ class AbstractBaseAction:
10
73
  # pylint: disable=too-few-public-methods
11
74
  """
12
75
  An abstract base class that define the interface for actions
13
76
  """
14
- def __init__(self):
15
- pass
16
77
 
17
- def _logic(self):
78
+ def __init__(self):
18
79
  pass
19
80
 
20
81
  def execute(self):
21
82
  """
22
- function that executes _logic() of each derived action
83
+ function that executes the logic of each concrete action
23
84
  """
24
- self._logic()
85
+ pass
25
86
 
26
87
 
27
88
  class SymbolicLink(AbstractBaseAction):
@@ -29,6 +90,7 @@ class SymbolicLink(AbstractBaseAction):
29
90
  """
30
91
  Action to create a symbolic link relative to the source of the link
31
92
  """
93
+
32
94
  def __init__(self, subcmd, source, dest):
33
95
  super().__init__()
34
96
  self.source = source
@@ -36,12 +98,13 @@ class SymbolicLink(AbstractBaseAction):
36
98
  self.subcmd = subcmd
37
99
  self.dest = dest
38
100
 
39
- def _logic(self):
101
+ def execute(self):
40
102
  self.dest.symlink_to(self.source_relative)
41
103
 
42
104
  def __repr__(self):
43
105
  return "dploy {subcmd}: link {dest} => {source}".format(
44
- subcmd=self.subcmd, dest=self.dest, source=self.source_relative)
106
+ subcmd=self.subcmd, dest=self.dest, source=self.source_relative
107
+ )
45
108
 
46
109
 
47
110
  class AlreadyLinked(AbstractBaseAction):
@@ -49,6 +112,7 @@ class AlreadyLinked(AbstractBaseAction):
49
112
  """
50
113
  Action to used to print an already linked message
51
114
  """
115
+
52
116
  def __init__(self, subcmd, source, dest):
53
117
  super().__init__()
54
118
  self.source = source
@@ -56,14 +120,13 @@ class AlreadyLinked(AbstractBaseAction):
56
120
  self.dest = dest
57
121
  self.subcmd = subcmd
58
122
 
59
- def _logic(self):
123
+ def execute(self):
60
124
  pass
61
125
 
62
126
  def __repr__(self):
63
127
  return "dploy {subcmd}: already linked {dest} => {source}".format(
64
- subcmd=self.subcmd,
65
- source=self.source_relative,
66
- dest=self.dest)
128
+ subcmd=self.subcmd, source=self.source_relative, dest=self.dest
129
+ )
67
130
 
68
131
 
69
132
  class AlreadyUnlinked(AbstractBaseAction):
@@ -71,6 +134,7 @@ class AlreadyUnlinked(AbstractBaseAction):
71
134
  """
72
135
  Action to used to print an already unlinked message
73
136
  """
137
+
74
138
  def __init__(self, subcmd, source, dest):
75
139
  super().__init__()
76
140
  self.source = source
@@ -78,14 +142,13 @@ class AlreadyUnlinked(AbstractBaseAction):
78
142
  self.dest = dest
79
143
  self.subcmd = subcmd
80
144
 
81
- def _logic(self):
145
+ def execute(self):
82
146
  pass
83
147
 
84
148
  def __repr__(self):
85
149
  return "dploy {subcmd}: already unlinked {dest} => {source}".format(
86
- subcmd=self.subcmd,
87
- source=self.source_relative,
88
- dest=self.dest)
150
+ subcmd=self.subcmd, source=self.source_relative, dest=self.dest
151
+ )
89
152
 
90
153
 
91
154
  class UnLink(AbstractBaseAction):
@@ -93,53 +156,60 @@ class UnLink(AbstractBaseAction):
93
156
  """
94
157
  Action to unlink a symbolic link
95
158
  """
159
+
96
160
  def __init__(self, subcmd, target):
97
161
  super().__init__()
98
162
  self.target = target
99
163
  self.subcmd = subcmd
100
164
 
101
- def _logic(self):
165
+ def execute(self):
102
166
  if not self.target.is_symlink():
103
- #pylint: disable=line-too-long
104
- raise RuntimeError('dploy detected and aborted an attempt to unlink a non-symlink this is a bug and should be reported')
167
+ # pylint: disable=line-too-long
168
+ raise RuntimeError(
169
+ "dploy detected and aborted an attempt to unlink a non-symlink {target} this is a bug and should be reported".format(
170
+ target=self.target
171
+ )
172
+ )
105
173
  self.target.unlink()
106
174
 
107
175
  def __repr__(self):
108
- source_relative = utils.get_relative_path(self.target.resolve(), self.target.parent)
109
176
  return "dploy {subcmd}: unlink {target} => {source}".format(
110
- subcmd=self.subcmd,
111
- target=self.target,
112
- source=source_relative)
177
+ subcmd=self.subcmd, target=self.target, source=utils.readlink(self.target)
178
+ )
179
+
113
180
 
114
181
  class MakeDirectory(AbstractBaseAction):
115
182
  # pylint: disable=too-few-public-methods
116
183
  """
117
184
  Action to create a directory
118
185
  """
186
+
119
187
  def __init__(self, subcmd, target):
120
188
  super().__init__()
121
189
  self.target = target
122
190
  self.subcmd = subcmd
123
191
 
124
- def _logic(self):
192
+ def execute(self):
125
193
  self.target.mkdir()
126
194
 
127
195
  def __repr__(self):
128
196
  return "dploy {subcmd}: make directory {target}".format(
129
- target=self.target,
130
- subcmd=self.subcmd)
197
+ target=self.target, subcmd=self.subcmd
198
+ )
199
+
131
200
 
132
201
  class RemoveDirectory(AbstractBaseAction):
133
202
  # pylint: disable=too-few-public-methods
134
203
  """
135
204
  Action to remove a directory
136
205
  """
206
+
137
207
  def __init__(self, subcmd, target):
138
208
  super().__init__()
139
209
  self.target = target
140
210
  self.subcmd = subcmd
141
211
 
142
- def _logic(self):
212
+ def execute(self):
143
213
  self.target.rmdir()
144
214
 
145
215
  def __repr__(self):
@@ -0,0 +1,114 @@
1
+ """
2
+ The command line interface
3
+ """
4
+
5
+ import sys
6
+ import argparse
7
+ from dploy import linkcmd
8
+ from dploy import stowcmd
9
+ from dploy import version
10
+ from dploy.error import DployError
11
+
12
+
13
+ def add_ignore_argument(parser):
14
+ """
15
+ adds the ignore argument to a subcmd parser
16
+ """
17
+ parser.add_argument(
18
+ "--ignore",
19
+ dest="ignore_patterns",
20
+ action="append",
21
+ default=None,
22
+ help="glob pattern used to ignore directories",
23
+ )
24
+
25
+
26
+ def create_parser():
27
+ """
28
+ create the CLI argument parser
29
+ """
30
+ parser = argparse.ArgumentParser(prog="dploy")
31
+
32
+ parser.add_argument(
33
+ "--version",
34
+ action="version",
35
+ version="%(prog)s {version}".format(version=version.__version__),
36
+ )
37
+ parser.add_argument(
38
+ "--silent", dest="is_silent", action="store_true", help="suppress all output"
39
+ )
40
+ parser.add_argument(
41
+ "--dry-run",
42
+ dest="is_dry_run",
43
+ action="store_true",
44
+ help="show what would be done without doing it",
45
+ )
46
+
47
+ sub_parsers = parser.add_subparsers(dest="subcmd")
48
+
49
+ stow_parser = sub_parsers.add_parser("stow")
50
+ stow_parser.add_argument("source", nargs="+", help="source directory to stow")
51
+ stow_parser.add_argument("dest", help="destination path to stow into")
52
+ add_ignore_argument(stow_parser)
53
+
54
+ unstow_parser = sub_parsers.add_parser("unstow")
55
+ unstow_parser.add_argument(
56
+ "source", nargs="+", help="source directory to unstow from"
57
+ )
58
+ unstow_parser.add_argument("dest", help="destination path to unstow")
59
+ add_ignore_argument(unstow_parser)
60
+
61
+ clean_parser = sub_parsers.add_parser("clean")
62
+ clean_parser.add_argument(
63
+ "source", nargs="+", help="source directory to clean from"
64
+ )
65
+ clean_parser.add_argument("dest", help="destination path to clean")
66
+ add_ignore_argument(clean_parser)
67
+
68
+ link_parser = sub_parsers.add_parser("link")
69
+ link_parser.add_argument("source", help="source file or directory to link")
70
+ link_parser.add_argument("dest", help="destination path to link")
71
+ add_ignore_argument(link_parser)
72
+ return parser
73
+
74
+
75
+ def run(arguments=None):
76
+ """
77
+ interpret the parser arguments and execute the corresponding commands
78
+ """
79
+
80
+ subcmd_map = {
81
+ "stow": stowcmd.Stow,
82
+ "unstow": stowcmd.UnStow,
83
+ "clean": stowcmd.Clean,
84
+ "link": linkcmd.Link,
85
+ }
86
+
87
+ try:
88
+ parser = create_parser()
89
+
90
+ if arguments is None:
91
+ args = parser.parse_args()
92
+ else:
93
+ args = parser.parse_args(arguments)
94
+
95
+ if args.subcmd in subcmd_map:
96
+ subcmd = subcmd_map[args.subcmd]
97
+ else:
98
+ parser.print_help()
99
+ sys.exit(0)
100
+
101
+ try:
102
+ subcmd(
103
+ args.source,
104
+ args.dest,
105
+ is_silent=args.is_silent,
106
+ is_dry_run=args.is_dry_run,
107
+ ignore_patterns=args.ignore_patterns,
108
+ )
109
+ except DployError:
110
+ sys.exit(1)
111
+
112
+ except (KeyboardInterrupt) as error:
113
+ print(error, file=sys.stderr)
114
+ sys.exit(130)