Skip to content

Commit 2a0c4ac

Browse files
committed
new package support for new project
1 parent 633fc62 commit 2a0c4ac

11 files changed

Lines changed: 238 additions & 5 deletions

File tree

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { window, extensions } from 'vscode';
2+
import * as fs from 'fs-extra';
3+
import * as path from 'path';
4+
5+
/**
6+
* Prompts the user to choose whether to create a new venv for a package.
7+
* Returns true if the user selects 'Yes', false if 'No', and undefined if cancelled.
8+
*/
9+
export async function promptForVenv(): Promise<boolean | undefined> {
10+
const venvChoice = await window.showQuickPick(['Yes', 'No'], {
11+
placeHolder: 'Would you like to create a new venv for this package?',
12+
ignoreFocusOut: true,
13+
});
14+
if (!venvChoice) {
15+
return undefined;
16+
}
17+
return venvChoice === 'Yes';
18+
}
19+
20+
/**
21+
* Returns true if GitHub Copilot extension is installed, false otherwise.
22+
*/
23+
export function isCopilotInstalled(): boolean {
24+
return !!extensions.getExtension('GitHub.copilot');
25+
}
26+
27+
/**
28+
* Prompts the user to choose whether to create a Copilot instructions file, only if Copilot is installed.
29+
* Returns true if the user selects 'Yes', false if 'No', and undefined if cancelled or Copilot not installed.
30+
*/
31+
export async function promptForCopilotInstructions(): Promise<boolean | undefined> {
32+
if (!isCopilotInstalled()) {
33+
return undefined;
34+
}
35+
const copilotChoice = await window.showQuickPick(['Yes', 'No'], {
36+
placeHolder: 'Would you like to create a Copilot instructions file?',
37+
ignoreFocusOut: true,
38+
});
39+
if (!copilotChoice) {
40+
return undefined;
41+
}
42+
return copilotChoice === 'Yes';
43+
}
44+
45+
export async function removeCopilotInstructions(destFolder: string) {
46+
const copilotFolder = path.join(destFolder, '.github');
47+
if (await fs.pathExists(copilotFolder)) {
48+
await fs.remove(copilotFolder);
49+
}
50+
}
51+
52+
export async function quickCreateNewVenv(destFolder: string) {
53+
const venvPath = path.join(destFolder, '.venv');
54+
try {
55+
// Placeholder: replace with your venv creation logic
56+
// await quickCreateVenv(...)
57+
window.showInformationMessage(`(Placeholder) Would create venv at: ${venvPath}`);
58+
} catch (err) {
59+
window.showErrorMessage(`Failed to create virtual environment: ${err}`);
60+
}
61+
}
62+
63+
// Helper to recursively replace all occurrences of a string in file/folder names and file contents
64+
export async function replaceInFilesAndNames(dir: string, searchValue: string, replaceValue: string) {
65+
const entries = await fs.readdir(dir, { withFileTypes: true });
66+
for (const entry of entries) {
67+
let entryName = entry.name;
68+
let fullPath = path.join(dir, entryName);
69+
let newFullPath = fullPath;
70+
// If the file or folder name contains searchValue, rename it
71+
if (entryName.includes(searchValue)) {
72+
const newName = entryName.replace(new RegExp(searchValue, 'g'), replaceValue);
73+
newFullPath = path.join(dir, newName);
74+
await fs.rename(fullPath, newFullPath);
75+
entryName = newName;
76+
}
77+
if (entry.isDirectory()) {
78+
await replaceInFilesAndNames(newFullPath, searchValue, replaceValue);
79+
} else {
80+
let content = await fs.readFile(newFullPath, 'utf8');
81+
if (content.includes(searchValue)) {
82+
content = content.replace(new RegExp(searchValue, 'g'), replaceValue);
83+
await fs.writeFile(newFullPath, content, 'utf8');
84+
}
85+
}
86+
}
87+
}
Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
1-
import { MarkdownString, window } from 'vscode';
1+
import * as fs from 'fs-extra';
2+
import * as path from 'path';
3+
import { Uri, workspace, MarkdownString, window } from 'vscode';
24
import { PythonProject, PythonProjectCreator, PythonProjectCreatorOptions } from '../../api';
3-
// import { runInBackground } from '../execution/runInBackground';
5+
import {
6+
promptForVenv,
7+
promptForCopilotInstructions,
8+
isCopilotInstalled,
9+
quickCreateNewVenv,
10+
removeCopilotInstructions,
11+
replaceInFilesAndNames,
12+
} from './creationHelpers';
13+
import { EXTENSION_ROOT_DIR } from '../../common/constants';
414

