Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/devportal #36

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rust-version = "1.67.1"
tauri-build = { version = "1.4", features = [] }

[dependencies]
async-std = "1.10.0"
dirs = "5.0.1"
fern = "0.6.2"
futures = "0.3.28"
Expand All @@ -36,6 +37,8 @@ walkdir = "2.3.3"
sysinfo = "0.29.7"
serialport = { version = "4.2.1" }
espflash = "2.0.1"
regex = "1.10.4"
deunicode = "1.6.0"

[features]
# this feature is used for production builds or when `devPath` points to the filesystem
Expand Down
92 changes: 92 additions & 0 deletions src-tauri/src/developer_portal/articles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use tauri::AppHandle;
use std::path::PathBuf;
use regex::Regex;
use deunicode::deunicode;

const ARTICLES_DIR: &str = "content/articles";

#[derive(serde::Serialize, serde::Deserialize)]
pub struct Article {
title: String,
content: String,
date: String,
draft: bool,
file_name: String,
}

#[tauri::command]
pub async fn get_articles(repo_path: String) -> Result<Vec<Article>, String> {
let path = PathBuf::from(repo_path).join(ARTICLES_DIR);
let mut articles = Vec::new();

for entry in std::fs::read_dir(path).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
if path.join("_index.md").exists() {
let content = std::fs::read_to_string(path.join("_index.md")).map_err(|e| e.to_string())?;
let article = parse_article(&content, path.file_name().unwrap().to_str().unwrap())?;
articles.push(article);
}
}
Ok(articles)
}

#[tauri::command]
pub async fn save_article(article: Article, repo_path: String) -> Result<(), String> {
let normalized_file_name = create_file_name(&article.title);
let path = PathBuf::from(repo_path).join(ARTICLES_DIR).join(&normalized_file_name).join("_index.md");
let content = format!(
r#"+++
title = '{}'
date = '{}'
draft = {}
+++
{}
"#,
article.title, article.date, article.draft, article.content
);
std::fs::create_dir_all(path.parent().unwrap()).map_err(|e| e.to_string())?;
std::fs::write(path, content).map_err(|e| e.to_string())?;
Ok(())
}

#[tauri::command]
pub async fn delete_article(file_name: String, repo_path: String) -> Result<(), String> {
let path = PathBuf::from(repo_path).join(ARTICLES_DIR).join(&file_name);
std::fs::remove_dir_all(path).map_err(|e| e.to_string())?;
Ok(())
}

fn parse_article(content: &str, file_name: &str) -> Result<Article, String> {
let re = Regex::new(r"(?s)\+\+\+(?P<meta>.+?)\+\+\+(?P<body>.*)").map_err(|e| e.to_string())?;
let caps = re.captures(content).ok_or("Invalid article format")?;
let meta = caps.name("meta").ok_or("Invalid article format")?.as_str();
let body = caps.name("body").map_or("", |m| m.as_str()).trim().to_string();
let mut article = Article {
title: String::new(),
content: body,
date: String::new(),
draft: false,
file_name: file_name.to_string(),
};

for line in meta.lines() {
if let Some((key, value)) = line.split_once('=') {
let key = key.trim();
let value = value.trim().trim_matches(|c| c == '"' || c == '\'');
match key {
"title" => article.title = value.to_string(),
"date" => article.date = value.to_string(),
"draft" => article.draft = value.parse::<bool>().map_err(|e| e.to_string())?,
_ => {}
}
}
}
Ok(article)
}

fn create_file_name(name: &str) -> String {
let re = Regex::new(r"[^a-zA-Z0-9-]").unwrap();
let normalized_name = deunicode(name).to_lowercase().replace(" ", "-");
re.replace_all(&normalized_name, "").to_string()
}
92 changes: 92 additions & 0 deletions src-tauri/src/developer_portal/authors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use tauri::AppHandle;
use crate::Author;
use std::path::PathBuf;
use regex::Regex;
use deunicode::deunicode;

