diff --git a/packages/gitmoji-changelog-cli/src/index.js b/packages/gitmoji-changelog-cli/src/index.js index 4ee3bd2..a51b5d8 100755 --- a/packages/gitmoji-changelog-cli/src/index.js +++ b/packages/gitmoji-changelog-cli/src/index.js @@ -47,7 +47,7 @@ yargs }, execute('update')) .option('format', { default: 'markdown', desc: 'changelog format (markdown, json)' }) - .option('preset', { default: 'node', desc: 'define preset mode', choices: ['node', 'generic', 'maven', 'cargo', 'helm'] }) + .option('preset', { default: 'node', desc: 'define preset mode', choices: ['node', 'generic', 'maven', 'cargo', 'helm', 'python'] }) .option('output', { desc: 'output changelog file' }) .option('group-similar-commits', { desc: '[⚗️ - beta] try to group similar commits', default: false }) .option('author', { default: false, desc: 'add the author in changelog lines' }) diff --git a/packages/gitmoji-changelog-cli/src/presets/python.js b/packages/gitmoji-changelog-cli/src/presets/python.js new file mode 100644 index 0000000..7aed44c --- /dev/null +++ b/packages/gitmoji-changelog-cli/src/presets/python.js @@ -0,0 +1,68 @@ +const toml = require('toml') +const fs = require('fs') + +module.exports = async () => { + try { + const pyprojectPromise = new Promise((resolve, reject) => { + try { + resolve(toml.parse(fs.readFileSync('pyproject.toml', 'utf-8'))) + } catch (err) { + reject(err) + } + }) + + const projectFile = await pyprojectPromise + const name = recursiveKeySearch('name', projectFile)[0] + const version = recursiveKeySearch('version', projectFile)[0] + let description = recursiveKeySearch('description', projectFile)[0] + + if (!name) { + throw new Error('Could not find name metadata in pyproject.toml') + } + if (!version) { + throw new Error('Could not find version metadata in pyproject.toml') + } + if (!description) { + description = '' + } + + return { + name, + version, + description, + } + } catch (e) { + return null + } +} + + +function recursiveKeySearch(key, data) { + // https://codereview.stackexchange.com/a/143914 + if (data === null) { + return [] + } + + if (data !== Object(data)) { + return [] + } + + let results = [] + + if (data.constructor === Array) { + for (let i = 0, len = data.length; i < len; i += 1) { + results = results.concat(recursiveKeySearch(key, data[i])) + } + return results + } + + for (let i = 0; i < Object.keys(data).length; i += 1) { + const dataKey = Object.keys(data)[i] + if (key === dataKey) { + results.push(data[key]) + } + results = results.concat(recursiveKeySearch(key, data[dataKey])) + } + + return results +} diff --git a/packages/gitmoji-changelog-cli/src/presets/python.spec.js b/packages/gitmoji-changelog-cli/src/presets/python.spec.js new file mode 100644 index 0000000..797aa17 --- /dev/null +++ b/packages/gitmoji-changelog-cli/src/presets/python.spec.js @@ -0,0 +1,137 @@ +const fs = require('fs') + +const loadProjectInfo = require('./python.js') + +describe('getPackageInfo', () => { + it('should extract metadata from a pyproject.toml made by poetry', async () =>{ + // Note the TOML section is distinct for poetry + fs.readFileSync.mockReturnValue(` + [tool.poetry] + name = "poetry-package-name" + version = "0.1.0" + description = "Description of the poetry package" + `) + + const result = await loadProjectInfo() + + expect(result).toEqual({ + name: 'poetry-package-name', + version: '0.1.0', + description: 'Description of the poetry package', + }) + }) + + it('should extract metadata from the PEP621 example pyproject.toml', async () =>{ + // [project] is the usual TOML section for the metadata + fs.readFileSync.mockReturnValue(` + [project] + name = "spam" + version = "2020.0.0" + description = "Lovely Spam! Wonderful Spam!" + readme = "README.rst" + requires-python = ">=3.8" + license = {file = "LICENSE.txt"} + keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"] + authors = [ + {email = "hi@pradyunsg.me"}, + {name = "Tzu-Ping Chung"} + ] + maintainers = [ + {name = "Brett Cannon", email = "brett@python.org"} + ] + classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python" + ] + + dependencies = [ + "httpx", + "gidgethub[httpx]>4.0.0", + "django>2.1; os_name != 'nt'", + "django>2.0; os_name == 'nt'" + ] + + [project.optional-dependencies] + test = [ + "pytest < 5.0.0", + "pytest-cov[all]" + ] + + [project.urls] + homepage = "example.com" + documentation = "readthedocs.org" + repository = "github.com" + changelog = "github.com/me/spam/blob/master/CHANGELOG.md" + + [project.scripts] + spam-cli = "spam:main_cli" + + [project.gui-scripts] + spam-gui = "spam:main_gui" + + [project.entry-points."spam.magical"] + tomatoes = "spam:main_tomatoes" + `) + + const result = await loadProjectInfo() + + expect(result).toEqual({ + name: 'spam', + version: '2020.0.0', + description: 'Lovely Spam! Wonderful Spam!', + }) + }) + + it('should extract metadata despite a missing description', async () =>{ + // The description metadata is optional. + fs.readFileSync.mockReturnValue(` + [project] + name = "no-description" + version = "0.0.1" + readme = "README.rst" + `) + + const result = await loadProjectInfo() + + expect(result).toEqual({ + name: 'no-description', + version: '0.0.1', + description: '', + }) + }) + + it('should use the first metadata value found from the top', async () =>{ + // Only the first occurance of the expected key names are taken. + fs.readFileSync.mockReturnValue(` + [other.section] + somebody = "once told me the" + world = "is gonna roll me" + + [project] + name = "project-1" + version = "0.0.1" + description = "Project 1 Description" + + [tool.poetry] + name = "project-2" + version = "0.0.2" + description = "Project 2 Description" + + [tool.something.else] + name = "project-3" + version = "0.0.3" + description = "Project 3 Description" + `) + + const result = await loadProjectInfo() + + expect(result).toEqual({ + name: 'project-1', + version: '0.0.1', + description: 'Project 1 Description', + }) + }) +}) + + +jest.mock('fs') diff --git a/packages/gitmoji-changelog-documentation/README.md b/packages/gitmoji-changelog-documentation/README.md index 1ef19f6..39f035e 100644 --- a/packages/gitmoji-changelog-documentation/README.md +++ b/packages/gitmoji-changelog-documentation/README.md @@ -165,6 +165,16 @@ The helm preset looks for 3 properties in your `Chart.yaml`: - version - description +#### Python + +The python preset looks for 3 properties in your `pyproject.toml`: + +- name +- version +- description + +(The value taken is the first one found in your `pyproject.toml` that matches the expected key name given above.) + ### Add a preset A preset need to export a function. When called this function must return three mandatory information about the project in which the cli has been called. The name of the project, a short description of it and its current version.