Rust-based library that enables applications to interact with cloud-based spreadsheet services (e.g., Google Sheets) as immutable, append-only databases. It ensures that once data is committed, it cannot be edited or deleted. Adjustments are made by appending new records, akin to double-entry bookkeeping.
- Immutable Data Entries: Once data is committed, it becomes read-only.
- Append-Only Adjustments: Modifications are handled by appending new records that reference the original entries.
- Cloud Service Integration: Supports integration with services like Google Sheets and Microsoft Excel 365.
- Local File Storage: Save ledger data to CSV files using the
FileAdapter. - User Authentication: Users authenticate via OAuth2 to link their cloud accounts.
- Data Sharing: Users can share their data with others, controlling access permissions.
- Resilient API Calls: Automatically retries transient errors with exponential backoff.
- Ledger Verification: Detects tampering by recomputing row hashes.
- Rust (version 1.74 or higher)
- Google Cloud account with Sheets API enabled
- OAuth2 credentials for Google Sheets API
- Microsoft account with Excel 365 access
Add the following to your Cargo.toml:
[dependencies]
feed-my-ledger = "2.0.0"use feed_my_ledger::core::{Ledger, Record};
fn main() {
let mut ledger = Ledger::default();
let record = Record::new(
"Sample transaction".into(),
"cash".into(),
"revenue".into(),
100.0,
"USD".into(),
None,
None,
vec!["example".into()],
)
.unwrap();
ledger.append(record);
}To work with a live Google Sheet, construct a GoogleSheets4Adapter that
communicates with the official Google Sheets REST API. This approach avoids
extra thirdβparty wrappers and keeps the dependency surface minimal. You may
optionally specify the worksheet name when creating the adapter; otherwise, it
defaults to Ledger:
use feed_my_ledger::cloud_adapters::GoogleSheets4Adapter;
use yup_oauth2::{self, InstalledFlowAuthenticator, InstalledFlowReturnMethod};
async fn example() -> Result<(), Box<dyn std::error::Error>> {
let secret = yup_oauth2::read_application_secret("client_secret.json").await?;
let auth = yup_oauth2::InstalledFlowAuthenticator::builder(
secret,
yup_oauth2::InstalledFlowReturnMethod::Interactive,
)
.build()
.await?;
let mut service = GoogleSheets4Adapter::with_sheet_name(auth, "Custom");
let sheet_id = service.create_sheet("ledger")?;
service.append_row(&sheet_id, vec!["hello".into()])?;
Ok(())
}The crate ships with a small CLI for local experimentation. To add a record and view the stored data:
$ cargo run --bin feed-my-ledger -- add \
--description "Coffee" \
--debit cash --credit expenses \
--amount 3.5 --currency USD
$ cargo run --bin feed-my-ledger -- listPass --local-dir <DIR> to store rows in local CSV files instead of a cloud
service:
$ cargo run --bin feed-my-ledger -- --local-dir ledger_data add \
--description "Coffee" \
--debit cash --credit expenses \
--amount 3.5 --currency USD
$ cargo run --bin feed-my-ledger -- --local-dir ledger_data listAdjust how many rows are sent per request with --batch-size (default 100):
$ cargo run --bin feed-my-ledger -- --batch-size 50 add \
--description "Coffee" \
--debit cash --credit expenses \
--amount 3.5 --currency USDBefore issuing API commands for the first time, authorize the application:
$ cargo run --bin feed-my-ledger -- loginAdjustments reference an existing record by ID:
$ cargo run --bin feed-my-ledger -- adjust \
--id <RECORD_ID> --description "Refund" \
--debit expenses --credit cash \
--amount 3.5 --currency USDShare the active sheet:
$ cargo run --bin feed-my-ledger -- share --email someone@example.comSwitch to a different sheet by URL:
$ cargo run --bin feed-my-ledger -- switch --link "https://docs.google.com/spreadsheets/d/<ID>/edit"Import statements from existing files. Supported formats are csv, qif, ofx, ledger, and json:
$ cargo run --bin feed-my-ledger -- import --format csv --file transactions.csv \
--map-description desc --map-debit debit --map-credit credit \
--map-amount value --map-currency currMapping flags override the default column names when importing CSV files.
If your CSV does not include a currency column, you can provide a default value:
$ cargo run --bin feed-my-ledger -- import --format csv --file transactions.csv --currency USDAll imported rows will use the supplied currency.
For QIF or OFX files with non-standard transaction date formats, provide a custom
--date-format:
$ cargo run --bin feed-my-ledger -- import --format qif --file statement.qif \
--date-format "%Y/%m/%d"Ledger text and JSON formats can also be imported:
$ cargo run --bin feed-my-ledger -- import --format ledger --file statement.ledger
$ cargo run --bin feed-my-ledger -- import --format json --file data.jsonWhen compiled with the bank-api feature, you can download statements directly:
$ cargo run --bin feed-my-ledger -- download --url "https://bank.example.com/statement.ofx"Verify ledger integrity:
$ cargo run --bin feed-my-ledger -- verifyFeedMyLedger looks for a config.toml file in the same directory as the
binary. This file stores your OAuth credentials and the spreadsheet ID used by
the CLI. When using --local-dir, only the sheet ID is persisted and no OAuth
credentials are required.
-
Create the file in your project root:
$ touch config.toml
-
Determine your spreadsheet ID. Open the sheet in your browser and copy the portion of the URL between
/d/and/edit, for examplehttps://docs.google.com/spreadsheets/d/<ID>/edit. -
Create credentials.json
-
Visit the Google Cloud Console and create or select a project.
-
Enable the Google Sheets API for that project.
-
Navigate to APIs & Services > Credentials and choose Create credentials > OAuth client ID. Configure the consent screen if prompted and select Desktop app.
-
Download the resulting JSON file and save it as
credentials.jsonin the project root or another location of your choice. -
Reference this path in the
credentials_pathfield ofconfig.toml.
-
-
Add the following contents, replacing the placeholder values:
[google_sheets] credentials_path = "path_to_credentials.json" spreadsheet_id = "<ID>" # optional: defaults to "Ledger" sheet_name = "Custom" [[budgets]] account = "expenses:food" amount = 200.0 currency = "USD" period = "monthly" [[schedules]] cron = "0 0 1 * *" description = "rent" debit = "expenses:rent" credit = "cash" amount = 1000.0 currency = "USD"
-
Save the file. The CLI reads this configuration on startup and will use the specified
sheet_namefor all ledger operations.
To connect FeedMyLedger to Microsoft Excel 365 you must register an application in Azure and provide workbook credentials.
- Open the Azure Portal and create a new application under Azure Active Directory > App registrations.
- Add the Files.ReadWrite delegated permission for Microsoft Graph and grant consent.
- Generate a client secret under Certificates & secrets and note the secret value as well as the Application (client) ID and Directory (tenant) ID.
- Create or select the workbook you want to use and copy its ID from the share link or via the Graph Explorer.
- Store these details in your
config.toml:[excel_365] tenant_id = "<TENANT_ID>" client_id = "<CLIENT_ID>" client_secret = "<CLIENT_SECRET>" workbook_id = "<WORKBOOK_ID>" # optional: defaults to "Ledger" sheet_name = "Ledger"
- Load this configuration when creating an
Excel365Adapterin your code. The included CLI does not yet read these fields automatically.
cargo testComprehensive documentation is available in the docs directory, covering:
- Module architecture
- Data model specification
- Public API usage
- Authentication integration
- Instructions for extending cloud service support
Contributions are welcome! Please read the CONTRIBUTING for guidelines on how to contribute to this project
This project is licensed under the MIT License - see the LICENSE file for details.
