Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Test

on:
push:
pull_request:
types: [opened, synchronize, reopened]

jobs:
test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Verify bundle is up to date
run: npm run build:check
- name: Run unit tests with coverage
run: npm run test:coverage
- uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 14
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
*.sh
*.sh
.vscode/
node_modules/
coverage/
77 changes: 69 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@

# Installing

* Googles guide to adding custom functions: [Google Help](https://developers.google.com/apps-script/guides/sheets/functions#creating_a_custom_function)
* Copy files manually.
* In the ./dist folder there is one file. Only one is required.
* Google's guide to adding custom functions: [Google Help](https://developers.google.com/apps-script/guides/sheets/functions#creating_a_custom_function)
* The `./dist` folder contains the single file used by Google Sheets.
* [CacheFinance.js](https://github.com/demmings/cachefinance/blob/main/dist/CacheFinance.js)
* Caches GOOGLEFINANCE results AND does 3'rd party website lookups when all else fails.
* This file is an amalgamation of the files in the **/src** folder.
* Therefore do NOT use the files in **/src** folder.
* Caches GOOGLEFINANCE results AND does 3rd party website lookups when all else fails.
* This file is built automatically from the modules in the **`/src`** folder.
* Do **not** copy individual files from `/src` into Apps Script.
* The simple approach is to copy and paste **CacheFinance.js**.
* From your sheets Select **Extensions** and then **Apps Script**
* Ensure that Editor is selected. It is the **< >**
Expand All @@ -56,7 +55,7 @@

# Using
* After adding the script, it will require new permissions.
* You need to open the script inside the Google Script editor, go to the Run menu and choose 'testYieldPct' from the dropdown. This will prompt you to authorize the script with the correct permissions.
* To authorize, run any function from the Apps Script editor **Run** menu that calls `CACHEFINANCE` (for example, paste `src/CacheFinanceAppScriptTests.js` into a second script file and run `testYieldPct`), or trigger the function from a sheet cell.
* **Fast REST API Websites**
* For faster stock price lookups when external finance data is used, add the key to **Apps Script** ==> **Project Settings** ==> **Script Properties**
* The free API will have limitied functionality in all cases. They are all throttled and may only provide end of day pricing. You can always get a paid subscription and use that API key if your needs are greater.
Expand Down Expand Up @@ -130,7 +129,7 @@
* ```=CACHEFINANCE("currency:CADEUR", "Price", "get")```
* "SET", "SETBLOCKED" - special case. Sets the preferred site (SET) and blocked site (SETBLOCKED)
* ```=CACHEFINANCE("TSE:CJP", "PRICE", "SET", "YAHOO")```
* "LIST - special case. Displays the ID for each web site lookup supported.
* "LIST" - special case. Displays the ID for each web site lookup supported.
* "?" - special case. Displays all supported special case commands.
* "REMOVE" - special case. Takes the preferred site and moves it to the blocked site.
* ```=CACHEFINANCE("TSE:CJP", "PRICE", "REMOVE")```
Expand Down Expand Up @@ -186,6 +185,68 @@

![Bulk Currency Setup](img/currencyConversions.png)

# Development

This repository includes a Node.js toolchain for building, testing, and validating the Apps Script bundle.

## Project layout

| Path | Purpose |
| --- | --- |
| `src/` | Source modules edited during development |
| `dist/CacheFinance.js` | Single-file Apps Script bundle (generated) |
| `scripts/` | Build utilities (`gas-source.mjs`, `build-cachefinance.mjs`) |
| `test/` | Vitest unit tests |
| `src/GasMocks.js` | Google Apps Script service mocks (tests only) |
| `src/CacheFinanceAppScriptTests.js` | Optional manual GAS test runners (not bundled) |

Each file in `src/` contains a `DEBUG` block at the top for Node.js imports during testing. That block is stripped automatically when building `dist/CacheFinance.js`.

## Prerequisites

* [Node.js](https://nodejs.org/) 22 or later
* npm

## Setup

```bash
npm ci
```

## Build

Generate or refresh `dist/CacheFinance.js` from `src/`:

```bash
npm run build
```

Verify the committed bundle is up to date (used in CI):

```bash
npm run build:check
```

The build only rewrites `dist/CacheFinance.js` when the output changes. The bundle header includes a source hash so unchanged sources produce an identical file.

## Test

```bash
npm test # run unit tests
npm run test:watch # watch mode
npm run test:coverage # tests with coverage report
npm run validate # build:check + coverage (CI command)
```

Coverage reports are written to `coverage/` (HTML report: `coverage/index.html`).

## Contributing changes

1. Edit files in `src/`.
2. Run `npm run build` to regenerate `dist/CacheFinance.js`.
3. Run `npm run validate` before opening a pull request.
4. Commit both `src/` and `dist/` changes.

# Roll Your Own Web Site Scraper

* Why Roll Your Own?
Expand Down
33 changes: 16 additions & 17 deletions dist/CacheFinance.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@

/*
* CacheFinance Apps Script bundle.
* Generated from src/. Do not edit this file directly.
* Source hash: f8c95689ce7c
*/
/**
* Enhancement to GOOGLEFINANCE function for stock/ETF symbols that a) return "#N/A" (temporary or consistently), b) data never available like 'yieldpct' for ETF's.
* @param {string} symbol - stock ticket with exchange (e.g. "NYSEARCA:VOO")
Expand Down Expand Up @@ -519,7 +523,7 @@ class ScriptSettings { // skipcq: JS-0128
try {
this.scriptProperties.setProperty(propertyKey, jsonData);
}
catch (ex) {
catch {
throw new Error("Cache Limit Exceeded. Long cache times have limited storage available. Only cache small tables for long periods.");
}
}
Expand Down Expand Up @@ -613,7 +617,7 @@ class ScriptSettings { // skipcq: JS-0128
try {
propertyValue = JSON.parse(allProperties[key]);
}
catch (e) {
catch {
// A property that is NOT cached by CACHEFINANCE
continue;
}
Expand Down Expand Up @@ -1295,7 +1299,7 @@ class FinanceSiteLookupAnalyzer {
try {
data = siteFunction.siteObject.getInfo(symbol, attribute);
}
catch (ex) {
catch {
Logger.log(`No SITE Object. Symbol=${symbol}. Attrib=${attribute}. Site=${site}`);
}

Expand Down Expand Up @@ -1592,8 +1596,6 @@ class CacheFinanceTestStatus {
}
}



/**
* @classdesc Concrete implementations for each finance website access.
*/
Expand Down Expand Up @@ -2071,7 +2073,7 @@ class YahooApi {
try {
html = UrlFetchApp.fetch(URL).getContentText();
}
catch (ex) {
catch {
return new StockAttributes();
}

Expand Down Expand Up @@ -2133,7 +2135,7 @@ class YahooApi {
stockData.stockName = data.chart.result[0].meta.longName;
}
}
catch (ex) {
catch {
Logger.log(`Failed to parse JSON: ${symbol}`);
}

Expand Down Expand Up @@ -2177,7 +2179,7 @@ class GlobeAndMail {
try {
html = UrlFetchApp.fetch(URL).getContentText();
}
catch (ex) {
catch {
return new StockAttributes();
}

Expand Down Expand Up @@ -2350,7 +2352,7 @@ class FinnHub {
jsonStr = UrlFetchApp.fetch(URL).getContentText();
data = FinnHub.parseResponse(jsonStr, symbol, attribute);
}
catch (ex) {
catch {
return data;
}

Expand Down Expand Up @@ -2451,7 +2453,7 @@ class AlphaVantage {
jsonStr = UrlFetchApp.fetch(URL).getContentText();
data = AlphaVantage.parseResponse(jsonStr, symbol, attribute);
}
catch (ex) {
catch {
return data;
}

Expand Down Expand Up @@ -2556,7 +2558,7 @@ class GoogleWebSiteFinance {
try {
html = UrlFetchApp.fetch(URL).getContentText();
}
catch (ex) {
catch {
return new StockAttributes();
}

Expand Down Expand Up @@ -2755,7 +2757,7 @@ class TwelveData {
jsonStr = UrlFetchApp.fetch(URL).getContentText();
data = TwelveData.parseResponse(jsonStr, symbol, attribute);
}
catch (ex) {
catch {
return data;
}

Expand Down Expand Up @@ -2888,7 +2890,7 @@ class CoinMarket {
jsonStr = UrlFetchApp.fetch(URL).getContentText();
data = CoinMarket.parseResponse(jsonStr, symbol, attribute);
}
catch (ex) {
catch {
return data;
}

Expand Down Expand Up @@ -2980,8 +2982,6 @@ class CoinMarket {
}
}



/**
* @classdesc Multi-purpose functions used within the cache finance custom functions.
*/
Expand Down Expand Up @@ -3484,4 +3484,3 @@ class ThresholdPeriod { // skipcq: JS-0128
this._maxPerPeriod = val;
}
}

Loading