mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-18 01:25:37 -05:00
Creating a new database project, project items
* can create, open, and close sqlproj files * can add sql objects to projects
This commit is contained in:
@@ -13,13 +13,21 @@ import { SqlConnectionDataSource } from './sqlConnectionStringSource';
|
||||
export abstract class DataSource {
|
||||
public name: string;
|
||||
public abstract get type(): string;
|
||||
public abstract get friendlyName(): string;
|
||||
public abstract get typeFriendlyName(): string;
|
||||
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
export class NoDataSourcesFileError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = NoDataSourcesFileError.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parses the specified file to load DataSource objects
|
||||
*/
|
||||
@@ -30,7 +38,8 @@ export async function load(dataSourcesFilePath: string): Promise<DataSource[]> {
|
||||
fileContents = await fs.readFile(dataSourcesFilePath);
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(constants.noDataSourcesFile);
|
||||
// TODO: differentiate between file not existing and other types of failures; need to know whether to prompt to create new
|
||||
throw new NoDataSourcesFileError(constants.noDataSourcesFile);
|
||||
}
|
||||
|
||||
const rawJsonContents = JSON.parse(fileContents.toString());
|
||||
|
||||
@@ -21,7 +21,7 @@ export class SqlConnectionDataSource extends DataSource {
|
||||
return SqlConnectionDataSource.type;
|
||||
}
|
||||
|
||||
public get friendlyName(): string {
|
||||
public get typeFriendlyName(): string {
|
||||
return constants.sqlConnectionStringFriendly;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ export class SqlConnectionDataSource extends DataSource {
|
||||
}
|
||||
}
|
||||
|
||||
public getSetting(settingName: string): string {
|
||||
return this.connectionStringComponents[settingName];
|
||||
}
|
||||
|
||||
public static fromJson(json: DataSourceJson): SqlConnectionDataSource {
|
||||
return new SqlConnectionDataSource(json.name, (json.data as unknown as SqlConnectionDataSourceJson).connectionString);
|
||||
}
|
||||
|
||||
@@ -4,39 +4,40 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as xml2js from 'xml2js';
|
||||
import * as path from 'path';
|
||||
import * as xmldom from 'xmldom';
|
||||
import * as constants from '../common/constants';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { DataSource } from './dataSources/dataSources';
|
||||
import { getErrorMessage } from '../common/utils';
|
||||
|
||||
/**
|
||||
* Class representing a Project, and providing functions for operating on it
|
||||
*/
|
||||
export class Project {
|
||||
public projectFile: string;
|
||||
public projectFilePath: string;
|
||||
public files: ProjectEntry[] = [];
|
||||
public dataSources: DataSource[] = [];
|
||||
|
||||
public get projectFolderPath() {
|
||||
return path.dirname(this.projectFilePath);
|
||||
}
|
||||
|
||||
private projFileXmlDoc: any = undefined;
|
||||
|
||||
constructor(projectFilePath: string) {
|
||||
this.projectFile = projectFilePath;
|
||||
this.projectFilePath = projectFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the project setting and contents from the file
|
||||
*/
|
||||
public async readProjFile() {
|
||||
let projFileContents = await fs.readFile(this.projectFile);
|
||||
|
||||
const parser = new xml2js.Parser({
|
||||
explicitArray: true,
|
||||
explicitCharkey: false,
|
||||
explicitRoot: false
|
||||
});
|
||||
|
||||
let result;
|
||||
const projFileText = await fs.readFile(this.projectFilePath);
|
||||
|
||||
try {
|
||||
result = await parser.parseStringPromise(projFileContents.toString());
|
||||
this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString());
|
||||
}
|
||||
catch (err) {
|
||||
vscode.window.showErrorMessage(err);
|
||||
@@ -45,23 +46,115 @@ export class Project {
|
||||
|
||||
// find all folders and files to include
|
||||
|
||||
for (const itemGroup of result['ItemGroup']) {
|
||||
if (itemGroup['Build'] !== undefined) {
|
||||
for (const fileEntry of itemGroup['Build']) {
|
||||
this.files.push(this.createProjectEntry(fileEntry.$['Include'], EntryType.File));
|
||||
}
|
||||
for (let ig = 0; ig < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; ig++) {
|
||||
const itemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[ig];
|
||||
|
||||
for (let b = 0; b < itemGroup.getElementsByTagName(constants.Build).length; b++) {
|
||||
this.files.push(this.createProjectEntry(itemGroup.getElementsByTagName(constants.Build)[b].getAttribute(constants.Include), EntryType.File));
|
||||
}
|
||||
|
||||
if (itemGroup['Folder'] !== undefined) {
|
||||
for (const folderEntry of itemGroup['Folder']) {
|
||||
this.files.push(this.createProjectEntry(folderEntry.$['Include'], EntryType.Folder));
|
||||
}
|
||||
for (let f = 0; f < itemGroup.getElementsByTagName(constants.Folder).length; f++) {
|
||||
this.files.push(this.createProjectEntry(itemGroup.getElementsByTagName(constants.Folder)[f].getAttribute(constants.Include), EntryType.Folder));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a folder to the project, and saves the project file
|
||||
* @param relativeFolderPath Relative path of the folder
|
||||
*/
|
||||
public async addFolderItem(relativeFolderPath: string): Promise<ProjectEntry> {
|
||||
const absoluteFolderPath = path.join(this.projectFolderPath, relativeFolderPath);
|
||||
await fs.mkdir(absoluteFolderPath, { recursive: true });
|
||||
|
||||
const folderEntry = this.createProjectEntry(relativeFolderPath, EntryType.Folder);
|
||||
this.files.push(folderEntry);
|
||||
|
||||
await this.addToProjFile(folderEntry);
|
||||
return folderEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a file to disk, adds that file to the project, and writes it to disk
|
||||
* @param relativeFilePath Relative path of the file
|
||||
* @param contents Contents to be written to the new file
|
||||
*/
|
||||
public async addScriptItem(relativeFilePath: string, contents: string): Promise<ProjectEntry> {
|
||||
const absoluteFilePath = path.join(this.projectFolderPath, relativeFilePath);
|
||||
await fs.mkdir(path.dirname(absoluteFilePath), { recursive: true });
|
||||
await fs.writeFile(absoluteFilePath, contents);
|
||||
|
||||
const fileEntry = this.createProjectEntry(relativeFilePath, EntryType.File);
|
||||
this.files.push(fileEntry);
|
||||
|
||||
await this.addToProjFile(fileEntry);
|
||||
|
||||
return fileEntry;
|
||||
}
|
||||
|
||||
private createProjectEntry(relativePath: string, entryType: EntryType): ProjectEntry {
|
||||
return new ProjectEntry(vscode.Uri.file(path.join(this.projectFile, relativePath)), entryType);
|
||||
return new ProjectEntry(vscode.Uri.file(path.join(this.projectFolderPath, relativePath)), relativePath, entryType);
|
||||
}
|
||||
|
||||
private findOrCreateItemGroup(containedTag?: string): any {
|
||||
let outputItemGroup = undefined;
|
||||
|
||||
// find any ItemGroup node that contains files; that's where we'll add
|
||||
for (let i = 0; i < this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup).length; i++) {
|
||||
const currentItemGroup = this.projFileXmlDoc.documentElement.getElementsByTagName(constants.ItemGroup)[i];
|
||||
|
||||
// if we're not hunting for a particular child type, or if we are and we find it, use the ItemGroup
|
||||
if (!containedTag || currentItemGroup.getElementsByTagName(containedTag).length > 0) {
|
||||
outputItemGroup = currentItemGroup;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if none already exist, make a new ItemGroup for it
|
||||
if (!outputItemGroup) {
|
||||
outputItemGroup = this.projFileXmlDoc.createElement(constants.ItemGroup);
|
||||
this.projFileXmlDoc.documentElement.appendChild(outputItemGroup);
|
||||
}
|
||||
|
||||
return outputItemGroup;
|
||||
}
|
||||
|
||||
private addFileToProjFile(path: string) {
|
||||
const newFileNode = this.projFileXmlDoc.createElement(constants.Build);
|
||||
newFileNode.setAttribute(constants.Include, path);
|
||||
|
||||
this.findOrCreateItemGroup(constants.Build).appendChild(newFileNode);
|
||||
}
|
||||
|
||||
private addFolderToProjFile(path: string) {
|
||||
const newFolderNode = this.projFileXmlDoc.createElement(constants.Folder);
|
||||
newFolderNode.setAttribute(constants.Include, path);
|
||||
|
||||
this.findOrCreateItemGroup(constants.Folder).appendChild(newFolderNode);
|
||||
}
|
||||
|
||||
private async addToProjFile(entry: ProjectEntry) {
|
||||
try {
|
||||
switch (entry.type) {
|
||||
case EntryType.File:
|
||||
this.addFileToProjFile(entry.relativePath);
|
||||
break;
|
||||
case EntryType.Folder:
|
||||
this.addFolderToProjFile(entry.relativePath);
|
||||
}
|
||||
|
||||
await this.serializeToProjFile(this.projFileXmlDoc);
|
||||
}
|
||||
catch (err) {
|
||||
vscode.window.showErrorMessage(getErrorMessage(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async serializeToProjFile(projFileContents: any) {
|
||||
const xml = new xmldom.XMLSerializer().serializeToString(projFileContents); // TODO: how to get this to serialize with "pretty" formatting
|
||||
|
||||
await fs.writeFile(this.projectFilePath, xml);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +165,18 @@ export class ProjectEntry {
|
||||
/**
|
||||
* Absolute file system URI
|
||||
*/
|
||||
uri: vscode.Uri;
|
||||
fsUri: vscode.Uri;
|
||||
relativePath: string;
|
||||
type: EntryType;
|
||||
|
||||
constructor(uri: vscode.Uri, type: EntryType) {
|
||||
this.uri = uri;
|
||||
constructor(uri: vscode.Uri, relativePath: string, type: EntryType) {
|
||||
this.fsUri = uri;
|
||||
this.relativePath = relativePath;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.uri.path;
|
||||
return this.fsUri.path;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,3 +52,5 @@ export class MessageTreeItem extends BaseProjectTreeItem {
|
||||
return new vscode.TreeItem(this.message, vscode.TreeItemCollapsibleState.None);
|
||||
}
|
||||
}
|
||||
|
||||
export const SpacerTreeItem = new MessageTreeItem('');
|
||||
|
||||
@@ -53,7 +53,7 @@ export class SqlConnectionDataSourceTreeItem extends DataSourceTreeItem {
|
||||
|
||||
public get treeItem(): vscode.TreeItem {
|
||||
let item = new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.Collapsed);
|
||||
item.label = `${this.dataSource.name} (${this.dataSource.friendlyName})`;
|
||||
item.label = `${this.dataSource.name} (${this.dataSource.typeFriendlyName})`;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,17 @@ export class FileNode extends BaseProjectTreeItem {
|
||||
}
|
||||
|
||||
public get treeItem(): vscode.TreeItem {
|
||||
return new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.None);
|
||||
const treeItem = new vscode.TreeItem(this.uri, vscode.TreeItemCollapsibleState.None);
|
||||
|
||||
treeItem.command = {
|
||||
title: 'Open file',
|
||||
command: 'vscode.open',
|
||||
arguments: [this.fileSystemUri]
|
||||
};
|
||||
|
||||
treeItem.contextValue = 'File';
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +68,7 @@ export class FileNode extends BaseProjectTreeItem {
|
||||
* Converts a full filesystem URI to a project-relative URI that's compatible with the project tree
|
||||
*/
|
||||
function fsPathToProjectUri(fileSystemUri: vscode.Uri, projectNode: ProjectRootTreeItem): vscode.Uri {
|
||||
const projBaseDir = path.dirname(projectNode.project.projectFile);
|
||||
const projBaseDir = path.dirname(projectNode.project.projectFilePath);
|
||||
let localUri = '';
|
||||
|
||||
if (fileSystemUri.fsPath.startsWith(projBaseDir)) {
|
||||
|
||||
@@ -20,7 +20,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||
project: Project;
|
||||
|
||||
constructor(project: Project) {
|
||||
super(vscode.Uri.parse(path.basename(project.projectFile)), undefined);
|
||||
super(vscode.Uri.parse(path.basename(project.projectFilePath)), undefined);
|
||||
|
||||
this.project = project;
|
||||
this.dataSourceNode = new DataSourcesTreeItem(this);
|
||||
@@ -57,16 +57,16 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||
|
||||
switch (entry.type) {
|
||||
case EntryType.File:
|
||||
newNode = new fileTree.FileNode(entry.uri, parentNode);
|
||||
newNode = new fileTree.FileNode(entry.fsUri, parentNode);
|
||||
break;
|
||||
case EntryType.Folder:
|
||||
newNode = new fileTree.FolderNode(entry.uri, parentNode);
|
||||
newNode = new fileTree.FolderNode(entry.fsUri, parentNode);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown EntryType: '${entry.type}'`);
|
||||
}
|
||||
|
||||
parentNode.fileChildren[path.basename(entry.uri.path)] = newNode;
|
||||
parentNode.fileChildren[path.basename(entry.fsUri.path)] = newNode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||
* Gets the immediate parent tree node for an entry in a project file
|
||||
*/
|
||||
private getEntryParentNode(entry: ProjectEntry): fileTree.FolderNode | ProjectRootTreeItem {
|
||||
const relativePathParts = utils.trimChars(utils.trimUri(vscode.Uri.file(this.project.projectFile), entry.uri), '/').split('/').slice(0, -1); // remove the last part because we only care about the parent
|
||||
const relativePathParts = utils.trimChars(utils.trimUri(vscode.Uri.file(this.project.projectFilePath), entry.fsUri), '/').split('/').slice(0, -1); // remove the last part because we only care about the parent
|
||||
|
||||
if (relativePathParts.length === 0) {
|
||||
return this; // if nothing left after trimming the entry itself, must been root
|
||||
@@ -84,7 +84,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
|
||||
|
||||
for (const part of relativePathParts) {
|
||||
if (current.fileChildren[part] === undefined) {
|
||||
current.fileChildren[part] = new fileTree.FolderNode(vscode.Uri.file(path.join(path.dirname(this.project.projectFile), part)), current);
|
||||
current.fileChildren[part] = new fileTree.FolderNode(vscode.Uri.file(path.join(path.dirname(this.project.projectFilePath), part)), current);
|
||||
}
|
||||
|
||||
if (current.fileChildren[part] instanceof fileTree.FileNode) {
|
||||
|
||||
Reference in New Issue
Block a user