diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0ab1e12
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*.{md,py,txt}]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/.gitignore b/.gitignore
index b6e4761..38a24a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,7 @@
__pycache__/
*.py[cod]
*$py.class
-
+.idea/
# C extensions
*.so
@@ -10,7 +10,7 @@ __pycache__/
.Python
build/
develop-eggs/
-dist/
+/dist/
downloads/
eggs/
.eggs/
diff --git a/CHANGELOGS.md b/CHANGELOGS.md
new file mode 100644
index 0000000..c64e7f4
--- /dev/null
+++ b/CHANGELOGS.md
@@ -0,0 +1,36 @@
+# CHANGELOGS
+
+## 1.1.0
+
+* Bulk refactors and code documentation
+* New commands for `static.py` files
+ * `--serve` for livewatch changes
+ * `--watch` for watch specific files and folders.
+ * `--port` especiffy serve port
+
+## 1.0.0
+
+* Back to namespace method for write templates names
+ * Example: `html5up/massively` instead of just `massively`
+* Template assets are not included in the library to minimize the size of the library, they were moved to
+ a [separate repository](https://github.com/jamstackpy/jamstack-templates).
+
+## 0.1.0
+
+* Improve **Plain** template
+* Add new templates (massively, phantom)
+* When creating a project, instead of writing the template like this: **html5up/massively**, you can use the name
+ directly, i.e. **massively**
+* The assets of each template are now included in the library
+
+## 0.0.4
+
+* Add README.md info
+
+## 0.0.3
+
+* Add plain template
+
+## 0.0.1
+
+* Initial release
diff --git a/README.md b/README.md
index e38b842..8d4ee1d 100644
--- a/README.md
+++ b/README.md
@@ -1,36 +1,75 @@
-# jamstack
+
+

+

-Install
+Also known as Jamstackpy, is a tool that allows you to create static websites using the power of **Python** hand in hand
+with the [Flask](https://github.com/pallets/flask) library. Its operation is based on templates which are rendered with
+the powerful Jinja engine generating your website with all its dependencies.
+
+## Installation
```bash
python -m pip install jamstack
```
-Create basic project
+## Create basic project
```bash
jamstack plain
```
-`jamstack plain myproject`
+## Templates
+
+Jamstack has templates available courtesy of [html5up](https://html5up.net).
+
+| Template | Command | Tutorial |
+|--------------------------------------------|-------------------|--------------------------------------------------------------------------|
+| [Massively](https://html5up.net/massively) | html5up/massively | |
+| [Phantom](https://html5up.net/phantom) | html5up/phantom | [**HERE**](https://github.com/jamstackpy/jamstack/wiki/Phantom-template) |
+The syntax is as follows:
+
+```bash
+jamstack t
+```
-with available templates
+Use the `--existing` flag if you want the project to be created in an existing folder
```bash
-jamstack t
+jamstack t html5up/massively myproject --existing
```
-The only available template is 'html5up/massively' (`jamstack t html5up/massively myproject`)
+By default, projects based in templates are created without the assets (stylesheets, images, etc...) to download them,
+you must pass
+the `--jamdo` option to the `static.py` file of the respective project.
-Use the --existing flag if you want the project to be created in an existing folder (`jamstack t html5up/massively myproject --existing`)
+## Build
-Run project:
+To build the site run the file `static.py`.
```bash
python static.py
```
-Use the --server flag if you want to start livewatch
+Your site will be generated in the **dist/** folder.
+
+## Other project command-line options
+
+| Argument | Description |
+|-----------|---------------------------------------------------------------------------|
+| `--serve` | Optional. Start project livewatch (autoreload when files change). |
+| `--watch` | Optional. Specify files and folders to watch. Must be separated by comma. |
+| `--port` | Optional. Specify server port. |
+
+## Sites using jamstack
+
+- [DeliciousPy Telegram Channel](https://deliciouspy.github.io/)
+- [The Flask Community Work Group](https://flaskcwg.github.io/)
+- [Abdur-Rahmaan Janhangeer](https://compileralchemy.github.io/)
+
+# TODO
+
+- [ ] Replace `python static.py --serve/--jamdo` by something like `jamstack --serve/--jamdo`
+- [ ] Remove Flask as Main requirement and just use Jinja
diff --git a/jamstack/__init__.py b/jamstack/__init__.py
index a1d58d1..9260b35 100644
--- a/jamstack/__init__.py
+++ b/jamstack/__init__.py
@@ -1,2 +1,2 @@
-version_info = (0, 0, 5)
-__version__ = ".".join([str(v) for v in version_info])
+version_info = (1, 1, 0)
+__version__ = ".".join(map(str, version_info))
diff --git a/jamstack/__main__.py b/jamstack/__main__.py
index d9dd81d..1754516 100644
--- a/jamstack/__main__.py
+++ b/jamstack/__main__.py
@@ -1,58 +1,54 @@
import os
from pathlib import Path
-from jamstack.api.file import trycopytree
import click
+from jamstack.api.file import trycopytree
+
package_folder = Path(__file__).parent.absolute()
sites_path = os.path.join(package_folder, 'sites')
-@click.group(help="")
+
+@click.group(help="Jamstack command line interface")
def cli():
pass
-@click.command(help="")
+@click.command(help="Create an empty, plain repository")
@click.argument('project_name')
@click.option('--existing/--not-existing', default=False)
def plain(project_name, existing):
path = '.'
- dirs_exist_ok = False
- if existing is True:
- dirs_exist_ok = True
trycopytree(
os.path.join(sites_path, 'plain'),
os.path.join(path, project_name),
- dirs_exist_ok=dirs_exist_ok
- )
+ existing
+ )
-@click.command(help="")
+@click.command(help="Create a repository from a template")
@click.argument('template')
@click.argument('project_name')
@click.option('--existing/--not-existing', default=False)
def t(template, project_name, existing):
path = '.'
+ namespace, project = template.split('/')
- namespace = template.split('/')[0]
- project = template.split('/')[1]
-
- dirs_exist_ok = False
- if existing is True:
- dirs_exist_ok = True
trycopytree(
os.path.join(sites_path, namespace, project),
os.path.join(path, project_name),
- dirs_exist_ok=dirs_exist_ok
- )
+ existing
+ )
cli.add_command(plain)
cli.add_command(t)
+
def main():
cli()
+
if __name__ == "__main__":
cli()
diff --git a/jamstack/api/file.py b/jamstack/api/file.py
index dad294c..86d4c8b 100644
--- a/jamstack/api/file.py
+++ b/jamstack/api/file.py
@@ -1,54 +1,33 @@
+import logging
import os
import shutil
import uuid
-# from werkzeug.utils import secure_filename
-
-def trycopytree(source, dest, verbose=False, dirs_exist_ok=False):
+def trycopytree(source, dest, dirs_exist_ok):
"""
- Recursive copy of folder
+ Copy a file or directory from source to dest.
Parameters
----------
source: str
- source folder path
+ source file or directory path
dest: str
- destination folder path
-
+ destination file or directory path
+ dirs_exist_ok:
+ especifies if the project folder already exist
Returns
-------
None
"""
try:
shutil.copytree(source, dest, dirs_exist_ok=dirs_exist_ok)
- if verbose:
- print("done copying {} to {}".format(source, dest))
- except Exception as e:
- print(e)
-
-
-def trycopy(source, dest, verbose=False):
- """
- Non-ecursive copy of folder
-
- Parameters
- ----------
- source: str
- source folder path
- dest: str
- destination folder path
-
- Returns
- -------
- None
- """
- try:
- shutil.copy(source, dest)
- if verbose:
- print("done copying {} to {}".format(source, dest))
+ os.mkdir(os.path.join(dest, 'dist')) # Maybe place in static.py?
+ print('Project created successfully! :)')
+ except FileExistsError:
+ print('Project folder already exist! Use --existing if you want to override it.')
except Exception as e:
- print(e)
+ logging.exception(e)
def trymkdir(path, verbose=False):
@@ -59,6 +38,8 @@ def trymkdir(path, verbose=False):
----------
path: str
path with folder already in
+ verbose: bool, optional
+ If True, print debug information. Default is False.
Returns
-------
@@ -69,7 +50,7 @@ def trymkdir(path, verbose=False):
if verbose:
print("created dir at", path)
except Exception as e:
- print(e)
+ logging.exception(e)
def trymkfile(path, content, verbose=False):
@@ -82,20 +63,22 @@ def trymkfile(path, content, verbose=False):
path to create file with filename included
content: str
file content
+ verbose: bool, optional
+ If True, print debug information. Default is False.
Returns
-------
None
"""
try:
- with open(path, "w+") as f:
+ with open(path, "x") as f:
f.write(content)
if verbose:
print("file created at {}".format(path))
print("with content:")
print(content)
except Exception as e:
- print(e)
+ logging.exception(e)
def absdiroffile(filepath):
@@ -117,18 +100,60 @@ def absdiroffile(filepath):
def get_folders(path):
- dirs = [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))]
+ """
+ Get a list of directories in the given path.
+
+ Parameters
+ ----------
+ path: str
+ Path to search for directories.
+
+ Returns
+ -------
+ list
+ List of directories in the given path.
+ """
+ dirs = [d for d in os.listdir(
+ path) if os.path.isdir(os.path.join(path, d))]
return dirs
def unique_filename(fname):
+ """
+ Generate a unique filename by prepending a UUID to the given filename.
+
+ Parameters
+ ----------
+ fname: str
+ Original filename.
+
+ Returns
+ -------
+ str
+ Unique filename with prepended UUID.
+ """
prepended = str(uuid.uuid4()).replace("-", "_")[:10]
return "{}_{}".format(prepended, fname)
def delete_file(path):
- os.remove(path)
+ """
+ Delete the file at the given path.
+
+ Parameters
+ ----------
+ path: str
+ Path of the file to delete.
+ Returns
+ -------
+ None
+ """
+ try:
+ os.remove(path)
+ logging.info("File deleted: {}".format(path))
+ except Exception as e:
+ logging.exception(e)
# def unique_sec_filename(filename):
# return unique_filename(secure_filename(filename))
diff --git a/jamstack/api/template.py b/jamstack/api/template.py
index 603368c..1f19945 100644
--- a/jamstack/api/template.py
+++ b/jamstack/api/template.py
@@ -1,41 +1,70 @@
-import uuid
-import datetime
import builtins
-import string
import copy
+import datetime
+import json
+import string
+
+from jinja2 import Environment, FileSystemLoader
-from jinja2 import Environment
-from jinja2 import FileSystemLoader
+def load_json(file):
+ with open(file, 'r') as f:
+ return json.load(f)
-def base_context():
- defaults = [_ for _ in dir(builtins)
- if _[0] in string.ascii_lowercase and
- _ not in ['copyright', 'credits']
- ]
- attrs = [getattr(builtins, _) for _ in defaults if _ not in ['copyright', 'credits']]
- builtins_dict = dict(zip(defaults, attrs))
+def get_builtins_context() -> dict:
+ """
+ Get a dictionary of built-in functions and variables.
+
+ Returns
+ -------
+ dict
+ Dictionary of built-in functions and variables.
+ """
+ builtins_dict = {}
+ for name in dir(builtins):
+ if name[0] in string.ascii_lowercase and name not in ['copyright', 'credits']:
+ value = getattr(builtins, name)
+ builtins_dict[name] = value
return copy.deepcopy(builtins_dict)
-def generate(file_in_templates, out_path, template_dir='templates', assets_path_append='', **kwargs):
+def generate(template_name: str, output_path: str, template_dir: str = 'templates', assets_path_append: str = '',
+ context: dict = None) -> None:
"""
- Generates necessary file(s)
- :param file_in_templates: template to work with
- :param out_path: output path to save the generated file to
- :param template_dir: templates directory
- :param assets_path_append:
- :param kwargs: variables
- :return: None
+ Generates necessary file(s) from a Jinja2 template.
+
+ Parameters
+ ----------
+ template_name: str
+ Template file to work with.
+ output_path: str
+ Output path to save the generated file to.
+ template_dir: str, optional
+ Templates directory. Default is 'templates'.
+ assets_path_append: str, optional
+ Path to append to assets. Default is ''.
+ context: dict, optional
+ Variables to pass to the template. Additional context variables can be provided using this parameter.
+
+ Returns
+ -------
+ None
"""
+ if context is None:
+ context = {}
file_loader = FileSystemLoader(template_dir)
env = Environment(loader=file_loader)
- template = env.get_template(file_in_templates)
+ template = env.get_template(template_name)
+
+ context.update({
+ 'year': datetime.datetime.now().year,
+ 'assets_path_append': assets_path_append,
+ **get_builtins_context(),
+ })
- build_id = str(uuid.uuid4()) # to be used
+ output = template.render(context)
- output = template.render(kwargs, year=datetime.datetime.now().year,
- build_id=build_id, assets_path_append=assets_path_append)
- print(output, file=open(out_path, 'w+', encoding="utf8"))
\ No newline at end of file
+ with open(output_path, 'w+', encoding="utf8") as f:
+ f.write(output)
diff --git a/jamstack/jamdo/jamdo.py b/jamstack/jamdo/jamdo.py
new file mode 100644
index 0000000..4ca1d2b
--- /dev/null
+++ b/jamstack/jamdo/jamdo.py
@@ -0,0 +1,54 @@
+import os
+import urllib.request
+
+import requests
+from tqdm import tqdm
+
+TEMPLATES_URL = "https://api.github.com/repos/jamstackpy/jamstack-templates/git/trees/main?recursive=1"
+
+
+def mkdirs(path):
+ if not os.path.isdir(path):
+ os.makedirs(path)
+
+
+def get_raw_url(file_path, url):
+ raw_url = url.replace(
+ 'https://api.github.com/repos/',
+ 'https://raw.githubusercontent.com/')
+ raw_url = raw_url.split('/git/blobs/')[0]
+ raw_url = raw_url + '/master/' + file_path
+ return raw_url
+
+
+def get_download_links(template):
+ api = requests.get(TEMPLATES_URL).json()
+ files = api['tree']
+ output = []
+ location = {}
+ for i, file in enumerate(files):
+ if template in file['path']:
+ if file['type'] == 'blob':
+ output.append([file['path'], get_raw_url(file['path'], file['url'])])
+ else:
+ location[file['path']] = i
+ return output, location
+
+
+def download(template, target_folder='*', recursive=True):
+ data = get_download_links(template)
+ files = data[0]
+ location = data[1]
+
+ if target_folder == '*':
+ start = 0
+ else:
+ start = location[target_folder]
+
+ with tqdm(total=len(files), desc="Downloading assets...") as pbar:
+ for i in files[start:]:
+ ndir = i[0].replace('templates/' + template, 'dist/assets/')
+ if recursive or ndir.split(target_folder)[1].count('/') <= 1:
+ mkdirs('.' + '/' + os.path.dirname(ndir))
+ urllib.request.urlretrieve(i[1], '.' + '/' + ndir)
+ pbar.update(1)
diff --git a/jamstack/sites/html5up/massively/info.json b/jamstack/sites/html5up/massively/info.json
index 23720a6..164df94 100644
--- a/jamstack/sites/html5up/massively/info.json
+++ b/jamstack/sites/html5up/massively/info.json
@@ -5,4 +5,4 @@
"instagram": "",
"github": ""
}
-}
\ No newline at end of file
+}
diff --git a/jamstack/sites/html5up/massively/settings.py b/jamstack/sites/html5up/massively/settings.py
index 5588b5a..ff5e831 100644
--- a/jamstack/sites/html5up/massively/settings.py
+++ b/jamstack/sites/html5up/massively/settings.py
@@ -1,9 +1,8 @@
-
OUTPUT_FOLDER = 'dist/'
info = {
- "social":{
+ "social": {
"facebook": "",
- "twitter": "https://twitter.com/osdotsystem",
+ "twitter": "",
"instagram": "",
"github": ""
},
@@ -12,4 +11,4 @@
["Page 2", "index2.html", "page_2"],
["Page 3", "index3.html", "page_3"],
]
-}
\ No newline at end of file
+}
diff --git a/jamstack/sites/html5up/massively/static.py b/jamstack/sites/html5up/massively/static.py
index d6d2729..8680250 100644
--- a/jamstack/sites/html5up/massively/static.py
+++ b/jamstack/sites/html5up/massively/static.py
@@ -1,49 +1,42 @@
-# https://github.com/pymug/website-AV19-AV20
-
-import sys
+import argparse
from os.path import join
-from jamstack.api.template import generate
-from jamstack.api.template import base_context
-from flask import Flask
from livereload import Server
import settings
+from jamstack.api.template import generate
+from jamstack.jamdo.jamdo import download as download_template
-context = base_context()
-context.update({
- "info": settings.info
-})
-
-
-def main(args):
- def gen():
- generate('index.html', join(settings.OUTPUT_FOLDER, 'index.html'), **context)
- generate('index2.html', join(settings.OUTPUT_FOLDER, 'index2.html'), **context)
- generate('index3.html', join(settings.OUTPUT_FOLDER, 'index3.html'), **context)
-
- if len(args) > 1 and args[1] == '--server':
- app = Flask(__name__)
-
- # remember to use DEBUG mode for templates auto reload
- # https://github.com/lepture/python-livereload/issues/144
- app.debug = True
- server = Server(app.wsgi_app)
- # run a shell command
- # server.watch('.', 'make static')
+def generate_site():
+ extra_context = {"info": settings.info}
- # run a function
+ generate('index.html', join(settings.OUTPUT_FOLDER, 'index.html'), context=extra_context)
+ generate('index2.html', join(settings.OUTPUT_FOLDER, 'index2.html'), context=extra_context)
+ generate('index3.html', join(settings.OUTPUT_FOLDER, 'index3.html'), context=extra_context)
- server.watch('.', gen, delay=5)
- server.watch('*.py')
- # output stdout into a file
- # server.watch('style.less', shell('lessc style.less', output='style.css'))
+def serve_files(port, watch):
+ server = Server()
+ for x in watch.split('|'):
+ server.watch(x, func=generate_site)
+ try:
+ server.serve(root=settings.OUTPUT_FOLDER, port=port)
+ except KeyboardInterrupt:
+ print("Shutting down...")
- server.serve()
- else:
- gen()
if __name__ == '__main__':
- main(sys.argv)
+ parser = argparse.ArgumentParser(description='Project manager.')
+ parser.add_argument('--serve', action='store_true', help='Serve files for livewatch')
+ parser.add_argument('--jamdo', action='store_true', help='Download template assets')
+ parser.add_argument('--watch', type=str, default='*.py|templates|templates/sections', help='Files/Folders to watch')
+ parser.add_argument('--port', type=int, default=8000, help='Port to serve')
+ args = parser.parse_args()
+
+ if args.serve:
+ serve_files(args.port, args.watch)
+ elif args.jamdo:
+ download_template('html5up/massively')
+ else:
+ generate_site()
diff --git a/jamstack/sites/html5up/massively/templates/index.html b/jamstack/sites/html5up/massively/templates/index.html
index 1bfc518..2b80bd0 100644
--- a/jamstack/sites/html5up/massively/templates/index.html
+++ b/jamstack/sites/html5up/massively/templates/index.html
@@ -10,32 +10,24 @@
{% include 'sections/head_content.html' %}
-
-
-
-
-
-
- {% include 'sections/nav.html' %}
-
- {% include 'sections/main.html' %}
-
- {% include 'sections/footer.html' %}
-
-
-
-
+
+
+
+ {% include 'sections/nav.html' %}
+ {% include 'sections/main.html' %}
+ {% include 'sections/footer.html' %}
+
+
-
+
{% include 'sections/bottom_scripts.html' %}