因重构代码等原因本项目已停更,新版本的Docsify侧边栏一键生成工具见我的新的Github项目: https://github.com/kn0sky/docsify-autosidebar
初学C++和Win32编程练手,想着做点对自己有用的东西吧,网上的一些例子没动力去做,有什么值得改进的地方希望大家能当场私信指出,提前谢谢大家啦
这个工具作为Win32API图形编程和C++学习的一次体验练手,目前功能够用,以后可能考虑重写代码
Github下载地址: https://github.com/kn0sky/StudyReport/blob/master/Docsify-SidebarTool.exe
使用演示:
实现思路分析
Docsify 框架通过 _sidebar.md 来生成侧边栏
我用Docsify框架主要用来存储笔记,所以需要有主页面和子页面
所以我的 _sidebar.md 要分两种,一个是根目录 _sidebar.md,一个是子目录_sidebar.md
根目录内容格式如下:
<!-- docs/_sidebar.md -->
- [关于wiki](/README.md)
- 系统编程:
- [C语言学习笔记](/note_c/README.md)
- [C++语言学习笔记](note_cpp/README.md)
子目录内容格式如下:
- [返回上一层](/README.md)
- [C++学习笔记:](note_cpp/README.md)
- [第一章:面向对象程序设计概述](note_cpp/note_cpp1.md)
- [第二章:C++概述](note_cpp/note_cpp2.md)
根目录有分类需求,且根目录下仅显示当前页面下的和目录里的README.md文件及其标题
子目录下显示子目录下的所有md文件及其标题
功能实现思路如下:
- 选择一个目录(docsify根目录),读取目录内的文件列表,获取其路径,存起来
- 如果有目录,则读取目录下的README.md文件,获取其路径,存起来(默认每个子目录下都有README.md文件)
- 根据存储的md文件路径,读取文件标题
- 按照规则输出_sidebar.md文件
- 对子目录重复上述流程
- 封装到类里,准备开发GUI
图形界面实现如下:
- 程序记录上次选择的地址,下次打开默认上次目录
- 通过复选框来选择是否生成子目录_sidebar.md
- 通过按钮点击来进行一键生成
功能实现(Console部分)
1. 读取文件列表
读取并保存指定目录下的文件名和子目录名
因为文件路径长短不一,经查资料得知,需要用一个动态数组的容器Vector来进行存储,可以简单认为Vector是一个动态数组
向容器里添加元素可以使用push_back方法
Struct _finddata_t是用来存储文件信息的结构体,位于头文件<io.h>
结构如下:
struct _finddata32_t
{
unsigned attrib; // 文件的属性,主要有这些:
// _A_ARCH(存档)、 _A_HIDDEN(隐藏)、_A_NORMAL(正常)
// _A_RDONLY(只读)、_A_SUBDIR(文件夹)、_A_SYSTEM(系统)
__time32_t time_create; // 从1970年1月1日0时0分0秒到现在时刻的秒数
__time32_t time_access; // 最后一次被访问的时间
__time32_t time_write; // 最后一次被修改的时间
_fsize_t size; // 文件的大小(字节)
char name[260]; // 文件名
};
查找文件用到的三个API函数:
1、_findfirst函数:long _findfirst(const char *, struct _finddata_t *);
第一个参数为文件名,可以用"*.*"来查找所有文件,也可以用"*.cpp"来查找.cpp文件。第二个参数是_finddata_t结构体指针。若查找成功,返回文件句柄,若失败,返回-1。
2、_findnext函数:int _findnext(long, struct _finddata_t *);
第一个参数为文件句柄,第二个参数同样为_finddata_t结构体指针。若查找成功,返回0,失败返回-1。
3、_findclose()函数:int _findclose(long);
只有一个参数,文件句柄。若关闭成功返回0,失败返回-1。
这一块的代码如下:
void GetFileList(string& path, vector<string>& files, vector<string>& dirs) {
HFILE hFile = 0; //文件句柄
struct _finddata_t Fileinfo; //文件信息,需要头文件io.h
string p;
int i = 0;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &Fileinfo)) != -1) {
do {
if ((Fileinfo.attrib == _A_SUBDIR)) { //如果是目录,则存到目录容器里
dirs.push_back(p.assign(path).append("\\").append(Fileinfo.name));
}
else { //如果是文件,则存到文件容器里
files.push_back(p.assign(path).append("\\").append(Fileinfo.name));
}
} while (_findnext(hFile, &Fileinfo) == 0);
_findclose(hFile);
}
}
int main() {
string pathname = "D:\\test";
vector<string> files; //动态数组,存储当前目录的文件
vector<string> dirs; //动态数组,存储当前目录的子目录
GetFileList(pathname,files,dirs);
for (int i = 0; i < dirs.size(); i++) {
cout << dirs[i] << endl;
}
for (int i = 0; i < files.size(); i++) {
cout << files[i] << endl;
}
Sleep(10000);
return 0;
}
2. 文件的读取和写入
这一块内容详解见我的C++学习笔记第七章
文件的读取:读取md文件的标题
//过滤#号
string SymFilter(string cap) {
int i = 0;
if (cap == "")return "NULL";
for (i = 0; i < cap.length(); i++) {
if (cap[i] != '#') {
break;
}
}
return cap.substr(i+1, cap.length());
}
//读取标题
string ReadCapital(string filepath) {
//读取标题
ifstream in;
char buf[64];
in.open(filepath); //打开文件
in.getline(buf, 64, '\n'); //获取标题
in.close();
return SymFilter(buf);
}
文件的写入:
//写入单行到sidebar.md
void Write_sidebarsimple(string sidebarpath,string str, ios::openmode wmode) {
ofstream out;
out.open(sidebarpath, wmode);
out << str << endl;
out.close();
}
3. 生成完整的sidebar内容
这一块逻辑比较简单,直接上代码
sidebar生成规则是,获取当前目录所有文件,如果是md文件,则显示文件标题和文件的路径,如果是目录,则显示目录下README.md文件的标题和路径,按照格式写入sidebar.md里
//拼接sidebar.md格式
string splice_sidebar(string capitial,string path) {
//拼接格式:"- [name](path)"
string content;
content.append(" - [");
content.append(capitial);
content.append("](");
content.append(path);
content.append(")");
return content;
}
//生成完整的Sidebar.md内容
void Write_Sidebar(vector<string> files,string sidebarpath) {
// 生成完整的侧边栏
for (int i = 0; i < files.size(); i++) {
string filepath;
if (files[i].substr(files[i].length() - 3, files[i].length()) != ".md") {
filepath = files[i];
if (filepath.substr(filepath.length() - 1, filepath.length()) == ".")
continue;
else if (filepath.substr(filepath.length() - 2, filepath.length()) == "..")
continue;
filepath = files[i].append("\\README.md");
}
else {
filepath = files[i];
if (filepath.substr(filepath.length() - 11, filepath.length()) == "_sidebar.md")
continue;
else if (filepath.substr(filepath.length() - 3, filepath.length()) != ".md")
continue;
}
//读取标题
string buf = ReadCapital(filepath);
//拼接格式:"- [name](path)"
string content = splice_sidebar(buf, filepath);
//生成_sidebar.md
Write_sidebarsimple(sidebarpath, content, ios::app);
}
}
4. 创建根/子目录sidebar
这里就是前面操作的整合了,在子目录上的操作与父目录相同
//创建根目录sidebar.md
void Create_sidebar_root(string& path, vector<string>& files, vector<string>& dirs) {
string sidebarpath = path.append("\\_sidebar.md");
// 初始化sidebar.md
Write_sidebarsimple(sidebarpath, "<!-- docs/_sidebar.md create by Doscify-SidebarTool -->", ios::out);
//先后写入子目录和当前目录文件
Write_sidebarsimple(sidebarpath, "- Directory:", ios::app);
Write_Sidebar(dirs, sidebarpath);
Write_sidebarsimple(sidebarpath, "- File:", ios::app);
Write_Sidebar(files, sidebarpath);
}
//创建子目录sidebar.md
void Create_sidebar_sub(vector<string>& subpath) {
for (int i = 2; i < subpath.size(); i++) {
vector<string> files; // 文件名
vector<string> dirs; // 目录名
GetFileList(subpath[i], files, dirs);
Create_sidebar_root(subpath[i], files, dirs);
}
}
至于文件的排列顺序,则会按照系统默认的顺序进行排列,可以通过文件名命名时使用1.2.3.4.等数字进行默认排序
如图所示:
功能实现了,就封装到类里了
功能实现(GUI 部分)
写一个枯燥的界面:
case WM_CREATE:{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
HWND hedit = CreateWindow(L"edit", L"D:\\test", WS_CHILD | WS_VISIBLE | WS_BORDER |ES_AUTOHSCROLL | ES_NOHIDESEL, 15, 15, 300, 25, hWnd, (HMENU)1111, pcs->hInstance, NULL);
HWND hStatic = CreateWindow(L"Static", L"框", WS_CHILD | WS_VISIBLE | SS_SIMPLE | SS_BLACKRECT, 15, 60, 200, 150, hWnd, (HMENU)1001, pcs->hInstance, NULL);
HWND hStatic2 = CreateWindow(L"Static", L"请选择生成模式:", WS_CHILD | WS_VISIBLE | SS_SIMPLE , 15, 70, 200, 25, hWnd, (HMENU)1002, pcs->hInstance, NULL);
HWND hBotton1 = CreateWindow(L"Button", L"仅生成根目录(默认选项)", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 15, 105, 200, 25, hWnd, (HMENU)1003, pcs->hInstance, NULL);
HWND hBotton2 = CreateWindow(L"Button", L"生成根目录和子目录", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 15, 140, 200, 25, hWnd, (HMENU)1004, pcs->hInstance, NULL);
HWND hBotton3 = CreateWindow(L"Button", L"一键生成", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 230, 160, 80, 50, hWnd, (HMENU)1005, pcs->hInstance, NULL);
break;
}
记录编辑框的路径内容保存到变量里
通过单选框的点击来修改flag的值从而判断进行何种生成
点击一键生成根据flag的值来调用生成
case WM_COMMAND: {
WORD id = LOWORD(wParam);
WORD code = HIWORD(wParam);
HWND hCtrl = (HWND)lParam;
if (id == 1111 && code == EN_CHANGE) {
TCHAR buf[64] = { 0 };
GetWindowText(hCtrl, buf, sizeof(buf));
path = TCHAR2STRING(buf);
OutputDebugString(buf);
OutputDebugString(L"\n");
}
else if (id == 1003) {
create_mode = 0;
OutputDebugString(L"0\n");
}
else if (id == 1004) {
create_mode = 1;
OutputDebugString(L"1\n");
}
else if (id == 1005) {
Docsifydir tmp;
string rootpath = path;
tmp.GetFileList(rootpath, tmp.files, tmp.dirs);
tmp.Create_sidebar_root(rootpath, tmp.files, tmp.dirs);
if(create_mode == 1)
tmp.Create_sidebar_sub(tmp.dirs);
OutputDebugString(L"Success!");
}
break;
}
完整代码
fun.h
#pragma once
#include<windows.h>
#include<iostream>
#include<vector>
#include<io.h>
#include<string>
#include<fstream>
using namespace std;
class Docsifydir {
public:
//获取文件和子目录列表
void GetFileList(string& path, vector<string>& files, vector<string>& dirs);
//在根目录下生成sidebar文件
void Create_sidebar_root(string& path, vector<string>& files, vector<string>& dirs);
//在子目录下生成sidebar文件
void Create_sidebar_sub(vector<string>& subpath);
vector<string> files; //存储获取到的文件
vector<string> dirs; //存储获取到的目录
void setrootlen(string rootpath) {
this->rootpathlength = strlen(rootpath.c_str());
}
private:
string SymFilter(string cap);
string ReadCapital(string filepath);
void Write_sidebarsimple(string sidebarpath, string str, ios::openmode wmode);
string splice_sidebar(string capitial, string path);
void Write_Sidebar(vector<string> files, string sidebarpath);
int rootpathlength; //路径长度,用来修改相对路径
};
//使用说明
//Docsifydir tmp;
//string rootpath = "D:\\test";
//setrootlen(rootpath);
//tmp.GetFileList(rootpath,tmp.files,tmp.dirs);
//tmp.Create_sidebar_root(rootpath, tmp.files, tmp.dirs);
//[可选]生成子目录的sidebar文件
//tmp.Create_sidebar_sub(tmp.dirs);
fun.cpp
#include "fun.h"
void Docsifydir::GetFileList(string& path, vector<string>& files, vector<string>& dirs) {
HFILE hFile = 0; //文件句柄
struct _finddata_t Fileinfo; //文件信息,需要头文件io.h
string p = "";
int i = 0;
if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &Fileinfo)) != -1) {
do {
if ((Fileinfo.attrib == _A_SUBDIR)) {
//cout << Fileinfo.name<<endl;
dirs.push_back(p.assign(path).append("\\").append(Fileinfo.name));
}
else {
files.push_back(p.assign(path).append("\\").append(Fileinfo.name));
}
} while (_findnext(hFile, &Fileinfo) == 0);
_findclose(hFile);
}
}
//过滤#号
string Docsifydir::SymFilter(string cap) {
int i = 0;
if (cap == "")return "NULL";
for (i = 0; i < cap.length(); i++) {
if (cap[i] != '#') {
break;
}
}
return cap.substr(i+1, cap.length());
}
//读取标题
string Docsifydir::ReadCapital(string filepath) {
//读取标题
ifstream in;
char buf[64];
in.open(filepath); //打开文件
in.getline(buf, 64, '\n'); //获取标题
in.close();
return SymFilter(buf);
}
//写入单行到sidebar.md
void Docsifydir::Write_sidebarsimple(string sidebarpath,string str, ios::openmode wmode) {
ofstream out;
out.open(sidebarpath, wmode);
out << str << endl;
out.close();
}
//拼接sidebar.md格式
string Docsifydir::splice_sidebar(string capitial,string path) {
path = path.substr(this->rootpathlength+1, path.length());
//拼接格式:"- [name](path)"
string content;
content.append(" - [");
content.append(capitial);
content.append("](");
content.append(path);
content.append(")");
return content;
}
//生成完整的Sidebar.md内容
void Docsifydir::Write_Sidebar(vector<string> files,string sidebarpath) {
// 生成完整的侧边栏
for (int i = 0; i < files.size(); i++) {
string filepath;
if (files[i].substr(files[i].length() - 3, files[i].length()) != ".md") {
filepath = files[i];
if (filepath.substr(filepath.length() - 1, filepath.length()) == ".")
continue;
else if (filepath.substr(filepath.length() - 2, filepath.length()) == "..")
continue;
filepath = files[i].append("\\README.md");
}
else {
filepath = files[i];
if (filepath.substr(filepath.length() - 11, filepath.length()) == "_sidebar.md")
continue;
else if (filepath.substr(filepath.length() - 3, filepath.length()) != ".md")
continue;
}
//读取标题
string buf = ReadCapital(filepath);
//拼接格式:"- [name](path)"
string content = splice_sidebar(buf, filepath);
//生成_sidebar.md
Write_sidebarsimple(sidebarpath, content, ios::app);
}
}
//创建根目录sidebar.md
void Docsifydir::Create_sidebar_root(string& path, vector<string>& files, vector<string>& dirs) {
string sidebarpath = path.append("\\_sidebar.md");
// 初始化sidebar.md
Write_sidebarsimple(sidebarpath, "<!-- docs/_sidebar.md create by Doscify-SidebarTool -->", ios::out);
//先后写入子目录和当前目录文件
Write_sidebarsimple(sidebarpath, "- Directory:", ios::app);
Write_Sidebar(dirs, sidebarpath);
Write_sidebarsimple(sidebarpath, "- File:", ios::app);
Write_Sidebar(files, sidebarpath);
}
//创建子目录sidebar.md
void Docsifydir::Create_sidebar_sub(vector<string>& subpath) {
for (int i = 2; i < subpath.size(); i++) {
vector<string> files; // 文件名
vector<string> dirs; // 目录名
GetFileList(subpath[i], files, dirs);
Create_sidebar_root(subpath[i], files, dirs);
}
}
WinMain.cpp
#include "fun.h"
#pragma comment(linker,"/subsystem:windows /entry:WinMainCRTStartup")
string path;//路径
int create_mode = 0;//0仅生成根目录,1生成根目录和子目录
//TCHAR转换string
string TCHAR2STRING(TCHAR* str)
{
std::string strstr;
int iLen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
char* chRtn = new char[iLen * sizeof(char)];
WideCharToMultiByte(CP_ACP, 0, str, -1, chRtn, iLen, NULL, NULL);
strstr = chRtn;
return strstr;
}
//窗口过程
LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_CREATE:{
LPCREATESTRUCT pcs = (LPCREATESTRUCT)lParam;
HWND hedit = CreateWindow(L"edit", L"D:\\test", WS_CHILD | WS_VISIBLE | WS_BORDER |ES_AUTOHSCROLL | ES_NOHIDESEL, 15, 15, 300, 25, hWnd, (HMENU)1111, pcs->hInstance, NULL);
HWND hStatic = CreateWindow(L"Static", L"框", WS_CHILD | WS_VISIBLE | SS_SIMPLE | SS_BLACKRECT, 15, 60, 200, 150, hWnd, (HMENU)1001, pcs->hInstance, NULL);
HWND hStatic2 = CreateWindow(L"Static", L"请选择生成模式:", WS_CHILD | WS_VISIBLE | SS_SIMPLE , 15, 70, 200, 25, hWnd, (HMENU)1002, pcs->hInstance, NULL);
HWND hBotton1 = CreateWindow(L"Button", L"仅生成根目录(默认选项)", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 15, 105, 200, 25, hWnd, (HMENU)1003, pcs->hInstance, NULL);
HWND hBotton2 = CreateWindow(L"Button", L"生成根目录和子目录", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 15, 140, 200, 25, hWnd, (HMENU)1004, pcs->hInstance, NULL);
HWND hBotton3 = CreateWindow(L"Button", L"一键生成", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 230, 160, 80, 50, hWnd, (HMENU)1005, pcs->hInstance, NULL);
break;
}
case WM_COMMAND: {
WORD id = LOWORD(wParam);
WORD code = HIWORD(wParam);
HWND hCtrl = (HWND)lParam;
if (id == 1111 && code == EN_CHANGE) {
TCHAR buf[64] = { 0 };
GetWindowText(hCtrl, buf, sizeof(buf));
path = TCHAR2STRING(buf);
OutputDebugString(buf);
OutputDebugString(L"\n");
}
else if (id == 1003) {
create_mode = 0;
OutputDebugString(L"0\n");
}
else if (id == 1004) {
create_mode = 1;
OutputDebugString(L"1\n");
}
else if (id == 1005) {
Docsifydir tmp;
string rootpath = path;
tmp.setrootlen(path);
tmp.GetFileList(rootpath, tmp.files, tmp.dirs);
tmp.Create_sidebar_root(rootpath, tmp.files, tmp.dirs);
if(create_mode == 1)
tmp.Create_sidebar_sub(tmp.dirs);
OutputDebugString(L"Success!");
}
break;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIstance, LPSTR IpCmdLine, int nCmdShow) {
//注册窗口类
WNDCLASS wnd;
wnd.cbClsExtra = 0;
wnd.cbWndExtra = 0;
wnd.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
wnd.hCursor = LoadCursor(NULL, IDC_ARROW);
wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wnd.hInstance = hInstance;
wnd.lpfnWndProc = MyWindowProc;
wnd.lpszClassName = L"Docsift-SdiebarTool";
wnd.lpszMenuName = NULL;
wnd.style = CS_HREDRAW;
RegisterClass(&wnd);
//创建窗口
HWND hWnd = CreateWindow(L"Docsift-SdiebarTool", L"_Sidebar.md 一键生成工具", WS_OVERLAPPEDWINDOW, 100, 100, 360, 265, NULL, NULL, hInstance, NULL);
//显示窗口
ShowWindow(hWnd, nCmdShow);
//更新窗口
UpdateWindow(hWnd);
//消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
参考资料
-
C++ vector 容器浅析
https://www.runoob.com/w3cnote/cpp-vector-container-analysis.html
-
C++获取指定目录下的所有文件
-
C++用 _findfirst 和 _findnext 查找文件
-
C++ 文件和流
-
c++之多文件结构