Tree Website generation implemented
[biaweb2.git] / biawebdocumenttree.hpp
1 #ifndef __BIAWEBDOCUMENTTREE__
2 #define __BIAWEBDOCUMENTTREE__
3 #include <memory>
4 #include <list>
5 #include <iostream>
6 #include <filesystem>
7 #include <sys/stat.h>
8 #include "biawebdocument.hpp"
9 #include "biawebstrings.hpp"
10 #include "biawebutil.hpp"
11
12 // to implement a document tree - both with or without subtrees
13 namespace biaweb {
14 class DocumentTree {
15 protected:
16 // the pointer to the parent tree if there is one or nullptr
17 DocumentTree* parent;
18 // child trees
19 std::list<DocumentTree> children;
20 // title of this tree
21 std::string title;
22 // summary for this tree - this is displayed in the index.html file of
23 // this tree before the list of articles in the tree
24 std::string summary;
25 // file stub of this tree
26 std::string stub;
27 // list of documents in this tree
28 std::list<Document> docs;
29 // set the parent - protected function as this has to be
30 // called only by add_child
31 void set_parent (DocumentTree *parent) {
32 this->parent = parent;
33 }
34
35 public:
36 // method to build a document tree from a path
37 void document_tree_builder (std::string srcpath);
38
39 // create new top level document tree
40 DocumentTree (std::string title, std::string stub = "") {
41 this->title = title;
42 // if stub is not empty set it
43 if (stub != "")
44 this->stub = stub;
45 // make the stub from the title
46 else
47 this->stub = convert_title (title);
48 this->parent = nullptr;
49 }
50
51 // set the summary for this tree
52 void set_summary (std::string summary) {
53 this->summary = summary;
54 }
55
56 // set the summary for this tree as markdown text
57 void set_markdown_summary (std::string summary) {
58 this->summary = convert_to_markdown (summary);
59 }
60
61 std::string get_summary () {
62 return this->summary;
63 }
64
65 // sort the documents as per creation time from latest to oldest
66 void sort_documents_creation_time () {
67 this->docs.sort ([] (Document &a, Document &b)
68 {return (a.get_creation_date() > b.get_creation_date()); });
69 }
70
71 // create the document index for this tree
72 void create_tree_html (std::string templatedir, std::string destdir);
73
74 // set the title
75 void set_title (std::string title) {
76 this->title = title;
77 // if no stub is set
78 if (this->stub == "")
79 this->stub = convert_title (title);
80 }
81
82 void set_stub (std::string stub) {
83 this->stub = stub;
84 }
85
86 std::string get_title () {
87 return this->title;
88 }
89
90 std::string get_stub () {
91 return this->stub;
92 }
93
94 // get the child level of this tree
95 unsigned int get_level ();
96
97 // get the stub hierarchy
98 std::string stub_hierarchy ();
99
100 // add a child tree to this tree
101 void add_child (DocumentTree *child) {
102 child->set_parent (this);
103 this->children.push_back (*child);
104 }
105
106 // add a document to this tree
107 void add_document (Document *doc) {
108 this->docs.push_back (*doc);
109 }
110
111 // print a visual representation of this tree with levels
112 void visualize_tree ();
113
114 // get a pointer to the parent of this tree
115 DocumentTree *get_parent () {
116 return this->parent;
117 }
118 };
119
120 // get the tree level - 0 if top level
121 unsigned int DocumentTree::get_level () {
122 unsigned int lev = 0;
123 DocumentTree *par = this->get_parent ();
124 while (par != nullptr) {
125 lev ++;
126 par = par->get_parent ();
127 }
128 return lev;
129 }
130
131 // get the stub hierarchy for this tree
132 std::string DocumentTree::stub_hierarchy () {
133 std::list<std::string> levels;
134 DocumentTree *par = this->get_parent();
135 while (par!= nullptr) {
136 levels.push_front (par->get_stub());
137 par = par->get_parent ();
138 }
139 std::string stub_str;
140 for (std::string level : levels) {
141 // if stub is empty, don't append a /
142 if (level != "")
143 stub_str += level + "/";
144 }
145 return stub_str;
146 }
147
148 // print the representation of this tree
149 void DocumentTree::visualize_tree () {
150 // print the tree level
151 std::cout << std::setw(3) << std::left << this->get_level ();
152 // indent as per the level
153 for (unsigned int i = 0; i < this->get_level(); i ++)
154 std::cout << "+--";
155 // print the title of this tree
156 std::cout << this->title << std::endl;
157 // recurse through the child trees if any and so on
158 for (DocumentTree child : children)
159 child.visualize_tree ();
160 }
161
162 // create the tree - the index file for this tree and all the documents and
163 // the child trees recursively - using the template specified
164 void DocumentTree::create_tree_html (std::string templatedir, std::string destdir) {
165 std::unique_ptr<Document> index (new Document (this->title));
166 index.get()->set_index ();
167 // set the file name path
168 std::string filepath = destdir + "/" + this->stub_hierarchy () + this->stub;
169 // set the url path - this should not have destdir as it is not part
170 // of the site tree
171 std::string urlpath = this->stub_hierarchy () + this->stub;
172 // if urlpath is not empty then append a / to the end of the URL. This
173 // is so that the base URL is not absolute
174 if (urlpath != "")
175 urlpath += "/";
176
177 // create the sidebars
178 // First sidebar
179 // Create a link to the index page and
180 // If this tree has a parent, create a sidebar link to the level up
181 std::unique_ptr<SideBar> bar1 (new SideBar());
182 SideBarItem item0;
183 item0.set_sidebar_text (INDEX);
184 item0.set_sidebar_url (urlpath + "index.html");
185 bar1.get()->add_sidebar_item (item0);
186 if (this->get_parent() != nullptr) {
187 SideBarItem item1;
188 item1.set_sidebar_text (GO_UP);
189 item1.set_sidebar_url (this->stub_hierarchy() + "index.html");
190 bar1.get()->add_sidebar_item (item1);
191 }
192 index.get()->add_side_bar (*bar1.get());
193
194 // create a sidebar for the child levels if there are children
195 std::unique_ptr<SideBar> bar2 (new SideBar ());
196 bar2.get()->set_title (SUB_CAT + this->title);
197 for (DocumentTree tree : this->children) {
198 // we use site relative URLs that rely on the base href tag
199 // so for biaweb generated sites, the base href tag should be
200 // used in the main template
201 SideBarItem item (tree.get_title(), urlpath +
202 tree.stub + "/" + "index.html");
203 bar2.get()->add_sidebar_item (item);
204 }
205 index.get()->add_side_bar (*bar2.get());
206
207 // create the path and then the index file
208 std::filesystem::create_directories (filepath);
209
210 // Create the list of documents in this tree with links
211 // Reuse the sidebar class and sidebaritem class which is
212 // basically a list of links but instead of adding a sidebar
213 // add it to the content portion
214 SideBar article_list;
215 article_list.set_title (this->title + ": " + ART_LIST);
216 // sort the documents as per creation time and then add the document
217 // links - newest documents should appear above older ones.
218 sort_documents_creation_time ();
219 for (Document doc : this->docs) {
220 SideBarItem item;
221 item.set_sidebar_text (doc.get_title());
222 item.set_sidebar_url (urlpath + doc.get_filename() + ".html");
223 article_list.add_sidebar_item (item);
224 // output the document also, add the side bars
225 doc.add_side_bar (*bar1.get());
226 doc.add_side_bar (*bar2.get());
227 doc.output_to_html (templatedir, filepath);
228 }
229 index.get()->set_content (this->summary + article_list.to_html(templatedir));
230
231 index.get()->output_to_html (templatedir, filepath);
232
233 // create index for children
234 for (DocumentTree tree : this->children)
235 tree.create_tree_html (templatedir, destdir);
236 }
237
238 // build a document tree from a filesystem path recursively
239 void DocumentTree::document_tree_builder (std::string srcpath_str) {
240 std::filesystem::path srcpath (srcpath_str);
241 this->title = srcpath.stem().string ();
242
243 // Get the directories to this child and add them as sub document
244 // trees
245 try {
246 for (auto fsitem : std::filesystem::directory_iterator (srcpath) )
247 {
248 // if it is a directory then build the tree for that directory
249 if (fsitem.is_directory ()) {
250 std::shared_ptr <DocumentTree> doctree
251 (new DocumentTree (fsitem.path().filename().string()));
252
253 this->add_child (doctree.get());
254 }
255 // add the regular files as documents in the tree
256 else if (fsitem.is_regular_file ()) {
257 // read the contents of the file
258 std::ifstream infile (fsitem.path().string());
259 std::string infilestr ((std::istreambuf_iterator<char> (infile)),
260 (std::istreambuf_iterator<char> ()));
261 // if it is an index file (specially named index) add
262 // the contents to the summary of the Doctree
263 if (fsitem.path().stem().string() == "index")
264 {
265 this->set_markdown_summary (infilestr);
266 }
267 // else it is a non-index file-
268 // create a Document and add it to the tree
269 else {
270 std::shared_ptr<Document> doc
271 (new Document (fsitem.path().stem().string()));
272
273 // file creation/modified date from system
274 struct stat buf;
275 if (stat (fsitem.path().string().c_str(), &buf) == 0)
276 {
277 doc.get()->set_creation_date (buf.st_ctim.tv_sec);
278 doc.get()->set_modified_date (buf.st_mtim.tv_sec);
279 }
280 doc.get()->set_markdown_content (infilestr);
281
282 this->add_document (doc.get());
283 }
284 }
285 }
286 }
287 catch (std::filesystem::filesystem_error) {
288 std::cout << "No such path! Specify an existing file path" << std::endl;
289 }
290
291 // add the trees for the children recursively
292 for (DocumentTree &child : this->children)
293 child.document_tree_builder (srcpath_str + "/" + child.title);
294 }
295 }
296
297 #endif