llm blogging | | parse patch file | Search

The blogAboutCode function generates a blog post about a project's code history, using a specified timeframe and model. It retrieves the commit history, extracts commits, and logs the first and last commit hashes.

Run example

npm run import -- "blog about code"

blog about code

const path = require('path')
const fs = require('fs')
const {Remarkable} = require('remarkable');
const md = new Remarkable({html: true, xhtmlOut: true, breaks: true});
const selectModel = importer.import("select llm")
const {spawnSync} = require('child_process')
const parsePatch = importer.import("parse patch file")
const {safeurl} = importer.import("domain cache tools")

const PROFILE_PATH = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE || '';
const PROJECT_PATH = path.join(__dirname, '..', 'Resources', 'Projects', 'code-summaries')


async function blogAboutCode(project, timeframe = 3, promptModel = 'Meta') {

  if(typeof project == 'undefined') {
    project = path.basename(path.resolve(path.join(__dirname, '..')))
  }

  if(typeof promptModel != 'function') {
    promptModel = await selectModel(promptModel)
  }

  // TODO: local pull
  if(!fs.existsSync(project)
    && fs.existsSync(path.join(PROFILE_PATH, project))) {
      project = path.join(PROFILE_PATH, project)
  }

  if(project.includes('://')
    && fs.existsSync(path.basename(project).replace('.git', ''))
  ) {
    project = path.join(PROFILE_PATH, path.basename(project).replace('.git', ''))
  }

  if(!fs.existsSync(project)) {
    console.error('No project')
    return
  }


  let result = await spawnSync('git', ['log', '--since=' + timeframe + '.days'], {
    cwd: project,
    timeout: 3000,
    stdio: ['pipe', 'pipe', 'pipe']
  })

  let commits = result.stdout.toString()

  //console.log(commits)
  const matches = [...commits.matchAll(/commit ([a-z0-9]+)/gi)]
  let first = matches[0]
  let last = matches[matches.length - 1]

  if(!first || !last) {
    console.error('No commits!')
    return
  }

  console.log(first[1], last[1])

  if(first == last) {
    result = await spawnSync('git', ['diff', '-U5', first[1]], {
      cwd: project,
      timeout: 3000,
      stdio: ['pipe', 'pipe', 'pipe']
    })
  } else {
    result = await spawnSync('git', ['diff', '-U5', last[1], first[1]], {
      cwd: project,
      timeout: 3000,
      stdio: ['pipe', 'pipe', 'pipe']
    })
  }

  let codePatch = result.stdout.toString()

  summary = parsePatch(codePatch)

  summaryOutputs = []
  let files = Object.keys(summary.files)
  for(let i = 0; i < files.length; i++) {
    if(files[i].match('cache')) {
      continue
    }
    console.log('Working on: ' + files[i])
    let q1 = 'Summerize this code like you\'re writing for a fun coding blog, include code excerpts as context:\n'
      + files[i] + '\nAdditions that were made:\n' + summary.files[files[i]].additions.join('')
      + '\nDeletions that were made:\n' + summary.files[files[i]].deletions.join('')
    console.log('User: ' + q1)
    let a1 = await promptModel(q1)
    console.log('AI: ' + a1)
    summaryOutputs.push('#' + files[i] + '\n\n' + a1)
  }

  const mdHtml = md.render(summaryOutputs.join('\n'))
  let now = new Date()
  const filename = path.join(PROJECT_PATH, safeurl(path.basename(project) + now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate()) + '.html')
  fs.writeFileSync(filename, mdHtml)
  return mdHtml
}


module.exports = blogAboutCode

What the code could have been:

```javascript
const fs = require('fs');
const path = require('path');
const { Remarkable } = require('remarkable');
const { safeurl } = require('./domain-cache-tools');
const { selectModel, importer } = require('./llm-importer');
const { parsePatch } = require('./parse-patch-file');
const { spawnSync } = require('child_process');

const PROJECT_PATH = (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) || '';
const DEFAULT_PROJECT_PATH = path.join(PROJECT_PATH, 'Resources', 'Projects', 'code-summaries');

const defaultProject = (project = path.basename(path.resolve(path.join(process.cwd(), '..')))) => {
  if (!fs.existsSync(project) && fs.existsSync(path.join(PROFILE_PATH, project))) {
    return path.join(PROFILE_PATH, project);
  }
  if (project.includes('://') && fs.existsSync(path.basename(project).replace('.git', ''))) {
    return path.join(PROFILE_PATH, path.basename(project).replace('.git', ''));
  }
  return project;
};

const getCommits = async (project, timeframe = 3) => {
  const result = await spawnSync('git', ['log', '--since=' + timeframe + '.days'], {
    cwd: project,
    timeout: 3000,
    stdio: ['pipe', 'pipe', 'pipe']
  });
  const commits = result.stdout.toString();
  const matches = [...commits.matchAll(/commit ([a-z0-9]+)/gi)];
  return matches;
};

const getDiff = async (first, last, project) => {
  if (first === last) {
    return await spawnSync('git', ['diff', '-U5', first[1]], {
      cwd: project,
      timeout: 3000,
      stdio: ['pipe', 'pipe', 'pipe']
    });
  }
  return await spawnSync('git', ['diff', '-U5', last[1], first[1]], {
    cwd: project,
    timeout: 3000,
    stdio: ['pipe', 'pipe', 'pipe']
  });
};

const processFiles = async (files, project, promptModel) => {
  const summaryOutputs = [];
  for (const file of files) {
    if (file.match('cache')) {
      continue;
    }
    const q1 = `Summerize this code like you're writing for a fun coding blog, include code excerpts as context: 
    ${file}
    Additions that were made: 
    ${summary.files[file].additions.join('')}
    Deletions that were made:
    ${summary.files[file].deletions.join('')}
    `;
    const a1 = await promptModel(q1);
    summaryOutputs.push(`# ${file}\n\n${a1}`);
  }
  return summaryOutputs;
};

const blogAboutCode = async (project, timeframe = 3, promptModel = 'Meta') => {
  project = defaultProject(project);
  if (!fs.existsSync(project)) {
    console.error('No project');
    return;
  }

  const commits = await getCommits(project, timeframe);
  if (!commits.length) {
    console.error('No commits!');
    return;
  }

  console.log(commits[0][1], commits[commits.length - 1][1]);

  const result = await getDiff(commits[0], commits[commits.length - 1], project);
  const codePatch = result.stdout.toString();
  const summary = parsePatch(codePatch);
  const summaryOutputs = await processFiles(Object.keys(summary.files), project, promptModel);

  const md = new Remarkable({ html: true, xhtmlOut: true, breaks: true });
  const mdHtml = md.render(summaryOutputs.join('\n'));
  const now = new Date();
  const filename = path.join(DEFAULT_PROJECT_PATH, safeurl(path.basename(project) + now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate()) + '.html');
  fs.writeFileSync(filename, mdHtml);
  return mdHtml;
};

module.exports = blogAboutCode;
```

Function Overview

blogAboutCode is an asynchronous function that generates blog posts about a given project's code history.

Parameters

Function Flow

  1. Project Path Resolution: Determines the project path by checking the current directory, user profile, and environment variables.
  2. Local Project Path: If the project path is not local, it tries to find a local clone of the repository.
  3. Git Log Generation: Runs git log to retrieve the commit history of the project, filtered by the specified timeframe.
  4. Commit Extraction: Extracts the first and last commit hashes from the log output.
  5. Diff Generation: Runs git diff to retrieve the changes between the first and last commits (if they are different).
  6. Commit Hashes Output: Logs the first and last commit hashes.

Error Handling

Notes