Skip to content

Commit c7ae807

Browse files
authored
Merge pull request #254 from rstudio/aron-quarto-documents
allow deploy and manifest creation from standalone Quarto documents
2 parents f64cf60 + f34ae7a commit c7ae807

File tree

4 files changed

+107
-60
lines changed

4 files changed

+107
-60
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [Unreleased]
8+
9+
### Added
10+
11+
- You can now deploy Quarto documents in addition to Quarto projects. This
12+
requires RStudio Connect release 2021.08.0 or later. Use `rsconnect deploy
13+
quarto` to deploy, or `rsconnect write-manifest quarto` to create a manifest
14+
file.
15+
716
## [1.8.1] - 2022-05-31
817

918
### Changed

rsconnect/actions.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,11 @@ def quarto_inspect(
505505
"""
506506
Runs 'quarto inspect' against the target and returns its output as a
507507
parsed JSON object.
508+
509+
The JSON result has different structure depending on whether or not the
510+
target is a directory or a file.
508511
"""
512+
509513
args = [quarto, "inspect", target]
510514
try:
511515
inspect_json = check_output(args, universal_newlines=True, stderr=subprocess.STDOUT)
@@ -527,7 +531,7 @@ def validate_quarto_engines(inspect):
527531

528532

529533
def write_quarto_manifest_json(
530-
directory: str,
534+
file_or_directory: str,
531535
inspect: typing.Any,
532536
app_mode: AppMode,
533537
environment: Environment,
@@ -538,7 +542,7 @@ def write_quarto_manifest_json(
538542
"""
539543
Creates and writes a manifest.json file for the given Quarto project.
540544
541-
:param directory: The directory containing the Quarto project.
545+
:param file_or_directory: The Quarto document or the directory containing the Quarto project.
542546
:param inspect: The parsed JSON from a 'quarto inspect' against the project.
543547
:param app_mode: The application mode to assume (such as AppModes.STATIC_QUARTO)
544548
:param environment: The (optional) Python environment to use.
@@ -547,10 +551,20 @@ def write_quarto_manifest_json(
547551
:param image: the optional docker image to be specified for off-host execution. Default = None.
548552
"""
549553

550-
extra_files = validate_extra_files(directory, extra_files)
551-
manifest, _ = make_quarto_manifest(directory, inspect, app_mode, environment, extra_files, excludes, image)
552-
manifest_path = join(directory, "manifest.json")
554+
manifest, _ = make_quarto_manifest(
555+
file_or_directory,
556+
inspect,
557+
app_mode,
558+
environment,
559+
extra_files,
560+
excludes,
561+
image,
562+
)
553563

564+
base_dir = file_or_directory
565+
if not isdir(file_or_directory):
566+
base_dir = dirname(file_or_directory)
567+
manifest_path = join(base_dir, "manifest.json")
554568
write_manifest_json(manifest_path, manifest)
555569

556570

@@ -1307,7 +1321,7 @@ def gather_basic_deployment_info_from_manifest(
13071321
def gather_basic_deployment_info_for_quarto(
13081322
connect_server: api.RSConnectServer,
13091323
app_store: AppStore,
1310-
directory: str,
1324+
file_or_directory: str,
13111325
new: bool,
13121326
app_id: int,
13131327
title: str,
@@ -1317,7 +1331,7 @@ def gather_basic_deployment_info_for_quarto(
13171331
13181332
:param connect_server: The Connect server information.
13191333
:param app_store: The store for the specified Quarto project directory.
1320-
:param directory: The target Quarto project directory.
1334+
:param file_or_directory: The Quarto document or directory containing the Quarto project.
13211335
:param new: A flag to force a new deployment.
13221336
:param app_id: The identifier of the content to redeploy.
13231337
:param title: The content title (optional). A default title is generated when one is not provided.
@@ -1349,11 +1363,11 @@ def gather_basic_deployment_info_for_quarto(
13491363
) % (app_mode.desc(), existing_app_mode.desc())
13501364
raise api.RSConnectException(msg)
13511365

1352-
if directory[-1] == "/":
1353-
directory = directory[:-1]
1366+
if file_or_directory[-1] == "/":
1367+
file_or_directory = file_or_directory[:-1]
13541368

13551369
default_title = not bool(title)
1356-
title = title or _default_title(directory)
1370+
title = title or _default_title(file_or_directory)
13571371

13581372
return (
13591373
app_id,
@@ -1589,19 +1603,18 @@ def create_api_deployment_bundle(
15891603

15901604

15911605
def create_quarto_deployment_bundle(
1592-
directory: str,
1606+
file_or_directory: str,
15931607
extra_files: typing.List[str],
15941608
excludes: typing.List[str],
15951609
app_mode: AppMode,
15961610
inspect: typing.Dict[str, typing.Any],
15971611
environment: Environment,
1598-
extra_files_need_validating: bool,
15991612
image: str = None,
16001613
) -> typing.IO[bytes]:
16011614
"""
16021615
Create an in-memory bundle, ready to deploy.
16031616
1604-
:param directory: the directory that contains the code being deployed.
1617+
:param file_or_directory: The Quarto document or the directory containing the Quarto project.
16051618
:param extra_files: a sequence of any extra files to include in the bundle.
16061619
:param excludes: a sequence of glob patterns that will exclude matched files.
16071620
:param entry_point: the module/executable object for the WSGi framework.
@@ -1614,13 +1627,10 @@ def create_quarto_deployment_bundle(
16141627
:param image: the optional docker image to be specified for off-host execution. Default = None.
16151628
:return: the bundle.
16161629
"""
1617-
if extra_files_need_validating:
1618-
extra_files = validate_extra_files(directory, extra_files)
1619-
16201630
if app_mode is None:
16211631
app_mode = AppModes.STATIC_QUARTO
16221632

1623-
return make_quarto_source_bundle(directory, inspect, app_mode, environment, extra_files, excludes, image)
1633+
return make_quarto_source_bundle(file_or_directory, inspect, app_mode, environment, extra_files, excludes, image)
16241634

16251635

16261636
def deploy_bundle(

rsconnect/bundle.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -309,9 +309,8 @@ def make_notebook_source_bundle(
309309
return bundle_file
310310

311311

312-
#
313312
def make_quarto_source_bundle(
314-
directory: str,
313+
file_or_directory: str,
315314
inspect: typing.Dict[str, typing.Any],
316315
app_mode: AppMode,
317316
environment: Environment,
@@ -326,17 +325,21 @@ def make_quarto_source_bundle(
326325
Returns a file-like object containing the bundle tarball.
327326
"""
328327
manifest, relevant_files = make_quarto_manifest(
329-
directory, inspect, app_mode, environment, extra_files, excludes, image
328+
file_or_directory, inspect, app_mode, environment, extra_files, excludes, image
330329
)
331330
bundle_file = tempfile.TemporaryFile(prefix="rsc_bundle")
332331

332+
base_dir = file_or_directory
333+
if not isdir(file_or_directory):
334+
base_dir = basename(file_or_directory)
335+
333336
with tarfile.open(mode="w:gz", fileobj=bundle_file) as bundle:
334337
bundle_add_buffer(bundle, "manifest.json", json.dumps(manifest, indent=2))
335338
if environment:
336339
bundle_add_buffer(bundle, environment.filename, environment.contents)
337340

338341
for rel_path in relevant_files:
339-
bundle_add_file(bundle, rel_path, directory)
342+
bundle_add_file(bundle, rel_path, base_dir)
340343

341344
# rewind file pointer
342345
bundle_file.seek(0)
@@ -804,7 +807,7 @@ def _create_quarto_file_list(
804807

805808

806809
def make_quarto_manifest(
807-
directory: str,
810+
file_or_directory: str,
808811
quarto_inspection: typing.Dict[str, typing.Any],
809812
app_mode: AppMode,
810813
environment: Environment,
@@ -815,7 +818,7 @@ def make_quarto_manifest(
815818
"""
816819
Makes a manifest for a Quarto project.
817820
818-
:param directory: The directory containing the Quarto project.
821+
:param file_or_directory: The Quarto document or the directory containing the Quarto project.
819822
:param quarto_inspection: The parsed JSON from a 'quarto inspect' against the project.
820823
:param app_mode: The application mode to assume.
821824
:param environment: The (optional) Python environment to use.
@@ -827,21 +830,29 @@ def make_quarto_manifest(
827830
if environment:
828831
extra_files = list(extra_files or []) + [environment.filename]
829832

830-
excludes = list(excludes or []) + [".quarto"]
833+
base_dir = file_or_directory
834+
if isdir(file_or_directory):
835+
# Directory as a Quarto project.
836+
excludes = list(excludes or []) + [".quarto"]
831837

832-
project_config = quarto_inspection.get("config", {}).get("project", {})
833-
output_dir = project_config.get("output-dir", None)
834-
if output_dir:
835-
excludes = excludes + [output_dir]
838+
project_config = quarto_inspection.get("config", {}).get("project", {})
839+
output_dir = project_config.get("output-dir", None)
840+
if output_dir:
841+
excludes = excludes + [output_dir]
842+
else:
843+
render_targets = project_config.get("render", [])
844+
for target in render_targets:
845+
t, _ = splitext(target)
846+
# TODO: Single-file inspect would give inspect.formats.html.pandoc.output-file
847+
# For foo.qmd, we would get an output-file=foo.html, but foo_files is not available.
848+
excludes = excludes + [t + ".html", t + "_files"]
849+
850+
relevant_files = _create_quarto_file_list(base_dir, extra_files, excludes)
836851
else:
837-
render_targets = project_config.get("render", [])
838-
for target in render_targets:
839-
t, _ = splitext(target)
840-
# TODO: Single-file inspect would give inspect.formats.html.pandoc.output-file
841-
# For foo.qmd, we would get an output-file=foo.html, but foo_files is not available.
842-
excludes = excludes + [t + ".html", t + "_files"]
852+
# Standalone Quarto document
853+
base_dir = dirname(file_or_directory)
854+
relevant_files = [file_or_directory] + extra_files
843855

844-
relevant_files = _create_quarto_file_list(directory, extra_files, excludes)
845856
manifest = make_source_manifest(
846857
app_mode,
847858
environment,
@@ -851,6 +862,6 @@ def make_quarto_manifest(
851862
)
852863

853864
for rel_path in relevant_files:
854-
manifest_add_file(manifest, rel_path, directory)
865+
manifest_add_file(manifest, rel_path, base_dir)
855866

856867
return manifest, relevant_files

0 commit comments

Comments
 (0)