mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 01:32:34 -05:00
Feat/create book (#9159)
* added secondary action * create book command * open as untitled * create toc.yml and update title * added comments * throw error if filenames have unsupported chars * update prompt message * remove the toLocaleLower * added await * moced createbookpath out of the command handler * removed tolocalelower and added comments * moved the formatting and file handling code from core to notebook * fixes for contents with folders * collapse the code cell * remove output * reused existing command to open book * comment typu and added await
This commit is contained in:
@@ -180,6 +180,15 @@
|
||||
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||
"light": "resources/light/open_notebook.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "notebook.command.createBook",
|
||||
"title": "%title.createJupyterBook%",
|
||||
"category": "%books-preview-category%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||
"light": "resources/light/open_notebook.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
@@ -321,6 +330,10 @@
|
||||
"command": "notebook.command.openBook",
|
||||
"when": "view == bookTreeView",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "notebook.command.createBook",
|
||||
"when": "view == bookTreeView"
|
||||
}
|
||||
],
|
||||
"notebook/toolbar": [
|
||||
|
||||
@@ -34,5 +34,6 @@
|
||||
"title.SavedBooks": "Saved Books",
|
||||
"title.UnsavedBooks": "Unsaved Books",
|
||||
"title.PreviewLocalizedBook": "Get localized SQL Server 2019 guide",
|
||||
"title.openJupyterBook": "Open Jupyter Book"
|
||||
"title.openJupyterBook": "Open Jupyter Book",
|
||||
"title.createJupyterBook": "Create Book"
|
||||
}
|
||||
|
||||
280
extensions/notebook/resources/notebooks/JupyterBooksCreate.ipynb
Normal file
280
extensions/notebook/resources/notebooks/JupyterBooksCreate.ipynb
Normal file
@@ -0,0 +1,280 @@
|
||||
{
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"name": "python3",
|
||||
"display_name": "Python 3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python",
|
||||
"version": "3.7.2",
|
||||
"mimetype": "text/x-python",
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"pygments_lexer": "ipython3",
|
||||
"nbconvert_exporter": "python",
|
||||
"file_extension": ".py"
|
||||
}
|
||||
},
|
||||
"nbformat_minor": 2,
|
||||
"nbformat": 4,
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"# Jupyter Books\n",
|
||||
"\n",
|
||||
"## 1. Installation\n",
|
||||
"\n",
|
||||
"To install the Jupyter Book command-line interface (CLI), use `pip`!"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "97541c75-b1c9-4e4c-9f0a-f93df4a550ef"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import sys\r\n",
|
||||
"\r\n",
|
||||
"#install jupyter-book\r\n",
|
||||
"cmd = f'{sys.executable} -m pip show jupyter-book'\r\n",
|
||||
"cmdOutput = !{cmd}\r\n",
|
||||
"if len(cmdOutput) > 0 and '0.6.4' in cmdOutput[1]:\r\n",
|
||||
" print('Jupyter-book required version is already installed!')\r\n",
|
||||
"else:\r\n",
|
||||
" !pip install jupyter-book"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "8bd77173-2f63-4bf8-95e8-af2a654fc91e",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## 2. Create a new book\r\n",
|
||||
"\r\n",
|
||||
"Create a book using your own notebooks and markdown pages:\r\n",
|
||||
"\r\n",
|
||||
"<span style=\"color:red\">Note:</span> Notebook and markdown filenames cannot contain spaces"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6a1b6bb8-9cb8-43d5-878f-2029d1eacb0e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import os, re, shutil\r\n",
|
||||
"\r\n",
|
||||
"overwrite = False\r\n",
|
||||
"book_name = input('Please provide the path where the book needs to be saved along with the book name ex-> D:\\Book1: ') \r\n",
|
||||
"\r\n",
|
||||
"if (os.path.exists(book_name)):\r\n",
|
||||
" new_book_name = input('A folder named ' + book_name + ' already exists. Enter a new name or the same name to overwrite the existing folder.\\n')\r\n",
|
||||
" if book_name == new_book_name:\r\n",
|
||||
" overwrite = True\r\n",
|
||||
" book_name = new_book_name\r\n",
|
||||
"\r\n",
|
||||
"content_folder = input('Please provide the path to your folder containing notebooks and markdown files: ')\r\n",
|
||||
"\r\n",
|
||||
"while (not os.path.exists(content_folder)):\r\n",
|
||||
" content_folder = input('Cannot find folder ' + content_folder + '. Please provide another path: ')\r\n",
|
||||
" \r\n",
|
||||
"if overwrite:\r\n",
|
||||
" !jupyter-book create \"$book_name\" --content-folder \"$content_folder\" --overwrite\r\n",
|
||||
"else:\r\n",
|
||||
" !jupyter-book create \"$book_name\" --content-folder \"$content_folder\""
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d1a363f0-d854-4466-be87-d01d4c7e51ef",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# Update toc file, book title and clean up the directores\n",
|
||||
"tocFilePath = os.path.join(book_name, \"_data\", \"toc.yml\")\n",
|
||||
"f = open(tocFilePath, \"r\")\n",
|
||||
"title = ''\n",
|
||||
"replacedString = ''\n",
|
||||
"result = f.read()\n",
|
||||
"f.close()\n",
|
||||
"contentFolders = []\n",
|
||||
"\n",
|
||||
"firstLevelUrls = re.findall(r'^(?:\\s+$[\\r\\n]+)+(\\- url: [a-zA-Z0-9\\\\.\\s\\-\\/]+$[\\r\\n]+)', result, re.MULTILINE)\n",
|
||||
"urls = re.findall(r'- url: [a-zA-Z0-9\\\\.\\s\\-\\/]+$', result, re.MULTILINE)\n",
|
||||
"headers = re.findall(r'- header: [a-zA-Z0-9\\\\.\\s-]+$', result, re.MULTILINE)\n",
|
||||
"\n",
|
||||
"try:\n",
|
||||
" if (firstLevelUrls or headers or urls):\n",
|
||||
" if (firstLevelUrls and len(firstLevelUrls) == 1):\n",
|
||||
" for url in firstLevelUrls:\n",
|
||||
" title = url[url.rindex(os.path.sep)+1:].rstrip()\n",
|
||||
" if (not headers):\n",
|
||||
" markdownUrl = urls[len(urls) -1]\n",
|
||||
" title = markdownUrl[markdownUrl.rindex(os.path.sep)+1:].rstrip()\n",
|
||||
" replacedString = \"\\n- title: %s\\n url: /%s\\n not_numbered: true\\n expand_sections: true\\n sections: %s\" % (title, title, url)\n",
|
||||
" result = result.replace(markdownUrl, '')\n",
|
||||
" else:\n",
|
||||
" replacedString = \"\\n- title: %s\\n url: /%s\\n not_numbered: true\\n\" % (title, title)\n",
|
||||
" result = result.replace(url, replacedString)\n",
|
||||
" if (headers):\n",
|
||||
" for header in headers:\n",
|
||||
" title = header[10:].rstrip()\n",
|
||||
" contentFolders.append(title.lower())\n",
|
||||
" filtered = list(filter(lambda x: (\"%s%s%s\" % (os.path.sep, title.lower(), os.path.sep)) in x, urls))\n",
|
||||
" index = urls.index(filtered[len(filtered)-1])\n",
|
||||
" urlValue = urls[index][urls[index].rindex(os.path.sep)+1:].rstrip()\n",
|
||||
" replacedString = \"\\n- title: %s\\n url: /%s/%s\\n not_numbered: true\\n expand_sections: true\\n sections: \" % (title, title.lower(), urlValue)\n",
|
||||
" result = result.replace(header, replacedString)\n",
|
||||
" result = result.replace(urls[index], '')\n",
|
||||
" del urls[index]\n",
|
||||
" if (urls):\n",
|
||||
" for url in urls:\n",
|
||||
" title = url[url.rindex(os.path.sep)+1:].rstrip()\n",
|
||||
" urlValue = title\n",
|
||||
" if (len(contentFolders) > 0):\n",
|
||||
" folders = url[7:].split(os.path.sep)\n",
|
||||
" if (folders[len(folders)-2] in contentFolders):\n",
|
||||
" parentFolder = contentFolders.index(folders[len(folders)-2])\n",
|
||||
" urlValue = \"%s/%s\" % (contentFolders[parentFolder], title)\n",
|
||||
" replacedString = \"\\n - title: %s\\n url: /%s\" % (title, urlValue)\n",
|
||||
" result = result.replace(url, replacedString)\n",
|
||||
" fwrite = open(tocFilePath, \"w\")\n",
|
||||
" fwrite.write(result)\n",
|
||||
" fwrite.close()\n",
|
||||
" else:\n",
|
||||
" raise SystemExit(f'\\n File Name contains unsupported-characters (ex: underscores) by Jupyter Book.\\n')\n",
|
||||
" # Update the Book title in config file\n",
|
||||
" configFilePath = os.path.join(book_name, \"_config.yml\")\n",
|
||||
" f = open(configFilePath, \"r\")\n",
|
||||
" result = f.read()\n",
|
||||
" f.close()\n",
|
||||
" titleLine = re.search(r'title: [a-zA-Z0-9\\\\.\\s\\-\\/]+$', result, re.MULTILINE).group()\n",
|
||||
" title = 'title: %s' % (os.path.splitext(os.path.basename(book_name))[0])\n",
|
||||
" result = result.replace(titleLine, title)\n",
|
||||
" fwrite = open(configFilePath, \"w\")\n",
|
||||
" fwrite.write(result)\n",
|
||||
" fwrite.close()\n",
|
||||
" # cleanup the directories\n",
|
||||
" with os.scandir(book_name) as root_dir:\n",
|
||||
" for path in root_dir:\n",
|
||||
" if path.is_file() and path.name not in ('_config.yml'):\n",
|
||||
" os.remove(path)\n",
|
||||
" if path.is_dir() and path.name not in ('_data', 'content'):\n",
|
||||
" shutil.rmtree(path)\n",
|
||||
"except Exception as e:\n",
|
||||
" print(str(e))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "6124730b-f52e-4103-8dbb-e3a62325fb55",
|
||||
"tags": [
|
||||
"hide_input"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"### <span style=\"color:red\">Issue:</span> generate_toc.py is missing\r\n",
|
||||
"\r\n",
|
||||
"Jupyter Book uses the Table of Contents to define the structure of your book. For example, your chapters, sub-chapters, etc.\r\n",
|
||||
"\r\n",
|
||||
"Need to manually modify the Table of Contents (located here: mybookname/_data/toc.yml) following structure below:\r\n",
|
||||
"\r\n",
|
||||
"```\r\n",
|
||||
"- title: mytitle # Title of chapter or section\r\n",
|
||||
" url: /myurl # URL of section relative to the /content/ folder.\r\n",
|
||||
" sections: # Contains a list of more entries that make up the chapter's sections\r\n",
|
||||
" not_numbered: true # if the section shouldn't have a number in the sidebar\r\n",
|
||||
" (e.g. Introduction or appendices)\r\n",
|
||||
" expand_sections: true # if you'd like the sections of this chapter to always\r\n",
|
||||
" be expanded in the sidebar.\r\n",
|
||||
" external: true # Whether the URL is an external link or points to content in the book\r\n",
|
||||
"```\r\n",
|
||||
"\r\n",
|
||||
"Example from demo book:\r\n",
|
||||
"\r\n",
|
||||
"```\r\n",
|
||||
"- title: Getting started\r\n",
|
||||
" url: /guide/01_overview\r\n",
|
||||
" not_numbered: true\r\n",
|
||||
" expand_sections: true\r\n",
|
||||
" sections:\r\n",
|
||||
" - title: Create your book\r\n",
|
||||
" url: /guide/02_create\r\n",
|
||||
" - title: Build and publish your book\r\n",
|
||||
" url: /guide/03_build\r\n",
|
||||
" - title: FAQ\r\n",
|
||||
" url: /guide/04_faq\r\n",
|
||||
" - title: How-to and advanced topics\r\n",
|
||||
" url: /guide/05_advanced\r\n",
|
||||
"```"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "5439d5e9-6a98-4255-8afa-3c2ba48bfc7e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"## 3. Open your Book!\r\n",
|
||||
"**Run the below cell and click on the link to view your book in Azure Data Studio.**"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "ab100e5c-13f4-484a-9a4a-49bb13cad027"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"import re, os\r\n",
|
||||
"from IPython.display import *\r\n",
|
||||
"if os.name == 'nt':\r\n",
|
||||
" display(HTML(\"<h2><b><a href=\\\"command:bookTreeView.openBook?"\"+str(re.escape(book_name))+\""\\\"><font size=\\\"3\\\">Click here to open your Book in ADS</font></a></b></h2>\"))\r\n",
|
||||
"else:\r\n",
|
||||
" display(HTML(\"<h2><b><a href=\\\"command:bookTreeView.openBook?"\"+str(book_name)+\""\\\"><font size=\\\"3\\\">Click here to open your Book in ADS</font></a></b></h2>\"))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "33d8e1cb-1eec-41ed-a368-1aeef9af62d4",
|
||||
"tags": []
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"source": [
|
||||
"<span style=\"color:red\">**Note**: On clicking the above link, we create a temporary toc.yml file for your convenience.</span>\r\n",
|
||||
"\r\n",
|
||||
" Please update that file inside your book (located at: *YourbookPath*/_data/toc.yml) if you want to further customize your book following \r\n",
|
||||
" the above instructions or https://jupyterbook.org/guide/01-5_tour.html#Table-of-Contents.\r\n",
|
||||
""
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "d193d588-847b-4725-9591-098d0fb24343"
|
||||
}
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"display(HTML(\"<h1><b>That's it!</b></h1><br/><p>You are good to view your book in Azure Data Studio by clicking on the above link.</p>\"))"
|
||||
],
|
||||
"metadata": {
|
||||
"azdata_cell_guid": "bd2fe173-66ce-48b3-8dc3-c4d7560953c8"
|
||||
},
|
||||
"outputs": [],
|
||||
"execution_count": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -149,8 +149,10 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
notebooks.push(notebook);
|
||||
}
|
||||
} else {
|
||||
if (!this._allNotebooks.get(pathToNotebook)) {
|
||||
this._allNotebooks.set(pathToNotebook, notebook);
|
||||
// convert to URI to avoid casing issue with drive letters when getting navigation links
|
||||
let uriToNotebook: vscode.Uri = vscode.Uri.file(pathToNotebook);
|
||||
if (!this._allNotebooks.get(uriToNotebook.fsPath)) {
|
||||
this._allNotebooks.set(uriToNotebook.fsPath, notebook);
|
||||
notebooks.push(notebook);
|
||||
}
|
||||
}
|
||||
@@ -205,8 +207,7 @@ export class BookModel implements azdata.nb.NavigationProvider {
|
||||
}
|
||||
|
||||
getNavigation(uri: vscode.Uri): Thenable<azdata.nb.NavigationResult> {
|
||||
let notebook: BookTreeItem =
|
||||
!this.openAsUntitled ? this._allNotebooks.get(uri.fsPath) : this._allNotebooks.get(path.basename(uri.fsPath));
|
||||
let notebook = !this.openAsUntitled ? this._allNotebooks.get(uri.fsPath) : this._allNotebooks.get(path.basename(uri.fsPath));
|
||||
let result: azdata.nb.NavigationResult;
|
||||
if (notebook) {
|
||||
result = {
|
||||
|
||||
@@ -112,8 +112,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
||||
try {
|
||||
if (this._openAsUntitled) {
|
||||
this.openNotebookAsUntitled(resource);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let doc = await vscode.workspace.openTextDocument(resource);
|
||||
vscode.window.showTextDocument(doc);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as os from 'os';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as path from 'path';
|
||||
|
||||
import { JupyterController } from './jupyter/jupyterController';
|
||||
import { AppContext } from './common/appContext';
|
||||
@@ -28,6 +29,7 @@ let controller: JupyterController;
|
||||
type ChooseCellType = { label: string, id: CellType };
|
||||
|
||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
||||
const createBookPath: string = path.posix.join(extensionContext.extensionPath, 'resources', 'notebooks', 'JupyterBooksCreate.ipynb');
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (bookPath: string, openAsUntitled: boolean, urlToOpen?: string) => openAsUntitled ? untitledBookTreeViewProvider.openBook(bookPath, urlToOpen) : bookTreeViewProvider.openBook(bookPath, urlToOpen)));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource)));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openUntitledNotebook', (resource) => untitledBookTreeViewProvider.openNotebookAsUntitled(resource)));
|
||||
@@ -38,6 +40,16 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => untitledBookTreeViewProvider.searchJupyterBooks()));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.openBook', () => bookTreeViewProvider.openNewBook()));
|
||||
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.createBook', async () => {
|
||||
let untitledFileName: vscode.Uri = vscode.Uri.parse(`untitled:${createBookPath}`);
|
||||
await vscode.workspace.openTextDocument(createBookPath).then((document) => {
|
||||
azdata.nb.showNotebookDocument(untitledFileName, {
|
||||
connectionProfile: null,
|
||||
initialContent: document.getText(),
|
||||
initialDirtyState: false
|
||||
});
|
||||
});
|
||||
}));
|
||||
extensionContext.subscriptions.push(vscode.commands.registerCommand('_notebook.command.new', (context?: azdata.ConnectedContext) => {
|
||||
let connectionProfile: azdata.IConnectionProfile = undefined;
|
||||
if (context && context.connectionProfile) {
|
||||
|
||||
Reference in New Issue
Block a user