11import configparser
2+ import logging
3+ import os
24import subprocess
3-
5+ import sys
46from pathlib import Path
7+ from typing import Optional , Union
58
69import click
710import fire
811
9- from ctfcli .cli .challenges import Challenge
10- from ctfcli .cli .config import Config
11- from ctfcli .cli .plugins import Plugins
12- from ctfcli .cli .templates import Templates
13- from ctfcli .cli .pages import Pages
14- from ctfcli .utils .plugins import load_plugins
12+ from ctfcli .cli .challenges import ChallengeCommand
13+ from ctfcli .cli .config import ConfigCommand
14+ from ctfcli .cli .pages import PagesCommand
15+ from ctfcli .cli .plugins import PluginsCommand
16+ from ctfcli .cli .templates import TemplatesCommand
17+ from ctfcli .core .exceptions import ProjectNotInitialized
18+ from ctfcli .core .plugins import load_plugins
1519from ctfcli .utils .git import check_if_dir_is_inside_git_repo
1620
21+ # Init logging
22+ logging .basicConfig (level = os .environ .get ("LOGLEVEL" , "INFO" ).upper ())
23+
24+ log = logging .getLogger ("ctfcli.main" )
25+
26+
27+ class CTFCLI :
28+ @staticmethod
29+ def init (
30+ directory : Optional [Union [str , os .PathLike ]] = None ,
31+ no_git : bool = False ,
32+ no_commit : bool = False ,
33+ ):
34+ log .debug (f"init: (directory={ directory } , no_git={ no_git } , no_commit={ no_commit } )" )
35+ project_path = Path .cwd ()
1736
18- class CTFCLI (object ):
19- def init (self , directory = None , no_config = False , no_git = False ):
20- # Create our event directory if requested and use it as our base directory
37+ # Create our project directory if requested
2138 if directory :
22- path = Path (directory )
23- path .mkdir ()
24- click .secho (f"Created empty directory in { path .absolute ()} " , fg = "green" )
25- else :
26- path = Path ("." )
39+ project_path = Path (directory )
2740
28- # Get variables from user
29- ctf_url = click .prompt (
30- "Please enter CTFd instance URL" , default = "" , show_default = False
31- )
32- ctf_token = click .prompt (
33- "Please enter CTFd Admin Access Token" , default = "" , show_default = False
34- )
35- # Confirm information with user
36- if (
37- click .confirm (f"Do you want to continue with { ctf_url } and { ctf_token } " )
38- is False
39- ):
40- click .echo ("Aborted!" )
41- return
41+ if not project_path .exists ():
42+ project_path .mkdir (parents = True )
43+ click .secho (f"Created empty directory in { project_path .absolute ()} " , fg = "green" )
4244
4345 # Avoid colliding with existing .ctf directory
44- if (path / ".ctf" ).exists ():
46+ if (project_path / ".ctf" ).exists ():
4547 click .secho (".ctf/ folder already exists. Aborting!" , fg = "red" )
4648 return
4749
50+ log .debug (f"project_path: { project_path } " )
51+
4852 # Create .ctf directory
49- (path / ".ctf" ).mkdir ()
53+ (project_path / ".ctf" ).mkdir ()
54+
55+ # Get variables from user
56+ ctf_url = click .prompt ("Please enter CTFd instance URL" , default = "" , show_default = False )
57+
58+ ctf_token = click .prompt ("Please enter CTFd Admin Access Token" , default = "" , show_default = False )
59+
60+ # Confirm information with user
61+ if not click .confirm (f"Do you want to continue with { ctf_url } and { ctf_token } " , default = True ):
62+ click .echo ("Aborted!" )
63+ return
5064
5165 # Create initial .ctf/config file
5266 config = configparser .ConfigParser ()
5367 config ["config" ] = {"url" : ctf_url , "access_token" : ctf_token }
5468 config ["challenges" ] = {}
55- with (path / ".ctf" / "config" ).open (mode = "a+" ) as f :
56- config .write (f )
69+ with (project_path / ".ctf" / "config" ).open (mode = "a+" ) as config_file :
70+ config .write (config_file )
5771
58- # Create a git repo in the event folder
59- if check_if_dir_is_inside_git_repo (dir = path .absolute ()) is True :
60- click .secho ("Already in git repo. Skipping git init." , fg = "yellow" )
61- elif no_git is True :
72+ # if git init is to be skipped we can return
73+ if no_git :
6274 click .secho ("Skipping git init." , fg = "yellow" )
63- else :
64- click .secho (f"Creating git repo in { path .absolute ()} " , fg = "green" )
65- subprocess .call (["git" , "init" , str (path )])
75+ return
76+
77+ # also skip git init if git is already initialized
78+ if check_if_dir_is_inside_git_repo (cwd = project_path ):
79+ click .secho ("Already in a git repo. Skipping git init." , fg = "yellow" )
80+
81+ # is git commit is to be skipped we can return
82+ if no_commit :
83+ click .secho ("Skipping git commit." , fg = "yellow" )
84+ return
85+
86+ subprocess .call (["git" , "add" , ".ctf/config" ], cwd = project_path )
87+ subprocess .call (["git" , "commit" , "-m" , "init ctfcli project" ], cwd = project_path )
88+ return
89+
90+ # Create a git repo in the project folder
91+ click .secho (f"Creating a git repo in { project_path } " , fg = "green" )
92+ subprocess .call (["git" , "init" , str (project_path )])
93+
94+ if no_commit :
95+ click .secho ("Skipping git commit." , fg = "yellow" )
96+ return
97+
98+ subprocess .call (["git" , "add" , ".ctf/config" ], cwd = project_path )
99+ subprocess .call (["git" , "commit" , "-m" , "init ctfcli project" ], cwd = project_path )
66100
67101 def config (self ):
68102 return COMMANDS .get ("config" )
@@ -81,21 +115,38 @@ def templates(self):
81115
82116
83117COMMANDS = {
84- "challenge" : Challenge (),
85- "config" : Config (),
86- "pages" : Pages (),
87- "plugins" : Plugins (),
88- "templates" : Templates (),
118+ "challenge" : ChallengeCommand (),
119+ "config" : ConfigCommand (),
120+ "pages" : PagesCommand (),
121+ "plugins" : PluginsCommand (),
122+ "templates" : TemplatesCommand (),
89123 "cli" : CTFCLI (),
90124}
91125
92126
93127def main ():
94- # load plugins
128+ # Load plugins
95129 load_plugins (COMMANDS )
96130
97131 # Load CLI
98- fire .Fire (CTFCLI )
132+ try :
133+ # if the command returns an int, then we serialize it as none to prevent fire from printing it
134+ # (this does not change the actual return value, so it's still good to use as an exit code)
135+ # everything else is returned as is, so fire can print help messages
136+ ret = fire .Fire (CTFCLI , serialize = lambda r : None if isinstance (r , int ) else r )
137+
138+ if isinstance (ret , int ):
139+ sys .exit (ret )
140+
141+ except ProjectNotInitialized :
142+ if click .confirm (
143+ "Outside of a ctfcli project, would you like to start a new project in this directory?" ,
144+ default = False ,
145+ ):
146+ CTFCLI .init ()
147+ except KeyboardInterrupt :
148+ click .secho ("\n [Ctrl-C] Aborting." , fg = "red" )
149+ sys .exit (2 )
99150
100151
101152if __name__ == "__main__" :
0 commit comments