const AUTHORS_DIR: &str = "data/authors";
const CONTENT_DIR: &str = "content/authors";

fn expand_tilde(path: &str) -> PathBuf {
if path.starts_with("~/") {
if let Some(home_dir) = dirs::home_dir() {
return home_dir.join(path.trim_start_matches("~/"));
}
}
PathBuf::from(path)
}

fn get_default_repo_dir() -> PathBuf {
dirs::home_dir().unwrap().join(".espressif").join("developer-portal")
}

fn create_file_name(name: &str) -> String {
let re = Regex::new(r"[^a-zA-Z0-9-]").unwrap();
let normalized_name = deunicode(name).to_lowercase().replace(" ", "-");
re.replace_all(&normalized_name, "").to_string()
}

fn create_author_page_content(name: &str) -> String {
format!(
"---\ntitle: \"{}\"\n---\n",
name
)
}

#[tauri::command]
pub async fn save_author(author: Author, original_file_name: Option<String>, repo_path: Option<String>) -> Result<(), String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);
let authors_dir = expanded_repo_dir.join(AUTHORS_DIR);
let content_dir = expanded_repo_dir.join(CONTENT_DIR);

let file_name = original_file_name.unwrap_or_else(|| create_file_name(&author.name));
let file_path = authors_dir.join(format!("{}.json", file_name));
let index_path = content_dir.join(&file_name).join("_index.md");

std::fs::write(&file_path, serde_json::to_string_pretty(&author).map_err(|e| e.to_string())?)
.map_err(|e| e.to_string())?;

std::fs::create_dir_all(index_path.parent().unwrap()).map_err(|e| e.to_string())?;
std::fs::write(&index_path, create_author_page_content(&author.name)).map_err(|e| e.to_string())?;

Ok(())
}

#[tauri::command]
pub async fn get_authors(repo_path: Option<String>) -> Result<Vec<Author>, String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);
let authors_path = expanded_repo_dir.join(AUTHORS_DIR);
let mut authors = Vec::new();
for entry in std::fs::read_dir(authors_path).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let path = entry.path();
if path.is_file() {
let author: Author = serde_json::from_reader(std::fs::File::open(path).map_err(|e| e.to_string())?)
.map_err(|e| e.to_string())?;
authors.push(author);
}
}
Ok(authors)
}

#[tauri::command]
pub async fn delete_author(file_name: String, repo_path: Option<String>) -> Result<(), String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);
let authors_dir = expanded_repo_dir.join(AUTHORS_DIR);
let content_dir = expanded_repo_dir.join(CONTENT_DIR);
let file_path = authors_dir.join(&file_name);
let index_path = content_dir.join(file_name.replace(".json", "")).join("_index.md");

std::fs::remove_file(file_path).map_err(|e| e.to_string())?;

std::fs::remove_file(index_path.clone()).map_err(|e| e.to_string())?;
let content_dir = index_path.parent().unwrap();
if std::fs::read_dir(content_dir).map_err(|e| e.to_string())?.next().is_none() {
std::fs::remove_dir(content_dir).map_err(|e| e.to_string())?;
}

Ok(())
}
79 changes: 79 additions & 0 deletions src-tauri/src/developer_portal/hugo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use async_std::sync::{Arc, Mutex};
use tauri::{AppHandle, State};
use crate::external_command::run_external_command_with_progress;
use std::path::PathBuf;

pub struct HugoState {
pub is_running: Arc<Mutex<bool>>,
}

const HUGO_COMMAND: &str = "hugo";
const HUGO_ARGS: &[&str] = &["server", "--source"];

fn expand_tilde(path: &str) -> PathBuf {
if path.starts_with("~/") {
if let Some(home_dir) = dirs::home_dir() {
return home_dir.join(path.trim_start_matches("~/"));
}
}
PathBuf::from(path)
}

