Skip to content

Commit 494ccd9

Browse files
Alexandru Cheltuitorcalexandru2018
andauthored
Migrated from Docopt to ArgParse (#204)
* Ported CLI to argparse, based on OO programming paradigm * Added descriptive comments * Updated usage string * Added logging * Updated exmaples * Removed function based CLI code * Removed docopt dependency * Added usage constant * Added back the PVPN_WAIT environment variable * Addressed Flake8 issues * Examples are now inline with the CLI * Removed unncesessary comment * Cleaned up code and improved readability * Removed dependencies still they are no longer needed * Updated inline command * Allow uppercase protocol with -p * Allign help message * Return missing -p to example Co-authored-by: Alexandru Cheltuitor <31934100+calexandru2018@users.noreply.github.com>
1 parent 965e36a commit 494ccd9

File tree

4 files changed

+175
-76
lines changed

4 files changed

+175
-76
lines changed

protonvpn_cli/cli.py

Lines changed: 129 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,3 @@
1-
"""
2-
A CLI for ProtonVPN.
3-
4-
Usage:
5-
protonvpn init
6-
protonvpn (c | connect) [<servername>] [-p <protocol>]
7-
protonvpn (c | connect) [-f | --fastest] [-p <protocol>]
8-
protonvpn (c | connect) [--cc <code>] [-p <protocol>]
9-
protonvpn (c | connect) [--sc] [-p <protocol>]
10-
protonvpn (c | connect) [--p2p] [-p <protocol>]
11-
protonvpn (c | connect) [--tor] [-p <protocol>]
12-
protonvpn (c | connect) [-r | --random] [-p <protocol>]
13-
protonvpn (r | reconnect)
14-
protonvpn (d | disconnect)
15-
protonvpn (s | status)
16-
protonvpn configure
17-
protonvpn refresh
18-
protonvpn examples
19-
protonvpn (-h | --help)
20-
protonvpn (-v | --version)
21-
22-
Options:
23-
-f, --fastest Select the fastest ProtonVPN server.
24-
-r, --random Select a random ProtonVPN server.
25-
--cc CODE Determine the country for fastest connect.
26-
--sc Connect to the fastest Secure-Core server.
27-
--p2p Connect to the fastest torrent server.
28-
--tor Connect to the fastest Tor server.
29-
-p PROTOCOL Determine the protocol (UDP or TCP).
30-
-h, --help Show this help message.
31-
-v, --version Display version.
32-
33-
Commands:
34-
init Initialize a ProtonVPN profile.
35-
c, connect Connect to a ProtonVPN server.
36-
r, reconnect Reconnect to the last server.
37-
d, disconnect Disconnect the current session.
38-
s, status Show connection status.
39-
configure Change ProtonVPN-CLI configuration.
40-
refresh Refresh OpenVPN configuration and server data.
41-
examples Print some example commands.
42-
43-
Arguments:
44-
<servername> Servername (CH#4, CH-US-1, HK5-Tor).
45-
"""
461
# Standard Libraries
472
import sys
483
import os
@@ -51,8 +6,7 @@
516
import getpass
527
import shutil
538
import time
54-
# External Libraries
55-
from docopt import docopt
9+
import argparse
5610
# protonvpn-cli Functions
5711
from . import connection
5812
from .logger import logger
@@ -63,7 +17,7 @@
6317
)
6418
# Constants
6519
from .constants import (
66-
CONFIG_DIR, CONFIG_FILE, PASSFILE, USER, VERSION, SPLIT_TUNNEL_FILE
20+
CONFIG_DIR, CONFIG_FILE, PASSFILE, USER, VERSION, SPLIT_TUNNEL_FILE, USAGE
6721
)
6822

6923

@@ -88,13 +42,62 @@ def cli():
8842
logger.debug("USER: {0}".format(USER))
8943
logger.debug("CONFIG_DIR: {0}".format(CONFIG_DIR))
9044

