org-report-stats/parse_orgmode_to_json.mjs

267 lines
7.5 KiB
JavaScript
Raw Normal View History

2023-03-04 23:52:46 +01:00
/**
* convertir un fichier .org vers des données structurées en json
* @type {*}
*/
import fs from 'node-fs';
2023-03-05 23:13:16 +01:00
import moment from 'moment';
/**********************
* initialize configs
**********************/
const sourceFileName = 'demo_more.org'
const sourceFilePath = './sources/' + sourceFileName;
2023-03-04 23:52:46 +01:00
let headers = []
2023-03-04 23:57:03 +01:00
let tasksObjectsForJsonExport = []
2023-03-04 23:52:46 +01:00
let headersByKind = {}
2023-03-05 23:13:16 +01:00
let writeJsonAfterParse = false;
2023-03-04 23:52:46 +01:00
2023-03-05 23:13:16 +01:00
/**************************************************************
* fetch the source orgmode file to read its contents
*************************************************************/
2023-03-04 23:52:46 +01:00
2023-03-05 23:13:16 +01:00
console.log('parse some org file', sourceFilePath)
if (!sourceFilePath) {
console.error('pas de fichier à ouvrir')
}
2023-03-04 23:52:46 +01:00
fs.stat(sourceFilePath, function (err, stat) {
if (err == null) {
console.log(`File ${sourceFilePath} exists`);
} else if (err.code === 'ENOENT') {
// file does not exist
console.error(`le fichier ${sourceFilePath} est introuvable. Impossible d en extraire des infos.`, err);
} else {
console.log('Some other error: ', err.code);
}
});
2023-03-05 23:13:16 +01:00
/**********************
* search elements
*********************/
let stateKeywordList = ['SOMEDAY', 'NEXT', 'TODO', 'CANCELLED', 'DONE', 'WAITING'];
let dateKeywordList = ['CREATED', 'SCHEDULED', 'DEADLINE', 'CLOSED','Refiled'];
let sectionKeywordList = ['PROPERTIES', 'LOGBOOK', 'END'];
let propertiesSection = {}
let logBookSection = {}
2023-03-04 23:52:46 +01:00
2023-03-05 23:13:16 +01:00
let headerKeywordSearch = '[' + stateKeywordList.join('|') + ']'
/**
* task object example
* @type {{level: string, header: string, dates: {CREATED: string, DONE: string, REFILED: string}, state: string, content: string, properties: {}, tags: [], tagsInherited: []}}
*/
2023-03-05 11:05:06 +01:00
let task = {
2023-03-05 23:13:16 +01:00
header: "",
level: "",
content: "",
state: "",
tags: [],
tagsInherited: [],
dates: {},
logbook: {},
properties: {},
2023-03-05 11:05:06 +01:00
}
2023-03-05 23:13:16 +01:00
// init first task object as empty clone
let currentTask = {...task};
let isHeader = false;
let isProperty = false;
let isLogbook = false;
let isFirst = true;
2023-03-05 11:05:06 +01:00
2023-03-05 23:13:16 +01:00
/**********************
* loop to parse all
*********************/
2023-03-04 23:52:46 +01:00
fs.readFile(sourceFilePath, 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
console.log(" parsing...")
// parcourir chaque ligne du fichier org
let everyline = data.split('\n');
// trouver les entêtes toutes les lignes qui commencent par * et espace.
everyline.forEach((line) => {
2023-03-05 23:13:16 +01:00
// gérer la création d'objets définissant les tâches et leurs propriétés
2023-03-04 23:52:46 +01:00
if (line.match(/^\*+? /)) {
2023-03-05 11:05:06 +01:00
// add last task to export list
2023-03-05 23:13:16 +01:00
if (!isFirst) {
tasksObjectsForJsonExport.push(currentTask)
console.log('currentTask.dates', currentTask.dates)
currentTask = {...task};
} else {
isFirst = false;
}
isHeader = true;
// compter les étoiles pour trouver le niveau du header
currentTask.level = line.match(/\*/g)?.length
2023-03-05 11:05:06 +01:00
// create a new task
2023-03-05 23:13:16 +01:00
line = line.replace('*', '')
line = line.replace(stateKeywordList, [].fill('', 0, stateKeywordList.length))
2023-03-04 23:52:46 +01:00
headers.push(line)
2023-03-05 11:05:06 +01:00
currentTask.header = line;
2023-03-05 23:13:16 +01:00
stateKeywordList.forEach(keyword => {
let keywordIsFound = lineHasKeyword(line, keyword)
2023-03-05 11:05:06 +01:00
2023-03-05 23:13:16 +01:00
if (keywordIsFound) {
2023-03-05 11:05:06 +01:00
currentTask.state = keyword
}
2023-03-05 23:13:16 +01:00
})
// trouver les tags
let tagsFound = line.match(/\:(.*)\:/g)
if (tagsFound) {
tagsFound = tagsFound[0];
2023-03-05 11:05:06 +01:00
console.log('tagsFound', tagsFound)
2023-03-05 23:13:16 +01:00
tagsFound = tagsFound.split(':').filter(item => item.length)
currentTask.tags = tagsFound;
}
// fin des recherches dans la ligne de Header
} else {
isHeader = false;
}
// examen des lignes de corps de tâche, ou de corps de section suite au header.
// classer les dates de création, cloture, et de logbook
let dateFound = searchDate(line)
if(dateFound){
dateKeywordList.forEach(keyword => {
if (lineHasSubstring(line, keyword)) {
if (!currentTask.dates[keyword]) {
currentTask.dates[keyword] = '';
2023-03-05 11:05:06 +01:00
}
2023-03-05 23:13:16 +01:00
currentTask.dates[keyword] = new Date(dateFound[0]);
} else {
// console.log('keyword', keyword)
}
})
}
2023-03-05 11:05:06 +01:00
2023-03-05 23:13:16 +01:00
// ajouter le corps complet de la section après le header
if (line.length && !isHeader) {
2023-03-05 11:05:06 +01:00
2023-03-05 23:13:16 +01:00
let cleanedLine = line.replace(/\s\s/g, ' ')
cleanedLine = line.replace(/ {2,}/g, ' ')
console.log('line', cleanedLine)
currentTask.corpus += `
` + cleanedLine;
2023-03-04 23:52:46 +01:00
}
2023-03-05 23:13:16 +01:00
2023-03-04 23:52:46 +01:00
})
2023-03-05 23:13:16 +01:00
// ajouter la dernière tâche parsée
tasksObjectsForJsonExport.push(currentTask)
2023-03-04 23:52:46 +01:00
console.log('headers', headers)
console.log(" parsing fini")
2023-03-05 23:13:16 +01:00
stateKeywordList.forEach(keyword => console.log('nombre de headers', keyword, headersByKind[keyword]?.length))
2023-03-04 23:52:46 +01:00
2023-03-05 23:13:16 +01:00
const jsonContent = {
statistics: {
lines_count: everyline.length,
headers_count: headers.length,
},
meta_data: {
author: '@tykayn@mastodon.Cipherbliss.com',
generated_at: new Date(),
generated_from_file: sourceFilePath + sourceFileName,
sources: 'https://forge.chapril.org/tykayn/org-report-stats.git'
},
tasks_list: tasksObjectsForJsonExport
}
// console.log('tasksObjectsForJsonExport', jsonContent)
if (writeJsonAfterParse) {
writeJsonFile('export_' + sourceFileName + '.json', JSON.stringify(jsonContent));
}
2023-03-04 23:57:03 +01:00
2023-03-04 23:52:46 +01:00
return;
})
2023-03-05 23:13:16 +01:00
function lineHasKeyword(line, keyword = 'TODO') {
2023-03-04 23:52:46 +01:00
2023-03-05 23:13:16 +01:00
let isFound = (line.indexOf('* ' + keyword) !== -1)
if (isFound) {
createNewHeaderKind(keyword)
2023-03-04 23:52:46 +01:00
headersByKind[keyword].push(line);
}
2023-03-05 23:13:16 +01:00
return isFound;
}
function lineHasSubstring(line, keyword) {
return (line.indexOf(keyword) !== -1)
2023-03-04 23:52:46 +01:00
}
function createNewHeaderKind(keyword) {
if (!headersByKind[keyword]) {
headersByKind[keyword] = [];
}
2023-03-04 23:57:03 +01:00
}
2023-03-05 23:13:16 +01:00
/**
* chercher des dates et heures au format
* YYYY-MM-DD HH:II:SS
*
* @param line
* @returns {*}
*/
function searchDate(line) {
// return line.match(/[(\d{4}\-\d{2}\-\d{2} ?\d{2}?\:?\d{2}?\:?\d{2}?)(\d{4}\-\d{2}\-\d{2})]/)
let simpleDay = line.match(/\d{4}\-\d{2}\-\d{2} \w{3}?\.?/)
let simpleDayHour = line.match(/\d{4}\-\d{2}\-\d{2} \w{3}?\.? \d{2}\:\d{2}/)
let simpleDayHourSec = line.match(/\d{4}\-\d{2}\-\d{2} \w{3}?\.? \d{2}\:\d{2}\:\d{2}/)
if(simpleDayHourSec){
return simpleDayHourSec;
}
if(simpleDayHour){
return simpleDayHour;
}
if(simpleDay){
return simpleDay;
}
}
/**
* afin de trouver la première date liée à une tâche parmi celles mentionnées, il faut comparer les dates
* @param date1
* @param date2
*/
function compareDatesAndKeepOldest(date1, date2) {
date1 = moment(date1)
date2 = moment(date2)
}
2023-03-04 23:57:03 +01:00
2023-03-05 11:05:06 +01:00
function writeJsonFile(fileName, fileContent) {
console.log('write file ', fileName);
2023-03-05 23:13:16 +01:00
2023-03-05 11:05:06 +01:00
return fs.writeFile(
`./output/${fileName}`,
fileContent,
"utf8",
(err) => {
if (err) {
console.log(`Error writing file: ${err}`);
} else {
console.log(`File ${fileName} is written successfully!`);
}
}
);
2023-03-04 23:52:46 +01:00
}