Showing preview only (248K chars total). Download the full file or copy to clipboard to get everything.
Repository: openfare/openfare
Branch: master
Commit: a380f2a71d85
Files: 100
Total size: 223.4 KB
Directory structure:
gitextract_d4f26qpi/
├── .dockerignore
├── .github/
│ └── workflows/
│ └── main.yaml
├── Cargo.toml
├── README.md
├── build/
│ └── musl/
│ ├── Containerfile
│ └── build-release.sh
├── openfare/
│ ├── Cargo.toml
│ ├── LICENSE
│ ├── README.md
│ ├── src/
│ │ ├── command/
│ │ │ ├── config.rs
│ │ │ ├── extensions.rs
│ │ │ ├── lock/
│ │ │ │ ├── common.rs
│ │ │ │ ├── condition.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── plan.rs
│ │ │ │ ├── profile.rs
│ │ │ │ └── validate.rs
│ │ │ ├── mod.rs
│ │ │ ├── pay.rs
│ │ │ ├── price/
│ │ │ │ ├── common.rs
│ │ │ │ ├── format/
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── table.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── package.rs
│ │ │ │ └── project.rs
│ │ │ ├── profile/
│ │ │ │ ├── mod.rs
│ │ │ │ ├── payment_method/
│ │ │ │ │ ├── btc_lightning.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── paypal.rs
│ │ │ │ └── push.rs
│ │ │ └── service/
│ │ │ ├── lnpay.rs
│ │ │ └── mod.rs
│ │ ├── common/
│ │ │ ├── fs/
│ │ │ │ └── mod.rs
│ │ │ ├── git.rs
│ │ │ ├── json.rs
│ │ │ ├── mod.rs
│ │ │ └── url.rs
│ │ ├── config/
│ │ │ ├── core.rs
│ │ │ ├── extensions.rs
│ │ │ ├── mod.rs
│ │ │ ├── paths.rs
│ │ │ ├── profile.rs
│ │ │ └── services/
│ │ │ ├── lnpay.rs
│ │ │ ├── mod.rs
│ │ │ └── portal.rs
│ │ ├── extensions/
│ │ │ ├── common.rs
│ │ │ ├── manage/
│ │ │ │ ├── github.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── process.rs
│ │ │ ├── mod.rs
│ │ │ ├── package.rs
│ │ │ └── project.rs
│ │ ├── handles/
│ │ │ ├── lock.rs
│ │ │ ├── mod.rs
│ │ │ └── profile.rs
│ │ ├── main.rs
│ │ ├── payments.rs
│ │ ├── services/
│ │ │ ├── lnpay.rs
│ │ │ ├── mod.rs
│ │ │ └── portal.rs
│ │ └── setup.rs
│ └── tests/
│ └── integration_tests.rs
├── openfare-lib/
│ ├── Cargo.toml
│ ├── LICENSE
│ └── src/
│ ├── api/
│ │ ├── mod.rs
│ │ └── services/
│ │ ├── basket.rs
│ │ ├── mod.rs
│ │ └── portal/
│ │ ├── basket.rs
│ │ └── mod.rs
│ ├── common/
│ │ ├── fs/
│ │ │ ├── archive.rs
│ │ │ └── mod.rs
│ │ └── mod.rs
│ ├── extension/
│ │ ├── commands/
│ │ │ ├── common.rs
│ │ │ ├── mod.rs
│ │ │ ├── package_dependencies_locks.rs
│ │ │ ├── project_dependencies_locks.rs
│ │ │ └── static_data.rs
│ │ ├── common.rs
│ │ ├── mod.rs
│ │ └── process.rs
│ ├── lib.rs
│ ├── lock/
│ │ ├── mod.rs
│ │ ├── payee.rs
│ │ ├── plan/
│ │ │ ├── conditions/
│ │ │ │ ├── common.rs
│ │ │ │ ├── employees_count.rs
│ │ │ │ ├── expiration.rs
│ │ │ │ ├── for_profit.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── parameters.rs
│ │ │ └── mod.rs
│ │ ├── schema/
│ │ │ ├── mod.rs
│ │ │ └── schema.json
│ │ ├── shares.rs
│ │ └── tests.rs
│ ├── package.rs
│ ├── price/
│ │ ├── conversions.rs
│ │ └── mod.rs
│ └── profile/
│ ├── mod.rs
│ └── payment_methods.rs
└── rust-toolchain
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
target
.git
================================================
FILE: .github/workflows/main.yaml
================================================
name: CI for release tags
on:
push:
tags:
- "v*"
jobs:
once:
name: Create single release for all builds
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create_release.outputs.upload_url }}
steps:
- name: Create a release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
build:
name: OS specific build
needs: once
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
include:
- os: windows-latest
target: x86_64-pc-windows-msvc
- os: macos-latest
target: x86_64-apple-darwin
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
steps:
- uses: actions/checkout@v2
- name: Set package metadata
id: meta
shell: bash
run: |
echo ::set-output name=NAME::openfare
echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/}
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: ${{ matrix.target }}
- name: Build musl
if: "contains(matrix.target, 'x86_64-unknown-linux-musl')"
run: |
alias podman="sudo docker"
mkdir -p ${{ github.workspace }}/target/${{ matrix.target }}/release
./build/musl/build-release.sh ${{ steps.meta.outputs.NAME }} ${{ github.workspace }}/target/${{ matrix.target }}/release/${{ steps.meta.outputs.NAME }}
- name: Build non-musl
if: "!contains(matrix.target, 'x86_64-unknown-linux-musl')"
env:
OPENSSL_STATIC: yes
uses: marcopolo/cargo@master
with:
command: build
args: --release --locked --all-features --verbose --package ${{ steps.meta.outputs.NAME }} --target ${{ matrix.target }}
working-directory: ${{ steps.meta.outputs.NAME }}
toolchain: stable
- name: Archive release assets
id: archive_assets
run: |
7z a -tzip ${{ github.workspace }}/${{ steps.meta.outputs.NAME }}-${{ steps.meta.outputs.VERSION }}-${{ matrix.target }}.zip ${{ github.workspace }}/target/${{ matrix.target }}/release/${{ steps.meta.outputs.NAME }}* ${{ github.workspace }}/openfare/LICENSE
- name: Upload release archive
id: upload_archive
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.once.outputs.upload_url }}
asset_path: ${{ github.workspace }}/${{ steps.meta.outputs.NAME }}-${{ steps.meta.outputs.VERSION }}-${{ matrix.target }}.zip
asset_name: ${{ steps.meta.outputs.NAME }}-${{ steps.meta.outputs.VERSION }}-${{ matrix.target }}.zip
asset_content_type: application/octet-stream
================================================
FILE: Cargo.toml
================================================
[workspace]
members = [
"openfare",
"openfare-lib",
]
[patch.crates-io]
openfare-lib = { path = './openfare-lib' }
================================================
FILE: README.md
================================================
<div align="center">
<a href="https://openfare.dev">
<img src="./assets/header.svg" alt="OpenFare Header" title="OpenFare" align="center" style="max-width: 400px; width: 400px" />
</a>
<br>
<br>
<a href="https://matrix.to/#/#openfare:matrix.org">
<img src="https://img.shields.io/matrix/openfare:matrix.org?label=chat&logo=matrix&style=flat-square" alt="Matrix">
</a>
</div>
<br clear="both"/>
---
**OpenFare is a funding mechanism which is deployable with one commit.**
The goal: facilitate funding the next million software content creators.
OpenFare can be used to fund open source or commercial software at any scale. It is a decentralized protocol which defines how payees can be paid.
OpenFare works by adding a [`OpenFare.lock` file](https://openfare.dev/doc/cli/lock.html) to a software package. The file includes:
* Payment addresses for code contributors (so that developers can receive funds directly).
* A funds split scheme.
The OpenFare [tool](https://openfare.dev/doc/cli/index.html) can then finds lock files from within a software dependency tree and help send payments to contributors.
This system leads to the following advantages:
* Donations span the entire software dependency tree. Critical software which is outside the limelight is supported.
* Micropayment obligations for commercial software can be managed across thousands of software dependencies.
Join the [chat room](https://matrix.to/#/#openfare:matrix.org) to discuss further.
## Summary
* [Introduction](https://openfare.dev/doc/introduction/index.html)
* [Funding FOSS](https://openfare.dev/doc/introduction/funding_foss.html)
* [Micropriced Software](https://openfare.dev/doc/introduction/micropriced_software.html)
* [Get Started](https://openfare.dev/doc/get_started.html)
* [Installation](https://openfare.dev/doc/installation.html)
* [Command Line Tool](https://openfare.dev/doc/cli/index.html)
* [Profile](https://openfare.dev/doc/cli/profile.html)
* [Lock](https://openfare.dev/doc/cli/lock.html)
* [Service](https://openfare.dev/doc/cli/service/index.html)
* [LNPAY](https://openfare.dev/doc/cli/service/lnpay.html)
* [Pay](https://openfare.dev/doc/cli/pay.html)
* [Price](https://openfare.dev/doc/cli/price.html)
* [Config](https://openfare.dev/doc/cli/config.html)
* [Extensions](https://openfare.dev/doc/cli/extensions.html)
* [Concepts](https://openfare.dev/doc/concepts/index.html)
* [Lock File](https://openfare.dev/doc/concepts/lock.html)
* [Payment Services](https://openfare.dev/doc/concepts/services.html)
## Get Started
Find more information about how to get started [here](https://openfare.dev/doc/get_started.html).
#### Create and share your profile to receive funds.
<a href="https://openfare.dev/doc/cli/profile.html">
<img src="./assets/profile.svg" align="center" width="70%"/>
</a>
<br clear="center"/>
<br clear="center"/>
#### Donate to your project's software dependency tree contributors.
<a href="https://openfare.dev/doc/cli/pay.html">
<img src="./assets/donate.svg" align="center" width="70%"/>
</a>
================================================
FILE: build/musl/Containerfile
================================================
FROM docker.io/ekidd/rust-musl-builder:1.57.0
# We need to add the source code to the image because `rust-musl-builder`
# assumes a UID of 1000, but TravisCI has switched to 2000.
ADD --chown=rust:rust . ./
CMD cargo build --release
================================================
FILE: build/musl/build-release.sh
================================================
#!/bin/bash
set -euo pipefail
echo "Building static binaries using ekidd/rust-musl-builder"
podman build -t build-"$1"-image -f build/musl/Containerfile .
podman run -it --name build-"$1" build-"$1"-image
podman cp build-"$1":/home/rust/src/target/x86_64-unknown-linux-musl/release/"$1" "$2"
podman rm build-"$1"
podman rmi build-"$1"-image
================================================
FILE: openfare/Cargo.toml
================================================
[package]
name = "openfare"
version = "0.6.2"
authors = ["rndhouse <rndhouse@protonmail.com>"]
edition = "2021"
homepage = "https://openfare.dev"
repository = "https://github.com/openfare/openfare"
license-file = "LICENSE"
default-run = "openfare"
description = "Micropayment funded software."
[dependencies]
openfare-lib = "0.6.2"
openfare-js = "0.6.2"
openfare-rs = "0.1.1"
anyhow = "1.0.31"
structopt = "0.3.21"
env_logger = "0.8.2"
log = "0.4.8"
regex = "1.3.9"
crossbeam = "0.8.0"
crossbeam-utils = "0.8.1"
maplit = "1.0.2"
dialoguer = "0.10.0"
rust_decimal = "1.20"
jsonschema = "0.15.1"
directories = "3.0.1"
dirs = "3.0.1"
tempdir = "0.3.7"
shellexpand = "2.1.0"
url = { version = "2.1.1", features = ["serde"] }
reqwest = { version = "0.11.0", features = ["blocking", "json"] }
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48"
blake3 = "0.3.7"
uuid = { version = "0.8.2", features = ["v4"] }
bincode = "1.2.1"
tokei = "12.1.2"
prettytable-rs = "0.8.0"
qrcode = { version = "0.12.0", features = ["image"] }
image = "0.23"
open = "2.1.0"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9" }
# Add openssl-sys as a direct dependency so it can be cross compiled to
# x86_64-unknown-linux-musl using the "vendored" feature below
openssl-sys = "0.9.72"
[features]
# Force openssl-sys to statically link in the openssl library. Necessary when
# cross compiling to x86_64-unknown-linux-musl.
vendored = ["openssl-sys/vendored"]
================================================
FILE: openfare/LICENSE
================================================
MIT License
Copyright (c) 2022 OpenFare LTD (UK)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: openfare/README.md
================================================
# OpenFare
> Micropayment funded software.
---
**OpenFare is a funding mechanism which is deployable with one commit.**
The goal: facilitate funding the next million software content creators.
OpenFare can be used to fund open source or commercial software at any scale. It is a decentralized protocol which defines how payees can be paid.
OpenFare works by adding a [`OpenFare.lock` file](https://openfare.dev/doc/cli/lock.html) to a software package. The file includes:
* Payment addresses for code contributors (so that developers can receive funds directly).
* A funds split scheme.
The OpenFare [tool](https://openfare.dev/doc/cli/index.html) can then finds lock files from within a software dependency tree and help send payments to contributors.
This system leads to the following advantages:
* Donations span the entire software dependency tree. Critical software which is outside the limelight is supported.
* Micropayment obligations for commercial software can be managed across thousands of software dependencies.
Join the [chat room](https://matrix.to/#/#openfare:matrix.org) to discuss further.
## Summary
* [Introduction](https://openfare.dev/doc/introduction/index.html)
* [Funding FOSS](https://openfare.dev/doc/introduction/funding_foss.html)
* [Micropriced Software](https://openfare.dev/doc/introduction/micropriced_software.html)
* [Get Started](https://openfare.dev/doc/get_started.html)
* [Installation](https://openfare.dev/doc/installation.html)
* [Command Line Tool](https://openfare.dev/doc/cli/index.html)
* [Profile](https://openfare.dev/doc/cli/profile.html)
* [Lock](https://openfare.dev/doc/cli/lock.html)
* [Service](https://openfare.dev/doc/cli/service/index.html)
* [LNPAY](https://openfare.dev/doc/cli/service/lnpay.html)
* [Pay](https://openfare.dev/doc/cli/pay.html)
* [Price](https://openfare.dev/doc/cli/price.html)
* [Config](https://openfare.dev/doc/cli/config.html)
* [Extensions](https://openfare.dev/doc/cli/extensions.html)
* [Concepts](https://openfare.dev/doc/concepts/index.html)
* [Lock File](https://openfare.dev/doc/concepts/lock.html)
* [Payment Services](https://openfare.dev/doc/concepts/services.html)
================================================
FILE: openfare/src/command/config.rs
================================================
use crate::common::fs::FileStore;
use crate::common::json::{Get, Set};
use anyhow::Result;
use structopt::{self, StructOpt};
#[derive(Debug, Clone, StructOpt)]
pub struct Arguments {
// SUBCOMMANDS
#[structopt(subcommand)]
commands: Subcommands,
}
#[derive(Debug, StructOpt, Clone)]
enum Subcommands {
/// Set config field values.
Set(SetArguments),
/// Show config field values.
Show(ShowArguments),
}
pub fn run_command(args: &Arguments) -> Result<()> {
match &args.commands {
Subcommands::Set(args) => {
set(&args)?;
}
Subcommands::Show(args) => {
show(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct SetArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: String,
/// Field value.
pub value: String,
}
fn set(args: &SetArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
config.set(&args.path, &args.value)?;
config.dump()?;
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct ShowArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: Option<String>,
}
fn show(args: &ShowArguments) -> Result<()> {
let config = crate::config::Config::load()?;
let value = config.get(&args.path)?;
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(())
}
================================================
FILE: openfare/src/command/extensions.rs
================================================
use anyhow::{format_err, Result};
use structopt::{self, StructOpt};
use crate::common::fs::FileStore;
use crate::common;
use crate::extensions;
#[derive(Debug, Clone, StructOpt)]
pub struct Arguments {
// SUBCOMMANDS
#[structopt(subcommand)]
commands: Subcommands,
}
#[derive(Debug, StructOpt, Clone)]
enum Subcommands {
/// Add and enable extension.
Add(AddArguments),
/// Disable and delete extension.
Remove(RemoveArguments),
/// Enable extension.
Enable(EnableArguments),
/// Disable extension without deleting.
Disable(DisableArguments),
/// Show extensions.
Show(ShowArguments),
}
pub fn run_command(args: &Arguments) -> Result<()> {
match &args.commands {
Subcommands::Add(args) => {
add(&args)?;
}
Subcommands::Remove(args) => {
remove(&args)?;
}
Subcommands::Enable(args) => {
enable(&args)?;
}
Subcommands::Disable(args) => {
disable(&args)?;
}
Subcommands::Show(args) => {
show(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddArguments {
/// Extension name, release archive URL, or GitHub repository URL.
#[structopt(name = "name-or-url")]
pub name_or_url: String,
// Optional installation directory path.
#[structopt(long = "install-directory", short = "d", name = "install-directory")]
pub install_directory: Option<String>,
}
fn add(args: &AddArguments) -> Result<()> {
log::info!("Adding extension using argument: {}", args.name_or_url);
let bin_directory = match &args.install_directory {
Some(install_directory) => {
let path = shellexpand::full(&install_directory)?.to_string();
std::path::PathBuf::from(&path)
}
None => common::fs::ensure_extensions_bin_directory()?.ok_or(format_err!(
"Failed to find suitable directory for installing extension binary.\n\
Please specify install directory with argument: --install-directory"
))?,
};
if !is_install_directory_discoverable(&bin_directory)? {
println!(
"WARNING: install directory is not the default \
or not included in the PATH environment variable.\n\
OpenFare may not be able to find the extension."
)
}
log::info!("Using extension bin directory: {}", bin_directory.display());
let extension_name = if args.name_or_url.contains("/") {
log::debug!("Identified argument as URL.");
let url = args.name_or_url.clone();
if let Some(url) = try_parse_user_url(&url)? {
log::debug!("Sanitized URL: {}", url);
extensions::manage::add_from_url(&url, &bin_directory)?
} else {
return Err(format_err!("Failed to parse URL: {}", url));
}
} else {
log::debug!("Identified argument as name.");
let name = extensions::manage::clean_name(&args.name_or_url);
let url = get_url_from_name(&name)?;
extensions::manage::add_from_url(&url, &bin_directory)?
};
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
println!("Added extension: {}", extension_name);
Ok(())
}
/// Returns true if OpenFare can discover extensions stored in given directory.
fn is_install_directory_discoverable(directory: &std::path::PathBuf) -> Result<bool> {
if is_directory_in_path_env(&directory)? {
Ok(true)
} else {
match common::fs::get_extensions_default_directory() {
Some(default_directory) => Ok(&default_directory == directory),
None => Ok(false),
}
}
}
fn is_directory_in_path_env(directory: &std::path::PathBuf) -> Result<bool> {
let env_path_value =
std::env::var_os("PATH").ok_or(format_err!("Failed to read PATH environment variable."))?;
let paths = std::env::split_paths(&env_path_value);
Ok(paths.into_iter().any(|path| path == *directory))
}
fn try_parse_user_url(url: &str) -> Result<Option<url::Url>> {
let url = if !url.starts_with("https://") && !url.starts_with("http://") {
String::from("https://") + url
} else {
url.to_string()
};
let url = match url.strip_suffix(".git") {
Some(url) => url.into(),
None => url,
};
Ok(url::Url::parse(&url).ok())
}
fn get_url_from_name(name: &str) -> Result<url::Url> {
Ok(url::Url::parse(
format!("https://github.com/openfare/openfare-{name}", name = name).as_str(),
)?)
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {
/// Extension name.
pub name: String,
}
fn remove(args: &RemoveArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
let name = extensions::manage::clean_name(&args.name);
extensions::manage::remove(&name)?;
println!("Removed extension: {}", name);
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct EnableArguments {
/// Extension name.
pub name: String,
}
fn enable(args: &EnableArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
let name = extensions::manage::clean_name(&args.name);
let all_extension_names = extensions::manage::get_all_names(&config)?;
if !all_extension_names.contains(&name) {
return Err(format_err!(
"Failed to find extension. Known extensions: {}",
all_extension_names
.into_iter()
.collect::<Vec<_>>()
.join(", ")
));
}
extensions::manage::enable(&name, &mut config)?;
println!("Enabled extension: {}", name);
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct DisableArguments {
/// Extension name.
pub name: String,
}
fn disable(args: &DisableArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
let name = extensions::manage::clean_name(&args.name);
let all_extension_names = extensions::manage::get_all_names(&config)?;
if !all_extension_names.contains(&name) {
return Err(format_err!(
"Failed to find extension. Known extensions: {}",
all_extension_names
.into_iter()
.collect::<Vec<_>>()
.join(", ")
));
}
extensions::manage::disable(&name, &mut config)?;
println!("Disabled extension: {}", name);
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct ShowArguments {}
fn show(_args: &ShowArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
for name in extensions::manage::get_all_names(&config)? {
println!("{}", name);
}
Ok(())
}
================================================
FILE: openfare/src/command/lock/common.rs
================================================
use structopt::{self, StructOpt};
#[derive(Debug, StructOpt, Clone)]
pub struct LockFilePathArg {
/// Lock file path. Searches in current working directory if not given.
#[structopt(name = "lock-file-path", long)]
pub path: Option<std::path::PathBuf>,
}
================================================
FILE: openfare/src/command/lock/condition.rs
================================================
use anyhow::Result;
use structopt::{self, StructOpt};
use super::common;
#[derive(Debug, StructOpt, Clone)]
pub struct ConditionArguments {
/// For profit organization.
#[structopt(long = "for-profit")]
pub for_profit: bool,
/// Expiration date. Example: "2022-01-31"
#[structopt(long)]
pub expiration: Option<String>,
/// Number of employees in the organization. Example: "> 100"
#[structopt(name = "employees-count", long = "employees-count")]
pub employees_count: Option<String>,
}
impl std::convert::TryInto<openfare_lib::lock::plan::conditions::Conditions>
for ConditionArguments
{
type Error = anyhow::Error;
fn try_into(self) -> Result<openfare_lib::lock::plan::conditions::Conditions, Self::Error> {
let for_profit = if self.for_profit {
Some(openfare_lib::lock::plan::conditions::ForProfit::new())
} else {
None
};
let expiration = if let Some(expiration) = &self.expiration {
Some(openfare_lib::lock::plan::conditions::Expiration::try_from(
expiration.as_str(),
)?)
} else {
None
};
let employees_count = if let Some(employees_count) = &self.employees_count {
Some(
openfare_lib::lock::plan::conditions::EmployeesCount::try_from(
employees_count.as_str(),
)?,
)
} else {
None
};
Ok(openfare_lib::lock::plan::conditions::Conditions {
for_profit,
expiration,
employees_count,
})
}
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddArguments {
/// Payment plan ID(s). All plans included if unset.
#[structopt(long, short)]
pub id: Vec<usize>,
#[structopt(flatten)]
pub conditions: ConditionArguments,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn add(args: &AddArguments) -> Result<()> {
let conditions = args.conditions.clone().try_into()?;
let plan_ids = args
.id
.iter()
.map(|id| id.to_string())
.collect::<std::collections::BTreeSet<_>>();
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
for (_plan_id, plan) in lock_handle
.lock
.plans
.iter_mut()
.filter(|(id, _plan)| plan_ids.contains(id.as_str()) || plan_ids.is_empty())
{
plan.conditions.set_some(&conditions);
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {
/// Payment plan ID(s). All plans included if unset.
#[structopt(long, short)]
pub id: Vec<usize>,
/// For profit organization.
#[structopt(long = "for-profit")]
pub for_profit: bool,
/// Number of employees in the organization.
#[structopt(long = "employees-count")]
pub employees_count: bool,
/// Expiration date.
#[structopt(long)]
pub expiration: bool,
/// Remove all conditions.
#[structopt(long, short)]
pub all: bool,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn remove(args: &RemoveArguments) -> Result<()> {
let plan_ids = args
.id
.iter()
.map(|id| id.to_string())
.collect::<std::collections::BTreeSet<_>>();
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
for (_plan_id, plan) in lock_handle
.lock
.plans
.iter_mut()
.filter(|(id, _plan)| plan_ids.contains(id.as_str()) || plan_ids.is_empty())
{
if args.for_profit || args.all {
plan.conditions.for_profit = None;
}
if args.expiration || args.all {
plan.conditions.expiration = None;
}
if args.employees_count || args.all {
plan.conditions.employees_count = None;
}
}
Ok(())
}
================================================
FILE: openfare/src/command/lock/mod.rs
================================================
use crate::common::json::{Get, Set};
use anyhow::Result;
use structopt::{self, StructOpt};
mod common;
mod condition;
mod plan;
mod profile;
mod validate;
#[derive(Debug, Clone, StructOpt)]
pub struct Arguments {
// SUBCOMMANDS
#[structopt(subcommand)]
commands: Subcommands,
}
#[derive(Debug, StructOpt, Clone)]
enum Subcommands {
/// New lock file.
New(NewArguments),
/// Add plan, profile, etc.
Add(AddArguments),
/// Set lock field.
Set(SetArguments),
/// Remove plan, profile, condition, etc.
Remove(RemoveSubcommands),
/// Update payee profiles.
Update(UpdateArguments),
/// Show lock fields.
Show(ShowArguments),
/// Check if a lock file contains errors
Validate(validate::Arguments),
}
pub fn run_command(args: &Arguments) -> Result<()> {
match &args.commands {
Subcommands::New(args) => {
new(&args)?;
}
Subcommands::Add(args) => {
add(&args)?;
}
Subcommands::Set(args) => {
set(&args)?;
}
Subcommands::Remove(args) => {
remove(&args)?;
}
Subcommands::Update(args) => {
update(&args)?;
}
Subcommands::Show(args) => {
show(&args)?;
}
Subcommands::Validate(args) => {
validate::run_command(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct NewArguments {
/// Overwrite existing file.
#[structopt(long, short)]
pub force: bool,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
fn new(args: &NewArguments) -> Result<()> {
let lock_handle = crate::handles::LockHandle::new(&args.lock_file_args.path, args.force)?;
println!("Created new lock file: {}", lock_handle.path().display());
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub enum AddArguments {
/// Add plan.
Plan(plan::AddArguments),
/// Add payee profile to payment plan(s).
Profile(profile::AddArguments),
/// Add condition(s) to plan(s).
Condition(condition::AddArguments),
}
fn add(args: &AddArguments) -> Result<()> {
match &args {
AddArguments::Plan(args) => {
plan::add(&args)?;
}
AddArguments::Profile(args) => {
profile::add(&args)?;
}
AddArguments::Condition(args) => {
condition::add(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub enum RemoveSubcommands {
/// Remove plan.
Plan(plan::RemoveArguments),
/// Remove payee profile from payment plan(s).
Profile(profile::RemoveArguments),
/// Remove condition(s) from payment plan(s).
Condition(condition::RemoveArguments),
}
fn remove(subcommand: &RemoveSubcommands) -> Result<()> {
match subcommand {
RemoveSubcommands::Plan(args) => {
plan::remove(&args)?;
}
RemoveSubcommands::Profile(args) => {
profile::remove(&args)?;
}
RemoveSubcommands::Condition(args) => {
condition::remove(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct UpdateArguments {
#[structopt(flatten)]
pub profile: profile::UpdateArguments,
}
fn update(args: &UpdateArguments) -> Result<()> {
profile::update(&args.profile)?;
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct SetArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: String,
/// Field value.
pub value: String,
}
fn set(args: &SetArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&None)?;
lock_handle.set(&args.path, &args.value)?;
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct ShowArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: Option<String>,
}
fn show(args: &ShowArguments) -> Result<()> {
let lock_handle = crate::handles::LockHandle::load(&None)?;
let value = lock_handle.get(&args.path)?;
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(())
}
================================================
FILE: openfare/src/command/lock/plan.rs
================================================
use super::common;
use anyhow::Result;
use structopt::{self, StructOpt};
#[derive(Debug, StructOpt, Clone)]
pub enum AddArguments {
/// Add plan for compulsory fees.
Compulsory(AddCompulsoryArguments),
/// Add plan for voluntary donations.
Voluntary(AddVoluntaryArguments),
}
pub fn add(subcommand: &AddArguments) -> Result<()> {
match subcommand {
AddArguments::Compulsory(args) => {
add_compulsory(&args)?;
}
AddArguments::Voluntary(args) => {
add_voluntary(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddCompulsoryArguments {
/// Payment plan price. Example: "50.2USD"
#[structopt(long, short)]
pub price: String,
#[structopt(flatten)]
pub conditions: super::condition::ConditionArguments,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn add_compulsory(args: &AddCompulsoryArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
let id = openfare_lib::lock::plan::next_id(&lock_handle.lock.plans)?;
let conditions = args.conditions.clone().try_into()?;
let plan = openfare_lib::lock::plan::Plan {
r#type: openfare_lib::lock::plan::PlanType::Compulsory,
conditions,
price: Some(args.price.parse().expect("parse price")),
};
lock_handle.lock.plans.insert(id.clone(), plan.clone());
println!(
"{}",
serde_json::to_string_pretty(&maplit::btreemap! {id.clone() => plan.clone()})?
);
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddVoluntaryArguments {
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn add_voluntary(args: &AddVoluntaryArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
let id = openfare_lib::lock::plan::next_id(&lock_handle.lock.plans)?;
let plan = openfare_lib::lock::plan::Plan {
r#type: openfare_lib::lock::plan::PlanType::Voluntary,
conditions: openfare_lib::lock::plan::conditions::Conditions::default(),
price: None,
};
lock_handle.lock.plans.insert(id.clone(), plan.clone());
println!(
"{}",
serde_json::to_string_pretty(&maplit::btreemap! {id.clone() => plan.clone()})?
);
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {
/// Payment plan ID(s). All plans included if unset.
#[structopt(long, short)]
pub id: Vec<u64>,
}
pub fn remove(args: &RemoveArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&None)?;
let ids = if args.id.is_empty() {
lock_handle
.lock
.plans
.iter()
.map(|(plan_id, _)| plan_id.clone())
.collect::<Vec<_>>()
} else {
args.id.iter().map(|id| id.to_string()).collect::<Vec<_>>()
};
// Ensure all IDs present for atomic removal.
for id in &ids {
if !lock_handle.lock.plans.contains_key(id) {
println!("Failed to find plan: {}", id);
}
}
for id in &ids {
lock_handle.lock.plans.remove(id);
}
if !ids.is_empty() {
println!("Plans removed (ID): {}", ids.join(", "));
}
Ok(())
}
================================================
FILE: openfare/src/command/lock/profile.rs
================================================
use super::common;
use crate::common::fs::FileStore;
use anyhow::{format_err, Result};
use std::str::FromStr;
use structopt::{self, StructOpt};
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddArguments {
/// Number of payment split shares.
#[structopt(long, short)]
pub shares: u64,
/// Payee profile URL.
#[structopt(long, short)]
pub url: Option<String>,
/// Payee label.
#[structopt(long, short)]
pub label: Option<String>,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn add(args: &AddArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
if lock_handle.lock.plans.is_empty() {
return Err(format_err!(
"No payment plan found. Add a plan first: openfare lock add plan"
));
}
let profile = get_profile(&args.url)?;
// Profile already included in lock as payee. Add shares only.
if let Some((label, _payee)) =
openfare_lib::lock::payee::get_lock_payee(&(*profile).clone(), &lock_handle.lock.payees)
{
add_shares(&label, args.shares, &mut lock_handle);
return Ok(());
}
let payee = get_payee(&profile);
// Derive unique label.
let label = get_label(&args.label, &profile)?;
let label = if lock_handle.lock.payees.contains_key(&label) {
openfare_lib::lock::payee::unique_label(&label, &payee)
} else {
label.clone()
};
lock_handle.lock.payees.insert(label.clone(), payee);
add_shares(&label, args.shares, &mut lock_handle);
Ok(())
}
fn get_payee(profile: &crate::handles::ProfileHandle) -> openfare_lib::lock::payee::Payee {
let url = if let Some(from_url_status) = &profile.from_url_status {
match from_url_status.method {
crate::handles::profile::FromUrlMethod::Git => {
// Prefer HTTPS git URL in lock.
from_url_status
.url
.git
.as_https_url()
.or(Some(from_url_status.url.original.clone()))
}
crate::handles::profile::FromUrlMethod::HttpGetJson => {
Some(from_url_status.url.to_string())
}
}
} else {
None
};
openfare_lib::lock::payee::Payee {
url,
profile: (**profile).clone(),
}
}
fn get_profile(url: &Option<String>) -> Result<crate::handles::ProfileHandle> {
// Parse URL argument.
let url = if let Some(url) = &url {
Some(crate::common::url::Url::from_str(&url)?)
} else {
None
};
Ok(if let Some(url) = &url {
crate::handles::ProfileHandle::from_url(&url)?
} else {
crate::handles::ProfileHandle::load()?
})
}
/// Get payee label from label argument or URL.
fn get_label(
label_arg: &Option<String>,
profile: &crate::handles::ProfileHandle,
) -> Result<String> {
let url_label = if let Some(from_url_status) = &profile.from_url_status {
match from_url_status.method {
crate::handles::profile::FromUrlMethod::Git => from_url_status.url.git.username.clone(),
crate::handles::profile::FromUrlMethod::HttpGetJson => None,
}
} else {
None
};
let label = label_arg.clone().or(url_label);
if let Some(label) = label {
Ok(label)
} else {
Err(anyhow::format_err!(
"Failed to derive payee label from known URLs, please specify using --label."
))
}
}
fn add_shares(payee_label: &str, payee_shares: u64, lock_handle: &mut crate::handles::LockHandle) {
if let Some(shares) = &mut lock_handle.lock.shares {
shares.insert(payee_label.to_string(), payee_shares);
} else {
lock_handle.lock.shares = Some(maplit::btreemap! {payee_label.to_string() => payee_shares})
}
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {
/// Payee profile label(s). If unset, removes payee corresponding to local profile only.
#[structopt(name = "label")]
pub labels: Vec<String>,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn remove(args: &RemoveArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
// If no payee labels given, use local payee label.
let labels = if args.labels.is_empty() {
get_lock_local_payee(&lock_handle)?
.and_then(|label| Some(vec![label]))
.unwrap_or_default()
} else {
args.labels.clone()
};
for label in labels {
// Remove from payees.
lock_handle.lock.payees.remove(&label);
// Remove from shares.
if let Some(shares) = &mut lock_handle.lock.shares {
shares.remove(&label);
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct UpdateArguments {
/// Payee profile label(s). If unset, updates all payee profiles.
#[structopt(name = "label")]
pub labels: Vec<String>,
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn update(args: &UpdateArguments) -> Result<()> {
let mut lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
let local_payee_label = get_lock_local_payee(&lock_handle)?;
for (label, payee) in &mut lock_handle.lock.payees {
// Skip label if labels given as argument and label wasn't given.
if !args.labels.is_empty() && !args.labels.contains(label) {
continue;
}
// Update local profile with local data rather than via URL.
if let Some(local_payee_label) = &local_payee_label {
if label == local_payee_label {
let profile = crate::handles::ProfileHandle::load()?;
let latest_profile = (*profile).clone();
if payee.profile != latest_profile {
log::debug!("Updating local profile: {}", label);
payee.profile = latest_profile;
}
continue;
}
}
if let Some(url) = &payee.url {
let url = crate::common::url::Url::from_str(&url)?;
let latest_profile = (*crate::handles::ProfileHandle::from_url(&url)?).clone();
if payee.profile != latest_profile {
log::debug!("Updating profile: {}", label);
payee.profile = latest_profile;
}
}
}
Ok(())
}
/// Returns the payee label associated with the local profile.
fn get_lock_local_payee(
lock_handle: &crate::handles::LockHandle,
) -> Result<Option<openfare_lib::lock::payee::Label>> {
let profile = crate::handles::ProfileHandle::load()?;
let label = if let Some((label, _)) =
openfare_lib::lock::payee::get_lock_payee(&*profile, &lock_handle.lock.payees)
{
Some(label.clone())
} else {
None
};
Ok(label)
}
================================================
FILE: openfare/src/command/lock/validate.rs
================================================
use super::common;
use anyhow::Result;
use structopt::{self, StructOpt};
#[derive(Debug, StructOpt, Clone)]
pub struct Arguments {
/// Optional path to lock file.
#[structopt(flatten)]
pub lock_file_args: common::LockFilePathArg,
}
pub fn run_command(args: &Arguments) -> Result<()> {
let lock_handle = crate::handles::LockHandle::load(&args.lock_file_args.path)?;
match lock_handle.lock.validate() {
Ok(_) => {
println!("Lock file is valid.")
}
Err(e) => {
println!("Error validating lock file: {}", e)
}
};
Ok(())
}
================================================
FILE: openfare/src/command/mod.rs
================================================
use anyhow::Result;
use structopt::{self, StructOpt};
mod config;
mod extensions;
mod lock;
mod pay;
mod price;
mod profile;
mod service;
#[derive(Debug, StructOpt, Clone)]
pub enum Command {
/// Price a package and its dependencies.
Price(price::Arguments),
/// Pay fees or donations to project dependencies.
Pay(pay::Arguments),
/// Manage profile.
Profile(profile::Arguments),
/// Manage lock file.
Lock(lock::Arguments),
/// Manage payment services.
Service(service::Arguments),
/// Configure settings.
Config(config::Arguments),
/// Manage extensions.
Extensions(extensions::Arguments),
}
pub fn run_command(command: Command, extension_args: &Vec<String>) -> Result<()> {
crate::setup::ensure()?;
log::info!("Running command: {:?}", command);
match command {
Command::Price(args) => {
price::run_command(&args, &extension_args)?;
}
Command::Pay(args) => {
pay::run_command(&args, &extension_args)?;
}
Command::Profile(args) => {
profile::run_command(&args)?;
}
Command::Lock(args) => {
lock::run_command(&args)?;
}
Command::Service(args) => {
service::run_command(&args)?;
}
Command::Config(args) => {
config::run_command(&args)?;
}
Command::Extensions(args) => {
extensions::run_command(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(about = "Micropayment funded software.")]
#[structopt(global_setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(global_setting = structopt::clap::AppSettings::DeriveDisplayOrder)]
pub struct Opts {
#[structopt(subcommand)]
pub command: Command,
}
================================================
FILE: openfare/src/command/pay.rs
================================================
use crate::common::fs::FileStore;
use anyhow::Result;
use structopt::{self, StructOpt};
use crate::extensions;
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct Arguments {
/// Donation. Example: 1usd or 200sats
pub donation: Option<openfare_lib::price::Price>,
/// Specify payment service.
#[structopt(long, short)]
pub service: Option<crate::services::Service>,
/// Set specified payment service as default.
#[structopt(long, short)]
pub default: bool,
/// Specify an extension for handling the package and its dependencies.
/// Example values: py, js, rs
#[structopt(long = "extension", short = "e", name = "name")]
pub extension_names: Option<Vec<String>>,
}
pub fn run_command(args: &Arguments, extension_args: &Vec<String>) -> Result<()> {
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
if args.default {
if let Some(service) = &args.service {
config.services.default = service.clone();
config.dump()?;
}
}
let extensions = extensions::manage::from_names_arg(&args.extension_names, &config)?;
let all_extension_locks = get_locks(&extensions, &extension_args)?;
let mut items = vec![];
for extension_locks in all_extension_locks {
if !openfare_lib::lock::plan::conditions::parameters::check_set(
&extension_locks.package_locks.conditions_metadata(),
&mut config.profile.parameters,
)? {
config.dump()?;
}
let basket_items = get_basket_items(&extension_locks, &config)?;
items.extend(basket_items);
}
crate::services::pay(&args.donation, &items, &args.service, &config)?;
Ok(())
}
pub struct ExtensionLocks {
pub extension_name: String,
pub package_locks: openfare_lib::package::PackageLocks,
}
/// Get dependencies locks from all extensions.
pub fn get_locks(
extensions: &Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
) -> Result<Vec<ExtensionLocks>> {
let working_directory = std::env::current_dir()?;
log::debug!("Current working directory: {}", working_directory.display());
let extensions_results = crate::extensions::project::dependencies_locks(
&working_directory,
&extensions,
&extension_args,
)?;
let extensions_results =
crate::extensions::common::filter_results(&extensions, &extensions_results)?;
let all_extension_locks: Vec<_> = extensions_results
.iter()
.map(|(extension, extension_result)| {
let package_locks = extension_result
.package_locks
.filter_valid_dependencies_locks();
ExtensionLocks {
extension_name: extension.name(),
package_locks,
}
})
.collect();
Ok(all_extension_locks)
}
/// Get applicable payment plans from dependencies packages.
pub fn get_basket_items(
extension_locks: &ExtensionLocks,
config: &crate::config::Config,
) -> Result<Vec<openfare_lib::api::services::basket::Item>> {
let mut basket_items: Vec<_> = vec![];
for (package, lock) in &extension_locks.package_locks.dependencies_locks {
let lock = match lock {
Some(lock) => lock,
None => continue,
};
let plans =
openfare_lib::lock::plan::filter_applicable(&lock.plans, &config.profile.parameters)?;
if plans.is_empty() {
// Skip package if no applicable plans found.
continue;
}
let total_price = plans
.iter()
.map(|(_id, plan)| plan.price.clone().unwrap_or_default())
.sum();
let item = openfare_lib::api::services::basket::Item {
package: package.clone(),
extension_name: extension_locks.extension_name.to_string(),
plans,
total_price,
payees: lock.payees.clone(),
shares: lock.shares.clone(),
};
basket_items.push(item);
}
Ok(basket_items)
}
================================================
FILE: openfare/src/command/price/common.rs
================================================
use anyhow::Result;
pub fn get_report(
package_locks: &openfare_lib::package::PackageLocks,
config: &crate::config::Config,
) -> Result<Option<PriceReport>> {
log::info!("Generating price report for package and it's dependencies.");
// Handle primary package first.
let mut package_reports = vec![];
if let Some(primary_package) = &package_locks.primary_package {
let primary_package_price_report = get_package_price_report(
&primary_package,
&package_locks.primary_package_lock,
&config,
)?;
package_reports.push(primary_package_price_report);
}
for (package, package_lock) in &package_locks.dependencies_locks {
let price_report = get_package_price_report(&package, &package_lock, &config)?;
package_reports.push(price_report);
}
log::info!(
"Number of package price reports generated: {}",
package_reports.len()
);
if package_reports.is_empty() {
return Ok(None);
}
let total_price = package_reports
.iter()
.map(|r| r.price_quantity.unwrap_or(rust_decimal::Decimal::from(0)))
.sum::<rust_decimal::Decimal>();
let price_report = PriceReport {
package_reports: package_reports,
price: openfare_lib::price::Price {
quantity: rust_decimal::Decimal::from(total_price),
currency: config.core.preferred_currency.clone(),
},
};
Ok(Some(price_report))
}
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct PriceReport {
pub package_reports: Vec<PackagePriceReport>,
pub price: openfare_lib::price::Price,
}
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub struct PackagePriceReport {
pub package: openfare_lib::package::Package,
pub plan_id: Option<openfare_lib::lock::plan::Id>,
pub price_quantity: Option<openfare_lib::price::Quantity>,
pub notes: Vec<String>,
}
/// Given a package's OpenFare lock, create a corresponding price report.
fn get_package_price_report(
package: &openfare_lib::package::Package,
package_lock: &Option<openfare_lib::lock::Lock>,
config: &crate::config::Config,
) -> Result<PackagePriceReport> {
let package_lock = match package_lock {
Some(lock) => lock,
None => {
return Ok(PackagePriceReport {
package: package.clone(),
plan_id: None,
price_quantity: None,
notes: vec![],
});
}
};
let applicable_plans = openfare_lib::lock::plan::filter_applicable(
&package_lock.plans,
&config.profile.parameters,
)?;
Ok(
if let Some((plan_id, plan)) = select_plan(&applicable_plans) {
PackagePriceReport {
package: package.clone(),
plan_id: Some((*plan_id).clone()),
price_quantity: Some(if let Some(price) = &plan.price {
price.quantity
} else {
rust_decimal::Decimal::from(0)
}),
notes: vec![],
}
} else {
PackagePriceReport {
package: package.clone(),
plan_id: None,
price_quantity: Some(rust_decimal::Decimal::from(0)),
notes: vec![],
}
},
)
}
fn select_plan<'a>(
applicable_plans: &'a openfare_lib::lock::plan::Plans,
) -> Option<(
&'a openfare_lib::lock::plan::Id,
&'a openfare_lib::lock::plan::Plan,
)> {
let max_price: rust_decimal::Decimal = applicable_plans
.iter()
.map(|(_, plan)| {
if let Some(price) = &plan.price {
price.quantity
} else {
rust_decimal::Decimal::from(0)
}
})
.sum();
applicable_plans
.iter()
.filter(|(_, plan)| {
if let Some(price) = &plan.price {
price.quantity == max_price
} else {
false
}
})
.next()
}
================================================
FILE: openfare/src/command/price/format/mod.rs
================================================
use super::common;
use anyhow::Result;
mod table;
pub enum Format {
Table,
}
pub fn print(
report: &common::PriceReport,
format: &Format,
first_row_separate: bool,
) -> Result<()> {
match format {
Format::Table => {
let table = table::get(&report, first_row_separate)?;
table.printstd();
}
}
Ok(())
}
================================================
FILE: openfare/src/command/price/format/table.rs
================================================
use anyhow::Result;
use prettytable::{self, cell};
/// Generates and returns a table from a given price report.
pub fn get(
price_report: &super::common::PriceReport,
first_row_separate: bool,
) -> Result<prettytable::Table> {
let mut table = prettytable::Table::new();
table.set_titles(prettytable::row![c =>
"name",
"version",
format!("price ({})", price_report.price.currency.to_string()),
"notes",
]);
table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR);
let mut reports_iter = price_report.package_reports.iter();
if first_row_separate {
if let Some(report) = reports_iter.next() {
let row = get_row(&report);
table.add_row(row);
table.add_row(prettytable::row![c => "", "", "", ""]);
}
}
for report in reports_iter {
let row = get_row(&report);
table.add_row(row);
}
Ok(table)
}
fn get_row(report: &super::common::PackagePriceReport) -> prettytable::Row {
let price = report
.price_quantity
.map(|p| p.to_string())
.unwrap_or("-".to_string());
prettytable::Row::new(vec![
prettytable::Cell::new_align(&report.package.name, prettytable::format::Alignment::LEFT),
prettytable::Cell::new_align(
&report.package.version,
prettytable::format::Alignment::LEFT,
),
prettytable::Cell::new_align(&price, prettytable::format::Alignment::CENTER),
])
}
================================================
FILE: openfare/src/command/price/mod.rs
================================================
use crate::common::fs::FileStore;
use anyhow::Result;
use structopt::{self, StructOpt};
use crate::extensions;
mod common;
mod format;
mod package;
mod project;
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct Arguments {
/// Package name.
#[structopt(name = "package-name")]
pub package_name: Option<String>,
/// Package version.
#[structopt(name = "package-version", requires("package-name"))]
pub package_version: Option<String>,
/// Specify an extension for handling the package and its dependencies.
/// Example values: py, js, rs
#[structopt(long = "extension", short = "e", name = "name")]
pub extension_names: Option<Vec<String>>,
}
pub fn run_command(args: &Arguments, extension_args: &Vec<String>) -> Result<()> {
let mut config = crate::config::Config::load()?;
extensions::manage::update_config(&mut config)?;
let extensions = extensions::manage::from_names_arg(&args.extension_names, &config)?;
match &args.package_name {
Some(package_name) => {
let extensions_results = package::query_extensions(
&package_name,
&args.package_version.as_deref(),
&extensions,
&extension_args,
)?;
for (_extension, result) in extensions_results {
if !openfare_lib::lock::plan::conditions::parameters::check_set(
&result.package_locks.conditions_metadata(),
&mut config.profile.parameters,
)? {
config.dump()?;
}
}
package::price(
&package_name,
&args.package_version.as_deref(),
&extensions,
&extension_args,
&config,
)?;
}
None => {
let extensions_results = project::query_extensions(&extensions, &extension_args)?;
for (_extension, result) in extensions_results {
if !openfare_lib::lock::plan::conditions::parameters::check_set(
&result.package_locks.conditions_metadata(),
&mut config.profile.parameters,
)? {
config.dump()?;
}
}
project::price(&extensions, &extension_args, &config)?;
}
}
Ok(())
}
================================================
FILE: openfare/src/command/price/package.rs
================================================
use super::{common, format};
use crate::extensions;
use anyhow::Result;
/// Prints a price report for a specific package and its dependencies.
pub fn price(
package_name: &str,
package_version: &Option<&str>,
extensions: &Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
config: &crate::config::Config,
) -> Result<()> {
let extensions_results = extensions::package::dependencies_locks(
&package_name,
&package_version,
&extensions,
&extension_args,
)?;
let mut locks_found = false;
for (_extension, extension_result) in
extensions::common::filter_results(&extensions, &extensions_results)?
{
locks_found |= extension_result.package_locks.has_locks();
if let Some(price_report) = common::get_report(&extension_result.package_locks, &config)? {
println!("Registry: {}", extension_result.registry_host_name);
println!("Total: {}", price_report.price);
format::print(&price_report, &format::Format::Table, true)?;
println!("");
}
}
if !locks_found {
println!("No OpenFare lock file found.")
}
Ok(())
}
pub fn query_extensions<'a>(
package_name: &str,
package_version: &Option<&str>,
extensions: &'a Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
) -> Result<
Vec<(
&'a Box<dyn openfare_lib::extension::Extension>,
openfare_lib::extension::commands::package_dependencies_locks::PackageDependenciesLocks,
)>,
> {
let extensions_results = extensions::package::dependencies_locks(
&package_name,
&package_version,
&extensions,
&extension_args,
)?;
Ok(
extensions::common::filter_results(&extensions, &extensions_results)?
.into_iter()
.map(|(extension, result)| (extension, result.to_owned()))
.collect(),
)
}
================================================
FILE: openfare/src/command/price/project.rs
================================================
use anyhow::Result;
use super::{common, format};
use crate::extensions;
/// Returns price information for a project and its dependencies.
pub fn price(
extensions: &Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
config: &crate::config::Config,
) -> Result<()> {
let working_directory = std::env::current_dir()?;
log::debug!("Current working directory: {}", working_directory.display());
let extensions_results =
extensions::project::dependencies_locks(&working_directory, &extensions, &extension_args)?;
let mut locks_found = false;
for (_extension, extension_result) in
extensions::common::filter_results(&extensions, &extensions_results)?
{
locks_found |= extension_result.package_locks.has_locks();
if let Some(price_report) = common::get_report(&extension_result.package_locks, &config)? {
println!(
"Project: {path}",
path = extension_result.project_path.display()
);
println!("Total: {}", price_report.price);
format::print(&price_report, &format::Format::Table, true)?;
println!("");
}
}
if !locks_found {
println!("No OpenFare lock files found.")
}
Ok(())
}
pub fn query_extensions<'a>(
extensions: &'a Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
) -> Result<
Vec<(
&'a Box<dyn openfare_lib::extension::Extension>,
openfare_lib::extension::commands::project_dependencies_locks::ProjectDependenciesLocks,
)>,
> {
let working_directory = std::env::current_dir()?;
log::debug!("Current working directory: {}", working_directory.display());
let extensions_results =
extensions::project::dependencies_locks(&working_directory, &extensions, &extension_args)?;
Ok(
extensions::common::filter_results(&extensions, &extensions_results)?
.into_iter()
.map(|(extension, result)| (extension, result.to_owned()))
.collect(),
)
}
================================================
FILE: openfare/src/command/profile/mod.rs
================================================
use crate::common::fs::FileStore;
use crate::common::json::{Get, Set};
use anyhow::Result;
use structopt::{self, StructOpt};
mod payment_method;
mod push;
#[derive(Debug, Clone, StructOpt)]
pub struct Arguments {
// SUBCOMMANDS
#[structopt(subcommand)]
commands: Subcommands,
}
#[derive(Debug, StructOpt, Clone)]
enum Subcommands {
/// Add payment method, etc.
Add(AddArguments),
/// Set payment method fields, etc.
Set(SetArguments),
/// Show profile fields.
Show(ShowArguments),
/// Remove payment method, etc.
Remove(RemoveArguments),
/// Push profile to git repository URL.
Push(push::Arguments),
}
pub fn run_command(args: &Arguments) -> Result<()> {
match &args.commands {
Subcommands::Add(args) => {
add(&args)?;
}
Subcommands::Set(args) => {
set(&args)?;
}
Subcommands::Show(args) => {
show(&args)?;
}
Subcommands::Remove(args) => {
remove(&args)?;
}
Subcommands::Push(args) => {
push::push(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub enum AddArguments {
/// Add payment method.
#[structopt(name = "payment-method")]
PaymentMethod(payment_method::AddSubcommands),
}
fn add(args: &AddArguments) -> Result<()> {
match &args {
AddArguments::PaymentMethod(args) => {
payment_method::add(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct SetArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: String,
/// Field value.
pub value: String,
}
fn set(args: &SetArguments) -> Result<()> {
let mut profile = crate::handles::ProfileHandle::load()?;
profile.set(&args.path, &args.value)?;
profile.dump()?;
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
enum RemoveArguments {
/// Remove payment method.
#[structopt(name = "payment-method")]
PaymentMethod(payment_method::RemoveSubcommands),
}
fn remove(args: &RemoveArguments) -> Result<()> {
match args {
RemoveArguments::PaymentMethod(args) => {
payment_method::remove(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct ShowArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: Option<String>,
}
fn show(args: &ShowArguments) -> Result<()> {
let profile = crate::handles::ProfileHandle::load()?;
let value = profile.get(&args.path)?;
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(())
}
================================================
FILE: openfare/src/command/profile/payment_method/btc_lightning.rs
================================================
use crate::common::fs::FileStore;
use anyhow::Result;
use structopt::{self, StructOpt};
type Method = openfare_lib::profile::payment_methods::BtcLightning;
const METHOD_TYPE: openfare_lib::profile::payment_methods::Methods =
openfare_lib::profile::payment_methods::Methods::BtcLightning;
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddArguments {
/// Wallet LNURL. Example: lnurl1dp69e...
pub lnurl: Option<String>,
/// Optional fallback: keysend node public key.
pub keysend: Option<String>,
/// Setup using payment service.
#[structopt(long, short, required_unless = "lnurl")]
pub service: Option<crate::services::Service>,
}
pub fn add(
args: &AddArguments,
) -> Result<Box<dyn openfare_lib::profile::payment_methods::PaymentMethod>> {
let config = crate::config::Config::load()?;
let lnurl = if let Some(lnurl) = &args.lnurl {
lnurl.clone()
} else {
if let Some(service) = &args.service {
crate::services::lnurl_receive_address(&service, &config)?
.ok_or(anyhow::format_err!("Service failed to derive LNURL."))?
} else {
return Err(anyhow::format_err!(
"Service must be specified if LNURL not given."
));
}
};
let payment_method = Method::new(&lnurl, &args.keysend)?;
let payment_method = Box::new(payment_method.clone())
as Box<dyn openfare_lib::profile::payment_methods::PaymentMethod>;
let mut profile = crate::handles::ProfileHandle::load()?;
(*profile).set_payment_method(&payment_method)?;
profile.dump()?;
Ok(payment_method)
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {}
pub fn remove(_args: &RemoveArguments) -> Result<()> {
let mut profile = crate::handles::ProfileHandle::load()?;
(*profile).remove_payment_method(&METHOD_TYPE)?;
profile.dump()?;
Ok(())
}
================================================
FILE: openfare/src/command/profile/payment_method/mod.rs
================================================
use anyhow::Result;
use structopt::{self, StructOpt};
mod btc_lightning;
mod paypal;
#[derive(Debug, StructOpt, Clone)]
pub enum AddSubcommands {
/// Set payment method: PayPal.
#[structopt(name = "paypal")]
PayPal(paypal::AddArguments),
/// Set payment method: Bitcoin Lightning Network.
#[structopt(name = "btc-ln")]
BtcLightning(btc_lightning::AddArguments),
}
pub fn add(subcommand: &AddSubcommands) -> Result<()> {
let payment_method = match subcommand {
AddSubcommands::PayPal(args) => paypal::add(&args)?,
AddSubcommands::BtcLightning(args) => btc_lightning::add(&args)?,
};
#[derive(serde::Serialize)]
struct Report {
#[serde(flatten)]
x: std::collections::BTreeMap<
openfare_lib::profile::payment_methods::Methods,
serde_json::Value,
>,
}
let report = Report {
x: maplit::btreemap! {payment_method.method() => payment_method.to_serde_json_value()?},
};
println!(
"Added payment-method:\n{}",
serde_json::to_string_pretty(&report)?
);
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub enum RemoveSubcommands {
/// Remove payment method: PayPal.
#[structopt(name = "paypal")]
PayPal(paypal::RemoveArguments),
/// Remove payment method: Bitcoin Lightning Network.
#[structopt(name = "btc-ln")]
BtcLightning(btc_lightning::RemoveArguments),
}
pub fn remove(subcommand: &RemoveSubcommands) -> Result<()> {
match subcommand {
RemoveSubcommands::PayPal(args) => {
paypal::remove(&args)?;
}
RemoveSubcommands::BtcLightning(args) => {
btc_lightning::remove(&args)?;
}
}
Ok(())
}
================================================
FILE: openfare/src/command/profile/payment_method/paypal.rs
================================================
use crate::common::fs::FileStore;
use anyhow::Result;
use structopt::{self, StructOpt};
type Method = openfare_lib::profile::payment_methods::PayPal;
const METHOD_TYPE: openfare_lib::profile::payment_methods::Methods =
openfare_lib::profile::payment_methods::Methods::PayPal;
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddArguments {
/// PayPal ID.
#[structopt(long)]
pub id: Option<String>,
/// Email.
#[structopt(long, required_unless = "id")]
pub email: Option<String>,
}
pub fn add(
args: &AddArguments,
) -> Result<Box<dyn openfare_lib::profile::payment_methods::PaymentMethod>> {
let payment_method = Method::new(&args.id, &args.email)?;
let payment_method =
Box::new(payment_method) as Box<dyn openfare_lib::profile::payment_methods::PaymentMethod>;
let mut profile = crate::handles::ProfileHandle::load()?;
(*profile).set_payment_method(&payment_method)?;
profile.dump()?;
Ok(payment_method)
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {}
pub fn remove(_args: &RemoveArguments) -> Result<()> {
let mut profile = crate::handles::ProfileHandle::load()?;
(*profile).remove_payment_method(&METHOD_TYPE)?;
profile.dump()?;
Ok(())
}
================================================
FILE: openfare/src/command/profile/push.rs
================================================
use crate::common::fs::FileStore;
use anyhow::Result;
use std::str::FromStr;
use structopt::{self, StructOpt};
#[derive(Debug, StructOpt, Clone)]
pub struct Arguments {
/// Git repository URL. If unset, uses current profile URL.
url: Option<String>,
}
pub fn push(args: &Arguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
let url = if let Some(url) = args.url.clone() {
let url = crate::common::url::Url::from_str(&url)?;
if config.profile.url.is_none() {
config.profile.url = Some(url.original.clone());
}
url
} else {
if let Some(url) = config.profile.url.clone() {
crate::common::url::Url::from_str(&url)?
} else {
return Err(anyhow::format_err!("Failed to find URL. Not found in config profile and not given as argument.\nSet profile URL using: openfare config set profile.url <url>"));
}
};
let tmp_dir = tempdir::TempDir::new("openfare_profile_push")?;
let tmp_directory_path = tmp_dir.path().to_path_buf();
log::debug!("Using temp directory: {}", tmp_directory_path.display());
clone_repo(&url, &tmp_directory_path)?;
let profile = crate::handles::ProfileHandle::load()?;
let remote_profile = (*profile).clone().into();
insert_profile(&remote_profile, &tmp_directory_path)?;
push_repo(&tmp_directory_path)?;
println!("Profile push complete.");
// Write updated config profile to disk only if all git operations succeed.
config.dump()?;
Ok(())
}
fn clone_repo(
url: &crate::common::url::Url,
tmp_directory_path: &std::path::PathBuf,
) -> Result<()> {
let url = if let Some(url) = url.git.as_ssh_url() {
url
} else {
url.original.clone()
};
println!("Cloning repository for writing profile: {}", url.as_str());
crate::common::git::run_command(
vec!["clone", "--depth", "1", url.as_str(), "."],
&tmp_directory_path,
)?;
Ok(())
}
fn push_repo(tmp_directory_path: &std::path::PathBuf) -> Result<()> {
println!("Pushing local clone.");
crate::common::git::run_command(vec!["add", "-A"], &tmp_directory_path)?;
crate::common::git::commit("Update OpenFare profile.", &tmp_directory_path)?;
crate::common::git::run_command(vec!["push", "origin"], &tmp_directory_path)?;
Ok(())
}
fn insert_profile(
remote_profile: &openfare_lib::profile::RemoteProfile,
directory_path: &std::path::PathBuf,
) -> Result<()> {
let path = directory_path.join(openfare_lib::profile::FILE_NAME);
println!("Writing profile to local clone: {}", path.display());
if path.is_file() {
std::fs::remove_file(&path)?;
}
let file = std::fs::OpenOptions::new()
.write(true)
.append(false)
.create(true)
.open(&path)
.expect(format!("Can't open/create file for writing: {}", path.display()).as_str());
let writer = std::io::BufWriter::new(file);
serde_json::to_writer_pretty(writer, &remote_profile)?;
Ok(())
}
================================================
FILE: openfare/src/command/service/lnpay.rs
================================================
use crate::common::fs::FileStore;
use anyhow::Result;
use structopt::{self, StructOpt};
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct AddArguments {
/// API key. Found here: https://cloud.lnpay.co/developers/dashboard
pub api_key: String,
/// Set payment method as default.
#[structopt(long, short)]
pub default: bool,
}
pub fn add(args: &AddArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
config.services.lnpay = Some(crate::config::services::lnpay::LnPay {
api_key: args.api_key.clone(),
});
config.dump()?;
println!("Added service: LNPAY (https://lnpay.co)");
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
pub struct RemoveArguments {}
pub fn remove(_args: &RemoveArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
if config.services.default == crate::services::Service::LnPay {
config.services.default = crate::services::Service::Portal;
}
config.services.lnpay = None;
config.dump()?;
Ok(())
}
================================================
FILE: openfare/src/command/service/mod.rs
================================================
use crate::common::fs::FileStore;
use crate::common::json::{Get, Set};
use anyhow::Result;
use structopt::{self, StructOpt};
mod lnpay;
#[derive(Debug, Clone, StructOpt)]
pub struct Arguments {
// SUBCOMMANDS
#[structopt(subcommand)]
commands: Subcommands,
}
#[derive(Debug, StructOpt, Clone)]
enum Subcommands {
/// Add payment service.
Add(AddArguments),
/// Set payment service parameters.
Set(SetArguments),
/// Show payment service parameters.
Show(ShowArguments),
/// Remove payment service.
Remove(RemoveArguments),
}
pub fn run_command(args: &Arguments) -> Result<()> {
match &args.commands {
Subcommands::Add(args) => {
log::info!("Running command: service add");
add(&args)?;
}
Subcommands::Set(args) => {
log::info!("Running command: service set");
set(&args)?;
}
Subcommands::Show(args) => {
log::info!("Running command: service show");
show(&args)?;
}
Subcommands::Remove(args) => {
log::info!("Running command: service remove");
remove(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub enum AddArguments {
/// Add service LNPAY (https://lnpay.co)
#[structopt(name = "lnpay")]
LnPay(lnpay::AddArguments),
}
fn add(args: &AddArguments) -> Result<()> {
match &args {
AddArguments::LnPay(args) => {
lnpay::add(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct SetArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: String,
/// Field value.
pub value: String,
}
fn set(args: &SetArguments) -> Result<()> {
let mut config = crate::config::Config::load()?;
config.services.set(&args.path, &args.value)?;
config.dump()?;
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
enum RemoveArguments {
/// Add service LNPAY (https://lnpay.co)
#[structopt(name = "lnpay")]
LnPay(lnpay::RemoveArguments),
}
fn remove(args: &RemoveArguments) -> Result<()> {
match args {
RemoveArguments::LnPay(args) => {
lnpay::remove(&args)?;
}
}
Ok(())
}
#[derive(Debug, StructOpt, Clone)]
pub struct ShowArguments {
/// Field path.
#[structopt(name = "field-path")]
pub path: Option<String>,
}
fn show(args: &ShowArguments) -> Result<()> {
let config = crate::config::Config::load()?;
let value = config.services.get(&args.path)?;
println!("{}", serde_json::to_string_pretty(&value)?);
Ok(())
}
================================================
FILE: openfare/src/common/fs/mod.rs
================================================
use anyhow::{Context, Result};
pub fn ensure_extensions_bin_directory() -> Result<Option<std::path::PathBuf>> {
// Attempt to create an extensions directory in the users home directory.
let extensions_directory = get_extensions_default_directory();
// Use user local bin if previous path is None.
let extensions_directory = extensions_directory.or(dirs::executable_dir());
// Ensure directory exists.
if let Some(extensions_directory) = &extensions_directory {
if !extensions_directory.exists() {
log::debug!(
"Creating OpenFare extensions bin directory: {}",
extensions_directory.display()
);
std::fs::create_dir_all(&extensions_directory)?;
set_directory_hidden_windows(&extensions_directory);
}
}
Ok(extensions_directory)
}
/// Does not create the directory.
/// Returns None if home directory does not exist.
pub fn get_extensions_default_directory() -> Option<std::path::PathBuf> {
let extensions_directory_name = ".openfare_extensions";
match dirs::home_dir() {
Some(home_directory) => {
if !home_directory.exists() {
None
} else {
let extensions_directory = home_directory.join(extensions_directory_name);
Some(extensions_directory)
}
}
None => None,
}
}
#[cfg(windows)]
fn set_directory_hidden_windows(_directory: &std::path::PathBuf) {
// TODO: Hide directory on Windows.
// winapi::um::fileapi::SetFileAttributesA()
}
#[cfg(not(windows))]
fn set_directory_hidden_windows(_directory: &std::path::PathBuf) {}
pub trait FilePath {
fn file_path() -> Result<std::path::PathBuf>;
}
pub trait FileStore: FilePath {
fn load() -> Result<Self>
where
Self: Sized;
fn dump(&mut self) -> Result<()>;
}
impl<'de, T> FileStore for T
where
T: FilePath + Default + serde::de::DeserializeOwned + serde::Serialize,
{
fn load() -> Result<Self> {
if !Self::file_path()?.is_file() {
let mut default = Self::default();
default.dump()?;
}
let file = std::fs::File::open(Self::file_path()?)?;
let reader = std::io::BufReader::new(file);
Ok(serde_json::from_reader(reader)?)
}
fn dump(&mut self) -> Result<()> {
if Self::file_path()?.is_file() {
std::fs::remove_file(&Self::file_path()?)?;
}
let file = std::fs::OpenOptions::new()
.write(true)
.append(false)
.create(true)
.open(&Self::file_path()?)
.context(format!(
"Can't open/create file for writing: {}",
Self::file_path()?.display()
))?;
let writer = std::io::BufWriter::new(file);
serde_json::to_writer_pretty(writer, &self)?;
Ok(())
}
}
================================================
FILE: openfare/src/common/git.rs
================================================
use anyhow::Result;
pub fn run_command(
args: Vec<&str>,
working_directory: &std::path::PathBuf,
) -> Result<std::process::Output> {
log::debug!(
"Executing git command: git {args}\nWorking directory: {working_directory}",
args = args.join(" ").to_string(),
working_directory = working_directory.display()
);
let output = std::process::Command::new("git")
.args(&args)
.current_dir(working_directory)
.output()?;
log::debug!("Command execution complete: {:?}", output);
if !output.status.success() {
let args = args.join(" ").to_string();
return Err(anyhow::format_err!(
"Git command error: git {args}\n{status}",
args = args,
status = output.status
));
}
Ok(output)
}
pub fn commit(message: &str, working_directory: &std::path::PathBuf) -> Result<()> {
let args = vec!["commit", "-am", message];
if run_command(args, &working_directory).is_err() {
log::debug!("Error encountered running git commit command. Possibly no change to commit.")
}
Ok(())
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GitUrl {
pub hostname: Option<String>,
pub username: Option<String>,
pub repository: Option<String>,
}
impl GitUrl {
pub fn new(hostname: Option<&str>, username: Option<&str>, repository: Option<&str>) -> GitUrl {
Self {
hostname: hostname.map(String::from),
username: username.map(String::from),
repository: repository.map(String::from),
}
}
pub fn as_ssh_url(&self) -> Option<String> {
if let Some(hostname) = &self.hostname {
if let Some(username) = &self.username {
if let Some(repository) = &self.repository {
return Some(format!(
"git@{hostname}:{username}/{repository}.git",
hostname = hostname,
username = username,
repository = repository
));
}
}
}
None
}
pub fn as_https_url(&self) -> Option<String> {
if let Some(hostname) = &self.hostname {
if let Some(username) = &self.username {
if let Some(repository) = &self.repository {
return Some(format!(
"https://{hostname}/{username}/{repository}.git",
hostname = hostname,
username = username,
repository = repository
));
}
}
}
None
}
}
impl std::str::FromStr for GitUrl {
type Err = anyhow::Error;
fn from_str(url: &str) -> Result<Self, Self::Err> {
if is_https_git_url(&url) {
parse_https_url(&url)
} else if is_ssh_git_url(&url) {
parse_git_url(&url)
} else {
Ok(Self {
hostname: None,
username: None,
repository: None,
})
}
}
}
fn parse_https_url(url: &str) -> Result<GitUrl> {
let re = regex::Regex::new(
r"(http(|s)://)?(?P<hostname>[^/]*)/(?P<username>[^/]*)(/(?P<repository>[^\.]*)(\.git)?)?$",
)?;
let captures = re.captures(url).ok_or(anyhow::format_err!(
"Failed to capture regex groups: {url}",
url = url
))?;
let hostname = captures.name("hostname").map_or(None, |m| Some(m.as_str()));
let username = captures.name("username").map_or(None, |m| Some(m.as_str()));
let repository = captures
.name("repository")
.map_or(None, |m| Some(m.as_str()));
// For GitHub or GitLab, if repository not given, assume same as username.
let repository = if hostname == Some("github.com") || hostname == Some("gitlab.com") {
repository.or(username)
} else {
repository
};
Ok(GitUrl::new(hostname, username, repository))
}
fn parse_git_url(url: &str) -> Result<GitUrl> {
let re = regex::Regex::new(
r"git@(?P<hostname>.*):(?P<username>.*)/(?P<repository>[^\.]*)(\.git)?$",
)?;
let captures = re.captures(url).ok_or(anyhow::format_err!(
"Failed to capture regex groups: {url}",
url = url
))?;
let hostname = captures.name("hostname").map_or(None, |m| Some(m.as_str()));
let username = captures.name("username").map_or(None, |m| Some(m.as_str()));
let repository = captures
.name("repository")
.map_or(None, |m| Some(m.as_str()));
Ok(GitUrl::new(hostname, username, repository))
}
fn is_https_git_url(url: &str) -> bool {
!is_ssh_git_url(&url)
}
fn is_ssh_git_url(url: &str) -> bool {
url.starts_with("git@")
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_parse_https_url() -> Result<()> {
let cases = vec![
"https://github.com/rndhouse/rndhouse_repo.git",
"http://gitlab.com/rndhouse/rndhouse_repo.git",
"github.com/rndhouse/rndhouse_repo",
];
let expected = vec![
GitUrl::new(Some("github.com"), Some("rndhouse"), Some("rndhouse_repo")),
GitUrl::new(Some("gitlab.com"), Some("rndhouse"), Some("rndhouse_repo")),
GitUrl::new(Some("github.com"), Some("rndhouse"), Some("rndhouse_repo")),
];
for (case, expect) in cases.iter().zip(expected.iter()) {
let result = GitUrl::from_str(case)?;
assert_eq!(&result, expect);
}
Ok(())
}
#[test]
fn test_parse_github_gitlab_user_https_url() -> Result<()> {
let cases = vec!["https://github.com/rndhouse", "http://gitlab.com/rndhouse"];
let expected = vec![
GitUrl::new(Some("github.com"), Some("rndhouse"), Some("rndhouse")),
GitUrl::new(Some("gitlab.com"), Some("rndhouse"), Some("rndhouse")),
];
for (case, expect) in cases.iter().zip(expected.iter()) {
let result = GitUrl::from_str(case)?;
assert_eq!(&result, expect);
}
Ok(())
}
#[test]
fn test_parse_ssh_url() -> Result<()> {
let cases = vec!["git@github.com:rndhouse/rndhouse_repo.git"];
let expected = vec![GitUrl::new(
Some("github.com"),
Some("rndhouse"),
Some("rndhouse_repo"),
)];
for (case, expect) in cases.iter().zip(expected.iter()) {
let result = GitUrl::from_str(case)?;
assert_eq!(&result, expect);
}
Ok(())
}
}
================================================
FILE: openfare/src/common/json.rs
================================================
use anyhow::Result;
// TODO: Rename: Subject -> JsonComponent
pub trait Subject<SubT> {
fn subject(&self) -> &SubT;
fn subject_mut(&mut self) -> &mut SubT;
}
pub trait Get<SubT>: Subject<SubT> {
fn get(&self, field_path: &Option<String>) -> Result<serde_json::Value>;
}
impl<'de, T, SubT> Get<SubT> for T
where
T: Subject<SubT>,
SubT: serde::de::DeserializeOwned + serde::Serialize,
{
fn get(&self, field_path: &Option<String>) -> Result<serde_json::Value> {
let subject = self.subject();
let value = serde_json::to_value(&subject)?;
let value = if let Some(field_path) = field_path {
let mut target = &value;
for field in field_path.split('.') {
target = target
.get(field)
.ok_or(anyhow::format_err!("Failed to find field: {}", field))?;
}
(*target).clone()
} else {
value
};
Ok(value)
}
}
pub trait Set<SubT>: Subject<SubT> {
fn set(&mut self, field_path: &str, value: &str) -> Result<()>;
}
impl<'de, T, SubT> Set<SubT> for T
where
T: Subject<SubT>,
SubT: serde::de::DeserializeOwned + serde::Serialize,
{
fn set(&mut self, field_path: &str, value: &str) -> Result<()> {
let subject = self.subject_mut();
let mut json_value = serde_json::to_value(&subject)?;
let mut target = &mut json_value;
for field in field_path.split('.') {
target = target
.get_mut(field)
.ok_or(anyhow::format_err!("Failed to find field: {}", field))?;
}
let value = match serde_json::from_str(value) {
Ok(v) => v,
Err(_) => serde_json::json!(value),
};
*target = value;
*subject = serde_json::from_value(json_value)?;
Ok(())
}
}
================================================
FILE: openfare/src/common/mod.rs
================================================
pub mod fs;
pub mod git;
pub mod json;
pub mod url;
pub static HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
================================================
FILE: openfare/src/common/url.rs
================================================
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Url {
pub original: String,
pub git: super::git::GitUrl,
}
impl std::str::FromStr for Url {
type Err = anyhow::Error;
fn from_str(url: &str) -> Result<Self, Self::Err> {
Ok(Self {
original: url.to_string(),
git: super::git::GitUrl::from_str(&url)?,
})
}
}
impl std::fmt::Display for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.original)
}
}
================================================
FILE: openfare/src/config/core.rs
================================================
#[derive(
Debug, Clone, Default, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::Deserialize,
)]
pub struct Core {
#[serde(rename = "preferred-currency")]
pub preferred_currency: openfare_lib::price::Currency,
}
impl std::fmt::Display for Core {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/config/extensions.rs
================================================
#[derive(
Debug, Clone, Default, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::Deserialize,
)]
pub struct Extensions {
pub enabled: std::collections::BTreeMap<String, bool>,
pub registries: std::collections::BTreeMap<String, String>,
}
impl std::fmt::Display for Extensions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/config/mod.rs
================================================
use anyhow::Result;
mod core;
mod extensions;
mod paths;
mod profile;
pub mod services;
pub use paths::Paths;
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Config {
pub core: core::Core,
pub services: services::Services,
pub profile: profile::Profile,
pub extensions: extensions::Extensions,
}
impl crate::common::json::Subject<Config> for Config {
fn subject(&self) -> &Self {
&self
}
fn subject_mut(&mut self) -> &mut Self {
self
}
}
impl crate::common::fs::FilePath for Config {
fn file_path() -> Result<std::path::PathBuf> {
let paths = paths::Paths::new()?;
Ok(paths.config_file)
}
}
impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/config/paths.rs
================================================
use anyhow::Result;
/// Filesystem config directory absolute paths.
#[derive(Debug)]
pub struct Paths {
pub root_directory: std::path::PathBuf,
pub config_file: std::path::PathBuf,
pub profile_file: std::path::PathBuf,
pub extensions_directory: std::path::PathBuf,
}
impl Paths {
pub fn new() -> Result<Self> {
let user_directories = directories::ProjectDirs::from("", "", "openfare").ok_or(
anyhow::format_err!("Failed to obtain a handle on the local user directory."),
)?;
let root_directory = user_directories.config_dir();
Ok(Self {
root_directory: root_directory.into(),
config_file: root_directory.join("config.json"),
profile_file: root_directory.join("profile.json"),
extensions_directory: root_directory.join("extensions"),
})
}
}
================================================
FILE: openfare/src/config/profile.rs
================================================
#[derive(
Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::Deserialize,
)]
pub struct Profile {
pub url: Option<String>,
#[serde(flatten)]
pub parameters: openfare_lib::lock::plan::conditions::Parameters,
}
impl std::fmt::Display for Profile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/config/services/lnpay.rs
================================================
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct LnPay {
#[serde(rename = "api-key")]
pub api_key: String,
}
impl std::fmt::Display for LnPay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/config/services/mod.rs
================================================
pub mod lnpay;
mod portal;
/// Payment services.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Services {
pub default: crate::services::Service,
pub portal: portal::Portal,
#[serde(skip_serializing_if = "Option::is_none")]
pub lnpay: Option<lnpay::LnPay>,
}
impl std::default::Default for Services {
fn default() -> Self {
Self {
default: crate::services::Service::Portal,
portal: portal::Portal::default(),
lnpay: None,
}
}
}
impl crate::common::json::Subject<Services> for Services {
fn subject(&self) -> &Services {
&self
}
fn subject_mut(&mut self) -> &mut Services {
self
}
}
impl std::fmt::Display for Services {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/config/services/portal.rs
================================================
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Portal {
pub url: url::Url,
#[serde(rename = "api-key")]
pub api_key: openfare_lib::api::services::portal::ApiKey,
pub email: Option<String>,
}
impl std::default::Default for Portal {
fn default() -> Self {
let api_key = {
let uuid = uuid::Uuid::new_v4();
let mut encode_buffer = uuid::Uuid::encode_buffer();
let uuid = uuid.to_hyphenated().encode_lower(&mut encode_buffer);
uuid.to_string()
};
Self {
url: url::Url::parse("https://openfare.dev/").unwrap(),
api_key,
email: None,
}
}
}
impl std::fmt::Display for Portal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string_pretty(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/extensions/common.rs
================================================
use anyhow::Result;
pub fn get_config_path(extension_name: &str) -> Result<std::path::PathBuf> {
let config_paths = crate::config::Paths::new()?;
Ok(config_paths.extensions_directory.join(format!(
"{extension_name}.json",
extension_name = extension_name
)))
}
pub fn filter_results<'a, 'b, T>(
extensions: &'a Vec<Box<dyn openfare_lib::extension::Extension>>,
results: &'b Vec<Result<T>>,
) -> Result<Vec<(&'a Box<dyn openfare_lib::extension::Extension>, &'b T)>> {
let mut filtered_results = vec![];
for (extension, result) in extensions.iter().zip(results.iter()) {
log::debug!(
"Inspecting result from extension: {name} ({version})",
name = extension.name(),
version = extension.version()
);
let result = match result {
Ok(result) => {
log::debug!(
"Found Ok result from extension: {name}",
name = extension.name(),
);
result
}
Err(error) => {
log::error!(
"Extension {name} error: {error}",
name = extension.name(),
error = error
);
continue;
}
};
filtered_results.push((extension.clone(), result.clone()));
}
Ok(filtered_results)
}
================================================
FILE: openfare/src/extensions/manage/github.rs
================================================
use anyhow::{format_err, Context, Result};
use std::io::Read;
use crate::common;
pub fn get_archive_url(repo_url: &url::Url) -> Result<Option<url::Url>> {
let platform = get_platform()?;
log::debug!("Identified target platform: {}", platform);
let releases = get_releases(&repo_url)?;
if releases.is_empty() {
log::debug!("Failed to find any releases corresponding to repository URL.");
} else {
log::debug!("Found {} candidate releases.", releases.len());
}
for release in releases {
if let Some(assets) = release.get("assets").and_then(|assets| assets.as_array()) {
for asset in assets {
if let Some(asset_name) = asset.get("name").and_then(|name| name.as_str()) {
if asset_name.contains(&platform) {
if let Some(url) = asset
.get("browser_download_url")
.and_then(|url| url.as_str())
{
return Ok(Some(url::Url::parse(url)?));
}
}
}
}
}
}
Ok(None)
}
/// Get releases given a repository URL such as: https://github.com/openfare/openfare-py
fn get_releases(repo_url: &url::Url) -> Result<Vec<serde_json::Value>> {
let releases_url = url::Url::parse(
format!(
"https://api.github.com/repos{path}/releases",
path = repo_url.path()
)
.as_str(),
)?;
log::debug!("Using releases URL: {}", releases_url);
let client = reqwest::blocking::Client::builder()
.user_agent(common::HTTP_USER_AGENT)
.build()?;
let mut result = client.get(&releases_url.to_string()).send()?;
let mut body = String::new();
result.read_to_string(&mut body)?;
let releases: serde_json::Value =
serde_json::from_str(&body).context(format!("JSON was not well-formatted:\n{}", body))?;
let releases = releases
.as_array()
.ok_or(format_err!("Failed to find releases from GitHub repo."))?;
Ok(releases.clone())
}
fn get_platform() -> Result<String> {
Ok(match std::env::consts::OS {
"linux" => "unknown-linux-musl",
"macos" => "apple-darwin",
"windows" => "pc-windows-msvc",
other => return Err(format_err!("Unsupported OS type: {}", other)),
}
.to_string())
}
================================================
FILE: openfare/src/extensions/manage/mod.rs
================================================
use crate::common::fs::FileStore;
use anyhow::{format_err, Result};
use std::convert::TryFrom;
use crate::config::Config;
use crate::extensions::common;
mod github;
mod process;
use openfare_lib::extension::FromLib;
/// Return handles to all known extensions.
pub fn get_all() -> Result<Vec<Box<dyn openfare_lib::extension::Extension>>> {
log::debug!("Identifying all extensions.");
let mut all_extensions = vec![
Box::new(openfare_js_lib::JsExtension::new())
as Box<dyn openfare_lib::extension::Extension>,
Box::new(openfare_rs_lib::RsExtension::new())
as Box<dyn openfare_lib::extension::Extension>,
];
for extension in process::get_all()? {
all_extensions.push(Box::new(extension) as Box<dyn openfare_lib::extension::Extension>);
}
Ok(all_extensions)
}
pub fn add_from_url(
url: &url::Url,
extensions_bin_directory: &std::path::PathBuf,
) -> Result<String> {
let archive_url = if is_supported_archive_url(&url)? {
url.clone()
} else {
match get_archive_url(&url)? {
Some(url) => url,
None => {
return Err(format_err!(
"Failed to obtain suitable release archive URL."
))
}
}
};
log::info!("Using archive URL: {}", archive_url);
let archive_type = openfare_lib::common::fs::archive::ArchiveType::try_from(
&std::path::PathBuf::from(archive_url.path()),
)?;
let tmp_dir = tempdir::TempDir::new("openfare_extension_add")?;
let tmp_directory_path = tmp_dir.path().to_path_buf();
log::info!(
"Downloading extension archive to temporary directory: {}",
tmp_directory_path.display()
);
let archive_path =
tmp_directory_path.join(format!("archive.{}", archive_type.try_to_string()?));
openfare_lib::common::fs::archive::download(&archive_url, &archive_path)?;
openfare_lib::common::fs::archive::extract(&archive_path, &tmp_directory_path)?;
let (bin_path, extension_name) = get_bin_file_metadata(&tmp_directory_path)?.ok_or(
format_err!("Failed to identify extension binary in archive."),
)?;
log::info!(
"Identified binary for extension {}: {}",
extension_name,
bin_path.display()
);
let bin_file_name = bin_path
.file_name()
.and_then(|name| name.to_str())
.ok_or(format_err!("Failed to derive extension binary file name."))?;
let bin_destination_path = extensions_bin_directory.join(bin_file_name);
log::info!("Copying binary to path: {}", bin_destination_path.display());
std::fs::copy(&bin_path, &bin_destination_path)?;
ensure_executable_permissions(&bin_destination_path)?;
tmp_dir.close()?;
Ok(extension_name)
}
#[cfg(target_family = "unix")]
fn ensure_executable_permissions(path: &std::path::PathBuf) -> Result<()> {
log::debug!(
"Setting executable permissions to 755 for file: {}",
path.display()
);
use std::os::unix::fs::PermissionsExt;
let permissions = std::fs::Permissions::from_mode(0o755);
std::fs::set_permissions(&path, permissions)?;
Ok(())
}
#[cfg(not(target_family = "unix"))]
fn ensure_executable_permissions(_path: &std::path::PathBuf) -> Result<()> {
Ok(())
}
fn get_bin_file_metadata(
directory: &std::path::PathBuf,
) -> Result<Option<(std::path::PathBuf, String)>> {
let regex_pattern = get_bin_name_regex()?;
for entry in std::fs::read_dir(&directory)? {
let entry = entry?;
let path = entry.path();
if let Some(name) = get_name_from_bin(&path, ®ex_pattern)? {
return Ok(Some((path, name)));
}
}
Ok(None)
}
fn get_bin_name_regex() -> Result<regex::Regex> {
Ok(regex::Regex::new(
r"openfare-(?P<name>[a-zA-Z0-9]*)(\.exe)?$",
)?)
}
fn get_name_from_bin(
path: &std::path::PathBuf,
regex_pattern: ®ex::Regex,
) -> Result<Option<String>> {
if let Some(file_name) = path.file_name().and_then(|name| name.to_str()) {
match regex_pattern.captures(file_name) {
Some(captures) => Ok(Some(captures["name"].to_string())),
None => Ok(None),
}
} else {
Ok(None)
}
}
fn is_supported_archive_url(url: &url::Url) -> Result<bool> {
let path = std::path::PathBuf::from(url.path());
Ok(
openfare_lib::common::fs::archive::ArchiveType::try_from(&path)?
!= openfare_lib::common::fs::archive::ArchiveType::Unknown,
)
}
/// Returns a release archive URL.
fn get_archive_url(url: &url::Url) -> Result<Option<url::Url>> {
Ok(if url.host_str() == Some("github.com") {
github::get_archive_url(&url)?
} else {
None
})
}
#[cfg(test)]
mod tests {
use super::*;
mod get_extension_bin_name {
use super::*;
#[test]
fn test_matching_file_name() -> Result<()> {
let regex_pattern = get_bin_name_regex()?;
let bin_path =
std::path::PathBuf::from("/tmp/openfare-extension_add/openfare-python/openfare-py");
let result = get_name_from_bin(&bin_path, ®ex_pattern)?;
let expected = Some("py".to_string());
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_not_matching_file_name() -> Result<()> {
let regex_pattern = get_bin_name_regex()?;
let bin_path = std::path::PathBuf::from(
"/tmp/openfare-extension_add/openfare-python/openfare-py.d",
);
let result = get_name_from_bin(&bin_path, ®ex_pattern)?;
let expected = None;
assert_eq!(result, expected);
Ok(())
}
}
}
/// Update config with discoverable extensions.
pub fn update_config(config: &mut Config) -> Result<()> {
log::debug!("Discover extensions and update config.");
let extensions = get_all()?;
let extension_name_map: std::collections::BTreeMap<_, _> = extensions
.iter()
.map(|extension| (extension.name(), extension))
.collect();
let all_found_names: std::collections::BTreeSet<_> =
extension_name_map.keys().cloned().collect();
let configured_names: std::collections::BTreeSet<_> = config
.extensions
.enabled
.keys()
.map(|name| name.clone())
.collect();
let stale_names: Vec<_> = configured_names.difference(&all_found_names).collect();
let registries_map = config.extensions.registries.clone();
for name in &stale_names {
config.extensions.enabled.remove(name.clone());
// Update registries map.
for (registry, extension_name) in ®istries_map {
if *extension_name == **name {
config.extensions.registries.remove(registry);
}
}
}
let new_names: Vec<_> = all_found_names.difference(&configured_names).collect();
for name in &new_names {
config.extensions.enabled.insert((**name).clone(), true);
// Update registries map.
if let Some(extension) = extension_name_map.get(name.as_str()) {
for registry in extension.registries() {
config
.extensions
.registries
.insert(registry, (*name).clone());
}
}
}
if !stale_names.is_empty() || !new_names.is_empty() {
config.dump()?;
}
Ok(())
}
/// Enable extension.
pub fn enable(name: &str, config: &mut Config) -> Result<()> {
if let Some(enabled_status) = config.extensions.enabled.get_mut(&name.to_string()) {
*enabled_status = true;
config.dump()?;
Ok(())
} else {
Err(format_err!("Failed to find extension."))
}
}
/// Disable extension.
pub fn disable(name: &str, config: &mut Config) -> Result<()> {
if let Some(enabled_status) = config.extensions.enabled.get_mut(&name.to_string()) {
*enabled_status = false;
config.dump()?;
Ok(())
} else {
Err(format_err!("Failed to find extension."))
}
}
pub fn remove(name: &str) -> Result<()> {
let mut config = Config::load()?;
update_config(&mut config)?;
let all_extension_names = get_all_names(&config)?;
if !all_extension_names.contains(name) {
return Err(format_err!(
"Failed to find extension. Known extensions: {}",
all_extension_names
.into_iter()
.collect::<Vec<_>>()
.join(", ")
));
}
// Remove extension specific config file.
let path = common::get_config_path(&name)?;
if path.is_file() {
log::info!("Removing extension config file: {}", path.display());
std::fs::remove_file(&path)?;
}
// Remove extension process file.
let extension_paths = process::get_extension_paths()?;
if let Some(path) = extension_paths.get(name) {
log::info!("Deleting extension bin file: {}", path.display());
std::fs::remove_file(&path)?;
}
update_config(&mut config)?;
Ok(())
}
/// Given an extension's name, returns true if the extension is enabled. Otherwise returns false.
fn is_enabled(name: &str, config: &Config) -> Result<bool> {
Ok(*config.extensions.enabled.get(name).unwrap_or(&false))
}
/// Returns enabled extensions.
fn enabled(
filter_for_names: &std::collections::BTreeSet<String>,
config: &Config,
) -> Result<Vec<Box<dyn openfare_lib::extension::Extension>>> {
log::debug!("Identifying enabled extensions.");
let extensions = get_all()?
.into_iter()
.filter(|extension| {
*config
.extensions
.enabled
.get(&extension.name())
.unwrap_or(&false)
})
.filter(|extension| filter_for_names.contains(&extension.name()))
.collect();
Ok(extensions)
}
/// Returns a set of all enabled installed extensions by names.
fn enabled_names(config: &Config) -> Result<std::collections::BTreeSet<String>> {
Ok(config
.extensions
.enabled
.iter()
.filter(|(_name, enabled_flag)| **enabled_flag)
.map(|(name, _enabled_flag)| name.clone())
.collect())
}
pub fn get_all_names(config: &Config) -> Result<std::collections::BTreeSet<String>> {
Ok(config
.extensions
.enabled
.iter()
.map(|(name, _enabled_flag)| name.clone())
.collect())
}
/// Check given extensions are enabled. If not specified select all enabled extensions.
pub fn from_names_arg(
extension_names: &Option<Vec<String>>,
config: &Config,
) -> Result<Vec<Box<dyn openfare_lib::extension::Extension>>> {
let names = match &extension_names {
Some(extension_names) => {
let disabled_names: Vec<_> = extension_names
.iter()
.cloned()
.filter(|name| !is_enabled(&name, &config).unwrap_or(false))
.collect();
if !disabled_names.is_empty() {
return Err(format_err!(
"The following disabled extensions were given: {}",
disabled_names.join(", ")
));
} else {
extension_names.into_iter().cloned().collect()
}
}
None => enabled_names(&config)?,
};
log::debug!("Using extensions: {:?}", names);
let extensions = enabled(&names.into(), &config)?;
Ok(extensions)
}
/// Clean extension name.
///
/// Example: openfare-py --> py
pub fn clean_name(name: &str) -> String {
match &name.strip_prefix(process::EXTENSION_FILE_NAME_PREFIX) {
Some(name) => name.to_string(),
None => name.to_string(),
}
}
================================================
FILE: openfare/src/extensions/manage/process.rs
================================================
use anyhow::Result;
use openfare_lib::extension::FromProcess;
use std::collections::HashMap;
use crate::extensions::common;
pub static EXTENSION_FILE_NAME_PREFIX: &str = "openfare-";
/// Discovers and loads process extensions.
pub fn get_all() -> Result<Vec<openfare_lib::extension::process::ProcessExtension>> {
let extension_paths = get_extension_paths()?;
let mut threads = vec![];
for (name, path) in extension_paths.iter() {
let extension_config_path = common::get_config_path(name)?;
let process_path = path.clone();
threads.push(std::thread::spawn(move || {
openfare_lib::extension::process::ProcessExtension::from_process(
&process_path,
&extension_config_path,
)
}));
}
let extensions: Vec<Result<openfare_lib::extension::process::ProcessExtension>> = threads
.into_iter()
.map(|thread| thread.join().unwrap())
.collect();
let mut extension_map = HashMap::new();
for ((_name, path), extension) in extension_paths.into_iter().zip(extensions.into_iter()) {
extension_map.insert((*path).to_path_buf(), extension);
}
let mut valid_extensions = Vec::new();
for (process_path, extension) in extension_map {
match extension {
Ok(v) => {
valid_extensions.push(v);
}
Err(e) => {
eprintln!(
"{extension_name}: Failed to load extension.\n{error}",
extension_name = process_path.display(),
error = e
);
}
};
}
Ok(valid_extensions)
}
pub fn get_extension_paths() -> Result<HashMap<String, std::path::PathBuf>> {
let mut result: HashMap<String, std::path::PathBuf> = HashMap::new();
for path in get_candidate_extension_paths()? {
// Skip non-valid paths.
if !path.is_dir() && !path.is_file() {
continue;
}
if path.is_file() {
let name = match get_extension_name(&path)? {
Some(name) => name,
None => {
continue;
}
};
result.insert(name, path);
continue;
}
// Inspect file in directory. Does not investigate child directories.
for entry in std::fs::read_dir(path)? {
let path = entry?.path();
if path.is_file() {
let name = match get_extension_name(&path)? {
Some(name) => name,
None => {
continue;
}
};
result.insert(name, path);
}
}
}
Ok(result)
}
fn get_candidate_extension_paths() -> Result<Vec<std::path::PathBuf>> {
let env_path_value = std::env::var_os("PATH").ok_or(anyhow::format_err!(
"Failed to read PATH environment variable."
))?;
let mut paths = std::env::split_paths(&env_path_value).collect::<Vec<_>>();
if let Some(extensions_home_directory) = crate::common::fs::get_extensions_default_directory() {
if extensions_home_directory.exists() {
paths.push(extensions_home_directory);
}
}
Ok(paths)
}
fn get_extension_name(file_path: &std::path::PathBuf) -> Result<Option<String>> {
let file_name = file_path
.file_name()
.ok_or(anyhow::format_err!("Failed to parse path file name."))?
.to_str()
.ok_or(anyhow::format_err!(
"Failed to parse path file name into string."
))?
.to_string();
let captures = match regex::Regex::new(&format!(
"{extension_file_name_prefix}([a-z]*).*",
extension_file_name_prefix = EXTENSION_FILE_NAME_PREFIX
))?
.captures(file_name.as_str())
{
Some(v) => v,
None => {
return Ok(None);
}
};
let name = match captures.get(1) {
Some(v) => v,
None => {
return Ok(None);
}
}
.as_str();
Ok(Some(name.to_string()))
}
================================================
FILE: openfare/src/extensions/mod.rs
================================================
pub mod common;
pub mod manage;
pub mod package;
pub mod project;
================================================
FILE: openfare/src/extensions/package.rs
================================================
use anyhow::Result;
/// Get package dependencies locks.
///
/// Conducts a parallel search across extensions.
pub fn dependencies_locks(
package_name: &str,
package_version: &Option<&str>,
extensions: &Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
) -> Result<
Vec<
Result<
openfare_lib::extension::commands::package_dependencies_locks::PackageDependenciesLocks,
>,
>,
> {
crossbeam_utils::thread::scope(|s| {
let mut threads = Vec::new();
for extension in extensions {
threads.push(s.spawn(move |_| {
extension.package_dependencies_locks(
&package_name,
&package_version,
&extension_args,
)
}));
}
let mut result = Vec::new();
for thread in threads {
result.push(thread.join().unwrap());
}
Ok(result)
})
.unwrap()
}
================================================
FILE: openfare/src/extensions/project.rs
================================================
use anyhow::Result;
/// Identify all supported dependency locks which are defined in a local project.
///
/// Conducts a parallel search across extensions.
pub fn dependencies_locks(
working_directory: &std::path::PathBuf,
extensions: &Vec<Box<dyn openfare_lib::extension::Extension>>,
extension_args: &Vec<String>,
) -> Result<
Vec<
Result<
openfare_lib::extension::commands::project_dependencies_locks::ProjectDependenciesLocks,
>,
>,
> {
crossbeam_utils::thread::scope(|s| {
let mut threads = Vec::new();
for extension in extensions {
threads.push(s.spawn(move |_| {
extension.project_dependencies_locks(&working_directory, &extension_args)
}));
}
let mut result = Vec::new();
for thread in threads {
result.push(thread.join().unwrap());
}
Ok(result)
})
.unwrap()
}
================================================
FILE: openfare/src/handles/lock.rs
================================================
use anyhow::{format_err, Context, Result};
use serde::Serialize;
use std::io::Write;
#[derive(Debug, Clone, Default)]
pub struct LockHandle {
pub lock: openfare_lib::lock::Lock,
lock_hash: Option<blake3::Hash>,
path: std::path::PathBuf,
}
impl LockHandle {
pub fn new(user_lock_file_path: &Option<std::path::PathBuf>, force: bool) -> Result<Self> {
let path = if let Some(user_lock_file_path) = user_lock_file_path {
user_lock_file_path.clone()
} else {
let working_directory = std::env::current_dir()?;
working_directory.join(openfare_lib::lock::FILE_NAME)
};
if path.exists() && !force {
return Err(format_err!(
"File already exists and --force flag not given. Exiting.\n{}",
path.display()
));
}
Ok(Self {
lock: openfare_lib::lock::Lock::default(),
lock_hash: None,
path,
})
}
pub fn load(user_lock_file_path: &Option<std::path::PathBuf>) -> Result<Self> {
let path = user_lock_file_path.clone().or(find_lock_file()?);
if let Some(path) = path {
Self::try_from(&path)
} else {
Err(anyhow::format_err!(
"Filed to find lock file. Provide path or change working directory."
))
}
}
pub fn path(&self) -> &std::path::PathBuf {
&self.path
}
fn get_lock_hash(lock: &openfare_lib::lock::Lock) -> Result<blake3::Hash> {
let serialized_lock = serde_json::to_string(&lock)?;
Ok(blake3::hash(&serialized_lock.as_bytes()))
}
}
impl std::convert::TryFrom<&std::path::PathBuf> for LockHandle {
type Error = anyhow::Error;
fn try_from(path: &std::path::PathBuf) -> Result<Self> {
let lock = from_file(&path)?;
let lock_hash = Some(Self::get_lock_hash(&lock)?);
let lock_handle = Self {
lock,
lock_hash,
path: path.clone(),
};
Ok(lock_handle)
}
}
impl Drop for LockHandle {
fn drop(&mut self) {
// Skip writing lock if unchanged.
let current_lock_hash = Self::get_lock_hash(&self.lock).expect("current lock hash");
if let Some(lock_hash) = self.lock_hash {
if current_lock_hash == lock_hash {
log::debug!("Lock file unchanged. Not writing to file.");
return ();
}
}
log::debug!("Lock file changed. Writing to file.");
if self.path.is_file() {
std::fs::remove_file(&self.path).unwrap_or_default();
}
let buf = Vec::new();
let formatter = serde_json::ser::PrettyFormatter::with_indent(b" ");
let mut serializer = serde_json::Serializer::with_formatter(buf, formatter);
self.lock.serialize(&mut serializer).unwrap();
let lock_json = String::from_utf8(serializer.into_inner()).unwrap();
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(false)
.create(true)
.open(&self.path)
.context(format!(
"Can't open/create file for writing: {}",
self.path.display()
))
.unwrap();
file.write_all(lock_json.as_bytes())
.expect("Unable to write data");
}
}
impl crate::common::json::Subject<openfare_lib::lock::Lock> for LockHandle {
fn subject(&self) -> &openfare_lib::lock::Lock {
&self.lock
}
fn subject_mut(&mut self) -> &mut openfare_lib::lock::Lock {
&mut self.lock
}
}
pub fn find_lock_file() -> Result<Option<std::path::PathBuf>> {
let working_directory = std::env::current_dir()?;
assert!(working_directory.is_absolute());
let mut working_directory = working_directory.clone();
loop {
let target_absolute_path = working_directory.join(openfare_lib::lock::FILE_NAME);
if target_absolute_path.is_file() {
return Ok(Some(target_absolute_path));
}
// No need to move further up the directory tree after this loop.
if working_directory == std::path::PathBuf::from("/") {
break;
}
// Move further up the directory tree.
working_directory.pop();
}
Ok(None)
}
fn from_file(path: &std::path::PathBuf) -> Result<openfare_lib::lock::Lock> {
let file = std::fs::File::open(&path)?;
let reader = std::io::BufReader::new(file);
let lock: openfare_lib::lock::Lock = serde_json::from_reader(reader)?;
Ok(lock)
}
================================================
FILE: openfare/src/handles/mod.rs
================================================
pub(crate) mod lock;
pub mod profile;
pub use lock::LockHandle;
pub use profile::ProfileHandle;
================================================
FILE: openfare/src/handles/profile.rs
================================================
use crate::common;
use anyhow::Result;
/// Profile structure which wraps core library Profile and adds managerial data fields.
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct ProfileHandle {
#[serde(flatten)]
profile: openfare_lib::profile::Profile,
#[serde(skip)]
pub from_url_status: Option<FromUrlStatus>,
}
#[derive(Debug, Clone)]
pub struct FromUrlStatus {
// URL used in retrieving profile.
pub url: crate::common::url::Url,
// Method used to retrieve profile.
pub method: FromUrlMethod,
}
#[derive(Debug, Clone)]
pub enum FromUrlMethod {
Git,
HttpGetJson,
}
impl ProfileHandle {
pub fn from_url(url: &crate::common::url::Url) -> Result<Self> {
match Self::from_http_get(&url) {
Ok(profile) => Ok(profile),
Err(_) => Self::from_git_url(&url),
}
}
fn from_http_get(url: &crate::common::url::Url) -> Result<Self> {
let client = reqwest::blocking::Client::new();
// Convert github.com to raw content form.
let url_str = url.to_string();
let url_str = if url_str.contains("github.com") {
url_str
.replace("github.com", "raw.githubusercontent.com")
.replace("/blob/", "/")
} else {
url_str
};
log::debug!("Sending HTTP GET request to endpoint: {}", url_str);
let response = client.get(url_str).send()?;
let remote_profile = response.json::<openfare_lib::profile::RemoteProfile>()?;
log::debug!("Response received.");
Ok(Self {
profile: remote_profile.profile,
from_url_status: Some(FromUrlStatus {
url: url.clone(),
method: FromUrlMethod::HttpGetJson,
}),
})
}
fn from_git_url(url: &crate::common::url::Url) -> Result<Self> {
let tmp_dir = tempdir::TempDir::new("openfare_profile_from_git_url")?;
let tmp_directory_path = tmp_dir.path().to_path_buf();
let clone_url = if let Some(url) = url.git.as_ssh_url() {
url
} else {
url.original.clone()
};
log::debug!("Attempting to clone repository using URL: {}", clone_url);
let output = crate::common::git::run_command(
vec!["clone", "--depth", "1", clone_url.as_str(), "."],
&tmp_directory_path,
)?;
log::debug!("Clone output: {:?}", output);
let path = tmp_directory_path.join(openfare_lib::profile::FILE_NAME);
if !path.exists() {
return Err(anyhow::format_err!(
"Failed to find profile JSON in repository: {}",
openfare_lib::profile::FILE_NAME
));
}
let file = std::fs::File::open(&path)?;
let reader = std::io::BufReader::new(file);
let remote_profile: openfare_lib::profile::RemoteProfile = serde_json::from_reader(reader)?;
Ok(Self {
profile: remote_profile.profile,
from_url_status: Some(FromUrlStatus {
url: url.clone(),
method: FromUrlMethod::Git,
}),
})
}
}
impl crate::common::json::Subject<openfare_lib::profile::Profile> for ProfileHandle {
fn subject(&self) -> &openfare_lib::profile::Profile {
&self.profile
}
fn subject_mut(&mut self) -> &mut openfare_lib::profile::Profile {
&mut self.profile
}
}
impl std::ops::Deref for ProfileHandle {
type Target = openfare_lib::profile::Profile;
fn deref(&self) -> &openfare_lib::profile::Profile {
&self.profile
}
}
impl std::ops::DerefMut for ProfileHandle {
fn deref_mut(&mut self) -> &mut openfare_lib::profile::Profile {
&mut self.profile
}
}
impl common::fs::FilePath for ProfileHandle {
fn file_path() -> Result<std::path::PathBuf> {
let paths = crate::config::Paths::new()?;
Ok(paths.profile_file)
}
}
impl std::fmt::Display for ProfileHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string(&self).map_err(|_| std::fmt::Error::default())?
)
}
}
================================================
FILE: openfare/src/main.rs
================================================
use env_logger;
use structopt::StructOpt;
mod command;
mod common;
mod config;
mod extensions;
mod handles;
mod payments;
mod services;
mod setup;
fn main() {
let env = env_logger::Env::new().filter_or("OPENFARE_LOG", "off");
env_logger::Builder::from_env(env).init();
let args: Vec<String> = std::env::args().collect();
let (openfare_args, extension_args) = split_extension_args(&args);
let commands = command::Opts::from_iter(openfare_args.iter());
match command::run_command(commands.command, &extension_args) {
Ok(_) => {}
Err(e) => {
eprintln!("{}", e);
std::process::exit(-2)
}
}
}
/// Arguments after -- are passed to extensions.
fn split_extension_args(args: &Vec<String>) -> (Vec<String>, Vec<String>) {
let split_element = "--";
let mut pre_split = vec![];
let mut post_split = vec![];
let mut split_point_found = false;
for arg in args {
if arg == split_element {
split_point_found = true;
continue;
}
if !split_point_found {
pre_split.push(arg.clone());
} else {
post_split.push(arg.clone());
}
}
(pre_split, post_split)
}
================================================
FILE: openfare/src/payments.rs
================================================
use anyhow::Result;
pub fn donation_splits(
donation: &openfare_lib::price::Price,
items: &Vec<openfare_lib::api::services::basket::Item>,
is_payee_applicable: fn(&openfare_lib::lock::payee::Payee) -> Result<bool>,
) -> Result<Vec<(openfare_lib::lock::payee::Payee, openfare_lib::price::Price)>> {
match &donation.currency {
openfare_lib::price::Currency::SATS => println!("Donation: {}", donation),
_ => println!(
"Donation: {} ({sats})",
donation,
sats = donation.to_sats()?
),
}
let donation = donation.to_sats()?;
// Filter for package which has a volunteer plan and at least one applicable payee.
let items = filter_voluntary(&items, is_payee_applicable);
// Round down to avoid overflowing specified donation.
let package_donation_quantity = (donation.quantity / rust_decimal::Decimal::from(items.len()))
.round_dp_with_strategy(
donation.currency.decimal_points(),
rust_decimal::prelude::RoundingStrategy::ToZero,
);
let package_donation = openfare_lib::price::Price {
quantity: package_donation_quantity,
currency: donation.currency.clone(),
};
println!(
"Splitting donation between {count_packages} packages. {package_donation} each.",
count_packages = items.len(),
package_donation = package_donation
);
let mut payee_donations =
Vec::<(openfare_lib::lock::payee::Payee, openfare_lib::price::Price)>::new();
for item in items {
let payees = filter_for_applicable_payees(&item.payees, is_payee_applicable)?;
if let Some(shares) = &item.shares {
// Only consider shares for applicable payees.
let shares = filter_for_applicable_shares(&shares, &payees)?;
let total: u64 = shares.iter().map(|(_, share)| share).sum();
let total = rust_decimal::Decimal::from(total);
for (label, share) in shares {
let share = rust_decimal::Decimal::from(share);
let fraction = share / total;
// Round down to avoid overflowing specified donation.
let payee_donation_quantity = (package_donation.quantity * fraction)
.round_dp_with_strategy(
package_donation.currency.decimal_points(),
rust_decimal::prelude::RoundingStrategy::ToZero,
);
let payee_donation = openfare_lib::price::Price {
quantity: payee_donation_quantity,
currency: package_donation.currency.clone(),
};
if let Some(payee) = item.payees.get(label.as_str()) {
payee_donations.push((payee.clone(), payee_donation));
}
}
} else {
// No shares defined, split package donation evenly between all applicable payees.
// Round down to avoid overflowing specified donation.
let payee_donation_quantity = (package_donation.quantity
/ rust_decimal::Decimal::from(payees.len()))
.round_dp_with_strategy(
package_donation.currency.decimal_points(),
rust_decimal::prelude::RoundingStrategy::ToZero,
);
let payee_donation = openfare_lib::price::Price {
quantity: payee_donation_quantity,
currency: package_donation.currency.clone(),
};
for (_, payee) in &payees {
payee_donations.push((payee.clone(), payee_donation.clone()));
}
}
}
check_payee_donations(&donation, &payee_donations);
Ok(payee_donations)
}
/// Filter for items which have at least one voluntary payment plan and corresponding applicable payee.
fn filter_voluntary(
items: &Vec<openfare_lib::api::services::basket::Item>,
is_payee_applicable: fn(&openfare_lib::lock::payee::Payee) -> Result<bool>,
) -> Vec<openfare_lib::api::services::basket::Item> {
items
.iter()
.cloned()
.filter(|item| {
let voluntary_plans = item
.plans
.iter()
.filter(|(_id, plan)| plan.r#type == openfare_lib::lock::plan::PlanType::Voluntary)
.collect::<Vec<_>>();
let valid_payees =
filter_for_applicable_payees(&item.valid_payees(), is_payee_applicable)
.unwrap_or_default();
!valid_payees.is_empty() && !voluntary_plans.is_empty()
})
.collect()
}
fn filter_for_applicable_payees(
payees: &openfare_lib::lock::payee::Payees,
is_payee_applicable: fn(&openfare_lib::lock::payee::Payee) -> Result<bool>,
) -> Result<openfare_lib::lock::payee::Payees> {
Ok(payees
.iter()
.filter(|(_, payee)| is_payee_applicable(&payee).unwrap_or(false))
.map(|(label, payee)| (label.clone(), payee.clone()))
.collect())
}
fn filter_for_applicable_shares(
shares: &openfare_lib::lock::plan::Shares,
applicable_label_payees: &openfare_lib::lock::payee::Payees,
) -> Result<openfare_lib::lock::plan::Shares> {
Ok(shares
.iter()
.filter(|(label, _share)| applicable_label_payees.contains_key(label.as_str()))
.map(|(label, share)| (label.clone(), share.clone()))
.collect())
}
fn check_payee_donations(
total_donation: &openfare_lib::price::Price,
payee_donations: &Vec<(openfare_lib::lock::payee::Payee, openfare_lib::price::Price)>,
) {
let total_payee_donations: openfare_lib::price::Quantity = payee_donations
.iter()
.map(|(_, price)| price.quantity)
.sum();
assert!(total_donation.quantity >= total_payee_donations);
}
================================================
FILE: openfare/src/services/lnpay.rs
================================================
use anyhow::Result;
use rust_decimal::prelude::ToPrimitive;
use serde::de::Deserialize;
static BASE_URL: &str = "https://api.lnpay.co/v1/";
static DEFAULT_WALLET_NAME: &str = "openfare";
pub type Invoice = String;
#[derive(Debug, Clone)]
pub struct Client {
api_key: String,
client: reqwest::blocking::Client,
}
impl Client {
pub fn new(api_key: &str) -> Self {
Self {
api_key: api_key.to_string(),
client: reqwest::blocking::Client::new(),
}
}
/// Get request builder.
fn get(&self, url: &url::Url) -> reqwest::blocking::RequestBuilder {
self.client
.get(url.clone())
.header(reqwest::header::USER_AGENT, crate::common::HTTP_USER_AGENT)
.header("X-Api-Key", self.api_key.clone())
}
/// Post request builder.
fn post(&self, url: &url::Url) -> reqwest::blocking::RequestBuilder {
self.client
.post(url.clone())
.header(reqwest::header::USER_AGENT, crate::common::HTTP_USER_AGENT)
.header("X-Api-Key", self.api_key.clone())
}
/// Send request.
fn send(
&self,
request: reqwest::blocking::RequestBuilder,
) -> Result<reqwest::blocking::Response> {
log::debug!("Sending request: {:?}", &request);
Ok(request.send()?)
}
pub fn wallets(&self) -> Result<Wallets> {
let url = url::Url::parse(BASE_URL)?.join("wallets")?;
let request = self.get(&url);
let wallets: Vec<Wallet> = self.send(request)?.json()?;
Ok(Wallets(wallets))
}
pub fn wallet(&self, user_label: &str) -> Result<Option<Wallet>> {
Ok(self
.wallets()?
.iter()
.filter(|w| w.user_label == user_label)
.cloned()
.next())
}
/// Creates a wallet if absent, otherwise returns error.
pub fn create_wallet(&self, user_label: &str) -> Result<Wallet> {
// Check for existing.
let wallets = self.wallets()?;
for wallet in wallets.0 {
if wallet.user_label.to_lowercase() == user_label.to_string().to_lowercase() {
return Err(anyhow::format_err!(
"Found existing wallet with the same user label."
));
}
}
let url = url::Url::parse(BASE_URL)?.join("wallet")?;
#[derive(Debug, serde::Serialize)]
struct Body {
user_label: String,
}
let body = Body {
user_label: user_label.to_string(),
};
let body = serde_json::to_string(&body)?;
let request = self
.post(&url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header(reqwest::header::CONTENT_LENGTH, body.len())
.body(body);
let wallet: Wallet = self.send(request)?.json()?;
Ok(wallet)
}
/// Creates a wallet if absent, otherwise returns existing.
pub fn ensure_wallet(&self, user_label: &str) -> Result<Wallet> {
Ok(if let Some(wallet) = self.wallet(&user_label)? {
wallet
} else {
self.create_wallet(&user_label)?
})
}
pub fn probe_lnurl(&self, lnurl: &str) -> Result<LnUrlProbe> {
let url = url::Url::parse(BASE_URL)?.join(format!("lnurlp/probe/{}", lnurl).as_str())?;
let request = self.get(&url);
let probe: LnUrlProbe = self.send(request)?.json()?;
Ok(probe)
}
pub fn invoice_from_lnurl(&self, amount_msat: usize, lnurl: &str) -> Result<Invoice> {
let probe = self.probe_lnurl(&lnurl)?;
let url = probe.callback;
let url = url::Url::parse(&url)?;
#[derive(Debug, serde::Deserialize)]
struct Response {
#[serde(rename = "pr")]
invoice: String,
}
let request = self
.get(&url)
.query(&[("amount", amount_msat.to_string().as_str())]);
let response: Response = self.send(request)?.json()?;
Ok(response.invoice)
}
pub fn pay_invoice(&self, invoice: &Invoice, wallet: &Wallet) -> Result<serde_json::Value> {
let url = url::Url::parse(BASE_URL)?.join(&format!(
"wallet/{wallet_key}/withdraw",
wallet_key = &wallet.key
))?;
#[derive(Debug, serde::Serialize)]
struct Body {
payment_request: String,
}
let body = Body {
payment_request: invoice.clone(),
};
let body = serde_json::to_string(&body)?;
let request = self
.post(&url)
.header(reqwest::header::CONTENT_TYPE, "application/json")
.header(reqwest::header::CONTENT_LENGTH, body.len())
.body(body);
let transaction: serde_json::Value = self.send(request)?.json()?;
Ok(transaction)
}
pub fn get_lnurl(&self, wallet: &Wallet) -> Result<String> {
let lnurlpay_id = wallet
.default_lnurlpay_id
.clone()
.ok_or(anyhow::format_err!(
"Failed to parse wallet default_lnurlpay_id field."
))?;
let url = url::Url::parse(BASE_URL)?
.join(&format!("lnurlp/{lnurlpay_id}", lnurlpay_id = lnurlpay_id))?;
let request = self.get(&url);
#[derive(Debug, serde::Deserialize)]
struct Response {
lnurl_encoded: String,
}
let response: Response = self.send(request)?.json()?;
Ok(response.lnurl_encoded)
}
pub fn pay_lnurl(
&self,
lnurl: &str,
amount_msat: usize,
wallet: &Wallet,
_comment: &str,
) -> Result<serde_json::Value> {
let invoice = self.invoice_from_lnurl(amount_msat, &lnurl)?;
let transaction = self.pay_invoice(&invoice, &wallet)?;
Ok(transaction)
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct LnUrlProbe {
#[serde(rename = "minSendable")]
min_sendable: usize,
#[serde(rename = "maxSendable")]
max_sendable: usize,
#[serde(rename = "commentAllowed", deserialize_with = "bool_from_int")]
comment_allowed: bool,
callback: String,
}
fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
match u8::deserialize(deserializer)? {
0 => Ok(false),
1 => Ok(true),
other => Err(serde::de::Error::invalid_value(
serde::de::Unexpected::Unsigned(other as u64),
&"zero or one",
)),
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Wallets(Vec<Wallet>);
impl std::ops::Deref for Wallets {
type Target = Vec<Wallet>;
fn deref(&self) -> &Vec<Wallet> {
&self.0
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Wallet {
// Wallet key. Example: wal_...
#[serde(rename = "id")]
key: String,
user_label: String,
balance: Option<usize>,
default_lnurlpay_id: Option<String>,
}
pub fn pay(
donation_splits: &Option<Vec<(openfare_lib::lock::payee::Payee, openfare_lib::price::Price)>>,
_items: &Vec<openfare_lib::api::services::portal::basket::Item>,
config: &crate::config::Config,
) -> Result<()> {
let lnpay_config = config
.services
.lnpay.clone().ok_or(anyhow::format_err!("Failed to find LNPAY config under services. Add LNPAY service: openfare service add lnpay --api-key=<key>"))?;
if let Some(donation_splits) = donation_splits {
pay_splits(&donation_splits, &lnpay_config)?;
}
// TODO: Handle applicable compulsory payments.
Ok(())
}
fn pay_splits(
splits: &Vec<(openfare_lib::lock::payee::Payee, openfare_lib::price::Price)>,
lnpay_config: &crate::config::services::lnpay::LnPay,
) -> Result<()> {
let total_payment: rust_decimal::Decimal = splits.iter().map(|(_, price)| price.quantity).sum();
let client = Client::new(&lnpay_config.api_key);
loop {
if let Some(wallet) = client.wallet(DEFAULT_WALLET_NAME)? {
log::debug!("Found payment origin wallet: {:?}", wallet);
let balance = wallet.balance.unwrap_or_default();
let balance = rust_decimal::Decimal::from(balance);
let lightning_network_fee_buffer = rust_decimal::Decimal::from(10 as i64);
log::debug!(
"Adding lightning network fee buffer: {}",
lightning_network_fee_buffer
);
let remainder = (total_payment + lightning_network_fee_buffer) - balance;
if remainder > rust_decimal::Decimal::from(0 as i64) {
let retry = handle_insufficient_balance(&remainder, &balance, &wallet, &client)?;
if !retry {
break;
}
} else {
println!("Found sufficient funds in wallet: {:?}", wallet);
for (payee, amount) in splits {
println!("Paying {amount} to payee:\n{:?}", payee, amount = amount);
let lnurl = get_lnurl(&payee.profile)?.ok_or(anyhow::format_err!(
"Code error: Failed to find LNURL for split payment."
))?;
let amount = amount.quantity.to_usize().ok_or(anyhow::format_err!(
"Failed to parse amount quantity as usize."
))?;
let amount_msat = amount * 1000;
// TODO: Add LNURL comment giving origin.
client.pay_lnurl(&lnurl, amount_msat, &wallet, "")?;
}
break;
}
}
}
Ok(())
}
fn handle_insufficient_balance(
remainder: &rust_decimal::Decimal,
balance: &rust_decimal::Decimal,
wallet: &Wallet,
client: &Client,
) -> Result<bool> {
let lnurl = client.get_lnurl(&wallet)?;
let remainder = remainder.to_usize().ok_or(anyhow::format_err!(
"Code error: remainder sats cant be represented as usize."
))?;
let invoice = client.invoice_from_lnurl(remainder * 1000, &lnurl)?;
println!(
"Wallet '{DEFAULT_WALLET_NAME}' does not contain enough SATS. Current balance: {balance}.",
DEFAULT_WALLET_NAME = DEFAULT_WALLET_NAME,
balance = balance
);
println!(
"Opening QR invoice for remainder (+ 10 sats network fee buffer): {remainder} SATS.",
remainder = remainder
);
let tmp_dir = tempdir::TempDir::new("openfare_pay_invoice_qr")?;
let tmp_directory_path = tmp_dir.path().to_path_buf();
show_qr(&invoice, &tmp_directory_path)?;
Ok(dialoguer::Confirm::new()
.with_prompt("Retry after invoice paid?")
.interact()?)
}
fn show_qr(invoice: &str, tmp_directory_path: &std::path::PathBuf) -> Result<()> {
let code = qrcode::QrCode::new(invoice.as_bytes())?;
let image = code.render::<image::Luma<u8>>().build();
let image_path = tmp_directory_path.join("invoice_qr.jpeg");
image.save(&image_path)?;
open::that_in_background(&image_path);
Ok(())
}
fn get_lnurl(profile: &openfare_lib::profile::Profile) -> Result<Option<String>> {
let payment_methods = profile.payment_methods()?;
let payment_method = payment_methods
.iter()
.filter(|pm| pm.method() == openfare_lib::profile::payment_methods::Methods::BtcLightning)
.next();
if let Some(payment_method) = payment_method {
let payment_method = payment_method.to_serde_json_value()?;
let payment_method: openfare_lib::profile::payment_methods::BtcLightning =
serde_json::from_value(payment_method)?;
Ok(Some(payment_method.lnurl))
} else {
Ok(None)
}
}
pub fn is_payee_applicable(payee: &openfare_lib::lock::payee::Payee) -> Result<bool> {
Ok(get_lnurl(&payee.profile)?.is_some())
}
pub fn lnurl_receive_address(config: &crate::config::Config) -> Result<String> {
let lnpay_config = config.services.lnpay.clone().ok_or(anyhow::format_err!("Failed to find LNPAY config under services. Add LNPAY service: openfare service add lnpay --api-key=<key>"))?;
let client = Client::new(&lnpay_config.api_key);
let wallet = client.ensure_wallet(DEFAULT_WALLET_NAME)?;
Ok(client.get_lnurl(&wallet)?)
}
================================================
FILE: openfare/src/services/mod.rs
================================================
use anyhow::Result;
pub mod lnpay;
mod portal;
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Service {
#[serde(rename = "portal")]
Portal,
#[serde(rename = "lnpay")]
LnPay,
}
impl std::str::FromStr for Service {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"portal" => Self::Portal,
"lnpay" => Self::LnPay,
_ => {
return Err(anyhow::format_err!("Unknown payment service: {}", s));
}
})
}
}
pub fn pay(
donation: &Option<openfare_lib::price::Price>,
items: &Vec<openfare_lib::api::services::basket::Item>,
service: &Option<Service>,
config: &crate::config::Config,
) -> Result<()> {
println!("Found {} packages with OpenFare support.", items.len());
if items.is_empty() {
return Ok(());
}
let service = service.clone().unwrap_or(config.services.default.clone());
match service {
Service::Portal => portal::pay(&items, &config)?,
Service::LnPay => {
let donation_splits = if let Some(donation) = donation {
Some(crate::payments::donation_splits(
&donation,
&items,
lnpay::is_payee_applicable,
)?)
} else {
None
};
lnpay::pay(&donation_splits, &items, &config)?
}
}
Ok(())
}
pub fn lnurl_receive_address(
service: &Service,
config: &crate::config::Config,
) -> Result<Option<String>> {
Ok(match service {
Service::Portal => None,
Service::LnPay => Some(lnpay::lnurl_receive_address(&config)?),
})
}
================================================
FILE: openfare/src/services/portal.rs
================================================
use anyhow::Result;
pub fn pay(
items: &Vec<openfare_lib::api::services::portal::basket::Item>,
config: &crate::config::Config,
) -> Result<()> {
let order = openfare_lib::api::services::portal::basket::Order {
items: items.clone(),
api_key: config.services.portal.api_key.clone(),
};
if order.is_empty() {
println!("No applicable payment plans found.");
return Ok(());
}
let checkout_url = submit_order(&order, &config)?;
println!("Checkout via URL:\n{}", checkout_url);
Ok(())
}
fn submit_order(
order: &openfare_lib::api::services::portal::basket::Order,
config: &crate::config::Config,
) -> Result<url::Url> {
let client = reqwest::blocking::Client::new();
let url = config
.services
.portal
.url
.join(&openfare_lib::api::services::portal::basket::ROUTE)?;
log::debug!("Submitting orders: {:?}", order);
log::debug!("HTTP POST orders to endpoint: {}", url);
let response = client.post(url.clone()).json(&order).send()?;
if response.status() != 200 {
return Err(anyhow::format_err!(
"Portal response error ({status}):\n{url}",
status = response.status(),
url = url.to_string()
));
}
let response: openfare_lib::api::services::portal::basket::Response = response.json()?;
Ok(response.checkout_url)
}
================================================
FILE: openfare/src/setup.rs
================================================
use anyhow::Result;
/// Ensure setup is complete.
pub fn ensure() -> Result<()> {
if !is_complete()? {
setup(false)?;
}
Ok(())
}
/// Setup config directory.
///
/// If config file exists and force is false, file will not be modified.
fn setup_config(paths: &crate::config::Paths, force: bool) -> Result<()> {
std::fs::create_dir_all(&paths.root_directory)?;
std::fs::create_dir_all(&paths.extensions_directory)?;
if force || !paths.config_file.is_file() {
log::debug!("Generating config file: {}", paths.config_file.display());
let mut config = crate::config::Config::default();
crate::extensions::manage::update_config(&mut config)?;
} else {
log::debug!(
"Not overwriting existing config file (--force: {:?}): {}",
force,
paths.config_file.display()
);
}
Ok(())
}
pub fn setup(force: bool) -> Result<()> {
let config_paths = crate::config::Paths::new()?;
log::debug!("Using config paths: {:#?}", config_paths);
setup_config(&config_paths, force)?;
log::debug!("Config setup complete.");
Ok(())
}
/// Returns true if setup is complete, otherwise returns false.
///
/// Checks for existence of config file.
pub fn is_complete() -> Result<bool> {
let config_paths = crate::config::Paths::new()?;
Ok(config_paths.config_file.is_file())
}
================================================
FILE: openfare/tests/integration_tests.rs
================================================
use anyhow::Result;
use std::path::Path;
use std::process::Command;
const TEST_LOCK_FILE_NAME: &str = "test.lock";
const EXEC_PATH: &str = "../target/debug/openfare";
#[test]
fn test_lock_file_creation_and_validation_are_consistent() -> Result<()> {
let tmp_dir = tempdir::TempDir::new("openfare_integration_test")?;
let tmp_dir = tmp_dir.path().to_path_buf();
assert!(Path::new(EXEC_PATH).exists());
let lock_file_path = tmp_dir.join(TEST_LOCK_FILE_NAME);
let lock_file_path = lock_file_path.to_str().unwrap();
// create the lock file
let output_status = Command::new(EXEC_PATH)
.args(["lock", "new", "--lock-file-path", lock_file_path])
.status()
.expect("execution failed")
.success();
assert!(
output_status,
"Could not create new lock file {}",
lock_file_path
);
// Add a plan
let output_status = Command::new(EXEC_PATH)
.args([
"lock",
"add",
"plan",
"compulsory",
"--price",
"5usd",
"--lock-file-path",
lock_file_path,
])
.status()
.expect("execution failed")
.success();
assert!(
output_status,
"Could not add plan to lock file {}",
lock_file_path
);
// Add a payee
let output_status = Command::new(EXEC_PATH)
.args([
"lock",
"add",
"profile",
"--shares",
"500",
"--label",
"steve",
"--lock-file-path",
lock_file_path,
])
.status()
.expect("execution failed")
.success();
assert!(
output_status,
"Could not add plan to lock file {}",
lock_file_path
);
// Validate the lock file
let output_status = Command::new(EXEC_PATH)
.args(["lock", "validate", "--lock-file-path", lock_file_path])
.status()
.expect("execution failed")
.success();
assert!(output_status, "Invalid lock file: {}", lock_file_path);
Ok(())
}
================================================
FILE: openfare-lib/Cargo.toml
================================================
[package]
name = "openfare-lib"
version = "0.6.2"
authors = ["rndhouse <rndhouse@protonmail.com>"]
edition = "2021"
homepage = "https://openfare.dev"
repository = "https://github.com/openfare/openfare"
license-file = "LICENSE"
description = "OpenFare core library."
[dependencies]
anyhow = "1.0.31"
structopt = "0.3.14"
regex = "1.3.9"
dialoguer = "0.10.0"
env_logger = "0.8.2"
log = "0.4.8"
lazy_static = "1.4.0"
lazy-regex = "2.2.2"
strum = { version = "0.24.0", features = ["derive"] }
url = { version = "2.1.1", features = ["serde"] }
reqwest = { version = "0.11.0", features = ["blocking"] }
chrono = "0.4"
serde = { version = "1.0.104", features = ["derive"] }
serde_json = "1.0.48"
jsonschema = "0.15.1"
bincode = "1.3.3"
hex = "0.4.3"
uuid = { version = "0.8.2", features = ["serde", "v4"] }
rust_decimal = "1.22.0"
zip = "0.5.10"
flate2 = "1.0.14"
tar = "0.4.33"
# Add openssl-sys as a direct dependency so it can be cross compiled to
# x86_64-unknown-linux-musl using the "vendored" feature below
openssl-sys = "0.9.72"
[features]
# Force openssl-sys to statically link in the openssl library. Necessary when
# cross compiling to x86_64-unknown-linux-musl.
vendored = ["openssl-sys/vendored"]
================================================
FILE: openfare-lib/LICENSE
================================================
MIT License
Copyright (c) 2022 OpenFare LTD (UK)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: openfare-lib/src/api/mod.rs
================================================
pub mod services;
lazy_static! {
pub static ref ROUTE: String = "/api/v1".to_string();
}
================================================
FILE: openfare-lib/src/api/services/basket.rs
================================================
pub type ExtensionName = String;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Item {
pub package: crate::package::Package,
pub extension_name: ExtensionName,
pub plans: std::collections::BTreeMap<String, crate::lock::plan::Plan>,
pub total_price: crate::price::Price,
pub payees: crate::lock::payee::Payees,
pub shares: Option<crate::lock::shares::Shares>,
}
impl Item {
// TODO: replace valid_payees(.) with lock.validate(.)
/// Returns valid payees.
///
/// A payee is valid if it has allocated shares or if shares are undefined.
pub fn valid_payees(&self) -> crate::lock::payee::Payees {
if let Some(shares) = &self.shares {
let payee_labels = shares
.iter()
.map(|(label, _)| label)
.collect::<std::collections::BTreeSet<_>>();
let payees = self
.payees
.iter()
.filter(|(label, _payee)| payee_labels.contains(label))
.map(|(label, payee)| (label.clone(), payee.clone()))
.collect();
payees
} else {
// If shares unspecified, all payees are applicable.
self.payees.clone()
}
}
}
================================================
FILE: openfare-lib/src/api/services/mod.rs
================================================
pub mod basket;
pub mod portal;
lazy_static! {
pub static ref ROUTE: String = format!("{}/services", super::ROUTE.as_str());
}
================================================
FILE: openfare-lib/src/api/services/portal/basket.rs
================================================
lazy_static! {
pub static ref ROUTE: String = format!("{}/basket", super::ROUTE.as_str());
}
pub use super::super::basket::Item;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Order {
pub items: Vec<Item>,
pub api_key: super::ApiKey,
}
impl Order {
/// Order is empty if no plan in any item.
pub fn is_empty(&self) -> bool {
self.items.iter().all(|item| item.plans.is_empty())
}
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct Response {
pub checkout_url: url::Url,
}
================================================
FILE: openfare-lib/src/api/services/portal/mod.rs
================================================
pub mod basket;
lazy_static! {
pub static ref ROUTE: String = format!("{}/portal", super::ROUTE.as_str());
}
pub type ApiKey = String;
================================================
FILE: openfare-lib/src/common/fs/archive.rs
================================================
use anyhow::{format_err, Result};
use std::convert::TryFrom;
use std::io::Write;
#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)]
pub enum ArchiveType {
Zip,
TarGz,
Tgz,
Unknown,
}
impl std::convert::TryFrom<&std::path::PathBuf> for ArchiveType {
type Error = anyhow::Error;
fn try_from(path: &std::path::PathBuf) -> Result<Self, Self::Error> {
Ok(match get_file_extension(&path)?.as_str() {
"zip" => Self::Zip,
"tar.gz" => Self::TarGz,
"tgz" => Self::Tgz,
_ => Self::Unknown,
})
}
}
impl ArchiveType {
pub fn try_to_string(&self) -> Result<String> {
Ok(match self {
ArchiveType::Zip => "zip",
ArchiveType::TarGz => "tar.gz",
ArchiveType::Tgz => "tgz",
ArchiveType::Unknown => {
return Err(format_err!(
"Failed to convert unknown archive type into string."
))
}
}
.to_string())
}
}
/// Extract and return archive file extension from given path.
fn get_file_extension(path: &std::path::PathBuf) -> Result<String> {
if path
.to_str()
.ok_or(format_err!("Failed to parse URL path as str."))?
.ends_with(".tar.gz")
{
return Ok("tar.gz".to_string());
}
Ok(path
.extension()
.unwrap_or(std::ffi::OsString::from("").as_os_str())
.to_str()
.ok_or(format_err!(
"Failed to parse file extension unicode characters."
))?
.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_correct_extension_extracted_for_tar_gz() -> Result<()> {
let result = get_file_extension(&std::path::PathBuf::from("/d3/d3-4.10.0.tar.gz"))?;
let expected = "tar.gz".to_string();
assert!(result == expected);
Ok(())
}
}
pub fn extract(
archive_path: &std::path::PathBuf,
destination_directory: &std::path::PathBuf,
) -> Result<std::path::PathBuf> {
log::debug!("Extracting archive: {}", archive_path.display());
let archive_type = ArchiveType::try_from(archive_path)?;
let workspace_directory = match archive_type {
ArchiveType::Zip => extract_zip(&archive_path, &destination_directory)?,
ArchiveType::Tgz | ArchiveType::TarGz => {
extract_tar_gz(&archive_path, &destination_directory)?
}
ArchiveType::Unknown => {
return Err(format_err!(
"Archive extraction failed. Unsupported archive file type: {}",
archive_path.display()
));
}
};
log::debug!(
"Archive extraction complete. Workspace directory: {}",
workspace_directory.display()
);
Ok(workspace_directory)
}
fn extract_zip(
archive_path: &std::path::PathBuf,
destination_directory: &std::path::PathBuf,
) -> Result<std::path::PathBuf> {
let file = std::fs::File::open(&archive_path)?;
let mut archive = zip::ZipArchive::new(file)?;
let extracted_directory = destination_directory.join(
archive
.by_index(0)?
.enclosed_name()
.ok_or(format_err!(
"Archive is unexpectedly empty: {}",
archive_path.display()
))?
.to_path_buf(),
);
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
let output_path = match file.enclosed_name() {
Some(path) => path.to_owned(),
None => continue,
};
let output_path = destination_directory.join(output_path);
if (&*file.name()).ends_with('/') {
std::fs::create_dir_all(&output_path)?;
} else {
if let Some(parent) = output_path.parent() {
if !parent.exists() {
std::fs::create_dir_all(&parent)?;
}
}
let mut output_file = std::fs::File::create(&output_path)?;
std::io::copy(&mut file, &mut output_file)?;
}
}
Ok(extracted_directory)
}
/// Extract .tar.gz archives.
///
/// Note that .tgz archives are the same as .tar.gz archives.
pub fn extract_tar_gz(
archive_path: &std::path::PathBuf,
destination_directory: &std::path::PathBuf,
) -> Result<std::path::PathBuf> {
let top_directory_name = get_tar_top_directory_name(&archive_path)?;
let file = std::fs::File::open(archive_path)?;
let decoder = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(decoder);
archive.unpack(&destination_directory)?;
let workspace_directory = if let Some(top_directory_name) = top_directory_name {
log::debug!(
"Found archive top level directory name: {}",
top_directory_name
);
let workspace_directory = destination_directory.join(top_directory_name);
workspace_directory
} else {
log::debug!("Archive top level directory not found. Creating stand-in.");
// Create temporary workspace directory with unique name.
let uuid = uuid::Uuid::new_v4();
let mut encode_buffer = uuid::Uuid::encode_buffer();
let uuid = uuid.to_hyphenated().encode_lower(&mut encode_buffer);
let workspace_directory_name = "openfare-workspace-".to_string() + uuid;
let workspace_directory = destination_directory.join(workspace_directory_name);
std::fs::create_dir(&workspace_directory)?;
let paths = std::fs::read_dir(destination_directory)?;
for path in paths {
let file_name = path?.file_name();
let path = destination_directory.join(&file_name);
if path == workspace_directory || &path == archive_path {
continue;
}
std::fs::rename(&path, workspace_directory.join(&file_name))?;
}
workspace_directory
};
log::debug!(
"Using workspace directory: {}",
workspace_directory.display()
);
Ok(workspace_directory)
}
/// Returns the top level directory name from within the given archive.
///
/// This function advances the archive's position counter.
/// The archive can not be unpacked after this operation, it is therefore dropped.
fn get_tar_top_directory_name(archive_path: &std::path::PathBuf) -> Result<Option<String>> {
let file = std::fs::File::open(archive_path)?;
let decoder = flate2::read::GzDecoder::new(file);
let mut archive = tar::Archive::new(decoder);
let first_archive_entry = archive
.entries()?
.nth(0)
.ok_or(format_err!("Archive empty."))??;
let first_archive_entry = (*first_archive_entry.path()?).to_path_buf();
let top_directory_name = first_archive_entry
.components()
.next()
.ok_or(format_err!("Archive empty."))?
.as_os_str()
.to_str()
.ok_or(format_err!("Failed to parse archive's first path."))?;
Ok(if top_directory_name == "/" {
None
} else {
Some(top_directory_name.to_string())
})
}
pub fn download(target_url: &url::Url, destination_path: &std::path::PathBuf) -> Result<()> {
log::debug!(
"Downloading archive to destination path: {}",
destination_path.display()
);
let response = reqwest::blocking::get(target_url.clone())?;
let mut file = std::fs::File::create(&destination_path)?;
let content = response.bytes()?;
file.write_all(&content)?;
file.sync_all()?;
log::debug!("Finished writing archive.");
Ok(())
}
================================================
FILE: openfare-lib/src/common/fs/mod.rs
================================================
pub mod archive;
================================================
FILE: openfare-lib/src/common/mod.rs
================================================
pub mod fs;
================================================
FILE: openfare-lib/src/extension/commands/common.rs
================================================
use crate::extension::process::ProcessResult;
use anyhow::Result;
pub fn communicate_result<T: serde::Serialize + std::fmt::Debug>(result: Result<T>) -> Result<()> {
let result = match result {
Ok(r) => ProcessResult {
ok: Some(r),
err: None,
},
Err(e) => ProcessResult {
ok: None,
err: Some(e.to_string()),
},
};
log::debug!("Communicating result: {:?}", result);
let result = bincode::serialize(&result).expect("serialize result with bincode");
let result = hex::encode(result);
println!("{}", result);
Ok(())
}
================================================
FILE: openfare-lib/src/extension/commands/mod.rs
================================================
use super::common::Extension;
use anyhow::Result;
use structopt::{self, StructOpt};
mod common;
pub mod package_dependencies_locks;
pub mod project_dependencies_locks;
mod static_data;
#[derive(Debug, StructOpt, Clone)]
enum Command {
/// Get extension static data.
#[structopt(name = "static-data")]
StaticData,
/// Identify package dependencies.
#[structopt(name = package_dependencies_locks::COMMAND_NAME)]
PackageDependenciesLocks(package_dependencies_locks::Arguments),
/// Identify project dependencies.
#[structopt(name = project_dependencies_locks::COMMAND_NAME)]
ProjectDependenciesLocks(project_dependencies_locks::Arguments),
}
fn run_command<T: Extension + std::fmt::Debug>(command: Command, extension: &mut T) -> Result<()> {
match command {
Command::StaticData => {
static_data::run_command(extension)?;
}
Command::PackageDependenciesLocks(args) => {
package_dependencies_locks::run_command(&args, extension)?;
}
Command::ProjectDependenciesLocks(args) => {
project_dependencies_locks::run_command(&args, extension)?;
}
}
Ok(())
}
// TODO: Use extension name and version in cli help.
#[derive(Debug, StructOpt, Clone)]
#[structopt(about = "Micropayment funded software.")]
#[structopt(global_setting = structopt::clap::AppSettings::ColoredHelp)]
#[structopt(global_setting = structopt::clap::AppSettings::DeriveDisplayOrder)]
struct Opts {
#[structopt(subcommand)]
pub command: Command,
}
pub fn run<T: Extension + std::fmt::Debug>(extension: &mut T) -> Result<()> {
let commands = Opts::from_args();
match run_command(commands.command, extension) {
Ok(_) => {}
Err(_e) => std::process::exit(-2),
};
Ok(())
}
================================================
FILE: openfare-lib/src/extension/commands/package_dependencies_locks.rs
================================================
use super::common;
use crate::extension::common::Extension;
use anyhow::Result;
use structopt::{self, StructOpt};
pub const COMMAND_NAME: &str = "package-dependencies-locks";
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
#[structopt(global_setting = structopt::clap::AppSettings::TrailingVarArg)]
pub struct Arguments {
/// Package name.
#[structopt(name = "package-name", long)]
pub package_name: String,
/// Package version.
#[structopt(name = "package-version", long)]
pub package_version: Option<String>,
#[structopt(name = "extension-args", long)]
pub extension_args: Vec<String>,
}
pub fn run_command<T: Extension + std::fmt::Debug>(args: &Arguments, extension: &T) -> Result<()> {
let result = extension.package_dependencies_locks(
&args.package_name,
&args.package_version.as_deref(),
&args.extension_args,
);
common::communicate_result(result)?;
Ok(())
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PackageDependenciesLocks {
pub registry_host_name: String,
pub package_locks: crate::package::PackageLocks,
}
================================================
FILE: openfare-lib/src/extension/commands/project_dependencies_locks.rs
================================================
use super::common;
use crate::extension::common::Extension;
use anyhow::Result;
use structopt::{self, StructOpt};
pub const COMMAND_NAME: &str = "project-dependencies-locks";
#[derive(Debug, StructOpt, Clone)]
#[structopt(
name = "no_version",
no_version,
global_settings = &[structopt::clap::AppSettings::DisableVersion]
)]
#[structopt(global_setting = structopt::clap::AppSettings::TrailingVarArg)]
pub struct Arguments {
/// Working directory.
#[structopt(name = "working-directory", long)]
pub working_directory: String,
#[structopt(name = "extension-args", long)]
pub extension_args: Vec<String>,
}
pub fn run_command<T: Extension + std::fmt::Debug>(args: &Arguments, extension: &T) -> Result<()> {
let working_directory = std::path::PathBuf::from(&args.working_directory);
let result = extension.project_dependencies_locks(&working_directory, &args.extension_args);
common::communicate_result(result)?;
Ok(())
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct ProjectDependenciesLocks {
pub project_path: std::path::PathBuf,
pub package_locks: crate::package::PackageLocks,
}
================================================
FILE: openfare-lib/src/extension/commands/static_data.rs
================================================
use super::common;
use crate::extension::common::Extension;
use anyhow::Result;
pub fn run_command<T: Extension + std::fmt::Debug>(extension: &T) -> Result<()> {
let data = Ok(crate::extension::process::StaticData {
name: extension.name(),
registry_host_names: extension.registries(),
version: extension.version(),
});
common::communicate_result(data)?;
Ok(())
}
================================================
FILE: openfare-lib/src/extension/common.rs
================================================
use anyhow::Result;
#[derive(
Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct VersionError(String);
impl VersionError {
pub fn from_missing_version() -> Self {
Self("Missing version number".to_string())
}
pub fn from_parse_error(raw_version_number: &str) -> Self {
Self(format!("Version parse error: {}", raw_version_number))
}
pub fn message(&self) -> String {
self.0.clone()
}
}
pub type VersionParseResult = std::result::Result<String, VersionError>;
/// A dependency as specified within a dependencies definition file.
#[derive(
Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct Dependency {
pub name: String,
pub version: VersionParseResult,
}
pub trait DependenciesCollection: Sized {
fn registry_host_name(&self) -> &String;
fn dependencies(&self) -> &Vec<Dependency>;
}
/// Package dependencies found by querying a registry.
#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PackageDependencies {
// Package version (included incase version not given as an argument).
pub package_version: VersionParseResult,
/// Dependencies registry host name.
pub registry_host_name: String,
/// Dependencies specified within the dependencies specification file.
pub dependencies: Vec<Dependency>,
}
impl DependenciesCollection for PackageDependencies {
fn registry_host_name(&self) -> &String {
&self.registry_host_name
}
fn dependencies(&self) -> &Vec<Dependency> {
&self.dependencies
}
}
/// A dependencies specification file found from inspecting the local filesystem.
#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FileDefinedDependencies {
/// Absolute file path for dependencies specification file.
pub path: std::path::PathBuf,
/// Dependencies registry host name.
pub registry_host_name: String,
/// Dependencies specified within the dependencies specification file.
pub dependencies: Vec<Dependency>,
}
impl DependenciesCollection for FileDefinedDependencies {
fn registry_host_name(&self) -> &String {
&self.registry_host_name
}
fn dependencies(&self) -> &Vec<Dependency> {
&self.dependencies
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RegistryPackageMetadata {
pub registry_host_name: String,
pub human_url: String,
pub artifact_url: String,
// True if this registry is the primary registry, otherwise false.
pub is_primary: bool,
// Included here incase package version was not given but found.
pub package_version: String,
}
pub trait FromLib: Extension + Send + Sync {
/// Initialize extension from a library.
fn new() -> Self
where
Self: Sized;
}
pub trait FromProcess: Extension + Send + Sync {
/// Initialize extension from a process.
fn from_process(
process_path: &std::path::PathBuf,
extension_config_path: &std::path::PathBuf,
) -> Result<Self>
where
Self: Sized;
}
pub trait Extension: Send + Sync {
// Returns extension short name.
fn name(&self) -> String;
// Returns supported registries host names.
fn registries(&self) -> Vec<String>;
// Returns extension version.
fn version(&self) -> String;
/// Returns OpenFare locks for a package and its dependencies.
fn package_dependencies_locks(
&self,
package_name: &str,
package_version: &Option<&str>,
extension_args: &Vec<String>,
) -> Result<super::commands::package_dependencies_locks::PackageDependenciesLocks>;
/// Return OpenFare locks for a local project's dependencies.
fn project_dependencies_locks(
&self,
working_directory: &std::path::PathBuf,
extension_args: &Vec<String>,
) -> Result<super::commands::project_dependencies_locks::ProjectDependenciesLocks>;
}
================================================
FILE: openfare-lib/src/extension/mod.rs
================================================
pub mod commands;
pub mod common;
pub mod process;
pub use common::{
DependenciesCollection, Dependency, Extension, FileDefinedDependencies, FromLib, FromProcess,
PackageDependencies, RegistryPackageMetadata, VersionParseResult,
};
================================================
FILE: openfare-lib/src/extension/process.rs
================================================
use anyhow::{format_err, Context, Result};
use super::commands;
use super::common;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct StaticData {
pub name: String,
pub registry_host_names: Vec<String>,
pub version: String,
}
#[derive(Debug, Clone)]
pub struct ProcessExtension {
process_path_: std::path::PathBuf,
name_: String,
registry_host_names_: Vec<String>,
version_: String,
}
impl common::FromProcess for ProcessExtension {
fn from_process(
process_path: &std::path::PathBuf,
extension_config_path: &std::path::PathBuf,
) -> Result<Self>
where
Self: Sized,
{
let static_data: StaticData = if extension_config_path.is_file() {
let file = std::fs::File::open(&extension_config_path)?;
let reader = std::io::BufReader::new(file);
serde_json::from_reader(reader)?
} else {
let static_data: Box<StaticData> = run_process(&process_path, &vec!["static-data"])?;
let static_data = *static_data;
let file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(&extension_config_path)
.context(format!(
"Can't open/create file for writing: {}",
extension_config_path.display()
))?;
let writer = std::io::BufWriter::new(file);
serde_json::to_writer(writer, &static_data)?;
static_data
};
Ok(ProcessExtension {
process_path_: process_path.clone(),
name_: static_data.name,
registry_host_names_: static_data.registry_host_names,
version_: static_data.version,
})
}
}
impl common::Extension for ProcessExtension {
fn name(&self) -> String {
self.name_.clone()
}
fn registries(&self) -> Vec<String> {
self.registry_host_names_.clone()
}
fn version(&self) -> String {
self.version_.clone()
}
fn package_dependencies_locks(
&self,
package_name: &str,
package_version: &Option<&str>,
extension_args: &Vec<String>,
) -> Result<commands::package_dependencies_locks::PackageDependenciesLocks> {
let mut args = vec![
super::commands::package_dependencies_locks::COMMAND_NAME,
"--package-name",
package_name,
];
if let Some(package_version) = package_version {
args.push("--package-version");
args.push(package_version);
}
for extension_arg in extension_args {
args.push("--extension-args");
args.push(extension_arg);
}
let output: Box<commands::package_dependencies_locks::PackageDependenciesLocks> =
run_process(&self.process_path_, &args)?;
Ok(*output)
}
/// Returns a list of local package dependencies specification files.
fn project_dependencies_locks(
&self,
working_directory: &std::path::PathBuf,
extension_args: &Vec<String>,
) -> Result<commands::project_dependencies_locks::ProjectDependenciesLocks> {
let working_directory = working_directory.to_str().ok_or(format_err!(
"Failed to parse path into string: {}",
working_directory.display()
))?;
let mut args = vec![
super::commands::project_dependencies_locks::COMMAND_NAME,
"--working-directory",
working_directory,
];
for extension_arg in extension_args {
args.push("--extension-args");
args.push(extension_arg);
}
let output: Box<commands::project_dependencies_locks::ProjectDependenciesLocks> =
run_process(&self.process_path_, &args)?;
Ok(*output)
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ProcessResult<T> {
pub ok: Option<T>,
pub err: Option<String>,
}
fn run_process<'a, T: ?Sized>(process_path: &std::path::PathBuf, args: &Vec<&str>) -> Result<Box<T>>
where
for<'de> T: serde::Deserialize<'de> + 'a,
{
log::debug!(
"Executing extensions process call with arguments\n{:?}",
args
);
let process = process_path.to_str().ok_or(format_err!(
"Failed to parse string from process path: {}",
process_path.display()
))?;
let handle = std::process::Command::new(process)
.args(args)
.stdin(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.output()?;
let stdout = String::from_utf8_lossy(&handle.stdout);
let stdout = stdout.to_string();
let result = hex::decode(&stdout)?.clone();
let process_result: ProcessResult<T> =
bincode::deserialize(&result).expect("deserialize result with bincode");
if let Some(result) = process_result.ok {
Ok(Box::new(result))
} else if let Some(result) = process_result.err {
Err(format_err!(result))
} else {
Err(format_err!("Failed to find ok or err result from process."))
}
}
================================================
FILE: openfare-lib/src/lib.rs
================================================
#[macro_use]
extern crate lazy_static;
pub static HTTP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
pub mod api;
pub mod common;
pub mod extension;
pub mod lock;
pub mod package;
pub mod price;
pub mod profile;
================================================
FILE: openfare-lib/src/lock/mod.rs
================================================
use anyhow::Result;
pub mod payee;
pub mod plan;
mod schema;
pub mod shares;
#[cfg(test)]
mod tests;
pub static FILE_NAME: &str = "OpenFare.lock";
/// A software package's OpenFare lock file (OpenFare.lock).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Lock {
#[serde(rename = "scheme-version")]
pub scheme_version: String,
pub plans: plan::Plans,
pub payees: payee::Payees,
#[serde(skip_serializing_if = "Option::is_none")]
pub shares: Option<shares::Shares>,
}
impl Lock {
/// Validate lock.
pub fn validate(&self) -> Result<()> {
let value = serde_json::to_value(&self)?;
schema::validate(&value)
}
}
impl std::default::Default for Lock {
fn default() -> Self {
Self {
scheme_version: schema::VERSION.to_string(),
plans: plan::Plans::new(),
payees: payee::Payees::new(),
shares: None,
}
}
}
================================================
FILE: openfare-lib/src/lock/payee.rs
================================================
pub type Label = String;
pub type Payees = std::collections::BTreeMap<Label, Payee>;
pub type PaymentMethodName = String;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Payee {
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(flatten)]
pub profile: crate::profile::Profile,
}
pub fn get_lock_payee(
profile: &crate::profile::Profile,
all_lock_payees: &std::collections::BTreeMap<Label, Payee>,
) -> Option<(Label, Payee)> {
for (name, existing_payee) in all_lock_payees {
if profile.unique_id == existing_payee.profile.unique_id {
return Some((name.clone(), existing_payee.clone()));
}
}
None
}
pub fn unique_label(payee_label: &Label, payee: &Payee) -> Label {
let unique_id = payee.profile.unique_id.to_string()[..13].to_string();
format!(
"{payee_label}___{unique_id}",
payee_label = payee_label,
unique_id = unique_id
)
}
================================================
FILE: openfare-lib/src/lock/plan/conditions/common.rs
================================================
use anyhow::{format_err, Result};
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Operator {
GreaterThanEqual,
GreaterThan,
LessThanEqual,
LessThan,
Equal,
}
impl std::string::ToString for Operator {
fn to_string(&self) -> String {
match self {
Self::GreaterThanEqual => ">=",
Self::GreaterThan => ">",
Self::LessThanEqual => "<=",
Self::LessThan => "<",
Self::Equal => "=",
}
.to_string()
}
}
impl std::convert::TryFrom<&str> for Operator {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self> {
Ok(match value {
">=" => Self::GreaterThanEqual,
">" => Self::GreaterThan,
"<=" => Self::LessThanEqual,
"<" => Self::LessThan,
"=" => Self::Equal,
_ => {
return Err(format_err!("Unknown operator: {}", value));
}
})
}
}
pub fn evaluate_operator<T: std::cmp::PartialOrd>(
variable_value: &T,
operator: &Operator,
condition_value: &T,
) -> bool {
match operator {
Operator::GreaterThanEqual => variable_value >= condition_value,
Operator::GreaterThan => variable_value > condition_value,
Operator::LessThanEqual => variable_value <= condition_value,
Operator::LessThan => variable_value < condition_value,
Operator::Equal => variable_value == condition_value,
}
}
pub trait Condition {
fn evaluate(&self, parameters: &super::Parameters) -> Result<bool>;
fn metadata(&self) -> Box<dyn ConditionMetadata>;
}
pub trait ConditionMetadata: std::fmt::Debug {
fn name(&self) -> String;
fn interactive_set_parameter(&self, parameters: &mut super::Parameters) -> Result<()>;
fn is_parameter_set(&self, parameters: &super::Parameters) -> bool;
}
================================================
FILE: openfare-lib/src/lock/plan/conditions/employees_count.rs
================================================
use super::common;
use anyhow::Result;
use strum::IntoEnumIterator;
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EmployeesCount(Range);
#[derive(
Debug,
Clone,
Ord,
PartialOrd,
Eq,
PartialEq,
strum::EnumIter,
serde::Serialize,
serde::Deserialize,
)]
#[serde(into = "String", try_from = "String")]
pub enum Range {
GreaterEqual1To50,
GreaterEqual50To150,
GreaterEqual150To500,
GreaterEqual500To1000,
GreaterEqual1000,
}
impl Range {
pub fn evaluate(&self, employees_count: &Self) -> bool {
self == employees_count
}
}
impl std::string::ToString for Range {
fn to_string(&self) -> String {
match self {
Self::GreaterEqual1To50 => "1 <= count < 50",
Self::GreaterEqual50To150 => "50 <= count < 150",
Self::GreaterEqual150To500 => "150 <= count < 500",
Self::GreaterEqual500To1000 => "500 <= count < 1000",
Self::GreaterEqual1000 => "1000 <= count",
}
.to_string()
}
}
impl Into<String> for Range {
fn into(self) -> String {
self.to_string()
}
}
impl std::convert::TryFrom<&str> for Range {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
for range in Self::iter() {
if range.to_string().as_str() == value {
return Ok(range);
}
}
let error_message = format!(
"Error parsing employees count range: {}\nAccepted values:\n{}",
value,
Self::iter()
.map(|range| range.to_string())
.collect::<Vec<String>>()
.join("\n")
);
Err(anyhow::format_err!(error_message))
}
}
impl std::convert::TryFrom<String> for Range {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
for range in Self::iter() {
if range.to_string().as_str() == value {
return Ok(range);
}
}
let error_message = format!(
"Error parsing employees count range: {}\nAccepted values:\n{}",
value,
Self::iter()
.map(|range| range.to_string())
.collect::<Vec<String>>()
.join("\n")
);
Err(anyhow::format_err!(error_message))
}
}
impl std::convert::TryFrom<&str> for EmployeesCount {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self(Range::try_from(value)?))
}
}
impl common::Condition for EmployeesCount {
fn evaluate(&self, parameters: &crate::lock::plan::conditions::Parameters) -> Result<bool> {
let employees_count = parameters
.employees_count
.as_ref()
.ok_or(anyhow::format_err!(
"Attempting to evaluate plan conditions using unset parameter `{}`.",
self.metadata().name()
))?;
Ok(self.0.evaluate(&employees_count))
}
fn metadata(&self) -> Box<dyn common::ConditionMetadata> {
Box::new(EmployeesCountMetadata) as Box<dyn common::ConditionMetadata>
}
}
#[derive(Debug, Clone)]
struct EmployeesCountMetadata;
impl common::ConditionMetadata for EmployeesCountMetadata {
fn name(&self) -> String {
"employees-count".to_string()
}
fn interactive_set_parameter(
&self,
parameters: &mut crate::lock::plan::conditions::Parameters,
) -> Result<()> {
println!("Select a range for the number of employees (count) within your organization:");
let ranges = Range::iter().collect::<Vec<_>>();
let items = ranges.iter().map(|r| r.to_string()).collect::<Vec<_>>();
let index = dialoguer::Select::new().items(&items).interact()?;
if let Some(range) = ranges.get(index) {
parameters.employees_count = Some(range.clone());
}
Ok(())
}
fn is_parameter_set(&self, parameters: &crate::lock::plan::conditions::Parameters) -> bool {
parameters.employees_count.is_some()
}
}
#[test]
fn test_from_str() -> Result<()> {
use common::Condition;
let range = Range::GreaterEqual1To50;
let mut parameters = crate::lock::plan::conditions::Parameters::default();
parameters.employees_count = Some(range.clone());
let employees_count = EmployeesCount::try_from(range.to_string().as_str())?;
assert!(employees_count.evaluate(¶meters)?);
Ok(())
}
================================================
FILE: openfare-lib/src/lock/plan/conditions/expiration.rs
================================================
use super::common;
use anyhow::Result;
use chrono::{TimeZone, Utc};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Expiration {
time: chrono::DateTime<Utc>,
}
impl std::convert::TryFrom<&str> for Expiration {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let time = parse_value(&value)?;
Ok(Self { time })
}
}
impl common::Condition for Expiration {
fn evaluate(&self, _parameters: &crate::lock::plan::conditions::Parameters) -> Result<bool> {
let current_time = chrono::offset::Utc::now();
let expiration = &self.time;
let result = common::evaluate_operator::<chrono::DateTime<Utc>>(
¤t_time,
&common::Operator::LessThan,
&expiration,
);
Ok(result)
}
fn metadata(&self) -> Box<dyn common::ConditionMetadata> {
Box::new(ExpirationMetadata) as Box<dyn common::ConditionMetadata>
}
}
impl serde::Serialize for Expiration {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(format!("{}", self.time.format("%Y-%m-%d"),).as_str())
}
}
struct Visitor {
marker: std::marker::PhantomData<fn() -> Expiration>,
}
impl Visitor {
fn new() -> Self {
Visitor {
marker: std::marker::PhantomData,
}
}
}
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Expiration;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a string such as '2022-01-31'")
}
fn visit_str<E>(self, value: &str) -> core::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
let time = parse_value(&value).expect("parse expiration condition value");
Ok(Self::Value { time })
}
}
impl<'de> serde::Deserialize<'de> for Expiration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(Visitor::new())
}
}
#[derive(Debug, Clone)]
struct ExpirationMetadata;
impl common::ConditionMetadata for ExpirationMetadata {
fn name(&self) -> String {
"expiration".to_string()
}
fn interactive_set_parameter(
&self,
_parameters: &mut crate::lock::plan::conditions::Parameters,
) -> Result<()> {
Ok(())
}
fn is_parameter_set(&self, _parameters: &crate::lock::plan::conditions::Parameters) -> bool {
true
}
}
fn parse_value(value: &str) -> Result<chrono::DateTime<Utc>> {
let date = chrono::NaiveDate::parse_from_str(&value, "%Y-%m-%d")?;
let time = naive_date_to_utc(&date)?;
Ok(time)
}
fn naive_date_to_utc(date: &chrono::NaiveDate) -> Result<chrono::DateTime<Utc>> {
// The known 1 hour time offset in seconds
let tz_offset
gitextract_d4f26qpi/ ├── .dockerignore ├── .github/ │ └── workflows/ │ └── main.yaml ├── Cargo.toml ├── README.md ├── build/ │ └── musl/ │ ├── Containerfile │ └── build-release.sh ├── openfare/ │ ├── Cargo.toml │ ├── LICENSE │ ├── README.md │ ├── src/ │ │ ├── command/ │ │ │ ├── config.rs │ │ │ ├── extensions.rs │ │ │ ├── lock/ │ │ │ │ ├── common.rs │ │ │ │ ├── condition.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── plan.rs │ │ │ │ ├── profile.rs │ │ │ │ └── validate.rs │ │ │ ├── mod.rs │ │ │ ├── pay.rs │ │ │ ├── price/ │ │ │ │ ├── common.rs │ │ │ │ ├── format/ │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── table.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── package.rs │ │ │ │ └── project.rs │ │ │ ├── profile/ │ │ │ │ ├── mod.rs │ │ │ │ ├── payment_method/ │ │ │ │ │ ├── btc_lightning.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── paypal.rs │ │ │ │ └── push.rs │ │ │ └── service/ │ │ │ ├── lnpay.rs │ │ │ └── mod.rs │ │ ├── common/ │ │ │ ├── fs/ │ │ │ │ └── mod.rs │ │ │ ├── git.rs │ │ │ ├── json.rs │ │ │ ├── mod.rs │ │ │ └── url.rs │ │ ├── config/ │ │ │ ├── core.rs │ │ │ ├── extensions.rs │ │ │ ├── mod.rs │ │ │ ├── paths.rs │ │ │ ├── profile.rs │ │ │ └── services/ │ │ │ ├── lnpay.rs │ │ │ ├── mod.rs │ │ │ └── portal.rs │ │ ├── extensions/ │ │ │ ├── common.rs │ │ │ ├── manage/ │ │ │ │ ├── github.rs │ │ │ │ ├── mod.rs │ │ │ │ └── process.rs │ │ │ ├── mod.rs │ │ │ ├── package.rs │ │ │ └── project.rs │ │ ├── handles/ │ │ │ ├── lock.rs │ │ │ ├── mod.rs │ │ │ └── profile.rs │ │ ├── main.rs │ │ ├── payments.rs │ │ ├── services/ │ │ │ ├── lnpay.rs │ │ │ ├── mod.rs │ │ │ └── portal.rs │ │ └── setup.rs │ └── tests/ │ └── integration_tests.rs ├── openfare-lib/ │ ├── Cargo.toml │ ├── LICENSE │ └── src/ │ ├── api/ │ │ ├── mod.rs │ │ └── services/ │ │ ├── basket.rs │ │ ├── mod.rs │ │ └── portal/ │ │ ├── basket.rs │ │ └── mod.rs │ ├── common/ │ │ ├── fs/ │ │ │ ├── archive.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── extension/ │ │ ├── commands/ │ │ │ ├── common.rs │ │ │ ├── mod.rs │ │ │ ├── package_dependencies_locks.rs │ │ │ ├── project_dependencies_locks.rs │ │ │ └── static_data.rs │ │ ├── common.rs │ │ ├── mod.rs │ │ └── process.rs │ ├── lib.rs │ ├── lock/ │ │ ├── mod.rs │ │ ├── payee.rs │ │ ├── plan/ │ │ │ ├── conditions/ │ │ │ │ ├── common.rs │ │ │ │ ├── employees_count.rs │ │ │ │ ├── expiration.rs │ │ │ │ ├── for_profit.rs │ │ │ │ ├── mod.rs │ │ │ │ └── parameters.rs │ │ │ └── mod.rs │ │ ├── schema/ │ │ │ ├── mod.rs │ │ │ └── schema.json │ │ ├── shares.rs │ │ └── tests.rs │ ├── package.rs │ ├── price/ │ │ ├── conversions.rs │ │ └── mod.rs │ └── profile/ │ ├── mod.rs │ └── payment_methods.rs └── rust-toolchain
SYMBOL INDEX (564 symbols across 78 files)
FILE: openfare-lib/src/api/services/basket.rs
type ExtensionName (line 1) | pub type ExtensionName = String;
type Item (line 4) | pub struct Item {
method valid_payees (line 18) | pub fn valid_payees(&self) -> crate::lock::payee::Payees {
FILE: openfare-lib/src/api/services/portal/basket.rs
type Order (line 7) | pub struct Order {
method is_empty (line 14) | pub fn is_empty(&self) -> bool {
type Response (line 20) | pub struct Response {
FILE: openfare-lib/src/api/services/portal/mod.rs
type ApiKey (line 7) | pub type ApiKey = String;
FILE: openfare-lib/src/common/fs/archive.rs
type ArchiveType (line 6) | pub enum ArchiveType {
type Error (line 14) | type Error = anyhow::Error;
method try_from (line 16) | fn try_from(path: &std::path::PathBuf) -> Result<Self, Self::Error> {
method try_to_string (line 27) | pub fn try_to_string(&self) -> Result<String> {
function get_file_extension (line 43) | fn get_file_extension(path: &std::path::PathBuf) -> Result<String> {
function test_correct_extension_extracted_for_tar_gz (line 67) | fn test_correct_extension_extracted_for_tar_gz() -> Result<()> {
function extract (line 75) | pub fn extract(
function extract_zip (line 100) | fn extract_zip(
function extract_tar_gz (line 144) | pub fn extract_tar_gz(
function get_tar_top_directory_name (line 199) | fn get_tar_top_directory_name(archive_path: &std::path::PathBuf) -> Resu...
function download (line 225) | pub fn download(target_url: &url::Url, destination_path: &std::path::Pat...
FILE: openfare-lib/src/extension/commands/common.rs
function communicate_result (line 4) | pub fn communicate_result<T: serde::Serialize + std::fmt::Debug>(result:...
FILE: openfare-lib/src/extension/commands/mod.rs
type Command (line 11) | enum Command {
function run_command (line 25) | fn run_command<T: Extension + std::fmt::Debug>(command: Command, extensi...
type Opts (line 47) | struct Opts {
function run (line 52) | pub fn run<T: Extension + std::fmt::Debug>(extension: &mut T) -> Result<...
FILE: openfare-lib/src/extension/commands/package_dependencies_locks.rs
constant COMMAND_NAME (line 6) | pub const COMMAND_NAME: &str = "package-dependencies-locks";
type Arguments (line 15) | pub struct Arguments {
function run_command (line 28) | pub fn run_command<T: Extension + std::fmt::Debug>(args: &Arguments, ext...
type PackageDependenciesLocks (line 39) | pub struct PackageDependenciesLocks {
FILE: openfare-lib/src/extension/commands/project_dependencies_locks.rs
constant COMMAND_NAME (line 6) | pub const COMMAND_NAME: &str = "project-dependencies-locks";
type Arguments (line 15) | pub struct Arguments {
function run_command (line 24) | pub fn run_command<T: Extension + std::fmt::Debug>(args: &Arguments, ext...
type ProjectDependenciesLocks (line 32) | pub struct ProjectDependenciesLocks {
FILE: openfare-lib/src/extension/commands/static_data.rs
function run_command (line 5) | pub fn run_command<T: Extension + std::fmt::Debug>(extension: &T) -> Res...
FILE: openfare-lib/src/extension/common.rs
type VersionError (line 6) | pub struct VersionError(String);
method from_missing_version (line 9) | pub fn from_missing_version() -> Self {
method from_parse_error (line 13) | pub fn from_parse_error(raw_version_number: &str) -> Self {
method message (line 17) | pub fn message(&self) -> String {
type VersionParseResult (line 22) | pub type VersionParseResult = std::result::Result<String, VersionError>;
type Dependency (line 28) | pub struct Dependency {
type DependenciesCollection (line 33) | pub trait DependenciesCollection: Sized {
method registry_host_name (line 34) | fn registry_host_name(&self) -> &String;
method dependencies (line 35) | fn dependencies(&self) -> &Vec<Dependency>;
method registry_host_name (line 52) | fn registry_host_name(&self) -> &String {
method dependencies (line 55) | fn dependencies(&self) -> &Vec<Dependency> {
method registry_host_name (line 74) | fn registry_host_name(&self) -> &String {
method dependencies (line 77) | fn dependencies(&self) -> &Vec<Dependency> {
type PackageDependencies (line 40) | pub struct PackageDependencies {
type FileDefinedDependencies (line 62) | pub struct FileDefinedDependencies {
type RegistryPackageMetadata (line 83) | pub struct RegistryPackageMetadata {
type FromLib (line 93) | pub trait FromLib: Extension + Send + Sync {
method new (line 95) | fn new() -> Self
type FromProcess (line 100) | pub trait FromProcess: Extension + Send + Sync {
method from_process (line 102) | fn from_process(
type Extension (line 110) | pub trait Extension: Send + Sync {
method name (line 112) | fn name(&self) -> String;
method registries (line 115) | fn registries(&self) -> Vec<String>;
method version (line 118) | fn version(&self) -> String;
method package_dependencies_locks (line 121) | fn package_dependencies_locks(
method project_dependencies_locks (line 129) | fn project_dependencies_locks(
FILE: openfare-lib/src/extension/process.rs
type StaticData (line 7) | pub struct StaticData {
type ProcessExtension (line 14) | pub struct ProcessExtension {
method from_process (line 22) | fn from_process(
method name (line 60) | fn name(&self) -> String {
method registries (line 64) | fn registries(&self) -> Vec<String> {
method version (line 68) | fn version(&self) -> String {
method package_dependencies_locks (line 72) | fn package_dependencies_locks(
method project_dependencies_locks (line 97) | fn project_dependencies_locks(
type ProcessResult (line 123) | pub struct ProcessResult<T> {
function run_process (line 128) | fn run_process<'a, T: ?Sized>(process_path: &std::path::PathBuf, args: &...
FILE: openfare-lib/src/lock/mod.rs
type Lock (line 16) | pub struct Lock {
method validate (line 27) | pub fn validate(&self) -> Result<()> {
method default (line 34) | fn default() -> Self {
FILE: openfare-lib/src/lock/payee.rs
type Label (line 1) | pub type Label = String;
type Payees (line 2) | pub type Payees = std::collections::BTreeMap<Label, Payee>;
type PaymentMethodName (line 3) | pub type PaymentMethodName = String;
type Payee (line 7) | pub struct Payee {
function get_lock_payee (line 14) | pub fn get_lock_payee(
function unique_label (line 26) | pub fn unique_label(payee_label: &Label, payee: &Payee) -> Label {
FILE: openfare-lib/src/lock/plan/conditions/common.rs
type Operator (line 4) | pub enum Operator {
method to_string (line 15) | fn to_string(&self) -> String {
type Error (line 28) | type Error = anyhow::Error;
method try_from (line 29) | fn try_from(value: &str) -> Result<Self> {
function evaluate_operator (line 43) | pub fn evaluate_operator<T: std::cmp::PartialOrd>(
type Condition (line 59) | pub trait Condition {
method evaluate (line 60) | fn evaluate(&self, parameters: &super::Parameters) -> Result<bool>;
method metadata (line 61) | fn metadata(&self) -> Box<dyn ConditionMetadata>;
type ConditionMetadata (line 64) | pub trait ConditionMetadata: std::fmt::Debug {
method name (line 65) | fn name(&self) -> String;
method interactive_set_parameter (line 66) | fn interactive_set_parameter(&self, parameters: &mut super::Parameters...
method is_parameter_set (line 67) | fn is_parameter_set(&self, parameters: &super::Parameters) -> bool;
FILE: openfare-lib/src/lock/plan/conditions/employees_count.rs
type EmployeesCount (line 7) | pub struct EmployeesCount(Range);
type Error (line 96) | type Error = anyhow::Error;
method try_from (line 97) | fn try_from(value: &str) -> Result<Self, Self::Error> {
method evaluate (line 103) | fn evaluate(&self, parameters: &crate::lock::plan::conditions::Paramet...
method metadata (line 114) | fn metadata(&self) -> Box<dyn common::ConditionMetadata> {
type Range (line 21) | pub enum Range {
method evaluate (line 30) | pub fn evaluate(&self, employees_count: &Self) -> bool {
method to_string (line 36) | fn to_string(&self) -> String {
method into (line 49) | fn into(self) -> String {
type Error (line 55) | type Error = anyhow::Error;
method try_from (line 56) | fn try_from(value: &str) -> Result<Self, Self::Error> {
type Error (line 75) | type Error = anyhow::Error;
method try_from (line 76) | fn try_from(value: String) -> Result<Self, Self::Error> {
type EmployeesCountMetadata (line 120) | struct EmployeesCountMetadata;
method name (line 123) | fn name(&self) -> String {
method interactive_set_parameter (line 127) | fn interactive_set_parameter(
method is_parameter_set (line 142) | fn is_parameter_set(&self, parameters: &crate::lock::plan::conditions:...
function test_from_str (line 148) | fn test_from_str() -> Result<()> {
FILE: openfare-lib/src/lock/plan/conditions/expiration.rs
type Expiration (line 7) | pub struct Expiration {
type Error (line 12) | type Error = anyhow::Error;
method try_from (line 13) | fn try_from(value: &str) -> Result<Self, Self::Error> {
method evaluate (line 20) | fn evaluate(&self, _parameters: &crate::lock::plan::conditions::Parame...
method metadata (line 31) | fn metadata(&self) -> Box<dyn common::ConditionMetadata> {
method serialize (line 37) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
method deserialize (line 74) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
type Visitor (line 45) | struct Visitor {
method new (line 50) | fn new() -> Self {
type Value (line 58) | type Value = Expiration;
method expecting (line 60) | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::...
method visit_str (line 64) | fn visit_str<E>(self, value: &str) -> core::result::Result<Self::Value...
type ExpirationMetadata (line 83) | struct ExpirationMetadata;
method name (line 86) | fn name(&self) -> String {
method interactive_set_parameter (line 90) | fn interactive_set_parameter(
method is_parameter_set (line 97) | fn is_parameter_set(&self, _parameters: &crate::lock::plan::conditions...
function parse_value (line 102) | fn parse_value(value: &str) -> Result<chrono::DateTime<Utc>> {
function naive_date_to_utc (line 108) | fn naive_date_to_utc(date: &chrono::NaiveDate) -> Result<chrono::DateTim...
function test_evaluate_cases (line 123) | fn test_evaluate_cases() -> Result<()> {
function test_parse_str (line 132) | fn test_parse_str() -> Result<()> {
FILE: openfare-lib/src/lock/plan/conditions/for_profit.rs
type ForProfit (line 5) | pub struct ForProfit {
method new (line 11) | pub fn new() -> Self {
method evaluate (line 17) | fn evaluate(&self, parameters: &crate::lock::plan::conditions::Paramet...
method metadata (line 24) | fn metadata(&self) -> Box<dyn common::ConditionMetadata> {
type ForProfitMetadata (line 30) | struct ForProfitMetadata;
method name (line 33) | fn name(&self) -> String {
method interactive_set_parameter (line 37) | fn interactive_set_parameter(
method is_parameter_set (line 52) | fn is_parameter_set(&self, parameters: &crate::lock::plan::conditions:...
function test_evaluate_cases (line 58) | fn test_evaluate_cases() -> Result<()> {
FILE: openfare-lib/src/lock/plan/conditions/mod.rs
type Conditions (line 16) | pub struct Conditions {
method as_vec (line 28) | pub fn as_vec(&self) -> Vec<Box<dyn Condition>> {
method metadata (line 42) | pub fn metadata(&self) -> Vec<Box<dyn ConditionMetadata>> {
method evaluate (line 49) | pub fn evaluate(&self, parameters: &crate::lock::plan::conditions::Par...
method set_some (line 57) | pub fn set_some(&mut self, incoming: &Self) {
FILE: openfare-lib/src/lock/plan/conditions/parameters.rs
type Parameters (line 5) | pub struct Parameters {
method default (line 17) | fn default() -> Self {
method fmt (line 27) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
function check_set (line 40) | pub fn check_set(
FILE: openfare-lib/src/lock/plan/mod.rs
type Id (line 7) | pub type Id = String;
type Plans (line 8) | pub type Plans = std::collections::BTreeMap<Id, Plan>;
type Shares (line 9) | pub type Shares = std::collections::BTreeMap<payee::Label, u64>;
type SplitPercent (line 10) | pub type SplitPercent = String;
function next_id (line 12) | pub fn next_id(plans: &Plans) -> Result<Id> {
type PlanType (line 26) | pub enum PlanType {
type Err (line 32) | type Err = anyhow::Error;
method from_str (line 33) | fn from_str(value: &str) -> std::result::Result<Self, anyhow::Error> {
type Plan (line 49) | pub struct Plan {
method is_applicable (line 59) | pub fn is_applicable(
function filter_applicable (line 74) | pub fn filter_applicable(
FILE: openfare-lib/src/lock/schema/mod.rs
function validate (line 13) | pub fn validate(value: &serde_json::Value) -> Result<()> {
function validation_errors_to_string (line 24) | fn validation_errors_to_string(errors: jsonschema::ErrorIterator) -> Str...
FILE: openfare-lib/src/lock/shares.rs
type Shares (line 3) | pub type Shares = std::collections::BTreeMap<payee::Label, u64>;
FILE: openfare-lib/src/lock/tests.rs
function generate_minimal_valid_lock (line 11) | fn generate_minimal_valid_lock() -> Value {
function generate_test_lock (line 43) | fn generate_test_lock() -> Value {
function generate_test_lock_file_with_typo (line 100) | fn generate_test_lock_file_with_typo() -> Value {
function generate_test_lock_file_with_non_number_plan_key (line 110) | fn generate_test_lock_file_with_non_number_plan_key() -> Value {
function generate_test_lock_file_with_past_zero_plan_key (line 120) | fn generate_test_lock_file_with_past_zero_plan_key() -> Value {
function generate_test_lock_file_with_negative_shares (line 130) | fn generate_test_lock_file_with_negative_shares() -> Value {
function generate_test_lock_file_with_invalid_plan_condition (line 139) | fn generate_test_lock_file_with_invalid_plan_condition() -> Value {
function generate_test_lock_file_with_more_share_labels_than_payees (line 150) | fn generate_test_lock_file_with_more_share_labels_than_payees() -> Value {
function generate_test_lock_file_with_incorrect_share_label (line 158) | fn generate_test_lock_file_with_incorrect_share_label() -> Value {
function print_if_err (line 169) | fn print_if_err(result: Result<()>) -> Result<()> {
function validate_lock_file_json_and_print_errs (line 177) | fn validate_lock_file_json_and_print_errs(val: Value) -> Result<()> {
function test_empty_lock_file_not_valid (line 184) | fn test_empty_lock_file_not_valid() {
function test_almost_empty_lock_file_not_valid (line 188) | fn test_almost_empty_lock_file_not_valid() {
function test_minimal_lock_file_is_valid (line 192) | fn test_minimal_lock_file_is_valid() {
function test_more_complex_lock_file_is_valid (line 196) | fn test_more_complex_lock_file_is_valid() {
function test_minor_typo_not_valid (line 200) | fn test_minor_typo_not_valid() {
function test_plan_numbers_can_start_past_zero (line 204) | fn test_plan_numbers_can_start_past_zero() {
function test_plan_key_must_be_number (line 211) | fn test_plan_key_must_be_number() {
function test_valid_payment_conditions_only (line 219) | fn test_valid_payment_conditions_only() {
function test_shares_cannot_be_negative (line 226) | fn test_shares_cannot_be_negative() {
function test_cannot_have_more_share_labels_than_payees (line 234) | fn test_cannot_have_more_share_labels_than_payees() {
function test_share_labels_must_match_payees (line 242) | fn test_share_labels_must_match_payees() {
FILE: openfare-lib/src/package.rs
type Package (line 5) | pub struct Package {
type DependenciesLocks (line 11) | pub type DependenciesLocks = std::collections::BTreeMap<Package, Option<...
type PackageLocks (line 16) | pub struct PackageLocks {
method has_locks (line 23) | pub fn has_locks(&self) -> bool {
method conditions_metadata (line 28) | pub fn conditions_metadata(&self) -> Vec<Box<dyn lock::plan::condition...
method filter_valid_dependencies_locks (line 58) | pub fn filter_valid_dependencies_locks(&self) -> Self {
FILE: openfare-lib/src/price/conversions.rs
function one_btc_in_usd (line 4) | pub fn one_btc_in_usd() -> Result<rust_decimal::Decimal> {
function usd_to_btc (line 18) | pub fn usd_to_btc(usd_price: &Price) -> Result<Price> {
function sats_to_btc (line 33) | pub fn sats_to_btc(sats_price: &Price) -> Result<Price> {
function usd_to_sats (line 48) | pub fn usd_to_sats(usd_price: &Price) -> Result<Price> {
function btc_to_sats (line 53) | pub fn btc_to_sats(btc_price: &Price) -> Result<Price> {
function btc_to_usd (line 68) | pub fn btc_to_usd(btc_price: &Price) -> Result<Price> {
function sats_to_usd (line 80) | pub fn sats_to_usd(sats_price: &Price) -> Result<Price> {
function test_sats_to_btc (line 90) | fn test_sats_to_btc() -> anyhow::Result<()> {
function test_btc_to_sats (line 99) | fn test_btc_to_sats() -> anyhow::Result<()> {
FILE: openfare-lib/src/price/mod.rs
type Currency (line 9) | pub enum Currency {
method decimal_points (line 17) | pub fn decimal_points(&self) -> u32 {
method to_symbol (line 25) | pub fn to_symbol(&self) -> String {
method default (line 36) | fn default() -> Self {
type Error (line 42) | type Error = anyhow::Error;
method try_from (line 43) | fn try_from(value: &str) -> Result<Self, Self::Error> {
method fmt (line 57) | fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Re...
type Quantity (line 67) | pub type Quantity = rust_decimal::Decimal;
type Price (line 70) | pub struct Price {
method to_symbolic (line 76) | pub fn to_symbolic(&self) -> String {
method to_btc (line 97) | pub fn to_btc(&self) -> Result<Price> {
method to_sats (line 105) | pub fn to_sats(&self) -> Result<Price> {
method to_usd (line 113) | pub fn to_usd(&self) -> Result<Price> {
method sum (line 123) | fn sum<I>(iter: I) -> Self
type Error (line 141) | type Error = anyhow::Error;
method try_from (line 142) | fn try_from(value: &str) -> Result<Self, Self::Error> {
type Err (line 159) | type Err = anyhow::Error;
method from_str (line 160) | fn from_str(s: &str) -> Result<Self, Self::Err> {
method fmt (line 166) | fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Re...
method serialize (line 178) | fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
method deserialize (line 242) | fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
type Visitor (line 194) | struct Visitor {
method new (line 199) | fn new() -> Self {
type Value (line 207) | type Value = Price;
method expecting (line 209) | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::...
method visit_str (line 213) | fn visit_str<E>(self, v: &str) -> core::result::Result<Self::Value, E>
function parse_quantity (line 250) | fn parse_quantity(regex_capture: &Option<regex::Match>) -> Result<rust_d...
function parse_currency (line 258) | fn parse_currency(regex_capture: &Option<regex::Match>) -> Result<Curren...
function test_to_symbolic_usd (line 278) | fn test_to_symbolic_usd() -> anyhow::Result<()> {
function test_to_symbolic_sat (line 287) | fn test_to_symbolic_sat() -> anyhow::Result<()> {
function test_serialize_usd (line 296) | fn test_serialize_usd() -> anyhow::Result<()> {
function test_serialize_btc (line 311) | fn test_serialize_btc() -> anyhow::Result<()> {
function test_str_price_correctly_parsed (line 327) | fn test_str_price_correctly_parsed() -> anyhow::Result<()> {
function test_decimal_price_correctly_parsed (line 338) | fn test_decimal_price_correctly_parsed() -> anyhow::Result<()> {
function test_usd_to_btc (line 349) | fn test_usd_to_btc() -> anyhow::Result<()> {
FILE: openfare-lib/src/profile/mod.rs
constant FILE_NAME (line 4) | pub const FILE_NAME: &'static str = "openfare-profile.json";
type RemoteProfile (line 7) | pub struct RemoteProfile {
method from (line 13) | fn from(profile: Profile) -> Self {
type Profile (line 20) | pub struct Profile {
method check (line 28) | pub fn check(&mut self) -> Result<()> {
method payment_methods (line 33) | pub fn payment_methods(&self) -> Result<Vec<Box<dyn payment_methods::P...
method set_payment_method (line 54) | pub fn set_payment_method(
method remove_payment_method (line 65) | pub fn remove_payment_method(&mut self, method: &payment_methods::Meth...
method default (line 72) | fn default() -> Self {
method from (line 81) | fn from(remote_profile: RemoteProfile) -> Self {
FILE: openfare-lib/src/profile/payment_methods.rs
type Name (line 3) | pub type Name = String;
type Methods (line 6) | pub enum Methods {
type MethodType (line 13) | pub trait MethodType {
method type_method (line 14) | fn type_method() -> Methods;
method type_method (line 59) | fn type_method() -> Methods {
method type_method (line 82) | fn type_method() -> Methods {
type PaymentMethod (line 17) | pub trait PaymentMethod {
method method (line 18) | fn method(&self) -> Methods;
method to_serde_json_value (line 19) | fn to_serde_json_value(&self) -> Result<serde_json::Value>;
method method (line 26) | fn method(&self) -> Methods {
method to_serde_json_value (line 30) | fn to_serde_json_value(&self) -> Result<serde_json::Value> {
type PayPal (line 36) | pub struct PayPal {
method new (line 47) | pub fn new(id: &Option<String>, email: &Option<String>) -> Result<Self> {
type LnUrl (line 64) | pub type LnUrl = String;
type BtcLightning (line 67) | pub struct BtcLightning {
method new (line 73) | pub fn new(lnurl: &str, keysend: &Option<String>) -> Result<Self> {
function check (line 87) | pub fn check(
FILE: openfare/src/command/config.rs
type Arguments (line 7) | pub struct Arguments {
type Subcommands (line 14) | enum Subcommands {
function run_command (line 22) | pub fn run_command(args: &Arguments) -> Result<()> {
type SetArguments (line 35) | pub struct SetArguments {
function set (line 44) | fn set(args: &SetArguments) -> Result<()> {
type ShowArguments (line 52) | pub struct ShowArguments {
function show (line 58) | fn show(args: &ShowArguments) -> Result<()> {
FILE: openfare/src/command/extensions.rs
type Arguments (line 10) | pub struct Arguments {
type Subcommands (line 17) | enum Subcommands {
function run_command (line 34) | pub fn run_command(args: &Arguments) -> Result<()> {
type AddArguments (line 61) | pub struct AddArguments {
function add (line 71) | fn add(args: &AddArguments) -> Result<()> {
function is_install_directory_discoverable (line 117) | fn is_install_directory_discoverable(directory: &std::path::PathBuf) -> ...
function is_directory_in_path_env (line 128) | fn is_directory_in_path_env(directory: &std::path::PathBuf) -> Result<bo...
function try_parse_user_url (line 136) | fn try_parse_user_url(url: &str) -> Result<Option<url::Url>> {
function get_url_from_name (line 149) | fn get_url_from_name(name: &str) -> Result<url::Url> {
type RemoveArguments (line 161) | pub struct RemoveArguments {
function remove (line 166) | fn remove(args: &RemoveArguments) -> Result<()> {
type EnableArguments (line 182) | pub struct EnableArguments {
function enable (line 187) | fn enable(args: &EnableArguments) -> Result<()> {
type DisableArguments (line 214) | pub struct DisableArguments {
function disable (line 219) | fn disable(args: &DisableArguments) -> Result<()> {
type ShowArguments (line 246) | pub struct ShowArguments {}
function show (line 248) | fn show(_args: &ShowArguments) -> Result<()> {
FILE: openfare/src/command/lock/common.rs
type LockFilePathArg (line 4) | pub struct LockFilePathArg {
FILE: openfare/src/command/lock/condition.rs
type ConditionArguments (line 7) | pub struct ConditionArguments {
type Error (line 24) | type Error = anyhow::Error;
method try_into (line 25) | fn try_into(self) -> Result<openfare_lib::lock::plan::conditions::Cond...
type AddArguments (line 62) | pub struct AddArguments {
function add (line 74) | pub fn add(args: &AddArguments) -> Result<()> {
type RemoveArguments (line 101) | pub struct RemoveArguments {
function remove (line 126) | pub fn remove(args: &RemoveArguments) -> Result<()> {
FILE: openfare/src/command/lock/mod.rs
type Arguments (line 12) | pub struct Arguments {
type Subcommands (line 19) | enum Subcommands {
function run_command (line 36) | pub fn run_command(args: &Arguments) -> Result<()> {
type NewArguments (line 69) | pub struct NewArguments {
function new (line 78) | fn new(args: &NewArguments) -> Result<()> {
type AddArguments (line 85) | pub enum AddArguments {
function add (line 96) | fn add(args: &AddArguments) -> Result<()> {
type RemoveSubcommands (line 113) | pub enum RemoveSubcommands {
function remove (line 124) | fn remove(subcommand: &RemoveSubcommands) -> Result<()> {
type UpdateArguments (line 140) | pub struct UpdateArguments {
function update (line 145) | fn update(args: &UpdateArguments) -> Result<()> {
type SetArguments (line 151) | pub struct SetArguments {
function set (line 160) | fn set(args: &SetArguments) -> Result<()> {
type ShowArguments (line 167) | pub struct ShowArguments {
function show (line 173) | fn show(args: &ShowArguments) -> Result<()> {
FILE: openfare/src/command/lock/plan.rs
type AddArguments (line 7) | pub enum AddArguments {
function add (line 15) | pub fn add(subcommand: &AddArguments) -> Result<()> {
type AddCompulsoryArguments (line 33) | pub struct AddCompulsoryArguments {
function add_compulsory (line 45) | pub fn add_compulsory(args: &AddCompulsoryArguments) -> Result<()> {
type AddVoluntaryArguments (line 71) | pub struct AddVoluntaryArguments {
function add_voluntary (line 76) | pub fn add_voluntary(args: &AddVoluntaryArguments) -> Result<()> {
type RemoveArguments (line 99) | pub struct RemoveArguments {
function remove (line 105) | pub fn remove(args: &RemoveArguments) -> Result<()> {
FILE: openfare/src/command/lock/profile.rs
type AddArguments (line 13) | pub struct AddArguments {
function add (line 30) | pub fn add(args: &AddArguments) -> Result<()> {
function get_payee (line 63) | fn get_payee(profile: &crate::handles::ProfileHandle) -> openfare_lib::l...
function get_profile (line 88) | fn get_profile(url: &Option<String>) -> Result<crate::handles::ProfileHa...
function get_label (line 103) | fn get_label(
function add_shares (line 125) | fn add_shares(payee_label: &str, payee_shares: u64, lock_handle: &mut cr...
type RemoveArguments (line 139) | pub struct RemoveArguments {
function remove (line 148) | pub fn remove(args: &RemoveArguments) -> Result<()> {
type UpdateArguments (line 178) | pub struct UpdateArguments {
function update (line 187) | pub fn update(args: &UpdateArguments) -> Result<()> {
function get_lock_local_payee (line 223) | fn get_lock_local_payee(
FILE: openfare/src/command/lock/validate.rs
type Arguments (line 6) | pub struct Arguments {
function run_command (line 12) | pub fn run_command(args: &Arguments) -> Result<()> {
FILE: openfare/src/command/mod.rs
type Command (line 13) | pub enum Command {
function run_command (line 36) | pub fn run_command(command: Command, extension_args: &Vec<String>) -> Re...
type Opts (line 69) | pub struct Opts {
FILE: openfare/src/command/pay.rs
type Arguments (line 13) | pub struct Arguments {
function run_command (line 31) | pub fn run_command(args: &Arguments, extension_args: &Vec<String>) -> Re...
type ExtensionLocks (line 59) | pub struct ExtensionLocks {
function get_locks (line 65) | pub fn get_locks(
function get_basket_items (line 95) | pub fn get_basket_items(
FILE: openfare/src/command/price/common.rs
function get_report (line 3) | pub fn get_report(
type PriceReport (line 50) | pub struct PriceReport {
type PackagePriceReport (line 56) | pub struct PackagePriceReport {
function get_package_price_report (line 64) | fn get_package_price_report(
function select_plan (line 109) | fn select_plan<'a>(
FILE: openfare/src/command/price/format/mod.rs
type Format (line 5) | pub enum Format {
function print (line 9) | pub fn print(
FILE: openfare/src/command/price/format/table.rs
function get (line 5) | pub fn get(
function get_row (line 34) | fn get_row(report: &super::common::PackagePriceReport) -> prettytable::R...
FILE: openfare/src/command/price/mod.rs
type Arguments (line 18) | pub struct Arguments {
function run_command (line 33) | pub fn run_command(args: &Arguments, extension_args: &Vec<String>) -> Re...
FILE: openfare/src/command/price/package.rs
function price (line 6) | pub fn price(
function query_extensions (line 40) | pub fn query_extensions<'a>(
FILE: openfare/src/command/price/project.rs
function price (line 7) | pub fn price(
function query_extensions (line 40) | pub fn query_extensions<'a>(
FILE: openfare/src/command/profile/mod.rs
type Arguments (line 10) | pub struct Arguments {
type Subcommands (line 17) | enum Subcommands {
function run_command (line 34) | pub fn run_command(args: &Arguments) -> Result<()> {
type AddArguments (line 56) | pub enum AddArguments {
function add (line 62) | fn add(args: &AddArguments) -> Result<()> {
type SetArguments (line 72) | pub struct SetArguments {
function set (line 81) | fn set(args: &SetArguments) -> Result<()> {
type RemoveArguments (line 89) | enum RemoveArguments {
function remove (line 95) | fn remove(args: &RemoveArguments) -> Result<()> {
type ShowArguments (line 105) | pub struct ShowArguments {
function show (line 111) | fn show(args: &ShowArguments) -> Result<()> {
FILE: openfare/src/command/profile/payment_method/btc_lightning.rs
type Method (line 5) | type Method = openfare_lib::profile::payment_methods::BtcLightning;
constant METHOD_TYPE (line 6) | const METHOD_TYPE: openfare_lib::profile::payment_methods::Methods =
type AddArguments (line 15) | pub struct AddArguments {
function add (line 25) | pub fn add(
type RemoveArguments (line 60) | pub struct RemoveArguments {}
function remove (line 62) | pub fn remove(_args: &RemoveArguments) -> Result<()> {
FILE: openfare/src/command/profile/payment_method/mod.rs
type AddSubcommands (line 7) | pub enum AddSubcommands {
function add (line 17) | pub fn add(subcommand: &AddSubcommands) -> Result<()> {
type RemoveSubcommands (line 42) | pub enum RemoveSubcommands {
function remove (line 52) | pub fn remove(subcommand: &RemoveSubcommands) -> Result<()> {
FILE: openfare/src/command/profile/payment_method/paypal.rs
type Method (line 5) | type Method = openfare_lib::profile::payment_methods::PayPal;
constant METHOD_TYPE (line 6) | const METHOD_TYPE: openfare_lib::profile::payment_methods::Methods =
type AddArguments (line 15) | pub struct AddArguments {
function add (line 25) | pub fn add(
type RemoveArguments (line 43) | pub struct RemoveArguments {}
function remove (line 45) | pub fn remove(_args: &RemoveArguments) -> Result<()> {
FILE: openfare/src/command/profile/push.rs
type Arguments (line 7) | pub struct Arguments {
function push (line 12) | pub fn push(args: &Arguments) -> Result<()> {
function clone_repo (line 46) | fn clone_repo(
function push_repo (line 63) | fn push_repo(tmp_directory_path: &std::path::PathBuf) -> Result<()> {
function insert_profile (line 71) | fn insert_profile(
FILE: openfare/src/command/service/lnpay.rs
type AddArguments (line 11) | pub struct AddArguments {
function add (line 20) | pub fn add(args: &AddArguments) -> Result<()> {
type RemoveArguments (line 36) | pub struct RemoveArguments {}
function remove (line 38) | pub fn remove(_args: &RemoveArguments) -> Result<()> {
FILE: openfare/src/command/service/mod.rs
type Arguments (line 9) | pub struct Arguments {
type Subcommands (line 16) | enum Subcommands {
function run_command (line 30) | pub fn run_command(args: &Arguments) -> Result<()> {
type AddArguments (line 53) | pub enum AddArguments {
function add (line 59) | fn add(args: &AddArguments) -> Result<()> {
type SetArguments (line 69) | pub struct SetArguments {
function set (line 78) | fn set(args: &SetArguments) -> Result<()> {
type RemoveArguments (line 86) | enum RemoveArguments {
function remove (line 92) | fn remove(args: &RemoveArguments) -> Result<()> {
type ShowArguments (line 102) | pub struct ShowArguments {
function show (line 108) | fn show(args: &ShowArguments) -> Result<()> {
FILE: openfare/src/common/fs/mod.rs
function ensure_extensions_bin_directory (line 3) | pub fn ensure_extensions_bin_directory() -> Result<Option<std::path::Pat...
function get_extensions_default_directory (line 26) | pub fn get_extensions_default_directory() -> Option<std::path::PathBuf> {
function set_directory_hidden_windows (line 43) | fn set_directory_hidden_windows(_directory: &std::path::PathBuf) {
function set_directory_hidden_windows (line 49) | fn set_directory_hidden_windows(_directory: &std::path::PathBuf) {}
type FilePath (line 51) | pub trait FilePath {
method file_path (line 52) | fn file_path() -> Result<std::path::PathBuf>;
type FileStore (line 55) | pub trait FileStore: FilePath {
method load (line 56) | fn load() -> Result<Self>
method dump (line 59) | fn dump(&mut self) -> Result<()>;
method load (line 66) | fn load() -> Result<Self> {
method dump (line 77) | fn dump(&mut self) -> Result<()> {
FILE: openfare/src/common/git.rs
function run_command (line 3) | pub fn run_command(
function commit (line 29) | pub fn commit(message: &str, working_directory: &std::path::PathBuf) -> ...
type GitUrl (line 38) | pub struct GitUrl {
method new (line 45) | pub fn new(hostname: Option<&str>, username: Option<&str>, repository:...
method as_ssh_url (line 53) | pub fn as_ssh_url(&self) -> Option<String> {
method as_https_url (line 69) | pub fn as_https_url(&self) -> Option<String> {
type Err (line 87) | type Err = anyhow::Error;
method from_str (line 88) | fn from_str(url: &str) -> Result<Self, Self::Err> {
function parse_https_url (line 103) | fn parse_https_url(url: &str) -> Result<GitUrl> {
function parse_git_url (line 127) | fn parse_git_url(url: &str) -> Result<GitUrl> {
function is_https_git_url (line 144) | fn is_https_git_url(url: &str) -> bool {
function is_ssh_git_url (line 148) | fn is_ssh_git_url(url: &str) -> bool {
function test_parse_https_url (line 158) | fn test_parse_https_url() -> Result<()> {
function test_parse_github_gitlab_user_https_url (line 179) | fn test_parse_github_gitlab_user_https_url() -> Result<()> {
function test_parse_ssh_url (line 195) | fn test_parse_ssh_url() -> Result<()> {
FILE: openfare/src/common/json.rs
type Subject (line 4) | pub trait Subject<SubT> {
method subject (line 5) | fn subject(&self) -> &SubT;
method subject_mut (line 6) | fn subject_mut(&mut self) -> &mut SubT;
type Get (line 9) | pub trait Get<SubT>: Subject<SubT> {
method get (line 10) | fn get(&self, field_path: &Option<String>) -> Result<serde_json::Value>;
method get (line 18) | fn get(&self, field_path: &Option<String>) -> Result<serde_json::Value> {
type Set (line 37) | pub trait Set<SubT>: Subject<SubT> {
method set (line 38) | fn set(&mut self, field_path: &str, value: &str) -> Result<()>;
method set (line 46) | fn set(&mut self, field_path: &str, value: &str) -> Result<()> {
FILE: openfare/src/common/url.rs
type Url (line 2) | pub struct Url {
type Err (line 8) | type Err = anyhow::Error;
method from_str (line 9) | fn from_str(url: &str) -> Result<Self, Self::Err> {
method fmt (line 18) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/core.rs
type Core (line 4) | pub struct Core {
method fmt (line 10) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/extensions.rs
type Extensions (line 4) | pub struct Extensions {
method fmt (line 10) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/mod.rs
type Config (line 12) | pub struct Config {
method subject (line 20) | fn subject(&self) -> &Self {
method subject_mut (line 23) | fn subject_mut(&mut self) -> &mut Self {
method file_path (line 29) | fn file_path() -> Result<std::path::PathBuf> {
method fmt (line 36) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/paths.rs
type Paths (line 5) | pub struct Paths {
method new (line 13) | pub fn new() -> Result<Self> {
FILE: openfare/src/config/profile.rs
type Profile (line 4) | pub struct Profile {
method fmt (line 11) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/services/lnpay.rs
type LnPay (line 2) | pub struct LnPay {
method fmt (line 8) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/services/mod.rs
type Services (line 6) | pub struct Services {
method default (line 14) | fn default() -> Self {
method subject (line 24) | fn subject(&self) -> &Services {
method subject_mut (line 27) | fn subject_mut(&mut self) -> &mut Services {
method fmt (line 33) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/config/services/portal.rs
type Portal (line 2) | pub struct Portal {
method default (line 10) | fn default() -> Self {
method fmt (line 26) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
FILE: openfare/src/extensions/common.rs
function get_config_path (line 3) | pub fn get_config_path(extension_name: &str) -> Result<std::path::PathBu...
function filter_results (line 11) | pub fn filter_results<'a, 'b, T>(
FILE: openfare/src/extensions/manage/github.rs
function get_archive_url (line 6) | pub fn get_archive_url(repo_url: &url::Url) -> Result<Option<url::Url>> {
function get_releases (line 37) | fn get_releases(repo_url: &url::Url) -> Result<Vec<serde_json::Value>> {
function get_platform (line 61) | fn get_platform() -> Result<String> {
FILE: openfare/src/extensions/manage/mod.rs
function get_all (line 12) | pub fn get_all() -> Result<Vec<Box<dyn openfare_lib::extension::Extensio...
function add_from_url (line 29) | pub fn add_from_url(
function ensure_executable_permissions (line 87) | fn ensure_executable_permissions(path: &std::path::PathBuf) -> Result<()> {
function ensure_executable_permissions (line 99) | fn ensure_executable_permissions(_path: &std::path::PathBuf) -> Result<(...
function get_bin_file_metadata (line 103) | fn get_bin_file_metadata(
function get_bin_name_regex (line 117) | fn get_bin_name_regex() -> Result<regex::Regex> {
function get_name_from_bin (line 123) | fn get_name_from_bin(
function is_supported_archive_url (line 137) | fn is_supported_archive_url(url: &url::Url) -> Result<bool> {
function get_archive_url (line 146) | fn get_archive_url(url: &url::Url) -> Result<Option<url::Url>> {
function test_matching_file_name (line 162) | fn test_matching_file_name() -> Result<()> {
function test_not_matching_file_name (line 173) | fn test_not_matching_file_name() -> Result<()> {
function update_config (line 187) | pub fn update_config(config: &mut Config) -> Result<()> {
function enable (line 241) | pub fn enable(name: &str, config: &mut Config) -> Result<()> {
function disable (line 252) | pub fn disable(name: &str, config: &mut Config) -> Result<()> {
function remove (line 262) | pub fn remove(name: &str) -> Result<()> {
function is_enabled (line 296) | fn is_enabled(name: &str, config: &Config) -> Result<bool> {
function enabled (line 301) | fn enabled(
function enabled_names (line 321) | fn enabled_names(config: &Config) -> Result<std::collections::BTreeSet<S...
function get_all_names (line 331) | pub fn get_all_names(config: &Config) -> Result<std::collections::BTreeS...
function from_names_arg (line 341) | pub fn from_names_arg(
function clean_name (line 371) | pub fn clean_name(name: &str) -> String {
FILE: openfare/src/extensions/manage/process.rs
function get_all (line 10) | pub fn get_all() -> Result<Vec<openfare_lib::extension::process::Process...
function get_extension_paths (line 53) | pub fn get_extension_paths() -> Result<HashMap<String, std::path::PathBu...
function get_candidate_extension_paths (line 89) | fn get_candidate_extension_paths() -> Result<Vec<std::path::PathBuf>> {
function get_extension_name (line 103) | fn get_extension_name(file_path: &std::path::PathBuf) -> Result<Option<S...
FILE: openfare/src/extensions/package.rs
function dependencies_locks (line 6) | pub fn dependencies_locks(
FILE: openfare/src/extensions/project.rs
function dependencies_locks (line 6) | pub fn dependencies_locks(
FILE: openfare/src/handles/lock.rs
type LockHandle (line 6) | pub struct LockHandle {
method new (line 13) | pub fn new(user_lock_file_path: &Option<std::path::PathBuf>, force: bo...
method load (line 35) | pub fn load(user_lock_file_path: &Option<std::path::PathBuf>) -> Resul...
method path (line 46) | pub fn path(&self) -> &std::path::PathBuf {
method get_lock_hash (line 50) | fn get_lock_hash(lock: &openfare_lib::lock::Lock) -> Result<blake3::Ha...
type Error (line 57) | type Error = anyhow::Error;
method try_from (line 58) | fn try_from(path: &std::path::PathBuf) -> Result<Self> {
method subject (line 109) | fn subject(&self) -> &openfare_lib::lock::Lock {
method subject_mut (line 112) | fn subject_mut(&mut self) -> &mut openfare_lib::lock::Lock {
method drop (line 71) | fn drop(&mut self) {
function find_lock_file (line 117) | pub fn find_lock_file() -> Result<Option<std::path::PathBuf>> {
function from_file (line 140) | fn from_file(path: &std::path::PathBuf) -> Result<openfare_lib::lock::Lo...
FILE: openfare/src/handles/profile.rs
type ProfileHandle (line 6) | pub struct ProfileHandle {
method from_url (line 30) | pub fn from_url(url: &crate::common::url::Url) -> Result<Self> {
method from_http_get (line 37) | fn from_http_get(url: &crate::common::url::Url) -> Result<Self> {
method from_git_url (line 64) | fn from_git_url(url: &crate::common::url::Url) -> Result<Self> {
method subject (line 102) | fn subject(&self) -> &openfare_lib::profile::Profile {
method subject_mut (line 105) | fn subject_mut(&mut self) -> &mut openfare_lib::profile::Profile {
type Target (line 111) | type Target = openfare_lib::profile::Profile;
method deref (line 113) | fn deref(&self) -> &openfare_lib::profile::Profile {
method deref_mut (line 119) | fn deref_mut(&mut self) -> &mut openfare_lib::profile::Profile {
method file_path (line 125) | fn file_path() -> Result<std::path::PathBuf> {
method fmt (line 132) | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
type FromUrlStatus (line 15) | pub struct FromUrlStatus {
type FromUrlMethod (line 24) | pub enum FromUrlMethod {
FILE: openfare/src/main.rs
function main (line 13) | fn main() {
function split_extension_args (line 31) | fn split_extension_args(args: &Vec<String>) -> (Vec<String>, Vec<String>) {
FILE: openfare/src/payments.rs
function donation_splits (line 3) | pub fn donation_splits(
function filter_voluntary (line 91) | fn filter_voluntary(
function filter_for_applicable_payees (line 113) | fn filter_for_applicable_payees(
function filter_for_applicable_shares (line 124) | fn filter_for_applicable_shares(
function check_payee_donations (line 135) | fn check_payee_donations(
FILE: openfare/src/services/lnpay.rs
type Invoice (line 8) | pub type Invoice = String;
type Client (line 11) | pub struct Client {
method new (line 17) | pub fn new(api_key: &str) -> Self {
method get (line 25) | fn get(&self, url: &url::Url) -> reqwest::blocking::RequestBuilder {
method post (line 33) | fn post(&self, url: &url::Url) -> reqwest::blocking::RequestBuilder {
method send (line 41) | fn send(
method wallets (line 49) | pub fn wallets(&self) -> Result<Wallets> {
method wallet (line 56) | pub fn wallet(&self, user_label: &str) -> Result<Option<Wallet>> {
method create_wallet (line 66) | pub fn create_wallet(&self, user_label: &str) -> Result<Wallet> {
method ensure_wallet (line 97) | pub fn ensure_wallet(&self, user_label: &str) -> Result<Wallet> {
method probe_lnurl (line 105) | pub fn probe_lnurl(&self, lnurl: &str) -> Result<LnUrlProbe> {
method invoice_from_lnurl (line 112) | pub fn invoice_from_lnurl(&self, amount_msat: usize, lnurl: &str) -> R...
method pay_invoice (line 128) | pub fn pay_invoice(&self, invoice: &Invoice, wallet: &Wallet) -> Resul...
method get_lnurl (line 151) | pub fn get_lnurl(&self, wallet: &Wallet) -> Result<String> {
method pay_lnurl (line 170) | pub fn pay_lnurl(
type LnUrlProbe (line 184) | pub struct LnUrlProbe {
function bool_from_int (line 197) | fn bool_from_int<'de, D>(deserializer: D) -> Result<bool, D::Error>
type Wallets (line 212) | pub struct Wallets(Vec<Wallet>);
type Target (line 215) | type Target = Vec<Wallet>;
method deref (line 217) | fn deref(&self) -> &Vec<Wallet> {
type Wallet (line 223) | pub struct Wallet {
function pay (line 232) | pub fn pay(
function pay_splits (line 247) | fn pay_splits(
function handle_insufficient_balance (line 291) | fn handle_insufficient_balance(
function show_qr (line 321) | fn show_qr(invoice: &str, tmp_directory_path: &std::path::PathBuf) -> Re...
function get_lnurl (line 330) | fn get_lnurl(profile: &openfare_lib::profile::Profile) -> Result<Option<...
function is_payee_applicable (line 346) | pub fn is_payee_applicable(payee: &openfare_lib::lock::payee::Payee) -> ...
function lnurl_receive_address (line 350) | pub fn lnurl_receive_address(config: &crate::config::Config) -> Result<S...
FILE: openfare/src/services/mod.rs
type Service (line 7) | pub enum Service {
type Err (line 15) | type Err = anyhow::Error;
method from_str (line 16) | fn from_str(s: &str) -> Result<Self, Self::Err> {
function pay (line 27) | pub fn pay(
function lnurl_receive_address (line 56) | pub fn lnurl_receive_address(
FILE: openfare/src/services/portal.rs
function pay (line 3) | pub fn pay(
function submit_order (line 22) | fn submit_order(
FILE: openfare/src/setup.rs
function ensure (line 4) | pub fn ensure() -> Result<()> {
function setup_config (line 14) | fn setup_config(paths: &crate::config::Paths, force: bool) -> Result<()> {
function setup (line 32) | pub fn setup(force: bool) -> Result<()> {
function is_complete (line 43) | pub fn is_complete() -> Result<bool> {
FILE: openfare/tests/integration_tests.rs
constant TEST_LOCK_FILE_NAME (line 5) | const TEST_LOCK_FILE_NAME: &str = "test.lock";
constant EXEC_PATH (line 6) | const EXEC_PATH: &str = "../target/debug/openfare";
function test_lock_file_creation_and_validation_are_consistent (line 9) | fn test_lock_file_creation_and_validation_are_consistent() -> Result<()> {
Condensed preview — 100 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (244K chars).
[
{
"path": ".dockerignore",
"chars": 11,
"preview": "target\n.git"
},
{
"path": ".github/workflows/main.yaml",
"chars": 3019,
"preview": "name: CI for release tags\n\non:\n push:\n tags:\n - \"v*\"\n\njobs:\n once:\n name: Create single release for all bui"
},
{
"path": "Cargo.toml",
"chars": 117,
"preview": "[workspace]\nmembers = [\n\t\"openfare\",\n\t\"openfare-lib\",\n]\n\n[patch.crates-io]\nopenfare-lib = { path = './openfare-lib' }"
},
{
"path": "README.md",
"chars": 3084,
"preview": "<div align=\"center\">\n\n<a href=\"https://openfare.dev\">\n <img src=\"./assets/header.svg\" alt=\"OpenFare Header\" title=\"Open"
},
{
"path": "build/musl/Containerfile",
"chars": 234,
"preview": "FROM docker.io/ekidd/rust-musl-builder:1.57.0\n\n# We need to add the source code to the image because `rust-musl-builder`"
},
{
"path": "build/musl/build-release.sh",
"chars": 342,
"preview": "#!/bin/bash\nset -euo pipefail\n\necho \"Building static binaries using ekidd/rust-musl-builder\"\npodman build -t build-\"$1\"-"
},
{
"path": "openfare/Cargo.toml",
"chars": 1480,
"preview": "[package]\nname = \"openfare\"\nversion = \"0.6.2\"\nauthors = [\"rndhouse <rndhouse@protonmail.com>\"]\nedition = \"2021\"\nhomepage"
},
{
"path": "openfare/LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2022 OpenFare LTD (UK)\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "openfare/README.md",
"chars": 2198,
"preview": "# OpenFare\n\n> Micropayment funded software.\n\n---\n\n**OpenFare is a funding mechanism which is deployable with one commit."
},
{
"path": "openfare/src/command/config.rs",
"chars": 1402,
"preview": "use crate::common::fs::FileStore;\nuse crate::common::json::{Get, Set};\nuse anyhow::Result;\nuse structopt::{self, StructO"
},
{
"path": "openfare/src/command/extensions.rs",
"chars": 7625,
"preview": "use anyhow::{format_err, Result};\nuse structopt::{self, StructOpt};\n\nuse crate::common::fs::FileStore;\n\nuse crate::commo"
},
{
"path": "openfare/src/command/lock/common.rs",
"chars": 267,
"preview": "use structopt::{self, StructOpt};\n\n#[derive(Debug, StructOpt, Clone)]\npub struct LockFilePathArg {\n /// Lock file pat"
},
{
"path": "openfare/src/command/lock/condition.rs",
"chars": 4208,
"preview": "use anyhow::Result;\nuse structopt::{self, StructOpt};\n\nuse super::common;\n\n#[derive(Debug, StructOpt, Clone)]\npub struct"
},
{
"path": "openfare/src/command/lock/mod.rs",
"chars": 4248,
"preview": "use crate::common::json::{Get, Set};\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\nmod common;\nmod condition;\nm"
},
{
"path": "openfare/src/command/lock/plan.rs",
"chars": 3726,
"preview": "use super::common;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\n#[derive(Debug, StructOpt, Clone)]\n\npub enum A"
},
{
"path": "openfare/src/command/lock/profile.rs",
"chars": 7367,
"preview": "use super::common;\nuse crate::common::fs::FileStore;\nuse anyhow::{format_err, Result};\nuse std::str::FromStr;\nuse struct"
},
{
"path": "openfare/src/command/lock/validate.rs",
"chars": 604,
"preview": "use super::common;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\n#[derive(Debug, StructOpt, Clone)]\npub struct "
},
{
"path": "openfare/src/command/mod.rs",
"chars": 1814,
"preview": "use anyhow::Result;\nuse structopt::{self, StructOpt};\n\nmod config;\nmod extensions;\nmod lock;\nmod pay;\nmod price;\nmod pro"
},
{
"path": "openfare/src/command/pay.rs",
"chars": 4252,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\nuse crate::extensions;\n\n#[deriv"
},
{
"path": "openfare/src/command/price/common.rs",
"chars": 4109,
"preview": "use anyhow::Result;\n\npub fn get_report(\n package_locks: &openfare_lib::package::PackageLocks,\n config: &crate::con"
},
{
"path": "openfare/src/command/price/format/mod.rs",
"chars": 371,
"preview": "use super::common;\nuse anyhow::Result;\nmod table;\n\npub enum Format {\n Table,\n}\n\npub fn print(\n report: &common::Pr"
},
{
"path": "openfare/src/command/price/format/table.rs",
"chars": 1519,
"preview": "use anyhow::Result;\nuse prettytable::{self, cell};\n\n/// Generates and returns a table from a given price report.\npub fn "
},
{
"path": "openfare/src/command/price/mod.rs",
"chars": 2505,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\nuse crate::extensions;\n\nmod com"
},
{
"path": "openfare/src/command/price/package.rs",
"chars": 1980,
"preview": "use super::{common, format};\nuse crate::extensions;\nuse anyhow::Result;\n\n/// Prints a price report for a specific packag"
},
{
"path": "openfare/src/command/price/project.rs",
"chars": 2095,
"preview": "use anyhow::Result;\n\nuse super::{common, format};\nuse crate::extensions;\n\n/// Returns price information for a project an"
},
{
"path": "openfare/src/command/profile/mod.rs",
"chars": 2620,
"preview": "use crate::common::fs::FileStore;\nuse crate::common::json::{Get, Set};\nuse anyhow::Result;\nuse structopt::{self, StructO"
},
{
"path": "openfare/src/command/profile/payment_method/btc_lightning.rs",
"chars": 2156,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\ntype Method = openfare_lib::pro"
},
{
"path": "openfare/src/command/profile/payment_method/mod.rs",
"chars": 1728,
"preview": "use anyhow::Result;\nuse structopt::{self, StructOpt};\nmod btc_lightning;\nmod paypal;\n\n#[derive(Debug, StructOpt, Clone)]"
},
{
"path": "openfare/src/command/profile/payment_method/paypal.rs",
"chars": 1498,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\ntype Method = openfare_lib::pro"
},
{
"path": "openfare/src/command/profile/push.rs",
"chars": 3053,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::Result;\nuse std::str::FromStr;\nuse structopt::{self, StructOpt};\n\n#[derive"
},
{
"path": "openfare/src/command/service/lnpay.rs",
"chars": 1293,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\n#[derive(Debug, StructOpt, Clon"
},
{
"path": "openfare/src/command/service/mod.rs",
"chars": 2620,
"preview": "use crate::common::fs::FileStore;\nuse crate::common::json::{Get, Set};\nuse anyhow::Result;\nuse structopt::{self, StructO"
},
{
"path": "openfare/src/common/fs/mod.rs",
"chars": 2921,
"preview": "use anyhow::{Context, Result};\n\npub fn ensure_extensions_bin_directory() -> Result<Option<std::path::PathBuf>> {\n // "
},
{
"path": "openfare/src/common/git.rs",
"chars": 6642,
"preview": "use anyhow::Result;\n\npub fn run_command(\n args: Vec<&str>,\n working_directory: &std::path::PathBuf,\n) -> Result<st"
},
{
"path": "openfare/src/common/json.rs",
"chars": 1875,
"preview": "use anyhow::Result;\n\n// TODO: Rename: Subject -> JsonComponent\npub trait Subject<SubT> {\n fn subject(&self) -> &SubT;"
},
{
"path": "openfare/src/common/mod.rs",
"chars": 154,
"preview": "pub mod fs;\npub mod git;\npub mod json;\npub mod url;\n\npub static HTTP_USER_AGENT: &str = concat!(env!(\"CARGO_PKG_NAME\"), "
},
{
"path": "openfare/src/common/url.rs",
"chars": 521,
"preview": "#[derive(Clone, Debug, Eq, PartialEq)]\npub struct Url {\n pub original: String,\n pub git: super::git::GitUrl,\n}\n\nim"
},
{
"path": "openfare/src/config/core.rs",
"chars": 497,
"preview": "#[derive(\n Debug, Clone, Default, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::Deserialize,\n)]\npub struct"
},
{
"path": "openfare/src/config/extensions.rs",
"chars": 529,
"preview": "#[derive(\n Debug, Clone, Default, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::Deserialize,\n)]\npub struct"
},
{
"path": "openfare/src/config/mod.rs",
"chars": 968,
"preview": "use anyhow::Result;\n\nmod core;\nmod extensions;\nmod paths;\nmod profile;\npub mod services;\n\npub use paths::Paths;\n\n#[deriv"
},
{
"path": "openfare/src/config/paths.rs",
"chars": 866,
"preview": "use anyhow::Result;\n\n/// Filesystem config directory absolute paths.\n#[derive(Debug)]\npub struct Paths {\n pub root_di"
},
{
"path": "openfare/src/config/profile.rs",
"chars": 521,
"preview": "#[derive(\n Clone, Debug, Default, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::Deserialize,\n)]\npub struct"
},
{
"path": "openfare/src/config/services/lnpay.rs",
"chars": 415,
"preview": "#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]\npub struct LnPay {\n #[serde(rename = \"api-key\""
},
{
"path": "openfare/src/config/services/mod.rs",
"chars": 987,
"preview": "pub mod lnpay;\nmod portal;\n\n/// Payment services.\n#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub stru"
},
{
"path": "openfare/src/config/services/portal.rs",
"chars": 966,
"preview": "#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]\npub struct Portal {\n pub url: url::Url,\n #[serde(ren"
},
{
"path": "openfare/src/extensions/common.rs",
"chars": 1412,
"preview": "use anyhow::Result;\n\npub fn get_config_path(extension_name: &str) -> Result<std::path::PathBuf> {\n let config_paths ="
},
{
"path": "openfare/src/extensions/manage/github.rs",
"chars": 2422,
"preview": "use anyhow::{format_err, Context, Result};\nuse std::io::Read;\n\nuse crate::common;\n\npub fn get_archive_url(repo_url: &url"
},
{
"path": "openfare/src/extensions/manage/mod.rs",
"chars": 11840,
"preview": "use crate::common::fs::FileStore;\nuse anyhow::{format_err, Result};\nuse std::convert::TryFrom;\n\nuse crate::config::Confi"
},
{
"path": "openfare/src/extensions/manage/process.rs",
"chars": 4121,
"preview": "use anyhow::Result;\nuse openfare_lib::extension::FromProcess;\nuse std::collections::HashMap;\n\nuse crate::extensions::com"
},
{
"path": "openfare/src/extensions/mod.rs",
"chars": 66,
"preview": "pub mod common;\npub mod manage;\npub mod package;\npub mod project;\n"
},
{
"path": "openfare/src/extensions/package.rs",
"chars": 1000,
"preview": "use anyhow::Result;\n\n/// Get package dependencies locks.\n///\n/// Conducts a parallel search across extensions.\npub fn de"
},
{
"path": "openfare/src/extensions/project.rs",
"chars": 938,
"preview": "use anyhow::Result;\n\n/// Identify all supported dependency locks which are defined in a local project.\n///\n/// Conducts "
},
{
"path": "openfare/src/handles/lock.rs",
"chars": 4611,
"preview": "use anyhow::{format_err, Context, Result};\nuse serde::Serialize;\nuse std::io::Write;\n\n#[derive(Debug, Clone, Default)]\np"
},
{
"path": "openfare/src/handles/mod.rs",
"chars": 97,
"preview": "pub(crate) mod lock;\npub mod profile;\n\npub use lock::LockHandle;\npub use profile::ProfileHandle;\n"
},
{
"path": "openfare/src/handles/profile.rs",
"chars": 4255,
"preview": "use crate::common;\nuse anyhow::Result;\n\n/// Profile structure which wraps core library Profile and adds managerial data "
},
{
"path": "openfare/src/main.rs",
"chars": 1233,
"preview": "use env_logger;\nuse structopt::StructOpt;\n\nmod command;\nmod common;\nmod config;\nmod extensions;\nmod handles;\nmod payment"
},
{
"path": "openfare/src/payments.rs",
"chars": 5825,
"preview": "use anyhow::Result;\n\npub fn donation_splits(\n donation: &openfare_lib::price::Price,\n items: &Vec<openfare_lib::ap"
},
{
"path": "openfare/src/services/lnpay.rs",
"chars": 12359,
"preview": "use anyhow::Result;\nuse rust_decimal::prelude::ToPrimitive;\nuse serde::de::Deserialize;\n\nstatic BASE_URL: &str = \"https:"
},
{
"path": "openfare/src/services/mod.rs",
"chars": 1757,
"preview": "use anyhow::Result;\n\npub mod lnpay;\nmod portal;\n\n#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserial"
},
{
"path": "openfare/src/services/portal.rs",
"chars": 1405,
"preview": "use anyhow::Result;\n\npub fn pay(\n items: &Vec<openfare_lib::api::services::portal::basket::Item>,\n config: &crate:"
},
{
"path": "openfare/src/setup.rs",
"chars": 1389,
"preview": "use anyhow::Result;\n\n/// Ensure setup is complete.\npub fn ensure() -> Result<()> {\n if !is_complete()? {\n setu"
},
{
"path": "openfare/tests/integration_tests.rs",
"chars": 2134,
"preview": "use anyhow::Result;\nuse std::path::Path;\nuse std::process::Command;\n\nconst TEST_LOCK_FILE_NAME: &str = \"test.lock\";\ncons"
},
{
"path": "openfare-lib/Cargo.toml",
"chars": 1210,
"preview": "[package]\nname = \"openfare-lib\"\nversion = \"0.6.2\"\nauthors = [\"rndhouse <rndhouse@protonmail.com>\"]\nedition = \"2021\"\nhome"
},
{
"path": "openfare-lib/LICENSE",
"chars": 1073,
"preview": "MIT License\n\nCopyright (c) 2022 OpenFare LTD (UK)\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "openfare-lib/src/api/mod.rs",
"chars": 94,
"preview": "pub mod services;\n\nlazy_static! {\n pub static ref ROUTE: String = \"/api/v1\".to_string();\n}\n"
},
{
"path": "openfare-lib/src/api/services/basket.rs",
"chars": 1273,
"preview": "pub type ExtensionName = String;\n\n#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]\npub struct Item {\n pu"
},
{
"path": "openfare-lib/src/api/services/mod.rs",
"chars": 132,
"preview": "pub mod basket;\npub mod portal;\n\nlazy_static! {\n pub static ref ROUTE: String = format!(\"{}/services\", super::ROUTE.a"
},
{
"path": "openfare-lib/src/api/services/portal/basket.rs",
"chars": 560,
"preview": "lazy_static! {\n pub static ref ROUTE: String = format!(\"{}/basket\", super::ROUTE.as_str());\n}\npub use super::super::b"
},
{
"path": "openfare-lib/src/api/services/portal/mod.rs",
"chars": 141,
"preview": "pub mod basket;\n\nlazy_static! {\n pub static ref ROUTE: String = format!(\"{}/portal\", super::ROUTE.as_str());\n}\n\npub t"
},
{
"path": "openfare-lib/src/common/fs/archive.rs",
"chars": 7588,
"preview": "use anyhow::{format_err, Result};\nuse std::convert::TryFrom;\nuse std::io::Write;\n\n#[derive(Debug, Clone, Eq, Ord, Partia"
},
{
"path": "openfare-lib/src/common/fs/mod.rs",
"chars": 17,
"preview": "pub mod archive;\n"
},
{
"path": "openfare-lib/src/common/mod.rs",
"chars": 12,
"preview": "pub mod fs;\n"
},
{
"path": "openfare-lib/src/extension/commands/common.rs",
"chars": 625,
"preview": "use crate::extension::process::ProcessResult;\nuse anyhow::Result;\n\npub fn communicate_result<T: serde::Serialize + std::"
},
{
"path": "openfare-lib/src/extension/commands/mod.rs",
"chars": 1804,
"preview": "use super::common::Extension;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\nmod common;\npub mod package_depende"
},
{
"path": "openfare-lib/src/extension/commands/package_dependencies_locks.rs",
"chars": 1249,
"preview": "use super::common;\nuse crate::extension::common::Extension;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\npub c"
},
{
"path": "openfare-lib/src/extension/commands/project_dependencies_locks.rs",
"chars": 1177,
"preview": "use super::common;\nuse crate::extension::common::Extension;\nuse anyhow::Result;\nuse structopt::{self, StructOpt};\n\npub c"
},
{
"path": "openfare-lib/src/extension/commands/static_data.rs",
"chars": 404,
"preview": "use super::common;\nuse crate::extension::common::Extension;\nuse anyhow::Result;\n\npub fn run_command<T: Extension + std::"
},
{
"path": "openfare-lib/src/extension/common.rs",
"chars": 4094,
"preview": "use anyhow::Result;\n\n#[derive(\n Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, serde::Serialize, serde::Deserial"
},
{
"path": "openfare-lib/src/extension/mod.rs",
"chars": 241,
"preview": "pub mod commands;\npub mod common;\npub mod process;\n\npub use common::{\n DependenciesCollection, Dependency, Extension,"
},
{
"path": "openfare-lib/src/extension/process.rs",
"chars": 5218,
"preview": "use anyhow::{format_err, Context, Result};\n\nuse super::commands;\nuse super::common;\n\n#[derive(Debug, serde::Serialize, s"
},
{
"path": "openfare-lib/src/lib.rs",
"chars": 253,
"preview": "#[macro_use]\nextern crate lazy_static;\n\npub static HTTP_USER_AGENT: &str = concat!(env!(\"CARGO_PKG_NAME\"), \"/\", env!(\"CA"
},
{
"path": "openfare-lib/src/lock/mod.rs",
"chars": 982,
"preview": "use anyhow::Result;\n\npub mod payee;\npub mod plan;\nmod schema;\npub mod shares;\n\n#[cfg(test)]\nmod tests;\n\npub static FILE_"
},
{
"path": "openfare-lib/src/lock/payee.rs",
"chars": 1019,
"preview": "pub type Label = String;\npub type Payees = std::collections::BTreeMap<Label, Payee>;\npub type PaymentMethodName = String"
},
{
"path": "openfare-lib/src/lock/plan/conditions/common.rs",
"chars": 1914,
"preview": "use anyhow::{format_err, Result};\n\n#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub enum"
},
{
"path": "openfare-lib/src/lock/plan/conditions/employees_count.rs",
"chars": 4574,
"preview": "use super::common;\nuse anyhow::Result;\n\nuse strum::IntoEnumIterator;\n\n#[derive(Debug, Clone, Eq, PartialEq, serde::Seria"
},
{
"path": "openfare-lib/src/lock/plan/conditions/expiration.rs",
"chars": 3987,
"preview": "use super::common;\nuse anyhow::Result;\n\nuse chrono::{TimeZone, Utc};\n\n#[derive(Debug, Clone, Eq, PartialEq)]\npub struct "
},
{
"path": "openfare-lib/src/lock/plan/conditions/for_profit.rs",
"chars": 1793,
"preview": "use super::common;\nuse anyhow::Result;\n\n#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]\npub"
},
{
"path": "openfare-lib/src/lock/plan/conditions/mod.rs",
"chars": 2154,
"preview": "use anyhow::Result;\n\nmod common;\nmod employees_count;\nmod expiration;\nmod for_profit;\npub mod parameters;\n\npub use commo"
},
{
"path": "openfare-lib/src/lock/plan/conditions/parameters.rs",
"chars": 1740,
"preview": "use super::common;\nuse anyhow::Result;\n\n#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, serde::Serialize, serde::"
},
{
"path": "openfare-lib/src/lock/plan/mod.rs",
"chars": 2522,
"preview": "use anyhow::{format_err, Result};\n\npub mod conditions;\n\nuse super::payee;\n\npub type Id = String;\npub type Plans = std::c"
},
{
"path": "openfare-lib/src/lock/schema/mod.rs",
"chars": 1065,
"preview": "use anyhow::Result;\n\npub static VERSION: &str = \"1\";\n\nlazy_static! {\n static ref SCHEMA: jsonschema::JSONSchema = {\n "
},
{
"path": "openfare-lib/src/lock/schema/schema.json",
"chars": 2449,
"preview": "{\n \"$id\": \"https://raw.githubusercontent.com/openfare/openfare/master/openfare-lib/src/lock/schema/schema.json\",\n \"$sc"
},
{
"path": "openfare-lib/src/lock/shares.rs",
"chars": 84,
"preview": "use super::payee;\n\npub type Shares = std::collections::BTreeMap<payee::Label, u64>;\n"
},
{
"path": "openfare-lib/src/lock/tests.rs",
"chars": 7157,
"preview": "use super::*;\nuse crate::price::{Currency, Price};\nuse crate::profile::Profile;\nuse anyhow::Result;\nuse payee::Payee;\nus"
},
{
"path": "openfare-lib/src/package.rs",
"chars": 2298,
"preview": "use crate::lock;\n\n/// A software package's name and version.\n#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, serd"
},
{
"path": "openfare-lib/src/price/conversions.rs",
"chars": 3078,
"preview": "use super::{Currency, Price};\nuse anyhow::Result;\n\npub fn one_btc_in_usd() -> Result<rust_decimal::Decimal> {\n let cl"
},
{
"path": "openfare-lib/src/price/mod.rs",
"chars": 9941,
"preview": "use anyhow::{format_err, Result};\nuse std::str::FromStr;\n\nmod conversions;\n\n#[derive(\n Debug, Clone, Hash, Ord, Parti"
},
{
"path": "openfare-lib/src/profile/mod.rs",
"chars": 2665,
"preview": "use anyhow::Result;\npub mod payment_methods;\n\npub const FILE_NAME: &'static str = \"openfare-profile.json\";\n\n#[derive(Deb"
},
{
"path": "openfare-lib/src/profile/payment_methods.rs",
"chars": 2774,
"preview": "use anyhow::{format_err, Result};\n\npub type Name = String;\n\n#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, serde"
},
{
"path": "rust-toolchain",
"chars": 31,
"preview": "[toolchain]\nchannel = \"1.57.0\"\n"
}
]
About this extraction
This page contains the full source code of the openfare/openfare GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 100 files (223.4 KB), approximately 56.9k tokens, and a symbol index with 564 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.