From 70d4dc1ebf9b8ee2b9f25d2262b369204090d760 Mon Sep 17 00:00:00 2001 From: Blackcatz1911 Date: Mon, 17 Sep 2018 10:24:21 +0200 Subject: [PATCH 01/37] Updated: German translation --- package.nls.de.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.nls.de.json b/package.nls.de.json index 1823b4a2..9fbee39b 100644 --- a/package.nls.de.json +++ b/package.nls.de.json @@ -1,13 +1,13 @@ { "ext.config.title": "Code Settings Sync Konfiguration", "ext.config.gist": "GitHub Gist-ID für Settings Sync.", - "ext.config.lastUpload": "Zeitstempel des letzten Uploads von Settings Sync. Leeren um Download zu forcieren.", - "ext.config.lastDownload": "Zeitstepel des letzten Downloads von Settings Sync. Leeren um Download zu forcieren.", + "ext.config.lastUpload": "Zeitstempel des letzten Uploads von Settings Sync. Leeren um Download zu erzwingen.", + "ext.config.lastDownload": "Zeitstepel des letzten Downloads von Settings Sync. Leeren um Download zu erzwingen.", "ext.config.autoDownload": "Auf true setzen um die Einstellungen beim Programmstart automatisch herunterzuladen. [Programmneustart benötigt]", "ext.config.autoUpload": "Auf true setzen um die Einstellungen bei Änderung automatisch hochzuladen. [Programmneustart benötigt]", "ext.config.forceDownload": "Auf true setzen um selbst bei aktuellen Einstellung die Daten neu herunterzuladen.", "ext.config.host": "Falls erwünscht, GitHub Enterprise Host definieren.", - "ext.config.pathPrefix": "Falls erwünscht, GitHub Enterprise api prefix definieren. Normalerweise '/api/v3'. Wird nur verwended wenn ein Host definiert ist.", + "ext.config.pathPrefix": "Falls erwünscht, GitHub Enterprise API-Prefix definieren. Normalerweise '/api/v3'. Wird nur verwended wenn ein Host definiert ist.", "ext.config.quietSync": "Zeigt bei true Ergebnisse nur in der Statusleiste statt einer Zusammenfassung an.", "ext.config.askGistName": "Fragt nach Gist-Namen bei Erstellung. Hilft dir, das Gist zu identifizieren, solltest du mehrere benutzen.", "ext.config.removeExtensions": "Auf false setzen wenn Erweiterungen beim Herunterladen der Einstellungen nicht entfernt werden sollen.", @@ -39,9 +39,9 @@ "cmd.otherOptions.shareSetting": "Sync : Einstellungen via öffentlichem Gist teilen", "cmd.otherOptions.shareSetting.beforeConfirm": "Sync : derzeitiges Gist entfernen und Einstellungen zu neuem Gist hinzufügen. Fortfahren?", "cmd.otherOptions.downloadSetting": "Sync : Einstellungen von öffentlichem Gist laden", - "cmd.otherOptions.toggleForceDownload": "Sync : Forcierter Download an/aus", - "cmd.otherOptions.toggleForceDownload.on": "Sync : Forcierter Download an.", - "cmd.otherOptions.toggleForceDownload.off": "Sync : Forcierter Download aus.", + "cmd.otherOptions.toggleForceDownload": "Sync : Erzwungener Download an/aus", + "cmd.otherOptions.toggleForceDownload.on": "Sync : Erzwungener Download an.", + "cmd.otherOptions.toggleForceDownload.off": "Sync : Erzwungener Download aus.", "cmd.otherOptions.toggleAutoUpload": "Sync : Automatischer Upload nach Änderung an/aus", "cmd.otherOptions.toggleAutoUpload.on": "Sync : Automatischer Upload nach Änderung an. Programmneustart notwendig.", "cmd.otherOptions.toggleAutoUpload.off": "Sync : Automatischer Upload nach Änderung aus.", @@ -78,8 +78,8 @@ "common.error.invalidGistId": "Sync : ungültige Gist Id. Bitte verifizieren: https://gist.github.com//.", "common.error.tokenNotSave": "Sync : Token nicht gesichert.", "common.error.gistNotSave": "Sync : Gist nicht gesichert.", - "common.action.openExtPage": "Open Extension Page", - "common.action.openExtTutorial": "Open Tutorial", + "common.action.openExtPage": "Erweiterungsseite öffnen", + "common.action.openExtTutorial": "Anleitung öffnen", "common.action.releaseNotes": "Versionshinweise", "common.action.writeReview": "Bewertung schreiben", "common.action.support": "Projekt unterstützen", @@ -92,4 +92,4 @@ "common.prompt.enterGistId": "Gist-ID der vorherig hochgeladeenen Einstellungen eingeben. Du kannst sie außerdem manuell in den Einstellungen setzen (sync.gist). [Enter] um fortzufahren, [Escape] um abzubrechen).", "common.prompt.enterGithubAccessToken": "Du kannst den Token auch manuell hinzufügen (Benutzerordner/syncLocalSettings.json). [Enter] um fortzufahren, [Escape] um abzurechen." -} \ No newline at end of file +} From 54752d3f1f2b23d446d1814f21cc07088bf79453 Mon Sep 17 00:00:00 2001 From: Shan Date: Wed, 19 Sep 2018 02:30:18 +0500 Subject: [PATCH 02/37] #641 --- src/commons.ts | 91 +++++++++++++++++++++++++------------------------- src/setting.ts | 2 +- src/sync.ts | 13 +++----- 3 files changed, 50 insertions(+), 56 deletions(-) diff --git a/src/commons.ts b/src/commons.ts index 88662ffa..ea84e5b6 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -418,53 +418,52 @@ export default class Commons { const writeReview = localize("common.action.writeReview"); const support = localize("common.action.support"); const joinCommunity = localize("common.action.joinCommunity"); - // TODO : Remove this, v3.1 Specific only. - vscode.window.showInformationMessage( - "Some Settings are updated. You can remove unnecessary sync settings from code. Read Sync guide for details." - ); - vscode.window - .showInformationMessage( - localize("common.info.updateTo", Environment.getVersion()), - releaseNotes, - writeReview, - support, - joinCommunity - ) - .then((val: string) => { - if (val === releaseNotes) { - vscode.commands.executeCommand( - "vscode.open", - vscode.Uri.parse( - "http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html" - ) - ); - } - if (val === writeReview) { - vscode.commands.executeCommand( - "vscode.open", - vscode.Uri.parse( - "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync#review-details" - ) - ); - } - if (val === support) { - vscode.commands.executeCommand( - "vscode.open", - vscode.Uri.parse( - "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=4W3EWHHBSYMM8&lc=IE&item_name=Code%20Settings%20Sync&item_number=visual%20studio%20code%20settings%20sync¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHosted" - ) - ); - } - if (val === joinCommunity) { - vscode.commands.executeCommand( - "vscode.open", - vscode.Uri.parse( - "https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk" - ) - ); - } - }); + if (!customSettings.disableUpdateMessage) { + vscode.window + .showInformationMessage( + localize("common.info.updateTo", Environment.getVersion()), + releaseNotes, + writeReview, + support, + joinCommunity + ) + .then((val: string) => { + if (val === releaseNotes) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "http://shanalikhan.github.io/2016/05/14/Visual-studio-code-sync-settings-release-notes.html" + ) + ); + } + if (val === writeReview) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync#review-details" + ) + ); + } + if (val === support) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=4W3EWHHBSYMM8&lc=IE&item_name=Code%20Settings%20Sync&item_number=visual%20studio%20code%20settings%20sync¤cy_code=USD&bn=PP-DonationsBF:btn_donate_SM.gif:NonHosted" + ) + ); + } + if (val === joinCommunity) { + vscode.commands.executeCommand( + "vscode.open", + vscode.Uri.parse( + "https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk" + ) + ); + } + }); + } } + if (fileChanged) { customSettings.version = Environment.CURRENT_VERSION; await this.SetCustomSettings(customSettings); diff --git a/src/setting.ts b/src/setting.ts index c0177d2d..021e0074 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -47,7 +47,7 @@ export class CustomSettings { public downloadPublicGist: boolean = false; public supportedFileExtensions: string[] = ["json", "code-snippets"]; public openTokenLink: boolean = true; - public useCliBaseInstallation: boolean = true; + public disableUpdateMessage: boolean = false; public lastUpload: Date = null; public lastDownload: Date = null; public githubEnterpriseUrl: string = null; diff --git a/src/sync.ts b/src/sync.ts index 16d497be..e555564d 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -502,16 +502,11 @@ export class Sync { } try { - // TODO: Remove Older installation way in next version. let useCli = true; - if (customSettings.useCliBaseInstallation) { - const autoUpdate: boolean = vscode.workspace - .getConfiguration("extensions") - .get("autoUpdate"); - useCli = autoUpdate; - } else { - useCli = false; - } + const autoUpdate: boolean = vscode.workspace + .getConfiguration("extensions") + .get("autoUpdate"); + useCli = autoUpdate; addedExtensions = await PluginService.InstallExtensions( content, From 38b3b3cadb1f9cdf1da1ff98437aca4f3122d294 Mon Sep 17 00:00:00 2001 From: Peter Squicciarini Date: Wed, 19 Sep 2018 09:50:10 -0400 Subject: [PATCH 03/37] Add support for VSCodium --- src/environmentPath.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/environmentPath.ts b/src/environmentPath.ts index 8203dae5..c60c825a 100644 --- a/src/environmentPath.ts +++ b/src/environmentPath.ts @@ -102,13 +102,15 @@ export class Environment { }); } - const possibleCodePaths = [ - this.isInsiders - ? "/Code - Insiders" - : this.isOss - ? "/Code - OSS" - : "/Code" - ]; + const possibleCodePaths = []; + if (this.isInsiders) { + possibleCodePaths.push("/Code - Insiders"); + } else if (this.isOss) { + possibleCodePaths.push("/Code - OSS"); + possibleCodePaths.push("/VSCodium"); + } else { + possibleCodePaths.push("/Code"); + } for (const possibleCodePath of possibleCodePaths) { try { fs.statSync(this.PATH + possibleCodePath); From 1948e5bfa4d10c4bd447a93e8a52e38ade9080a4 Mon Sep 17 00:00:00 2001 From: Takara Hokao Date: Wed, 26 Sep 2018 23:28:36 +0900 Subject: [PATCH 04/37] Corrected the message to be displayed --- src/sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sync.ts b/src/sync.ts index e555564d..919f06b4 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -810,7 +810,7 @@ export class Sync { ); } else { vscode.window.showInformationMessage( - localize("cmd.otherOptions.preserve.info.done1", input, val) + localize("cmd.otherOptions.preserve.info.done2", input, val) ); } } From bef2a5df6bff63919850c9872d8ac3781670ea11 Mon Sep 17 00:00:00 2001 From: Takara Hokao Date: Wed, 26 Sep 2018 23:24:14 +0900 Subject: [PATCH 05/37] Add Japanese translation file --- package.nls.ja.json | 94 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 package.nls.ja.json diff --git a/package.nls.ja.json b/package.nls.ja.json new file mode 100644 index 00000000..6b748fcb --- /dev/null +++ b/package.nls.ja.json @@ -0,0 +1,94 @@ +{ + "ext.config.title": "Code Settings Sync 設定", + "ext.config.gist": "Settings Sync で使用する GitHub GIST ID", + "ext.config.lastUpload": "Settings Sync 最終アップロード日時. 手動でダウンロードする場合は空にしてください.", + "ext.config.lastDownload": "Settings Sync 最終ダウンロード日時. 手動でダウンロードする場合は空にしてください.", + "ext.config.autoDownload": "エディタを開いたときに設定を自動でダウンロードする場合は true にしてください. [再起動が必要です]", + "ext.config.autoUpload": "変更したときに設定を自動でアップロードする場合は true にしてください. [再起動が必要です]", + "ext.config.forceDownload": "ローカルの設定が更新されていても, リモート設定をダウンロードしたい場合は true にしてください.", + "ext.config.host": "必要に応じて GitHub Enterprise のホストを指定してください.", + "ext.config.pathPrefix": "必要に応じて GitHub Enterprise API プリフィックスを指定してください. 通常であれば '/api/v3'. hostフィールドが指定されているときだけ使います.", + "ext.config.quietSync": "true にした場合, サイレントモードになります. 出力パネルではなくステータスバーに同期結果を表示します.", + "ext.config.askGistName": "作成時に gist 名を尋ねます. 複数の gist を使用しているときにgistを識別するのに役に立ちます.", + "ext.config.removeExtensions": "ダウンロード時に拡張機能を削除したくない場合 false にします.", + "ext.config.syncExtensions": "拡張機能を同期したくない場合 false にします.", + "cmd.howSetting.title": "Sync : 設定方法", + "cmd.updateSettings.title": "Sync : 設定をアップロード", + "cmd.updateSettings.info.uploading": "Sync : Github に設定を アップロード / 更新.", + "cmd.updateSettings.info.uploadingFile": "Sync : ファイルのアップロード中です.", + "cmd.updateSettings.info.uploadingDone": "Sync : アップロード完了. GIST ID : {0} . この ID をコピーして他のデバイスでの同期に使用してください.", + "cmd.updateSettings.info.uploadingSuccess": "Sync : アップロードされました.", + "cmd.updateSettings.info.shareGist": "Sync : 他の拡張機能ユーザーに ID を共有して設定を共有します.", + "cmd.updateSettings.info.readding": "Sync : 設定と拡張機能を読み込みます.", + "cmd.updateSettings.info.newGistCreated": "Sync : 新しい gist を作成しました.", + "cmd.updateSettings.warning.noToken": "Sync : ローカルの同期設定ファイルで GitHub トークンを設定するか, 'downloadPublicGist' を無効にしてください.", + "cmd.updateSettings.error.newGistCreateFail": "Sync : Gist を作成することができません.", + "cmd.updateSettings.error.readGistFail": "Sync : GIST ID: {0} 読み取ることができません.", + "cmd.updateSettings.error.gistNotSave": "Sync : GIST が保存されていません", + "cmd.downloadSettings.title": "Sync : 設定をダウンロード", + "cmd.downloadSettings.info.downloaded": "Sync : ダウンロードが完了しました.", + "cmd.downloadSettings.info.readdingOnline": "Sync : オンラインの設定を読み込み中です.", + "cmd.downloadSettings.info.gotLatestVersion": "Sync : 既に最新の設定を持っています.", + "cmd.downloadSettings.error.removeExtFail": "Sync : 一部の拡張機能を削除できません.", + "cmd.downloadSettings.error.unableSave": "Sync : 拡張機能の設定ファイルを保存できません.", + "cmd.resetSettings.title": "Sync : 拡張機能の設定をリセット", + "cmd.resetSettings.info.resetting": "Sync : 設定をリセット中です.", + "cmd.resetSettings.info.settingClear": "Sync : 設定がクリアされました.", + "cmd.otherOptions.title": "Sync : 詳細なオプション", + "cmd.otherOptions.editLocalSetting": "Sync : 拡張機能のローカル設定の編集", + "cmd.otherOptions.shareSetting": "Sync : 公開 GIST で設定の共有", + "cmd.otherOptions.shareSetting.beforeConfirm": "Sync : これにより, 現在の GIST が削除され, 新しく公開 GIST に設定がアップロードされます. 続行しますか?", + "cmd.otherOptions.downloadSetting": "Sync : 公開 GIST から設定のダウンロード", + "cmd.otherOptions.toggleForceDownload": "Sync : 強制ダウンロード ON / OFF 切り替え", + "cmd.otherOptions.toggleForceDownload.on": "Sync : 強制ダウンロードを ON にしました.", + "cmd.otherOptions.toggleForceDownload.off": "Sync : 強制ダウンロードを OFF にしました.", + "cmd.otherOptions.toggleAutoUpload": "Sync : 設定変更時の自動アップロード ON / OFF 切り替え", + "cmd.otherOptions.toggleAutoUpload.on": "Sync : 設定変更時の自動アップロードを ON にしました. 再起動後に反映されます.", + "cmd.otherOptions.toggleAutoUpload.off": "Sync : 設定変更時の自動アップロードを OFF にしました.", + "cmd.otherOptions.toggleAutoDownload": "Sync : 起動時の自動ダウンロード ON / OFF 切り替え", + "cmd.otherOptions.toggleAutoDownload.on": "Sync : VSCode 起動時の自動ダウンロードを ON にしました.", + "cmd.otherOptions.toggleAutoDownload.off": "Sync : VSCode 起動時の自動ダウンロードを OFF にしました.", + "cmd.otherOptions.toggleSummaryPage": "Sync : アップロード / ダウンロード 時のサイレントモード ON / OFF 切り替え", + "cmd.otherOptions.preserve": "Sync : ダウンロード後に上書きをしないように設定を保存", + "cmd.otherOptions.preserve.placeholder": "保存する settings.json のキーを入力してください.", + "cmd.otherOptions.preserve.prompt": "例 : 'http.proxy' => このコンピュータのプロキシを保存して, それで上書きします. 空の場合プロキシを削除します.", + "cmd.otherOptions.preserve.info.done1": "Sync : 終了します. {0} の値はダウンロード後 settings.json から削除されます.", + "cmd.otherOptions.preserve.info.done2": "Sync : 終了します. ダウンロード後に {0} : {1} で settings.json に保持します.", + "cmd.otherOptions.joinCommunity": "Sync : コミュニティに参加する", + "cmd.otherOptions.openIssue": "Sync : Issue を作成する", + "cmd.otherOptions.releaseNotes": "Sync : リリースノート", + "cmd.otherOptions.quietSync.on": "Sync : サイレントモードを ON にしました. ステータスバーは ダウンロード / アップロード時に更新されます.", + "cmd.otherOptions.quietSync.off": "Sync : サイレントモードを OFF にしました. 要約は ダウンロード / アップロード 時に表示されます.", + "cmd.otherOptions.warning.tokenNotRequire": "Sync : Settings Sync はこれ以降 GitHub Token を尋ねません.", + "cmd.otherOptions.error.toggleFail": "Sync : 切り替えできません.", + "common.info.installed": "Sync : 設定を作成しました, インストールありがとうございます!", + "common.info.needHelp": "Sync : この拡張機能の設定をお手伝いしますか?", + "common.info.excludeFile": "Sync : アップロードとダウンロードの設定で ファイル / フォルダ を除外することができます.", + "common.info.updating": "Sync : アップデート中です ... お待ちください.", + "common.info.initAutoUpload": "Sync : 5秒後に自動アップロードを開始します.", + "common.info.setToken": "Sync : GitHub token を `syncLocalSettings.json` に手動で設定できます", + "common.info.tokenSaved": "Sync : トークンが保存されました", + "common.info.gistSaved": "Sync : Gist が保存されました", + "common.info.updateTo": "Sync : v{0} への更新", + "common.info.donate": "Sync : この拡張機能を気に入りましたか? レビューや寄付はどうですか? (^_-)", + "common.error.message": "Sync : エラーログがコンソールに表示されます (ヘルプ > 開発者ツールの切り替え).", + "common.error.connection": "Sync : インターネットに接続されてない, もしくは GitHub に接族ができません. コンソールに例外が出力されました", + "common.error.canNotSave": "Sync : 設定が保存できません. 有効な JSON settings.json ファイルがあることを確認してください. ( 例 : 後続のカンマがない )", + "common.error.invalidToken": "Sync : 無効もしくは期限切れの GitHub Token です. README に記載のスコープで新しいトークンを生成してください. コンソールに例外が出力されました.", + "common.error.invalidGistId": "Sync : 無効な Gist Id が入力されました. あなたの gist を確認してください : https://gist.github.com//.", + "common.error.tokenNotSave": "Sync : トークンが保存されませんでした.", + "common.error.gistNotSave": "Sync : Gist が保存されませんでした.", + "common.action.openExtPage": "拡張機能のページを開きます", + "common.action.openExtTutorial": "チュートリアルを開きます", + "common.action.releaseNotes": "リリースノート", + "common.action.writeReview": "レビューを書きます", + "common.action.support": "このプロジェクトをサポートします", + "common.action.joinCommunity": "コミュニティに参加します", + "common.action.donate": "寄付します", + "common.placeholder.enterGithubAccessToken": "GitHub の個人アクセストークンを入力してください", + "common.placeholder.enterGistId": "Gist Id を入力してください", + "common.placeholder.multipleGist": "Gist 名 (例 : Personal Settings)", + "common.prompt.multipleGist": "複数の gist を持っている場合に設定を識別できます.", + "common.prompt.enterGistId": "以前にアップロードした設定の Gist Id を入力してください. コード設定で手動で設定する事もできます (sync.gist). 確定するには [Enter], キャンセルするには [Esc] を押してください.", + "common.prompt.enterGithubAccessToken": "手動でトークンを追加する事もできます (ユーザーフォルダ / syncLocalSettings.json). 確定するには [Enter], キャンセルするには [Esc] を押してください." +} From 59fb77dbac4cab97936949ae75e3915cf826d42b Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Fri, 5 Oct 2018 04:06:26 -0300 Subject: [PATCH 06/37] =Adding HostName property. Get hostname using 'os.hostname()' --- src/environmentPath.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/environmentPath.ts b/src/environmentPath.ts index 8203dae5..0d589064 100644 --- a/src/environmentPath.ts +++ b/src/environmentPath.ts @@ -27,6 +27,7 @@ export class Environment { public ExtensionFolder: string = null; public PATH: string = null; public OsType: OsType = null; + public HostName: string = null; public FILE_SETTING: string = null; public FILE_LAUNCH: string = null; @@ -73,6 +74,7 @@ export class Environment { process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; this.PATH = process.env.APPDATA; this.OsType = OsType.Windows; + this.HostName = os.hostname(); if (!this.PATH) { if (process.platform === "darwin") { From 84ba33b092fa3cbdc1100742efff7e792c1cf894 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Fri, 5 Oct 2018 04:12:29 -0300 Subject: [PATCH 07/37] =Need definition for supported OS's. Using strict match regex to get @sync pragmas. Should it support multiple spaces? Replacing settings.json content before writing file. Should be better removing lines instead of add comments? Replacing settings.json before upload to remove @sync ignore pragms. --- src/sync.ts | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/src/sync.ts b/src/sync.ts index 16d497be..c196a1db 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -16,6 +16,8 @@ import { LocalConfig } from "./setting"; +const OS_SUPPORTED = ["windows", "linux", "mac", "darwin"]; + export class Sync { constructor(private context: vscode.ExtensionContext) {} /** @@ -208,6 +210,25 @@ export class Sync { allSettingFiles.push(snippetFile); } } + + if (snippetFile.fileName === env.FILE_SETTING_NAME) { + snippetFile.content = removeIgnoredSettings(snippetFile.content); + } + } + + function removeIgnoredSettings(settingsContent: string): string { + let result: string = settingsContent; + const ignoreSettings: RegExpMatchArray = settingsContent.match( + /\/\/\s\@sync\signore\n.+,?/g // should be space or hypen? + ); + + if (ignoreSettings !== null) { + for (const line of ignoreSettings) { + result = result.replace(line, ""); // remove line? + } + } + + return result; } const extProp: CloudSetting = new CloudSetting(); @@ -359,6 +380,87 @@ export class Sync { return; } + function GetOsEnum(osName: string) { + switch (osName.toLocaleLowerCase()) { + case "windows": + return OsType.Windows; + case "linux": + return OsType.Linux; + case "mac": // should we define unique names for each OS? + case "darwin": + return OsType.Mac; + } + } + + function ProcessPragmaSettings(settingsContent: string): string { + let result: string = settingsContent; + const pragmaSettings: RegExpMatchArray = settingsContent.match( + /\/\/\s\@sync\s(os=(\w+)\s?)?(host=(\w+)\s?)?\n(.+),?/g // should support multiple spaces and line-breaks? + ); + + if (pragmaSettings !== null) { + for (const line of pragmaSettings) { + // line e.g.: // @sync os=windows host=Laptop\n"window.menuBarVisibility": "none", + if (line.indexOf("os=") === -1 && line.indexOf("host=") === -1) { + continue; + } + + // check OS pragma + try { + const osMatch: RegExpMatchArray = line.match(/os=(\w+)/); + if (osMatch !== null) { + const osFromPragma = osMatch[1]; + if ( + osFromPragma && + OS_SUPPORTED.includes(osFromPragma) && + GetOsEnum(osFromPragma) !== env.OsType + ) { + // should remove or comment the line? + // result = result.replace(line, ""); + // handle multiple line breaks ? + const commentedLine = line.replace(/\n(.+)/, settingLine => { + return `\n\t// ${settingLine.trim()}`; + }); + + result = result.replace(line, commentedLine); + } + } + + // check OS pragma + const hostMatch: RegExpMatchArray = line.match(/host=(\w+)/); + if (hostMatch !== null) { + const hostFromPragma = hostMatch[1]; + if ( + hostFromPragma && + hostFromPragma.toLowerCase() !== + env.HostName.toLocaleLowerCase() // should be case sensitive? + ) { + const commentedLine = line.replace(/\n(.+)/, settingLine => { + return `\n\t// ${settingLine.trim()}`; + }); + + result = result.replace(line, commentedLine); + } + } + } catch (e) { + continue; + } + } + } + + const ignoreSettings: RegExpMatchArray = settingsContent.match( + /\/\/\s\@sync\signore\n.+,?/g // should be space or hypen? + ); + + if (ignoreSettings !== null) { + for (const line of ignoreSettings) { + result = result.replace(line, ""); // remove line? + } + } + + return result; + } + async function StartDownload( syncSetting: ExtensionConfig, customSettings: CustomSettings @@ -466,7 +568,7 @@ export class Sync { for (const file of updatedFiles) { let writeFile: boolean = false; - const content: string = file.content; + let content: string = file.content; if (content !== "") { if (file.gistName === env.FILE_EXTENSION_NAME) { @@ -569,6 +671,10 @@ export class Sync { file.fileName ); + if (file.gistName === env.FILE_SETTING_NAME) { + content = ProcessPragmaSettings(content); + } + actionList.push( FileService.WriteFile(filePath, content) .then(() => { From 072ba1c4002ef923da448581e878afaff85df370 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 17:57:34 -0300 Subject: [PATCH 08/37] Added English message for OSNotSupported message while processing uploading content --- package.nls.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.nls.json b/package.nls.json index bc89355d..a59c0df2 100644 --- a/package.nls.json +++ b/package.nls.json @@ -22,6 +22,7 @@ "cmd.updateSettings.info.readding": "Sync : Reading Settings and Extensions.", "cmd.updateSettings.info.newGistCreated": "Sync : New gist created.", "cmd.updateSettings.warning.noToken": "Sync : Set GitHub Token or disable 'downloadPublicGist' from local Sync settings file.", + "cmd.updateSettings.warning.OSNotSupported": "Sync Pragma OS value {0} not supported at line {1}", "cmd.updateSettings.error.newGistCreateFail": "Sync : Unable to create Gist.", "cmd.updateSettings.error.readGistFail": "Sync : GIST ID: {0} UNABLE TO READ.", "cmd.updateSettings.error.gistNotSave": "Sync : GIST NOT SAVED", From fe6db09eb9bf0427104c854dd979d5fb48fb4e58 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 17:58:24 -0300 Subject: [PATCH 09/37] mocha and chai packages added as dev dependencies. Test script not working while compiling. --- package.json | 327 ++++++++++++++++++++++++++------------------------- 1 file changed, 165 insertions(+), 162 deletions(-) diff --git a/package.json b/package.json index f73923fc..419379ae 100644 --- a/package.json +++ b/package.json @@ -1,171 +1,174 @@ { - "name": "code-settings-sync", - "displayName": "Settings Sync", - "description": "Synchronize Settings, Snippets, Themes, File Icons, Launch, Keybindings, Workspaces and Extensions Across Multiple Machines Using GitHub Gist.", - "version": "3.1.2", - "icon": "images/cloud.png", - "publisher": "Shan", - "author": { - "name": "Shan Khan", - "url": "http://shanalikhan.github.io", - "email": "shanalikhan@hotmail.com" + "name": "code-settings-sync", + "displayName": "Settings Sync", + "description": "Synchronize Settings, Snippets, Themes, File Icons, Launch, Keybindings, Workspaces and Extensions Across Multiple Machines Using GitHub Gist.", + "version": "3.1.2", + "icon": "images/cloud.png", + "publisher": "Shan", + "author": { + "name": "Shan Khan", + "url": "http://shanalikhan.github.io", + "email": "shanalikhan@hotmail.com" + }, + "homepage": "https://shanalikhan.github.io", + "galleryBanner": { + "color": "#3B4859", + "theme": "dark" + }, + "badges": [ + { + "url": "https://vsmarketplacebadge.apphb.com/version/Shan.code-settings-sync.svg", + "description": "Latest Version", + "href": "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" }, - "homepage": "https://shanalikhan.github.io", - "galleryBanner": { - "color": "#3B4859", - "theme": "dark" + { + "url": "https://vsmarketplacebadge.apphb.com/installs/Shan.code-settings-sync.svg", + "description": "Total Downloads", + "href": "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" }, - "badges": [ - { - "url": "https://vsmarketplacebadge.apphb.com/version/Shan.code-settings-sync.svg", - "description": "Latest Version", - "href": "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" - }, - { - "url": "https://vsmarketplacebadge.apphb.com/installs/Shan.code-settings-sync.svg", - "description": "Total Downloads", - "href": "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" - }, - { - "url": "https://vsmarketplacebadge.apphb.com/rating/Shan.code-settings-sync.svg", - "description": "Ratings", - "href": "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" - }, - { - "url": "https://img.shields.io/badge/Join%20Community-slack-green.svg", - "description": "Join Slack Community", - "href": "https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/shanalikhan/code-settings-sync.git" + { + "url": "https://vsmarketplacebadge.apphb.com/rating/Shan.code-settings-sync.svg", + "description": "Ratings", + "href": "https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync" }, - "bugs": { - "url": "https://github.com/shanalikhan/code-settings-sync/issues", - "email": "shanalikhan@hotmail.com" - }, - "engines": { - "vscode": "^1.8.0" - }, - "categories": [ - "Other" - ], - "keywords": [ - "vscode-sync", - "vscode-settings-sync", - "code-settings-sync", - "settings-sync", - "workspace-sync", - "multi-root ready" + { + "url": "https://img.shields.io/badge/Join%20Community-slack-green.svg", + "description": "Join Slack Community", + "href": "https://join.slack.com/t/codesettingssync/shared_invite/enQtMzE3MjY5NTczNDMwLTYwMTIwNGExOGE2MTJkZWU0OTU5MmI3ZTc4N2JkZjhjMzY1OTk5OGExZjkwMDMzMDU4ZTBlYjk5MGQwZmMyNzk" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/shanalikhan/code-settings-sync.git" + }, + "bugs": { + "url": "https://github.com/shanalikhan/code-settings-sync/issues", + "email": "shanalikhan@hotmail.com" + }, + "engines": { + "vscode": "^1.8.0" + }, + "categories": [ + "Other" + ], + "keywords": [ + "vscode-sync", + "vscode-settings-sync", + "code-settings-sync", + "settings-sync", + "workspace-sync", + "multi-root ready" + ], + "activationEvents": [ + "*" + ], + "main": "./out/src/extension", + "contributes": { + "commands": [ + { + "command": "extension.HowSettings", + "title": "%cmd.howSetting.title%" + }, + { + "command": "extension.downloadSettings", + "title": "%cmd.downloadSettings.title%" + }, + { + "command": "extension.updateSettings", + "title": "%cmd.updateSettings.title%" + }, + { + "command": "extension.resetSettings", + "title": "%cmd.resetSettings.title%" + }, + { + "command": "extension.otherOptions", + "title": "%cmd.otherOptions.title%" + } ], - "activationEvents": [ - "*" + "keybindings": [ + { + "key": "alt+shift+u", + "command": "extension.updateSettings" + }, + { + "key": "alt+shift+d", + "command": "extension.downloadSettings" + } ], - "main": "./out/src/extension", - "contributes": { - "commands": [ - { - "command": "extension.HowSettings", - "title": "%cmd.howSetting.title%" - }, - { - "command": "extension.downloadSettings", - "title": "%cmd.downloadSettings.title%" - }, - { - "command": "extension.updateSettings", - "title": "%cmd.updateSettings.title%" - }, - { - "command": "extension.resetSettings", - "title": "%cmd.resetSettings.title%" - }, - { - "command": "extension.otherOptions", - "title": "%cmd.otherOptions.title%" - } - ], - "keybindings": [ - { - "key": "alt+shift+u", - "command": "extension.updateSettings" - }, - { - "key": "alt+shift+d", - "command": "extension.downloadSettings" - } - ], - "configuration": { - "properties": { - "sync.gist": { - "type": "string", - "default": "", - "description": "%ext.config.gist%" - }, - "sync.autoDownload": { - "type": "boolean", - "default": false, - "description": "%ext.config.autoDownload%" - }, - "sync.autoUpload": { - "type": "boolean", - "default": false, - "description": "%ext.config.autoUpload%" - }, - "sync.forceDownload": { - "type": "boolean", - "default": false, - "description": "%ext.config.forceDownload%" - }, - "sync.quietSync": { - "type": "boolean", - "default": false, - "description": "%ext.config.quietSync%" - }, - "sync.askGistName": { - "type": "boolean", - "default": false, - "description": "%ext.config.askGistName%" - }, - "sync.removeExtensions": { - "type": "boolean", - "default": true, - "description": "%ext.config.removeExtensions%" - }, - "sync.syncExtensions": { - "type": "boolean", - "default": true, - "description": "%ext.config.syncExtensions%" - } - }, - "title": "%ext.config.title%" + "configuration": { + "properties": { + "sync.gist": { + "type": "string", + "default": "", + "description": "%ext.config.gist%" + }, + "sync.autoDownload": { + "type": "boolean", + "default": false, + "description": "%ext.config.autoDownload%" + }, + "sync.autoUpload": { + "type": "boolean", + "default": false, + "description": "%ext.config.autoUpload%" + }, + "sync.forceDownload": { + "type": "boolean", + "default": false, + "description": "%ext.config.forceDownload%" + }, + "sync.quietSync": { + "type": "boolean", + "default": false, + "description": "%ext.config.quietSync%" + }, + "sync.askGistName": { + "type": "boolean", + "default": false, + "description": "%ext.config.askGistName%" + }, + "sync.removeExtensions": { + "type": "boolean", + "default": true, + "description": "%ext.config.removeExtensions%" + }, + "sync.syncExtensions": { + "type": "boolean", + "default": true, + "description": "%ext.config.syncExtensions%" } - }, - "scripts": { - "vscode:prepublish": "npm run tslint-check && npm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install", - "tslint-check": "tslint -c ./tslint.json ./src/**/*.ts ./src/*.ts", - "format": "prettier --write './src/**/*.ts'" - }, - "devDependencies": { - "@types/fs-extra": "^5.0.4", - "@types/node": "^10.7.0", - "prettier": "^1.14.2", - "tslint": "^5.11.0", - "tslint-plugin-prettier": "^1.3.0", - "typescript": "^3.0.1", - "vscode": "^1.1.21" - }, - "dependencies": { - "@octokit/rest": "^15.10.0", - "adm-zip": "^0.4.11", - "chokidar": "^2.0.2", - "fs-extra": "^7.0.0", - "https-proxy-agent": "^2.1.1", - "lockfile": "^1.0.4", - "temp": "^0.8.3" + }, + "title": "%ext.config.title%" } + }, + "scripts": { + "vscode:prepublish": "npm run tslint-check && npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "tslint-check": "tslint -c ./tslint.json ./src/**/*.ts ./src/*.ts", + "format": "prettier --write './src/**/*.ts'", + "test": "npm run vscode:prepublish && node ./node_modules/bin/mocha --recursive" + }, + "devDependencies": { + "@types/fs-extra": "^5.0.4", + "@types/node": "^10.7.0", + "chai": "^4.2.0", + "mocha": "^5.2.0", + "prettier": "^1.14.2", + "tslint": "^5.11.0", + "tslint-plugin-prettier": "^1.3.0", + "typescript": "^3.0.1", + "vscode": "^1.1.21" + }, + "dependencies": { + "@octokit/rest": "^15.10.0", + "adm-zip": "^0.4.11", + "chokidar": "^2.0.2", + "fs-extra": "^7.0.0", + "https-proxy-agent": "^2.1.1", + "lockfile": "^1.0.4", + "temp": "^0.8.3" + } } From 3782c762258528f84d71512e1d378f57ef7bd814 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 17:59:26 -0300 Subject: [PATCH 10/37] Moving pragma functions to a separated file --- src/sync.ts | 109 +++++----------------------------------------------- 1 file changed, 10 insertions(+), 99 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index c196a1db..e6ae4fd2 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -16,7 +16,7 @@ import { LocalConfig } from "./setting"; -const OS_SUPPORTED = ["windows", "linux", "mac", "darwin"]; +import PragmaUtil from "./pragmaUtil"; export class Sync { constructor(private context: vscode.ExtensionContext) {} @@ -212,23 +212,11 @@ export class Sync { } if (snippetFile.fileName === env.FILE_SETTING_NAME) { - snippetFile.content = removeIgnoredSettings(snippetFile.content); - } - } - - function removeIgnoredSettings(settingsContent: string): string { - let result: string = settingsContent; - const ignoreSettings: RegExpMatchArray = settingsContent.match( - /\/\/\s\@sync\signore\n.+,?/g // should be space or hypen? - ); - - if (ignoreSettings !== null) { - for (const line of ignoreSettings) { - result = result.replace(line, ""); // remove line? - } + snippetFile.content = PragmaUtil.processBeforeUpload( + snippetFile.content, + vscode.window + ); } - - return result; } const extProp: CloudSetting = new CloudSetting(); @@ -380,87 +368,6 @@ export class Sync { return; } - function GetOsEnum(osName: string) { - switch (osName.toLocaleLowerCase()) { - case "windows": - return OsType.Windows; - case "linux": - return OsType.Linux; - case "mac": // should we define unique names for each OS? - case "darwin": - return OsType.Mac; - } - } - - function ProcessPragmaSettings(settingsContent: string): string { - let result: string = settingsContent; - const pragmaSettings: RegExpMatchArray = settingsContent.match( - /\/\/\s\@sync\s(os=(\w+)\s?)?(host=(\w+)\s?)?\n(.+),?/g // should support multiple spaces and line-breaks? - ); - - if (pragmaSettings !== null) { - for (const line of pragmaSettings) { - // line e.g.: // @sync os=windows host=Laptop\n"window.menuBarVisibility": "none", - if (line.indexOf("os=") === -1 && line.indexOf("host=") === -1) { - continue; - } - - // check OS pragma - try { - const osMatch: RegExpMatchArray = line.match(/os=(\w+)/); - if (osMatch !== null) { - const osFromPragma = osMatch[1]; - if ( - osFromPragma && - OS_SUPPORTED.includes(osFromPragma) && - GetOsEnum(osFromPragma) !== env.OsType - ) { - // should remove or comment the line? - // result = result.replace(line, ""); - // handle multiple line breaks ? - const commentedLine = line.replace(/\n(.+)/, settingLine => { - return `\n\t// ${settingLine.trim()}`; - }); - - result = result.replace(line, commentedLine); - } - } - - // check OS pragma - const hostMatch: RegExpMatchArray = line.match(/host=(\w+)/); - if (hostMatch !== null) { - const hostFromPragma = hostMatch[1]; - if ( - hostFromPragma && - hostFromPragma.toLowerCase() !== - env.HostName.toLocaleLowerCase() // should be case sensitive? - ) { - const commentedLine = line.replace(/\n(.+)/, settingLine => { - return `\n\t// ${settingLine.trim()}`; - }); - - result = result.replace(line, commentedLine); - } - } - } catch (e) { - continue; - } - } - } - - const ignoreSettings: RegExpMatchArray = settingsContent.match( - /\/\/\s\@sync\signore\n.+,?/g // should be space or hypen? - ); - - if (ignoreSettings !== null) { - for (const line of ignoreSettings) { - result = result.replace(line, ""); // remove line? - } - } - - return result; - } - async function StartDownload( syncSetting: ExtensionConfig, customSettings: CustomSettings @@ -672,7 +579,11 @@ export class Sync { ); if (file.gistName === env.FILE_SETTING_NAME) { - content = ProcessPragmaSettings(content); + content = PragmaUtil.processBeforeWrite( + content, + env.OsType, + localSettings.customConfig.hostName + ); } actionList.push( From 9d56d30595d0a81b8cdb27f3bad993f3d6e63afc Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 18:00:39 -0300 Subject: [PATCH 11/37] Added Pragma util static class. Support for host, env and os values in every orther. Remove whitespaces before upload. Alert user if OS value is not a valid OS. --- src/pragmaUtil.ts | 229 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 src/pragmaUtil.ts diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts new file mode 100644 index 00000000..ca6fc04d --- /dev/null +++ b/src/pragmaUtil.ts @@ -0,0 +1,229 @@ +import { OsType } from "./enums"; +import localize from "./localize"; + +const SUPPORTED_OS = ["windows", "linux", "mac"]; + +export function GetOsEnum(osName: string): OsType { + switch (osName.toLowerCase()) { + case "windows": + return OsType.Windows; + case "linux": + return OsType.Linux; + case "mac": + return OsType.Mac; + } +} + +/** + * Comment/Uncomment lines if matches OS name or Hostname. + * Usage: @sync os=[OS name] host=[hostName] + * Notes: Hostname must be defined in sync.host setting. It could be used for parse any JSON valid string. + * @export + * @class PragmaUtil + */ +export default class PragmaUtil { + /** + * Process @sync pragma statements before file is saved. + * Comment lines that don't match with the user OS or host value. + * @static + * @param {string} settingsContent a valid JSON string + * @returns {string} + * @memberof PragmaUtil + */ + public static processBeforeWrite( + settingsContent: string, + osType: OsType, + hostName: string + ): string { + let result: string = settingsContent; + + const pragmaSettingsBlocks: RegExpMatchArray = result.match( + this.PragmaRegExp + ); + + if (pragmaSettingsBlocks !== null) { + for (let block of pragmaSettingsBlocks) { + // line e.g.: // @sync os=windows host=Laptop\n"window.menuBarVisibility": "none", + + try { + // check OS pragma + const osMatch: RegExpMatchArray = block.match(/os=(\w+)/); + if (osMatch !== null) { + const osFromPragma = osMatch[1].toLowerCase(); + + if (!SUPPORTED_OS.includes(osFromPragma)) { + continue; + } + if (GetOsEnum(osFromPragma) !== osType) { + result = result.replace(block, this.commentLineAfterBreak(block)); + continue; // no need to lookup the host name + } + } + + // check host pragma + const hostMatch: RegExpMatchArray = block.match(/host=(\S+)/); + if (hostMatch !== null) { + const hostFromPragma = hostMatch[1]; + if ( + hostName === null || + hostName === "" || + hostFromPragma.toLowerCase() !== hostName.toLowerCase() + ) { + result = result.replace(block, this.commentLineAfterBreak(block)); + continue; + } + } + + // check env pragma + const envMatch: RegExpMatchArray = block.match(/env=(\S+)/); + if (envMatch !== null) { + const envFromPragma = envMatch[1]; + if (!process.env[envFromPragma.toUpperCase()]) { + result = result.replace(block, this.commentLineAfterBreak(block)); + } + } + } catch (e) { + continue; + } + } + } + + result = this.removeIgnoreBlocks(result); + + return result; + } + + /** + * Remove @sync-ignore settings before upload. + * + * @static + * @param {string} settingsContent + * @param {require('vscode').window} window + * @returns {string} + * @memberof PragmaUtil + */ + public static processBeforeUpload(settingsContent: string, window): string { + let result: string = settingsContent; + result = this.removeIgnoreBlocks(result); + + const lines = result.split("\n"); + + // alert not supported OS + const pragmaMatches: RegExpMatchArray = result.match(this.PragmaRegExp); + if (pragmaMatches) { + for (let block of pragmaMatches) { + try { + let newBlock: string; + const osMatch: RegExpMatchArray = block.match( + this.OSPragmaWhiteSpacesSupportRegExp + ); + if (osMatch !== null) { + const osFromPragma = osMatch[1] || osMatch[2] || osMatch[3]; + + if (osFromPragma !== "" && /\s/.test(osFromPragma)) { + newBlock = block.replace(osFromPragma, osFromPragma.trimLeft()); + result = result.replace(block, newBlock); + block = newBlock; + } + + const trimmed = osFromPragma.toLowerCase().trim(); + if (!SUPPORTED_OS.includes(trimmed)) { + console.warn("Sync: Invalid OS", osFromPragma); + if (window !== null) { + window.showWarningMessage( + localize( + "cmd.updateSettings.warning.OSNotSupported", + trimmed, + lines.indexOf(block) + ) + ); + } + } + } + + const hostMatch: RegExpMatchArray = block.match( + this.HostPragmaWhiteSpacesSupportRegExp + ); + if (hostMatch !== null) { + const hostFromPragma = hostMatch[1] || hostMatch[2] || hostMatch[3]; + if (hostFromPragma !== "" && /\s/.test(hostFromPragma)) { + newBlock = block.replace( + hostFromPragma, + hostFromPragma.trimLeft() + ); + result = result.replace(block, newBlock); + + block = newBlock; + } + } + + const envMatch: RegExpMatchArray = block.match( + this.EnvPragmaWhiteSpacesSupportRegExp + ); + if (envMatch !== null) { + const envFromPragma = envMatch[1] || envMatch[2] || envMatch[3]; + if (envFromPragma !== "" && /\s/.test(envFromPragma)) { + result = result.replace( + block, + block.replace(envFromPragma, envFromPragma.trimLeft()) + ); + } + } + } catch (e) { + console.log("Sync: Proccess before upload error.", e.message); + continue; + } + } + } + + return result; + } + + public static removeIgnoreBlocks(settingsContent: string): string { + let result: string = settingsContent; + result = result.replace(/\@sync ignore/g, "@sync-ignore"); + const ignoreSettingsBlocks: RegExpMatchArray = result.match( + this.IgnorePragmaRegExp + ); + + if (ignoreSettingsBlocks !== null) { + for (const block of ignoreSettingsBlocks) { + result = result.replace(block, ""); + } + } + + return result; + } + + public static matchPragmaSettings(settingsContent: string): RegExpMatchArray { + return settingsContent.match(this.PragmaRegExp); + } + + /** + * Insert Javascript comment slashes + * + * @private + * @param {string} settingContent + * @param {string} line + * @returns {strign} + * @memberof PragmaUtil + */ + public static commentLineAfterBreak(block: string): string { + const settingLine = block.match(/\n[ \t]*(.+)/); + if ( + settingLine !== null && + settingLine[1] && + !settingLine[1].startsWith("//") + ) { + return block.replace(settingLine[1], l => "// " + l); + } + + return block; + } + + private static readonly PragmaRegExp: RegExp = /\/\/[ \t]*\@sync[ \t]+(?:os=.+[ \t]*)?(?:host=.+[ \t]*)?(?:env=.+[ \t]*)?\n[ \t]*.+,?/g; + private static readonly IgnorePragmaRegExp: RegExp = /\/\/[ \t]*\@sync-ignore.*\n.+,?/g; + private static readonly HostPragmaWhiteSpacesSupportRegExp = /(?:host=(.+)os=)|(?:host=(.+)env=)|host=(.+)\n?/; + private static readonly OSPragmaWhiteSpacesSupportRegExp = /(?:os=(.+)host=)|(?:os=(.+)env=)|os=(.+)\n?/; + private static readonly EnvPragmaWhiteSpacesSupportRegExp = /(?:env=(.+)host=)|(?:env=(.+)os=)|env=(.+)\n?/; +} From 741351c95db8ee6adc886cb51da65a98aa09796c Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 18:01:05 -0300 Subject: [PATCH 12/37] Added hostName property to CustomSettings --- src/setting.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/setting.ts b/src/setting.ts index c0177d2d..e514771c 100644 --- a/src/setting.ts +++ b/src/setting.ts @@ -52,4 +52,5 @@ export class CustomSettings { public lastDownload: Date = null; public githubEnterpriseUrl: string = null; public askGistName: boolean = false; + public hostName: string = null; } From 1664358b15f684d07cd5f00e6721e237f13f4829 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 18:02:07 -0300 Subject: [PATCH 13/37] Tests files added. Estructure test per feature. --- test/index.js | 8 ++++++ test/pragmaUtil/index.js | 45 +++++++++++++++++++++++++++++++ test/pragmaUtil/testSettings.json | 32 ++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 test/index.js create mode 100644 test/pragmaUtil/index.js create mode 100644 test/pragmaUtil/testSettings.json diff --git a/test/index.js b/test/index.js new file mode 100644 index 00000000..a5874a69 --- /dev/null +++ b/test/index.js @@ -0,0 +1,8 @@ +const chai = require("chai") +const expect = chai.expect + +global.expect = expect + +describe('PragmaUtil', () => { + require('./pragmaUtil') +}) \ No newline at end of file diff --git a/test/pragmaUtil/index.js b/test/pragmaUtil/index.js new file mode 100644 index 00000000..da3f129d --- /dev/null +++ b/test/pragmaUtil/index.js @@ -0,0 +1,45 @@ +const fs = require('fs') +const expect = require('chai').expect + +let test_settings = null + +const OSTypes = require('../../out/src/enums').OsType +const PragmaUtil = require('../../out/src/pragmaUtil').default + +describe('Process before upload', function () { + this.beforeAll(() => { + test_settings = fs.readFileSync(__dirname + '/testSettings.json', 'utf8') + }) + + it('should remove @sync-ignore and @sync ignore lines', () => { + expect(PragmaUtil.removeIgnoreBlocks(test_settings)).to.not.contains('@sync-ignore').and.not.contains('@sync ignore') + }) + + it('should trim os, host and env', () => { + expect(PragmaUtil.processBeforeUpload(test_settings)).to.match(/@sync os=linux host=trim env=TEST_ENV/) + }) + + it('should comment line after linebreak', () => { + const line = '// @sync host=mac1 os=_mac_\n\t"mac": 3,' + expect(PragmaUtil.commentLineAfterBreak(line)).to.match(/\/\/\s"mac"/) + }) + + it('should get eight @sync pragma valid lines', () => { + const processed = PragmaUtil.processBeforeUpload(test_settings) + expect(PragmaUtil.matchPragmaSettings(processed).length).to.be.equals(8) + }) + + it('should not comment os=linux settings lines', () => { + let processed = PragmaUtil.processBeforeUpload(test_settings) + processed = PragmaUtil.processBeforeWrite(processed, OSTypes['Linux'], null) + expect(processed).to.match(/\s+"not_commented"/) + + }) + + + it('should leave only settings that matches with os=mac host=mac2 env=TEST_ENV', () => { + const processed = PragmaUtil.processBeforeUpload(test_settings) + process.env["TEST_ENV"] = true + expect(PragmaUtil.processBeforeWrite(processed, OSTypes['Mac'], 'mac2')).to.match(/\n\s+"mac2"/).and.match(/\n\s+"mactest"/) + }) +}) \ No newline at end of file diff --git a/test/pragmaUtil/testSettings.json b/test/pragmaUtil/testSettings.json new file mode 100644 index 00000000..b09b43ae --- /dev/null +++ b/test/pragmaUtil/testSettings.json @@ -0,0 +1,32 @@ +{ + + // @sync os=windows host=test + "test1": 1, + + // @sync os=windows host=test + "test2": 2, + + // @sync os= linux host= trim env= TEST_ENV + "test3": 3, + + // @sync os=linux + "not_commented": 2, + + // @sync host=_mac1_ os=mac + "mac": 3, + + // @sync host=mac2 os=mac env=TEST_ENV + "mac2": 3, + + // @sync os=mac + "mactest": "", + + // @sync host=test1 + "onlyHost": "", + + // @sync-ignore + "test4": 12, + + // @sync ignore + "test5": 12 +} \ No newline at end of file From 681066ece966ca243eeda831adb10cef2dbc6062 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Sun, 7 Oct 2018 18:08:49 -0300 Subject: [PATCH 14/37] Added some documentation. hostName property on custom config. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c10b065b..82ce88ba 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ 5. Auto upload Settings on file change. 6. Share the Gist with other users and let them download your settings. 7. Supports GitHub Enterprise +8. Support pragmas with @sync keywords: host, os and env are supported. ``` @@ -247,7 +248,8 @@ You can customize the sync: "useCliBaseInstallation": true, "lastUpload": null, "lastDownload": null, - "githubEnterpriseUrl": null + "githubEnterpriseUrl": null, + "hostName": null } ``` From ac8be0daf55b892c591c75c9bbbde53ac55324ca Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 8 Oct 2018 00:13:40 -0300 Subject: [PATCH 15/37] uncomment lines function. Uncomment all @sync settings before upload. Only insert comments if it doesn't match with matchine os or host or env. Uncomment line before write if it matched. --- src/pragmaUtil.ts | 32 ++++++++++++++++++++++++------- test/pragmaUtil/index.js | 16 ++++++++++++++++ test/pragmaUtil/testSettings.json | 4 ++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index ca6fc04d..0aa2d561 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -42,9 +42,8 @@ export default class PragmaUtil { ); if (pragmaSettingsBlocks !== null) { - for (let block of pragmaSettingsBlocks) { + for (const block of pragmaSettingsBlocks) { // line e.g.: // @sync os=windows host=Laptop\n"window.menuBarVisibility": "none", - try { // check OS pragma const osMatch: RegExpMatchArray = block.match(/os=(\w+)/); @@ -78,10 +77,14 @@ export default class PragmaUtil { const envMatch: RegExpMatchArray = block.match(/env=(\S+)/); if (envMatch !== null) { const envFromPragma = envMatch[1]; - if (!process.env[envFromPragma.toUpperCase()]) { + if (process.env[envFromPragma.toUpperCase()] === undefined) { result = result.replace(block, this.commentLineAfterBreak(block)); + continue; } } + + // if os, host and evn matched the current machine make sure to uncomment the setting + result = result.replace(block, this.uncommentLineAfterBreak(block)); } catch (e) { continue; } @@ -163,12 +166,14 @@ export default class PragmaUtil { if (envMatch !== null) { const envFromPragma = envMatch[1] || envMatch[2] || envMatch[3]; if (envFromPragma !== "" && /\s/.test(envFromPragma)) { - result = result.replace( - block, - block.replace(envFromPragma, envFromPragma.trimLeft()) - ); + newBlock = block.replace(envFromPragma, envFromPragma.trimLeft()); + result = result.replace(block, newBlock); + block = newBlock; } } + + // uncomment line before upload + result = result.replace(block, this.uncommentLineAfterBreak(block)); } catch (e) { console.log("Sync: Proccess before upload error.", e.message); continue; @@ -221,6 +226,19 @@ export default class PragmaUtil { return block; } + public static uncommentLineAfterBreak(block: string): string { + const settingLine = block.match(/\n[ \t]*(.+)/); + if ( + settingLine !== null && + settingLine[1] && + settingLine[1].startsWith("//") + ) { + return block.replace(settingLine[1], l => l.replace("//", "")); + } + + return block; + } + private static readonly PragmaRegExp: RegExp = /\/\/[ \t]*\@sync[ \t]+(?:os=.+[ \t]*)?(?:host=.+[ \t]*)?(?:env=.+[ \t]*)?\n[ \t]*.+,?/g; private static readonly IgnorePragmaRegExp: RegExp = /\/\/[ \t]*\@sync-ignore.*\n.+,?/g; private static readonly HostPragmaWhiteSpacesSupportRegExp = /(?:host=(.+)os=)|(?:host=(.+)env=)|host=(.+)\n?/; diff --git a/test/pragmaUtil/index.js b/test/pragmaUtil/index.js index da3f129d..40dc4c63 100644 --- a/test/pragmaUtil/index.js +++ b/test/pragmaUtil/index.js @@ -24,11 +24,27 @@ describe('Process before upload', function () { expect(PragmaUtil.commentLineAfterBreak(line)).to.match(/\/\/\s"mac"/) }) + it('should uncomment line after linebreak', () => { + const line = '// @sync host=mac1 os=_mac_\n\t//"mac": 3,' + expect(PragmaUtil.uncommentLineAfterBreak(line)).to.match(/\t"mac"/) + }) + it('should get eight @sync pragma valid lines', () => { const processed = PragmaUtil.processBeforeUpload(test_settings) expect(PragmaUtil.matchPragmaSettings(processed).length).to.be.equals(8) }) + it('should uncomment all lines', () => { + const commentedSettings = ` + // @sync os=linux + // "window": 1, + // @sync os=mac + // "mac": 1 + ` + + expect(PragmaUtil.processBeforeUpload(commentedSettings)).to.match(/\s+"window"/).and.to.match(/\s+"mac"/) + }) + it('should not comment os=linux settings lines', () => { let processed = PragmaUtil.processBeforeUpload(test_settings) processed = PragmaUtil.processBeforeWrite(processed, OSTypes['Linux'], null) diff --git a/test/pragmaUtil/testSettings.json b/test/pragmaUtil/testSettings.json index b09b43ae..17a21cc3 100644 --- a/test/pragmaUtil/testSettings.json +++ b/test/pragmaUtil/testSettings.json @@ -7,7 +7,7 @@ "test2": 2, // @sync os= linux host= trim env= TEST_ENV - "test3": 3, + //"test2": 3, // @sync os=linux "not_commented": 2, @@ -16,7 +16,7 @@ "mac": 3, // @sync host=mac2 os=mac env=TEST_ENV - "mac2": 3, + //"mac2": 3, // @sync os=mac "mactest": "", From 484ed0d31484f7bfd17415c612b8e198fbd6349e Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 8 Oct 2018 03:34:21 -0300 Subject: [PATCH 16/37] Do replace ments one time per setting pragma. Test uncoment function. More redeable settings json for testing. --- src/pragmaUtil.ts | 32 ++++++++++++++++--------------- test/pragmaUtil/index.js | 11 +++++++++++ test/pragmaUtil/testSettings.json | 14 +++++++------- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index 0aa2d561..c355db79 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -114,19 +114,20 @@ export default class PragmaUtil { // alert not supported OS const pragmaMatches: RegExpMatchArray = result.match(this.PragmaRegExp); if (pragmaMatches) { - for (let block of pragmaMatches) { + for (const block of pragmaMatches) { try { - let newBlock: string; - const osMatch: RegExpMatchArray = block.match( + let newBlock: string = block; + const osMatch: RegExpMatchArray = newBlock.match( this.OSPragmaWhiteSpacesSupportRegExp ); if (osMatch !== null) { const osFromPragma = osMatch[1] || osMatch[2] || osMatch[3]; if (osFromPragma !== "" && /\s/.test(osFromPragma)) { - newBlock = block.replace(osFromPragma, osFromPragma.trimLeft()); - result = result.replace(block, newBlock); - block = newBlock; + newBlock = newBlock.replace( + osFromPragma, + osFromPragma.trimLeft() + ); } const trimmed = osFromPragma.toLowerCase().trim(); @@ -144,19 +145,16 @@ export default class PragmaUtil { } } - const hostMatch: RegExpMatchArray = block.match( + const hostMatch: RegExpMatchArray = newBlock.match( this.HostPragmaWhiteSpacesSupportRegExp ); if (hostMatch !== null) { const hostFromPragma = hostMatch[1] || hostMatch[2] || hostMatch[3]; if (hostFromPragma !== "" && /\s/.test(hostFromPragma)) { - newBlock = block.replace( + newBlock = newBlock.replace( hostFromPragma, hostFromPragma.trimLeft() ); - result = result.replace(block, newBlock); - - block = newBlock; } } @@ -166,14 +164,18 @@ export default class PragmaUtil { if (envMatch !== null) { const envFromPragma = envMatch[1] || envMatch[2] || envMatch[3]; if (envFromPragma !== "" && /\s/.test(envFromPragma)) { - newBlock = block.replace(envFromPragma, envFromPragma.trimLeft()); - result = result.replace(block, newBlock); - block = newBlock; + newBlock = newBlock.replace( + envFromPragma, + envFromPragma.trimLeft() + ); } } // uncomment line before upload - result = result.replace(block, this.uncommentLineAfterBreak(block)); + result = result.replace( + block, + this.uncommentLineAfterBreak(newBlock) + ); } catch (e) { console.log("Sync: Proccess before upload error.", e.message); continue; diff --git a/test/pragmaUtil/index.js b/test/pragmaUtil/index.js index 40dc4c63..8564c430 100644 --- a/test/pragmaUtil/index.js +++ b/test/pragmaUtil/index.js @@ -45,6 +45,17 @@ describe('Process before upload', function () { expect(PragmaUtil.processBeforeUpload(commentedSettings)).to.match(/\s+"window"/).and.to.match(/\s+"mac"/) }) + it('should uncomment lines before write file for os=linux', () => { + const commentedSettings = `{ + // @sync os=linux + // "linux": 1, + // @sync os=mac + "mac": 1 + }` + const processed = PragmaUtil.processBeforeWrite(commentedSettings, OSTypes['Linux'], null) + expect(processed).to.match(/\s+"linux"/).and.to.match(/.+\/\/\s+"mac"/) + }) + it('should not comment os=linux settings lines', () => { let processed = PragmaUtil.processBeforeUpload(test_settings) processed = PragmaUtil.processBeforeWrite(processed, OSTypes['Linux'], null) diff --git a/test/pragmaUtil/testSettings.json b/test/pragmaUtil/testSettings.json index 17a21cc3..de217cce 100644 --- a/test/pragmaUtil/testSettings.json +++ b/test/pragmaUtil/testSettings.json @@ -1,19 +1,19 @@ { - // @sync os=windows host=test - "test1": 1, + // @sync os=windows host=h1 + "setting.test": 1, - // @sync os=windows host=test - "test2": 2, + // @sync os=windows host=h2 + "setting.test": 2, // @sync os= linux host= trim env= TEST_ENV - //"test2": 3, + //"setting.test": 3, // @sync os=linux - "not_commented": 2, + "not_commented": 4, // @sync host=_mac1_ os=mac - "mac": 3, + "setting.test": 5, // @sync host=mac2 os=mac env=TEST_ENV //"mac2": 3, From 9ba0d02532739b1dcb5ef3e19b898e6805684af2 Mon Sep 17 00:00:00 2001 From: Shan Khan Date: Mon, 8 Oct 2018 13:40:13 +0500 Subject: [PATCH 17/37] Test Directory --- test/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/index.ts diff --git a/test/index.ts b/test/index.ts new file mode 100644 index 00000000..e69de29b From 692d2f2bb597515f956375251bc3a58a7d2acce2 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 8 Oct 2018 20:24:04 -0300 Subject: [PATCH 18/37] Add SUPPORTED_OS and osTypeFromString to environmentPath.ts as requested. Get OS from OsType enum. Remove os.hostName() --- src/environmentPath.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/environmentPath.ts b/src/environmentPath.ts index 9bef73a0..8e378fdc 100644 --- a/src/environmentPath.ts +++ b/src/environmentPath.ts @@ -7,6 +7,16 @@ import * as path from "path"; import * as vscode from "vscode"; import { OsType } from "./enums"; +export const SUPPORTED_OS: string[] = Object.keys(OsType) + .filter(k => !/\d/.test(k)) + .map(k => k.toLowerCase()); // . ["windows", "linux", "mac"]; + +export function osTypeFromString(osName: string): OsType { + const capitalized: string = + osName[0].toUpperCase() + osName.substr(1).toLowerCase(); + return OsType[capitalized]; +} + export class Environment { public static CURRENT_VERSION: number = 312; public static getVersion(): string { @@ -27,7 +37,6 @@ export class Environment { public ExtensionFolder: string = null; public PATH: string = null; public OsType: OsType = null; - public HostName: string = null; public FILE_SETTING: string = null; public FILE_LAUNCH: string = null; @@ -74,7 +83,6 @@ export class Environment { process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; this.PATH = process.env.APPDATA; this.OsType = OsType.Windows; - this.HostName = os.hostname(); if (!this.PATH) { if (process.platform === "darwin") { From 123c68a31b6e8fbd4e7c03305569527d137b2167 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 8 Oct 2018 20:27:27 -0300 Subject: [PATCH 19/37] Check valid JSON before writting file. All the comments and trilling commas are removed. Must check this. If not valid OS is detected inform user. Added function to remove comments from text. --- src/pragmaUtil.ts | 168 ++++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 81 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index c355db79..a4075248 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -1,19 +1,7 @@ import { OsType } from "./enums"; +import { osTypeFromString, SUPPORTED_OS } from "./environmentPath"; import localize from "./localize"; -const SUPPORTED_OS = ["windows", "linux", "mac"]; - -export function GetOsEnum(osName: string): OsType { - switch (osName.toLowerCase()) { - case "windows": - return OsType.Windows; - case "linux": - return OsType.Linux; - case "mac": - return OsType.Mac; - } -} - /** * Comment/Uncomment lines if matches OS name or Hostname. * Usage: @sync os=[OS name] host=[hostName] @@ -42,27 +30,36 @@ export default class PragmaUtil { ); if (pragmaSettingsBlocks !== null) { + let osMatch: RegExpMatchArray; + let osFromPragma: string; + + let hostMatch: RegExpMatchArray; + let hostFromPragma: string; + + let envMatch: RegExpMatchArray; + let envFromPragma: string; + for (const block of pragmaSettingsBlocks) { // line e.g.: // @sync os=windows host=Laptop\n"window.menuBarVisibility": "none", try { // check OS pragma - const osMatch: RegExpMatchArray = block.match(/os=(\w+)/); + osMatch = block.match(/os=(\w+)/); if (osMatch !== null) { - const osFromPragma = osMatch[1].toLowerCase(); + osFromPragma = osMatch[1].toLowerCase(); if (!SUPPORTED_OS.includes(osFromPragma)) { continue; } - if (GetOsEnum(osFromPragma) !== osType) { + if (osTypeFromString(osFromPragma) !== osType) { result = result.replace(block, this.commentLineAfterBreak(block)); continue; // no need to lookup the host name } } // check host pragma - const hostMatch: RegExpMatchArray = block.match(/host=(\S+)/); + hostMatch = block.match(/host=(\S+)/); if (hostMatch !== null) { - const hostFromPragma = hostMatch[1]; + hostFromPragma = hostMatch[1]; if ( hostName === null || hostName === "" || @@ -74,9 +71,9 @@ export default class PragmaUtil { } // check env pragma - const envMatch: RegExpMatchArray = block.match(/env=(\S+)/); + envMatch = block.match(/env=(\S+)/); if (envMatch !== null) { - const envFromPragma = envMatch[1]; + envFromPragma = envMatch[1]; if (process.env[envFromPragma.toUpperCase()] === undefined) { result = result.replace(block, this.commentLineAfterBreak(block)); continue; @@ -86,6 +83,7 @@ export default class PragmaUtil { // if os, host and evn matched the current machine make sure to uncomment the setting result = result.replace(block, this.uncommentLineAfterBreak(block)); } catch (e) { + console.error("Sync: Error processing pragmas ", e.message); continue; } } @@ -93,6 +91,19 @@ export default class PragmaUtil { result = this.removeIgnoreBlocks(result); + // check is a valid JSON + + try { + // remove comments and trailing comma + const uncommented = this.removeAllComments(result).replace( + /,\s*\}/g, + " }" + ); + JSON.parse(uncommented); + } catch (e) { + console.error("Sync: Result content is not a valid JSON.", e.message); + } + return result; } @@ -101,85 +112,76 @@ export default class PragmaUtil { * * @static * @param {string} settingsContent - * @param {require('vscode').window} window * @returns {string} * @memberof PragmaUtil */ - public static processBeforeUpload(settingsContent: string, window): string { + public static processBeforeUpload(settingsContent: string): string { let result: string = settingsContent; result = this.removeIgnoreBlocks(result); - const lines = result.split("\n"); + const lines = result.split("\n").map(l => l.trim()); // alert not supported OS const pragmaMatches: RegExpMatchArray = result.match(this.PragmaRegExp); if (pragmaMatches) { - for (const block of pragmaMatches) { - try { - let newBlock: string = block; - const osMatch: RegExpMatchArray = newBlock.match( - this.OSPragmaWhiteSpacesSupportRegExp - ); - if (osMatch !== null) { - const osFromPragma = osMatch[1] || osMatch[2] || osMatch[3]; + let newBlock: string; - if (osFromPragma !== "" && /\s/.test(osFromPragma)) { - newBlock = newBlock.replace( - osFromPragma, - osFromPragma.trimLeft() - ); - } + let osMatch: RegExpMatchArray; + let osFromPragma: string; - const trimmed = osFromPragma.toLowerCase().trim(); - if (!SUPPORTED_OS.includes(trimmed)) { - console.warn("Sync: Invalid OS", osFromPragma); - if (window !== null) { - window.showWarningMessage( - localize( - "cmd.updateSettings.warning.OSNotSupported", - trimmed, - lines.indexOf(block) - ) - ); - } - } + let hostMatch: RegExpMatchArray; + let hostFromPragma: string; + + let envMatch: RegExpMatchArray; + let envFromPragma: string; + + for (const block of pragmaMatches) { + newBlock = block; + osMatch = newBlock.match(this.OSPragmaWhiteSpacesSupportRegExp); + if (osMatch !== null) { + osFromPragma = osMatch[1] || osMatch[2] || osMatch[3]; + + if (osFromPragma !== "" && /\s/.test(osFromPragma)) { + newBlock = newBlock.replace(osFromPragma, osFromPragma.trimLeft()); } - const hostMatch: RegExpMatchArray = newBlock.match( - this.HostPragmaWhiteSpacesSupportRegExp - ); - if (hostMatch !== null) { - const hostFromPragma = hostMatch[1] || hostMatch[2] || hostMatch[3]; - if (hostFromPragma !== "" && /\s/.test(hostFromPragma)) { - newBlock = newBlock.replace( - hostFromPragma, - hostFromPragma.trimLeft() - ); - } + const trimmed = osFromPragma.toLowerCase().trim(); + if (!SUPPORTED_OS.includes(trimmed)) { + console.warn("Sync: Invalid OS", osFromPragma); + throw new Error( + localize( + "cmd.updateSettings.warning.OSNotSupported", + trimmed, + lines.indexOf(block.split("\n")[0]) + 1 + ) + ); } + } - const envMatch: RegExpMatchArray = block.match( - this.EnvPragmaWhiteSpacesSupportRegExp - ); - if (envMatch !== null) { - const envFromPragma = envMatch[1] || envMatch[2] || envMatch[3]; - if (envFromPragma !== "" && /\s/.test(envFromPragma)) { - newBlock = newBlock.replace( - envFromPragma, - envFromPragma.trimLeft() - ); - } + hostMatch = newBlock.match(this.HostPragmaWhiteSpacesSupportRegExp); + if (hostMatch !== null) { + hostFromPragma = hostMatch[1] || hostMatch[2] || hostMatch[3]; + if (hostFromPragma !== "" && /\s/.test(hostFromPragma)) { + newBlock = newBlock.replace( + hostFromPragma, + hostFromPragma.trimLeft() + ); } + } - // uncomment line before upload - result = result.replace( - block, - this.uncommentLineAfterBreak(newBlock) - ); - } catch (e) { - console.log("Sync: Proccess before upload error.", e.message); - continue; + envMatch = block.match(this.EnvPragmaWhiteSpacesSupportRegExp); + if (envMatch !== null) { + envFromPragma = envMatch[1] || envMatch[2] || envMatch[3]; + if (envFromPragma !== "" && /\s/.test(envFromPragma)) { + newBlock = newBlock.replace( + envFromPragma, + envFromPragma.trimLeft() + ); + } } + + // uncomment line before upload + result = result.replace(block, this.uncommentLineAfterBreak(newBlock)); } } @@ -222,7 +224,7 @@ export default class PragmaUtil { settingLine[1] && !settingLine[1].startsWith("//") ) { - return block.replace(settingLine[1], l => "// " + l); + return block.replace(settingLine[1], l => "//" + l); } return block; @@ -241,6 +243,10 @@ export default class PragmaUtil { return block; } + public static removeAllComments(text: string): string { + return text.replace(/\s(\/\/.+)|(\/\*.+\*\/)/g, ""); + } + private static readonly PragmaRegExp: RegExp = /\/\/[ \t]*\@sync[ \t]+(?:os=.+[ \t]*)?(?:host=.+[ \t]*)?(?:env=.+[ \t]*)?\n[ \t]*.+,?/g; private static readonly IgnorePragmaRegExp: RegExp = /\/\/[ \t]*\@sync-ignore.*\n.+,?/g; private static readonly HostPragmaWhiteSpacesSupportRegExp = /(?:host=(.+)os=)|(?:host=(.+)env=)|host=(.+)\n?/; From 517de3d4be5b6df9ea49a1731fecd873ca457b00 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 8 Oct 2018 20:28:28 -0300 Subject: [PATCH 20/37] Chatch exception during upload. Should it abort upload? --- src/sync.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index 40257f07..5c96edcd 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -212,10 +212,15 @@ export class Sync { } if (snippetFile.fileName === env.FILE_SETTING_NAME) { - snippetFile.content = PragmaUtil.processBeforeUpload( - snippetFile.content, - vscode.window - ); + try { + snippetFile.content = PragmaUtil.processBeforeUpload( + snippetFile.content + ); + } catch (e) { + Commons.LogException(null, e.message, true); + console.error(e); + return; + } } } From 9542ea12ca2e1900964c9b32318b85487292b2fa Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 8 Oct 2018 20:30:10 -0300 Subject: [PATCH 21/37] Adding VS Code tests in typescript. Run test changing launch mode to "Launch Test". Remove javascript files. --- test/extension.test.ts | 3 ++ test/index.js | 8 ---- test/index.ts | 8 ++++ test/pragmaUtil/index.js | 72 ------------------------------- test/pragmaUtil/index.ts | 92 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 80 deletions(-) create mode 100644 test/extension.test.ts delete mode 100644 test/index.js delete mode 100644 test/pragmaUtil/index.js create mode 100644 test/pragmaUtil/index.ts diff --git a/test/extension.test.ts b/test/extension.test.ts new file mode 100644 index 00000000..1f248602 --- /dev/null +++ b/test/extension.test.ts @@ -0,0 +1,3 @@ +describe("PragmaUtil", () => { + require("./pragmaUtil"); +}); diff --git a/test/index.js b/test/index.js deleted file mode 100644 index a5874a69..00000000 --- a/test/index.js +++ /dev/null @@ -1,8 +0,0 @@ -const chai = require("chai") -const expect = chai.expect - -global.expect = expect - -describe('PragmaUtil', () => { - require('./pragmaUtil') -}) \ No newline at end of file diff --git a/test/index.ts b/test/index.ts index e69de29b..abb2dd76 100644 --- a/test/index.ts +++ b/test/index.ts @@ -0,0 +1,8 @@ +import * as testRunner from "vscode/lib/testrunner"; + +testRunner.configure({ + ui: "bdd", + useColors: true, + timeout: 5000 +}); +module.exports = testRunner; diff --git a/test/pragmaUtil/index.js b/test/pragmaUtil/index.js deleted file mode 100644 index 8564c430..00000000 --- a/test/pragmaUtil/index.js +++ /dev/null @@ -1,72 +0,0 @@ -const fs = require('fs') -const expect = require('chai').expect - -let test_settings = null - -const OSTypes = require('../../out/src/enums').OsType -const PragmaUtil = require('../../out/src/pragmaUtil').default - -describe('Process before upload', function () { - this.beforeAll(() => { - test_settings = fs.readFileSync(__dirname + '/testSettings.json', 'utf8') - }) - - it('should remove @sync-ignore and @sync ignore lines', () => { - expect(PragmaUtil.removeIgnoreBlocks(test_settings)).to.not.contains('@sync-ignore').and.not.contains('@sync ignore') - }) - - it('should trim os, host and env', () => { - expect(PragmaUtil.processBeforeUpload(test_settings)).to.match(/@sync os=linux host=trim env=TEST_ENV/) - }) - - it('should comment line after linebreak', () => { - const line = '// @sync host=mac1 os=_mac_\n\t"mac": 3,' - expect(PragmaUtil.commentLineAfterBreak(line)).to.match(/\/\/\s"mac"/) - }) - - it('should uncomment line after linebreak', () => { - const line = '// @sync host=mac1 os=_mac_\n\t//"mac": 3,' - expect(PragmaUtil.uncommentLineAfterBreak(line)).to.match(/\t"mac"/) - }) - - it('should get eight @sync pragma valid lines', () => { - const processed = PragmaUtil.processBeforeUpload(test_settings) - expect(PragmaUtil.matchPragmaSettings(processed).length).to.be.equals(8) - }) - - it('should uncomment all lines', () => { - const commentedSettings = ` - // @sync os=linux - // "window": 1, - // @sync os=mac - // "mac": 1 - ` - - expect(PragmaUtil.processBeforeUpload(commentedSettings)).to.match(/\s+"window"/).and.to.match(/\s+"mac"/) - }) - - it('should uncomment lines before write file for os=linux', () => { - const commentedSettings = `{ - // @sync os=linux - // "linux": 1, - // @sync os=mac - "mac": 1 - }` - const processed = PragmaUtil.processBeforeWrite(commentedSettings, OSTypes['Linux'], null) - expect(processed).to.match(/\s+"linux"/).and.to.match(/.+\/\/\s+"mac"/) - }) - - it('should not comment os=linux settings lines', () => { - let processed = PragmaUtil.processBeforeUpload(test_settings) - processed = PragmaUtil.processBeforeWrite(processed, OSTypes['Linux'], null) - expect(processed).to.match(/\s+"not_commented"/) - - }) - - - it('should leave only settings that matches with os=mac host=mac2 env=TEST_ENV', () => { - const processed = PragmaUtil.processBeforeUpload(test_settings) - process.env["TEST_ENV"] = true - expect(PragmaUtil.processBeforeWrite(processed, OSTypes['Mac'], 'mac2')).to.match(/\n\s+"mac2"/).and.match(/\n\s+"mactest"/) - }) -}) \ No newline at end of file diff --git a/test/pragmaUtil/index.ts b/test/pragmaUtil/index.ts new file mode 100644 index 00000000..36dce643 --- /dev/null +++ b/test/pragmaUtil/index.ts @@ -0,0 +1,92 @@ +import { expect } from "chai"; +import fs = require("fs"); +import { OsType } from "../../src/enums"; +import PragmaUtil from "../../src/pragmaUtil"; + +let testSettings = null; + +describe("Process before upload", function() { + this.beforeAll(() => { + testSettings = fs.readFileSync( + __dirname + "/../../../test/pragmaUtil/testSettings.json", + "utf8" + ); + }); + + it("should remove @sync-ignore and @sync ignore lines", () => { + expect(PragmaUtil.removeIgnoreBlocks(testSettings)) + .to.not.contains("@sync-ignore") + .and.not.contains("@sync ignore"); + }); + + it("should trim os, host and env", () => { + expect(PragmaUtil.processBeforeUpload(testSettings)).to.match( + /@sync os=linux host=trim env=TEST_ENV/ + ); + }); + + it("should comment line after linebreak", () => { + const line = '// @sync host=mac1 os=_mac_\n\t"mac": 3,'; + expect(PragmaUtil.commentLineAfterBreak(line)).to.match(/\/\/\s*"mac"/); + }); + + it("should uncomment line after linebreak", () => { + const line = '// @sync host=mac1 os=_mac_\n\t//"mac": 3,'; + expect(PragmaUtil.uncommentLineAfterBreak(line)).to.match(/\s*"mac"/); + }); + + it("should get eight @sync pragma valid lines", () => { + const processed = PragmaUtil.processBeforeUpload(testSettings); + expect(PragmaUtil.matchPragmaSettings(processed).length).to.be.equals(8); + }); + + it("should uncomment all lines", () => { + const commentedSettings = ` + // @sync os=linux + // "window": 1, + // @sync os=mac + // "mac": 1 + `; + + expect(PragmaUtil.processBeforeUpload(commentedSettings)) + .to.match(/\s+"window"/) + .and.to.match(/\s+"mac"/); + }); + + it("should uncomment lines before write file for os=linux", () => { + const commentedSettings = `{ + // @sync os=linux + // "linux": 1, + // @sync os=mac + "mac": 1 + }`; + const processed = PragmaUtil.processBeforeWrite( + commentedSettings, + OsType.Linux, + null + ); + expect(processed) + .to.match(/\s+"linux"/) + .and.to.match(/.+\/\/"mac"/); + }); + + it("should not comment os=linux settings lines", () => { + let processed = PragmaUtil.processBeforeUpload(testSettings); + processed = PragmaUtil.processBeforeWrite(processed, OsType.Linux, null); + expect(processed).to.match(/\s+"not_commented"/); + }); + + it("should leave only settings that matches with os=mac host=mac2 env=TEST_ENV", () => { + const processed = PragmaUtil.processBeforeUpload(testSettings); + // tslint:disable-next-line:no-string-literal + process.env["TEST_ENV"] = "1"; + expect(PragmaUtil.processBeforeWrite(processed, OsType.Mac, "mac2")) + .to.match(/\n\s+"mac2"/) + .and.match(/\n\s+"mactest"/); + }); + + it("should remove all comments and parse JSON", () => { + const possibleJson = PragmaUtil.removeAllComments(testSettings); + expect(JSON.parse.bind(null, possibleJson)).to.not.throw(); + }); +}); From 5755226d5b1e173f55c01f03c10c6e86bcb8ed0f Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 24 Oct 2018 00:00:10 -0300 Subject: [PATCH 22/37] Fix bug with the first line of settings.json. Get the local content and extract the ignored lines before writing the new settings. Add the ignored lines at the beginning of the settings object. --- src/pragmaUtil.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index a4075248..1f515b58 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -14,16 +14,17 @@ export default class PragmaUtil { * Process @sync pragma statements before file is saved. * Comment lines that don't match with the user OS or host value. * @static - * @param {string} settingsContent a valid JSON string + * @param {string} newContent a valid JSON string * @returns {string} * @memberof PragmaUtil */ public static processBeforeWrite( - settingsContent: string, + localContent: string, + newContent: string, osType: OsType, hostName: string ): string { - let result: string = settingsContent; + let result: string = newContent; const pragmaSettingsBlocks: RegExpMatchArray = result.match( this.PragmaRegExp @@ -90,7 +91,8 @@ export default class PragmaUtil { } result = this.removeIgnoreBlocks(result); - + const ignoredBlocks = this.getIgnoredBlocks(localContent); // get the settings that must prevale + result = result.replace(/{\s*\n/, `{\n\ ${ignoredBlocks}\n\n`); // always formated with four spaces? // check is a valid JSON try { @@ -204,6 +206,17 @@ export default class PragmaUtil { return result; } + public static getIgnoredBlocks(content: string): string { + content = content.replace(/\@sync ignore/g, "@sync-ignore"); + const ignoredBlocks: RegExpMatchArray = content.match( + this.IgnorePragmaRegExp + ); + if (ignoredBlocks == null) { + return ""; + } + return ignoredBlocks.join(""); + } + public static matchPragmaSettings(settingsContent: string): RegExpMatchArray { return settingsContent.match(this.PragmaRegExp); } @@ -244,7 +257,7 @@ export default class PragmaUtil { } public static removeAllComments(text: string): string { - return text.replace(/\s(\/\/.+)|(\/\*.+\*\/)/g, ""); + return text.replace(/\s*(\/\/.+)|(\/\*.+\*\/)/g, ""); } private static readonly PragmaRegExp: RegExp = /\/\/[ \t]*\@sync[ \t]+(?:os=.+[ \t]*)?(?:host=.+[ \t]*)?(?:env=.+[ \t]*)?\n[ \t]*.+,?/g; From 28278a008ae71a998643287faa565e42971f6ff3 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 24 Oct 2018 00:00:59 -0300 Subject: [PATCH 23/37] Read local file in order to process ignored settings before writing the file. --- src/sync.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sync.ts b/src/sync.ts index 2a68fc84..8b4bd6b3 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -597,7 +597,9 @@ export class Sync { } if (file.gistName === env.FILE_SETTING_NAME) { + const localContent = await FileService.ReadFile(filePath); content = PragmaUtil.processBeforeWrite( + localContent, content, env.OsType, localSettings.customConfig.hostName From 338d9cc969ea10e25b71deb1d311c411063d0ad8 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 24 Oct 2018 00:02:27 -0300 Subject: [PATCH 24/37] Addapt test to changes. --- test/pragmaUtil/index.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/pragmaUtil/index.ts b/test/pragmaUtil/index.ts index 36dce643..f06c954d 100644 --- a/test/pragmaUtil/index.ts +++ b/test/pragmaUtil/index.ts @@ -61,6 +61,7 @@ describe("Process before upload", function() { "mac": 1 }`; const processed = PragmaUtil.processBeforeWrite( + commentedSettings, commentedSettings, OsType.Linux, null @@ -72,7 +73,12 @@ describe("Process before upload", function() { it("should not comment os=linux settings lines", () => { let processed = PragmaUtil.processBeforeUpload(testSettings); - processed = PragmaUtil.processBeforeWrite(processed, OsType.Linux, null); + processed = PragmaUtil.processBeforeWrite( + processed, + processed, + OsType.Linux, + null + ); expect(processed).to.match(/\s+"not_commented"/); }); @@ -80,7 +86,9 @@ describe("Process before upload", function() { const processed = PragmaUtil.processBeforeUpload(testSettings); // tslint:disable-next-line:no-string-literal process.env["TEST_ENV"] = "1"; - expect(PragmaUtil.processBeforeWrite(processed, OsType.Mac, "mac2")) + expect( + PragmaUtil.processBeforeWrite(processed, processed, OsType.Mac, "mac2") + ) .to.match(/\n\s+"mac2"/) .and.match(/\n\s+"mactest"/); }); From 511f1e0b6068cb787a54f0dcfabc0ade05d919d1 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 24 Oct 2018 00:24:19 -0300 Subject: [PATCH 25/37] Get tabs or spaces and line breaks for each ignored line. --- src/pragmaUtil.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index 1f515b58..3dc0b61c 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -92,7 +92,7 @@ export default class PragmaUtil { result = this.removeIgnoreBlocks(result); const ignoredBlocks = this.getIgnoredBlocks(localContent); // get the settings that must prevale - result = result.replace(/{\s*\n/, `{\n\ ${ignoredBlocks}\n\n`); // always formated with four spaces? + result = result.replace(/{\s*\n/, `{\n${ignoredBlocks}\n`); // always formated with four spaces? // check is a valid JSON try { @@ -261,7 +261,7 @@ export default class PragmaUtil { } private static readonly PragmaRegExp: RegExp = /\/\/[ \t]*\@sync[ \t]+(?:os=.+[ \t]*)?(?:host=.+[ \t]*)?(?:env=.+[ \t]*)?\n[ \t]*.+,?/g; - private static readonly IgnorePragmaRegExp: RegExp = /\/\/[ \t]*\@sync-ignore.*\n.+,?/g; + private static readonly IgnorePragmaRegExp: RegExp = /[ \t]*\/\/[ \t]*\@sync-ignore.*\n.+,?\n+/g; private static readonly HostPragmaWhiteSpacesSupportRegExp = /(?:host=(.+)os=)|(?:host=(.+)env=)|host=(.+)\n?/; private static readonly OSPragmaWhiteSpacesSupportRegExp = /(?:os=(.+)host=)|(?:os=(.+)env=)|os=(.+)\n?/; private static readonly EnvPragmaWhiteSpacesSupportRegExp = /(?:env=(.+)host=)|(?:env=(.+)os=)|env=(.+)\n?/; From b4d376d4823f3abb90e0834b5396953c8ba93373 Mon Sep 17 00:00:00 2001 From: MarvinJWendt Date: Tue, 18 Dec 2018 15:09:46 +0100 Subject: [PATCH 26/37] Optimize images (#740) *Total -- 20.98kb -> 13.27kb (36.77%) /images/slack.png -- 13.83kb -> 8.04kb (41.9%) /images/cloud.png -- 7.15kb -> 5.23kb (26.84%) --- images/cloud.png | Bin 7324 -> 5358 bytes images/slack.png | Bin 14163 -> 8229 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/cloud.png b/images/cloud.png index bc48787e5abc188da1377c4011c8f52977e44000..51c7b6e71cbd2bbcf4265b3b33d23cca874f22f2 100644 GIT binary patch literal 5358 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 literal 7324 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

