Skip to content

Commit 5f39deb

Browse files
authored
Merge branch 'main' into bugfix/12338
2 parents 2fb2c6d + 29091da commit 5f39deb

File tree

10 files changed

+301
-21
lines changed

10 files changed

+301
-21
lines changed

news/changelog-1.7.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,27 @@ All changes included in 1.7:
123123
### `jupyter`
124124

125125
- ([#12114](https://github.com/quarto-dev/quarto-cli/issues/12114)): `JUPYTERCACHE` environment variable from [Jupyter cache CLI](https://jupyter-cache.readthedocs.io/en/latest/using/cli.html) is now respected by Quarto when `cache: true` is used. This environment variable allows to change the path of the cache directory.
126+
- ([#12374](https://github.com/quarto-dev/quarto-cli/issues/12374)): Detect language properly in Jupyter notebooks that lack the `language` field in their `kernelspec`s.
126127

127128
## Other Fixes and Improvements
128129

130+
- A new folder `quarto-session-temp` can be created in `.quarto` to store temporary files created by Quarto during a rendering. Reminder: `.quarto` is for internal use of Quarto and should not be versioned (thus added to `.gitignore`).
131+
- ([fb38eb5](https://github.com/quarto-dev/quarto-cli/commit/fb38eb56c11e09f44cef58fd3b697ff24bb5a3f3)) Use the `latest` parser for Acorn when analyzing JS code imported from OJS blocks.
129132
- ([#7260](https://github.com/quarto-dev/quarto-cli/issues/7260)): Add support for `active` class in tabsets so the `.active` tab shows up by default.
130133
- ([#8613](https://github.com/quarto-dev/quarto-cli/issues/8613)): Fix `giscus` color on load to support dark mode (by @kv9898).
131134
- ([#11441](https://github.com/quarto-dev/quarto-cli/issues/11441)): Don't add newlines around shortcodes during processing.
132135
- ([#11643](https://github.com/quarto-dev/quarto-cli/issues/11643)): Improve highlighting of nested code block inside markdown code block, i.e. using ` ```{{python}} ` or ` ```python ` inside ` ````markdown` fenced code block.
133-
- ([fb38eb5](https://github.com/quarto-dev/quarto-cli/commit/fb38eb56c11e09f44cef58fd3b697ff24bb5a3f3)) Use the `latest` parser for Acorn when analyzing JS code imported from OJS blocks.
134136
- ([#10532](https://github.com/quarto-dev/quarto-cli/issues/10532)): Quarto changed default of `--headless=old` to `--headless` as [Chrome 132 has removed old headless mode](https://developer.chrome.com/blog/removing-headless-old-from-chrome) and only support new mode. For using old mode, `QUARTO_CHROMIUM` could be set to a [new `chrome-headless-shell` binary](https://developer.chrome.com/blog/chrome-headless-shell) or too an older chrome version (between 128 and 132) and `QUARTO_CHROMIUM_HEADLESS_MODE` set to `old` for using old headless mode with that compatabitle version.
135137
- ([#10961](https://github.com/quarto-dev/quarto-cli/issues/10961)): Add more information on which Chrome Headless will be used in `quarto check install`. This is helpful to help debug mermaid issues.
138+
- ([#11606](https://github.com/quarto-dev/quarto-cli/discussions/11606)): Added a new `QUARTO_DOCUMENT_FILE` env var available to computation engine to the name of the file currently being rendered.
139+
- ([#11803](https://github.com/quarto-dev/quarto-cli/pull/11803)): Added a new CLI command `quarto call`. First users of this interface are the new `quarto call engine julia ...` subcommands.
136140
- ([#11951](https://github.com/quarto-dev/quarto-cli/issues/11951)): Raw LaTeX table without `tbl-` prefix label for using Quarto crossref are now correctly passed through unmodified.
137141
- ([#12117](https://github.com/quarto-dev/quarto-cli/issues/12117)): Color output to stdout and stderr is now correctly rendered for `html` format in the Jupyter and Julia engines.
138142
- ([#12264](https://github.com/quarto-dev/quarto-cli/issues/12264)): Upgrade `dart-sass` to 1.85.1.
139143
- ([#11803](https://github.com/quarto-dev/quarto-cli/pull/11803)): Added a new CLI command `quarto call`. First users of this interface are the new `quarto call engine julia ...` subcommands.
140-
- A new folder `quarto-session-temp` can be created in `.quarto` to store temporary files created by Quarto during a rendering. Reminder: `.quarto` is for internal use of Quarto and should not be versioned (thus added to `.gitignore`).
141-
- ([#11606](https://github.com/quarto-dev/quarto-cli/discussions/11606)): Added a new `QUARTO_DOCUMENT_FILE` env var available to computation engine to the name of the file currently being rendered.
142144
- ([#12338](https://github.com/quarto-dev/quarto-cli/issues/12338)): Add an additional workaround for the SCSS parser used in color variable extraction.
145+
- ([#12369](https://github.com/quarto-dev/quarto-cli/pull/12369)): `quarto preview` correctly throws a YAML validation error when a `format` key does not conform.
146+
147+
## Languages
148+
149+
- ([#12366](https://github.com/quarto-dev/quarto-cli/pull/12366)): Added Bulgarian translation for Quarto UI text (credit: @sakelariev)

src/command/convert/jupyter.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,23 @@ export async function jupyterNotebookToMarkdown(
4747
) {
4848
// read notebook & alias kernelspec
4949
const notebook = fixupFrontMatter(jupyterFromFile(file));
50-
const kernelspec = notebook.metadata.kernelspec;
50+
let kernelspec = notebook.metadata.kernelspec;
51+
52+
// https://github.com/quarto-dev/quarto-cli/issues/12374
53+
// narrow fix for .ipynbs that have a language_info field but no kernelspec.language
54+
if (
55+
kernelspec.language === undefined && notebook.metadata.language_info?.name
56+
) {
57+
kernelspec = {
58+
...kernelspec,
59+
language: notebook.metadata.language_info?.name,
60+
};
61+
}
62+
if (kernelspec.language === undefined) {
63+
throw new Error(
64+
"No language found in kernelspec for notebook " + file,
65+
);
66+
}
5167

5268
// generate markdown
5369
const md: string[] = [];

src/command/render/render-contexts.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ function mergeQuartoConfigs(
347347

348348
// bibliography needs to always be an array so it can be merged
349349
const fixupMergeableScalars = (metadata: Metadata) => {
350+
// see https://github.com/quarto-dev/quarto-cli/pull/12372
351+
// and https://github.com/quarto-dev/quarto-cli/pull/12369
352+
// for more details on why we need this check, as a consequence of an unintuitive
353+
// ordering of YAML validation operations
354+
if (metadata === null) return metadata;
350355
[
351356
kBibliography,
352357
kCss,

src/core/jupyter/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ export interface JupyterCapabilitiesEx extends JupyterCapabilities {
7474
venv?: boolean;
7575
}
7676

77+
// cf https://github.com/jupyter/nbformat/blob/main/nbformat/v4/nbformat.v4.schema.json
78+
// note that this doesn't directly define the kernelspec as used in kernel.json
79+
// but defines the kernelspec object used in .ipynb files
80+
//
81+
// note in addition that Quarto needs to know the language name which
82+
// might not come from the kernelspec itself but will exist in a language_info
83+
// field. When the kernelspec object in a jupyter notebook is missing the language field,
84+
// this object's language field will come from the language_info.name field
85+
//
86+
// see https://github.com/quarto-dev/quarto-cli/issues/12374
7787
export interface JupyterKernelspec {
7888
name: string;
7989
language: string;
@@ -88,10 +98,20 @@ export interface JupyterAssets {
8898
supporting_dir: string;
8999
}
90100

101+
// cf https://github.com/jupyter/nbformat/blob/main/nbformat/v4/nbformat.v4.schema.json
102+
export type JupyterLanguageInfo = {
103+
name: string;
104+
codemirror_mode?: string | Record<string, unknown>;
105+
file_extension?: string;
106+
mimetype?: string;
107+
pygments_lexer?: string;
108+
};
109+
91110
export interface JupyterNotebook {
92111
metadata: {
93112
kernelspec: JupyterKernelspec;
94113
widgets?: Record<string, unknown>;
114+
language_info?: JupyterLanguageInfo;
95115
[key: string]: unknown;
96116
};
97117
cells: JupyterCell[];

src/execute/jupyter/jupyter-kernel.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export async function executeKernelOneshot(
5555
}
5656

5757
trace(options, "Executing notebook with oneshot kernel");
58-
const debug = !!options.format.execute[kExecuteDebug];
58+
const debug = !!options.format.execute[kExecuteDebug] ||
59+
(!!Deno.env.get("QUARTO_JUPYTER_DEBUG"));
5960
const result = await execJupyter(
6061
"execute",
6162
{ ...options, debug },

src/execute/jupyter/jupyter.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,20 @@ export const jupyterEngine: ExecutionEngine = {
181181
let nb: JupyterNotebook | undefined;
182182
if (isJupyterNotebook(file)) {
183183
const nbJSON = Deno.readTextFileSync(file);
184-
nb = JSON.parse(nbJSON) as JupyterNotebook;
184+
const nbRaw = JSON.parse(nbJSON);
185+
186+
// https://github.com/quarto-dev/quarto-cli/issues/12374
187+
// kernelspecs are not guaranteed to have a language field
188+
// so we need to check for it and if not present
189+
// use the language_info.name field
190+
if (
191+
nbRaw.metadata.kernelspec &&
192+
nbRaw.metadata.kernelspec.language === undefined &&
193+
nbRaw.metadata.language_info?.name
194+
) {
195+
nbRaw.metadata.kernelspec.language = nbRaw.metadata.language_info.name;
196+
}
197+
nb = nbRaw as JupyterNotebook;
185198
}
186199

187200
// cache check for percent script

src/resources/jupyter/notebook.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636

3737
NB_FORMAT_VERSION = 4
3838

39+
def get_language_from_nb_metadata(metadata):
40+
ks_lang = metadata.kernelspec.get("language", None)
41+
li_name = None
42+
li = metadata.get("language_info", None)
43+
if li:
44+
li_name = metadata.language_info.get("name", None)
45+
return ks_lang or li_name
46+
3947
# exception to indicate the kernel needs restarting
4048
class RestartKernel(Exception):
4149
pass
@@ -192,7 +200,7 @@ def notebook_execute(options, status):
192200
nb_parameterize(nb, quarto_kernel_setup_options["params"])
193201

194202
# insert setup cell
195-
setup_cell = nb_setup_cell(nb.metadata.kernelspec, quarto_kernel_setup_options)
203+
setup_cell = nb_setup_cell(nb, quarto_kernel_setup_options)
196204
nb.cells.insert(0, setup_cell)
197205

198206
nb_cache = retrieve_nb_from_cache(nb, status, **quarto_kernel_setup_options)
@@ -254,7 +262,8 @@ def handle_meta_object(obj):
254262
if cell.cell_type == 'code':
255263
total_code_cells += 1
256264
# map cells to their labels
257-
label = nb_cell_yaml_options(client.nb.metadata.kernelspec.language, cell).get('label', '')
265+
language = get_language_from_nb_metadata(client.nb.metadata)
266+
label = nb_cell_yaml_options(language, cell).get('label', '')
258267
cell_labels.append(label)
259268
# find max label length
260269
max_label_len = max(max_label_len, len(label))
@@ -350,7 +359,7 @@ def handle_meta_object(obj):
350359
nb_write(client.nb, input)
351360

352361
# execute cleanup cell
353-
cleanup_cell = nb_cleanup_cell(nb.metadata.kernelspec, resource_dir)
362+
cleanup_cell = nb_cleanup_cell(nb, resource_dir)
354363
if cleanup_cell:
355364
kernel_supports_daemonization = True
356365
nb.cells.append(cleanup_cell)
@@ -425,18 +434,20 @@ async def get_info():
425434
def nb_write(nb, input):
426435
nbformat.write(nb, input, version = NB_FORMAT_VERSION)
427436

428-
def nb_setup_cell(kernelspec, options):
437+
def nb_setup_cell(nb, options):
429438
options = dict(options)
430439
options["allow_empty"] = True
431-
return nb_language_cell('setup', kernelspec, **options)
440+
return nb_language_cell('setup', nb, **options)
432441

433-
def nb_cleanup_cell(kernelspec, resource_dir):
434-
return nb_language_cell('cleanup', kernelspec, resource_dir, False)
442+
def nb_cleanup_cell(nb, resource_dir):
443+
return nb_language_cell('cleanup', nb, resource_dir, False)
435444

436-
def nb_language_cell(name, kernelspec, resource_dir, allow_empty, **args):
437-
trace(json.dumps(kernelspec, indent=2))
445+
def nb_language_cell(name, nb, resource_dir, allow_empty, **args):
446+
kernelspec = nb.metadata.kernelspec
447+
language = get_language_from_nb_metadata(nb.metadata)
448+
trace(json.dumps(nb.metadata, indent=2))
438449
source = ''
439-
lang_dir = os.path.join(resource_dir, 'jupyter', 'lang', kernelspec.language)
450+
lang_dir = os.path.join(resource_dir, 'jupyter', 'lang', language)
440451
if os.path.isdir(lang_dir):
441452
cell_file = glob.glob(os.path.join(lang_dir, name + '.*'))
442453
# base64-encode the run_path given
@@ -445,7 +456,7 @@ def nb_language_cell(name, kernelspec, resource_dir, allow_empty, **args):
445456
with open(cell_file[0], 'r') as file:
446457
source = file.read().format(**args)
447458
else:
448-
trace(f'No {kernelspec.language} directory found in {lang_dir}')
459+
trace(f'No {language} directory found in {lang_dir}')
449460
trace(f'Will look for explicit quarto setup cell information in kernelspec dir')
450461
try:
451462
with open(os.path.join(kernelspec.path, f"quarto_{name}_cell"), 'r') as file:
@@ -500,8 +511,9 @@ def nb_kernel_dependencies(setup_cell):
500511

501512
def cell_execute(client, cell, index, execution_count, eval_default, store_history):
502513

514+
language = get_language_from_nb_metadata(client.nb.metadata)
503515
# read cell options
504-
cell_options = nb_cell_yaml_options(client.nb.metadata.kernelspec.language, cell)
516+
cell_options = nb_cell_yaml_options(language, cell)
505517

506518
# check options for eval and error
507519
eval = cell_options.get('eval', eval_default)
@@ -560,7 +572,7 @@ def clear_user_expressions():
560572
del metadata["user_expressions"]
561573

562574
# find expressions in source
563-
language = client.nb.metadata.kernelspec.language
575+
language = get_language_from_nb_metadata(client.nb.metadata)
564576
source = ''.join(cell.source)
565577
expressions = re.findall(
566578
fr'(?:^|[^`])`{{{language}}}[ \t]([^`]+)`',
@@ -623,7 +635,7 @@ def nb_parameterize(nb, params):
623635

624636
# alias kernel name and language
625637
kernel_name = nb.metadata.kernelspec.name
626-
language = nb.metadata.kernelspec.language
638+
language = get_language_from_nb_metadata(nb.metadata)
627639

628640
# find params index and note any tags/yaml on it (exit if no params)
629641
params_index = find_first_tagged_cell_index(nb, "parameters")
@@ -701,7 +713,7 @@ def find_first_tagged_cell_index(nb, tag):
701713
return parameters_indices[0]
702714

703715
def nb_strip_yaml_options(client, source):
704-
yaml_lines = nb_cell_yaml_lines(client.nb.metadata.kernelspec.language, source)
716+
yaml_lines = nb_cell_yaml_lines(get_language_from_nb_metadata(client.nb.metadata), source)
705717
num_yaml_lines = len(yaml_lines)
706718
if num_yaml_lines > 0:
707719
return "\n".join(source.splitlines()[num_yaml_lines:])
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
toc-title-document: "Съдържание"
2+
toc-title-website: "На тази страница"
3+
4+
related-formats-title: "Други формати"
5+
related-notebooks-title: "Тетрадки"
6+
source-notebooks-prefix: "Източник"
7+
other-links-title: "Други връзки"
8+
code-links-title: "Връзки към код"
9+
launch-dev-container-title: "Стартиране на контейнер за разработка"
10+
launch-binder-title: "Стартиране на Binder"
11+
12+
article-notebook-label: "Статия - Тетрадка"
13+
notebook-preview-download: "Изтегли тетрадка"
14+
notebook-preview-download-src: "Изтегли източник"
15+
notebook-preview-back: "Обратно към статията"
16+
manuscript-meca-bundle: "MECA пакет"
17+
18+
section-title-abstract: "Резюме"
19+
section-title-appendices: "Приложения"
20+
section-title-footnotes: "Бележки под линия"
21+
section-title-references: "Референции"
22+
section-title-reuse: "Повторна употреба"
23+
section-title-copyright: "Авторски права"
24+
section-title-citation: "Цитиране"
25+
26+
appendix-attribution-cite-as: "За атрибуция, моля цитирайте това произведение като:"
27+
appendix-attribution-bibtex: "Цитиране в BibTeX:"
28+
appendix-view-license: "Преглед на лиценз"
29+
30+
title-block-author-single: "Автор"
31+
title-block-author-plural: "Автори"
32+
title-block-affiliation-single: "Институция"
33+
title-block-affiliation-plural: "Институции"
34+
title-block-published: "Публикувано"
35+
title-block-modified: "Променено"
36+
title-block-keywords: "Ключови думи"
37+
38+
callout-tip-title: "Съвет"
39+
callout-note-title: "Бележка"
40+
callout-warning-title: "Предупреждение"
41+
callout-important-title: "Важно"
42+
callout-caution-title: "Внимание"
43+
44+
code-summary: "Код"
45+
46+
code-tools-menu-caption: "Код"
47+
code-tools-show-all-code: "Покажи целия код"
48+
code-tools-hide-all-code: "Скрий целия код"
49+
code-tools-view-source: "Преглед на източника"
50+
code-tools-source-code: "Изходен код"
51+
52+
tools-share: "Сподели"
53+
tools-download: "Изтегли"
54+
55+
code-line: "Ред"
56+
code-lines: "Редове"
57+
58+
copy-button-tooltip: "Копирай в клипборда"
59+
copy-button-tooltip-success: "Копирано!"
60+
61+
repo-action-links-edit: "Редактирай тази страница"
62+
repo-action-links-source: "Преглед на източника"
63+
repo-action-links-issue: "Докладвай проблем"
64+
65+
back-to-top: "Обратно в началото"
66+
67+
search-no-results-text: "Няма резултати"
68+
search-matching-documents-text: "съвпадащи документи"
69+
search-copy-link-title: "Копирай връзката към търсенето"
70+
search-hide-matches-text: "Скрий допълнителните съвпадения"
71+
search-more-match-text: "още едно съвпадение в този документ"
72+
search-more-matches-text: "още съвпадения в този документ"
73+
search-clear-button-title: "Изчисти"
74+
search-text-placeholder: ""
75+
search-detached-cancel-button-title: "Отказ"
76+
search-submit-button-title: "Търси"
77+
search-label: "Търсене"
78+
79+
toggle-section: "Превключи секцията"
80+
toggle-sidebar: "Превключи навигацията в страничната лента"
81+
toggle-dark-mode: "Превключи на тъмен режим"
82+
toggle-reader-mode: "Превключи на режим за четене"
83+
toggle-navigation: "Превключи навигацията"
84+
85+
crossref-fig-title: "Фигура"
86+
crossref-tbl-title: "Таблица"
87+
crossref-lst-title: "Изброяване"
88+
crossref-thm-title: "Теорема"
89+
crossref-lem-title: "Лема"
90+
crossref-cor-title: "Следствие"
91+
crossref-prp-title: "Предложение"
92+
crossref-cnj-title: "Хипотеза"
93+
crossref-def-title: "Дефиниция"
94+
crossref-exm-title: "Пример"
95+
crossref-exr-title: "Упражнение"
96+
crossref-ch-prefix: "Глава"
97+
crossref-apx-prefix: "Приложение"
98+
crossref-sec-prefix: "Раздел"
99+
crossref-eq-prefix: "Уравнение"
100+
crossref-lof-title: "Списък на фигурите"
101+
crossref-lot-title: "Списък на таблиците"
102+
crossref-lol-title: "Списък на изброяванията"
103+
104+
environment-proof-title: "Доказателство"
105+
environment-remark-title: "Забележка"
106+
environment-solution-title: "Решение"
107+
108+
listing-page-order-by: "Подреди по"
109+
listing-page-order-by-default: "По подразбиране"
110+
listing-page-order-by-date-asc: "Най-старо"
111+
listing-page-order-by-date-desc: "Най-ново"
112+
listing-page-order-by-number-desc: "От високо към ниско"
113+
listing-page-order-by-number-asc: "От ниско към високо"
114+
listing-page-field-date: "Дата"
115+
listing-page-field-title: "Заглавие"
116+
listing-page-field-description: "Описание"
117+
listing-page-field-author: "Автор"
118+
listing-page-field-filename: "Име на файла"
119+
listing-page-field-filemodified: "Променено"
120+
listing-page-field-subtitle: "Подзаглавие"
121+
listing-page-field-readingtime: "Време за четене"
122+
listing-page-field-wordcount: "Брой думи"
123+
listing-page-field-categories: "Категории"
124+
listing-page-minutes-compact: "{0} мин"
125+
listing-page-category-all: "Всички"
126+
listing-page-no-matches: "Няма съвпадения"
127+
listing-page-words: "{0} думи"
128+
listing-page-filter: "Филтър"
129+
130+
draft: "Чернова"
131+

0 commit comments

Comments
 (0)