1#!/usr/local/bin/python3.8 2 3import argparse 4import locale 5import os 6 7from jinja2 import Environment, FileSystemLoader 8 9import pytz 10 11try: 12 import readline # NOQA 13except ImportError: 14 pass 15 16try: 17 import tzlocal 18 _DEFAULT_TIMEZONE = tzlocal.get_localzone().zone 19except ImportError: 20 _DEFAULT_TIMEZONE = 'Europe/Rome' 21 22from pelican import __version__ 23 24locale.setlocale(locale.LC_ALL, '') 25try: 26 _DEFAULT_LANGUAGE = locale.getlocale()[0] 27except ValueError: 28 # Don't fail on macosx: "unknown locale: UTF-8" 29 _DEFAULT_LANGUAGE = None 30if _DEFAULT_LANGUAGE is None: 31 _DEFAULT_LANGUAGE = 'en' 32else: 33 _DEFAULT_LANGUAGE = _DEFAULT_LANGUAGE.split('_')[0] 34 35_TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 36 "templates") 37_jinja_env = Environment( 38 loader=FileSystemLoader(_TEMPLATES_DIR), 39 trim_blocks=True, 40) 41 42 43_GITHUB_PAGES_BRANCHES = { 44 'personal': 'main', 45 'project': 'gh-pages' 46} 47 48CONF = { 49 'pelican': 'pelican', 50 'pelicanopts': '', 51 'basedir': os.curdir, 52 'ftp_host': 'localhost', 53 'ftp_user': 'anonymous', 54 'ftp_target_dir': '/', 55 'ssh_host': 'localhost', 56 'ssh_port': 22, 57 'ssh_user': 'root', 58 'ssh_target_dir': '/var/www', 59 's3_bucket': 'my_s3_bucket', 60 'cloudfiles_username': 'my_rackspace_username', 61 'cloudfiles_api_key': 'my_rackspace_api_key', 62 'cloudfiles_container': 'my_cloudfiles_container', 63 'dropbox_dir': '~/Dropbox/Public/', 64 'github_pages_branch': _GITHUB_PAGES_BRANCHES['project'], 65 'default_pagination': 10, 66 'siteurl': '', 67 'lang': _DEFAULT_LANGUAGE, 68 'timezone': _DEFAULT_TIMEZONE 69} 70 71# url for list of valid timezones 72_TZ_URL = 'https://en.wikipedia.org/wiki/List_of_tz_database_time_zones' 73 74 75# Create a 'marked' default path, to determine if someone has supplied 76# a path on the command-line. 77class _DEFAULT_PATH_TYPE(str): 78 is_default_path = True 79 80 81_DEFAULT_PATH = _DEFAULT_PATH_TYPE(os.curdir) 82 83 84def ask(question, answer=str, default=None, length=None): 85 if answer == str: 86 r = '' 87 while True: 88 if default: 89 r = input('> {} [{}] '.format(question, default)) 90 else: 91 r = input('> {} '.format(question)) 92 93 r = r.strip() 94 95 if len(r) <= 0: 96 if default: 97 r = default 98 break 99 else: 100 print('You must enter something') 101 else: 102 if length and len(r) != length: 103 print('Entry must be {} characters long'.format(length)) 104 else: 105 break 106 107 return r 108 109 elif answer == bool: 110 r = None 111 while True: 112 if default is True: 113 r = input('> {} (Y/n) '.format(question)) 114 elif default is False: 115 r = input('> {} (y/N) '.format(question)) 116 else: 117 r = input('> {} (y/n) '.format(question)) 118 119 r = r.strip().lower() 120 121 if r in ('y', 'yes'): 122 r = True 123 break 124 elif r in ('n', 'no'): 125 r = False 126 break 127 elif not r: 128 r = default 129 break 130 else: 131 print("You must answer 'yes' or 'no'") 132 return r 133 elif answer == int: 134 r = None 135 while True: 136 if default: 137 r = input('> {} [{}] '.format(question, default)) 138 else: 139 r = input('> {} '.format(question)) 140 141 r = r.strip() 142 143 if not r: 144 r = default 145 break 146 147 try: 148 r = int(r) 149 break 150 except ValueError: 151 print('You must enter an integer') 152 return r 153 else: 154 raise NotImplementedError( 155 'Argument `answer` must be str, bool, or integer') 156 157 158def ask_timezone(question, default, tzurl): 159 """Prompt for time zone and validate input""" 160 lower_tz = [tz.lower() for tz in pytz.all_timezones] 161 while True: 162 r = ask(question, str, default) 163 r = r.strip().replace(' ', '_').lower() 164 if r in lower_tz: 165 r = pytz.all_timezones[lower_tz.index(r)] 166 break 167 else: 168 print('Please enter a valid time zone:\n' 169 ' (check [{}])'.format(tzurl)) 170 return r 171 172 173def main(): 174 parser = argparse.ArgumentParser( 175 description="A kickstarter for Pelican", 176 formatter_class=argparse.ArgumentDefaultsHelpFormatter) 177 parser.add_argument('-p', '--path', default=_DEFAULT_PATH, 178 help="The path to generate the blog into") 179 parser.add_argument('-t', '--title', metavar="title", 180 help='Set the title of the website') 181 parser.add_argument('-a', '--author', metavar="author", 182 help='Set the author name of the website') 183 parser.add_argument('-l', '--lang', metavar="lang", 184 help='Set the default web site language') 185 186 args = parser.parse_args() 187 188 print('''Welcome to pelican-quickstart v{v}. 189 190This script will help you create a new Pelican-based website. 191 192Please answer the following questions so this script can generate the files 193needed by Pelican. 194 195 '''.format(v=__version__)) 196 197 project = os.path.join( 198 os.environ.get('VIRTUAL_ENV', os.curdir), '.project') 199 no_path_was_specified = hasattr(args.path, 'is_default_path') 200 if os.path.isfile(project) and no_path_was_specified: 201 CONF['basedir'] = open(project).read().rstrip("\n") 202 print('Using project associated with current virtual environment. ' 203 'Will save to:\n%s\n' % CONF['basedir']) 204 else: 205 CONF['basedir'] = os.path.abspath(os.path.expanduser( 206 ask('Where do you want to create your new web site?', 207 answer=str, default=args.path))) 208 209 CONF['sitename'] = ask('What will be the title of this web site?', 210 answer=str, default=args.title) 211 CONF['author'] = ask('Who will be the author of this web site?', 212 answer=str, default=args.author) 213 CONF['lang'] = ask('What will be the default language of this web site?', 214 str, args.lang or CONF['lang'], 2) 215 216 if ask('Do you want to specify a URL prefix? e.g., https://example.com ', 217 answer=bool, default=True): 218 CONF['siteurl'] = ask('What is your URL prefix? (see ' 219 'above example; no trailing slash)', 220 str, CONF['siteurl']) 221 222 CONF['with_pagination'] = ask('Do you want to enable article pagination?', 223 bool, bool(CONF['default_pagination'])) 224 225 if CONF['with_pagination']: 226 CONF['default_pagination'] = ask('How many articles per page ' 227 'do you want?', 228 int, CONF['default_pagination']) 229 else: 230 CONF['default_pagination'] = False 231 232 CONF['timezone'] = ask_timezone('What is your time zone?', 233 CONF['timezone'], _TZ_URL) 234 235 automation = ask('Do you want to generate a tasks.py/Makefile ' 236 'to automate generation and publishing?', bool, True) 237 238 if automation: 239 if ask('Do you want to upload your website using FTP?', 240 answer=bool, default=False): 241 CONF['ftp'] = True, 242 CONF['ftp_host'] = ask('What is the hostname of your FTP server?', 243 str, CONF['ftp_host']) 244 CONF['ftp_user'] = ask('What is your username on that server?', 245 str, CONF['ftp_user']) 246 CONF['ftp_target_dir'] = ask('Where do you want to put your ' 247 'web site on that server?', 248 str, CONF['ftp_target_dir']) 249 if ask('Do you want to upload your website using SSH?', 250 answer=bool, default=False): 251 CONF['ssh'] = True, 252 CONF['ssh_host'] = ask('What is the hostname of your SSH server?', 253 str, CONF['ssh_host']) 254 CONF['ssh_port'] = ask('What is the port of your SSH server?', 255 int, CONF['ssh_port']) 256 CONF['ssh_user'] = ask('What is your username on that server?', 257 str, CONF['ssh_user']) 258 CONF['ssh_target_dir'] = ask('Where do you want to put your ' 259 'web site on that server?', 260 str, CONF['ssh_target_dir']) 261 262 if ask('Do you want to upload your website using Dropbox?', 263 answer=bool, default=False): 264 CONF['dropbox'] = True, 265 CONF['dropbox_dir'] = ask('Where is your Dropbox directory?', 266 str, CONF['dropbox_dir']) 267 268 if ask('Do you want to upload your website using S3?', 269 answer=bool, default=False): 270 CONF['s3'] = True, 271 CONF['s3_bucket'] = ask('What is the name of your S3 bucket?', 272 str, CONF['s3_bucket']) 273 274 if ask('Do you want to upload your website using ' 275 'Rackspace Cloud Files?', answer=bool, default=False): 276 CONF['cloudfiles'] = True, 277 CONF['cloudfiles_username'] = ask('What is your Rackspace ' 278 'Cloud username?', str, 279 CONF['cloudfiles_username']) 280 CONF['cloudfiles_api_key'] = ask('What is your Rackspace ' 281 'Cloud API key?', str, 282 CONF['cloudfiles_api_key']) 283 CONF['cloudfiles_container'] = ask('What is the name of your ' 284 'Cloud Files container?', 285 str, 286 CONF['cloudfiles_container']) 287 288 if ask('Do you want to upload your website using GitHub Pages?', 289 answer=bool, default=False): 290 CONF['github'] = True, 291 if ask('Is this your personal page (username.github.io)?', 292 answer=bool, default=False): 293 CONF['github_pages_branch'] = \ 294 _GITHUB_PAGES_BRANCHES['personal'] 295 else: 296 CONF['github_pages_branch'] = \ 297 _GITHUB_PAGES_BRANCHES['project'] 298 299 try: 300 os.makedirs(os.path.join(CONF['basedir'], 'content')) 301 except OSError as e: 302 print('Error: {}'.format(e)) 303 304 try: 305 os.makedirs(os.path.join(CONF['basedir'], 'output')) 306 except OSError as e: 307 print('Error: {}'.format(e)) 308 309 try: 310 with open(os.path.join(CONF['basedir'], 'pelicanconf.py'), 311 'w', encoding='utf-8') as fd: 312 conf_python = dict() 313 for key, value in CONF.items(): 314 conf_python[key] = repr(value) 315 316 _template = _jinja_env.get_template('pelicanconf.py.jinja2') 317 fd.write(_template.render(**conf_python)) 318 fd.close() 319 except OSError as e: 320 print('Error: {}'.format(e)) 321 322 try: 323 with open(os.path.join(CONF['basedir'], 'publishconf.py'), 324 'w', encoding='utf-8') as fd: 325 _template = _jinja_env.get_template('publishconf.py.jinja2') 326 fd.write(_template.render(**CONF)) 327 fd.close() 328 except OSError as e: 329 print('Error: {}'.format(e)) 330 331 if automation: 332 try: 333 with open(os.path.join(CONF['basedir'], 'tasks.py'), 334 'w', encoding='utf-8') as fd: 335 _template = _jinja_env.get_template('tasks.py.jinja2') 336 fd.write(_template.render(**CONF)) 337 fd.close() 338 except OSError as e: 339 print('Error: {}'.format(e)) 340 try: 341 with open(os.path.join(CONF['basedir'], 'Makefile'), 342 'w', encoding='utf-8') as fd: 343 py_v = 'python3' 344 _template = _jinja_env.get_template('Makefile.jinja2') 345 fd.write(_template.render(py_v=py_v, **CONF)) 346 fd.close() 347 except OSError as e: 348 print('Error: {}'.format(e)) 349 350 print('Done. Your new project is available at %s' % CONF['basedir']) 351 352 353if __name__ == "__main__": 354 main() 355