515
export class NewPackageProject implements PythonProjectCreator {
616
public readonly name = 'newPackage';
@@ -11,8 +21,75 @@ export class NewPackageProject implements PythonProjectCreator {
1121
constructor() {}
1222

1323
async create(_options?: PythonProjectCreatorOptions): Promise<PythonProject | undefined> {
14-
// show notification that the package creation was selected than return undefined
15-
window.showInformationMessage('Creating a new Python package...');
16-
return undefined;
24+
// Prompt for package name
25+
const packageName = await window.showInputBox({
26+
prompt: 'What is the name of the package? (e.g. my_package)',
27+
ignoreFocusOut: true,
28+
});
29+
if (!packageName) {
30+
return undefined;
31+
}
32+
33+
// Use helper for venv
34+
const createVenv = await promptForVenv();
35+
if (createVenv === undefined) {
36+
return undefined;
37+
}
38+
39+
// Only prompt for Copilot instructions if Copilot is installed
40+
let createCopilotInstructions = false;
41+
if (isCopilotInstalled()) {
42+
const copilotResult = await promptForCopilotInstructions();
43+
if (copilotResult === undefined) {
44+
return undefined;
45+
}
46+
createCopilotInstructions = copilotResult === true;
47+
}
48+
49+
window.showInformationMessage(
50+
`Creating a new Python project: ${packageName}\nvenv: ${createVenv}\nCopilot instructions: ${createCopilotInstructions}`,
51+
);
52+
53+
// 1. Copy template folder
54+
const templateFolder = path.join(
55+
EXTENSION_ROOT_DIR,
56+
'src',
57+
'features',
58+
'creators',
59+
'templates',
60+
'newPackageTemplate',
61+
);
62+
if (!(await fs.pathExists(templateFolder))) {
63+
window.showErrorMessage('Template folder does not exist.');
64+
return undefined;
65+
// might need another check or error handling here
66+
}
67+
const workspaceFolders = workspace.workspaceFolders;
68+
if (!workspaceFolders || workspaceFolders.length === 0) {
69+
window.showErrorMessage('No workspace folder is open.');
70+
return undefined;
71+
}
72+
const destRoot = workspaceFolders[0].uri.fsPath;
73+
const destFolder = path.join(destRoot, `${packageName}_project`);
74+
await fs.copy(templateFolder, destFolder);
75+
76+
// 2. Replace <package_name> in all files and file/folder names using helper
77+
await replaceInFilesAndNames(destFolder, 'package_name', packageName);
78+
79+
// 3. Remove Copilot instructions folder if needed
80+
if (!createCopilotInstructions) {
81+
await removeCopilotInstructions(destFolder);
82+
}
83+
84+
// 4. Create virtual environment if requested
85+
if (createVenv) {
86+
await quickCreateNewVenv(destFolder);
87+
}
88+
89+
// Return a PythonProject object if needed by your API
90+
return {
91+
name: packageName,
92+
uri: Uri.file(destFolder),
93+
};
1794
}
1895
}

src/features/creators/templates/newPackageTemplate/.github/copilot-instructions.md

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Environments
2+
.env
3+
.venv
4+
env/
5+
venv/
6+
ENV/
7+
env.bak/
8+
venv.bak/
9+
10+
# Byte-compiled / optimized / DLL files
11+
__pycache__/
12+
13+
# Distribution / packaging
14+
.Python
15+
build/
16+
develop-eggs/
17+
dist/
18+
downloads/
19+
eggs/
20+
.eggs/
21+
lib/
22+
lib64/
23+
parts/
24+
sdist/
25+
var/
26+
wheels/
27+
*.egg-info/
28+
.installed.cfg
29+
*.egg
30+
MANIFEST
31+
32+
33+
.pytest_cache/
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"tests"
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.pytestEnabled": true
7+
}

src/features/creators/templates/newPackageTemplate/README.md

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pytest
2+
setuptools
3+
wheel

src/features/creators/templates/newPackageTemplate/package_name/__init__.py

Whitespace-only changes.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
def main() -> None:
2+
print("Start coding in Python today!")
3+
4+
5+
main()
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[project]
2+
name = "Package Template"
3+
version = "1.0.0"
4+
description = "A Python package template."
5+
readme = "README.md"
6+
7+
requires-python = ">=3.8"
8+
9+
[project.optional-dependencies]
10+
dev-requirements = {file = "dev-requirements.txt"}
11+
12+
[build-system]
13+
requires = ["setuptools", "wheel"]
14+
build-backend = "setuptools.build_meta"
15+
16+
17+
18+
19+
20+
21+

0 commit comments

Comments
 (0)