Skip to content

Commit

Permalink
feat: added ignore option for upload
Browse files Browse the repository at this point in the history
  • Loading branch information
jakobrosenberg committed Mar 17, 2022
1 parent 25b8d4d commit e61dd1b
Show file tree
Hide file tree
Showing 15 changed files with 134 additions and 34 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,19 @@
"configuration": {
"title": "PyMakr Preview",
"properties": {
"pymakr.ignore": {
"type": "array",
"items": {
"type": "string"
},
"default": [
".vscode",
".gitignore",
".git",
"env",
"venv"
]
},
"pymakr.logLevel": {
"type": "string",
"default": "info",
Expand Down Expand Up @@ -533,6 +546,7 @@
"cheap-watch": "^1.0.4",
"consolite": "^0.3.3",
"micropython-ctl-cont": "^1.13.8",
"picomatch": "^2.3.1",
"prompts": "^2.4.2",
"serialport": "^10.2.2"
}
Expand Down
62 changes: 35 additions & 27 deletions src/Device.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
const { dirname } = require("path");
const { dirname, relative } = require("path");
const { readFileSync, statSync, readdirSync, mkdirSync, createWriteStream } = require("fs");
const { MicroPythonDevice } = require("micropython-ctl-cont");
const { createBlockingProxy } = require("./utils/blockingProxy");
const { waitFor, cherryPick } = require("./utils/misc");
const { waitFor, cherryPick, getNearestPymakrConfig, getNearestPymakrProjectDir } = require("./utils/misc");
const { writable } = require("./utils/store");
const { StateManager } = require("./utils/stateManager");
const picomatch = require("picomatch");

/**
* @typedef {Object} DeviceConfig
Expand Down Expand Up @@ -217,38 +218,45 @@ class Device {
this.pymakr.projectsProvider.refresh();
}

async _uploadFile(file, destination) {
const _destination = `/flash/${destination}`.replace(/\/+/g, "/");
const destinationDir = dirname(_destination);
this.log.traceShort("uploadFile", file, "to", _destination);
const data = Buffer.from(readFileSync(file));
// todo move mkdir logic to micropython-ctl-cont
try {
await this.adapter.mkdir(destinationDir);
} catch (err) {
if (!err.message.match("OSError: \\[Errno 17\\] EEXIST")) throw err;
}
return this.adapter.putFile(_destination, data, { checkIfSimilarBeforeUpload: true });
}

async _uploadDir(dir, destination) {
for (const file of readdirSync(dir)) await this._upload(`${dir}/${file}`, `${destination}/${file}`);
}

_upload(source, destination) {
return statSync(source).isDirectory()
? this._uploadDir(source, destination)
: this._uploadFile(source, destination);
}

/**
* Uploads file or folder to device
* @param {string} source
* @param {string} destination
*/
async upload(source, destination) {
const ignores = this.pymakr.config.get().get("ignore");
const projectDir = getNearestPymakrProjectDir(source);
const pymakrConfig = getNearestPymakrConfig(projectDir);
if (pymakrConfig) ignores.push(...pymakrConfig.py_ignore);

const isIgnore = picomatch(ignores);

const _uploadFile = async (file, destination) => {
const _destination = `/flash/${destination}`.replace(/\/+/g, "/");
const destinationDir = dirname(_destination);
this.log.traceShort("uploadFile", file, "to", _destination);
const data = Buffer.from(readFileSync(file));
// todo move mkdir logic to micropython-ctl-cont
try {
await this.adapter.mkdir(destinationDir);
} catch (err) {
if (!err.message.match("OSError: \\[Errno 17\\] EEXIST")) throw err;
}
return this.adapter.putFile(_destination, data, { checkIfSimilarBeforeUpload: true });
};

const _uploadDir = async (dir, destination) => {
for (const file of readdirSync(dir)) await _upload(`${dir}/${file}`, `${destination}/${file}`);
};

const _upload = (source, destination) => {
const relativePath = relative(projectDir, source).replace(/\\/g, "/");
if (!isIgnore(relativePath))
return statSync(source).isDirectory() ? _uploadDir(source, destination) : _uploadFile(source, destination);
};

this.log.info("upload", source, "to", destination);
await this._upload(source, destination);
await _upload(source, destination);
this.log.info("upload completed");
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class Commands {
*/
createProject: async (uri, config) => {
const defaultConfig = {
py_ignore: ["conf", ".vscode", ".gitignore", ".git", "env", "venv"],
py_ignore: [".vscode", ".gitignore", ".git", "env", "venv"],
};

config = Object.assign(defaultConfig, config);
Expand Down
37 changes: 34 additions & 3 deletions src/utils/misc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { relative, resolve } = require("path");
const { existsSync, readFileSync } = require("fs");
const { relative, resolve, dirname } = require("path");

/**
* creates a function that can only be called once
Expand Down Expand Up @@ -105,8 +106,8 @@ const getNearestParent = (parents) => {
};

/**
* Curried function.Returns the relative path from the nearest provided parent
* @param {string[]} parents
* Curried function. Returns the relative path from the nearest provided parent
* @param {string[]} parents array of file paths
* @returns {(child:string)=>string}
*/
const getRelativeFromNearestParent = (parents) => (child) => {
Expand All @@ -122,6 +123,34 @@ const getRelativeFromNearestParent = (parents) => (child) => {
const getRelativeFromNearestParentPosix = (parents) => (child) =>
getRelativeFromNearestParent(parents)(child).replaceAll("\\", "/");

/**
* reads a json file
* @param {string} path
* @returns {Object.<string|number, any>}
*/
const readJsonFile = (path) => JSON.parse(readFileSync(path, "utf8"));

/**
* resolves the nearest pymakr.conf
* @param {string} path
* @returns {PymakrConfFile}
*/
const getNearestPymakrConfig = (path) => {
const projectPath = getNearestPymakrProjectDir(path);
if (projectPath) return readJsonFile(`${projectPath}/pymakr.conf`);
else return null;
};

const getNearestPymakrProjectDir = (path) => {
const configPath = `${path}/pymakr.conf`;
if (existsSync(configPath)) return path;
else {
const parentDir = dirname(path);
if (parentDir !== path) return getNearestPymakrProjectDir(parentDir);
else return null;
}
};

module.exports = {
once,
coerceArray,
Expand All @@ -134,4 +163,6 @@ module.exports = {
getNearestParent,
getRelativeFromNearestParent,
getRelativeFromNearestParentPosix,
getNearestPymakrConfig,
getNearestPymakrProjectDir,
};
11 changes: 11 additions & 0 deletions src/utils/specs/_sampleProject/pymakr.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"py_ignore": [
"conf",
".vscode",
".gitignore",
".git",
"env",
"venv"
],
"name": "sample-project"
}
11 changes: 11 additions & 0 deletions src/utils/specs/misc.spec.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import {
cherryPick,
getDifference,
getNearestParent,
getNearestPymakrConfig,
getRelativeFromNearestParent,
getRelativeFromNearestParentPosix,
mapEnumsToQuickPick,
once,
} from "../misc.js";

const __dirname = dirname(fileURLToPath(import.meta.url));

test("once functions can only be called once", () => {
let counter = 0;
const callme = once(() => counter++);
Expand Down Expand Up @@ -62,3 +67,9 @@ test("getNearestParent + relative", () => {
assert.equal(getRelativeFromNearestParent(parents)(child), "child\\path");
assert.equal(getRelativeFromNearestParentPosix(parents)(child), "child/path");
});

test("getNearestPymakrConfig", () => {
const path = `${__dirname}/_sampleProject/folder/subfolder/foo`;
const result = getNearestPymakrConfig(path);
assert.equal(result.name, 'sample-project')
});
8 changes: 7 additions & 1 deletion src/utils/vscodeHelpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const vscode = require("vscode");
const { Project } = require("../Project.js");
const { ProjectDeviceTreeItem, ProjectTreeItem } = require("../providers/ProjectsProvider.js");
const { getNearestPymakrProjectDir } = require("./misc.js");

/**
* @param {pymakr} pymakr
Expand All @@ -17,19 +18,24 @@ const createVSCodeHelpers = (pymakr) => {
if (projectRef instanceof ProjectTreeItem) return projectRef.project;
if (projectRef instanceof vscode.Uri) return helpers._getProjectByFsPath(projectRef.fsPath);
if (typeof projectRef === "string") return helpers._getProjectByFsPath(projectRef);
throw new Error("projectRef did not match an accepted type");
},

/**
* Use coerceProject instead
* @param {string} fsPath
* @returns {Project}
*/
_getProjectByFsPath: (fsPath) => pymakr.projectsStore.get().find((project) => project.folder === fsPath),
_getProjectByFsPath: (fsPath) => {
const projectPath = getNearestPymakrProjectDir(fsPath);
return pymakr.projectsStore.get().find((project) => project.folder === projectPath);
},

/**
* @param {vscode.Uri | import('../Project.js').Project} projectOrUri
*/
devicePickerByProject: async (projectOrUri) => {
if (!projectOrUri) throw new Error("projectOrUri can't be undefined");
const project = helpers.coerceProject(projectOrUri);

const answers = await vscode.window.showQuickPick(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I should not be uploaded
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I should not be uploaded
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
should be uploaded
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
should be uploaded
6 changes: 6 additions & 0 deletions test/suite/integration/file-management/_sample/pymakr.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"py_ignore": [
"ignoreme/**"
],
"name": "sample"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ test("file management", async ({ test }) => {
const uri = vscode.Uri.file(__dirname + "/_sample");
await pymakr.commands.upload(uri, device, "/");
const files = await device.adapter.listFiles("/flash", { recursive: false });
assert.equal(files.length, 3);
assert.deepEqual(
files.map((f) => f.filename),
["/flash/main.py", "/flash/sample-file-1.md", "/flash/sample-file-2.md"]
["/flash/includeme", "/flash/main.py", "/flash/pymakr.conf", "/flash/sample-file-1.md", "/flash/sample-file-2.md"]
);
});
test("can erase and provision a device", async () => {
Expand Down
9 changes: 9 additions & 0 deletions types/typedef.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,12 @@
* @typedef {DeviceTreeItem | ProjectDeviceTreeItem} AnyDeviceTreeItem
* @typedef {import('vscode').TreeItem|import('vscode').Uri|string|Project} projectRef
*/

/**
* @typedef {object} PymakrConfFile
* @prop {string[]} py_ignore
* @prop {string} name
* @prop {boolean} ctrl_c_on_connect
* @prop {boolean} reboot_after_upload
* @prop {boolean} safe_boot_on_upload
*/

0 comments on commit e61dd1b

Please sign in to comment.