mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-16 09:35:36 -05:00
* swapping vscode calls for apiwrapper for testability * Adding mainController tests * Adding unit tests for input validation * Adding project controller tests, reorganizing error handling * Removing commented-out code
173 lines
5.7 KiB
TypeScript
173 lines
5.7 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as path from 'path';
|
|
import * as xmldom from 'xmldom';
|
|
import * as constants from '../common/constants';
|
|
|
|
import { Uri } from 'vscode';
|
|
import { promises as fs } from 'fs';
|
|
import { DataSource } from './dataSources/dataSources';
|
|
|
|
/**
|
|
* Class representing a Project, and providing functions for operating on it
|
|
*/
|
|
export class Project {
|
|
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.projectFilePath = projectFilePath;
|
|
}
|
|
|
|
/**
|
|
* Reads the project setting and contents from the file
|
|
*/
|
|
public async readProjFile() {
|
|
const projFileText = await fs.readFile(this.projectFilePath);
|
|
this.projFileXmlDoc = new xmldom.DOMParser().parseFromString(projFileText.toString());
|
|
|
|
// find all folders and files to include
|
|
|
|
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));
|
|
}
|
|
|
|
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(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) {
|
|
switch (entry.type) {
|
|
case EntryType.File:
|
|
this.addFileToProjFile(entry.relativePath);
|
|
break;
|
|
case EntryType.Folder:
|
|
this.addFolderToProjFile(entry.relativePath);
|
|
}
|
|
|
|
await this.serializeToProjFile(this.projFileXmlDoc);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents an entry in a project file
|
|
*/
|
|
export class ProjectEntry {
|
|
/**
|
|
* Absolute file system URI
|
|
*/
|
|
fsUri: Uri;
|
|
relativePath: string;
|
|
type: EntryType;
|
|
|
|
constructor(uri: Uri, relativePath: string, type: EntryType) {
|
|
this.fsUri = uri;
|
|
this.relativePath = relativePath;
|
|
this.type = type;
|
|
}
|
|
|
|
public toString(): string {
|
|
return this.fsUri.path;
|
|
}
|
|
}
|
|
|
|
export enum EntryType {
|
|
File,
|
|
Folder
|
|
}
|