├── ui ├── start_server.sh ├── favicon.ico ├── main.js ├── rebuild.sh ├── index.html └── README.adoc ├── update-rep_lang-rev.sh ├── .envrc ├── crates ├── sensemaker_ui │ ├── yew_style.css │ ├── index.html │ ├── src │ │ ├── lib.rs │ │ └── app.rs │ ├── Cargo.toml │ └── main.js ├── social_sensemaker_core │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── social_sensemaker_macros │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── common │ ├── Cargo.toml │ └── src │ │ ├── util.rs │ │ └── lib.rs ├── frontend-tui │ ├── Cargo.toml │ └── src │ │ ├── event.rs │ │ └── main.rs └── social_sensemaker │ ├── Cargo.toml │ ├── src │ └── lib.rs │ └── tests │ └── simple.rs ├── .gitmodules ├── happs └── social_sensemaker │ ├── dna.yaml │ └── happ.yaml ├── .gitignore ├── shell.nix ├── Cargo.toml ├── package.json ├── LICENSE ├── README.adoc ├── flake.nix ├── .github └── workflows │ └── test.yml └── flake.lock /ui/start_server.sh: -------------------------------------------------------------------------------- 1 | miniserve -p 8080 . 2 | -------------------------------------------------------------------------------- /ui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neighbour-hoods/social_sensemaker/HEAD/ui/favicon.ico -------------------------------------------------------------------------------- /update-rep_lang-rev.sh: -------------------------------------------------------------------------------- 1 | find crates -type f -name Cargo.toml -print0 | xargs -0 sed -i "s/$1/$2/g" 2 | -------------------------------------------------------------------------------- /.envrc: -------------------------------------------------------------------------------- 1 | eval "$(lorri direnv)" 2 | 3 | export CARGO_HOME=~/.cargo 4 | export CARGO_TARGET_DIR=$(pwd)/target 5 | -------------------------------------------------------------------------------- /crates/sensemaker_ui/yew_style.css: -------------------------------------------------------------------------------- 1 | .alert { 2 | padding: 20px; 3 | background-color: #f44336; 4 | color: white; 5 | } 6 | -------------------------------------------------------------------------------- /crates/social_sensemaker_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "social_sensemaker_core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | -------------------------------------------------------------------------------- /ui/main.js: -------------------------------------------------------------------------------- 1 | import init, { run_app } from './pkg/frontend.js'; 2 | async function main() { 3 | await init('./pkg/frontend_bg.wasm'); 4 | run_app(); 5 | } 6 | main() 7 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "crates/holochain_client_wrapper"] 2 | path = crates/holochain_client_wrapper 3 | url = git@github.com:neighbour-hoods/holochain_client_wrapper.git 4 | -------------------------------------------------------------------------------- /happs/social_sensemaker/dna.yaml: -------------------------------------------------------------------------------- 1 | manifest_version: "1" 2 | name: "social_sensemaker" 3 | uuid: "" 4 | properties: null 5 | zomes: 6 | - name: sensemaker_main 7 | bundled: "./social_sensemaker.wasm" 8 | # properties: 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | node_modules 4 | 5 | happs/**/*.dna 6 | happs/**/*.happ 7 | happs/**/*.wasm 8 | .hc 9 | .hc_live_* 10 | 11 | ui/pkg/ 12 | 13 | # nix 14 | result* 15 | 16 | # unknown cause 17 | crates/LICENSE 18 | -------------------------------------------------------------------------------- /ui/rebuild.sh: -------------------------------------------------------------------------------- 1 | # must be run from containing directory 2 | CARGO_TARGET_DIR=$(pwd)/../target wasm-pack build --dev $(pwd)/../crates/frontend --target web --out-dir $(pwd)/pkg 3 | rollup ./main.js --format iife --file ./pkg/bundle.js 4 | -------------------------------------------------------------------------------- /ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | rep_lang_playground 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /crates/social_sensemaker_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub const OWNER_TAG: &str = "sensemaker_owner"; 2 | pub const SENSEMAKER_ZOME_NAME: &str = "sensemaker_main"; 3 | pub const SM_COMP_TAG: &str = "sm_comp"; 4 | pub const SM_INIT_TAG: &str = "sm_init"; 5 | pub const SM_DATA_TAG: &str = "sm_data"; 6 | -------------------------------------------------------------------------------- /ui/README.adoc: -------------------------------------------------------------------------------- 1 | = UI 2 | 3 | == building 4 | 5 | [source] 6 | ---- 7 | source ./rebuild.sh 8 | ---- 9 | 10 | == serving 11 | 12 | serve with your static content server of choice, and then load `index.html` in a browser. 13 | 14 | `start_server.sh` is provided for convenience, using miniserve. 15 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import ( 2 | let 3 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 4 | in fetchTarball { 5 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; } 7 | ) { 8 | src = ./.; 9 | }).shellNix.default 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/common", 4 | "crates/social_sensemaker", 5 | "crates/social_sensemaker_core", 6 | "crates/social_sensemaker_macros", 7 | "crates/sensemaker_ui", 8 | 9 | # this keeps breaking due to not-so-actively maintained deps. 10 | # letting it bitrot for now. 11 | # "crates/frontend-tui", 12 | ] 13 | -------------------------------------------------------------------------------- /happs/social_sensemaker/happ.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: social_sensemaker 4 | description: ~ 5 | roles: 6 | - id: main 7 | provisioning: 8 | strategy: create 9 | deferred: false 10 | dna: 11 | bundled: "./social_sensemaker.dna" 12 | properties: ~ 13 | uuid: ~ 14 | version: ~ 15 | clone_limit: 0 16 | -------------------------------------------------------------------------------- /crates/social_sensemaker_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "social_sensemaker_macros" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | syn = { version = "1", features = ["full", "extra-traits"] } 11 | quote = "1" 12 | proc-macro2 = "1" 13 | 14 | social_sensemaker_core = { path = "../social_sensemaker_core" } 15 | -------------------------------------------------------------------------------- /crates/sensemaker_ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sensemaker_ui 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /crates/sensemaker_ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | 3 | use wasm_bindgen::prelude::*; 4 | use web_sys::Element; 5 | 6 | #[wasm_bindgen] 7 | pub fn run_app( 8 | element: Element, 9 | admin_ws_js: JsValue, 10 | app_ws_js: JsValue, 11 | cell_id_js: JsValue, 12 | ) -> Result<(), JsValue> { 13 | let props = app::ModelProps { 14 | admin_ws_js, 15 | app_ws_js, 16 | cell_id_js, 17 | }; 18 | yew::start_app_with_props_in_element::(element, props); 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /crates/sensemaker_ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sensemaker_ui" 3 | version = "0.1.0" 4 | edition = "2018" 5 | license-file = "../../LICENSE" 6 | 7 | [lib] 8 | path = "src/lib.rs" 9 | crate-type = ["rlib", "cdylib"] 10 | 11 | [dependencies] 12 | js-sys = "0.3.59" 13 | wasm-bindgen = "0.2" 14 | weblog = "0.3.0" 15 | yew = "0.19.3" 16 | 17 | holochain_client_wrapper = { path = "../holochain_client_wrapper/holochain_client_wrapper" } 18 | 19 | [dependencies.web-sys] 20 | version = "0.3" 21 | features = [ 22 | "HtmlInputElement", 23 | ] 24 | -------------------------------------------------------------------------------- /crates/sensemaker_ui/main.js: -------------------------------------------------------------------------------- 1 | import init, { run_app } from './pkg/sensemaker_ui.js'; 2 | import { AppWebsocket, AdminWebsocket } from '@holochain/client'; 3 | 4 | async function main() { 5 | await init('/pkg/sensemaker_ui_bg.wasm'); 6 | let element = document.getElementById("sensemaker_ui_main"); 7 | let admin_ws_js = await AdminWebsocket.connect("ws://localhost:9000"); 8 | let app_ws_js = await AppWebsocket.connect("ws://localhost:9999"); 9 | // TODO change this \/ to sensemaker at some point 10 | let app_info = await app_ws_js.appInfo({ installed_app_id: 'test-app' }); 11 | let cell_id_js = app_info.cell_data[0].cell_id; 12 | run_app(element, admin_ws_js, app_ws_js, cell_id_js); 13 | } 14 | main() 15 | -------------------------------------------------------------------------------- /crates/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = "0.1.0" 4 | authors = ["Michael Hueschen "] 5 | edition = "2018" 6 | license-file = "../../LICENSE" 7 | 8 | [dependencies] 9 | base64 = "0.13.0" 10 | combine = "4.6.4" 11 | hdk = "0.0.136" 12 | pretty = "0.11.3" 13 | serde = "1" 14 | 15 | rep_lang_concrete_syntax = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "364213a6b1bca2f3ebdedb9a043c0b864e4d6a49" } 16 | rep_lang_core = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "364213a6b1bca2f3ebdedb9a043c0b864e4d6a49", features = ["hc"] } 17 | rep_lang_runtime = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "364213a6b1bca2f3ebdedb9a043c0b864e4d6a49", features = ["hc"] } 18 | social_sensemaker_core = { path = "../social_sensemaker_core" } 19 | social_sensemaker_macros = { path = "../social_sensemaker_macros" } 20 | -------------------------------------------------------------------------------- /crates/frontend-tui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "frontend-tui" 3 | version = "0.1.0" 4 | edition = "2018" 5 | license-file = "../../LICENSE" 6 | 7 | [[bin]] 8 | name = "rlp" 9 | path = "src/main.rs" 10 | 11 | [dependencies] 12 | combine = "4.5.2" 13 | futures = "0.3" 14 | holo_hash = { version = "0.0.14", default-features = false } 15 | holochain_client = { git = "https://github.com/holochain/holochain-client-rust.git", rev = "77fae4a989fd75d5a01580e0226f733a65a1716f" } 16 | holochain_types = "0.0.37" 17 | holochain_zome_types = { version = "0.0.33", default-features = false } 18 | pretty = "0.10.0" 19 | rand = "0.8.4" 20 | scrawl = "1.1.0" 21 | serde_json = "1.0.70" 22 | structopt = "0.3.25" 23 | termion = "1.5.6" 24 | tokio = "1.13.0" 25 | tui = "0.16.0" 26 | xdg = "2.4.0" 27 | 28 | common = { path = "../common" } 29 | rep_lang_concrete_syntax = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "bf44c3e80e0c98cfded6b7c1ba7caa38cb2449a8" } 30 | rep_lang_core = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "bf44c3e80e0c98cfded6b7c1ba7caa38cb2449a8" } 31 | rep_lang_runtime = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "bf44c3e80e0c98cfded6b7c1ba7caa38cb2449a8" } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "social_sensemaker", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@holochain/client": "^0.7.0", 6 | "esbuild": "^0.14.54" 7 | }, 8 | "scripts": { 9 | "hc-js-build:init": "npm install ./crates/holochain_client_wrapper/submodules/holochain-client-js", 10 | "hc-js-build": "./node_modules/.bin/esbuild ./crates/holochain_client_wrapper/submodules/holochain-client-js/src/index.ts --format=esm --bundle --outfile=./crates/holochain_client_wrapper/holochain_client_wrapper/src/holochain_client_wrapper.js", 11 | "ui:build": "wasm-pack build ./crates/sensemaker_ui --target web && ./node_modules/.bin/esbuild ./crates/sensemaker_ui/main.js --format=iife --bundle --outfile=./crates/sensemaker_ui/pkg/bundle.js", 12 | "ui:serve": "miniserve -p 8080 ./crates/sensemaker_ui", 13 | "hc:test": "cargo test", 14 | "hc:build": "cargo build -p social_sensemaker --release --target wasm32-unknown-unknown && cp $CARGO_TARGET_DIR/wasm32-unknown-unknown/release/social_sensemaker.wasm ./happs/social_sensemaker", 15 | "hc:pack": "hc dna pack happs/social_sensemaker && hc app pack happs/social_sensemaker", 16 | "hc:clean": "rm -rf .hc*", 17 | "hc:run": "hc sandbox -f=9000 generate happs/social_sensemaker -r=9999 network mdns" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/social_sensemaker/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "social_sensemaker" 3 | version = "0.1.0" 4 | authors = ["Michael Hueschen "] 5 | edition = "2018" 6 | license-file = "../../LICENSE" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | hdk = "0.0.136" 12 | serde = "1" 13 | 14 | common = { path = "../common" } 15 | rep_lang_concrete_syntax = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "364213a6b1bca2f3ebdedb9a043c0b864e4d6a49" } 16 | rep_lang_core = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "364213a6b1bca2f3ebdedb9a043c0b864e4d6a49", features = ["hc"] } 17 | rep_lang_runtime = { git = "https://github.com/neighbour-hoods/rep_lang.git", rev = "364213a6b1bca2f3ebdedb9a043c0b864e4d6a49", features = ["hc"] } 18 | 19 | [dev-dependencies] 20 | anyhow = "1.0" 21 | futures = { version = "0.3", default-features = false } 22 | tokio = { version = "1", features = ["full"] } 23 | # 24 | hdk = "0.0.136" 25 | holochain = { version = "0.0.143", default-features = false, features = ["test_utils"] } 26 | kitsune_p2p = "0.0.35" 27 | kitsune_p2p_types ="0.0.24" 28 | observability = "0.1.3" 29 | # 30 | common = { path = "../common" } 31 | 32 | [lib] 33 | path = "src/lib.rs" 34 | crate-type = ["cdylib", "rlib"] 35 | -------------------------------------------------------------------------------- /crates/frontend-tui/src/event.rs: -------------------------------------------------------------------------------- 1 | use holo_hash::HeaderHash; 2 | use std::io; 3 | use std::sync::mpsc::{self, Receiver, Sender}; 4 | use std::thread; 5 | use termion::event::Key; 6 | use termion::input::TermRead; 7 | 8 | use common::SensemakerEntry; 9 | 10 | pub enum Event { 11 | Input(Key), 12 | HcInfo(HI), 13 | ViewerSes(Vec), 14 | SelectorSes(Vec<(HeaderHash, SensemakerEntry)>), 15 | } 16 | 17 | /// A small event handler that wrap termion input and tick events. Each event 18 | /// type is handled in its own thread and returned to a common `Receiver` 19 | pub struct Events { 20 | rx: Receiver>, 21 | #[allow(dead_code)] 22 | input_handle: thread::JoinHandle<()>, 23 | } 24 | 25 | impl Events { 26 | pub fn mk() -> (Events, Sender>) { 27 | let (tx, rx) = mpsc::channel(); 28 | let input_handle = { 29 | let tx = tx.clone(); 30 | thread::spawn(move || { 31 | let stdin = io::stdin(); 32 | for key in stdin.keys().flatten() { 33 | if let Err(_err) = tx.send(Event::Input(key)) { 34 | // silently exit, otherwise output is distractingly visible 35 | return; 36 | } 37 | } 38 | }) 39 | }; 40 | (Events { rx, input_handle }, tx) 41 | } 42 | 43 | pub fn next(&self) -> Result, mpsc::RecvError> { 44 | self.rx.recv() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | * Copyright (c) 2022, Neighbourhoods Network Ltd. 2 | 3 | * License. The copyright holder hereby grants you, the royalty-free, 4 | * non-exclusive and worldwide right, under the copyright holder’s 5 | * intellectual property rights, to: 6 | * (i) use the social_sensemaker library ("Library") for evaluation purposes; 7 | * (ii) use and copy the associated documentation that is made available in 8 | * connection with the Library for the purposes of (i) above. 9 | 10 | * For the avoidance of doubt, the above license does not include or imply the grant of any 11 | * right or license to distribute or grant any license or sublicense concerning 12 | * the Library on a “naked” or “stand-alone” basis and such is 13 | * prohibited under this license. 14 | 15 | * There are no implied licenses granted hereunder and all rights, save for those 16 | * expressly granted, shall remain with the copyright holder. 17 | 18 | * Restrictions. you shall not: 19 | * (a) use the Library for any commercial purposes; 20 | * (b) create derivative works of the Library. 21 | 22 | * THE LIBRARY IS PROVIDED BY COPYRIGHT HOLDER 'AS IS' AND 23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 28 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 29 | * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 30 | * ARISING IN ANY WAY OUT OF THE USE OF THE LIBRARY, EVEN IF ADVISED 31 | * OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /crates/common/src/util.rs: -------------------------------------------------------------------------------- 1 | use hdk::prelude::*; 2 | 3 | /// Tries to do a DHT get to retrieve data for the entry_hash, 4 | /// and if this get is successful and we get some element, tries 5 | /// to convert this element into a type T and return the result 6 | pub fn try_get_and_convert>( 7 | entry_hash: EntryHash, 8 | get_options: GetOptions, 9 | ) -> ExternResult { 10 | match get(entry_hash.clone(), get_options)? { 11 | Some(element) => try_from_element(element), 12 | None => Err(WasmError::Guest(format!( 13 | "There is no element at the hash {}", 14 | entry_hash 15 | ))), 16 | } 17 | } 18 | 19 | pub fn try_get_and_convert_with_hh>( 20 | entry_hash: EntryHash, 21 | get_options: GetOptions, 22 | ) -> ExternResult<(T, HeaderHash)> { 23 | match get(entry_hash.clone(), get_options)? { 24 | Some(element) => { 25 | let hh = element.header_address().clone(); 26 | let v = try_from_element(element)?; 27 | Ok((v, hh)) 28 | } 29 | None => Err(WasmError::Guest(format!( 30 | "There is no element at the hash {}", 31 | entry_hash 32 | ))), 33 | } 34 | } 35 | 36 | pub fn get_hh(entry_hash: EntryHash, get_options: GetOptions) -> ExternResult { 37 | match get(entry_hash.clone(), get_options)? { 38 | Some(element) => { 39 | let hh = element.header_address().clone(); 40 | Ok(hh) 41 | } 42 | None => Err(WasmError::Guest(format!( 43 | "There is no element at the hash {}", 44 | entry_hash 45 | ))), 46 | } 47 | } 48 | 49 | /// Attempts to get an element at the entry_hash and returns it 50 | /// if the element exists 51 | #[allow(dead_code)] 52 | pub fn try_get_element(entry_hash: EntryHash, get_options: GetOptions) -> ExternResult { 53 | match get(entry_hash.clone(), get_options)? { 54 | Some(element) => Ok(element), 55 | None => Err(WasmError::Guest(format!( 56 | "There is no element at the hash {}", 57 | entry_hash 58 | ))), 59 | } 60 | } 61 | 62 | /// Tries to extract the entry from the element, and if the entry is there 63 | /// tries to convert it to type T and return the result 64 | #[allow(dead_code)] 65 | pub fn try_from_element>(element: Element) -> ExternResult { 66 | match element.entry() { 67 | element::ElementEntry::Present(entry) => T::try_from(entry.clone()).map_err(|_| { 68 | WasmError::Guest(format!( 69 | "Couldn't convert Element entry {:?} into data type {}", 70 | entry, 71 | std::any::type_name::() 72 | )) 73 | }), 74 | _ => Err(WasmError::Guest(format!( 75 | "Element {:?} does not have an entry", 76 | element 77 | ))), 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/social_sensemaker_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "proc-macro"] 2 | 3 | use proc_macro::TokenStream; 4 | use proc_macro2::{Ident, Span}; 5 | 6 | use social_sensemaker_core::SENSEMAKER_ZOME_NAME; 7 | 8 | // TODO think about hdk_extern and which zome/happ it goes into. will the widgets want 9 | // to invoke a macro, similar to `sensemaker_cell_id_fns`, s.t. the hdk_extern registers 10 | // in their wasm? 11 | #[proc_macro_attribute] 12 | pub fn expand_remote_calls(_attrs: TokenStream, item: TokenStream) -> TokenStream { 13 | // expand_remote_calls is only valid for functions 14 | let item_fn = syn::parse_macro_input!(item as syn::ItemFn); 15 | let fn_name = item_fn.sig.ident.to_string(); 16 | 17 | let mut new_fn = item_fn.clone(); 18 | 19 | // prefix fn ident. 20 | new_fn.sig.ident = Ident::new(&format!("remote_{}", fn_name), Span::call_site()); 21 | 22 | // arg list. tuple munging. 23 | { 24 | let arg_pat_type = match item_fn 25 | .sig 26 | .inputs 27 | .first() 28 | .expect("hdk fn should have 1 arg") 29 | { 30 | syn::FnArg::Typed(pat_type) => pat_type, 31 | _ => panic!("expand_remote_calls: invalid Receiver FnArg"), 32 | }; 33 | let arg_pat_type_ty = &arg_pat_type.ty; 34 | let token_streams = vec![ 35 | (quote::quote! { cell_id: CellId }).into(), 36 | (quote::quote! { cap_secret: Option }).into(), 37 | (quote::quote! { payload: #arg_pat_type_ty }).into(), 38 | ]; 39 | 40 | // drain element from `inputs` 41 | assert!(new_fn.sig.inputs.pop().is_some()); 42 | assert!(new_fn.sig.inputs.is_empty()); 43 | 44 | // add our above 3 to `inputs` 45 | for token_stream in token_streams { 46 | let fn_arg = syn::parse_macro_input!(token_stream as syn::FnArg); 47 | new_fn.sig.inputs.push(fn_arg); 48 | } 49 | } 50 | 51 | // body with bridge call. 52 | { 53 | let token_stream = (quote::quote! { 54 | { 55 | match call( 56 | CallTargetCell::Other(cell_id), 57 | #SENSEMAKER_ZOME_NAME.into(), 58 | #fn_name.into(), 59 | cap_secret, 60 | payload, 61 | )? { 62 | ZomeCallResponse::Ok(response) => Ok(response.decode()?), 63 | err => { 64 | error!("ZomeCallResponse error: {:?}", err); 65 | Err(WasmError::Guest(format!("{}: {:?}", #fn_name, err))) 66 | } 67 | } 68 | } 69 | }) 70 | .into(); 71 | let fn_body = syn::parse_macro_input!(token_stream as syn::Block); 72 | new_fn.block = Box::new(fn_body); 73 | } 74 | 75 | let doc_comment = format!("make a bridge call to `{}`", fn_name); 76 | (quote::quote! { 77 | #[hdk_extern] 78 | #item_fn 79 | 80 | #[doc = #doc_comment] 81 | #new_fn 82 | }) 83 | .into() 84 | } 85 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = social_sensemaker 2 | 3 | integrating `rep_lang` & Holochain. 4 | 5 | == submodules 6 | 7 | either: 8 | 9 | [source] 10 | ---- 11 | git clone --recurse-submodules $URL 12 | ---- 13 | 14 | or, after cloning: 15 | 16 | [source] 17 | ---- 18 | git submodule update --init --recursive 19 | ---- 20 | 21 | == holochain-client-js wrapper setup 22 | 23 | [source] 24 | ---- 25 | npm run hc-js-build:init 26 | npm run hc-js-build 27 | ---- 28 | 29 | == entering dev environment 30 | 31 | install a https://nixos.wiki/wiki/Flakes#Installing_flakes[Flakes] supporting https://nixos.org/download.html[`nix`]. 32 | 33 | edit `/etc/nix/nix.conf` to include the following: 34 | 35 | ---- 36 | experimental-features = nix-command flakes 37 | ---- 38 | 39 | optionally (but strongly suggested because it will dramatically speed up first-build times), also add these lines to `nix.conf` in order to enable the Holochain binary caches. 40 | public keys can be checked at the substituter URLs. 41 | 42 | ---- 43 | substituters = https://cache.nixos.org/ https://cache.holo.host/ https://holochain-ci.cachix.org 44 | trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cache.holo.host-1:lNXIXtJgS9Iuw4Cu6X0HINLu9sTfcjEntnrgwMQIMcE= cache.holo.host-2:ZJCkX3AUYZ8soxTLfTb60g+F3MkWD7hkH9y8CgqwhDQ= holochain-ci.cachix.org-1:5IUSkZc0aoRS53rfkvH9Kid40NpyjwCMCzwRTXy+QN8= 45 | 46 | alternately, use https://github.com/nix-community/lorri[`lorri`] and https://github.com/direnv/direnv[`direnv`] to manage tooling and shell environment transparently. 47 | ---- 48 | 49 | then (from the repo root) run: 50 | 51 | ---- 52 | nix develop --impure 53 | ---- 54 | 55 | == building & running 56 | 57 | (inside of the dev shell, from prev section) 58 | 59 | building: 60 | 61 | [source] 62 | ---- 63 | # perhaps this can go in package.json? 64 | npm install 65 | 66 | npm run fe:build 67 | npm run hc:build 68 | npm run hc:pack 69 | ---- 70 | 71 | running: 72 | 73 | [source] 74 | ---- 75 | npm run fe:run 76 | npm run hc:run 77 | ---- 78 | 79 | === running with multiple conductors 80 | 81 | [source] 82 | ---- 83 | # perform above build steps 84 | npm run hc:clean 85 | 86 | # terminal 1-1 87 | hc sandbox -f=9009 generate happs/social_sensemaker -r=9999 network mdns 88 | 89 | # terminal 1-2 90 | cargo run --bin rlp -- -f 9009 -p 9999 2> /tmp/rlp1.log 91 | 92 | # terminal 2-1 93 | hc sandbox -f=9008 generate happs/social_sensemaker -r=9998 network mdns 94 | 95 | # terminal 2-2 96 | cargo run --bin rlp -- -f 9008 -p 9998 2> /tmp/rlp2.log 97 | ---- 98 | 99 | create an IE in terminal 1-2 and see it appear in terminal 2-2. 100 | 101 | == sweettest tests 102 | 103 | [source] 104 | ---- 105 | $ npm run hc:build 106 | $ npm run hc:pack 107 | 108 | # INFO 109 | # cargo test depends on having the .happ around to be installed. 110 | # this is due to Holochain having a high degree of "dynamism" in how it runs happs. 111 | # I don't think we can do anything more "compile time" ish instead... 112 | # in CI, for later, we will need to make sure the main build completes and the 113 | # artifacts are available to the test suite... 114 | $ cargo test 115 | ---- 116 | -------------------------------------------------------------------------------- /crates/social_sensemaker/src/lib.rs: -------------------------------------------------------------------------------- 1 | use hdk::prelude::*; 2 | 3 | use common::{ 4 | create_sensemaker_entry_full, mk_sensemaker_entry, CreateSensemakerEntryInput, SchemeEntry, 5 | SchemeRoot, SensemakerEntry, 6 | }; 7 | 8 | entry_defs![ 9 | Path::entry_def(), 10 | PathEntry::entry_def(), 11 | SensemakerEntry::entry_def(), 12 | SchemeEntry::entry_def(), 13 | SchemeRoot::entry_def() 14 | ]; 15 | 16 | #[hdk_extern] 17 | pub fn init(_: ()) -> ExternResult { 18 | let mut functions = GrantedFunctions::new(); 19 | functions.insert((zome_info()?.name, "get_sensemaker_entry_by_path".into())); 20 | functions.insert(( 21 | zome_info()?.name, 22 | "set_sensemaker_entry_parse_rl_expr".into(), 23 | )); 24 | functions.insert((zome_info()?.name, "initialize_sm_data".into())); 25 | functions.insert((zome_info()?.name, "step_sm".into())); 26 | 27 | let grant = ZomeCallCapGrant { 28 | access: CapAccess::Unrestricted, 29 | functions, 30 | tag: "".into(), 31 | }; 32 | create_cap_grant(grant)?; 33 | 34 | Ok(InitCallbackResult::Pass) 35 | } 36 | 37 | #[hdk_extern] 38 | pub(crate) fn validate_create_entry_sensemaker_entry( 39 | op: Op, 40 | ) -> ExternResult { 41 | validate_create_update_entry_sensemaker_entry(op) 42 | } 43 | 44 | #[hdk_extern] 45 | pub(crate) fn validate_update_entry_sensemaker_entry( 46 | op: Op, 47 | ) -> ExternResult { 48 | validate_create_update_entry_sensemaker_entry(op) 49 | } 50 | 51 | pub fn validate_create_update_entry_sensemaker_entry( 52 | op: Op, 53 | ) -> ExternResult { 54 | let entry: Entry = match op { 55 | Op::StoreEntry { 56 | entry: entry @ Entry::App(_), 57 | header: _, 58 | } => entry, 59 | Op::RegisterUpdate { 60 | update: _, 61 | new_entry, 62 | original_header: _, 63 | original_entry: _, 64 | } => new_entry, 65 | _ => { 66 | return Ok(ValidateCallbackResult::Invalid( 67 | "Unexpected op: not StoreEntry or RegisterUpdate".into(), 68 | )) 69 | } 70 | }; 71 | 72 | let se: SensemakerEntry = match entry_to_struct(&entry)? { 73 | Some(se) => Ok(se), 74 | None => Err(WasmError::Guest(format!( 75 | "Couldn't convert Entry {:?} into SensemakerEntry", 76 | entry 77 | ))), 78 | }?; 79 | 80 | let computed_se = mk_sensemaker_entry(se.operator, se.operands)?; 81 | 82 | if computed_se.output_scheme != se.output_scheme { 83 | return Ok(ValidateCallbackResult::Invalid(format!( 84 | "SensemakerEntry scheme mismatch:\ 85 | \ncomputed: {:?}\ 86 | \nreceived: {:?}", 87 | computed_se.output_scheme, se.output_scheme 88 | ))); 89 | } 90 | 91 | if computed_se.output_flat_value != se.output_flat_value { 92 | return Ok(ValidateCallbackResult::Invalid(format!( 93 | "SensemakerEntry value mismatch:\ 94 | \ncomputed: {:?}\ 95 | \nreceived: {:?}", 96 | computed_se.output_flat_value, se.output_flat_value 97 | ))); 98 | } 99 | 100 | Ok(ValidateCallbackResult::Valid) 101 | } 102 | 103 | #[hdk_extern] 104 | pub fn create_sensemaker_entry(input: CreateSensemakerEntryInput) -> ExternResult { 105 | create_sensemaker_entry_full(input).map(|t| t.0) 106 | } 107 | 108 | pub fn entry_to_struct>( 109 | entry: &Entry, 110 | ) -> Result, SerializedBytesError> { 111 | match entry { 112 | Entry::App(eb) => Ok(Some(A::try_from(SerializedBytes::from(eb.to_owned()))?)), 113 | _ => Ok(None), 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nh-nix-env.url = "github:neighbour-hoods/nh-nix-env"; 4 | }; 5 | 6 | outputs = { nh-nix-env, ... }: 7 | let 8 | flake-utils = nh-nix-env.metavalues.flake-utils; 9 | nh-supported-systems = nh-nix-env.metavalues.nh-supported-systems; 10 | rustVersion = nh-nix-env.metavalues.rustVersion; 11 | naersk = nh-nix-env.metavalues.naersk; 12 | wasmTarget = nh-nix-env.metavalues.wasmTarget; 13 | holonixMain = nh-nix-env.metavalues.holonixMain; 14 | in 15 | flake-utils.lib.eachSystem nh-supported-systems (system: 16 | let 17 | pkgs = nh-nix-env.values.${system}.pkgs; 18 | in 19 | 20 | { 21 | devShell = nh-nix-env.shells.${system}.holochainDevShell { 22 | extraBuildInputs = with pkgs; [ 23 | nodePackages.rollup 24 | wasm-pack 25 | ]; 26 | }; 27 | 28 | # packages.social_sensemaker-cargo2nix = 29 | # let 30 | # # create nixpkgs that contains rustBuilder from cargo2nix overlay 31 | # crossPkgs = import nixpkgs { 32 | # inherit system; 33 | 34 | # crossSystem = { 35 | # config = "wasm32-unknown-wasi"; 36 | # system = "wasm32-wasi"; 37 | # useLLVM = true; 38 | # }; 39 | 40 | # overlays = [ 41 | # (import "${cargo2nix}/overlay") 42 | # rust-overlay.overlay 43 | # ]; 44 | # }; 45 | 46 | # # create the workspace & dependencies package set 47 | # rustPkgs = crossPkgs.rustBuilder.makePackageSet' { 48 | # rustChannel = rustVersion; 49 | # packageFun = import ./crates/social_sensemaker/Cargo.nix; 50 | # target = "wasm32-unknown-unknown"; 51 | # }; 52 | 53 | # in 54 | 55 | # rustPkgs.workspace.social_sensemaker {}; 56 | 57 | packages.social_sensemaker-naersk = 58 | let 59 | rust = pkgs.rust-bin.stable.${rustVersion}.default.override { 60 | targets = [ wasmTarget ]; 61 | }; 62 | 63 | naersk' = pkgs.callPackage naersk { 64 | cargo = rust; 65 | rustc = rust; 66 | }; 67 | 68 | ri-wasm = naersk'.buildPackage { 69 | src = ./.; 70 | copyLibs = true; 71 | CARGO_BUILD_TARGET = wasmTarget; 72 | cargoBuildOptions = (opts: opts ++ ["--package=social_sensemaker"]); 73 | gitAllRefs = true; 74 | }; 75 | 76 | in 77 | 78 | pkgs.stdenv.mkDerivation { 79 | name = "social_sensemaker-happ"; 80 | buildInputs = [ 81 | holonixMain.pkgs.holochainBinaries.hc 82 | ]; 83 | unpackPhase = "true"; 84 | installPhase = '' 85 | mkdir $out 86 | cp ${ri-wasm}/lib/social_sensemaker.wasm $out 87 | cp ${happs/social_sensemaker/dna.yaml} $out/dna.yaml 88 | cp ${happs/social_sensemaker/happ.yaml} $out/happ.yaml 89 | hc dna pack $out 90 | hc app pack $out 91 | ''; 92 | }; 93 | 94 | packages.rlp-tui = 95 | let 96 | rust = pkgs.rust-bin.stable.${rustVersion}.default; 97 | 98 | naersk' = pkgs.callPackage naersk { 99 | cargo = rust; 100 | rustc = rust; 101 | }; 102 | 103 | in 104 | 105 | naersk'.buildPackage { 106 | src = ./.; 107 | copyLibs = true; 108 | cargoBuildOptions = (opts: opts ++ ["--package=frontend-tui"]); 109 | buildInputs = with pkgs; [ 110 | openssl 111 | pkgconfig 112 | ]; 113 | }; 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | RUST_BACKTRACE: 1 10 | 11 | jobs: 12 | 13 | cargo_tests: 14 | if: ${{ false }} # disable for now 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, macos-latest] 19 | fail-fast: false 20 | steps: 21 | - uses: actions/checkout@v2.4.0 22 | - uses: cachix/install-nix-action@v17 23 | with: 24 | install_url: https://releases.nixos.org/nix/nix-2.8.1/install 25 | extra_nix_config: | 26 | trusted-public-keys = cache.holo.host-1:lNXIXtJgS9Iuw4Cu6X0HINLu9sTfcjEntnrgwMQIMcE= cache.holo.host-2:ZJCkX3AUYZ8soxTLfTb60g+F3MkWD7hkH9y8CgqwhDQ= holochain-ci.cachix.org-1:5IUSkZc0aoRS53rfkvH9Kid40NpyjwCMCzwRTXy+QN8= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= 27 | substituters = https://cache.holo.host https://holochain-ci.cachix.org https://cache.nixos.org/ 28 | - uses: cachix/cachix-action@v10 29 | with: 30 | name: neighbourhoods 31 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 32 | - run: nix-shell --command "npm install && npm run hc:build && npm run hc:pack && npm run hc:test" 33 | 34 | rustfmt: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2.4.0 38 | - uses: cachix/install-nix-action@v17 39 | with: 40 | install_url: https://releases.nixos.org/nix/nix-2.8.1/install 41 | extra_nix_config: | 42 | trusted-public-keys = cache.holo.host-1:lNXIXtJgS9Iuw4Cu6X0HINLu9sTfcjEntnrgwMQIMcE= cache.holo.host-2:ZJCkX3AUYZ8soxTLfTb60g+F3MkWD7hkH9y8CgqwhDQ= holochain-ci.cachix.org-1:5IUSkZc0aoRS53rfkvH9Kid40NpyjwCMCzwRTXy+QN8= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= 43 | substituters = https://cache.holo.host https://holochain-ci.cachix.org https://cache.nixos.org/ 44 | - uses: cachix/cachix-action@v10 45 | with: 46 | name: neighbourhoods 47 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 48 | - run: nix develop --impure --command cargo fmt -- --check 49 | 50 | confirm_hc_sandbox_generation_success: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v2.4.0 54 | - uses: cachix/install-nix-action@v17 55 | with: 56 | install_url: https://releases.nixos.org/nix/nix-2.8.1/install 57 | extra_nix_config: | 58 | trusted-public-keys = cache.holo.host-1:lNXIXtJgS9Iuw4Cu6X0HINLu9sTfcjEntnrgwMQIMcE= cache.holo.host-2:ZJCkX3AUYZ8soxTLfTb60g+F3MkWD7hkH9y8CgqwhDQ= holochain-ci.cachix.org-1:5IUSkZc0aoRS53rfkvH9Kid40NpyjwCMCzwRTXy+QN8= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= 59 | substituters = https://cache.holo.host https://holochain-ci.cachix.org https://cache.nixos.org/ 60 | - uses: cachix/cachix-action@v10 61 | with: 62 | name: neighbourhoods 63 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 64 | - run: nix develop --impure --command npm run hc:build 65 | - run: nix develop --impure --command npm run hc:pack 66 | - run: nix develop --impure --command hc sandbox generate happs/social_sensemaker 67 | 68 | social_sensemaker-naersk: 69 | runs-on: ubuntu-latest 70 | steps: 71 | - uses: actions/checkout@v2.4.0 72 | - uses: cachix/install-nix-action@v17 73 | with: 74 | install_url: https://releases.nixos.org/nix/nix-2.8.1/install 75 | extra_nix_config: | 76 | trusted-public-keys = cache.holo.host-1:lNXIXtJgS9Iuw4Cu6X0HINLu9sTfcjEntnrgwMQIMcE= cache.holo.host-2:ZJCkX3AUYZ8soxTLfTb60g+F3MkWD7hkH9y8CgqwhDQ= holochain-ci.cachix.org-1:5IUSkZc0aoRS53rfkvH9Kid40NpyjwCMCzwRTXy+QN8= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= 77 | substituters = https://cache.holo.host https://holochain-ci.cachix.org https://cache.nixos.org/ 78 | - uses: cachix/cachix-action@v10 79 | with: 80 | name: neighbourhoods 81 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 82 | - run: nix build .#social_sensemaker-naersk --impure 83 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "cargo2nix": { 4 | "inputs": { 5 | "flake-compat": "flake-compat", 6 | "flake-utils": "flake-utils", 7 | "nixpkgs": "nixpkgs", 8 | "rust-overlay": "rust-overlay" 9 | }, 10 | "locked": { 11 | "lastModified": 1653986861, 12 | "narHash": "sha256-LG+0bIyNI5V5mcNPOCjoQrdiu1S1PIBAaQqXetbTLik=", 13 | "owner": "cargo2nix", 14 | "repo": "cargo2nix", 15 | "rev": "f1059d071a1f12d379f5628c30d45c238c83572a", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "cargo2nix", 20 | "repo": "cargo2nix", 21 | "type": "github" 22 | } 23 | }, 24 | "flake-compat": { 25 | "flake": false, 26 | "locked": { 27 | "lastModified": 1650374568, 28 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 29 | "owner": "edolstra", 30 | "repo": "flake-compat", 31 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 32 | "type": "github" 33 | }, 34 | "original": { 35 | "owner": "edolstra", 36 | "repo": "flake-compat", 37 | "type": "github" 38 | } 39 | }, 40 | "flake-compat_2": { 41 | "flake": false, 42 | "locked": { 43 | "lastModified": 1650374568, 44 | "narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=", 45 | "owner": "edolstra", 46 | "repo": "flake-compat", 47 | "rev": "b4a34015c698c7793d592d66adbab377907a2be8", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "edolstra", 52 | "repo": "flake-compat", 53 | "type": "github" 54 | } 55 | }, 56 | "flake-utils": { 57 | "locked": { 58 | "lastModified": 1653893745, 59 | "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", 60 | "owner": "numtide", 61 | "repo": "flake-utils", 62 | "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", 63 | "type": "github" 64 | }, 65 | "original": { 66 | "owner": "numtide", 67 | "repo": "flake-utils", 68 | "type": "github" 69 | } 70 | }, 71 | "flake-utils_2": { 72 | "locked": { 73 | "lastModified": 1653893745, 74 | "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", 75 | "owner": "numtide", 76 | "repo": "flake-utils", 77 | "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", 78 | "type": "github" 79 | }, 80 | "original": { 81 | "owner": "numtide", 82 | "repo": "flake-utils", 83 | "type": "github" 84 | } 85 | }, 86 | "flake-utils_3": { 87 | "locked": { 88 | "lastModified": 1637014545, 89 | "narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=", 90 | "owner": "numtide", 91 | "repo": "flake-utils", 92 | "rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4", 93 | "type": "github" 94 | }, 95 | "original": { 96 | "owner": "numtide", 97 | "repo": "flake-utils", 98 | "type": "github" 99 | } 100 | }, 101 | "holonix": { 102 | "flake": false, 103 | "locked": { 104 | "lastModified": 1654758726, 105 | "narHash": "sha256-m+FasCc2ea7ZiIkHbKf3V50RNvomUvnq8sCpSZ5XLa8=", 106 | "owner": "holochain", 107 | "repo": "holonix", 108 | "rev": "2b874668324ae5a7804066f61afbed913ac0cb9c", 109 | "type": "github" 110 | }, 111 | "original": { 112 | "owner": "holochain", 113 | "repo": "holonix", 114 | "type": "github" 115 | } 116 | }, 117 | "naersk": { 118 | "inputs": { 119 | "nixpkgs": "nixpkgs_2" 120 | }, 121 | "locked": { 122 | "lastModified": 1653413650, 123 | "narHash": "sha256-wojDHjb+eU80MPH+3HQaK0liUy8EgR95rvmCl24i58Y=", 124 | "owner": "nix-community", 125 | "repo": "naersk", 126 | "rev": "69daaceebe12c070cd5ae69ba38f277bbf033695", 127 | "type": "github" 128 | }, 129 | "original": { 130 | "owner": "nix-community", 131 | "repo": "naersk", 132 | "type": "github" 133 | } 134 | }, 135 | "nh-nix-env": { 136 | "inputs": { 137 | "cargo2nix": "cargo2nix", 138 | "flake-compat": "flake-compat_2", 139 | "flake-utils": "flake-utils_2", 140 | "holonix": "holonix", 141 | "naersk": "naersk", 142 | "nixpkgs": "nixpkgs_3", 143 | "rust-overlay": "rust-overlay_2" 144 | }, 145 | "locked": { 146 | "lastModified": 1655838960, 147 | "narHash": "sha256-uqKh5j8LQm4EkqGeb7TesEodovvZ7SuYLY7P8eY/wmk=", 148 | "owner": "neighbour-hoods", 149 | "repo": "nh-nix-env", 150 | "rev": "28fb46f79f7bc3a40de73c15ca85654c2ef4aa2a", 151 | "type": "github" 152 | }, 153 | "original": { 154 | "owner": "neighbour-hoods", 155 | "repo": "nh-nix-env", 156 | "type": "github" 157 | } 158 | }, 159 | "nixpkgs": { 160 | "locked": { 161 | "lastModified": 1653896915, 162 | "narHash": "sha256-BWrtLy5LLlVgfoqyLKSviayyDtidefKX+UAGPakN2Pc=", 163 | "owner": "nixos", 164 | "repo": "nixpkgs", 165 | "rev": "be5e3632be60356fabde8a25718018a50bb62d6f", 166 | "type": "github" 167 | }, 168 | "original": { 169 | "owner": "nixos", 170 | "ref": "release-22.05", 171 | "repo": "nixpkgs", 172 | "type": "github" 173 | } 174 | }, 175 | "nixpkgs_2": { 176 | "locked": { 177 | "lastModified": 1653918805, 178 | "narHash": "sha256-6ahwAnBNGgqSNSn/6RnsxrlFi+fkA+RyT6o/5S1915o=", 179 | "owner": "NixOS", 180 | "repo": "nixpkgs", 181 | "rev": "a0a69be4b5ee63f1b5e75887a406e9194012b492", 182 | "type": "github" 183 | }, 184 | "original": { 185 | "id": "nixpkgs", 186 | "type": "indirect" 187 | } 188 | }, 189 | "nixpkgs_3": { 190 | "locked": { 191 | "lastModified": 1653931853, 192 | "narHash": "sha256-O3wncIouj9x7gBPntzHeK/Hkmm9M1SGlYq7JI7saTAE=", 193 | "owner": "nixos", 194 | "repo": "nixpkgs", 195 | "rev": "f1c167688a6f81f4a51ab542e5f476c8c595e457", 196 | "type": "github" 197 | }, 198 | "original": { 199 | "owner": "nixos", 200 | "ref": "nixos-unstable", 201 | "repo": "nixpkgs", 202 | "type": "github" 203 | } 204 | }, 205 | "nixpkgs_4": { 206 | "locked": { 207 | "lastModified": 1637453606, 208 | "narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=", 209 | "owner": "NixOS", 210 | "repo": "nixpkgs", 211 | "rev": "8afc4e543663ca0a6a4f496262cd05233737e732", 212 | "type": "github" 213 | }, 214 | "original": { 215 | "owner": "NixOS", 216 | "ref": "nixpkgs-unstable", 217 | "repo": "nixpkgs", 218 | "type": "github" 219 | } 220 | }, 221 | "root": { 222 | "inputs": { 223 | "nh-nix-env": "nh-nix-env" 224 | } 225 | }, 226 | "rust-overlay": { 227 | "inputs": { 228 | "flake-utils": [ 229 | "nh-nix-env", 230 | "cargo2nix", 231 | "flake-utils" 232 | ], 233 | "nixpkgs": [ 234 | "nh-nix-env", 235 | "cargo2nix", 236 | "nixpkgs" 237 | ] 238 | }, 239 | "locked": { 240 | "lastModified": 1653878966, 241 | "narHash": "sha256-T51Gck/vrJZi1m+uTbhEFTRgZmE59sydVONadADv358=", 242 | "owner": "oxalica", 243 | "repo": "rust-overlay", 244 | "rev": "8526d618af012a923ca116be9603e818b502a8db", 245 | "type": "github" 246 | }, 247 | "original": { 248 | "owner": "oxalica", 249 | "repo": "rust-overlay", 250 | "type": "github" 251 | } 252 | }, 253 | "rust-overlay_2": { 254 | "inputs": { 255 | "flake-utils": "flake-utils_3", 256 | "nixpkgs": "nixpkgs_4" 257 | }, 258 | "locked": { 259 | "lastModified": 1653965133, 260 | "narHash": "sha256-N3sguyZc55GvT6gNXxZpZDrpKwC7QvErnbKxaKdOvKY=", 261 | "owner": "oxalica", 262 | "repo": "rust-overlay", 263 | "rev": "299e85fc8fd9def5fdb9d517c9f367f8d15bd0de", 264 | "type": "github" 265 | }, 266 | "original": { 267 | "owner": "oxalica", 268 | "repo": "rust-overlay", 269 | "type": "github" 270 | } 271 | } 272 | }, 273 | "root": "root", 274 | "version": 7 275 | } 276 | -------------------------------------------------------------------------------- /crates/sensemaker_ui/src/app.rs: -------------------------------------------------------------------------------- 1 | use wasm_bindgen::prelude::*; 2 | use weblog::{console_error, console_log}; 3 | use yew::prelude::*; 4 | 5 | use holochain_client_wrapper::{ 6 | AdminWebsocket, AdminWsCmd, AdminWsCmdResponse, AppWebsocket, AppWsCmd, AppWsCmdResponse, 7 | CellId, DeserializeFromJsObj, HashRoleProof, SerializeToJsObj, 8 | }; 9 | 10 | pub enum Msg { 11 | AdminWs(WsMsg), 12 | AppWs(WsMsg), 13 | Log(String), 14 | Error(String), 15 | WidgetsInstalled, 16 | } 17 | 18 | pub enum WsMsg { 19 | Cmd(WSCMD), 20 | CmdResponse(Result), 21 | } 22 | 23 | pub struct Model { 24 | admin_ws: AdminWebsocket, 25 | app_ws: AppWebsocket, 26 | } 27 | 28 | #[derive(Properties, PartialEq)] 29 | pub struct ModelProps { 30 | pub admin_ws_js: JsValue, 31 | pub app_ws_js: JsValue, 32 | pub cell_id_js: JsValue, 33 | } 34 | 35 | impl Component for Model { 36 | type Message = Msg; 37 | type Properties = ModelProps; 38 | 39 | fn create(ctx: &Context) -> Self { 40 | let props = ctx.props(); 41 | let cell_id = CellId::deserialize_from_js_obj(props.cell_id_js.clone()); 42 | let cell_id_ = cell_id.clone(); 43 | let app_ws: AppWebsocket = props.app_ws_js.clone().into(); 44 | let app_ws_ = app_ws.clone(); 45 | let admin_ws: AdminWebsocket = props.admin_ws_js.clone().into(); 46 | let admin_ws_ = admin_ws.clone(); 47 | ctx.link().send_future(async move { 48 | let ret = async { 49 | let active_apps = match admin_ws_.call(AdminWsCmd::ListActiveApps).await { 50 | Ok(AdminWsCmdResponse::ListActiveApps(x)) => Ok(x), 51 | Ok(resp) => Err(format!( 52 | "impossible: invalid response 53 | {:?}", 54 | resp 55 | )), 56 | Err(err) => Err(format!("err: {:?}", err)), 57 | }?; 58 | console_log!(format!("active_apps: {:?}", active_apps)); 59 | 60 | let target_dna_pairs: Vec<(String, String)> = vec![ 61 | ("memez_main_zome", "../widgets_rs/happs/memez/memez.dna"), 62 | ("paperz_main_zome", "../widgets_rs/happs/paperz/paperz.dna"), 63 | ] 64 | .into_iter() 65 | .map(|(x, y)| (x.into(), y.into())) 66 | .collect(); 67 | 68 | let mut all_succeeded = true; 69 | for (target_dna_pair_name, target_dna_pair_path) in target_dna_pairs { 70 | // TODO we could handle the cases of installed-but-not-enabled, etc, later. 71 | if active_apps.contains(&target_dna_pair_name) { 72 | console_log!(format!("dna {} is already active", target_dna_pair_name)); 73 | } else { 74 | // TODO this error handling is a bit brittle 75 | match install_enable_dna( 76 | cell_id_.clone(), 77 | admin_ws_.clone(), 78 | target_dna_pair_name.clone(), 79 | target_dna_pair_path, 80 | ) 81 | .await 82 | { 83 | Err(err) => { 84 | console_error!(err); 85 | all_succeeded = false; 86 | } 87 | Ok(widget_cell_id) => { 88 | // TODO call widget method to set the sensemaker cell_id for it 89 | let cmd = AppWsCmd::CallZome { 90 | cell_id: widget_cell_id.clone(), 91 | zome_name: target_dna_pair_name.into(), 92 | fn_name: "set_sensemaker_cell_id".into(), 93 | payload: cell_id_.clone().serialize_to_js_obj(), 94 | provenance: widget_cell_id.1.clone(), 95 | cap: "".into(), 96 | }; 97 | let resp = app_ws_.call(cmd).await; 98 | match resp { 99 | Ok(AppWsCmdResponse::CallZome(val)) => { 100 | console_log!(format!("set_sensemaker_cell_id: {:?}", val)) 101 | } 102 | Ok(resp) => { 103 | console_error!(format!( 104 | "impossible: invalid response: {:?}", 105 | resp 106 | )) 107 | } 108 | Err(err) => { 109 | console_error!(format!("err: {:?}", err)) 110 | } 111 | } 112 | } 113 | } 114 | } 115 | } 116 | Ok(all_succeeded) 117 | }; 118 | match ret.await { 119 | Err(err) => Msg::Error(err), 120 | Ok(false) => Msg::Error("see console error log".into()), 121 | Ok(true) => Msg::WidgetsInstalled, 122 | } 123 | }); 124 | Self { admin_ws, app_ws } 125 | } 126 | 127 | fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { 128 | match msg { 129 | Msg::AdminWs(ws_msg) => match ws_msg { 130 | WsMsg::Cmd(cmd) => { 131 | let ws = self.admin_ws.clone(); 132 | ctx.link().send_future(async move { 133 | Msg::AdminWs(WsMsg::CmdResponse(ws.call(cmd).await)) 134 | }); 135 | false 136 | } 137 | 138 | WsMsg::CmdResponse(resp) => { 139 | match resp { 140 | Ok(val) => { 141 | console_log!(format!("WsMsg::CmdResponse: {:?}", val)); 142 | } 143 | Err(err) => { 144 | console_error!(format!("WsMsg::CmdResponse: error: {:?}", err)); 145 | } 146 | }; 147 | false 148 | } 149 | }, 150 | 151 | Msg::AppWs(ws_msg) => match ws_msg { 152 | WsMsg::Cmd(cmd) => { 153 | let ws = self.app_ws.clone(); 154 | ctx.link().send_future(async move { 155 | Msg::AppWs(WsMsg::CmdResponse(ws.call(cmd).await)) 156 | }); 157 | false 158 | } 159 | 160 | WsMsg::CmdResponse(resp) => { 161 | match resp { 162 | Ok(val) => { 163 | console_log!(format!("WsMsg::CmdResponse: {:?}", val)); 164 | } 165 | Err(err) => { 166 | console_error!(format!("WsMsg::CmdResponse: error: {:?}", err)); 167 | } 168 | }; 169 | false 170 | } 171 | }, 172 | 173 | Msg::Error(err) => { 174 | console_error!("Error: ", err); 175 | false 176 | } 177 | 178 | Msg::Log(err) => { 179 | console_log!("Log: ", err); 180 | false 181 | } 182 | 183 | Msg::WidgetsInstalled => { 184 | console_log!("widgets installed!"); 185 | false 186 | } 187 | } 188 | } 189 | 190 | fn view(&self, _ctx: &Context) -> Html { 191 | html! { 192 |
193 |

{"hello, sensemaker 👋"}

194 |
195 | } 196 | } 197 | } 198 | async fn install_enable_dna( 199 | cell_id: CellId, 200 | ws: AdminWebsocket, 201 | installed_app_id: String, 202 | path: String, 203 | ) -> Result { 204 | let cmd = AdminWsCmd::RegisterDna { 205 | path, 206 | uid: None, 207 | properties: None, 208 | }; 209 | let dna_hash = match ws.call(cmd).await { 210 | Ok(AdminWsCmdResponse::RegisterDna(x)) => Ok(x), 211 | Ok(resp) => Err(format!("impossible: invalid response: {:?}", resp)), 212 | Err(err) => Err(format!("err: {:?}", err)), 213 | }?; 214 | let agent_key = cell_id.1; 215 | let cmd = AdminWsCmd::InstallApp { 216 | installed_app_id: installed_app_id.clone(), 217 | agent_key: agent_key.clone(), 218 | dnas: vec![HashRoleProof { 219 | hash: dna_hash.clone(), 220 | role_id: "thedna".into(), 221 | membrane_proof: None, 222 | }], 223 | }; 224 | let install_app = match ws.call(cmd).await { 225 | Ok(AdminWsCmdResponse::InstallApp(x)) => Ok(x), 226 | Ok(resp) => Err(format!("impossible: invalid response: {:?}", resp)), 227 | Err(err) => Err(format!("err: {:?}", err)), 228 | }?; 229 | console_log!(format!("install_app: {:?}", install_app)); 230 | let cmd = AdminWsCmd::EnableApp { installed_app_id }; 231 | let enable_app = match ws.call(cmd).await { 232 | Ok(AdminWsCmdResponse::EnableApp(x)) => Ok(x), 233 | Ok(resp) => Err(format!("impossible: invalid response: {:?}", resp)), 234 | Err(err) => Err(format!("err: {:?}", err)), 235 | }?; 236 | console_log!(format!("enable_app: {:?}", enable_app)); 237 | let new_dna_cell_id = (dna_hash, agent_key); 238 | Ok(new_dna_cell_id) 239 | } 240 | -------------------------------------------------------------------------------- /crates/social_sensemaker/tests/simple.rs: -------------------------------------------------------------------------------- 1 | use hdk::prelude::*; 2 | use holochain::conductor::config::ConductorConfig; 3 | // use holochain::sweettest::{SweetConductor, SweetNetwork, SweetZome}; 4 | use holochain::sweettest::{SweetAppBatch, SweetConductorBatch, SweetDnaFile}; 5 | // use holochain::test_utils::host_fn_caller::Post; 6 | // use holochain::test_utils::wait_for_integration_1m; 7 | // use holochain::test_utils::wait_for_integration_with_others_10s; 8 | // use holochain::test_utils::WaitOps; 9 | use std::path::Path; 10 | 11 | const APP_ID: &str = "sensemaker"; 12 | const ZOME_NAME: &str = "sensemaker_main"; 13 | 14 | #[tokio::test(flavor = "multi_thread")] 15 | pub async fn test_creation_retrieval_se() -> anyhow::Result<()> { 16 | use holochain::test_utils::consistency_10s; 17 | use kitsune_p2p::KitsuneP2pConfig; 18 | use std::sync::Arc; 19 | 20 | use common::{CreateSensemakerEntryInput, SensemakerEntry}; 21 | use rep_lang_core::abstract_syntax::{Expr, Lit}; 22 | 23 | let _g = observability::test_run().ok(); 24 | const NUM_CONDUCTORS: usize = 3; 25 | 26 | let mut tuning = 27 | kitsune_p2p_types::config::tuning_params_struct::KitsuneP2pTuningParams::default(); 28 | tuning.gossip_strategy = "none".to_string(); 29 | 30 | let mut network = KitsuneP2pConfig::default(); 31 | network.tuning_params = Arc::new(tuning); 32 | let mut config = ConductorConfig::default(); 33 | config.network = Some(network); 34 | let mut conductors = SweetConductorBatch::from_config(NUM_CONDUCTORS, config).await; 35 | 36 | let path = Path::new("../../happs/social_sensemaker/social_sensemaker.dna"); 37 | let dna_file = SweetDnaFile::from_bundle(path).await.unwrap(); 38 | 39 | let apps = conductors.setup_app(APP_ID, &[dna_file]).await.unwrap(); 40 | conductors.exchange_peer_info().await; 41 | 42 | let ((alice,), (bobbo,), (carol,)) = apps.into_tuples(); 43 | 44 | let expr = Expr::Lit(Lit::LInt(0)); 45 | let csei = CreateSensemakerEntryInput { 46 | expr: expr.clone(), 47 | args: vec![], 48 | }; 49 | let hh: HeaderHash = conductors[0] 50 | .call(&alice.zome(ZOME_NAME), "create_sensemaker_entry", csei) 51 | .await; 52 | 53 | // wait for gossip to propagate 54 | // TODO figure out how to avoid an arbitrary hardcoded delay. can we check for consistency 55 | // async? 56 | consistency_10s(&[&alice, &bobbo, &carol]).await; 57 | 58 | { 59 | // assert correct retrieval 60 | let (_se_hash, se): (EntryHash, SensemakerEntry) = conductors[1] 61 | .call( 62 | &bobbo.zome(ZOME_NAME), 63 | "get_sensemaker_entry_by_headerhash", 64 | hh.clone(), 65 | ) 66 | .await; 67 | assert_eq!(se.operator, expr.clone()); 68 | } 69 | 70 | { 71 | // assert correct retrieval 72 | let (_se_hash, se): (EntryHash, SensemakerEntry) = conductors[2] 73 | .call( 74 | &carol.zome(ZOME_NAME), 75 | "get_sensemaker_entry_by_headerhash", 76 | hh, 77 | ) 78 | .await; 79 | assert_eq!(se.operator, expr); 80 | } 81 | 82 | Ok(()) 83 | } 84 | 85 | #[tokio::test(flavor = "multi_thread")] 86 | pub async fn test_round_robin_incrementation() -> anyhow::Result<()> { 87 | use holochain::test_utils::consistency_10s; 88 | 89 | use common::{CreateSensemakerEntryInput, SensemakerEntry, SensemakerOperand}; 90 | use rep_lang_core::{ 91 | abstract_syntax::{Expr, Lit, PrimOp}, 92 | app, 93 | }; 94 | use rep_lang_runtime::eval::{FlatValue, Value}; 95 | 96 | const NUM_CONDUCTORS: usize = 4; 97 | const ROUND_ROBIN_COUNT: usize = 51; 98 | 99 | let (conductors, apps) = setup_conductors_cells(NUM_CONDUCTORS).await; 100 | let cells = apps.cells_flattened(); 101 | 102 | let init_csei = CreateSensemakerEntryInput { 103 | expr: Expr::Lit(Lit::LInt(0)), 104 | args: vec![], 105 | }; 106 | let hh: HeaderHash = conductors[0] 107 | .call( 108 | &cells[0].zome(ZOME_NAME), 109 | "create_sensemaker_entry", 110 | init_csei, 111 | ) 112 | .await; 113 | 114 | let mut last_se_hh = hh; 115 | for idx in 0..ROUND_ROBIN_COUNT { 116 | // await consistency 117 | consistency_10s(&cells).await; 118 | 119 | let csei = CreateSensemakerEntryInput { 120 | expr: app!(Expr::Prim(PrimOp::Add), Expr::Lit(Lit::LInt(1))), 121 | args: vec![SensemakerOperand::SensemakerOperand(last_se_hh)], 122 | }; 123 | 124 | let new_hh: HeaderHash = conductors[idx % NUM_CONDUCTORS] 125 | .call( 126 | &cells[idx % NUM_CONDUCTORS].zome(ZOME_NAME), 127 | "create_sensemaker_entry", 128 | csei, 129 | ) 130 | .await; 131 | 132 | last_se_hh = new_hh; 133 | } 134 | 135 | // check final value 136 | consistency_10s(&cells).await; 137 | let (_final_se_hash, final_se): (EntryHash, SensemakerEntry) = conductors[0] 138 | .call( 139 | &cells[0].zome(ZOME_NAME), 140 | "get_sensemaker_entry_by_headerhash", 141 | last_se_hh, 142 | ) 143 | .await; 144 | assert_eq!( 145 | final_se.output_flat_value, 146 | FlatValue(Value::VInt(ROUND_ROBIN_COUNT as i64)) 147 | ); 148 | 149 | Ok(()) 150 | } 151 | 152 | /// test arity-2 functions with `fib`. 153 | /// the fibonacci sequences starts off `0, 1, 1, 2, 3, 5, 8, 13 ...`. each term is 154 | /// the sum of the previous 2 terms. we start off by creating 2 155 | /// `SensemakerEntry`s with values `0 :: Int` & `1 :: Int`, respectively. 156 | #[tokio::test(flavor = "multi_thread")] 157 | pub async fn test_round_robin_fibonacci() -> anyhow::Result<()> { 158 | use holochain::test_utils::consistency_10s; 159 | 160 | use common::{CreateSensemakerEntryInput, SensemakerEntry, SensemakerOperand}; 161 | use rep_lang_core::abstract_syntax::{Expr, Lit, PrimOp}; 162 | use rep_lang_runtime::eval::{FlatValue, Value}; 163 | 164 | const NUM_CONDUCTORS: usize = 5; 165 | const ROUND_ROBIN_COUNT: usize = 37; 166 | 167 | let (conductors, apps) = setup_conductors_cells(NUM_CONDUCTORS).await; 168 | let cells = apps.cells_flattened(); 169 | 170 | // TODO can the commonalities be abstracted? unsure about async closure 171 | // capturing env. 172 | let mut hh_0 = { 173 | let init_csei = CreateSensemakerEntryInput { 174 | expr: Expr::Lit(Lit::LInt(0)), 175 | args: vec![], 176 | }; 177 | let init_hh: HeaderHash = conductors[0] 178 | .call( 179 | &cells[0].zome(ZOME_NAME), 180 | "create_sensemaker_entry", 181 | init_csei, 182 | ) 183 | .await; 184 | init_hh 185 | }; 186 | let mut hh_1 = { 187 | let init_csei = CreateSensemakerEntryInput { 188 | expr: Expr::Lit(Lit::LInt(1)), 189 | args: vec![], 190 | }; 191 | let init_hh: HeaderHash = conductors[0] 192 | .call( 193 | &cells[0].zome(ZOME_NAME), 194 | "create_sensemaker_entry", 195 | init_csei, 196 | ) 197 | .await; 198 | init_hh 199 | }; 200 | 201 | for idx in 1..ROUND_ROBIN_COUNT { 202 | // await consistency 203 | consistency_10s(&cells).await; 204 | 205 | let csei = CreateSensemakerEntryInput { 206 | expr: Expr::Prim(PrimOp::Add), 207 | args: vec![ 208 | SensemakerOperand::SensemakerOperand(hh_0.clone()), 209 | SensemakerOperand::SensemakerOperand(hh_1.clone()), 210 | ], 211 | }; 212 | 213 | let new_hh: HeaderHash = conductors[idx % NUM_CONDUCTORS] 214 | .call( 215 | &cells[idx % NUM_CONDUCTORS].zome(ZOME_NAME), 216 | "create_sensemaker_entry", 217 | csei, 218 | ) 219 | .await; 220 | 221 | hh_0 = hh_1; 222 | hh_1 = new_hh; 223 | } 224 | 225 | // check final value 226 | consistency_10s(&cells).await; 227 | let (_final_se_hash, final_se): (EntryHash, SensemakerEntry) = conductors[0] 228 | .call( 229 | &cells[0].zome(ZOME_NAME), 230 | "get_sensemaker_entry_by_headerhash", 231 | hh_1, 232 | ) 233 | .await; 234 | assert_eq!( 235 | final_se.output_flat_value, 236 | FlatValue(Value::VInt(nth_fib(ROUND_ROBIN_COUNT as i64))) 237 | ); 238 | 239 | Ok(()) 240 | } 241 | 242 | #[tokio::test(flavor = "multi_thread")] 243 | pub async fn test_round_robin_arity_n_sum() -> anyhow::Result<()> { 244 | use holochain::test_utils::consistency_10s; 245 | 246 | use common::{CreateSensemakerEntryInput, SensemakerEntry, SensemakerOperand}; 247 | use rep_lang_core::{ 248 | abstract_syntax::{Expr, Lit, Name, PrimOp}, 249 | app, 250 | }; 251 | use rep_lang_runtime::eval::{FlatValue, Value}; 252 | 253 | const NUM_CONDUCTORS: usize = 5; 254 | const ROUND_ROBIN_COUNT: usize = 37; 255 | 256 | let (conductors, apps) = setup_conductors_cells(NUM_CONDUCTORS).await; 257 | let cells = apps.cells_flattened(); 258 | 259 | let init_csei = CreateSensemakerEntryInput { 260 | expr: Expr::Lit(Lit::LInt(1)), 261 | args: vec![], 262 | }; 263 | let hh: HeaderHash = conductors[0] 264 | .call( 265 | &cells[0].zome(ZOME_NAME), 266 | "create_sensemaker_entry", 267 | init_csei, 268 | ) 269 | .await; 270 | 271 | let mut arg_hh_s = vec![hh]; 272 | for idx in 0..=ROUND_ROBIN_COUNT { 273 | // await consistency 274 | consistency_10s(&cells).await; 275 | 276 | let expr = { 277 | // generate fresh names 278 | let names: Vec = (0..arg_hh_s.len()) 279 | .map(|n| Name(format!("arg_{}", n))) 280 | .collect(); 281 | 282 | // wrap said fresh names into `Expr`s 283 | let name_vars = names.clone().into_iter().map(Expr::Var); 284 | 285 | // fold a summation over the args, with accumulator 0 286 | let app_f = |acc, arg| app!(app!(Expr::Prim(PrimOp::Add), acc), arg); 287 | let app = name_vars.fold(Expr::Lit(Lit::LInt(0)), app_f); 288 | 289 | // fold over the generated freshnames to construct a lambda which will bind the 290 | // names used in the applicaton 291 | let lam_f = |bd, nm| Expr::Lam(nm, Box::new(bd)); 292 | names.into_iter().rev().fold(app, lam_f) 293 | }; 294 | let csei = CreateSensemakerEntryInput { 295 | expr, 296 | args: arg_hh_s 297 | .iter() 298 | .cloned() 299 | .map(SensemakerOperand::SensemakerOperand) 300 | .collect(), 301 | }; 302 | 303 | let new_hh: HeaderHash = conductors[idx % NUM_CONDUCTORS] 304 | .call( 305 | &cells[idx % NUM_CONDUCTORS].zome(ZOME_NAME), 306 | "create_sensemaker_entry", 307 | csei, 308 | ) 309 | .await; 310 | 311 | arg_hh_s.push(new_hh); 312 | } 313 | 314 | let final_hh = arg_hh_s.last().expect("args should be non-empty"); 315 | 316 | // check final value 317 | consistency_10s(&cells).await; 318 | let (_final_se_hash, final_se): (EntryHash, SensemakerEntry) = conductors[0] 319 | .call( 320 | &cells[0].zome(ZOME_NAME), 321 | "get_sensemaker_entry_by_headerhash", 322 | final_hh, 323 | ) 324 | .await; 325 | assert_eq!( 326 | final_se.output_flat_value, 327 | FlatValue(Value::VInt(nth_sum_all(ROUND_ROBIN_COUNT as u32))) 328 | ); 329 | 330 | Ok(()) 331 | } 332 | 333 | //////////////////////////////////////////////////////////////////////////////// 334 | // helpers 335 | //////////////////////////////////////////////////////////////////////////////// 336 | async fn setup_conductors_cells(num_conductors: usize) -> (SweetConductorBatch, SweetAppBatch) { 337 | use kitsune_p2p::KitsuneP2pConfig; 338 | use std::sync::Arc; 339 | 340 | let _g = observability::test_run().ok(); 341 | 342 | let mut tuning = 343 | kitsune_p2p_types::config::tuning_params_struct::KitsuneP2pTuningParams::default(); 344 | tuning.gossip_strategy = "none".to_string(); 345 | 346 | let mut network = KitsuneP2pConfig::default(); 347 | network.tuning_params = Arc::new(tuning); 348 | let mut config = ConductorConfig::default(); 349 | config.network = Some(network); 350 | let mut conductors = SweetConductorBatch::from_config(num_conductors, config).await; 351 | 352 | let path = Path::new("../../happs/social_sensemaker/social_sensemaker.dna"); 353 | let dna_file = SweetDnaFile::from_bundle(path).await.unwrap(); 354 | 355 | let apps = conductors.setup_app(APP_ID, &[dna_file]).await.unwrap(); 356 | conductors.exchange_peer_info().await; 357 | 358 | (conductors, apps) 359 | } 360 | 361 | fn nth_fib(mut n: i64) -> i64 { 362 | let mut x0 = 0; 363 | let mut x1 = 1; 364 | while n > 1 { 365 | n -= 1; 366 | let tmp = x0 + x1; 367 | x0 = x1; 368 | x1 = tmp; 369 | } 370 | x1 371 | } 372 | 373 | fn nth_sum_all(n: u32) -> i64 { 374 | 2_i64.pow(n) 375 | } 376 | -------------------------------------------------------------------------------- /crates/frontend-tui/src/main.rs: -------------------------------------------------------------------------------- 1 | use combine::{stream::position, EasyParser, StreamOnce}; 2 | use holo_hash::HeaderHash; 3 | use holochain_client::{AdminWebsocket, AppWebsocket, ZomeCall}; 4 | use holochain_types::{ 5 | app::AppBundleSource, 6 | dna::{AgentPubKey, DnaBundle, DnaHash}, 7 | prelude::{CellId, InstallAppBundlePayload}, 8 | }; 9 | use holochain_zome_types::zome_io::ExternIO; 10 | use pretty::RcDoc; 11 | use std::{ 12 | cmp, error, io, 13 | path::{Path, PathBuf}, 14 | sync::mpsc::Sender, 15 | }; 16 | use structopt::StructOpt; 17 | use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; 18 | use tui::{ 19 | backend::TermionBackend, 20 | layout::{Constraint, Direction, Layout}, 21 | style::{Modifier, Style}, 22 | text::{Span, Spans, Text}, 23 | widgets::{Block, Borders, Paragraph, Wrap}, 24 | Terminal, 25 | }; 26 | 27 | use common::{CreateSensemakerEntryInput, SensemakerEntry, SensemakerOperand}; 28 | use rep_lang_concrete_syntax::{parse::expr, pretty::ppr_expr, util::pretty::to_pretty}; 29 | use rep_lang_core::abstract_syntax::Expr; 30 | use rep_lang_runtime::{ 31 | env::Env, 32 | infer::{close_over, infer_expr, normalize, unifies, InferState}, 33 | types::{Scheme, Type}, 34 | }; 35 | 36 | mod event; 37 | use event::{Event, Events}; 38 | 39 | const APP_ID: &str = "sensemaker"; 40 | 41 | #[derive(Debug, Clone)] 42 | pub enum ExprState { 43 | Valid(ValidExprState), 44 | Invalid(String), 45 | } 46 | 47 | #[derive(Debug, Clone)] 48 | pub struct ValidExprState { 49 | expr_sc: Scheme, 50 | expr: Expr, 51 | /// any SEs we have already selected for `expr` to be applied to. Vec 52 | /// ordering is the order in which they will be applied. 53 | args: Vec<(HeaderHash, SensemakerEntry)>, 54 | /// SEs which have not yet been selected for application, but are 55 | /// candidates (meaning that `expr` must be a closure & `expr_sc` must have a 56 | /// toplevel `TArr`. and the closure argument's `Scheme` unifies with all 57 | /// of these candidates individually). 58 | next_application_candidates: Vec<(HeaderHash, SensemakerEntry)>, 59 | /// index (if any) of our current choice from `next_application_candidates`. 60 | /// invariant for value `Some(i)`, `0 <= i < next_application_candidates.len()` 61 | candidate_choice_index: Option, 62 | } 63 | 64 | impl ValidExprState { 65 | /// compute the `Scheme` which results from applying `expr_sc` to the 66 | /// `Scheme`s of all `args`. 67 | fn computed_application_sc(&self) -> Result { 68 | let mut is = InferState::new(); 69 | 70 | let Scheme(_, normalized_expr_ty) = normalize(&mut is, self.expr_sc.clone()); 71 | 72 | let applicator = 73 | |acc_ty_res: Result, arg_sc: Scheme| -> Result { 74 | match acc_ty_res? { 75 | Type::TArr(fn_arg_ty, fn_ret_ty) => { 76 | // check unification of normalized type 77 | let Scheme(_, normalized_arg_ty) = normalize(&mut is, arg_sc); 78 | match unifies(normalized_arg_ty, *fn_arg_ty) { 79 | Err(msg) => Err(format!("unification error: {:?}", msg)), 80 | Ok(_) => Ok(*fn_ret_ty), 81 | } 82 | } 83 | _ => Err("arity mismatch".to_string()), 84 | } 85 | }; 86 | let full_application: Type = self 87 | .args 88 | .iter() 89 | .map(|(_, se)| se.output_scheme.clone()) 90 | .fold(Ok(normalized_expr_ty), applicator)?; 91 | Ok(close_over(full_application)) 92 | } 93 | } 94 | 95 | fn ppr_ves(ves: &ValidExprState) -> RcDoc<()> { 96 | let docs = vec![ 97 | RcDoc::text("scheme:\n"), 98 | ves.expr_sc.ppr().nest(1).group(), 99 | RcDoc::text("\nexpr:\n"), 100 | ppr_expr(&ves.expr).nest(1).group(), 101 | RcDoc::text("\nargs:\n"), 102 | RcDoc::text(format!("{:?}", ves.args)).nest(1).group(), 103 | ]; 104 | RcDoc::concat(docs) 105 | } 106 | 107 | impl ExprState { 108 | fn is_valid(&self) -> bool { 109 | matches!(self, ExprState::Valid(_)) 110 | } 111 | 112 | fn has_valid_candidate_idx(&self) -> bool { 113 | match &self { 114 | ExprState::Valid(ves) => ves.candidate_choice_index.is_some(), 115 | _ => false, 116 | } 117 | } 118 | 119 | fn has_args(&self) -> bool { 120 | match &self { 121 | ExprState::Valid(ves) => !ves.args.is_empty(), 122 | _ => false, 123 | } 124 | } 125 | } 126 | 127 | enum ViewState { 128 | Viewer(Vec), 129 | Creator, 130 | } 131 | 132 | impl ViewState { 133 | fn toggle(&self) -> ViewState { 134 | match &self { 135 | ViewState::Viewer(_) => ViewState::Creator, 136 | ViewState::Creator => ViewState::Viewer(vec![]), 137 | } 138 | } 139 | 140 | fn is_creator(&self) -> bool { 141 | matches!(self, ViewState::Creator) 142 | } 143 | 144 | fn is_viewer(&self) -> bool { 145 | matches!(self, ViewState::Viewer(_)) 146 | } 147 | } 148 | 149 | #[derive(Clone)] 150 | pub struct HcInfo { 151 | pub admin_ws: AdminWebsocket, 152 | pub app_ws: AppWebsocket, 153 | pub agent_pk: AgentPubKey, 154 | pub dna_hash: DnaHash, 155 | } 156 | 157 | impl HcInfo { 158 | async fn get_sensemaker_entries_which_unify( 159 | &mut self, 160 | opt_target_sc: Option, 161 | ) -> Vec<(HeaderHash, SensemakerEntry)> { 162 | let payload = ExternIO::encode(opt_target_sc).unwrap(); 163 | let cell_id = CellId::new(self.dna_hash.clone(), self.agent_pk.clone()); 164 | let zc = ZomeCall { 165 | cell_id, 166 | zome_name: "sensemaker_main".into(), 167 | fn_name: "get_sensemaker_entries_which_unify".into(), 168 | payload, 169 | cap_secret: None, 170 | provenance: self.agent_pk.clone(), 171 | }; 172 | let result = self.app_ws.zome_call(zc).await.unwrap(); 173 | result.decode().unwrap() 174 | } 175 | 176 | async fn create_sensemaker_entry(&mut self, input: CreateSensemakerEntryInput) -> HeaderHash { 177 | let payload = ExternIO::encode(input).unwrap(); 178 | let cell_id = CellId::new(self.dna_hash.clone(), self.agent_pk.clone()); 179 | let zc = ZomeCall { 180 | cell_id, 181 | zome_name: "sensemaker_main".into(), 182 | fn_name: "create_sensemaker_entry".into(), 183 | payload, 184 | cap_secret: None, 185 | provenance: self.agent_pk.clone(), 186 | }; 187 | let result = self.app_ws.zome_call(zc).await.unwrap(); 188 | result.decode().unwrap() 189 | } 190 | } 191 | 192 | struct App { 193 | /// the text which may parse to an `Expr`. 194 | expr_input: String, 195 | /// the result of parsing and typechecking `expr_input`. 196 | expr_state: ExprState, 197 | /// this is an Option so we can close the Events stream when we open EDITOR 198 | /// (by setting this field to `None` and thereby allowing the `Events` go 199 | /// out of scope and be collected). 200 | /// when the TUI has control of the screen & input, this should always be 201 | /// `Some`. 202 | opt_events: Option>, 203 | event_sender: Sender>, 204 | hc_info: Option, 205 | hc_responses: Vec, 206 | view_state: ViewState, 207 | } 208 | 209 | impl App { 210 | fn new() -> App { 211 | let (events, event_sender) = Events::mk(); 212 | App { 213 | expr_input: String::new(), 214 | expr_state: ExprState::Invalid("init".into()), 215 | opt_events: Some(events), 216 | event_sender, 217 | hc_info: None, 218 | hc_responses: vec!["not connected".into()], 219 | view_state: ViewState::Creator, 220 | } 221 | } 222 | 223 | fn log_hc_response(&mut self, s: String) { 224 | self.hc_responses.insert(0, s); 225 | } 226 | 227 | async fn get_selection_candidates(&mut self) { 228 | // zome call to look for SEs which unify with the argument 229 | if let ExprState::Valid(ves) = &self.expr_state { 230 | if let Ok(Scheme(tvs, Type::TArr(arg, _))) = ves.computed_application_sc() { 231 | let opt_target_sc = Some(Scheme(tvs.clone(), *arg.clone())); 232 | let hash_se_s = self 233 | .hc_info 234 | .as_mut() 235 | .unwrap() 236 | .get_sensemaker_entries_which_unify(opt_target_sc) 237 | .await; 238 | self.event_sender 239 | .send(Event::SelectorSes(hash_se_s)) 240 | .expect("send to succeed"); 241 | } 242 | } 243 | } 244 | } 245 | 246 | #[tokio::main] 247 | async fn main() -> Result<(), Box> { 248 | // cli arg parsing 249 | let args = Cli::from_args(); 250 | 251 | // terminal initialization 252 | let stdout = io::stdout().into_raw_mode()?; 253 | let stdout = MouseTerminal::from(stdout); 254 | let stdout = AlternateScreen::from(stdout); 255 | let backend = TermionBackend::new(stdout); 256 | let mut terminal = Terminal::new(backend)?; 257 | 258 | let mut app = App::new(); 259 | 260 | { 261 | let app_ws = AppWebsocket::connect(format!("ws://127.0.0.1:{}", args.hc_app_port)) 262 | .await 263 | .expect("connect to succeed"); 264 | let mut admin_ws = 265 | AdminWebsocket::connect(format!("ws://127.0.0.1:{}", args.hc_admin_port)) 266 | .await 267 | .expect("connect to succeed"); 268 | let agent_pk = admin_ws.generate_agent_pub_key().await.unwrap(); 269 | let dna_hash = { 270 | let path = Path::new("./happs/social_sensemaker/social_sensemaker.dna"); 271 | let bundle = DnaBundle::read_from_file(path).await.unwrap(); 272 | let (_dna_file, dna_hash) = bundle.into_dna_file(None, None).await.unwrap(); 273 | dna_hash 274 | }; 275 | let hc_info = HcInfo { 276 | admin_ws: admin_ws.clone(), 277 | app_ws, 278 | agent_pk: agent_pk.clone(), 279 | dna_hash, 280 | }; 281 | app.event_sender 282 | .send(Event::HcInfo(hc_info)) 283 | .expect("send to succeed"); 284 | 285 | let pathbuf = PathBuf::from("./happs/social_sensemaker/social_sensemaker.happ"); 286 | let iabp = InstallAppBundlePayload { 287 | source: AppBundleSource::Path(pathbuf), 288 | agent_key: agent_pk, 289 | installed_app_id: Some(APP_ID.into()), 290 | membrane_proofs: Default::default(), 291 | uid: None, 292 | }; 293 | let _app_info = admin_ws.install_app_bundle(iabp).await.unwrap(); 294 | let _enable_app_response = admin_ws.enable_app(APP_ID.into()).await.unwrap(); 295 | } 296 | 297 | loop { 298 | // draw UI 299 | terminal.draw(|f| match &app.view_state { 300 | ViewState::Creator => { 301 | let chunks = Layout::default() 302 | .direction(Direction::Vertical) 303 | .margin(1) 304 | .constraints( 305 | [ 306 | Constraint::Length(2), 307 | Constraint::Min(15), 308 | Constraint::Min(15), 309 | Constraint::Min(25), 310 | Constraint::Length(6), 311 | ] 312 | .as_ref(), 313 | ) 314 | .split(f.size()); 315 | 316 | let mut default_commands = vec![ 317 | Span::raw("press "), 318 | Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), 319 | Span::raw(" to exit, "), 320 | Span::styled("Tab", Style::default().add_modifier(Modifier::BOLD)), 321 | Span::raw(" to switch to viewer, "), 322 | Span::styled("e", Style::default().add_modifier(Modifier::BOLD)), 323 | Span::raw(" to launch $EDITOR"), 324 | ]; 325 | let mut valid_expr_commands = vec![ 326 | Span::raw(", "), 327 | Span::styled("c", Style::default().add_modifier(Modifier::BOLD)), 328 | Span::raw(" to create entry"), 329 | ]; 330 | let mut select_commands = vec![ 331 | Span::raw(", "), 332 | Span::styled("s", Style::default().add_modifier(Modifier::BOLD)), 333 | Span::raw(" to select candidate arg"), 334 | ]; 335 | let mut deselect_commands = vec![ 336 | Span::raw(", "), 337 | Span::styled("d", Style::default().add_modifier(Modifier::BOLD)), 338 | Span::raw(" to deselect last arg"), 339 | ]; 340 | let msg = { 341 | if app.expr_state.is_valid() { 342 | default_commands.append(&mut valid_expr_commands); 343 | } 344 | if app.expr_state.has_valid_candidate_idx() { 345 | default_commands.append(&mut select_commands); 346 | } 347 | if app.expr_state.has_args() { 348 | default_commands.append(&mut deselect_commands); 349 | } 350 | default_commands.push(Span::raw(".")); 351 | default_commands 352 | }; 353 | 354 | let help_message = 355 | Paragraph::new(Text::from(Spans::from(msg))).wrap(Wrap { trim: false }); 356 | f.render_widget(help_message, chunks[0]); 357 | 358 | let expr_input = Paragraph::new(app.expr_input.as_ref()) 359 | .style(Style::default()) 360 | .block(Block::default().borders(Borders::ALL).title("expr input")); 361 | f.render_widget(expr_input, chunks[1]); 362 | 363 | { 364 | let dims = chunks[2]; 365 | let expr_state_text = match &app.expr_state { 366 | ExprState::Invalid(msg) => format!("invalid state:\n\n{}", msg), 367 | ExprState::Valid(ves) => { 368 | let app_sc_ppr = ves 369 | .computed_application_sc() 370 | .map(|app_sc| to_pretty(app_sc.ppr(), dims.width.into())); 371 | let ppr_string = to_pretty(ppr_ves(ves), dims.width.into()); 372 | // TODO figure out how to prettyprint this properly 373 | format!( 374 | "computed_application_sc:\n{:?}\n\n{}", 375 | app_sc_ppr, ppr_string 376 | ) 377 | } 378 | }; 379 | let expr_state_msg = Paragraph::new(expr_state_text) 380 | .wrap(Wrap { trim: false }) 381 | .style(Style::default()) 382 | .block(Block::default().borders(Borders::ALL).title("expr_state")); 383 | f.render_widget(expr_state_msg, dims); 384 | } 385 | 386 | { 387 | let dims = chunks[3]; 388 | let mut text: Text = Text::from(""); 389 | if let ExprState::Valid(ves) = &app.expr_state { 390 | let candidate_vec: Vec = ves 391 | .next_application_candidates 392 | .iter() 393 | .enumerate() 394 | .map(|(idx, candidate)| { 395 | let mut style = Style::default(); 396 | if Some(idx) == ves.candidate_choice_index { 397 | style = style.add_modifier(Modifier::BOLD); 398 | } 399 | Spans::from(Span::styled(format!("{:?}", candidate), style)) 400 | }) 401 | .collect(); 402 | text.extend(candidate_vec); 403 | }; 404 | let msg = Paragraph::new(text) 405 | .wrap(Wrap { trim: false }) 406 | .style(Style::default()) 407 | .block(Block::default().borders(Borders::ALL).title("SE selector")); 408 | f.render_widget(msg, dims); 409 | } 410 | 411 | let hc_responses = app 412 | .hc_responses 413 | .iter() 414 | .map(|resp| "- ".to_string() + resp) 415 | .collect::>() 416 | .join("\n"); 417 | let app_info = Paragraph::new(hc_responses).style(Style::default()).block( 418 | Block::default() 419 | .borders(Borders::ALL) 420 | .title("holochain responses (newest first)"), 421 | ); 422 | f.render_widget(app_info, chunks[4]); 423 | } 424 | 425 | ViewState::Viewer(se_s) => { 426 | let chunks = Layout::default() 427 | .direction(Direction::Vertical) 428 | .margin(1) 429 | .constraints([Constraint::Length(1), Constraint::Min(1)].as_ref()) 430 | .split(f.size()); 431 | 432 | let help_spans = vec![ 433 | Span::raw("press "), 434 | Span::styled("q", Style::default().add_modifier(Modifier::BOLD)), 435 | Span::raw(" to exit, "), 436 | Span::styled("Tab", Style::default().add_modifier(Modifier::BOLD)), 437 | Span::raw(" to switch to creator."), 438 | ]; 439 | let help_msg = Paragraph::new(Text::from(Spans::from(help_spans))); 440 | f.render_widget(help_msg, chunks[0]); 441 | 442 | let rendered_se_s: String = se_s 443 | .iter() 444 | .map(|se| to_pretty(se.ppr(), chunks[1].width.into())) 445 | .collect::>() 446 | .join("\n\n"); 447 | 448 | let block = Paragraph::new(rendered_se_s) 449 | .wrap(Wrap { trim: false }) 450 | .style(Style::default()) 451 | .block(Block::default().borders(Borders::ALL).title("viewer")); 452 | f.render_widget(block, chunks[1]); 453 | } 454 | })?; 455 | 456 | // handle input 457 | let evt = { 458 | match app.opt_events { 459 | None => panic!("impossible: logic error"), 460 | Some(ref itr) => itr.next()?, 461 | } 462 | }; 463 | match evt { 464 | Event::Input(Key::Char('q')) => { 465 | terminal.clear().expect("clear to succeed"); 466 | break; 467 | } 468 | Event::Input(Key::Char('e')) if app.view_state.is_creator() => { 469 | app.opt_events = None; 470 | terminal.clear().expect("clear to succeed"); 471 | app.expr_input = scrawl::with(&app.expr_input)?; 472 | { 473 | let (events, event_sender) = Events::mk(); 474 | app.opt_events = Some(events); 475 | app.event_sender = event_sender; 476 | } 477 | terminal.clear().expect("clear to succeed"); 478 | app.expr_state = match expr().easy_parse(position::Stream::new(&app.expr_input[..])) 479 | { 480 | Err(err) => ExprState::Invalid(format!("parse error:\n\n{}\n", err)), 481 | Ok((expr, extra_input)) => { 482 | if extra_input.is_partial() { 483 | ExprState::Invalid(format!( 484 | "error: unconsumed input: {:?}", 485 | extra_input 486 | )) 487 | } else { 488 | match infer_expr(&Env::new(), &expr) { 489 | Err(err) => ExprState::Invalid(format!("type error: {:?}", err)), 490 | Ok(expr_sc) => ExprState::Valid(ValidExprState { 491 | expr_sc, 492 | expr, 493 | args: vec![], 494 | next_application_candidates: vec![], 495 | candidate_choice_index: None, 496 | }), 497 | } 498 | } 499 | } 500 | }; 501 | app.get_selection_candidates().await; 502 | } 503 | Event::Input(Key::Char('c')) if app.view_state.is_creator() => { 504 | match (&app.expr_state, &mut app.hc_info) { 505 | (ExprState::Invalid(_), _) => {} // invalid expr 506 | (_, None) => {} // no hc_ws client 507 | (ExprState::Valid(ves), Some(hc_info)) => { 508 | let args = ves 509 | .args 510 | .iter() 511 | .map(|(e_hash, _se)| { 512 | SensemakerOperand::SensemakerOperand(e_hash.clone()) 513 | }) 514 | .collect(); 515 | let input = CreateSensemakerEntryInput { 516 | expr: ves.expr.clone(), 517 | args, 518 | }; 519 | let se_hash = hc_info.create_sensemaker_entry(input).await; 520 | app.log_hc_response(format!("create: se_hash: {:?}", se_hash)); 521 | } 522 | } 523 | } 524 | Event::Input(Key::Down) if app.view_state.is_creator() => { 525 | if let ExprState::Valid(ves) = &mut app.expr_state { 526 | ves.candidate_choice_index = ves 527 | .candidate_choice_index 528 | // this allows us to do a max comparison with "1 less than the len" 529 | // without fear of underflow on an usize. 530 | .map(|i| cmp::min(i + 2, ves.next_application_candidates.len()) - 1); 531 | } 532 | } 533 | Event::Input(Key::Up) if app.view_state.is_creator() => { 534 | if let ExprState::Valid(ves) = &mut app.expr_state { 535 | ves.candidate_choice_index = 536 | ves.candidate_choice_index.map(|i| i.saturating_sub(1)); 537 | } 538 | } 539 | Event::Input(Key::Char('s')) if app.expr_state.has_valid_candidate_idx() => { 540 | match &mut app.expr_state { 541 | ExprState::Invalid(_) => {} // should be unreachable due to match guard 542 | ExprState::Valid(ves) => { 543 | // should be safe due to match guard 544 | let idx = ves.candidate_choice_index.unwrap(); 545 | // TODO is it possible to avoid cloning and pull the memory out of the Vec? 546 | let selection = ves.next_application_candidates[idx].clone(); 547 | ves.next_application_candidates = vec![]; 548 | ves.args.push(selection); 549 | ves.candidate_choice_index = None; 550 | } 551 | } 552 | app.get_selection_candidates().await; 553 | } 554 | Event::Input(Key::Char('d')) if app.expr_state.has_args() => { 555 | match &mut app.expr_state { 556 | ExprState::Invalid(_) => {} // should be unreachable due to match guard 557 | ExprState::Valid(ves) => { 558 | ves.next_application_candidates = vec![]; 559 | ves.args.pop(); 560 | ves.candidate_choice_index = None; 561 | } 562 | } 563 | app.get_selection_candidates().await; 564 | } 565 | Event::Input(Key::Char('\t')) => { 566 | app.view_state = app.view_state.toggle(); 567 | // TODO this cloning could maybe be eliminated 568 | let mut hc_info = app.hc_info.clone().unwrap(); 569 | 570 | if app.view_state.is_viewer() { 571 | let opt_target_sc: Option = None; 572 | let hash_se_s: Vec<(HeaderHash, SensemakerEntry)> = hc_info 573 | .get_sensemaker_entries_which_unify(opt_target_sc) 574 | .await; 575 | let se_s = hash_se_s.into_iter().map(|(_eh, se)| se).collect(); 576 | app.event_sender 577 | .send(Event::ViewerSes(se_s)) 578 | .expect("send to succeed"); 579 | } 580 | } 581 | Event::HcInfo(hc_info) => { 582 | app.hc_info = Some(hc_info); 583 | app.log_hc_response("hc_info: connected".into()); 584 | } 585 | Event::SelectorSes(hash_se_s) => { 586 | app.log_hc_response("got selector SEs".into()); 587 | if let ExprState::Valid(ves) = &mut app.expr_state { 588 | if !hash_se_s.is_empty() { 589 | ves.candidate_choice_index = Some(0); 590 | } 591 | ves.next_application_candidates = hash_se_s; 592 | } 593 | } 594 | Event::ViewerSes(se_s) => { 595 | app.log_hc_response("got viewer SEs".into()); 596 | app.view_state = ViewState::Viewer(se_s); 597 | } 598 | _ => {} 599 | } 600 | } 601 | Ok(()) 602 | } 603 | 604 | //////////////////////////////////////////////////////////////////////////////// 605 | // CLI arg parsing 606 | //////////////////////////////////////////////////////////////////////////////// 607 | 608 | #[derive(StructOpt, Debug)] 609 | #[structopt(about = HELP)] 610 | struct Cli { 611 | /// Holochain app port 612 | #[structopt(long, short = "p", default_value = "9999")] 613 | hc_app_port: u64, 614 | 615 | /// Holochain admin port 616 | #[structopt(long, short = "f", default_value = "9000")] 617 | hc_admin_port: u64, 618 | } 619 | 620 | const HELP: &str = r#" 621 | ############################ 622 | # # 623 | # ██████╗░██╗░░░░░██████╗░ # 624 | # ██╔══██╗██║░░░░░██╔══██╗ # 625 | # ██████╔╝██║░░░░░██████╔╝ # 626 | # ██╔══██╗██║░░░░░██╔═══╝░ # 627 | # ██║░░██║███████╗██║░░░░░ # 628 | # ╚═╝░░╚═╝╚══════╝╚═╝░░░░░ # 629 | # # 630 | ############################ 631 | "#; 632 | -------------------------------------------------------------------------------- /crates/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_type = "proc-macro"] 2 | 3 | use hdk::{ 4 | entry::must_get_valid_element, 5 | prelude::{holo_hash::DnaHash, *}, 6 | }; 7 | 8 | use combine::{stream::position, EasyParser, StreamOnce}; 9 | use pretty::RcDoc; 10 | use std::collections::HashMap; 11 | 12 | use rep_lang_concrete_syntax::{parse::expr, pretty::ppr_expr}; 13 | use rep_lang_core::{ 14 | abstract_syntax::{Expr, Gas, Name, PrimOp}, 15 | app, lam, 16 | }; 17 | use rep_lang_runtime::{ 18 | env::Env, 19 | eval::{ 20 | eval_, flat_thunk_to_sto_ref, inject_flatvalue_to_flatthunk, lookup_sto, new_term_env, 21 | value_to_flat_value, EvalState, FlatValue, Normalizable, Sto, 22 | }, 23 | infer::{self, infer_expr_with_is, normalize, unifies, InferState}, 24 | types::Scheme, 25 | }; 26 | use social_sensemaker_core::{OWNER_TAG, SM_DATA_TAG, SM_INIT_TAG}; 27 | use social_sensemaker_macros::expand_remote_calls; 28 | 29 | pub mod util; 30 | 31 | // TODO think carefully on what this should be. 32 | pub type Marker = (); 33 | 34 | #[derive(Debug, Clone, Serialize, Deserialize)] 35 | pub enum SensemakerOperand { 36 | // these dereference to `SensemakerEntry` 37 | SensemakerOperand(HeaderHash), 38 | // these dereference to `FlatThunk`?? 39 | OtherOperand(HeaderHash), 40 | } 41 | 42 | impl SensemakerOperand { 43 | pub fn ppr(&self) -> RcDoc<()> { 44 | match &self { 45 | SensemakerOperand::SensemakerOperand(hh) => { 46 | RcDoc::text(format!("SensemakerOperand({})", hh)) 47 | } 48 | SensemakerOperand::OtherOperand(hh) => RcDoc::text(format!("OtherOperand({})", hh)), 49 | } 50 | } 51 | } 52 | 53 | #[hdk_entry(id = "sensemaker_entry")] 54 | #[derive(Clone)] 55 | pub struct SensemakerEntry { 56 | pub operator: Expr, 57 | pub operands: Vec, 58 | pub output_scheme: Scheme, 59 | pub output_flat_value: FlatValue, 60 | pub start_gas: Gas, 61 | } 62 | 63 | impl SensemakerEntry { 64 | pub fn ppr(&self) -> RcDoc<()> { 65 | let docs = vec![ 66 | RcDoc::line(), 67 | RcDoc::concat(vec![ 68 | RcDoc::text("operator:"), 69 | RcDoc::line(), 70 | ppr_expr(&self.operator).nest(1), 71 | RcDoc::text(","), 72 | ]) 73 | .nest(1) 74 | .group(), 75 | RcDoc::line(), 76 | RcDoc::concat(vec![ 77 | RcDoc::text("operands: ["), 78 | RcDoc::concat( 79 | self.operands 80 | .iter() 81 | .map(|operand| RcDoc::line().append(operand.ppr().append(RcDoc::text(",")))) 82 | .collect::>(), 83 | ) 84 | .nest(1) 85 | .group(), 86 | RcDoc::line(), 87 | RcDoc::text("],"), 88 | ]) 89 | .nest(1) 90 | .group(), 91 | RcDoc::line(), 92 | RcDoc::concat(vec![ 93 | RcDoc::text("output_scheme:"), 94 | RcDoc::line(), 95 | self.output_scheme.ppr().nest(1), 96 | RcDoc::text(","), 97 | ]) 98 | .nest(1) 99 | .group(), 100 | RcDoc::line(), 101 | RcDoc::concat(vec![ 102 | RcDoc::text("output_flat_value:"), 103 | RcDoc::line(), 104 | self.output_flat_value.ppr().nest(1), 105 | RcDoc::text(","), 106 | ]) 107 | .nest(1) 108 | .group(), 109 | RcDoc::line(), 110 | RcDoc::concat(vec![ 111 | RcDoc::text("start_gas:"), 112 | RcDoc::line(), 113 | RcDoc::text(format!("{}", self.start_gas)).nest(1), 114 | RcDoc::text(","), 115 | ]) 116 | .nest(1) 117 | .group(), 118 | ]; 119 | RcDoc::concat(vec![ 120 | RcDoc::text("SensemakerEntry {"), 121 | RcDoc::concat(docs).nest(2), 122 | RcDoc::line(), 123 | RcDoc::text("}"), 124 | ]) 125 | } 126 | } 127 | 128 | /// input to `create_sensemaker_entry_full` 129 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 130 | pub struct CreateSensemakerEntryInput { 131 | pub expr: Expr, 132 | pub args: Vec, 133 | } 134 | 135 | #[hdk_entry] 136 | pub struct SchemeRoot; 137 | 138 | #[hdk_entry] 139 | pub struct SchemeEntry { 140 | pub sc: Scheme, 141 | } 142 | 143 | // functions 144 | 145 | #[hdk_extern] 146 | pub fn get_sensemaker_entries_which_unify( 147 | opt_target_sc: Option, 148 | ) -> ExternResult> { 149 | get_linked_sensemaker_entries_which_unify((hash_entry(SchemeRoot)?, opt_target_sc)) 150 | } 151 | 152 | // this doesn't really make sense, because the only structure which is guaranteed to have 153 | // the proper Scheme linking layout is the `SchemeRoot`. 154 | #[hdk_extern] 155 | pub fn get_linked_sensemaker_entries_which_unify( 156 | (target_hash, opt_target_sc): (EntryHash, Option), 157 | ) -> ExternResult> { 158 | let scheme_entry_links = get_links(target_hash, None)?; 159 | let scheme_entry_hashes: Vec = scheme_entry_links 160 | .into_iter() 161 | .map(|lnk| { 162 | lnk.target 163 | .into_entry_hash() 164 | .expect("Link target should be Entry.") 165 | }) 166 | .collect(); 167 | let filtered_scheme_entry_hashes: Vec = match opt_target_sc { 168 | // if no target Scheme, we do not filter 169 | None => scheme_entry_hashes, 170 | // if yes target Scheme, we filter based on unification 171 | Some(target_sc) => { 172 | let mut is = InferState::new(); 173 | let Scheme(_, normalized_target_ty) = normalize(&mut is, target_sc); 174 | scheme_entry_hashes 175 | .into_iter() 176 | .filter(|s_eh| { 177 | let mut flag = || -> ExternResult { 178 | // retrieve `SchemeEntry` element, decode to entry 179 | let element = (match get(s_eh.clone(), GetOptions::content())? { 180 | Some(el) => Ok(el), 181 | None => Err(WasmError::Guest(format!( 182 | "could not dereference hash: {}", 183 | s_eh.clone() 184 | ))), 185 | })?; 186 | let scheme_entry: SchemeEntry = 187 | match element.into_inner().1.to_app_option()? { 188 | Some(se) => Ok(se), 189 | None => { 190 | Err(WasmError::Guest(format!("non-present arg: {}", *s_eh))) 191 | } 192 | }?; 193 | // check unification of normalized type 194 | let Scheme(_, normalized_candidate_ty) = 195 | normalize(&mut is, scheme_entry.sc); 196 | // we are only interested in whether a type error occured 197 | Ok(unifies(normalized_target_ty.clone(), normalized_candidate_ty).is_ok()) 198 | }; 199 | // any `ExternResult` `Err`s are treated as disqualifiers for filtration 200 | // purposes. 201 | flag().unwrap_or(false) 202 | }) 203 | .collect() 204 | } 205 | }; 206 | filtered_scheme_entry_hashes 207 | .into_iter() 208 | .flat_map(|s_eh| get_links(s_eh, None)) 209 | .flatten() 210 | .map(|lnk| { 211 | get_sensemaker_entry( 212 | lnk.target 213 | .into_entry_hash() 214 | .expect("Link target should be Entry."), 215 | ) 216 | }) 217 | .collect() 218 | } 219 | 220 | /// this function creates an `SensemakerEntry`, whose `Scheme` is essentially 221 | /// `forall a. List a`. 222 | /// 223 | /// all SEs should have compatible `Scheme`s. this function will not check that, 224 | /// but if `create_entry` is used later & type inference fails, the SE won't be 225 | /// created. 226 | pub fn pack_ses_into_list_se(ses: Vec) -> ExternResult { 227 | let mut es = EvalState::new(); 228 | 229 | let fresh_names: Vec = ses.iter().map(|_| es.fresh_name()).collect(); 230 | 231 | // construct the list by `Cons`ing each element onto the accumulator 232 | let add_cons = 233 | |acc, nm: &Name| app!(app!(Expr::Prim(PrimOp::Cons), Expr::Var(nm.clone())), acc); 234 | let app_body = fresh_names.iter().fold(Expr::Prim(PrimOp::Nil), add_cons); 235 | 236 | // create the outer lambda, successively wrapping a lambda which 237 | // binds each fresh name. 238 | let wrap_lambda = |acc, nm| lam!(nm, acc); 239 | let full_lam = fresh_names.into_iter().rev().fold(app_body, wrap_lambda); 240 | 241 | let operands = ses 242 | .into_iter() 243 | .map(SensemakerOperand::SensemakerOperand) 244 | .collect(); 245 | mk_sensemaker_entry(full_lam, operands) 246 | } 247 | 248 | /// assumes that the first `HeaderHash` is the operator, and that successive 249 | /// `HeaderHash`es are operands. applies them in that order. does not check 250 | /// whether types match up. 251 | pub fn mk_application_se(hh_s: Vec) -> ExternResult { 252 | // there must be at least an operator 253 | if hh_s.len() <= 1 { 254 | return Err(WasmError::Guest("no operator provided".into())); 255 | } 256 | 257 | let mut es = EvalState::new(); 258 | 259 | let fresh_names: Vec = hh_s.iter().map(|_| es.fresh_name()).collect(); 260 | 261 | let apply_vars = |acc, nm: &Name| app!(acc, Expr::Var(nm.clone())); 262 | // we pull out the operator, so it may be applied to the others 263 | let init_acc = Expr::Var(fresh_names[0].clone()); 264 | // we skip the operator, since it's the init_acc 265 | let app_body = fresh_names.iter().skip(1).fold(init_acc, apply_vars); 266 | 267 | // create the outer lambda, successively wrapping a lambda which 268 | // binds each fresh name. 269 | let wrap_lambda = |acc, nm| lam!(nm, acc); 270 | let full_lam = fresh_names.into_iter().rev().fold(app_body, wrap_lambda); 271 | 272 | let operands = hh_s 273 | .into_iter() 274 | .map(SensemakerOperand::SensemakerOperand) 275 | .collect(); 276 | mk_sensemaker_entry(full_lam, operands) 277 | } 278 | 279 | #[hdk_extern] 280 | pub fn get_sensemaker_entry(arg_hash: EntryHash) -> ExternResult<(HeaderHash, SensemakerEntry)> { 281 | let element = (match get(arg_hash.clone(), GetOptions::content())? { 282 | Some(el) => Ok(el), 283 | None => Err(WasmError::Guest(format!( 284 | "could not dereference arg: {}", 285 | arg_hash 286 | ))), 287 | })?; 288 | let hh = element.header_address().clone(); 289 | match element.into_inner().1.to_app_option()? { 290 | Some(se) => Ok((hh, se)), 291 | None => Err(WasmError::Guest(format!("non-present arg: {}", arg_hash))), 292 | } 293 | } 294 | 295 | pub fn mk_sensemaker_entry_parse(expr_str: String) -> ExternResult { 296 | match expr().easy_parse(position::Stream::new(&expr_str[..])) { 297 | Err(err) => Err(WasmError::Guest(format!("parse error:\n\n{}\n", err))), 298 | Ok((expr, extra_input)) => { 299 | if extra_input.is_partial() { 300 | Err(WasmError::Guest(format!( 301 | "error: unconsumed input: {:?}", 302 | extra_input 303 | ))) 304 | } else { 305 | mk_sensemaker_entry(expr, vec![]) 306 | } 307 | } 308 | } 309 | } 310 | 311 | pub fn mk_sensemaker_entry( 312 | expr: Expr, 313 | args: Vec, 314 | ) -> ExternResult { 315 | let arg_hh_s: Vec = args 316 | .iter() 317 | .map(|io| match io { 318 | SensemakerOperand::SensemakerOperand(hh) => hh.clone(), 319 | SensemakerOperand::OtherOperand(_) => todo!("OtherOperand"), 320 | }) 321 | .collect(); 322 | 323 | // don't need result, just a preliminary check before hitting DHT 324 | let _expr_sc = 325 | infer_expr_with_is(&Env::new(), &mut InferState::new(), &expr).map_err(|type_error| { 326 | WasmError::Guest(format!("type error in `expr`: {:?}", type_error)) 327 | })?; 328 | 329 | // dereference `arg_hh_s` 330 | let int_entrs: Vec = arg_hh_s 331 | .iter() 332 | .cloned() 333 | .map(|arg_hash| { 334 | let element = must_get_valid_element(arg_hash.clone())?; 335 | match element.into_inner().1.to_app_option()? { 336 | Some(se) => Ok(se), 337 | None => Err(WasmError::Guest(format!("non-present arg: {}", arg_hash))), 338 | } 339 | }) 340 | .collect::>()?; 341 | 342 | let mut is = InferState::new(); 343 | let mut es = EvalState::new(); 344 | let mut type_env = Env::new(); 345 | // we normalize up here, before conjuring fresh names for the `args`, in order to avoid 346 | // potential contamination. I'm not sure it is necessary, but doing it to be safe. 347 | let normalized_expr = expr.normalize(&mut HashMap::new(), &mut es); 348 | let arg_named_scheme_values: Vec<(Name, Scheme, FlatValue)> = int_entrs 349 | .iter() 350 | .map(|se| { 351 | ( 352 | es.fresh_name(), 353 | infer::normalize(&mut is, se.output_scheme.clone()), 354 | se.output_flat_value.normalize(&mut HashMap::new(), &mut es), 355 | ) 356 | }) 357 | .collect(); 358 | type_env.extends( 359 | arg_named_scheme_values 360 | .iter() 361 | .map(|t| (t.0.clone(), t.1.clone())), 362 | ); 363 | 364 | let applicator = |bd, nm: Name| app!(bd, Expr::Var(nm)); 365 | let full_application: Expr = arg_named_scheme_values 366 | .iter() 367 | .map(|t| t.0.clone()) 368 | .fold(normalized_expr, applicator); 369 | 370 | // TODO substantiate whether this Scheme will have high-indexed `Tv`s, which might be 371 | // unintuitive / cause issues with programmatic `Scheme` matching. 372 | let full_application_sc = 373 | infer_expr_with_is(&type_env, &mut is, &full_application).map_err(|type_error| { 374 | WasmError::Guest(format!("type error in full application: {:?}", type_error)) 375 | })?; 376 | 377 | let mut term_env = new_term_env(); 378 | let mut sto: Sto = Sto::new(); 379 | 380 | for (nm, flat_val) in arg_named_scheme_values 381 | .iter() 382 | .map(|t| (t.0.clone(), t.2.clone())) 383 | { 384 | let v_ref = 385 | flat_thunk_to_sto_ref(&mut es, &mut sto, inject_flatvalue_to_flatthunk(flat_val)); 386 | term_env.insert(nm, v_ref); 387 | } 388 | 389 | let full_application_vr = eval_(&term_env, &mut sto, &mut es, &full_application); 390 | let full_application_val = lookup_sto(&mut es, &full_application_vr, &mut sto); 391 | let full_application_flat_val = value_to_flat_value(&mut es, &full_application_val, &mut sto); 392 | 393 | let new_se: SensemakerEntry = SensemakerEntry { 394 | operator: expr, 395 | operands: arg_hh_s 396 | .into_iter() 397 | .map(SensemakerOperand::SensemakerOperand) 398 | .collect(), 399 | output_scheme: full_application_sc, 400 | output_flat_value: full_application_flat_val, 401 | start_gas: es.current_gas_count(), 402 | }; 403 | Ok(new_se) 404 | } 405 | 406 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 407 | pub struct CreateSensemakerEntryInputParse { 408 | pub expr: String, 409 | pub args: Vec, 410 | } 411 | 412 | /// INFO this is incomplete and doesn't currently parse the `args` 413 | #[hdk_extern] 414 | pub fn create_sensemaker_entry_parse( 415 | input: CreateSensemakerEntryInputParse, 416 | ) -> ExternResult<(HeaderHash, SensemakerEntry)> { 417 | let (hh, _eh, se) = match expr().easy_parse(position::Stream::new(&input.expr[..])) { 418 | Err(err) => Err(WasmError::Guest(format!("parse error:\n\n{}\n", err))), 419 | Ok((expr, extra_input)) => { 420 | if extra_input.is_partial() { 421 | Err(WasmError::Guest(format!( 422 | "error: unconsumed input: {:?}", 423 | extra_input 424 | ))) 425 | } else { 426 | create_sensemaker_entry_full(CreateSensemakerEntryInput { 427 | expr, 428 | // TODO parse `input.args`. 429 | // parsing a HeaderHash seems like it should be possible, but I've not done 430 | // it before. we might also want some indicator of which of 431 | // SensemakerOperand constructor is desired? 432 | args: Vec::new(), 433 | }) 434 | } 435 | } 436 | }?; 437 | Ok((hh, se)) 438 | } 439 | 440 | pub fn create_sensemaker_entry_full( 441 | input: CreateSensemakerEntryInput, 442 | ) -> ExternResult<(HeaderHash, EntryHash, SensemakerEntry)> { 443 | let se = mk_sensemaker_entry(input.expr, input.args)?; 444 | 445 | // create SchemeRoot (if needed) 446 | match get(hash_entry(&SchemeRoot)?, GetOptions::content())? { 447 | None => { 448 | let _hh = create_entry(&SchemeRoot)?; 449 | } 450 | Some(_) => {} 451 | }; 452 | 453 | // create Scheme entry & link from SchemeRoot (if needed) 454 | let scheme_entry = SchemeEntry { 455 | sc: se.output_scheme.clone(), 456 | }; 457 | let scheme_entry_hash = hash_entry(&scheme_entry)?; 458 | match get(scheme_entry_hash.clone(), GetOptions::content())? { 459 | None => { 460 | let _hh = create_entry(&scheme_entry)?; 461 | create_link( 462 | hash_entry(SchemeRoot)?, 463 | scheme_entry_hash.clone(), 464 | LinkType::new(0), 465 | LinkTag::new(OWNER_TAG), 466 | )?; 467 | } 468 | Some(_) => {} 469 | }; 470 | 471 | // create SE & link from Scheme entry (if needed) 472 | let se_eh = hash_entry(&se)?; 473 | match get(se_eh.clone(), GetOptions::content())? { 474 | None => { 475 | let hh = create_entry(&se)?; 476 | create_link( 477 | scheme_entry_hash, 478 | se_eh.clone(), 479 | LinkType::new(0), 480 | LinkTag::new(OWNER_TAG), 481 | )?; 482 | Ok((hh, se_eh, se)) 483 | } 484 | Some(element) => Ok((element.header_address().clone(), se_eh, se)), 485 | } 486 | } 487 | 488 | #[hdk_extern] 489 | pub fn get_sensemaker_entry_by_headerhash( 490 | arg_hash: HeaderHash, 491 | ) -> ExternResult<(EntryHash, SensemakerEntry)> { 492 | let element = (match get(arg_hash.clone(), GetOptions::content())? { 493 | Some(el) => Ok(el), 494 | None => Err(WasmError::Guest(format!( 495 | "could not dereference arg: {}", 496 | arg_hash 497 | ))), 498 | })?; 499 | match element.into_inner().1.to_app_option()? { 500 | Some(se) => { 501 | let se_hash = hash_entry(&se)?; 502 | Ok((se_hash, se)) 503 | } 504 | None => Err(WasmError::Guest(format!("non-present arg: {}", arg_hash))), 505 | } 506 | } 507 | 508 | pub fn get_latest_path_entry( 509 | path_string: String, 510 | link_tag_string: String, 511 | ) -> ExternResult> { 512 | let path = Path::from(path_string); 513 | get_latest_linked_entry(path.path_entry_hash()?, link_tag_string) 514 | } 515 | 516 | pub fn get_latest_linked_entry( 517 | target: EntryHash, 518 | link_tag_string: String, 519 | ) -> ExternResult> { 520 | let links = get_links(target, Some(LinkTag::new(link_tag_string)))?; 521 | match links 522 | .into_iter() 523 | .max_by(|x, y| x.timestamp.cmp(&y.timestamp)) 524 | { 525 | None => Ok(None), 526 | Some(link) => Ok(Some( 527 | link.target.into_entry_hash().expect("Should be an entry."), 528 | )), 529 | } 530 | } 531 | 532 | //////////////////////////////////////////////////////////////////////////////// 533 | // "remote" code, to be imported-by / called-in widgets 534 | //////////////////////////////////////////////////////////////////////////////// 535 | 536 | #[hdk_entry] 537 | #[derive(Clone)] 538 | pub struct SensemakerCellId { 539 | // must include extension 540 | pub dna_hash: DnaHash, 541 | // encoded file bytes payload 542 | pub agent_pubkey: AgentPubKey, 543 | } 544 | 545 | impl SensemakerCellId { 546 | pub fn to_cell_id(self) -> CellId { 547 | CellId::new(self.dna_hash, self.agent_pubkey) 548 | } 549 | } 550 | 551 | pub fn sensemaker_cell_id_anchor() -> ExternResult { 552 | anchor("sensemaker_cell_id".into(), "".into()) 553 | } 554 | 555 | #[macro_export] 556 | macro_rules! sensemaker_cell_id_fns { 557 | () => { 558 | #[hdk_extern] 559 | fn set_sensemaker_cell_id( 560 | (dna_hash, agent_pubkey): (DnaHash, AgentPubKey), 561 | ) -> ExternResult { 562 | let sensemaker_cell_id: SensemakerCellId = SensemakerCellId { 563 | dna_hash, 564 | agent_pubkey, 565 | }; 566 | let sensemaker_cell_id_hh = create_entry(sensemaker_cell_id.clone())?; 567 | let sensemaker_cell_id_eh = hash_entry(sensemaker_cell_id)?; 568 | create_link( 569 | sensemaker_cell_id_anchor()?, 570 | sensemaker_cell_id_eh, 571 | LinkType(0), 572 | LinkTag::new(OWNER_TAG), 573 | )?; 574 | 575 | Ok(sensemaker_cell_id_hh) 576 | } 577 | 578 | #[hdk_extern] 579 | fn get_sensemaker_cell_id(_: ()) -> ExternResult { 580 | match get_latest_linked_entry(sensemaker_cell_id_anchor()?, OWNER_TAG.into())? { 581 | Some(entryhash) => { 582 | let sensemaker_cell_id_entry: SensemakerCellId = 583 | util::try_get_and_convert(entryhash.clone(), GetOptions::content())?; 584 | Ok(sensemaker_cell_id_entry.to_cell_id()) 585 | } 586 | None => Err(WasmError::Guest( 587 | "get_sensemaker_cell_id: no cell_id".into(), 588 | )), 589 | } 590 | } 591 | }; 592 | } 593 | 594 | #[expand_remote_calls] 595 | pub fn get_sensemaker_entry_by_path_with_hh( 596 | (path_string, link_tag_string): (String, String), 597 | ) -> ExternResult> { 598 | match get_latest_path_entry(path_string, link_tag_string)? { 599 | Some(entryhash) => { 600 | let (sensemaker_entry, se_hh) = 601 | util::try_get_and_convert_with_hh(entryhash.clone(), GetOptions::content())?; 602 | Ok(Some((entryhash, se_hh, sensemaker_entry))) 603 | } 604 | None => Ok(None), 605 | } 606 | } 607 | 608 | #[expand_remote_calls] 609 | pub fn get_sensemaker_entry_by_path( 610 | (path_string, link_tag_string): (String, String), 611 | ) -> ExternResult> { 612 | match get_latest_path_entry(path_string, link_tag_string)? { 613 | Some(entryhash) => { 614 | let sensemaker_entry = 615 | util::try_get_and_convert(entryhash.clone(), GetOptions::content())?; 616 | Ok(Some((entryhash, sensemaker_entry))) 617 | } 618 | None => Ok(None), 619 | } 620 | } 621 | 622 | #[expand_remote_calls] 623 | pub fn set_sensemaker_entry( 624 | (path_string, link_tag_string, target_eh): (String, String, EntryHash), 625 | ) -> ExternResult<()> { 626 | let path = Path::try_from(path_string)?; 627 | path.ensure()?; 628 | let anchor_hash = path.path_entry_hash()?; 629 | create_link( 630 | anchor_hash, 631 | target_eh, 632 | LinkType(0), 633 | LinkTag::new(link_tag_string), 634 | )?; 635 | Ok(()) 636 | } 637 | 638 | #[expand_remote_calls] 639 | pub fn set_sensemaker_entry_parse_rl_expr( 640 | (path_string, link_tag_string, expr_str): (String, String, String), 641 | ) -> ExternResult<()> { 642 | let (_, sensemaker_entry) = create_sensemaker_entry_parse(CreateSensemakerEntryInputParse { 643 | expr: expr_str, 644 | args: vec![], 645 | })?; 646 | let sensemaker_entryhash = hash_entry(sensemaker_entry)?; 647 | 648 | set_sensemaker_entry((path_string, link_tag_string, sensemaker_entryhash)) 649 | } 650 | 651 | #[expand_remote_calls] 652 | pub fn initialize_sm_data_path((path_prefix, path_suffix): (String, String)) -> ExternResult<()> { 653 | let target_path_string = compose_paths(&path_prefix, &path_suffix); 654 | match get_latest_path_entry(path_prefix, SM_INIT_TAG.into())? { 655 | None => Err(WasmError::Guest("initialize_sm_data: no sm_init".into())), 656 | Some(init_eh) => set_sensemaker_entry((target_path_string, SM_DATA_TAG.into(), init_eh)), 657 | } 658 | } 659 | 660 | #[expand_remote_calls] 661 | pub fn initialize_sm_data((path_prefix, target_eh): (String, EntryHash)) -> ExternResult<()> { 662 | let target_eh_bytes: Vec = target_eh.into_inner(); 663 | let path_suffix = vec_u8_b64_encode(&target_eh_bytes); 664 | initialize_sm_data_path((path_prefix, path_suffix)) 665 | } 666 | 667 | #[expand_remote_calls] 668 | pub fn step_sm_path((path_prefix, path_suffix, act): (String, String, String)) -> ExternResult<()> { 669 | let sm_data_path = compose_paths(&path_prefix, &path_suffix); 670 | 671 | // fetch sm_data 672 | let (sm_data_eh, _sm_data_entry) = 673 | match get_sensemaker_entry_by_path((sm_data_path.clone(), "sm_data".into()))? { 674 | Some(pair) => Ok(pair), 675 | None => Err(WasmError::Guest("sm_data: invalid".into())), 676 | }?; 677 | 678 | // fetch sm_comp 679 | let (sm_comp_eh, _sm_comp_entry) = 680 | match get_sensemaker_entry_by_path((path_prefix, "sm_comp".into()))? { 681 | Some(pair) => Ok(pair), 682 | None => Err(WasmError::Guest("sm_comp: invalid".into())), 683 | }?; 684 | 685 | let sm_comp_hh = util::get_hh(sm_comp_eh, GetOptions::content())?; 686 | let sm_data_hh = util::get_hh(sm_data_eh, GetOptions::content())?; 687 | 688 | // create action SensemakerEntry 689 | let (act_se_hh, _act_se) = create_sensemaker_entry_parse(CreateSensemakerEntryInputParse { 690 | expr: act, 691 | args: vec![], 692 | })?; 693 | 694 | // compose application SensemakerEntry & create it 695 | let application_se = mk_application_se(vec![sm_comp_hh, sm_data_hh, act_se_hh])?; 696 | debug!("{:?}", application_se); 697 | let _application_se_hh = create_entry(&application_se)?; 698 | let application_se_eh = hash_entry(&application_se)?; 699 | debug!("{:?}", application_se_eh); 700 | { 701 | let path = Path::from(sm_data_path); 702 | path.ensure()?; 703 | let path_hash = path.path_entry_hash()?; 704 | let hh = create_link( 705 | path_hash, 706 | application_se_eh, 707 | LinkType(0), 708 | LinkTag::new("sm_data"), 709 | ); 710 | debug!("create_link hh : {:?}", hh); 711 | } 712 | Ok(()) 713 | } 714 | 715 | #[expand_remote_calls] 716 | pub fn step_sm((path_prefix, target_eh, act): (String, EntryHash, String)) -> ExternResult<()> { 717 | let target_eh_bytes: Vec = target_eh.into_inner(); 718 | let path_suffix = vec_u8_b64_encode(&target_eh_bytes); 719 | step_sm_path((path_prefix, path_suffix, act)) 720 | } 721 | 722 | pub fn compose_entry_hash_path(path_string: &String, target_eh: EntryHash) -> String { 723 | let target_eh_bytes: Vec = target_eh.into_inner(); 724 | format!("{}.{}", path_string, vec_u8_b64_encode(&target_eh_bytes)) 725 | } 726 | 727 | pub fn compose_paths(path1: &String, path2: &String) -> String { 728 | format!("{}.{}", path1, path2) 729 | } 730 | 731 | pub fn vec_u8_b64_encode(vec: &Vec) -> String { 732 | base64::encode(vec) 733 | } 734 | --------------------------------------------------------------------------------