Skip to content

Commit

Permalink
feat: add support for withFQBN (#18)
Browse files Browse the repository at this point in the history
This feature allows the creation of an FQBN from the root FQBN,
incorporating configuration options from the CLI while also allowing
for overwrites with previously persisted options appended to the FQBN.

Signed-off-by: dankeboy36 <[email protected]>
  • Loading branch information
dankeboy36 authored Dec 14, 2024
1 parent c22ae58 commit b0eb2ad
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 18 deletions.
74 changes: 71 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ For a deeper understanding of how FQBN works, you should understand the
- [sanitize](#sanitize)
- [toString](#tostring)
- [withConfigOptions](#withconfigoptions)
- [withFQBN](#withfqbn)

## Constructors

Expand Down Expand Up @@ -269,9 +270,9 @@ Adds new configuration options and updates the existing ones. New entries are ap

#### Parameters

| Name | Type | Description |
| :----------------- | :-------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `...configOptions` | [`ConfigOption`](#configoption)[] | Configuration options to update the FQBN. These options are provided by the Arduino CLI through the gRPC equivalent of the [`board --details`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#boarddetailsresponse) command. |
| Name | Type | Description |
| :----------------- | :----------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `...configOptions` | readonly [`ConfigOption`](#configoption)[] | Configuration options to update the FQBN. These options are provided by the Arduino CLI through the gRPC equivalent of the [`board --details`](https://arduino.github.io/arduino-cli/latest/rpc/commands/#boarddetailsresponse) command. |

#### Returns

Expand Down Expand Up @@ -326,6 +327,73 @@ const fqbn3 = fqbn2.withConfigOptions(
assert.deepStrictEqual(fqbn3.options, { o1: 'v2', o2: 'v2' });
```

---

### withFQBN

**withFQBN**(`fqbn`): [`FQBN`](#classesfqbnmd)

Creates an immutable copy of the current Fully Qualified Board Name (FQBN) after updating the custom board configuration options extracted from another FQBN.
New configuration options are added, and existing ones are updated accordingly.
New entries are appended to the end of the FQBN, while the order of the existing options remains unchanged.
If a configuration option is present in the current FQBN but absent in the other, the configuration option will still remain in place.
Note that errors will occur if the FQBNs do not match.

#### Parameters

| Name | Type | Description |
| :----- | :------- | :------------------------------------------- |
| `fqbn` | `string` | the other [FQBN](#classesfqbnmd) to merge in |

#### Returns

[`FQBN`](#classesfqbnmd)

**`Example`**

```ts
// Creates a new FQBN instance by appending the custom board options extracted from the other FQBN to the end of the original FQBN.
const fqbn1 = new FQBN('arduino:samd:mkr1000');
const fqbn2 = fqbn1.withFQBN('arduino:samd:mkr1000:o1=v1');
assert.strictEqual(fqbn2.vendor, 'arduino');
assert.strictEqual(fqbn2.arch, 'samd');
assert.strictEqual(fqbn2.boardId, 'mkr1000');
assert.deepStrictEqual(fqbn2.options, { o1: 'v1' });
```

**`Example`**

```ts
// FQBNs are immutable.
assert.strictEqual(fqbn1.options, undefined);
assert.ok(fqbn2.options);
```

**`Example`**

```ts
// Always maintains the position of existing configuration option keys while updating the selected value.
const fqbn3 = fqbn2.withFQBN('arduino:samd:mkr1000:o2=v2,o1=v2');
assert.deepStrictEqual(fqbn3.options, { o1: 'v2', o2: 'v2' });
assert.deepStrictEqual(fqbn3.toString(), 'arduino:samd:mkr1000:o1=v2,o2=v2');
```

**`Example`**

```ts
// Never removes config options.
const fqbn4 = fqbn3.withFQBN('arduino:samd:mkr1000');
assert.deepStrictEqual(fqbn4.options, { o1: 'v2', o2: 'v2' });
assert.deepStrictEqual(fqbn4.toString(), 'arduino:samd:mkr1000:o1=v2,o2=v2');
```

**`Example`**

```ts
// Errors on mismatching FQBNs.
assert.throws(() => fqbn4.withFQBN('arduino:avr:uno:o1=v3'));
```

<a name="modulesmd"></a>

[fqbn](#readmemd) / Exports
Expand Down
2 changes: 1 addition & 1 deletion src/__examples__/equals.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { FQBN } from '../index';

// the custom board option keys order is insignificant when comparing two FQBNs
// The key order of the custom board configuration options is insignificant when comparing two FQBNs.
assert.ok(
new FQBN('arduino:samd:mkr1000:o1=v1,o2=v2').equals(
new FQBN('arduino:samd:mkr1000:o2=v2,o1=v1')
Expand Down
6 changes: 3 additions & 3 deletions src/__examples__/new.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import assert from 'node:assert/strict';
import { FQBN } from '../index';

// valid FQBN
// Valid FQBN.
const fqbn1 = new FQBN('arduino:samd:mkr1000');
assert.ok(fqbn1);
assert.strictEqual(fqbn1.vendor, 'arduino');
assert.strictEqual(fqbn1.arch, 'samd');
assert.strictEqual(fqbn1.boardId, 'mkr1000');
assert.strictEqual(fqbn1.options, undefined);

// valid FQBN with custom board options
// Valid FQBN with custom board options.
const fqbn2 = new FQBN('arduino:samd:mkr1000:o1=v1');
assert.ok(fqbn2);
assert.strictEqual(fqbn2.vendor, 'arduino');
assert.strictEqual(fqbn2.arch, 'samd');
assert.strictEqual(fqbn2.boardId, 'mkr1000');
assert.deepStrictEqual(fqbn2.options, { o1: 'v1' });

// invalid FQBN
// Invalid FQBN.
assert.throws(() => new FQBN('invalid'));
4 changes: 2 additions & 2 deletions src/__examples__/sanitize.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import assert from 'node:assert/strict';
import { FQBN } from '../index';

// removes the custom board config options
// Removes the custom board config options.
assert.strictEqual(
new FQBN('arduino:samd:mkr1000:o1=v1,o2=v2').sanitize().toString(),
'arduino:samd:mkr1000'
);

// returns the same instance when no custom board options are available
// Returns the same instance when no custom board options are available.
const fqbn = new FQBN('arduino:samd:mkr1000');
assert.ok(fqbn === fqbn.sanitize());
6 changes: 3 additions & 3 deletions src/__examples__/toString.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import assert from 'node:assert/strict';
import { FQBN } from '../index';

// creates the string representation of the FQBN
// Generates the string representation of the FQBN.
assert.strictEqual(
new FQBN('arduino:samd:mkr1000').toString(),
'arduino:samd:mkr1000'
);

// keeps the order of the custom board option keys
// Keeps the order of the custom board option keys.
assert.strictEqual(
new FQBN('arduino:samd:mkr1000:o1=v1').toString(),
'arduino:samd:mkr1000:o1=v1'
);

// can skip the config options from the serialization
// Skips the config options from the serialization.
assert.strictEqual(
new FQBN('arduino:samd:mkr1000:o1=v1').toString(true),
'arduino:samd:mkr1000'
Expand Down
4 changes: 2 additions & 2 deletions src/__examples__/valid.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import assert from 'node:assert/strict';
import { FQBN, valid } from '../index';

// valid FQBN
// Valid FQBN.
assert.ok(valid('arduino:samd:mkr1000') instanceof FQBN);
assert.ok(valid('arduino:samd:mkr1000:o1=v1') instanceof FQBN);

// invalid FQBN
// Invalid FQBN.
assert.strictEqual(valid('invalid'), undefined);
6 changes: 3 additions & 3 deletions src/__examples__/withConfigOptions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { FQBN } from '../index';

// creates a new FQBN instance by appending the custom board options to the end of the FQBN
// Creates a new FQBN instance by appending the custom board options to the end of the original FQBN.
const fqbn1 = new FQBN('arduino:samd:mkr1000');
const fqbn2 = fqbn1.withConfigOptions({
option: 'o1',
Expand All @@ -15,11 +15,11 @@ assert.strictEqual(fqbn2.arch, 'samd');
assert.strictEqual(fqbn2.boardId, 'mkr1000');
assert.deepStrictEqual(fqbn2.options, { o1: 'v1' });

// FQBNs are immutable
// FQBNs are immutable.
assert.strictEqual(fqbn1.options, undefined);
assert.ok(fqbn2.options);

// never changes the position of existing config option keys, but updates the selected value
// Always maintains the position of existing configuration option keys while updating the selected value.
const fqbn3 = fqbn2.withConfigOptions(
{
option: 'o1',
Expand Down
27 changes: 27 additions & 0 deletions src/__examples__/withFQBN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import assert from 'node:assert/strict';
import { FQBN } from '../index';

// Creates a new FQBN instance by appending the custom board options extracted from the other FQBN to the end of the original FQBN.
const fqbn1 = new FQBN('arduino:samd:mkr1000');
const fqbn2 = fqbn1.withFQBN('arduino:samd:mkr1000:o1=v1');
assert.strictEqual(fqbn2.vendor, 'arduino');
assert.strictEqual(fqbn2.arch, 'samd');
assert.strictEqual(fqbn2.boardId, 'mkr1000');
assert.deepStrictEqual(fqbn2.options, { o1: 'v1' });

// FQBNs are immutable.
assert.strictEqual(fqbn1.options, undefined);
assert.ok(fqbn2.options);

// Always maintains the position of existing configuration option keys while updating the selected value.
const fqbn3 = fqbn2.withFQBN('arduino:samd:mkr1000:o2=v2,o1=v2');
assert.deepStrictEqual(fqbn3.options, { o1: 'v2', o2: 'v2' });
assert.deepStrictEqual(fqbn3.toString(), 'arduino:samd:mkr1000:o1=v2,o2=v2');

// Never removes config options.
const fqbn4 = fqbn3.withFQBN('arduino:samd:mkr1000');
assert.deepStrictEqual(fqbn4.options, { o1: 'v2', o2: 'v2' });
assert.deepStrictEqual(fqbn4.toString(), 'arduino:samd:mkr1000:o1=v2,o2=v2');

// Errors on mismatching FQBNs.
assert.throws(() => fqbn4.withFQBN('arduino:avr:uno:o1=v3'));
3 changes: 3 additions & 0 deletions src/__tests__/example.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ describe('examples', function () {
it('withConfigOptions', () =>
assert.doesNotThrow(() => require('../__examples__/withConfigOptions')));

it('withConfigOptions', () =>
assert.doesNotThrow(() => require('../__examples__/withFQBN')));

it('sanitize', () =>
assert.doesNotThrow(() => require('../__examples__/sanitize')));

Expand Down
30 changes: 30 additions & 0 deletions src/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,5 +365,35 @@ describe('fqbn', () => {
assert.strictEqual(fqbn.options, undefined);
});
});

describe('withFQBN', () => {
it('should be noop when other has no config options', () => {
const fqbn = new FQBN('a:b:c:o1=v1');
const actual = fqbn.withFQBN('a:b:c');
assert.ok(fqbn === actual);
assert.deepStrictEqual(actual.options, { o1: 'v1' });
});

it('should not remove config options', () => {
const fqbn = new FQBN('a:b:c:o1=v1');
const actual = fqbn.withFQBN('a:b:c');
assert.ok(fqbn === actual);
assert.deepStrictEqual(actual.options, { o1: 'v1' });
});

it('should not change the order', () => {
const fqbn = new FQBN('a:b:c:o1=v1');
const actual = fqbn.withFQBN('a:b:c:o2=v2,o1=v2');
assert.strictEqual(actual.toString(), 'a:b:c:o1=v2,o2=v2');
});

it('should error on mismatching FQBNs', () => {
const fqbn = new FQBN('a:b:c:o1=v1');
assert.throws(
() => fqbn.withFQBN('a:b:x:o2=v2,o1=v2'),
/ConfigOptionError: .*/
);
});
});
});
});
62 changes: 61 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export class FQBN {
* );
* assert.deepStrictEqual(fqbn3.options, { o1: 'v2', o2: 'v2' });
*/
withConfigOptions(...configOptions: ConfigOption[]): FQBN {
withConfigOptions(...configOptions: readonly ConfigOption[]): FQBN {
if (!configOptions.length) {
return this;
}
Expand Down Expand Up @@ -246,6 +246,66 @@ export class FQBN {
return new FQBN(serialize(vendor, arch, boardId, options));
}

/**
* Creates an immutable copy of the current Fully Qualified Board Name (FQBN) after updating the custom board configuration options extracted from another FQBN.
* New configuration options are added, and existing ones are updated accordingly.
* New entries are appended to the end of the FQBN, while the order of the existing options remains unchanged.
* If a configuration option is present in the current FQBN but absent in the other, the configuration option will still remain in place.
* Note that errors will occur if the FQBNs do not match.
*
* @param fqbn the other {@link FQBN} to merge in
*
* @example
* // Creates a new FQBN instance by appending the custom board options extracted from the other FQBN to the end of the original FQBN.
* const fqbn1 = new FQBN('arduino:samd:mkr1000');
* const fqbn2 = fqbn1.withFQBN('arduino:samd:mkr1000:o1=v1');
* assert.strictEqual(fqbn2.vendor, 'arduino');
* assert.strictEqual(fqbn2.arch, 'samd');
* assert.strictEqual(fqbn2.boardId, 'mkr1000');
* assert.deepStrictEqual(fqbn2.options, { o1: 'v1' });
*
* @example
* // FQBNs are immutable.
* assert.strictEqual(fqbn1.options, undefined);
* assert.ok(fqbn2.options);
*
* @example
* // Always maintains the position of existing configuration option keys while updating the selected value.
* const fqbn3 = fqbn2.withFQBN('arduino:samd:mkr1000:o2=v2,o1=v2');
* assert.deepStrictEqual(fqbn3.options, { o1: 'v2', o2: 'v2' });
* assert.deepStrictEqual(fqbn3.toString(), 'arduino:samd:mkr1000:o1=v2,o2=v2');
*
* @example
* // Never removes config options.
* const fqbn4 = fqbn3.withFQBN('arduino:samd:mkr1000');
* assert.deepStrictEqual(fqbn4.options, { o1: 'v2', o2: 'v2' });
* assert.deepStrictEqual(fqbn4.toString(), 'arduino:samd:mkr1000:o1=v2,o2=v2');
*
* @example
* // Errors on mismatching FQBNs.
* assert.throws(() => fqbn4.withFQBN('arduino:avr:uno:o1=v3'));
*/
withFQBN(fqbn: string): FQBN {
const other = new FQBN(fqbn);
if (!this.sanitize().equals(other.sanitize())) {
throw new ConfigOptionError(
fqbn,
`Mismatching FQBNs. this: ${this.toString()}, other: ${fqbn}`
);
}
return this.withConfigOptions(
...Object.entries(other.options ?? {}).map(([option, value]) => ({
option,
values: [
{
value,
selected: true,
},
],
}))
);
}

/**
* This function returns a new {@link FQBN} instance that does not include any configuration options.
*
Expand Down

0 comments on commit b0eb2ad

Please sign in to comment.