91-
args = docopt(__doc__, version="ProtonVPN-CLI v{0}".format(VERSION))
92-
logger.debug("Arguments\n{0}".format(str(args).replace("\n", "")))
45+
ProtonVPNCLI()
46+
47+
48+
class ProtonVPNCLI():
49+
server_features_dict = dict(
50+
p2p=4,
51+
sc=1,
52+
tor=2
53+
)
54+
55+
def __init__(self):
56+
parser = argparse.ArgumentParser(
57+
prog="protonvpn",
58+
add_help=False
59+
)
60+
61+
parser.add_argument("command", nargs="?")
62+
parser.add_argument("-v", "--version", required=False, action="store_true")
63+
parser.add_argument("-h", "--help", required=False, action="store_true")
64+
65+
args = parser.parse_args(sys.argv[1:2])
66+
67+
logger.debug("Main argument\n{0}".format(args))
68+
69+
if args.version:
70+
print("\nProtonVPN CLI v.{}".format(VERSION))
71+
parser.exit(1)
72+
elif not args.command or not hasattr(self, args.command) or args.help:
73+
print(USAGE)
74+
parser.exit(1)
75+
76+
getattr(self, args.command)()
77+
78+
def init(self):
79+
"""CLI command that intialiazes ProtonVPN profile"""
80+
parser = argparse.ArgumentParser(description="Initialize ProtonVPN profile", prog="protonvpn init")
81+
parser.add_argument(
82+
"-i", "--inline", nargs=3, required=False,
83+
help="Inline intialize profile. (username password protocol)", metavar=""
84+
)
85+
86+
args = parser.parse_args(sys.argv[2:])
87+
logger.debug("Sub-arguments\n{0}".format(args))
88+
89+
if args.inline:
90+
print("Please intialize without '-i/--inline' as it is not fully supported yet.")
91+
sys.exit(1)
9392

94-
# Parse arguments
95-
if args.get("init"):
9693
init_cli()
97-
elif args.get("c") or args.get("connect"):
94+
95+
def c(self):
96+
"""Short CLI command for connecting to the VPN"""
97+
self.connect()
98+
99+
def connect(self):
100+
"""Full CLI command for connecting to the VPN"""
98101
check_root()
99102
check_init()
100103

@@ -106,45 +109,97 @@ def cli():
106109
if int(os.environ.get("PVPN_WAIT", 0)) > 0:
107110
wait_for_network(int(os.environ["PVPN_WAIT"]))
108111

109-
protocol = args.get("-p")
110-
if protocol is not None and protocol.lower().strip() in ["tcp", "udp"]:
112+
parser = argparse.ArgumentParser(description="Connect to ProtonVPN", prog="protonvpn c")
113+
group = parser.add_mutually_exclusive_group()
114+
group.add_argument("servername", nargs="?", help="Servername (CH#4, CH-US-1, HK5-Tor).", metavar="")
115+
group.add_argument("-f", "--fastest", help="Connect to the fastest ProtonVPN server.", action="store_true")
116+
group.add_argument("-r", "--random", help="Connect to a random ProtonVPN server.", action="store_true")
117+
group.add_argument("--cc", help="Connect to the specified country code (SE, PT, BR, AR).", metavar="")
118+
group.add_argument("--sc", help="Connect to the fastest Secure-Core server.", action="store_true")
119+
group.add_argument("--p2p", help="Connect to the fastest torrent server.", action="store_true")
120+
group.add_argument("--tor", help="Connect to the fastest Tor server.", action="store_true")
121+
parser.add_argument(
122+
"-p", "--protocol", help="Connect via specified protocol.",
123+
choices=["udp", "tcp"], metavar="", type=str.lower
124+
)
125+
126+
args = parser.parse_args(sys.argv[2:])
127+
logger.debug("Sub-arguments:\n{0}".format(args))
128+
129+
protocol = args.protocol
130+
if protocol and protocol.lower().strip() in ["tcp", "udp"]:
111131
protocol = protocol.lower().strip()
112132

