# Project export: Pier

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: Pier is a CLI tool focused on elevating the developer experience by automatically generating and publishing human-readable technical documentation. Start your voyage to great documentation with Pier.
- Devpost: https://devpost.com/software/pier-spbfcd
- GitHub: https://github.com/TylerMorton/Pier
- Team: 1 GitHub contributor(s) — Tyler Morton (2 commits)

## Devpost submission (written by the team)

### Inspiration

Tired of writing tedious technical documentation, I sought a solution that could generate human-readable, practical documentation. Pier allows me to save time focusing on writing code knowing I can work with a readable documentation without any writing of my own.

### What it does

Pier generates readable documentation.

### How we built it

Build using Rust to iterate fast over file directories and for file text processing. Using AI for creating readable documentation, and MkDocs for quick documentation site deployment.

### Challenges we ran into

Working with optimizing for performance was challenging.

### Accomplishments we're proud of

Versatility with regards to input and output locations for project codebases and doc generation.

### What we learned

First time working with the OpenAI API and MkDocs. Also how to reduce overhead for performance.

### What's next

AI: Fine-tuning, local models Performance: Reducing overhead, faster file processing

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 5 recognized source files, 10 KB.
- C (language) — detected in the code
- OpenAI (technology) — detected in the code
- Rust (language) — detected in the code

## Codebase structure (from repository index)

### Files (12 of 12)

```
.gitignore
c_example/complex_maths.c
c_example/complex_maths.h
Cargo.lock
Cargo.toml
setup.sh
src/.obsidian/app.json
src/.obsidian/appearance.json
src/.obsidian/core-plugins.json
src/.obsidian/workspace.json
src/doc_gen.rs
src/main.rs
```

### Dependencies

- Cargo.toml: clap@4.5.20, dotenvy@0.15.7, openai@1.0.0-alpha.16, regex@1.11.0, reqwest@0.12.8, serde@1.0.210, serde_json@1.0.132, tokio@1.40.0

### Recent commits (newest first)

- Final commits
- close to final commit
- initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### Cargo.toml

```
[package]
name = "pier"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4.5.20", features = ["derive"] }
dotenvy = "0.15.7"
openai = "1.0.0-alpha.16"
regex = "1.11.0"
reqwest = "0.12.8"
serde = "1.0.210"
serde_json = "1.0.132"
tokio = "1.40.0"

```

### src/main.rs

```rust
mod doc_gen;
use doc_gen::{doc_cleanup, doc_file_parse, welcome_doc};

use dotenvy::dotenv;
use openai::{
    chat::{ChatCompletionMessage, ChatCompletionMessageRole},
    set_base_url, set_key,
};
use std::fs::{read_dir, remove_file, File};
use std::path::Path;
use std::process;
use std::{
    env,
    io::{Read, Write},
};

use clap::{Arg, Command, Parser};

// Hardcoded ignore list

#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    #[arg(short, long)]
    dir_path: String, //#[arg(short, long)]
                      //file_path: String
}

fn chat_history_setup() -> Vec<ChatCompletionMessage> {
    vec![ChatCompletionMessage {
        role: ChatCompletionMessageRole::System,
        content: Some("Determine if the file is a library file, if not then just respond exactly with \"SKIP\". Otherwise. Given the library file create documentation for each function in markdown format. Use the EXACT format of: function identifier with parameter types should be in h2, description then the actualy description, and additional info. Make sure there are no duplicate functions. ALWAYS end with two newlines".to_string()),
        name: None,
        function_call: None,
    },
    ]
}

fn chat_history_setup_fn_list() -> Vec<ChatCompletionMessage> {
    vec![ChatCompletionMessage {
        role: ChatCompletionMessageRole::System,
        content: Some(
            "List the function names from the library file. STRICTLY list name then newline."
                .to_string(),
        ),
        name: None,
        function_call: None,
    }]
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // todo timeout or the thing might never end
    //

    //let args = Args::parse();
    let ignore_list = vec![
        "docs.".to_string()
        "benches".to_string(),
        ".git".to_string(),
        ".gitignore".to_string(),
        ".github".to_string(),
        "target".to_string(),
        "fuzz".to_string(),
        "fuzzer".to_string(),
    ];

    dotenv().unwrap();
    set_key(env::var("OPENAI_API_KEY").unwrap());
    set_base_url("https://api.openai.com/v1".to_string());

    let matches = Command::new("Knock")
        .version("1.0")
        .subcommand(
            Command::new("generate").about("Generates documention").arg(
                Arg::new("dir-path")
                    .long("dir-path")
                    .required(true)
                    .help("Directory path to project"),
            ),
        )
        .subcommand(
            Command::new("serve")
                .about("Serves files from the specified directory")
                .arg(
                    Arg::new("folder")
                        .required(true)
                        .help("Serve documentation live"),
                ),
        )
        .get_matches();

    match matches.subcommand() {
        Some(("generate", sub_m)) => {
            let dir_path: String = sub_m
                .get_one::<String>("dir-path")
                .expect("dir-path required")
                .to_owned();

            let _output = process::Command::new("sh")
                .arg("-c")
                .arg("pip install mkdocs")
                .output()
                .expect("failed to execute process");

            let _output = process::Command::new("sh")
                .arg("-c")
                .arg("mkdocs new .")
                .output()
                .expect("failed to execute process");

            //   let dir_path = args.dir_path;
            let mut dir_list = vec![dir_path];

            //let mut messages: Vec<ChatCompletionMessage> = Vec::new();
            let mut messages: Vec<ChatCompletionMessage> = chat_history_setup();
            //let fn_list_msgs: Vec<ChatCompletionMessage> = chat_history_setup_fn_list();

            println!("Files processed:");
            while let Some(dir) = dir_list.pop() {
                for entry in read_dir(dir).unwrap() {
                    if let Ok(entry) = entry {
                        let f_type = entry.file_type().unwrap();
                        if f_type.is_dir() {
                            if !ignore_list
                                .contains(&entry.file_name().to_str().unwrap().to_string())
                            {
                                dir_list.push(entry.path().to_str().unwrap().to_string());
                            }
                        }
                        if f_type.is_file() {
                            println!("{}", entry.file_name().to_str().unwrap().to_string());
                            messages = doc_file_parse(messages, entry).await?;
                        }
                    }
                }
            }
            let mut file = File::open("docs/docs.md")?;
            let mut contents = String::new();
            file.read_to_string(&mut contents)?;
            let _ = doc_cleanup(contents).await;
            let _ = remove_file("docs/docs.md").unwrap();
            println!("\n\nDocumentation generated!");
            let _ = remove_file("docs/index.md");
            let mut file = File::create("docs/index.md")?;
            let _ = file.write(welcome_doc().as_bytes());

            Ok(())
        }
        Some(("serve", sub_m)) => {
            let folder = sub_m
                .get_one::<String>("folder")
                .expect("Expected folder path");
            let current_dir = env::current_dir().expect("Failed to get cwd");
            println!("Serving on http://127.0.0.1:8000/");

            let mkdocs_output = process::Command::new("mkdocs")
                .arg("serve")
                .current_dir(Path::new(folder))
                //.current_dir(&current_dir)
                .output()
                .expect("failed to execute process");

            Ok(())
        }
        _ => {
            println!("No valid subcommand provided. pier [generate | serve]");
            Ok(())
        }
    }
}

```

