diff --git a/.gitignore b/.gitignore index 4aa701ba..f978a8d7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ pip-log.txt #Mr Developer .mr.developer.cfg -jb/_build/ \ No newline at end of file +jb/_build/ +jb/.wrangler/cache/wrangler-account.json +jb/extra_html/cover-source.jpg \ No newline at end of file diff --git a/README.md b/README.md index c4eee160..3e1a9973 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # Think Python, 3.ª edición +Portada en español de Think Python, 3.ª edición + Materiales, notebooks de Jupyter y versión web en español de la 3.ª edición de *Think Python: How to Think Like a Computer Scientist*, de Allen B. Downey. Traducción al español por [midudev](https://midu.dev) (Miguel Ángel Durán). ## Enlaces -- Repositorio del proyecto: [libropython.es](https://libropython.es) +- Página pública del libro: [libropython.es](https://libropython.es) +- Repositorio del proyecto: [github.com/midudev/ThinkPython](https://github.com/midudev/ThinkPython) - Página original del libro: [Green Tea Press](http://thinkpython.com) - Versión impresa y electrónica en inglés: [Bookshop.org](https://bookshop.org/a/98697/9781098155438) y [Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325) @@ -52,6 +55,28 @@ python3 -m playwright install chromium - HTML: `jb/_build/html/index.html` - PDF: `jb/_build/html/think-python-es.pdf` - Archivos estáticos para producción: `jb/_build/html/` + - Carpeta lista para desplegar: `dist/` + +## Desplegar en Cloudflare Workers + +El archivo `wrangler.toml` despliega los assets estáticos desde `./dist`. + +1. Construye el libro: + + ```bash + cd jb + ./build.sh + cd .. + ``` + +2. Despliega con Wrangler: + + ```bash + npx wrangler login + npx wrangler deploy + ``` + +3. Para usar `https://libropython.es`, asigna ese dominio al Worker desde Cloudflare Dashboard, en Workers & Pages > tu Worker > Settings > Domains & Routes. ## Abrir los notebooks diff --git a/jb/_config.yml b/jb/_config.yml index 6d475908..65e043fe 100644 --- a/jb/_config.yml +++ b/jb/_config.yml @@ -1,5 +1,5 @@ # Book settings -title: Think Python (edición en español) +title: Think Python en español author: Allen B. Downey latex: @@ -14,6 +14,7 @@ repository: branch: v3 html: baseurl: https://libropython.es + favicon: extra_html/favicon.ico use_repository_button: true extra_footer: | @@ -53,7 +54,19 @@ parse: sphinx: local_extensions: full_book_pdf: _extensions + seo: _extensions/seo.py config: + language: es + html_title: Think Python en español + html_short_title: Libro Python + html_extra_path: + - extra_html + templates_path: + - _templates + html_static_path: + - _static + html_css_files: + - custom.css mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js mathjax3_config: tex: diff --git a/jb/_extensions/full_book_pdf.py b/jb/_extensions/full_book_pdf.py index 4b0b5ce6..8919f573 100644 --- a/jb/_extensions/full_book_pdf.py +++ b/jb/_extensions/full_book_pdf.py @@ -1,11 +1,15 @@ -"""Header download button for the generated full-book PDF.""" +"""Site links for the public book build.""" PDF_FILENAME = "think-python-es.pdf" PDF_TOOLTIP = "Descargar el libro completo en PDF" +SITE_HOME_URL = "https://libropython.es" -def use_full_book_pdf_download(app, pagename, templatename, context, doctree): - """Replace the page-download dropdown with the full-book PDF link.""" +def update_site_links(app, pagename, templatename, context, doctree): + """Point site-level home/download links to their public targets.""" + context["theme_logo_link"] = SITE_HOME_URL + context["theme_site_home_url"] = SITE_HOME_URL + header_buttons = context.get("header_buttons") if not isinstance(header_buttons, list): return @@ -29,5 +33,5 @@ def use_full_book_pdf_download(app, pagename, templatename, context, doctree): def setup(app): - app.connect("html-page-context", use_full_book_pdf_download, priority=700) + app.connect("html-page-context", update_site_links, priority=700) return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/jb/_extensions/seo.py b/jb/_extensions/seo.py new file mode 100644 index 00000000..1678a2eb --- /dev/null +++ b/jb/_extensions/seo.py @@ -0,0 +1,86 @@ +"""SEO metadata for the public book site.""" + +import html +import re + +SITE_URL = "https://libropython.es" +SITE_NAME = "Libro Python" +BOOK_TITLE = "Think Python en español" +AUTHOR = "Allen B. Downey" +TRANSLATOR = "midudev" +TWITTER_SITE = "@midudev" +THEME_COLOR = "#2d3748" +LOCALE = "es_ES" + +DEFAULT_DESCRIPTION = ( + "Think Python en español: introducción gratuita a Python de Allen B. Downey. " + "3.ª edición traducida con capítulos online, notebooks en Colab y PDF descargable." +) + +PAGE_DESCRIPTIONS = { + "index": DEFAULT_DESCRIPTION, +} + + +def _page_url(app, pagename: str) -> str: + base = SITE_URL.rstrip("/") + if pagename in {app.config.root_doc, "index"}: + return f"{base}/" + return f"{base}/{pagename}.html" + + +_TAG_RE = re.compile(r"<[^>]+>") + + +def _strip_html(value: str) -> str: + """Return *value* without HTML tags and with entities decoded. + + Sphinx feeds template titles with markup like + ``6. Valores de retorno``. Embedding + that verbatim inside ```` or ```` breaks the + HTML because of the inner double quotes, so we sanitise it before use. + """ + + text = _TAG_RE.sub("", value or "") + return html.unescape(text).strip() + + +def _page_title(app, pagename: str, title: str) -> str: + clean = _strip_html(title) + if pagename in {app.config.root_doc, "index"}: + return BOOK_TITLE + return f"{clean} — {BOOK_TITLE}" + + +def update_seo_context(app, pagename, templatename, context, doctree): + description = PAGE_DESCRIPTIONS.get(pagename, DEFAULT_DESCRIPTION) + is_home = pagename in {app.config.root_doc, "index"} + page_title = _page_title(app, pagename, context.get("title", BOOK_TITLE)) + page_url = _page_url(app, pagename) + image_url = f"{SITE_URL.rstrip('/')}/og-image.jpg" + + if is_home: + context["title"] = BOOK_TITLE + context["titlesuffix"] = "" + context["pageurl"] = page_url + + context["seo"] = { + "site_url": SITE_URL, + "site_name": SITE_NAME, + "book_title": BOOK_TITLE, + "description": description, + "page_title": page_title, + "page_url": page_url, + "image_url": image_url, + "author": AUTHOR, + "translator": TRANSLATOR, + "twitter_site": TWITTER_SITE, + "theme_color": THEME_COLOR, + "locale": LOCALE, + "is_home": is_home, + } + + +def setup(app): + app.connect("html-page-context", update_seo_context, priority=800) + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/jb/_static/custom.css b/jb/_static/custom.css new file mode 100644 index 00000000..cd7391cc --- /dev/null +++ b/jb/_static/custom.css @@ -0,0 +1,3 @@ +.title.logo__title { + text-wrap: balance; +} diff --git a/jb/_static/think_python_3e_es.png b/jb/_static/think_python_3e_es.png new file mode 100644 index 00000000..fc543daf Binary files /dev/null and b/jb/_static/think_python_3e_es.png differ diff --git a/jb/_templates/layout.html b/jb/_templates/layout.html new file mode 100644 index 00000000..5b9fba28 --- /dev/null +++ b/jb/_templates/layout.html @@ -0,0 +1,60 @@ +{% extends "!layout.html" %} + +{% block htmltitle -%} +<title>{{ seo.page_title|striptags|e }} +{%- endblock %} + +{% block extrahead %} + {{ super() }} + {%- if seo -%} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {%- endif -%} +{% endblock %} diff --git a/jb/_templates/sbt-sidebar-nav.html b/jb/_templates/sbt-sidebar-nav.html new file mode 100644 index 00000000..98dde954 --- /dev/null +++ b/jb/_templates/sbt-sidebar-nav.html @@ -0,0 +1,24 @@ + diff --git a/jb/build.sh b/jb/build.sh index 8c651fba..6a6d88db 100755 --- a/jb/build.sh +++ b/jb/build.sh @@ -4,16 +4,20 @@ set -euo pipefail # Build the Jupyter book version cd "$(dirname "$0")" -# Link the translated notebooks into the Jupyter Book source directory. +# Copy the translated notebooks into the Jupyter Book source directory so +# prep_notebooks.py can add build-only metadata without modifying originals. for notebook in ../chapters/chap[01][0-9]*.ipynb; do target="$(basename "$notebook")" - if [ -e "$target" ] && [ ! -L "$target" ]; then - echo "Refusing to replace existing non-symlink: $target" >&2 + if [ -e "$target" ] && [ ! -f "$target" ] && [ ! -L "$target" ]; then + echo "Refusing to replace existing non-file: $target" >&2 exit 1 fi - ln -sfn "$notebook" "$target" + rm -f "$target" + cp "$notebook" "$target" done +python3 prep_notebooks.py + if command -v jupyter-book >/dev/null 2>&1; then jupyter_book_cmd="jupyter-book" else @@ -37,6 +41,7 @@ pdf_filename="think-python-es.pdf" pdf_build_dir="_build/pdfhtml" pdf_source="$pdf_build_dir/_build/pdf/book.pdf" html_pdf="_build/html/$pdf_filename" +dist_dir="../dist" "$jupyter_book_cmd" build . "$jupyter_book_cmd" build . --builder pdfhtml --path-output "$pdf_build_dir" @@ -48,3 +53,8 @@ fi cp "$pdf_source" "$html_pdf" echo "Full-book PDF copied to $html_pdf" + +rm -rf "$dist_dir" +mkdir -p "$dist_dir" +cp -R _build/html/. "$dist_dir/" +echo "Static site copied to $dist_dir" diff --git a/jb/extra_html/apple-touch-icon.png b/jb/extra_html/apple-touch-icon.png new file mode 100644 index 00000000..ade399ad Binary files /dev/null and b/jb/extra_html/apple-touch-icon.png differ diff --git a/jb/extra_html/favicon-16.png b/jb/extra_html/favicon-16.png new file mode 100644 index 00000000..8b7ef2bf Binary files /dev/null and b/jb/extra_html/favicon-16.png differ diff --git a/jb/extra_html/favicon-32.png b/jb/extra_html/favicon-32.png new file mode 100644 index 00000000..4f60c26b Binary files /dev/null and b/jb/extra_html/favicon-32.png differ diff --git a/jb/extra_html/favicon-32x32.png b/jb/extra_html/favicon-32x32.png new file mode 100644 index 00000000..4f60c26b Binary files /dev/null and b/jb/extra_html/favicon-32x32.png differ diff --git a/jb/extra_html/favicon.ico b/jb/extra_html/favicon.ico new file mode 100644 index 00000000..417f3f5d Binary files /dev/null and b/jb/extra_html/favicon.ico differ diff --git a/jb/extra_html/favicon.svg b/jb/extra_html/favicon.svg new file mode 100644 index 00000000..3e2a3e12 --- /dev/null +++ b/jb/extra_html/favicon.svg @@ -0,0 +1,4 @@ + + + Py + diff --git a/jb/extra_html/icon-512.png b/jb/extra_html/icon-512.png new file mode 100644 index 00000000..9fe41360 Binary files /dev/null and b/jb/extra_html/icon-512.png differ diff --git a/jb/extra_html/og-image.jpg b/jb/extra_html/og-image.jpg new file mode 100644 index 00000000..0e6ebd6e Binary files /dev/null and b/jb/extra_html/og-image.jpg differ diff --git a/jb/extra_html/site.webmanifest b/jb/extra_html/site.webmanifest new file mode 100644 index 00000000..d0ebedf8 --- /dev/null +++ b/jb/extra_html/site.webmanifest @@ -0,0 +1,23 @@ +{ + "name": "Think Python en español", + "short_name": "Think Python", + "description": "Think Python en español: libro gratuito para aprender Python con notebooks y PDF.", + "start_url": "/", + "display": "standalone", + "background_color": "#2d3748", + "theme_color": "#2d3748", + "lang": "es", + "icons": [ + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png" + } + ] +} diff --git a/jb/index.md b/jb/index.md index 2864b08a..4718a1d7 100644 --- a/jb/index.md +++ b/jb/index.md @@ -1,10 +1,15 @@ -# Think Python (edición en español) +--- +title: Think Python en español +description: Think Python en español — introducción gratuita a Python de Allen B. Downey, 3.ª edición traducida con notebooks y PDF. +--- + +# Think Python en español *Think Python* es una introducción a Python para personas que nunca han programado, o para quienes lo han intentado y se han encontrado con dificultades. Esta edición en español traduce la tercera edición de *Think Python: How to Think Like a Computer Scientist*, de Allen B. Downey, con traducción de [midudev](https://midu.dev). - +Portada en español de Think Python, 3.ª edición Puedes pedir las versiones impresa y electrónica de *Think Python 3e* en [Bookshop.org](https://bookshop.org/a/98697/9781098155438) y diff --git a/jb/prep_notebooks.py b/jb/prep_notebooks.py index 514949d0..00e1593c 100644 --- a/jb/prep_notebooks.py +++ b/jb/prep_notebooks.py @@ -3,13 +3,25 @@ def process_cell(cell): - # get tags - tags = cell['metadata'].get('tags', []) + tags = cell['metadata'].setdefault('tags', []) - # add hide-cell tag to solutions if cell['cell_type'] == 'code': source = cell['source'] + # Hide the setup cells that download modules and configure autoreload. + # In most chapters both bits live in the same cell, but in some (e.g. + # chap04) they are split across two consecutive cells, so we detect + # each pattern independently to cover both layouts. + is_download_cell = 'def download(url):' in source + is_autoreload_cell = ( + 'import thinkpython' in source and '%autoreload' in source + ) + + if is_download_cell or is_autoreload_cell: + for tag in ['remove-cell', 'keep']: + if tag not in tags: + tags.append(tag) + # remove solutions if source.startswith('# Solution') or 'solution' in tags: cell['source'] = [] diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 00000000..340fdd33 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,20 @@ +name = "think-python-es" +compatibility_date = "2026-05-19" + +[observability] +enabled = false +head_sampling_rate = 1 + +[observability.logs] +enabled = true +head_sampling_rate = 1 +persist = true +invocation_logs = true + +[observability.traces] +enabled = false +persist = true +head_sampling_rate = 1 + +[assets] +directory = "./dist"