diff --git a/dsync b/dsync index 186cca0..41d4051 100644 --- a/dsync +++ b/dsync @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -VERSION = "0.2.0" +VERSION = "0.2.1" import re import sys @@ -121,26 +121,25 @@ def _index_by_name(nodes: List[Dict[str, Optional[str]]], name: str) -> int: return -1 -# Accepts either "user@host" or "user@host:/absolute/dir" -_NODE_ARG_RE = re.compile(r"^(?P[^:]+):(?P/.*)$") - +def _is_pathlike(p: str) -> bool: + """Return True if token looks like a remote path we should pass to rsync.""" + return p.startswith(("/", "~", "./", "../")) def _parse_node_token(token: str) -> Tuple[str, Optional[str]]: - """Parse a node token into (name, remote_dir). - - Supported forms: - "user@host" -> ("user@host", None) - "user@host:/path/to/dir" -> ("user@host", "/path/to/dir") - - Args: - token: CLI argument representing a node. - - Returns: - Tuple of (node_name, remote_dir_or_None). """ - m = _NODE_ARG_RE.match(token) - if m: - return m.group("name"), m.group("dir") + Parse a node token into (name, remote_dir). + + Supports: + - "user@host" -> ("user@host", None) + - "user@host:/abs/path" -> ("user@host", "/abs/path") + - "user@host:~/home/path" -> ("user@host", "~/home/path") + - "user@host:./relative" -> ("user@host", "./relative") + - "user@host:../relative" -> ("user@host", "../relative") + """ + if ":" in token: + name, tail = token.split(":", 1) + if name and tail and _is_pathlike(tail): + return name, tail return token, None @@ -479,16 +478,15 @@ node_modules/ def add_nodes(*args: str) -> None: - """Add one or multiple nodes. - - Supported forms: - dsync add-node user@host - dsync add-node user@host:/remote/dir - dsync add-node user@host /remote/dir # alt form for a single node - dsync add-node user@h1 user@h2:/dir user@h3 ... - - Args: - *args: Node tokens, see forms above. + """ + Add one or multiple nodes. + + Supported forms (can be mixed in one command): + dsync add-node user@h1 + dsync add-node user@h1:/path + dsync add-node user@h1 ~/path # as two tokens (pair) + dsync add-node user@h1 ./rel # pair with relative + dsync add-node user@h1 /abs user@h2:~/p2 user@h3 """ if not args: print(colored("No nodes provided", Colors.YELLOW)) @@ -498,35 +496,67 @@ def add_nodes(*args: str) -> None: nodes = _normalize_nodes(config) master_dir = config.get("master_dir", str(project_dir)) - # Special alt-form: exactly two args "user@host /remote/dir" - if len(args) == 2 and args[1].startswith(("/", ".", "~")): - name, rdir = args[0], args[1] - idx = _index_by_name(nodes, name) - if idx >= 0: - print(colored(f"Node {name} already exists", Colors.YELLOW)) - else: - nodes.append({"name": name, "remote_dir": rdir}) - print(colored(f"Added node: {name} ({rdir})", Colors.GREEN)) - config["nodes"] = nodes - save_config(config, project_dir) - return + added_or_updated = False + i = 0 + n = len(args) + + while i < n: + tok = args[i] - added_any = False - for tok in args: + # 1) Full "node:path" form? name, rdir = _parse_node_token(tok) + if rdir is not None: + idx = _index_by_name(nodes, name) + if idx >= 0: + # update existing node dir + nodes[idx]["remote_dir"] = rdir + print(colored(f"Updated node: {name} ({rdir})", Colors.GREEN)) + else: + nodes.append({"name": name, "remote_dir": rdir}) + print(colored(f"Added node: {name} ({rdir})", Colors.GREEN)) + added_or_updated = True + i += 1 + continue + + # 2) Pair form: "node" followed by a path-like token + next_is_path = (i + 1 < n) and _is_pathlike(args[i + 1]) + if not _is_pathlike(tok) and next_is_path: + name = tok + rdir = args[i + 1] + idx = _index_by_name(nodes, name) + if idx >= 0: + nodes[idx]["remote_dir"] = rdir + print(colored(f"Updated node: {name} ({rdir})", Colors.GREEN)) + else: + nodes.append({"name": name, "remote_dir": rdir}) + print(colored(f"Added node: {name} ({rdir})", Colors.GREEN)) + added_or_updated = True + i += 2 + continue + + # 3) Lone path-like token (e.g., "/abs") — ignore with warning + if _is_pathlike(tok): + print(colored(f"Ignored stray path token: {tok}", Colors.YELLOW)) + i += 1 + continue + + # 4) Bare node (no path provided) + name = tok idx = _index_by_name(nodes, name) if idx >= 0: + # already exists, keep its dir; do not spam print(colored(f"Node {name} already exists", Colors.YELLOW)) - continue - nodes.append({"name": name, "remote_dir": rdir}) - print(colored(f"Added node: {name} ({rdir or master_dir})", Colors.GREEN)) - added_any = True + else: + nodes.append({"name": name, "remote_dir": None}) + print(colored(f"Added node: {name} ({master_dir})", Colors.GREEN)) + added_or_updated = True + i += 1 - if added_any: + if added_or_updated: config["nodes"] = nodes save_config(config, project_dir) else: - print(colored("No new nodes added", Colors.YELLOW)) + print(colored("No new nodes added or updated", Colors.YELLOW)) def del_nodes(*nodes_to_remove: str) -> None: