Skip to content

feat: Detect name & version from pyproject.toml #121

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/pyright-scip/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/pyright-scip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"webpack-cli": "^4.9.1"
},
"dependencies": {
"@iarna/toml": "2.2.5",
"@iarna/toml": "^2.2.5",
"command-exists": "^1.2.9",
"commander": "^9.2.0",
"diff": "^5.0.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/pyright-scip/snapshots/input/pyproject_1/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def main():
pass

main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[project]
name = "abc"

[tool.poetry]
version = "16.05"
13 changes: 13 additions & 0 deletions packages/pyright-scip/snapshots/output/pyproject_1/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# < definition scip-python python abc 16.05 main/__init__:
#documentation (module) main

def main():
# ^^^^ definition abc 16.05 main/main().
# documentation ```python
# > def main(): # -> None:
# > ```
pass

main()
#^^^ reference abc 16.05 main/main().

4 changes: 2 additions & 2 deletions packages/pyright-scip/src/MainCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ export function mainCommand(
.argument('<path>', 'the directory containing `input` directories')
.option('--check', 'whether to update or check', false)
.option('--only <name>', 'only generate snapshots for <name>')
.requiredOption('--project-name <name>', 'the name of the current project, pypi name if applicable')
.requiredOption('--project-version <version>', 'the name of the current project, pypi name if applicable')
.option('--project-name <name>', 'the name of the current project, pypi name if applicable', '')
.option('--project-version <version>', 'the name of the current project, pypi name if applicable', '')
.option(
'--output <path>',
'Path to the output file. If this path is relative, it is interpreted relative to the value for --cwd.',
Expand Down
4 changes: 2 additions & 2 deletions packages/pyright-scip/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class ScipPyrightConfig {
pyprojectFilePath = this._findPyprojectTomlFile(projectRoot);

if (!pyprojectFilePath && !commandLineOptions.fromVsCodeExtension) {
pyprojectFilePath = this._findPyprojectTomlFileHereOrUp(projectRoot);
pyprojectFilePath = this.findPyprojectTomlFileHereOrUp(projectRoot);
}

if (pyprojectFilePath) {
Expand Down Expand Up @@ -364,7 +364,7 @@ export class ScipPyrightConfig {
return undefined;
}

private _findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined {
public findPyprojectTomlFileHereOrUp(searchPath: string): string | undefined {
return forEachAncestorDirectory(searchPath, (ancestor) => this._findPyprojectTomlFile(ancestor));
}

Expand Down
70 changes: 58 additions & 12 deletions packages/pyright-scip/src/indexer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as child_process from 'child_process';
import * as path from 'path';
import * as TOML from '@iarna/toml';
import { Event } from 'vscode-languageserver/lib/common/api';

import { Program } from 'pyright-internal/analyzer/program';
Expand Down Expand Up @@ -28,6 +29,45 @@ export class Indexer {
pyrightConfig: ConfigOptions;
projectFiles: Set<string>;

public static inferProjectInfo(
inferProjectVersionFromCommit: boolean,
getPyprojectTomlContents: () => string | undefined
): { name: string | undefined; version: string | undefined } {
let name = undefined;
let version = undefined;
try {
const pyprojectTomlContents = getPyprojectTomlContents();
if (pyprojectTomlContents) {
const tomlMap = TOML.parse(pyprojectTomlContents);
// See: https://packaging.python.org/en/latest/specifications/declaring-project-metadata/#specification
let project = tomlMap['project'] as TOML.JsonMap | undefined;
if (project) {
name = project['name'];
version = project['version'];
}
if (!name || !version) {
// See: https://python-poetry.org/docs/pyproject/
let tool = tomlMap['tool'] as TOML.JsonMap | undefined;
if (tool) {
let toolPoetry = tool['poetry'] as TOML.JsonMap | undefined;
if (toolPoetry) {
name = name ?? toolPoetry['name'];
version = version ?? toolPoetry['version'];
}
}
}
}
} catch (_) {}
name = typeof name === 'string' ? name : undefined;
version = typeof version === 'string' ? version : undefined;
if (!version && inferProjectVersionFromCommit) {
try {
version = child_process.execSync('git rev-parse HEAD').toString().trim();
} catch (_) {}
}
return { name, version };
}

constructor(public scipConfig: ScipConfig) {
this.counter = new Counter();

Expand All @@ -40,23 +80,29 @@ export class Indexer {
//
// private _getConfigOptions(host: Host, commandLineOptions: CommandLineOptions): ConfigOptions {
let fs = new PyrightFileSystem(createFromRealFileSystem());
let config = new ScipPyrightConfig(scipConfig, fs);
this.pyrightConfig = config.getConfigOptions();

if (
scipConfig.infer.projectVersionFromCommit &&
(!scipConfig.projectVersion || scipConfig.projectVersion === '')
) {
try {
scipConfig.projectVersion = child_process.execSync('git rev-parse HEAD').toString().trim();
} catch (e) {
scipConfig.projectVersion = '';
if (!scipConfig.projectName || !scipConfig.projectVersion) {
const { name, version } = Indexer.inferProjectInfo(
scipConfig.infer.projectVersionFromCommit,
(): string | undefined => {
const tomlPath = config.findPyprojectTomlFileHereOrUp(scipConfig.projectRoot);
if (tomlPath) {
return fs.readFileSync(tomlPath, 'utf8');
}
return undefined;
}
);
if (!scipConfig.projectName && name) {
scipConfig.projectName = name;
}
if (!scipConfig.projectVersion && version) {
scipConfig.projectVersion = version;
}
}

let config = new ScipPyrightConfig(scipConfig, fs);
this.pyrightConfig = config.getConfigOptions();

const matcher = new FileMatcher(this.pyrightConfig, fs);

this.projectFiles = new Set(matcher.matchFiles(this.pyrightConfig.include, this.pyrightConfig.exclude));
if (scipConfig.targetOnly) {
scipConfig.targetOnly = path.resolve(scipConfig.targetOnly);
Expand Down
110 changes: 103 additions & 7 deletions packages/pyright-scip/test/test-main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,97 @@
import { main } from '../src/main-impl';
import * as path from 'path';
import * as fs from 'fs';
import { Indexer } from '../src/indexer';

function testMain(mode: 'check' | 'update'): void {
function testPyprojectParsing() {
const testCases = [
{
expected: { name: undefined, version: undefined },
tomlContents: [
``,
`[project]`,
`[tool.poetry]`,
`[tool]
poetry = {}`,
`[tool.poetry]
name = false
version = {}`,
],
},
{
expected: { name: 'abc', version: undefined },
tomlContents: [
`[project]
name = "abc"`,
`[tool.poetry]
name = "abc"`,
`[tool]
poetry = { name = "abc" }`,
`[project]
name = "abc"
[tool.poetry]
name = "ignored"`,
],
},
{
expected: { name: undefined, version: '16.05' },
tomlContents: [
`[project]
version = "16.05"`,
`[tool.poetry]
version = "16.05"`,
`[tool]
poetry = { version = "16.05" }`,
`[project]
version = "16.05"
[tool.poetry]
version = "ignored"`,
],
},
{
expected: { name: 'abc', version: '16.05' },
tomlContents: [
`[project]
name = "abc"
version = "16.05"`,
`[tool.poetry]
name = "abc"
version = "16.05"`,
`[project]
name = "abc"
[tool.poetry]
version = "16.05"`,
`[project]
version = "16.05"
[tool.poetry]
name = "abc"`,
`[project]
[tool.poetry]
name = "abc"
version = "16.05"`,
],
},
];

for (const testCase of testCases) {
for (const content of testCase.tomlContents) {
const got = Indexer.inferProjectInfo(false, () => content);
const want = testCase.expected;
if (got.name !== want.name) {
throw `name mismatch (got: ${got.name}, expected: ${want.name}) for ${content}`;
}
if (got.version !== want.version) {
throw `version mismatch (got: ${got.version}, expected: ${want.version}) for ${content}`;
}
}
}
}

function unitTests(): void {
testPyprojectParsing();
}

function snapshotTests(mode: 'check' | 'update'): void {
const nodePath = process.argv[0];
const startCwd = process.cwd();
// Returns list of subdir names, not absolute paths.
Expand All @@ -12,8 +101,12 @@ function testMain(mode: 'check' | 'update'): void {
const packageInfo = JSON.parse(fs.readFileSync(packageInfoPath, 'utf8'));
for (const subdirName of subdirNames) {
console.assert(!subdirName.includes(path.sep));
let projectName = packageInfo['default']['name'];
let projectVersion = packageInfo['default']['version'];
let projectName = undefined;
let projectVersion = undefined;
if (!fs.existsSync(path.join(inputDir, subdirName, 'pyproject.toml'))) {
projectName = packageInfo['default']['name'];
projectVersion = packageInfo['default']['version'];
}
if (subdirName in packageInfo['special']) {
projectName = packageInfo['special'][subdirName]['name'];
projectVersion = packageInfo['special'][subdirName]['version'];
Expand All @@ -26,20 +119,23 @@ function testMain(mode: 'check' | 'update'): void {
'--environment',
'snapshots/testEnv.json',
'--quiet',
'--project-name',
projectName,
'--project-version',
projectVersion,
'--only',
subdirName,
]; // FIXME: This should pass with a --dev flag
argv.push(...(projectName ? ['--project-name', projectName] : []));
argv.push(...(projectVersion ? ['--project-version', projectVersion] : []));
if (mode === 'check') {
argv.push('--check');
}
main(argv);
}
}

function testMain(mode: 'check' | 'update'): void {
unitTests();
snapshotTests(mode);
}

if (process.argv.indexOf('--check') !== -1) {
testMain('check');
} else {
Expand Down