s!mJbkR@$zG& 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 WUMYQj`vYEf11QR>$&^c(1pE&mlwB Date: Tue, 8 Jan 2019 14:46:46 +0500 Subject: [PATCH 27/37] fix(package): update temp to version 0.9.0 (#737) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d11a5457..864d9216 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,6 @@ "fs-extra": "^7.0.1", "https-proxy-agent": "^2.2.1", "lockfile": "^1.0.4", - "temp": "^0.8.3" + "temp": "^0.9.0" } } From 384c8a4aa01b3552bf3f4c5ba32ad945c80037b8 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 14 Jan 2019 02:42:24 -0300 Subject: [PATCH 28/37] Remove unnecesary tests --- test/pragmaUtil/index.ts | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/test/pragmaUtil/index.ts b/test/pragmaUtil/index.ts index f06c954d..4945a83e 100644 --- a/test/pragmaUtil/index.ts +++ b/test/pragmaUtil/index.ts @@ -13,33 +13,12 @@ describe("Process before upload", function() { ); }); - it("should remove @sync-ignore and @sync ignore lines", () => { - expect(PragmaUtil.removeIgnoreBlocks(testSettings)) - .to.not.contains("@sync-ignore") - .and.not.contains("@sync ignore"); - }); - it("should trim os, host and env", () => { expect(PragmaUtil.processBeforeUpload(testSettings)).to.match( /@sync os=linux host=trim env=TEST_ENV/ ); }); - it("should comment line after linebreak", () => { - const line = '// @sync host=mac1 os=_mac_\n\t"mac": 3,'; - expect(PragmaUtil.commentLineAfterBreak(line)).to.match(/\/\/\s*"mac"/); - }); - - it("should uncomment line after linebreak", () => { - const line = '// @sync host=mac1 os=_mac_\n\t//"mac": 3,'; - expect(PragmaUtil.uncommentLineAfterBreak(line)).to.match(/\s*"mac"/); - }); - - it("should get eight @sync pragma valid lines", () => { - const processed = PragmaUtil.processBeforeUpload(testSettings); - expect(PragmaUtil.matchPragmaSettings(processed).length).to.be.equals(8); - }); - it("should uncomment all lines", () => { const commentedSettings = ` // @sync os=linux From 569e2436cdcae7566a86fa3a0d8321da9fbcc582 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 14 Jan 2019 02:45:55 -0300 Subject: [PATCH 29/37] New Approach for parsing pragmas Instead of replacing entires blocks, we will loop each line in the settings file. For each line we are checking if should be ignored or if comments must be toggled. There is a block of code that is repeated to ensure the readability of the code. --- src/pragmaUtil.ts | 261 ++++++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 125 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index 3dc0b61c..4937155f 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -24,27 +24,34 @@ export default class PragmaUtil { osType: OsType, hostName: string ): string { - let result: string = newContent; - - const pragmaSettingsBlocks: RegExpMatchArray = result.match( - this.PragmaRegExp - ); - - if (pragmaSettingsBlocks !== null) { - let osMatch: RegExpMatchArray; - let osFromPragma: string; - - let hostMatch: RegExpMatchArray; - let hostFromPragma: string; - - let envMatch: RegExpMatchArray; - let envFromPragma: string; + const parsedLines: string[] = []; + const lines = newContent.split("\n"); + let osMatch: RegExpMatchArray; + let osFromPragma: string; + + let hostMatch: RegExpMatchArray; + let hostFromPragma: string; + + let envMatch: RegExpMatchArray; + let envFromPragma: string; + let currentLine: string = ""; + + const parseLine = (line: string, shouldComment: boolean) => { + if (!line.trim().startsWith("//") && shouldComment) { + return "//" + line; + } else { + return line.replace("//", ""); + } + }; - for (const block of pragmaSettingsBlocks) { - // line e.g.: // @sync os=windows host=Laptop\n"window.menuBarVisibility": "none", + for (let index = 0; index < lines.length; index++) { + let shouldComment = false; + currentLine = lines[index]; + if (this.PragmaRegExp.test(currentLine)) { + parsedLines.push(currentLine); try { // check OS pragma - osMatch = block.match(/os=(\w+)/); + osMatch = currentLine.match(/os=(\w+)/); if (osMatch !== null) { osFromPragma = osMatch[1].toLowerCase(); @@ -52,13 +59,11 @@ export default class PragmaUtil { continue; } if (osTypeFromString(osFromPragma) !== osType) { - result = result.replace(block, this.commentLineAfterBreak(block)); - continue; // no need to lookup the host name + shouldComment = true; } } - // check host pragma - hostMatch = block.match(/host=(\S+)/); + hostMatch = currentLine.match(/host=(\S+)/); if (hostMatch !== null) { hostFromPragma = hostMatch[1]; if ( @@ -66,32 +71,53 @@ export default class PragmaUtil { hostName === "" || hostFromPragma.toLowerCase() !== hostName.toLowerCase() ) { - result = result.replace(block, this.commentLineAfterBreak(block)); - continue; + shouldComment = true; } } // check env pragma - envMatch = block.match(/env=(\S+)/); + envMatch = currentLine.match(/env=(\S+)/); if (envMatch !== null) { envFromPragma = envMatch[1]; if (process.env[envFromPragma.toUpperCase()] === undefined) { - result = result.replace(block, this.commentLineAfterBreak(block)); - continue; + shouldComment = true; } } - // if os, host and evn matched the current machine make sure to uncomment the setting - result = result.replace(block, this.uncommentLineAfterBreak(block)); + currentLine = lines[++index]; // check the next line for comments + parsedLines.push(parseLine(currentLine, shouldComment)); + if (currentLine.match(/".+"\s*:\s*{/)) { + let openedBlock = true; + while (openedBlock) { + currentLine = lines[++index]; + parsedLines.push(parseLine(currentLine, shouldComment)); + if (currentLine.indexOf("}") !== -1) { + openedBlock = false; + } + } + } } catch (e) { console.error("Sync: Error processing pragmas ", e.message); continue; } + } else if (this.IgnorePragmaRegExp.test(currentLine)) { + currentLine = lines[++index]; // ignore the following lines + if (currentLine.match(/".+"\s*:\s*{/)) { + let openedBlock = true; + while (openedBlock) { + index++; + if (currentLine.indexOf("}") !== -1) { + openedBlock = false; + } + } + } + } else { + parsedLines.push(currentLine); } } - result = this.removeIgnoreBlocks(result); - const ignoredBlocks = this.getIgnoredBlocks(localContent); // get the settings that must prevale + let result = parsedLines.join("\n"); + const ignoredBlocks = this.getIgnoredBlocks(localContent); // get the settings that must prevail result = result.replace(/{\s*\n/, `{\n${ignoredBlocks}\n`); // always formated with four spaces? // check is a valid JSON @@ -118,33 +144,44 @@ export default class PragmaUtil { * @memberof PragmaUtil */ public static processBeforeUpload(settingsContent: string): string { - let result: string = settingsContent; - result = this.removeIgnoreBlocks(result); - - const lines = result.split("\n").map(l => l.trim()); - - // alert not supported OS - const pragmaMatches: RegExpMatchArray = result.match(this.PragmaRegExp); - if (pragmaMatches) { - let newBlock: string; - - let osMatch: RegExpMatchArray; - let osFromPragma: string; - - let hostMatch: RegExpMatchArray; - let hostFromPragma: string; - - let envMatch: RegExpMatchArray; - let envFromPragma: string; - - for (const block of pragmaMatches) { - newBlock = block; - osMatch = newBlock.match(this.OSPragmaWhiteSpacesSupportRegExp); + const lines = settingsContent.split("\n"); + let osMatch: RegExpMatchArray; + let osFromPragma: string; + + let hostMatch: RegExpMatchArray; + let hostFromPragma: string; + + let envMatch: RegExpMatchArray; + let envFromPragma: string; + + const parsedLines: string[] = []; + let currentLine = ""; + + for (let index = 0; index < lines.length; index++) { + currentLine = lines[index]; + + if (this.IgnorePragmaRegExp.test(currentLine)) { + currentLine = lines[++index]; // ignore the following lines + if (currentLine.match(/".+"\s*:\s*{/)) { + let openedBlock = true; + while (openedBlock) { + index++; + if (currentLine.indexOf("}") !== -1) { + openedBlock = false; + } + } + } + } else if (this.PragmaRegExp.test(currentLine)) { + // alert not supported OS + osMatch = currentLine.match(this.OSPragmaWhiteSpacesSupportRegExp); if (osMatch !== null) { osFromPragma = osMatch[1] || osMatch[2] || osMatch[3]; if (osFromPragma !== "" && /\s/.test(osFromPragma)) { - newBlock = newBlock.replace(osFromPragma, osFromPragma.trimLeft()); + currentLine = currentLine.replace( + osFromPragma, + osFromPragma.trimLeft() + ); } const trimmed = osFromPragma.toLowerCase().trim(); @@ -154,114 +191,88 @@ export default class PragmaUtil { localize( "cmd.updateSettings.warning.OSNotSupported", trimmed, - lines.indexOf(block.split("\n")[0]) + 1 + index + 1 ) ); } } - hostMatch = newBlock.match(this.HostPragmaWhiteSpacesSupportRegExp); + hostMatch = currentLine.match(this.HostPragmaWhiteSpacesSupportRegExp); if (hostMatch !== null) { hostFromPragma = hostMatch[1] || hostMatch[2] || hostMatch[3]; if (hostFromPragma !== "" && /\s/.test(hostFromPragma)) { - newBlock = newBlock.replace( + currentLine = currentLine.replace( hostFromPragma, hostFromPragma.trimLeft() ); } } - envMatch = block.match(this.EnvPragmaWhiteSpacesSupportRegExp); + envMatch = currentLine.match(this.EnvPragmaWhiteSpacesSupportRegExp); if (envMatch !== null) { envFromPragma = envMatch[1] || envMatch[2] || envMatch[3]; if (envFromPragma !== "" && /\s/.test(envFromPragma)) { - newBlock = newBlock.replace( + currentLine = currentLine.replace( envFromPragma, envFromPragma.trimLeft() ); } } - // uncomment line before upload - result = result.replace(block, this.uncommentLineAfterBreak(newBlock)); - } - } - - return result; - } - - public static removeIgnoreBlocks(settingsContent: string): string { - let result: string = settingsContent; - result = result.replace(/\@sync ignore/g, "@sync-ignore"); - const ignoreSettingsBlocks: RegExpMatchArray = result.match( - this.IgnorePragmaRegExp - ); - - if (ignoreSettingsBlocks !== null) { - for (const block of ignoreSettingsBlocks) { - result = result.replace(block, ""); + parsedLines.push(currentLine); + currentLine = lines[++index]; // check the next line for comments + parsedLines.push(currentLine.replace("//", "")); + if (currentLine.match(/".+"\s*:\s*{/)) { + let openedBlock = true; + while (openedBlock) { + currentLine = lines[++index]; + parsedLines.push(currentLine.replace("//", "")); + if (currentLine.indexOf("}") !== -1) { + openedBlock = false; + } + } + } + } else { + parsedLines.push(currentLine); } } - return result; + return parsedLines.join("\n"); } public static getIgnoredBlocks(content: string): string { content = content.replace(/\@sync ignore/g, "@sync-ignore"); - const ignoredBlocks: RegExpMatchArray = content.match( - this.IgnorePragmaRegExp - ); - if (ignoredBlocks == null) { - return ""; - } - return ignoredBlocks.join(""); - } - - public static matchPragmaSettings(settingsContent: string): RegExpMatchArray { - return settingsContent.match(this.PragmaRegExp); - } - - /** - * Insert Javascript comment slashes - * - * @private - * @param {string} settingContent - * @param {string} line - * @returns {strign} - * @memberof PragmaUtil - */ - public static commentLineAfterBreak(block: string): string { - const settingLine = block.match(/\n[ \t]*(.+)/); - if ( - settingLine !== null && - settingLine[1] && - !settingLine[1].startsWith("//") - ) { - return block.replace(settingLine[1], l => "//" + l); - } - - return block; - } - - public static uncommentLineAfterBreak(block: string): string { - const settingLine = block.match(/\n[ \t]*(.+)/); - if ( - settingLine !== null && - settingLine[1] && - settingLine[1].startsWith("//") - ) { - return block.replace(settingLine[1], l => l.replace("//", "")); + const ignoredBlocks: string[] = []; + const lines = content.split("\n"); + let currentLine = ""; + for (let index = 0; index < lines.length; index++) { + currentLine = lines[index]; + if (this.IgnorePragmaRegExp.test(currentLine)) { + ignoredBlocks.push(currentLine); + currentLine = lines[++index]; + ignoredBlocks.push(currentLine); + + if (currentLine.match(/".+"\s*:\s*{/)) { + let openedBlock = true; + while (openedBlock) { + currentLine = lines[++index]; + ignoredBlocks.push(currentLine.replace("//", "")); + if (currentLine.indexOf("}") !== -1) { + openedBlock = false; + } + } + } + } } - - return block; + return ignoredBlocks.join("\n"); } public static removeAllComments(text: string): string { return text.replace(/\s*(\/\/.+)|(\/\*.+\*\/)/g, ""); } - private static readonly PragmaRegExp: RegExp = /\/\/[ \t]*\@sync[ \t]+(?:os=.+[ \t]*)?(?:host=.+[ \t]*)?(?:env=.+[ \t]*)?\n[ \t]*.+,?/g; - private static readonly IgnorePragmaRegExp: RegExp = /[ \t]*\/\/[ \t]*\@sync-ignore.*\n.+,?\n+/g; + private static readonly PragmaRegExp: RegExp = /\/{2}[\s\t]*\@sync[\s\t]+(?:os=.+[\s\t]*)?(?:host=.+[\s\t]*)?(?:env=.+[\s\t]*)?/; + private static readonly IgnorePragmaRegExp: RegExp = /\/{2}[\s\t]*\@sync-ignore/; private static readonly HostPragmaWhiteSpacesSupportRegExp = /(?:host=(.+)os=)|(?:host=(.+)env=)|host=(.+)\n?/; private static readonly OSPragmaWhiteSpacesSupportRegExp = /(?:os=(.+)host=)|(?:os=(.+)env=)|os=(.+)\n?/; private static readonly EnvPragmaWhiteSpacesSupportRegExp = /(?:env=(.+)host=)|(?:env=(.+)os=)|env=(.+)\n?/; From 51866a447a78fbd90cfa033d13e46e11f453b962 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 14 Jan 2019 04:02:09 -0300 Subject: [PATCH 30/37] Support for Brackets and unifying repeated code --- src/pragmaUtil.ts | 134 +++++++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 62 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index 4937155f..a566b8b9 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -48,7 +48,6 @@ export default class PragmaUtil { let shouldComment = false; currentLine = lines[index]; if (this.PragmaRegExp.test(currentLine)) { - parsedLines.push(currentLine); try { // check OS pragma osMatch = currentLine.match(/os=(\w+)/); @@ -83,34 +82,20 @@ export default class PragmaUtil { shouldComment = true; } } - - currentLine = lines[++index]; // check the next line for comments - parsedLines.push(parseLine(currentLine, shouldComment)); - if (currentLine.match(/".+"\s*:\s*{/)) { - let openedBlock = true; - while (openedBlock) { - currentLine = lines[++index]; - parsedLines.push(parseLine(currentLine, shouldComment)); - if (currentLine.indexOf("}") !== -1) { - openedBlock = false; - } - } - } + parsedLines.push(currentLine); + index = this.checkNextLines( + lines, + parsedLines, + index, + false, + shouldComment + ); } catch (e) { console.error("Sync: Error processing pragmas ", e.message); continue; } } else if (this.IgnorePragmaRegExp.test(currentLine)) { - currentLine = lines[++index]; // ignore the following lines - if (currentLine.match(/".+"\s*:\s*{/)) { - let openedBlock = true; - while (openedBlock) { - index++; - if (currentLine.indexOf("}") !== -1) { - openedBlock = false; - } - } - } + index = this.checkNextLines(lines, parsedLines, index, true, false); } else { parsedLines.push(currentLine); } @@ -161,16 +146,7 @@ export default class PragmaUtil { currentLine = lines[index]; if (this.IgnorePragmaRegExp.test(currentLine)) { - currentLine = lines[++index]; // ignore the following lines - if (currentLine.match(/".+"\s*:\s*{/)) { - let openedBlock = true; - while (openedBlock) { - index++; - if (currentLine.indexOf("}") !== -1) { - openedBlock = false; - } - } - } + index = this.checkNextLines(lines, parsedLines, index, true); } else if (this.PragmaRegExp.test(currentLine)) { // alert not supported OS osMatch = currentLine.match(this.OSPragmaWhiteSpacesSupportRegExp); @@ -220,18 +196,7 @@ export default class PragmaUtil { } parsedLines.push(currentLine); - currentLine = lines[++index]; // check the next line for comments - parsedLines.push(currentLine.replace("//", "")); - if (currentLine.match(/".+"\s*:\s*{/)) { - let openedBlock = true; - while (openedBlock) { - currentLine = lines[++index]; - parsedLines.push(currentLine.replace("//", "")); - if (currentLine.indexOf("}") !== -1) { - openedBlock = false; - } - } - } + index = this.checkNextLines(lines, parsedLines, index, false, false); } else { parsedLines.push(currentLine); } @@ -242,29 +207,24 @@ export default class PragmaUtil { public static getIgnoredBlocks(content: string): string { content = content.replace(/\@sync ignore/g, "@sync-ignore"); - const ignoredBlocks: string[] = []; + const ignoredLines: string[] = []; const lines = content.split("\n"); let currentLine = ""; for (let index = 0; index < lines.length; index++) { currentLine = lines[index]; if (this.IgnorePragmaRegExp.test(currentLine)) { - ignoredBlocks.push(currentLine); - currentLine = lines[++index]; - ignoredBlocks.push(currentLine); - - if (currentLine.match(/".+"\s*:\s*{/)) { - let openedBlock = true; - while (openedBlock) { - currentLine = lines[++index]; - ignoredBlocks.push(currentLine.replace("//", "")); - if (currentLine.indexOf("}") !== -1) { - openedBlock = false; - } - } - } + ignoredLines.push(currentLine); + index = this.checkNextLines( + lines, + ignoredLines, + index, + false, + false, + true + ); } } - return ignoredBlocks.join("\n"); + return ignoredLines.join("\n"); } public static removeAllComments(text: string): string { @@ -276,4 +236,54 @@ export default class PragmaUtil { private static readonly HostPragmaWhiteSpacesSupportRegExp = /(?:host=(.+)os=)|(?:host=(.+)env=)|host=(.+)\n?/; private static readonly OSPragmaWhiteSpacesSupportRegExp = /(?:os=(.+)host=)|(?:os=(.+)env=)|os=(.+)\n?/; private static readonly EnvPragmaWhiteSpacesSupportRegExp = /(?:env=(.+)host=)|(?:env=(.+)os=)|env=(.+)\n?/; + + private static toggleComments(line: string, shouldComment: boolean) { + if (shouldComment && !line.trim().startsWith("//")) { + return " //" + line; // 2 spaces as formmating + } else { + return line.replace("//", ""); + } + } + + // checks and advance index + private static checkNextLines( + lines: string[], + parsedLines: string[], + currentIndex: number, + shouldIgnore: boolean, + shouldComment: boolean = false, + checkTrailingComma: boolean = false + ): number { + let currentLine = lines[++currentIndex]; // check the next line for comments + if (!shouldIgnore) { + parsedLines.push(this.toggleComments(currentLine, shouldComment)); + } + + if (checkTrailingComma && !currentLine.trim().endsWith(",")) { + currentLine = currentLine.trimRight() + ","; + } + const opensCurlyBraces = /".+"\s*:\s*{/.test(currentLine); + const opensBrackets = /".+"\s*:\s*\[/.test(currentLine); + + let openedBlock = opensCurlyBraces || opensBrackets; + if (openedBlock) { + while (openedBlock) { + currentLine = lines[++currentIndex]; + if ( + (opensCurlyBraces && currentLine.indexOf("}") !== -1) || + (opensBrackets && currentLine.indexOf("]") !== -1) + ) { + if (checkTrailingComma && !currentLine.trim().endsWith(",")) { + currentLine = currentLine.trimRight() + ","; // we add a coma to avoid parse error when we paste the ignored settings at the beginning of the file + } + openedBlock = false; + } + if (!shouldIgnore) { + parsedLines.push(this.toggleComments(currentLine, shouldComment)); + } + } + } + + return currentIndex; + } } From a52b1a428b97d39d926295e4a2648101481a8071 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 14 Jan 2019 12:23:10 -0300 Subject: [PATCH 31/37] Test for multi-line settings supporting curly braces and brackets. Changing test text extension. --- test/pragmaUtil/index.ts | 25 ++++++++++++++++++- .../{testSettings.json => testSettings.txt} | 0 2 files changed, 24 insertions(+), 1 deletion(-) rename test/pragmaUtil/{testSettings.json => testSettings.txt} (100%) diff --git a/test/pragmaUtil/index.ts b/test/pragmaUtil/index.ts index 4945a83e..1b1b2b31 100644 --- a/test/pragmaUtil/index.ts +++ b/test/pragmaUtil/index.ts @@ -8,7 +8,7 @@ let testSettings = null; describe("Process before upload", function() { this.beforeAll(() => { testSettings = fs.readFileSync( - __dirname + "/../../../test/pragmaUtil/testSettings.json", + __dirname + "/../../../test/pragmaUtil/testSettings.txt", "utf8" ); }); @@ -76,4 +76,27 @@ describe("Process before upload", function() { const possibleJson = PragmaUtil.removeAllComments(testSettings); expect(JSON.parse.bind(null, possibleJson)).to.not.throw(); }); + + it("should parse multi-line settings", () => { + const commentedSettings = `{ + // @sync os=linux + // "multi": { + "setting": false, + }, + // @sync os=mac + "mac": 1 + }`; + const processed = PragmaUtil.processBeforeWrite( + commentedSettings, + commentedSettings, + OsType.Mac, + null + ); + expect(processed) + .to.match(/\/{2}\s+"multi"/) + .and.to.match(/\/{2}\s+"multi"/) + .and.to.match(/\/{2}\s+"setting"/) + .and.to.match(/\/{2}\s+},/) + .and.to.match(/\s+"mac"/); + }) }); diff --git a/test/pragmaUtil/testSettings.json b/test/pragmaUtil/testSettings.txt similarity index 100% rename from test/pragmaUtil/testSettings.json rename to test/pragmaUtil/testSettings.txt From 192049b9a4dc5bc4f15be90056f68981b77067e3 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Mon, 14 Jan 2019 12:24:18 -0300 Subject: [PATCH 32/37] Remove unused function. Add new lines after ignored ettings block. --- src/pragmaUtil.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index a566b8b9..4a70a768 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -36,14 +36,6 @@ export default class PragmaUtil { let envFromPragma: string; let currentLine: string = ""; - const parseLine = (line: string, shouldComment: boolean) => { - if (!line.trim().startsWith("//") && shouldComment) { - return "//" + line; - } else { - return line.replace("//", ""); - } - }; - for (let index = 0; index < lines.length; index++) { let shouldComment = false; currentLine = lines[index]; @@ -103,7 +95,7 @@ export default class PragmaUtil { let result = parsedLines.join("\n"); const ignoredBlocks = this.getIgnoredBlocks(localContent); // get the settings that must prevail - result = result.replace(/{\s*\n/, `{\n${ignoredBlocks}\n`); // always formated with four spaces? + result = result.replace(/{\s*\n/, `{\n${ignoredBlocks}\n\n\n`); // 3 lines breaks to separate from other settings // check is a valid JSON try { @@ -255,13 +247,15 @@ export default class PragmaUtil { checkTrailingComma: boolean = false ): number { let currentLine = lines[++currentIndex]; // check the next line for comments - if (!shouldIgnore) { - parsedLines.push(this.toggleComments(currentLine, shouldComment)); - } if (checkTrailingComma && !currentLine.trim().endsWith(",")) { currentLine = currentLine.trimRight() + ","; } + // nothing more to do, just add the line to the parsedLines array + if (!shouldIgnore) { + parsedLines.push(this.toggleComments(currentLine, shouldComment)); + } + const opensCurlyBraces = /".+"\s*:\s*{/.test(currentLine); const opensBrackets = /".+"\s*:\s*\[/.test(currentLine); From 33c9924a374e0cf8e28680a7b119c5ab03cd1c8f Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 1 May 2019 18:31:49 -0300 Subject: [PATCH 33/37] Comments added --- src/pragmaUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index 4a70a768..9ba7047a 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -237,7 +237,7 @@ export default class PragmaUtil { } } - // checks and advance index + // Checks and advance line reading index private static checkNextLines( lines: string[], parsedLines: string[], From 201f802908dd95938197e5c1459bdacc55aa2866 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 1 May 2019 20:59:35 -0300 Subject: [PATCH 34/37] Fix #754 The pragma util keeps a copy of the settings that are upload to the gist. The file is located at /settings.sync. If the content to be upload doesn't deffer from the local content after taking out the @sync-ignore, the file wont be uploaded. --- src/pragmaUtil.ts | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index da03a7ca..da2954c5 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -1,6 +1,8 @@ +import { ExtensionContext } from "vscode"; import { OsType } from "./enums"; import { osTypeFromString, SUPPORTED_OS } from "./environmentPath"; import localize from "./localize"; +import { FileService } from "./service/fileService"; /** * Comment/Uncomment lines if matches OS name or Hostname. @@ -122,7 +124,10 @@ export default class PragmaUtil { * @returns {string} * @memberof PragmaUtil */ - public static processBeforeUpload(settingsContent: string): string { + public static async processBeforeUpload( + settingsContent: string, + context: ExtensionContext + ): Promise { const lines = settingsContent.split("\n"); let osMatch: RegExpMatchArray; let osFromPragma: string; @@ -136,6 +141,17 @@ export default class PragmaUtil { const parsedLines: string[] = []; let currentLine = ""; + let settingsFileExists = false; + + if (context !== null) { + settingsFileExists = await FileService.FileExists( + `${context.globalStoragePath}/settings.sync` + ); + } + + // Compare each line between new content and existing settings file + let shouldUpload = false; + for (let index = 0; index < lines.length; index++) { currentLine = lines[index]; @@ -196,7 +212,28 @@ export default class PragmaUtil { } } - return parsedLines.join("\n"); + const result = parsedLines.join("\n"); + + if (settingsFileExists && context !== null) { + try { + const localSettingFile = await FileService.ReadFile( + `${context.globalStoragePath}/settings.sync` + ); + shouldUpload = localSettingFile !== result; + } catch (error) { + console.warn("Sync: Could not read local settings file", error.message); + } + } + + if (!settingsFileExists || shouldUpload) { + // Create or update local settings file + await FileService.WriteFile( + `${context.globalStoragePath}/settings.sync`, + result + ); + } + + return [result, shouldUpload]; } public static getIgnoredBlocks(content: string): string { From 99713afc7c47b59c59235a5407b9fa56088cfc92 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 1 May 2019 21:02:18 -0300 Subject: [PATCH 35/37] Fix #754: Sync object Checks the global storage path at constructor. Checks if the content of the setting file should be uploaded. --- src/sync.ts | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/sync.ts b/src/sync.ts index cd498faa..7e21adec 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -21,7 +21,10 @@ import PragmaUtil from "./pragmaUtil"; let globalCommonService: Commons; export class Sync { - constructor(private context: vscode.ExtensionContext) {} + constructor(private context: vscode.ExtensionContext) { + // Check global storage dir + FileService.CreateDirectory(context.globalStoragePath); + } /** * Run when extension have been activated */ @@ -100,7 +103,11 @@ export class Sync { localConfig.customConfig.token, localConfig.customConfig.githubEnterpriseUrl ); - await startGitProcess(localConfig.extConfig, localConfig.customConfig); + await startGitProcess.call( + this, + localConfig.extConfig, + localConfig.customConfig + ); } catch (error) { Commons.LogException(error, globalCommonService.ERROR_MESSAGE, true); return; @@ -219,25 +226,36 @@ export class Sync { for (const snippetFile of contentFiles) { if (snippetFile.fileName !== env.FILE_KEYBINDING_MAC) { if (snippetFile.content !== "") { + let shouldUpload = true; + if (snippetFile.fileName === env.FILE_KEYBINDING_NAME) { snippetFile.gistName = env.OsType === OsType.Mac ? env.FILE_KEYBINDING_MAC : env.FILE_KEYBINDING_DEFAULT; } - allSettingFiles.push(snippetFile); - } - } - if (snippetFile.fileName === env.FILE_SETTING_NAME) { - try { - snippetFile.content = PragmaUtil.processBeforeUpload( - snippetFile.content - ); - } catch (e) { - Commons.LogException(null, e.message, true); - console.error(e); - return; + if (snippetFile.fileName === env.FILE_SETTING_NAME) { + try { + const [ + content, + shouldUploadSettingsFile + ] = await PragmaUtil.processBeforeUpload( + snippetFile.content, + this.context + ); + snippetFile.content = content; + shouldUpload = shouldUploadSettingsFile; + } catch (e) { + Commons.LogException(null, e.message, true); + console.error(e); + return; + } + } + + if (shouldUpload) { + allSettingFiles.push(snippetFile); + } } } } From 0d2feac674a04e4f880f3b5c51ab827b48341b46 Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Wed, 1 May 2019 21:04:34 -0300 Subject: [PATCH 36/37] Update tests --- test/pragmaUtil/index.ts | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/test/pragmaUtil/index.ts b/test/pragmaUtil/index.ts index 1b1b2b31..a29d0bfa 100644 --- a/test/pragmaUtil/index.ts +++ b/test/pragmaUtil/index.ts @@ -13,13 +13,13 @@ describe("Process before upload", function() { ); }); - it("should trim os, host and env", () => { - expect(PragmaUtil.processBeforeUpload(testSettings)).to.match( - /@sync os=linux host=trim env=TEST_ENV/ - ); + it("should trim os, host and env", async done => { + const [result] = await PragmaUtil.processBeforeUpload(testSettings, null); + expect(result).to.match(/@sync os=linux host=trim env=TEST_ENV/); + done(); }); - it("should uncomment all lines", () => { + it("should uncomment all lines", async done => { const commentedSettings = ` // @sync os=linux // "window": 1, @@ -27,9 +27,15 @@ describe("Process before upload", function() { // "mac": 1 `; - expect(PragmaUtil.processBeforeUpload(commentedSettings)) + const [result] = await PragmaUtil.processBeforeUpload( + commentedSettings, + null + ); + expect(result) .to.match(/\s+"window"/) .and.to.match(/\s+"mac"/); + + done(); }); it("should uncomment lines before write file for os=linux", () => { @@ -50,8 +56,8 @@ describe("Process before upload", function() { .and.to.match(/.+\/\/"mac"/); }); - it("should not comment os=linux settings lines", () => { - let processed = PragmaUtil.processBeforeUpload(testSettings); + it("should not comment os=linux settings lines", async done => { + let [processed] = await PragmaUtil.processBeforeUpload(testSettings, null); processed = PragmaUtil.processBeforeWrite( processed, processed, @@ -59,10 +65,14 @@ describe("Process before upload", function() { null ); expect(processed).to.match(/\s+"not_commented"/); + done(); }); - it("should leave only settings that matches with os=mac host=mac2 env=TEST_ENV", () => { - const processed = PragmaUtil.processBeforeUpload(testSettings); + it("should leave only settings that matches with os=mac host=mac2 env=TEST_ENV", async done => { + const [processed] = await PragmaUtil.processBeforeUpload( + testSettings, + null + ); // tslint:disable-next-line:no-string-literal process.env["TEST_ENV"] = "1"; expect( @@ -70,6 +80,8 @@ describe("Process before upload", function() { ) .to.match(/\n\s+"mac2"/) .and.match(/\n\s+"mactest"/); + + done(); }); it("should remove all comments and parse JSON", () => { @@ -98,5 +110,5 @@ describe("Process before upload", function() { .and.to.match(/\/{2}\s+"setting"/) .and.to.match(/\/{2}\s+},/) .and.to.match(/\s+"mac"/); - }) + }); }); From a6adea381207b18a0ea66e1bbeab9735f107c3ad Mon Sep 17 00:00:00 2001 From: Brian Mayo Date: Fri, 3 May 2019 18:49:28 -0300 Subject: [PATCH 37/37] Merge 865 --- src/pragmaUtil.ts | 15 +++++++++++---- test/pragmaUtil/index.ts | 26 ++++++++++---------------- test/pragmaUtil/testSettings.txt | 3 +++ 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/pragmaUtil.ts b/src/pragmaUtil.ts index da2954c5..3f65d1fd 100644 --- a/src/pragmaUtil.ts +++ b/src/pragmaUtil.ts @@ -225,7 +225,7 @@ export default class PragmaUtil { } } - if (!settingsFileExists || shouldUpload) { + if ((!settingsFileExists || shouldUpload) && context !== null) { // Create or update local settings file await FileService.WriteFile( `${context.globalStoragePath}/settings.sync`, @@ -269,11 +269,18 @@ export default class PragmaUtil { private static readonly EnvPragmaWhiteSpacesSupportRegExp = /(?:env=(.+)host=)|(?:env=(.+)os=)|env=(.+)\n?/; private static toggleComments(line: string, shouldComment: boolean) { - if (shouldComment && !line.trim().startsWith("//")) { - return " //" + line; // 2 spaces as formmating + const isCommented = line.trim().startsWith("//"); + if (shouldComment) { + if (!isCommented) { + return " //" + line; // 2 spaces as formating + } } else { - return line.replace("//", ""); + if (isCommented) { + return line.replace("//", ""); + } } + + return line; } // Checks and advance line reading index diff --git a/test/pragmaUtil/index.ts b/test/pragmaUtil/index.ts index a29d0bfa..df15661c 100644 --- a/test/pragmaUtil/index.ts +++ b/test/pragmaUtil/index.ts @@ -13,29 +13,26 @@ describe("Process before upload", function() { ); }); - it("should trim os, host and env", async done => { + it("should trim os, host and env", async () => { const [result] = await PragmaUtil.processBeforeUpload(testSettings, null); - expect(result).to.match(/@sync os=linux host=trim env=TEST_ENV/); - done(); + await expect(result).to.match(/@sync os=linux host=trim env=TEST_ENV/); }); - it("should uncomment all lines", async done => { + it("should uncomment all lines", async () => { const commentedSettings = ` // @sync os=linux // "window": 1, // @sync os=mac - // "mac": 1 + // "server": "http://exmaple.com `; const [result] = await PragmaUtil.processBeforeUpload( commentedSettings, null ); - expect(result) + await expect(result) .to.match(/\s+"window"/) - .and.to.match(/\s+"mac"/); - - done(); + .and.to.match(/\s+"server"/); }); it("should uncomment lines before write file for os=linux", () => { @@ -53,10 +50,10 @@ describe("Process before upload", function() { ); expect(processed) .to.match(/\s+"linux"/) - .and.to.match(/.+\/\/"mac"/); + .and.to.match(/\s+\/\/\s+"mac"/); }); - it("should not comment os=linux settings lines", async done => { + it("should not comment os=linux settings lines", async () => { let [processed] = await PragmaUtil.processBeforeUpload(testSettings, null); processed = PragmaUtil.processBeforeWrite( processed, @@ -65,23 +62,20 @@ describe("Process before upload", function() { null ); expect(processed).to.match(/\s+"not_commented"/); - done(); }); - it("should leave only settings that matches with os=mac host=mac2 env=TEST_ENV", async done => { + it("should leave only settings that matches with os=mac host=mac2 env=TEST_ENV", async () => { const [processed] = await PragmaUtil.processBeforeUpload( testSettings, null ); // tslint:disable-next-line:no-string-literal process.env["TEST_ENV"] = "1"; - expect( + await expect( PragmaUtil.processBeforeWrite(processed, processed, OsType.Mac, "mac2") ) .to.match(/\n\s+"mac2"/) .and.match(/\n\s+"mactest"/); - - done(); }); it("should remove all comments and parse JSON", () => { diff --git a/test/pragmaUtil/testSettings.txt b/test/pragmaUtil/testSettings.txt index de217cce..14f05281 100644 --- a/test/pragmaUtil/testSettings.txt +++ b/test/pragmaUtil/testSettings.txt @@ -18,6 +18,9 @@ // @sync host=mac2 os=mac env=TEST_ENV //"mac2": 3, + // @sync host=mac2 + //"server": "http://example.com", + // @sync os=mac "mactest": "",