113-
if args.get("--random"):
133+
if args.random:
114134
connection.random_c(protocol)
115-
elif args.get("--fastest"):
135+
elif args.fastest:
116136
connection.fastest(protocol)
117-
elif args.get("<servername>"):
118-
connection.direct(args.get("<servername>"), protocol)
119-
elif args.get("--cc") is not None:
120-
connection.country_f(args.get("--cc"), protocol)
121-
# Features: 1: Secure-Core, 2: Tor, 4: P2P
122-
elif args.get("--p2p"):
123-
connection.feature_f(4, protocol)
124-
elif args.get("--sc"):
125-
connection.feature_f(1, protocol)
126-
elif args.get("--tor"):
127-
connection.feature_f(2, protocol)
137+
elif args.servername:
138+
connection.direct(args.servername, protocol)
139+
elif args.cc:
140+
connection.country_f(args.cc, protocol)
141+
elif args.p2p:
142+
connection.feature_f(self.server_features_dict.get("p2p", None), protocol)
143+
elif args.sc:
144+
connection.feature_f(self.server_features_dict.get("sc", None), protocol)
145+
elif args.tor:
146+
connection.feature_f(self.server_features_dict.get("tor", None), protocol)
128147
else:
129148
connection.dialog()
130-
elif args.get("r") or args.get("reconnect"):
149+
150+
def r(self):
151+
"""Short CLI command to reconnect to the last connected VPN Server"""
152+
self.reconnect()
153+
154+
def reconnect(self):
155+
"""Full CLI command to reconnect to the last connected VPN Server"""
131156
check_root()
132157
check_init()
133158
connection.reconnect()
134-
elif args.get("d") or args.get("disconnect"):
159+
160+
def d(self):
161+
"""Short CLI command to disconnect the VPN if a connection is present"""
162+
self.disconnect()
163+
164+
def disconnect(self):
165+
"""Full CLI command to disconnect the VPN if a connection is present"""
135166
check_root()
136167
check_init()
137168
connection.disconnect()
138-
elif args.get("s") or args.get("status"):
169+
170+
def s(self):
171+
"""Short CLI command to display the current VPN status"""
172+
self.status()
173+
174+
def status(self):
175+
"""Full CLI command to display the current VPN status"""
139176
connection.status()
140-
elif args.get("configure"):
177+
178+
def cf(self):
179+
"""Short CLI command to change single configuration values"""
180+
self.configure()
181+
182+
def configure(self):
183+
"""Full CLI command to change single configuration values"""
141184
check_root()
142185
check_init()
143186
configure_cli()
144-
elif args.get("refresh"):
187+
188+
def rf(self):
189+
"""Short CLI command to refresh server list"""
190+
self.refresh()
191+
192+
def refresh(self):
193+
"""Full CLI command to refresh server list"""
145194
check_init()
146195
pull_server_data(force=True)
147-
elif args.get("examples"):
196+
197+
def ex(self):
198+
"""Short CLI command to display usage examples"""
199+
self.examples()
200+
201+
def examples(self):
202+
"""Full CLI command to display usage examples"""
148203
print_examples()
149204

150205

protonvpn_cli/constants.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,49 @@
1818
OVPN_FILE = os.path.join(CONFIG_DIR, "connect.ovpn")
1919
PASSFILE = os.path.join(CONFIG_DIR, "pvpnpass")
2020
VERSION = "2.2.4"
21+
22+
USAGE = """
23+
ProtonVPN CLI
24+
25+
Usage:
26+
protonvpn init
27+
protonvpn (c | connect) [<servername>] [-p <protocol>]
28+
protonvpn (c | connect) [-f | --fastest] [-p <protocol>]
29+
protonvpn (c | connect) [--cc <code>] [-p <protocol>]
30+
protonvpn (c | connect) [--sc] [-p <protocol>]
31+
protonvpn (c | connect) [--p2p] [-p <protocol>]
32+
protonvpn (c | connect) [--tor] [-p <protocol>]
33+
protonvpn (c | connect) [-r | --random] [-p <protocol>]
34+
protonvpn (r | reconnect)
35+
protonvpn (d | disconnect)
36+
protonvpn (s | status)
37+
protonvpn (cf | configure)
38+
protonvpn (rf | refresh)
39+
protonvpn (ex | examples)
40+
protonvpn (-h | --help)
41+
protonvpn (-v | --version)
42+
43+
Options:
44+
-f, --fastest Select the fastest ProtonVPN server.
45+
-r, --random Select a random ProtonVPN server.
46+
--cc CODE Determine the country for fastest connect.
47+
--sc Connect to the fastest Secure-Core server.
48+
--p2p Connect to the fastest torrent server.
49+
--tor Connect to the fastest Tor server.
50+
-p PROTOCOL Determine the protocol (UDP or TCP).
51+
-h, --help Show this help message.
52+
-v, --version Display version.
53+
54+
Commands:
55+
init Initialize a ProtonVPN profile.
56+
c, connect Connect to a ProtonVPN server.
57+
r, reconnect Reconnect to the last server.
58+
d, disconnect Disconnect the current session.
59+
s, status Show connection status.
60+
cf, configure Change ProtonVPN-CLI configuration.
61+
rf, refresh Refresh OpenVPN configuration and server data.
62+
ex, examples Print some example commands.
63+
64+
Arguments:
65+
<servername> Servername (CH#4, CH-US-1, HK5-Tor).
66+
"""

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# Call with pip install -r requirements.txt
2-
docopt
32
requests
43
pythondialog
54
jinja2

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
},
4040
install_requires=[
4141
"requests",
42-
"docopt",
4342
"pythondialog",
4443
"jinja2",
4544
],

0 commit comments

Comments
 (0)