fn get_default_repo_dir() -> PathBuf {
dirs::home_dir().unwrap().join(".espressif").join("developer-portal")
}

#[tauri::command]
pub async fn launch_hugo(state: State<'_, HugoState>, app: AppHandle, repo_path: Option<String>) -> Result<String, String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);

{
let mut is_running = state.is_running.lock().await;
if *is_running {
return Err("Hugo is already running".to_string());
}
*is_running = true;
}

let result = run_external_command_with_progress(app.clone(), HUGO_COMMAND, &[HUGO_ARGS[0], expanded_repo_dir.to_str().unwrap()], "hugo-progress").await;

if result.is_err() {
let mut is_running = state.is_running.lock().await;
*is_running = false;
return Err("Failed to start Hugo server".to_string());
}

Ok("Hugo server started".into())
}

#[tauri::command]
pub async fn restart_hugo(state: State<'_, HugoState>, app: AppHandle, repo_path: Option<String>) -> Result<String, String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);

let relaunch: bool;
{
let mut is_running = state.is_running.lock().await;
relaunch = *is_running;
if relaunch {
run_external_command_with_progress(app.clone(), "pkill", &["-f", "hugo server"], "hugo-progress").await.ok();
*is_running = false;
}
}

if relaunch {
let result = run_external_command_with_progress(app.clone(), HUGO_COMMAND, &[HUGO_ARGS[0], expanded_repo_dir.to_str().unwrap()], "hugo-progress").await;

if result.is_err() {
let mut is_running = state.is_running.lock().await;
*is_running = false;
return Err("Failed to restart Hugo server".to_string());
}

let mut is_running = state.is_running.lock().await;
*is_running = true;
}

Ok("Hugo server restarted".to_string())
}
10 changes: 10 additions & 0 deletions src-tauri/src/developer_portal/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
pub mod hugo;
pub mod repo;
pub mod articles;
pub mod authors;

// Re-exporting commands for easy access
pub use hugo::*;
pub use repo::*;
pub use articles::*;
pub use authors::*;
43 changes: 43 additions & 0 deletions src-tauri/src/developer_portal/repo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use tauri::AppHandle;
use crate::external_command::run_external_command_with_progress;
use std::path::PathBuf;

fn expand_tilde(path: &str) -> PathBuf {
if path.starts_with("~/") {
if let Some(home_dir) = dirs::home_dir() {
return home_dir.join(path.trim_start_matches("~/"));
}
}
PathBuf::from(path)
}

fn get_default_repo_dir() -> PathBuf {
dirs::home_dir().unwrap().join(".espressif").join("developer-portal")
}

#[tauri::command]
pub async fn clone_devportal_repo(repo_url: String, repo_path: Option<String>, app: AppHandle) -> Result<String, String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);
let result = run_external_command_with_progress(
app,
"git",
&["clone", &repo_url, expanded_repo_dir.to_str().unwrap()],
"clone-progress",
)
.await;

if result.is_err() {
return Err("Failed to clone repository".to_string());
}

Ok("Repository cloned successfully".into())
}

#[tauri::command]
pub async fn check_devportal(repo_path: Option<String>) -> Result<bool, String> {
let repo_dir = repo_path.unwrap_or_else(|| get_default_repo_dir().to_str().unwrap().to_string());
let expanded_repo_dir = expand_tilde(&repo_dir);

Ok(expanded_repo_dir.exists())
}
2 changes: 0 additions & 2 deletions src-tauri/src/esp_idf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ pub fn run_install_script(
{
let args = vec![file_path.to_str().unwrap()];
run_external_command_with_progress(
window.clone(),
app.clone(),
"bash",
&args,
Expand All @@ -43,7 +42,6 @@ pub fn run_install_script(
{
let args = vec!["/c", file_path.to_str().unwrap()];
run_external_command_with_progress(
window.clone(),
app.clone(),
"cmd",
&args,
Expand Down
Loading