软件使用
项目介绍
温馨提示:如果本地还未搭建 hugo 博客,可以使用我的另一个 hugo 博客自动搭建工具
-
QtRun:
-
介绍:一个用纯 C++ 写的命令行工具。
-
主要作用:根据提供的 hugo 博客本地地址进行命令行式的自动化写作,会把每一篇文章的图片、分类、标题等内容自动化完成。
-
构建方式:确保生成 exe 文件的目录下含有以下文件,且确保编译器支持 C++17。
BlogPath.txt #提供本地hugo博客路径 categories.txt #提供可供选择的分类(没有也没关系) initImg.txt #提供可选择的图片 mob.txt #提供用于生成的模板 ed_Path.txt #提供打开的编辑器路径(没有也没关系)
-
使用方式:可查看源码得到更详尽的解答
QtRun [title name] [category name] Qtrun [-op]
-
-
QtRunBlog:
- 介绍:使用 Qt+cmake 搭建的图形化 hugo 自动化工具,写作的相关部分都是调用的 QtRun ,所以运行时 QtRun 的配置文件和 QtRun 都必须在它的 exe 目录之下。而其他其他部分调用的 git 命令行,所以需要本地有 git 工具。
- 主要作用:提供图形化的 hugo 写作体验。
- 构建方式:本地需要 Qt6 环境,选择本项目目录便可完成构建。
QtRun C++ 源代码
实现了自动化命令行写作,QtRunBlog图形化界面调用的就是它的命令行
//
// Created by Alone on 2022-1-24.
//
//TODO aaaaaaa得出感悟:1.数据较为复杂的情况下尽量不要使用全局变量 2.在构造函数初始化的时候千万不要直接new空间给它,记得随时随地nullptr
#include <fstream>
#include <iostream>
#include <string>
#include <sys/stat.h>
#include <unistd.h>
#include <windows.h>
#include <vector>
#include <unordered_map>
#include <ctime>
#include <filesystem>
#include <random>
#define IMGS_PATH "./initImg.txt"
#define MOB_PATH "./mob.txt"
#define CATEGORIES_PATH "./categories.txt"
#define BLOG_SRC "./BlogPath.txt"
std::filesystem::path POSTS_PATH;//用于获取post_path
using namespace std;
//TODO 建立枚举映射
enum class SHOW_ARGS : int {
EMPTY, CATEGORIES, IMG, BLOG_PATH
};
//TODO 命令行参数的枚举映射
unordered_map<string, SHOW_ARGS> MAP{
{"-sc", SHOW_ARGS::CATEGORIES},
{"-si", SHOW_ARGS::IMG},
{"-sp", SHOW_ARGS::BLOG_PATH}
};
//TODO 封装文件读取类
class FileReader {
stringstream in_buf;
ifstream reader;
public:
FileReader() = default;
FileReader(const FileReader &) = delete;
FileReader(FileReader &&) = delete;
~FileReader() {
if (reader.is_open())
reader.close();
}
void open(const string &path) {
reader.open(path);
if (!reader.is_open()) {
perror("reader open failed");
exit(1);
}
in_buf << reader.rdbuf();
}
bool readAll(string &dst) {
if (in_buf.good())
dst = in_buf.str();
else
return false;
return true;
}
bool readline(string &dst) {
if (in_buf.good())
getline(in_buf, dst);
else return false;
return true;
}
};
//TODO 封装文件写入类
class FileWriter {
char *out_buf;
ofstream writer;
size_t cur_buf_size;
size_t max_buf_size;
private:
void _write() { //缓冲区写满,写入文件中
writer.write(out_buf, max_buf_size);
cur_buf_size = 0;
}
public:
FileWriter() : cur_buf_size(0), max_buf_size(512), out_buf(nullptr) {};
FileWriter(const FileReader &) = delete;
FileWriter(FileReader &&) = delete;
FileWriter(const string &path, ios::openmode mode = ios::out) {
writer.open(path, mode);
if (!writer.is_open()) {
perror("writer open failed");
exit(1);
}
cur_buf_size = 0;
max_buf_size = 512;
out_buf = new char[max_buf_size + 5];
}
~FileWriter() {
if (cur_buf_size > 0) {
writer.write(out_buf, cur_buf_size);
cur_buf_size = 0;
}
delete[] out_buf;
out_buf = nullptr;
writer.flush();
writer.close();
}
static bool exist(const string &path) {
return (access(path.c_str(), F_OK) != -1);
}
void open(const string &path, ios::openmode mode = ios::out) {
writer.open(path, mode);
if (!writer.is_open()) {
perror("writer open failed");
exit(1);
}
out_buf = new char[max_buf_size + 5];
}
void write(const string &src) {//TODO 缓冲机制的重要组成
if (writer.is_open()) {//只有在open文件后才能写入
if (src.empty()) return;
if (cur_buf_size == max_buf_size)
_write();
size_t psize = src.size() + cur_buf_size;//如果全盘写入缓冲区后,缓冲区需要的大小
int startp = 0, maxLen;
while (psize > max_buf_size) { //当这次写入缓冲区的数据量大于缓冲区的大小,则进行不断写满更新操作
maxLen = max_buf_size - cur_buf_size;
copy(src.begin() + startp, src.begin() + startp + maxLen, out_buf + cur_buf_size);//copy到满状态,再来一次write
_write();
startp += maxLen;
psize -= max_buf_size;
}
//如果写入数据不超出缓冲区大小,则直接写入
copy(src.begin() + startp, src.end(), out_buf + cur_buf_size);
cur_buf_size += src.size() - startp;
}
}
FileWriter &append(const string &src) {//TODO 和write没区别,只是支持链式调用
write(src);
return *this;
}
};
//TODO 整个项目需要操作的变量(很不推荐用全局变量,我就是因为这玩意就导致了bug)
FileReader readImg, readText, readCategories;//用于文件io的变量
FileWriter appendCategories, fileWriter;
vector<string> imgs, categories; //用于存下磁盘到内存的数据,根据名字判断存的啥
time_t now = time(NULL);
//TODO 打开typora软件
void open_exe_from_path(const char *path) {
WinExec(path, SW_SHOWNORMAL);
cout << "open your custom editor successfully!" << endl;
}
//TODO 打印内容
void show(vector<string> &src) {
for (int i = 0; i < src.size(); i++) {
if (!src[i].empty())
printf("%d: %s\n", i, src[i].c_str());
}
}
//TODO 替换string的内容
void to_replace(string &s, const string &target, const string &replacement) {
int i = 0;
int find_ret;
int tar_len = target.size();
while ((find_ret = s.find(target, i)) != -1) {
s.replace(find_ret, tar_len, replacement);
i = find_ret;
}
}
//TODO 打印出错的信息,并退出程序
void exit_print(const char *content) {
printf("running failed: %s\n", content);
exit(1);
}
//TODO 得到当前的时间
string get_cur_time() {
tm *tm_t = localtime(&now);
char c_time[50];
sprintf(c_time, "%04d-%02d-%02d",
tm_t->tm_year + 1900,
tm_t->tm_mon + 1,
tm_t->tm_mday);
return string(c_time);
}
//TODO 基本的初始化数据
void InitInputFileInfo() {
readImg.open(IMGS_PATH);
readText.open(MOB_PATH);
readCategories.open(CATEGORIES_PATH);
}
void InitOutputFileInfo(const string &targetPath) {
fileWriter.open(targetPath);
appendCategories.open(CATEGORIES_PATH, ios::app);
}
void InitImgs() {
string tmp;
while (readImg.readline(tmp)) {
if (!tmp.empty())
imgs.push_back(tmp);
}
//todo 经过洗牌算法把数组里面的内容打乱
shuffle(imgs.begin(), imgs.end(), std::default_random_engine(now));
}
void InitCategories() {
string tmp;
while (readCategories.readline(tmp)) {
categories.push_back(tmp);
}
}
void InitPostPath() {
FileReader fd;
fd.open(BLOG_SRC);
string pth;
fd.readline(pth);
POSTS_PATH = pth;
}
//TODO 处理三个参数的情况
void solve(const char *arg1, const char *arg2) {
//todo 初始化io逻辑
InitInputFileInfo();
InitImgs();
InitCategories();
string text;
string category;
InitPostPath();
POSTS_PATH /= "content";
POSTS_PATH /= "posts";
POSTS_PATH /= string(arg1) + ".md";
if (filesystem::exists(POSTS_PATH))
exit_print("file exist!");
InitOutputFileInfo(POSTS_PATH.string());
//todo 替换文本
srand(now); //以当前时间作为种子重新播种
int randomIndex = rand() % imgs.size();//随机选择一张图片
readText.readAll(text);
to_replace(text, "%s", arg1);//更换文章名字为标题名称
to_replace(text, "%T", imgs[randomIndex]);//将图片内容进行替换
to_replace(text, "%D", get_cur_time()); //将当前日期进行替换
//根据arg2来选择替换的分类内容
if (to_string(atoi(arg2)) == arg2) {//判断arg2传递的是否是数字
int index = atoi(arg2);
if (index < categories.size() - 1) {//数字合法选择对应下标的分类
category = categories[index];
} else {//数字不合法,退出程序
exit_print("number not allowed");
}
} else {//不是数字,则说明创建了新的分类
category = arg2;
appendCategories.append(string(arg2) + "\n");
}
to_replace(text, "#", category);
//todo 最后再将数据写入磁盘
fileWriter.write(text);
}
int main(int argc, char const *argv[]) {
system("chcp 65001");
SHOW_ARGS state;//由于switch语句的第一层不能定义临时变量
switch (argc) {
case 2://todo 外界传来的字符串实际上不是很重要,主要是可以根据这个字符串确定枚举状态,然后再进行不同内容的show操作
state = MAP[argv[1]];
if (state == SHOW_ARGS::EMPTY) {//1.哈希表中无该字符串
fputs("args error,may be you should get something below:\n", stderr);
for (auto[k, v]:MAP) {//显示参数提示
if (v == SHOW_ARGS::EMPTY)continue;//把上面获得值的时候创建的对象给跳过
fputs((k + '\n').c_str(), stderr);
}
exit(1);
} else if (state == SHOW_ARGS::CATEGORIES) {//2.显示分类信息
InitInputFileInfo();
InitCategories();
show(categories);
} else if (state == SHOW_ARGS::IMG) {//3.显示图片的链接
InitInputFileInfo();
InitImgs();
show(imgs);
} else if (state == SHOW_ARGS::BLOG_PATH) {//4.显示博客地址
InitPostPath();
cout << POSTS_PATH << '\n';
}
return 0;//仅仅展示数据就退出
case 3:
solve(argv[1], argv[2]);
break;
default:
printf("Usage :\n%s [title name] [category name]\n", argv[0]);
printf("%s [-op]\nsuch as -s to show what category number can choose.\n", argv[0]);
exit(1);
}
ifstream reader;//读取编辑器的地址并打开
reader.open("./ed_Path.txt");
if (!reader.is_open()) {
exit_print("file reader failed in read editorPath");
}
string str;
reader >> str;
if (!str.empty())
open_exe_from_path(str.c_str());
else { //如果文件编辑器路径未设置,则默认用vscode打开目录
string cmd = "code " + POSTS_PATH.string();
system(cmd.c_str());
printf("open vscode successfully!\n");
}
reader.close();
cout << "finish yet!" << endl;
return 0;
}