### setup.sh

```shell
#!/bin/bash


git clone https://github.com/svartalf/rust-macaddr.git
mv rust_macaddr rust_examples/rust-macaddr

```

### c_example/complex_maths.c

```c
#include "complex_maths.h"

complex CMPLX(double real, double imag) {
	return complex { real, imag }
}

double creal(complex c) {
	return c.real
}

double cimag(complex c) {
	return c.imag
}


```

### c_example/complex_maths.h

```c
#ifndef __COMPLEX_MATHS__
#define __COMPLEX_MATHS__

typedef struct {
	.real = double,
	.imag = double 
} complex;

complex CMPLX(double real, double imag);

double creal(complex c);

double cimag(complex c);

#endif


```

### src/doc_gen.rs

```rust
use dotenvy::dotenv;
use std::fs::{read_dir, DirEntry, File};
use std::process::Command;
use std::{
    env,
    io::{Read, Write},
};

use openai::{
    chat::{ChatCompletion, ChatCompletionMessage, ChatCompletionMessageRole},
    set_base_url, set_key,
};

pub fn welcome_doc() -> String {
    String::from(
        "
# Welcome to your Project's Documentation
Generated from Pier!

Check out the project [here](https://github.com/TylerMorton/Pier).
        ",
    )
}

pub async fn doc_cleanup(doc_contents: String) -> Result<(), Box<dyn std::error::Error>> {
    let cleanup_sys_prompt = vec![ChatCompletionMessage {
        role: ChatCompletionMessageRole::System,
        content: Some(
            "Format the document to be a formal library document in markdown. Make sure there is consistency throughout the whole document. Have a short title about the library and it's functionality. Have the functions that originally had header 2 as code blocks instead of as headers with their parameters."
                .to_string(),
        ),
        name: None,
        function_call: None,
    },
    ChatCompletionMessage {
        role: ChatCompletionMessageRole::User,
        content: Some(doc_contents),
        name: None,
        function_call: None,
        },
    ];

    let chat_completion = ChatCompletion::builder("gpt-3.5-turbo", cleanup_sys_prompt.clone())
        .create()
        .await
        .unwrap();

    let returned_message = chat_completion.choices.first().unwrap().message.clone();

    let mut docs = File::create("docs/library.md")?;

    let _ = docs.write(returned_message.content.clone().unwrap().trim().as_bytes());
    Ok(())
}

pub async fn doc_file_parse(
    mut messages: Vec<ChatCompletionMessage>,
    //mut fn_list_msgs: Vec<ChatCompletionMessage>,
    //mut function_list: Vec<String>,
    entry: DirEntry,
) -> Result<Vec<ChatCompletionMessage>, Box<dyn std::error::Error>> {
    //messages.append(&mut chat_history_setup());
    let mut file = File::open(entry.path().to_str().unwrap())?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    // Make sure you have a file named `.env` with the `OPENAI_KEY` environment variable defined!

    messages.push(ChatCompletionMessage {
        role: ChatCompletionMessageRole::User,
        content: Some(contents),
        name: None,
        function_call: None,
    });

    /*
    let fn_list_chat_completion = ChatCompletion::builder("gpt-3.5-turbo", fn_list_msgs.clone())
        .create()
        .await
        .unwrap();
    */
    //let returned_message = chat_completion.choices.first().unwrap().message.clone();

    let chat_completion = ChatCompletion::builder("gpt-3.5-turbo", messages.clone())
        .create()
        .await
        .unwrap();

    let returned_message = chat_completion.choices.first().unwrap().message.clone();

    let mut docs = File::options()
        .append(true)
        .create(true)
        .open("docs/docs.md")?;

    let returned_msg = returned_message.content.clone().unwrap();
    let returned_msg = returned_msg.trim();
    if returned_msg.contains("SKIP") {
        return Ok(messages);
    }
    let _ = docs.write(returned_msg.as_bytes());
    //messages.push(returned_message);
    Ok(messages)
}

```