C++练习:Docsify侧边栏生成工具

selph
selph
发布于 2020-08-18 / 803 阅读
0
0

C++练习:Docsify侧边栏生成工具

因重构代码等原因本项目已停更,新版本的Docsify侧边栏一键生成工具见我的新的Github项目: https://github.com/kn0sky/docsify-autosidebar

初学C++和Win32编程练手,想着做点对自己有用的东西吧,网上的一些例子没动力去做,有什么值得改进的地方希望大家能当场私信指出,提前谢谢大家啦

这个工具作为Win32API图形编程和C++学习的一次体验练手,目前功能够用,以后可能考虑重写代码

Github下载地址: https://github.com/kn0sky/StudyReport/blob/master/Docsify-SidebarTool.exe

使用演示:

sidebartool

实现思路分析

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.等数字进行默认排序

如图所示:

image-20200817210256934

功能实现了,就封装到类里了

功能实现(GUI 部分)

写一个枯燥的界面:

image-20200818121836880

	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;
}

参考资料


评论