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