begin scaffolding for lerna
This commit is contained in:
101
packages/netlify-cms-lib-util/__tests__/path.spec.js
Normal file
101
packages/netlify-cms-lib-util/__tests__/path.spec.js
Normal file
@ -0,0 +1,101 @@
|
||||
import { fileExtensionWithSeparator, fileExtension } from '../pathHelper';
|
||||
|
||||
describe('fileExtensionWithSeparator', () => {
|
||||
it('should return the extension of a file', () => {
|
||||
expect(
|
||||
fileExtensionWithSeparator('index.html')
|
||||
).toEqual(
|
||||
'.html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the extension of a file path', () => {
|
||||
expect(
|
||||
fileExtensionWithSeparator('/src/main/index.html')
|
||||
).toEqual(
|
||||
'.html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the extension of a file path with trailing slash', () => {
|
||||
expect(
|
||||
fileExtensionWithSeparator('/src/main/index.html/')
|
||||
).toEqual(
|
||||
'.html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the extension for an extension with two ..', () => {
|
||||
expect(
|
||||
fileExtensionWithSeparator('/src/main/index..html')
|
||||
).toEqual(
|
||||
'.html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an empty string for the parent path ..', () => {
|
||||
expect(
|
||||
fileExtensionWithSeparator('..')
|
||||
).toEqual(
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an empty string if the file has no extension', () => {
|
||||
expect(
|
||||
fileExtensionWithSeparator('/src/main/index')
|
||||
).toEqual(
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fileExtension', () => {
|
||||
it('should return the extension of a file', () => {
|
||||
expect(
|
||||
fileExtension('index.html')
|
||||
).toEqual(
|
||||
'html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the extension of a file path', () => {
|
||||
expect(
|
||||
fileExtension('/src/main/index.html')
|
||||
).toEqual(
|
||||
'html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the extension of a file path with trailing slash', () => {
|
||||
expect(
|
||||
fileExtension('/src/main/index.html/')
|
||||
).toEqual(
|
||||
'html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the extension for an extension with two ..', () => {
|
||||
expect(
|
||||
fileExtension('/src/main/index..html')
|
||||
).toEqual(
|
||||
'html'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an empty string for the parent path ..', () => {
|
||||
expect(
|
||||
fileExtension('..')
|
||||
).toEqual(
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an empty string if the file has no extension', () => {
|
||||
expect(
|
||||
fileExtension('/src/main/index')
|
||||
).toEqual(
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
7023
packages/netlify-cms-lib-util/package-lock.json
generated
Normal file
7023
packages/netlify-cms-lib-util/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
packages/netlify-cms-lib-util/package.json
Normal file
23
packages/netlify-cms-lib-util/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "netlify-cms-lib-util",
|
||||
"description": "Shared utilities for Netlify CMS.",
|
||||
"version": "2.0.0-alpha.0",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"netlify-cms"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "parcel build src --out-dir ."
|
||||
},
|
||||
"dependencies": {
|
||||
"immutable": "^3.7.6",
|
||||
"localforage": "^1.4.2",
|
||||
"lodash": "^4.13.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"parcel-bundler": "^1.9.4"
|
||||
}
|
||||
}
|
16
packages/netlify-cms-lib-util/src/index.js
Normal file
16
packages/netlify-cms-lib-util/src/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
import localForage from './localForage';
|
||||
import { resolvePath, basename, fileExtensionWithSeparator, fileExtension } from './path';
|
||||
import { filterPromises, resolvePromiseProperties, then } from './promise';
|
||||
import unsentRequest from './unsentRequest';
|
||||
|
||||
export {
|
||||
localForage,
|
||||
resolvePath,
|
||||
basename,
|
||||
fileExtensionWithSeparator,
|
||||
fileExtension,
|
||||
filterPromises,
|
||||
resolvePromiseProperties,
|
||||
then,
|
||||
unsentRequest,
|
||||
};
|
18
packages/netlify-cms-lib-util/src/localForage.js
Normal file
18
packages/netlify-cms-lib-util/src/localForage.js
Normal file
@ -0,0 +1,18 @@
|
||||
import localForage from "localforage";
|
||||
|
||||
function localForageTest() {
|
||||
const testKey = 'localForageTest';
|
||||
localForage.setItem(testKey, {expires: Date.now() + 300000}).then(() => {
|
||||
localForage.removeItem(testKey);
|
||||
}).catch((err) => {
|
||||
if (err.code === 22) {
|
||||
const message = `Unable to set localStorage key. Quota exceeded! Full disk?`;
|
||||
return alert(`${message}\n\n${err}`);
|
||||
}
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
|
||||
localForageTest();
|
||||
|
||||
export default localForage;
|
95
packages/netlify-cms-lib-util/src/path.js
Normal file
95
packages/netlify-cms-lib-util/src/path.js
Normal file
@ -0,0 +1,95 @@
|
||||
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
|
||||
const normalizePath = path => path.replace(/[\\\/]+/g, '/');
|
||||
|
||||
export function resolvePath(path, basePath) { // eslint-disable-line
|
||||
// No path provided, skip
|
||||
if (!path) return null;
|
||||
|
||||
// It's an absolute path.
|
||||
if (absolutePath.test(path)) return path;
|
||||
|
||||
if (path.indexOf('/') === -1) {
|
||||
// It's a single file name, no directories. Prepend public folder
|
||||
return normalizePath(`/${ basePath }/${ path }`);
|
||||
}
|
||||
|
||||
// It's a relative path. Prepend a forward slash.
|
||||
return normalizePath(`/${ path }`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last portion of a path. Similar to the Unix basename command.
|
||||
* @example Usage example
|
||||
* path.basename('/foo/bar/baz/asdf/quux.html')
|
||||
* // returns
|
||||
* 'quux.html'
|
||||
*
|
||||
* path.basename('/foo/bar/baz/asdf/quux.html', '.html')
|
||||
* // returns
|
||||
* 'quux'
|
||||
*/
|
||||
export function basename(p, ext = "") {
|
||||
// Special case: Normalize will modify this to '.'
|
||||
if (p === '') {
|
||||
return p;
|
||||
}
|
||||
// Normalize the string first to remove any weirdness.
|
||||
p = normalizePath(p);
|
||||
// Get the last part of the string.
|
||||
const sections = p.split('/');
|
||||
const lastPart = sections[sections.length - 1];
|
||||
// Special case: If it's empty, then we have a string like so: foo/
|
||||
// Meaning, 'foo' is guaranteed to be a directory.
|
||||
if (lastPart === '' && sections.length > 1) {
|
||||
return sections[sections.length - 2];
|
||||
}
|
||||
// Remove the extension, if need be.
|
||||
if (ext.length > 0) {
|
||||
const lastPartExt = lastPart.substr(lastPart.length - ext.length);
|
||||
if (lastPartExt === ext) {
|
||||
return lastPart.substr(0, lastPart.length - ext.length);
|
||||
}
|
||||
}
|
||||
return lastPart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension of the path, from the last '.' to end of string in the
|
||||
* last portion of the path. If there is no '.' in the last portion of the path
|
||||
* or the first character of it is '.', then it returns an empty string.
|
||||
* @example Usage example
|
||||
* path.fileExtensionWithSeparator('index.html')
|
||||
* // returns
|
||||
* '.html'
|
||||
*/
|
||||
export function fileExtensionWithSeparator(p) {
|
||||
p = normalizePath(p);
|
||||
const sections = p.split('/');
|
||||
p = sections.pop();
|
||||
// Special case: foo/file.ext/ should return '.ext'
|
||||
if (p === '' && sections.length > 0) {
|
||||
p = sections.pop();
|
||||
}
|
||||
if (p === '..') {
|
||||
return '';
|
||||
}
|
||||
const i = p.lastIndexOf('.');
|
||||
if (i === -1 || i === 0) {
|
||||
return '';
|
||||
}
|
||||
return p.substr(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the extension of the path, from after the last '.' to end of string in the
|
||||
* last portion of the path. If there is no '.' in the last portion of the path
|
||||
* or the first character of it is '.', then it returns an empty string.
|
||||
* @example Usage example
|
||||
* path.fileExtension('index.html')
|
||||
* // returns
|
||||
* 'html'
|
||||
*/
|
||||
export function fileExtension(p) {
|
||||
const ext = fileExtensionWithSeparator(p);
|
||||
return ext === '' ? ext : ext.substr(1);
|
||||
}
|
22
packages/netlify-cms-lib-util/src/promise.js
Normal file
22
packages/netlify-cms-lib-util/src/promise.js
Normal file
@ -0,0 +1,22 @@
|
||||
import zipObject from 'lodash/zipObject';
|
||||
|
||||
export const filterPromises = (arr, filter) =>
|
||||
Promise.all(arr.map(entry => filter(entry)))
|
||||
.then(bits => arr.filter(entry => bits.shift()));
|
||||
|
||||
export const resolvePromiseProperties = (obj) => {
|
||||
// Get the keys which represent promises
|
||||
const promiseKeys = Object.keys(obj).filter(
|
||||
key => typeof obj[key].then === "function");
|
||||
|
||||
const promises = promiseKeys.map(key => obj[key]);
|
||||
|
||||
// Resolve all promises
|
||||
return Promise.all(promises)
|
||||
.then(resolvedPromises =>
|
||||
// Return a copy of obj with promises overwritten by their
|
||||
// resolved values
|
||||
Object.assign({}, obj, zipObject(promiseKeys, resolvedPromises)));
|
||||
};
|
||||
|
||||
export const then = fn => p => Promise.resolve(p).then(fn);
|
81
packages/netlify-cms-lib-util/src/unsentRequest.js
Normal file
81
packages/netlify-cms-lib-util/src/unsentRequest.js
Normal file
@ -0,0 +1,81 @@
|
||||
import { fromJS, List, Map } from 'immutable';
|
||||
import curry from 'lodash/curry';
|
||||
import flow from 'lodash/flow';
|
||||
import isString from 'lodash/isString';
|
||||
|
||||
const decodeParams = paramsString => List(paramsString.split("&"))
|
||||
.map(s => List(s.split("=")).map(decodeURIComponent))
|
||||
.update(Map);
|
||||
|
||||
const fromURL = wholeURL => {
|
||||
const [url, allParamsString] = wholeURL.split("?");
|
||||
return Map({ url, ...(allParamsString ? { params: decodeParams(allParamsString) } : {}) });
|
||||
};
|
||||
|
||||
const encodeParams = params => params.entrySeq()
|
||||
.map(([k, v]) => `${ encodeURIComponent(k) }=${ encodeURIComponent(v) }`)
|
||||
.join("&");
|
||||
|
||||
const toURL = req => `${ req.get("url") }${ req.get("params") ? `?${ encodeParams(req.get("params")) }` : "" }`;
|
||||
|
||||
const toFetchArguments = req => [toURL(req), req.delete("url").delete("params").toJS()];
|
||||
|
||||
const maybeRequestArg = req => {
|
||||
if (isString(req)) { return fromURL(req); }
|
||||
if (req) { return fromJS(req); }
|
||||
return Map();
|
||||
};
|
||||
const ensureRequestArg = func => req => func(maybeRequestArg(req));
|
||||
const ensureRequestArg2 = func => (arg, req) => func(arg, maybeRequestArg(req));
|
||||
|
||||
// This actually performs the built request object
|
||||
const performRequest = ensureRequestArg(req => fetch(...toFetchArguments(req)));
|
||||
|
||||
// Each of the following functions takes options and returns another
|
||||
// function that performs the requested action on a request. They each
|
||||
// default to containing an empty object, so you can simply call them
|
||||
// without arguments to generate a request with only those properties.
|
||||
const getCurriedRequestProcessor = flow([ensureRequestArg2, curry]);
|
||||
const getPropSetFunctions = path => [
|
||||
getCurriedRequestProcessor((val, req) => req.setIn(path, val)),
|
||||
getCurriedRequestProcessor((val, req) => (req.getIn(path) ? req : req.setIn(path, val))),
|
||||
];
|
||||
const getPropMergeFunctions = path => [
|
||||
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p=Map()) => p.merge(obj))),
|
||||
getCurriedRequestProcessor((obj, req) => req.updateIn(path, (p=Map()) => Map(obj).merge(p))),
|
||||
];
|
||||
|
||||
const [withMethod, withDefaultMethod] = getPropSetFunctions(["method"]);
|
||||
const [withBody, withDefaultBody] = getPropSetFunctions(["body"]);
|
||||
const [withParams, withDefaultParams] = getPropMergeFunctions(["params"]);
|
||||
const [withHeaders, withDefaultHeaders] = getPropMergeFunctions(["headers"]);
|
||||
|
||||
// withRoot sets a root URL, unless the URL is already absolute
|
||||
const absolutePath = new RegExp('^(?:[a-z]+:)?//', 'i');
|
||||
const withRoot = getCurriedRequestProcessor((root, req) => req.update("url", p => {
|
||||
if (absolutePath.test(p)) { return p; }
|
||||
return (root && p && p[0] !== "/" && root[root.length - 1] !== "/")
|
||||
? `${ root }/${ p }`
|
||||
: `${ root }${ p }`;
|
||||
}));
|
||||
|
||||
// withTimestamp needs no argument and has to run as late as possible,
|
||||
// so it calls `withParams` only when it's actually called with a
|
||||
// request.
|
||||
const withTimestamp = ensureRequestArg(req => withParams({ ts: new Date().getTime() }, req));
|
||||
|
||||
export default {
|
||||
toURL,
|
||||
fromURL,
|
||||
performRequest,
|
||||
withMethod,
|
||||
withDefaultMethod,
|
||||
withBody,
|
||||
withDefaultBody,
|
||||
withHeaders,
|
||||
withDefaultHeaders,
|
||||
withParams,
|
||||
withDefaultParams,
|
||||
withRoot,
|
||||
withTimestamp,
|
||||
};
|
Reference in New Issue
Block a user