Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
tslaton committed Aug 14, 2019
2 parents d9f7b9e + 860877e commit e3644c9
Show file tree
Hide file tree
Showing 212 changed files with 2,820 additions and 3,170 deletions.
11 changes: 6 additions & 5 deletions CONTRIBUTING.md
Expand Up @@ -44,13 +44,14 @@ a keyboard shortcut or automatically on save.
## Submitting a Pull Request Contribution

Generally, an issue should be opened describing a piece of proposed work and the
issues it solves before a pull request is opened.
issues it solves before a pull request is opened.

### Issue Management
Opening an issue lets community members participate in the design discussion,
makes others aware of work being done, and sets the stage for a fruitful community
interaction. A pull request should reference the issue it is addressing. Once the
pull request is merged, the issue related to it will also be closed. If there is

Opening an issue lets community members participate in the design discussion,
makes others aware of work being done, and sets the stage for a fruitful community
interaction. A pull request should reference the issue it is addressing. Once the
pull request is merged, the issue related to it will also be closed. If there is
additional discussion around implemementation the issue may be re-opened. Once 30 days
have passed with no additional discussion, the [lock bot](https://github.com/apps/lock) will lock the issue. If
additional discussion is desired, or if the pull request doesn't fully address the
Expand Down
2 changes: 2 additions & 0 deletions buildutils/package.json
Expand Up @@ -49,6 +49,7 @@
"mini-css-extract-plugin": "~0.6.0",
"package-json": "^6.3.0",
"path": "~0.12.7",
"prettier": "^1.18.2",
"semver": "^6.1.0",
"sort-package-json": "~1.22.1",
"typescript": "~3.5.1",
Expand All @@ -60,6 +61,7 @@
"@types/inquirer": "^6.0.3",
"@types/mini-css-extract-plugin": "^0.2.0",
"@types/node": "^12.0.2",
"@types/prettier": "^1.16.4",
"@types/webpack": "^4.4.32",
"rimraf": "~2.6.2"
}
Expand Down
5 changes: 1 addition & 4 deletions buildutils/src/build.ts
Expand Up @@ -183,10 +183,7 @@ export namespace Build {
},
{
test: /\.svg/,
use: [
{ loader: 'svg-url-loader', options: {} },
{ loader: 'svgo-loader', options: { plugins: [] } }
]
use: [{ loader: 'svg-url-loader', options: { encoding: 'none' } }]
},
{
test: /\.(png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
Expand Down
175 changes: 164 additions & 11 deletions buildutils/src/ensure-package.ts
Expand Up @@ -6,17 +6,48 @@
import * as fs from 'fs-extra';
import * as glob from 'glob';
import * as path from 'path';
import * as prettier from 'prettier';
import * as ts from 'typescript';
import { getDependency } from './get-dependency';
import * as utils from './utils';

const CSS_HEADER = `
const HEADER_TEMPLATE = `
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
/* This file was auto-generated by {{funcName}}() in @jupyterlab/buildutils */
`;

const ICON_IMPORTS_TEMPLATE = `
import { Icon } from './interfaces';
// icon svg import statements
{{iconImportStatements}}
// defaultIcons definition
export namespace IconImports {
export const defaultIcons: ReadonlyArray<Icon.IModel> = [
{{iconModelDeclarations}}
];
}
`;

const ICON_CSS_CLASSES_TEMPLATE = `
/**
* (DEPRECATED) Support for consuming icons as CSS background images
*/
/* Icons urls */
:root {
{{iconCSSUrls}}
}
/* Icon CSS class declarations */
{{iconCSSDeclarations}}
`;

/**
Expand Down Expand Up @@ -138,17 +169,20 @@ export async function ensurePackage(

// Template the CSS index file.
if (cssImports && fs.existsSync(path.join(pkgPath, 'style/base.css'))) {
let cssIndex = CSS_HEADER.trim();
const funcName = 'ensurePackage';
let cssIndexContents = utils.fromTemplate(
HEADER_TEMPLATE,
{ funcName },
{ end: '' }
);
cssImports.forEach(cssImport => {
cssIndex += `\n@import url('~${cssImport}');`;
cssIndexContents += `\n@import url('~${cssImport}');`;
});
cssIndex += "\n\n@import url('./base.css');\n";
const cssPath = path.join(pkgPath, 'style/index.css');
const prev = fs.readFileSync(cssPath, { encoding: 'utf8' });
if (prev !== cssIndex) {
messages.push(`Updated ./${data.style}`);
fs.writeFileSync(cssPath, cssIndex);
}
cssIndexContents += "\n\n@import url('./base.css');\n";

// write out cssIndexContents, if needed
const cssIndexPath = path.join(pkgPath, 'style/index.css');
messages.push(...ensureFile(cssIndexPath, cssIndexContents, false));
}

// Look for unused packages
Expand Down Expand Up @@ -290,6 +324,78 @@ export async function ensurePackage(
return messages;
}

/**
* An extra ensure function just for the @jupyterlab/ui-components package.
* Ensures that the icon svg import statements are synced with the contents
* of ui-components/style/icons.
*
* @param pkgPath - The path to the @jupyterlab/ui-components package.
*
* @returns A list of changes that were made to ensure the package.
*/
export async function ensureUiComponents(pkgPath: string): Promise<string[]> {
const funcName = 'ensureUiComponents';
let messages: string[] = [];

const svgs = glob.sync(path.join(pkgPath, 'style/icons', '**/*.svg'));

/* support for glob import of icon svgs */
const iconSrcDir = path.join(pkgPath, 'src/icon');

// build the per-icon import code
let _iconImportStatements: string[] = [];
let _iconModelDeclarations: string[] = [];
svgs.forEach(svg => {
const name = utils.stem(svg);
const nameCamel = utils.camelCase(name) + 'Svg';
_iconImportStatements.push(
`import ${nameCamel} from '${path.relative(iconSrcDir, svg)}';`
);
_iconModelDeclarations.push(`{ name: '${name}', svg: ${nameCamel} }`);
});
const iconImportStatements = _iconImportStatements.join('\n');
const iconModelDeclarations = _iconModelDeclarations.join(',\n');

// generate the actual contents of the iconImports file
const iconImportsPath = path.join(iconSrcDir, 'iconImports.ts');
const iconImportsContents = utils.fromTemplate(
HEADER_TEMPLATE + ICON_IMPORTS_TEMPLATE,
{ funcName, iconImportStatements, iconModelDeclarations }
);
messages.push(...ensureFile(iconImportsPath, iconImportsContents));

/* support for deprecated icon CSS classes */
const iconCSSDir = path.join(pkgPath, 'style');

// build the per-icon import code
let _iconCSSUrls: string[] = [];
let _iconCSSDeclarations: string[] = [];
svgs.forEach(svg => {
const name = utils.stem(svg);
const urlName = 'jp-icon-' + name;
const className = 'jp-' + utils.camelCase(name, true) + 'Icon';

_iconCSSUrls.push(
`--${urlName}: url('${path.relative(iconCSSDir, svg)}');`
);
_iconCSSDeclarations.push(
`.${className} {background-image: var(--${urlName})}`
);
});
const iconCSSUrls = _iconCSSUrls.join('\n');
const iconCSSDeclarations = _iconCSSDeclarations.join('\n');

// generate the actual contents of the iconCSSClasses file
const iconCSSClassesPath = path.join(iconCSSDir, 'deprecated.css');
const iconCSSClassesContent = utils.fromTemplate(
HEADER_TEMPLATE + ICON_CSS_CLASSES_TEMPLATE,
{ funcName, iconCSSUrls, iconCSSDeclarations }
);
messages.push(...ensureFile(iconCSSClassesPath, iconCSSClassesContent));

return messages;
}

/**
* The options used to ensure a package.
*/
Expand Down Expand Up @@ -340,6 +446,53 @@ export interface IEnsurePackageOptions {
differentVersions?: string[];
}

/**
* Ensure that contents of a file match a supplied string. If they do match,
* do nothing and return an empty array. If they don't match, overwrite the
* file and return an array with an update message.
*
* @param path: The path to the file being checked. The file must exist,
* or else this function does nothing.
*
* @param contents: The desired file contents.
*
* @param prettify: default = true. If true, format the contents with
* `prettier` before comparing/writing. Set to false only if you already
* know your code won't be modified later by the `prettier` git commit hook.
*
* @returns a string array with 0 or 1 messages.
*/
function ensureFile(
path: string,
contents: string,
prettify: boolean = true
): string[] {
let messages: string[] = [];

if (!fs.existsSync(path)) {
// bail
messages.push(
`Tried to ensure the contents of ./${path}, but the file does not exist`
);
return messages;
}

// run the newly generated contents through prettier before comparing
if (prettify) {
contents = prettier.format(contents, { filepath: path, singleQuote: true });
}

const prev = fs.readFileSync(path, {
encoding: 'utf8'
});
if (prev !== contents) {
fs.writeFileSync(path, contents);
messages.push(`Updated ./${path}`);
}

return messages;
}

/**
* Extract the module imports from a TypeScript source file.
*
Expand Down
16 changes: 15 additions & 1 deletion buildutils/src/ensure-repo.ts
Expand Up @@ -13,7 +13,11 @@
*/
import * as path from 'path';
import * as utils from './utils';
import { ensurePackage, IEnsurePackageOptions } from './ensure-package';
import {
ensurePackage,
ensureUiComponents,
IEnsurePackageOptions
} from './ensure-package';

type Dict<T> = { [key: string]: T };

Expand Down Expand Up @@ -341,6 +345,16 @@ export async function ensureIntegrity(): Promise<boolean> {
}
}

// ensure the icon svg imports
pkgMessages = await ensureUiComponents(pkgPaths['@jupyterlab/ui-components']);
if (pkgMessages.length > 0) {
let pkgName = '@jupyterlab/ui-components';
if (!messages[pkgName]) {
messages[pkgName] = [];
}
messages[pkgName] = messages[pkgName].concat(pkgMessages);
}

// Handle the top level package.
let corePath = path.resolve('.', 'package.json');
let coreData: any = utils.readJSONFile(corePath);
Expand Down
88 changes: 88 additions & 0 deletions buildutils/src/utils.ts
Expand Up @@ -114,6 +114,54 @@ export function writeJSONFile(filePath: string, data: any): boolean {
return false;
}

/**
* Simple template substitution for template vars of the form {{name}}
*
* @param templ: the template string.
* Ex: `This header generated by {{funcName}}`
*
* @param subs: an object in which the parameter keys are the template
* variables and the parameter values are the substitutions.
*
* @param options: function options.
*
* @param options.autoindent: default = true. If true, will try to match
* indentation level of {{var}} in substituted template.
*
* @param options.end: default = '\n'. Inserted at the end of
* a template post-substitution and post-trim.
*
* @returns the input template with all {{vars}} substituted, then `.trim`-ed.
*/
export function fromTemplate(
templ: string,
subs: Dict<string>,
options: { autoindent?: boolean; end?: string } = {}
) {
// default options values
const autoindent =
options.autoindent === undefined ? true : options.autoindent;
const end = options.end === undefined ? '\n' : options.end;

Object.keys(subs).forEach(key => {
const val = subs[key];

if (autoindent) {
// try to match the indentation level of the {{var}} in the input template.
templ = templ.split(`{{${key}}}`).reduce((acc, cur) => {
// Regex: 0 or more non-newline whitespaces followed by end of string
let indentRe = acc.match(/([^\S\r\n]*).*$/);
let indent = indentRe ? indentRe[1] : '';
return acc + val.split('\n').join('\n' + indent) + cur;
});
} else {
templ = templ.split(`{{${key}}}`).join(val);
}
});

return templ.trim() + end;
}

/**
*
* Call a command, checking its status.
Expand Down Expand Up @@ -286,3 +334,43 @@ export function ensureUnixPathSep(source: string) {
}
return source.replace(backSlash, '/');
}

/**
* Get the last portion of a path, without its extension (if any).
*
* @param path - The file path.
*
* @returns the last part of the path, sans extension.
*/
export function stem(path: string): string {
return path
.split('\\')
.pop()
.split('/')
.pop()
.split('.')
.shift();
}

/**
* Given a 'snake-case', 'snake_case', or 'snake case' string,
* will return the camel case version: 'snakeCase'.
*
* @param str: the snake-case input string.
*
* @param upper: default = false. If true, the first letter of the
* returned string will be capitalized.
*
* @returns the camel case version of the input string.
*/
export function camelCase(str: string, upper: boolean = false): string {
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+|-+|_+)/g, function(match, index) {
if (+match === 0 || match[0] === '-') {
return '';
} else if (index === 0 && !upper) {
return match.toLowerCase();
} else {
return match.toUpperCase();
}
});
}
1 change: 1 addition & 0 deletions dev_mode/imports.css
Expand Up @@ -31,6 +31,7 @@
@import url('~@jupyterlab/tabmanager-extension/style/index.css');
@import url('~@jupyterlab/terminal-extension/style/index.css');
@import url('~@jupyterlab/tooltip-extension/style/index.css');
@import url('~@jupyterlab/ui-components-extension/style/index.css');
@import url('~@jupyterlab/vdom-extension/style/index.css');
@import url('~@jupyterlab/vega4-extension/style/index.css');
@import url('~@jupyterlab/vega5-extension/style/index.css');

0 comments on commit e3644c9

Please sign in to comment.