├── .gitignore ├── 3RDPARTY ├── DiffPlex.txt ├── Ookii.Dialogs.txt ├── OpenMCDF.txt ├── SharpZipLib.txt └── VBACompressionCodec.txt ├── LICENSE.md ├── LICENSE.rtf ├── MakeInstaller.iss ├── MakePortable.bat ├── README.md └── src ├── VBASync.Tests ├── IntegrationTests │ ├── Files │ │ ├── PublishToXlsmIsRepeatableFiles.Designer.cs │ │ ├── PublishToXlsmIsRepeatableFiles.resx │ │ └── PublishToXlsmIsRepeatableFiles │ │ │ ├── Book1.xlsm │ │ │ ├── Module1.bas │ │ │ ├── Project.ini │ │ │ ├── Sheet1.cls │ │ │ ├── Sheet2.cls │ │ │ └── ThisWorkbook.cls │ ├── FixCaseTests.cs │ └── PublishTests.cs ├── Mocks │ ├── FakeSystemOperations.cs │ ├── QuickSession.cs │ └── QuickSessionSettings.cs ├── Packages.config ├── UnitTests │ ├── AppIniFileTests.cs │ ├── HookTests.cs │ ├── IniFileTests.cs │ ├── ModuleProcessingTests.cs │ └── StartupTests.cs └── VBASync.Tests.csproj ├── VBASync.WPF ├── AboutWindow.xaml ├── AboutWindow.xaml.cs ├── ChangesViewModel.cs ├── FodyWeavers.xml ├── Icons │ ├── ClassIcon.png │ ├── DocIcon.png │ ├── English.png │ ├── FormIcon.png │ ├── French.png │ ├── ModuleIcon.png │ ├── ProjectIcon.png │ └── VBA Sync Simple.ico ├── MainViewModel.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Packages.config ├── Properties │ └── AssemblyInfo.cs ├── SessionView.xaml ├── SessionView.xaml.cs ├── SessionViewModel.cs ├── SettingsViewModel.cs ├── SettingsWindow.xaml ├── SettingsWindow.xaml.cs ├── VBASync.WPF.csproj ├── ViewModelBase.cs ├── WpfCommand.cs ├── WpfConverter.cs ├── WpfManager.cs ├── WpfStatic.cs └── WpfValidation.cs ├── VBASync.ini ├── VBASync ├── FodyWeavers.xml ├── Localization │ ├── VBASyncResources.Designer.cs │ ├── VBASyncResources.fr.Designer.cs │ ├── VBASyncResources.fr.resx │ └── VBASyncResources.resx ├── Model │ ├── ActiveSession.cs │ ├── AppIniFile.cs │ ├── DiffPlex_ModVba.cs │ ├── Extensions.cs │ ├── FrxObjects │ │ ├── CommandButtonControl.cs │ │ ├── Common.cs │ │ ├── FormControl.cs │ │ ├── FrxReader.cs │ │ ├── LabelControl.cs │ │ ├── MorphDataControl.cs │ │ ├── RawControl.cs │ │ └── TabStripControl.cs │ ├── Hook.cs │ ├── ILocateModules.cs │ ├── ISystemOperations.cs │ ├── IniFile.cs │ ├── Lib.cs │ ├── ModuleProcessing.cs │ ├── Patch.cs │ ├── ProjectIni.cs │ ├── RealSystemOperations.cs │ ├── SessionInterfaces.cs │ ├── Startup.cs │ ├── VbaFolder.cs │ ├── VbaRepositoryFolder.cs │ └── VbaTemporaryFolder.cs ├── Packages.config ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── VBA Sync Simple.ico └── VBASync.csproj └── VbaSync.sln /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | dist/ 5 | VBACompressionCodec.dll 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | nCrunchTemp_* 119 | 120 | # MightyMoose 121 | *.mm.* 122 | AutoTest.Net/ 123 | 124 | # Web workbench (sass) 125 | .sass-cache/ 126 | 127 | # Installshield output folder 128 | [Ee]xpress/ 129 | 130 | # DocProject is a documentation generator add-in 131 | DocProject/buildhelp/ 132 | DocProject/Help/*.HxT 133 | DocProject/Help/*.HxC 134 | DocProject/Help/*.hhc 135 | DocProject/Help/*.hhk 136 | DocProject/Help/*.hhp 137 | DocProject/Help/Html2 138 | DocProject/Help/html 139 | 140 | # Click-Once directory 141 | publish/ 142 | 143 | # Publish Web Output 144 | *.[Pp]ublish.xml 145 | *.azurePubxml 146 | # TODO: Comment the next line if you want to checkin your web deploy settings 147 | # but database connection strings (with potential passwords) will be unencrypted 148 | *.pubxml 149 | *.publishproj 150 | 151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 152 | # checkin your Azure Web App publish settings, but sensitive information contained 153 | # in these scripts will be unencrypted 154 | PublishScripts/ 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # Microsoft Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Microsoft Azure Emulator 173 | ecf/ 174 | rcf/ 175 | 176 | # Windows Store app package directories and files 177 | AppPackages/ 178 | BundleArtifacts/ 179 | Package.StoreAssociation.xml 180 | _pkginfo.txt 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | ~$* 191 | *~ 192 | *.dbmdl 193 | *.dbproj.schemaview 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | -------------------------------------------------------------------------------- /3RDPARTY/DiffPlex.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control 14 | with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management 15 | of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or 16 | (iii) beneficial ownership of such entity. 17 | 18 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 19 | 20 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, 21 | and configuration files. 22 | 23 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to 24 | compiled object code, generated documentation, and conversions to other media types. 25 | 26 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice 27 | that is included in or attached to the work (an example is provided in the Appendix below). 28 | 29 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial 30 | revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, 31 | Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and 32 | Derivative Works thereof. 33 | 34 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or 35 | Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or 36 | Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, 37 | verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, 38 | source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and 39 | improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as 40 | "Not a Contribution." 41 | 42 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently 43 | incorporated within the Work. 44 | 45 | 2. Grant of Copyright License. 46 | 47 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 48 | irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such 49 | Derivative Works in Source or Object form. 50 | 51 | 3. Grant of Patent License. 52 | 53 | Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 54 | irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, 55 | where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or 56 | by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity 57 | (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or 58 | contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation 59 | is filed. 60 | 61 | 4. Redistribution. 62 | 63 | You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, 64 | provided that You meet the following conditions: 65 | 66 | You must give any other recipients of the Work or Derivative Works a copy of this License; and 67 | You must cause any modified files to carry prominent notices stating that You changed the files; and 68 | You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source 69 | form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 70 | If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the 71 | attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of 72 | the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along 73 | with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents 74 | of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works 75 | that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed 76 | as modifying the License. 77 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, 78 | or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise 79 | complies with the conditions stated in this License. 80 | 81 | 5. Submission of Contributions. 82 | 83 | Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms 84 | and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms 85 | of any separate license agreement you may have executed with Licensor regarding such Contributions. 86 | 87 | 6. Trademarks. 88 | 89 | This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for 90 | reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 91 | 92 | 7. Disclaimer of Warranty. 93 | 94 | Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 95 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, 96 | MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and 97 | assume any risks associated with Your exercise of permissions under this License. 98 | 99 | 8. Limitation of Liability. 100 | 101 | In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate 102 | and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, 103 | or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to 104 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor 105 | has been advised of the possibility of such damages. 106 | 107 | 9. Accepting Warranty or Additional Liability. 108 | 109 | While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other 110 | liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole 111 | responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability 112 | incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 113 | 114 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /3RDPARTY/Ookii.Dialogs.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chelh/VBASync/a67bb92a9c736fa10f147cf897ce083886210e09/3RDPARTY/Ookii.Dialogs.txt -------------------------------------------------------------------------------- /3RDPARTY/SharpZipLib.txt: -------------------------------------------------------------------------------- 1 | Copyright © 2000-2016 SharpZipLib Contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons 7 | to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or 10 | substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 13 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 14 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 15 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Created 2017 by Chelsea Hughes 2 | 3 | Thanks to GitHub user hectorticoli for the French translation. 4 | 5 | I release all rights to this work. You may use it for any purpose, and alter 6 | and redistribute it freely. If you use this in another product, credit would 7 | be appreciated but not required. 8 | 9 | This software is provided “as-is,” without any express or implied warranty. 10 | In no event will I or any other contributor be held liable for any damages 11 | arising from the use of this software. 12 | 13 | ----------------------------------------------------------------------------- 14 | 15 | See the `3RDPARTY` folder for the licenses of these third-party components: 16 | 17 | * DiffPlex (modified by me for use with VBA) 18 | * OpenMCDF 19 | * SharpZipLib 20 | * Ookii.Dialogs 21 | * `VBACompressionCodec.dll` 22 | 23 | -------------------------------------------------------------------------------- /MakeInstaller.iss: -------------------------------------------------------------------------------- 1 | #define MyAppName "VBA Sync Tool" 2 | #define MyAppVersion "2.2.0" 3 | #define MyAppPublisher "Chelsea Hughes" 4 | #define MyAppURL "https://github.com/chelh/VBASync" 5 | #define MyAppExeName "VBASync.exe" 6 | 7 | [Setup] 8 | AppId={{FCE92422-DABC-447E-8DC4-504C206D2784} 9 | AppName={#MyAppName} 10 | AppVersion={#MyAppVersion} 11 | ;AppVerName={#MyAppName} {#MyAppVersion} 12 | AppPublisher={#MyAppPublisher} 13 | AppPublisherURL={#MyAppURL} 14 | AppSupportURL={#MyAppURL} 15 | AppUpdatesURL={#MyAppURL} 16 | DefaultDirName={pf}\{#MyAppName} 17 | DefaultGroupName={#MyAppName} 18 | AllowNoIcons=yes 19 | LicenseFile=C:\Users\Chel\Documents\VBA Sync Tool\LICENSE.rtf 20 | OutputBaseFilename=Install {#MyAppName} {#MyAppVersion} 21 | OutputDir=dist 22 | Compression=lzma 23 | SolidCompression=yes 24 | 25 | [Languages] 26 | Name: "en"; MessagesFile: "compiler:Default.isl" 27 | Name: "fr"; MessagesFile: "compiler:Languages\French.isl" 28 | 29 | [Files] 30 | Source: "src\VBASync\bin\Release\VBASync.exe"; DestDir: "{app}"; Flags: ignoreversion 31 | Source: "src\VBASync.WPF\bin\Release\VBASync.WPF.dll"; DestDir: "{app}"; Flags: ignoreversion 32 | Source: "src\VBACompressionCodec.dll"; DestDir: "{app}"; Flags: ignoreversion 33 | Source: "LICENSE.rtf"; DestDir: "{app}"; Flags: ignoreversion 34 | Source: "3RDPARTY\*"; DestDir: "{app}\3RDPARTY"; Flags: ignoreversion 35 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 36 | 37 | [InstallDelete] 38 | Type: files; Name: "{app}\VBA Sync Tool.exe" 39 | 40 | [Icons] 41 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 42 | Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 43 | 44 | [Run] 45 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 46 | 47 | [INI] 48 | Filename: "{app}\VBASync.ini"; Section: "General"; Key: "Language"; String: """{language}"""; Flags: createkeyifdoesntexist 49 | 50 | [UninstallDelete] 51 | Type: files; Name: "{app}\VBASync.ini" 52 | -------------------------------------------------------------------------------- /MakePortable.bat: -------------------------------------------------------------------------------- 1 | set version=2.2.0 2 | set pathTo7Zip=%ProgramFiles%\7-Zip\ 3 | 4 | "%pathTo7Zip%7z.exe" a "dist\Portable VBA Sync Tool %version%.zip" "3RDPARTY\*" "LICENSE.rtf" ".\src\VBACompressionCodec.dll" ".\src\VBASync\bin\Release\VBASync.exe" ".\src\VBASync.WPF\bin\Release\VBASync.WPF.dll" ".\src\VBASync.ini" 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VBA Sync ![VBA Sync logo](http://i.imgur.com/sQAsBy4.png) 2 | 3 | Microsoft Office VBA code is usually held in binary format, making proper 4 | version control difficult. VBA Sync synchronizes macros between a 5 | VBA-enabled file and a folder, enabling easy version control using Git, SVN, 6 | Mercurial, or any other VCS. 7 | 8 | **I no longer maintain this project. I have released it into the public domain.** 9 | 10 | [**Download my final release (v2.2.0)**](https://github.com/chelh/VBASync/releases/latest) 11 | [**Look for a newer version, or advertise your new version**](https://github.com/chelh/VBASync/issues/36) 12 | 13 | ## Features 14 | VBA Sync works *directly with the Office file,* unlike most 15 | other solutions, which use a host application (e.g., Excel) to manipulate 16 | the VBA code. This gives it several advantages: 17 | * Does not require special Excel settings. 18 | * Does not add to your VBA code base. 19 | * Allows you to use any off-the-shelf version control system. 20 | * Allows you to cherry-pick which modules to extract or publish. 21 | * Minimizes spurious changes by ignoring case on variable names, 22 | making merges easier. 23 | * Extracts full code including several hidden attributes. 24 | * Also extracts settings not tied to a particular module, 25 | like references. 26 | * Generates FRX files compatible with the VBE, but 27 | *without* any embedded timestamp. 28 | * Allows you to extract or publish a FRM module without necessarily 29 | updating its FRX module. 30 | * Works with document or worksheet modules in the same way 31 | as any other module. 32 | * Supports Excel 97-2003, Excel 2007+, Word 97-2003, Word 2007+, 33 | PowerPoint 2007+, and Outlook files. 34 | * Compatible with Windows, Linux, and Mac. 35 | 36 | ## Using 37 | VBA Sync has two modes: **Extract** mode extracts modules 38 | from the file into the folder. You can then commit the extracted files 39 | to version control. **Publish** mode publishes modules from 40 | the folder into the file. You should do this after merges. 41 | 42 | After you select a mode, a folder path, and a file path, the tool will 43 | list which modules have changed, with a checkbox next to each. Tick 44 | the checkbox next to each module with changes you'd like to apply. 45 | Double-click an entry to run a diff tool against the old and new files. 46 | (This requires setting up a diff tool under **File**→**Settings**.) 47 | If the underlying files change, click **Refresh**. When you're ready 48 | to synchronize, click **Apply** or **OK**. 49 | 50 | You can save and load session settings from the **File** menu. Settings 51 | are saved as `.ini` files. If a settings file is named `VBASync.ini` 52 | and located in the working directory, VBA Sync will load those 53 | settings automatically. I recommend taking advantage of this and 54 | launching VBA Sync from a shortcut with the working directory overridden, 55 | to avoid having to specify the folder and file each time you need to synchronize. 56 | 57 | ![VBA Sync after selecting folder and file locations](http://i.imgur.com/GrXx2VH.png) 58 | 59 | ## Command-line 60 | You can also specify settings on the command-line via switches: 61 | 62 | Switch | Meaning 63 | ------ | ------ 64 | `-x` | Extract VBA from Office file (default) 65 | `-p` | Publish VBA to Office file 66 | `-f ` | Specify Office file 67 | `-d ` | Specify version-control directory 68 | `-r` | Do the selected action, then immediately exit (**required** on Linux/Mac) 69 | `-i` | Ignore empty modules 70 | `-u` | Search subdirectories of version-control directory 71 | `-a` | Allow adding new document modules when publishing (expert option) 72 | `-e` | Allow deleting document modules when publishing (expert option) 73 | `-h ` | If `-p` was specified earlier, set the before-publish hook. Else set the after-extract hook. 74 | 75 | Any other parameter passed to VBA Sync will be read and parsed as a session `.ini` file. 76 | 77 | ## Public domain software 78 | Created 2017 by Chelsea Hughes 79 | 80 | Thanks to GitHub user hectorticoli for the French translation. 81 | 82 | I release all rights to this work. You may use it for any purpose, and alter 83 | and redistribute it freely. If you use this in another product, credit would 84 | be appreciated but is not required. 85 | 86 | This software is provided “as-is,” without any express or implied warranty. 87 | In no event will I or any other contributor be held liable for any damages 88 | arising from the use of this software. 89 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace VBASync.Tests.IntegrationTests.Files { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class PublishToXlsmIsRepeatableFiles { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal PublishToXlsmIsRepeatableFiles() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("VBASync.Tests.IntegrationTests.Files.PublishToXlsmIsRepeatableFiles", typeof(PublishToXlsmIsRepeatableFiles).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Byte[]. 65 | /// 66 | internal static byte[] Book1 { 67 | get { 68 | object obj = ResourceManager.GetObject("Book1", resourceCulture); 69 | return ((byte[])(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Byte[]. 75 | /// 76 | internal static byte[] Module1 { 77 | get { 78 | object obj = ResourceManager.GetObject("Module1", resourceCulture); 79 | return ((byte[])(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Byte[]. 85 | /// 86 | internal static byte[] Project { 87 | get { 88 | object obj = ResourceManager.GetObject("Project", resourceCulture); 89 | return ((byte[])(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Byte[]. 95 | /// 96 | internal static byte[] Sheet1 { 97 | get { 98 | object obj = ResourceManager.GetObject("Sheet1", resourceCulture); 99 | return ((byte[])(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up a localized resource of type System.Byte[]. 105 | /// 106 | internal static byte[] Sheet2 { 107 | get { 108 | object obj = ResourceManager.GetObject("Sheet2", resourceCulture); 109 | return ((byte[])(obj)); 110 | } 111 | } 112 | 113 | /// 114 | /// Looks up a localized resource of type System.Byte[]. 115 | /// 116 | internal static byte[] ThisWorkbook { 117 | get { 118 | object obj = ResourceManager.GetObject("ThisWorkbook", resourceCulture); 119 | return ((byte[])(obj)); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | PublishToXlsmIsRepeatableFiles\Book1.xlsm;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 123 | 124 | 125 | PublishToXlsmIsRepeatableFiles\Module1.bas;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | PublishToXlsmIsRepeatableFiles\Project.ini;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | PublishToXlsmIsRepeatableFiles\Sheet1.cls;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 132 | 133 | 134 | PublishToXlsmIsRepeatableFiles\Sheet2.cls;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 135 | 136 | 137 | PublishToXlsmIsRepeatableFiles\ThisWorkbook.cls;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 138 | 139 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/Book1.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chelh/VBASync/a67bb92a9c736fa10f147cf897ce083886210e09/src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/Book1.xlsm -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/Module1.bas: -------------------------------------------------------------------------------- 1 | Attribute VB_Name = "Module1" 2 | Option Explicit 3 | 4 | Sub tes() 5 | MsgBox "Hello, world!" 6 | End Sub 7 | 8 | Sub tes2() 9 | MsgBox "Hello, world!" 10 | End Sub 11 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/Project.ini: -------------------------------------------------------------------------------- 1 | CodePage=1252 2 | SysKind=3 3 | Version=1523482917.2 4 | ID="{158E021C-4D17-42C0-BF12-EADCE4E95A41}" 5 | Name="VBAProject" 6 | HelpContextID="0" 7 | VersionCompatible32="393222000" 8 | CMG="C0C228AE2CAE2CAE2CAE2C" 9 | DPB="8082686B696B696B" 10 | GC="4042A82BA92BA9D4" 11 | 12 | [Host Extender Info] 13 | &H00000001={3832D640-CF90-11CF-8E43-00A0C911005A};VBE;&H00000000 14 | 15 | [Constants] 16 | 17 | [Reference stdole] 18 | LibId=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\system32\stdole2.tlb#OLE Automation 19 | 20 | [Reference Office] 21 | LibId=*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.0#0#C:\Program Files\Common Files\Microsoft Shared\OFFICE16\MSO.DLL#Microsoft Office 16.0 Object Library 22 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/Sheet1.cls: -------------------------------------------------------------------------------- 1 | VERSION 1.0 CLASS 2 | BEGIN 3 | MultiUse = -1 'True 4 | END 5 | Attribute VB_Name = "Sheet1" 6 | Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}" 7 | Attribute VB_GlobalNameSpace = False 8 | Attribute VB_Creatable = False 9 | Attribute VB_PredeclaredId = True 10 | Attribute VB_Exposed = True 11 | Attribute VB_TemplateDerived = False 12 | Attribute VB_Customizable = True 13 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/Sheet2.cls: -------------------------------------------------------------------------------- 1 | VERSION 1.0 CLASS 2 | BEGIN 3 | MultiUse = -1 'True 4 | END 5 | Attribute VB_Name = "Sheet2" 6 | Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}" 7 | Attribute VB_GlobalNameSpace = False 8 | Attribute VB_Creatable = False 9 | Attribute VB_PredeclaredId = True 10 | Attribute VB_Exposed = True 11 | Attribute VB_TemplateDerived = False 12 | Attribute VB_Customizable = True 13 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/Files/PublishToXlsmIsRepeatableFiles/ThisWorkbook.cls: -------------------------------------------------------------------------------- 1 | VERSION 1.0 CLASS 2 | BEGIN 3 | MultiUse = -1 'True 4 | END 5 | Attribute VB_Name = "ThisWorkbook" 6 | Attribute VB_Base = "0{00020819-0000-0000-C000-000000000046}" 7 | Attribute VB_GlobalNameSpace = False 8 | Attribute VB_Creatable = False 9 | Attribute VB_PredeclaredId = True 10 | Attribute VB_Exposed = True 11 | Attribute VB_TemplateDerived = False 12 | Attribute VB_Customizable = True 13 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/FixCaseTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using VBASync.Model; 3 | 4 | namespace VBASync.Tests.IntegrationTests 5 | { 6 | [TestFixture] 7 | public class FixCaseTests 8 | { 9 | [Test] 10 | public void FixCaseBasic() 11 | { 12 | const string oldFile = @"VERSION 1.0 CLASS 13 | BEGIN 14 | MultiUse = -1 'True 15 | END 16 | Attribute VB_Name = ""ThisWorkbook"" 17 | Attribute VB_Base = ""0{00020819-0000-0000-C000-000000000046}"" 18 | Attribute VB_GlobalNameSpace = False 19 | Attribute VB_Creatable = False 20 | Attribute VB_PredeclaredId = True 21 | Attribute VB_Exposed = True 22 | Attribute VB_TemplateDerived = False 23 | Attribute VB_Customizable = True 24 | Option Explicit 25 | 26 | ' hello world! 27 | Sub tes() 28 | Dim HelloWorld$ 29 | HelloWorld = ""Hello, world!"" 30 | MsgBox HelloWorld 31 | End Sub 32 | Sub tes2() 33 | MsgBox 2 + 2 34 | End Sub 35 | Sub tes3() 36 | MsgBox ""Hello, world!"" 37 | End Sub 38 | "; 39 | const string newFileRaw = @"VERSION 1.0 CLASS 40 | BEGIN 41 | MultiUse = -1 'True 42 | END 43 | Attribute VB_Name = ""ThisWorkbook"" 44 | Attribute VB_Base = ""0{00020819-0000-0000-C000-000000000046}"" 45 | Attribute VB_GlobalNameSpace = False 46 | Attribute VB_Creatable = False 47 | Attribute VB_PredeclaredId = True 48 | Attribute VB_Exposed = True 49 | Attribute VB_TemplateDerived = False 50 | Attribute VB_Customizable = True 51 | Option Explicit 52 | 53 | ' Hello world! 54 | Sub Tes() 55 | Dim helloworld$ 56 | helloworld = ""Hello, World!"" 57 | MsgBox helloworld 58 | End Sub 59 | 60 | Sub Tes2() 61 | MsgBox 2 + 3 62 | End Sub 63 | 64 | Sub Tes3() 65 | MsgBox ""Hello, world!"" 66 | End Sub 67 | "; 68 | const string newFileFix = @"VERSION 1.0 CLASS 69 | BEGIN 70 | MultiUse = -1 'True 71 | END 72 | Attribute VB_Name = ""ThisWorkbook"" 73 | Attribute VB_Base = ""0{00020819-0000-0000-C000-000000000046}"" 74 | Attribute VB_GlobalNameSpace = False 75 | Attribute VB_Creatable = False 76 | Attribute VB_PredeclaredId = True 77 | Attribute VB_Exposed = True 78 | Attribute VB_TemplateDerived = False 79 | Attribute VB_Customizable = True 80 | Option Explicit 81 | 82 | ' Hello world! 83 | Sub tes() 84 | Dim HelloWorld$ 85 | helloworld = ""Hello, World!"" 86 | MsgBox HelloWorld 87 | End Sub 88 | 89 | Sub tes2() 90 | MsgBox 2 + 3 91 | End Sub 92 | 93 | Sub tes3() 94 | MsgBox ""Hello, world!"" 95 | End Sub 96 | "; 97 | Assert.That(newFileFix, Is.EqualTo(ModuleProcessing.FixCase(oldFile, newFileRaw))); 98 | } 99 | 100 | [Test] 101 | public void FixCaseWasDuplicatingLines() 102 | { 103 | const string oldFile = @"Attribute VB_Name = ""Module1"" 104 | Option Explicit 105 | 106 | 107 | Sub StubbedOuttes() 108 | MsgBox ""Hello, world!"" 109 | End Sub 110 | 111 | Sub StubbedOuttes2() 112 | 113 | MsgBox ""Hello, world!"" 114 | End Sub 115 | "; 116 | 117 | const string newFile = @"Attribute VB_Name = ""Module1"" 118 | Option Explicit 119 | 120 | Sub tes() 121 | MsgBox ""Hello, world!"" 122 | End Sub 123 | 124 | Sub tes2() 125 | MsgBox ""Hello, world!"" 126 | End Sub 127 | "; 128 | 129 | Assert.That(newFile, Is.EqualTo(ModuleProcessing.FixCase(oldFile, newFile))); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/VBASync.Tests/IntegrationTests/PublishTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Linq; 3 | using VBASync.Model; 4 | using VBASync.Tests.Mocks; 5 | 6 | namespace VBASync.Tests.IntegrationTests 7 | { 8 | [TestFixture] 9 | public class PublishTests 10 | { 11 | [Test] 12 | public void PublishToXlsmIsRepeatable() 13 | { 14 | var fso = new FakeSystemOperations(); 15 | fso.FileWriteAllBytes("Book1.xlsm", Files.PublishToXlsmIsRepeatableFiles.Book1); 16 | fso.FileWriteAllBytes("repo/Module1.bas", Files.PublishToXlsmIsRepeatableFiles.Module1); 17 | fso.FileWriteAllBytes("repo/Project.ini", Files.PublishToXlsmIsRepeatableFiles.Project); 18 | fso.FileWriteAllBytes("repo/Sheet1.cls", Files.PublishToXlsmIsRepeatableFiles.Sheet1); 19 | fso.FileWriteAllBytes("repo/Sheet2.cls", Files.PublishToXlsmIsRepeatableFiles.Sheet2); 20 | fso.FileWriteAllBytes("repo/ThisWorkbook.cls", Files.PublishToXlsmIsRepeatableFiles.ThisWorkbook); 21 | 22 | var session = new QuickSession 23 | { 24 | Action = ActionType.Publish, 25 | AutoRun = true, 26 | FilePath = "Book1.xlsm", 27 | FolderPath = "repo/" 28 | }; 29 | var settings = new QuickSessionSettings 30 | { 31 | AddNewDocumentsToFile = false, 32 | AfterExtractHook = null, 33 | BeforePublishHook = null, 34 | DeleteDocumentsFromFile = false, 35 | IgnoreEmpty = false 36 | }; 37 | 38 | using (var actor = new ActiveSession(fso, session, settings)) 39 | { 40 | actor.Apply(actor.GetPatches().ToList()); 41 | } 42 | var fileContentsAfterPublish1 = fso.FileReadAllBytes("Book1.xlsm"); 43 | 44 | fso.FileWriteAllBytes("Book1.xlsm", Files.PublishToXlsmIsRepeatableFiles.Book1); 45 | 46 | using (var actor = new ActiveSession(fso, session, settings)) 47 | { 48 | actor.Apply(actor.GetPatches().ToList()); 49 | } 50 | var fileContentsAfterPublish2 = fso.FileReadAllBytes("Book1.xlsm"); 51 | 52 | Assert.That(fileContentsAfterPublish1, Is.EqualTo(fileContentsAfterPublish2)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/VBASync.Tests/Mocks/FakeSystemOperations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using VBASync.Model; 9 | 10 | namespace VBASync.Tests.Mocks 11 | { 12 | internal class FakeSystemOperations : ISystemOperations 13 | { 14 | private readonly ConcurrentDictionary _files 15 | = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); 16 | 17 | public virtual bool IsWindows => false; 18 | 19 | public virtual Stream CreateNewFile(string path) 20 | { 21 | if (!_files.TryAdd(path, new byte[0])) 22 | { 23 | throw new ApplicationException($"Tried to create new file at '{path}', which already exists!"); 24 | } 25 | return new MemoryStreamWithFlushCallback(s => _files[path] = s.ToArray()); 26 | } 27 | 28 | public virtual void DirectoryCreateDirectory(string path) 29 | { 30 | } 31 | 32 | public virtual void DirectoryDelete(string path, bool recursive) 33 | { 34 | } 35 | 36 | public virtual IEnumerable DirectoryGetFiles(string folderPath) 37 | => DirectoryGetFiles(folderPath, "*", false); 38 | 39 | public virtual IEnumerable DirectoryGetFiles(string folderPath, string mask) 40 | => DirectoryGetFiles(folderPath, mask, false); 41 | 42 | public virtual IEnumerable DirectoryGetFiles(string folderPath, string mask, bool recurse) 43 | { 44 | if (!folderPath.EndsWith("/")) 45 | { 46 | folderPath += "/"; 47 | } 48 | foreach (var s in _files.Keys) 49 | { 50 | if (InRightDirectory(s) && FitsMask(PathGetFileName(s), mask)) 51 | { 52 | yield return s; 53 | } 54 | } 55 | 56 | bool FitsMask(string fileName, string fileMask) 57 | { 58 | if (fileMask == "*") 59 | { 60 | return true; 61 | } 62 | return new Regex("^" + fileMask.Replace(".", "[.]").Replace("*", ".*").Replace("?", ".") 63 | + "$", RegexOptions.IgnoreCase).IsMatch(fileName); 64 | } 65 | 66 | bool InRightDirectory(string filePath) 67 | { 68 | if (!filePath.StartsWith(folderPath)) 69 | { 70 | return false; 71 | } 72 | if (recurse) 73 | { 74 | return true; 75 | } 76 | return filePath.LastIndexOf('/') < folderPath.Length; 77 | } 78 | } 79 | 80 | public virtual void FileCopy(string src, string dest) => FileCopy(src, dest, false); 81 | 82 | public virtual void FileCopy(string src, string dest, bool overwrite) 83 | { 84 | if (overwrite) 85 | { 86 | _files[dest] = _files[src]; 87 | } 88 | else if (!_files.TryAdd(dest, _files[src])) 89 | { 90 | throw new ApplicationException($"Tried to copy to a location '{dest}' that already exists!"); 91 | } 92 | } 93 | 94 | public virtual void FileDelete(string path) 95 | { 96 | if (!_files.TryRemove(path, out _)) 97 | { 98 | throw new ApplicationException($"Tried to remove '{path}', which doesn't exist!"); 99 | } 100 | } 101 | 102 | public virtual bool FileExists(string path) => _files.ContainsKey(path); 103 | public virtual byte[] FileReadAllBytes(string path) => _files[path]; 104 | 105 | public virtual string FileReadAllText(string path, Encoding encoding) 106 | => encoding.GetString(FileReadAllBytes(path)); 107 | 108 | public virtual void FileWriteAllBytes(string path, byte[] bytes) => _files[path] = bytes; 109 | 110 | public virtual void FileWriteAllLines(string path, IEnumerable lines, Encoding encoding) 111 | => FileWriteAllText(path, string.Join("\n", lines), encoding); 112 | 113 | public virtual void FileWriteAllText(string path, string text, Encoding encoding) 114 | => FileWriteAllBytes(path, encoding.GetBytes(text)); 115 | 116 | public virtual Stream OpenFileForRead(string path) => new MemoryStream(_files[path]); 117 | 118 | public virtual Stream OpenFileForWrite(string path) 119 | => new MemoryStreamWithFlushCallback(FileReadAllBytes(path), s => _files[path] = s.ToArray()); 120 | 121 | public string PathCombine(params string[] parts) 122 | { 123 | var sb = new StringBuilder(); 124 | for (var i = 0; i < parts.Length; ++i) 125 | { 126 | if (i > 0) 127 | { 128 | sb.Append('/'); 129 | } 130 | if (i < parts.Length - 1) 131 | { 132 | sb.Append(parts[i].TrimEnd('/')); 133 | } 134 | else 135 | { 136 | sb.Append(parts[i]); 137 | } 138 | } 139 | return sb.ToString(); 140 | } 141 | 142 | public string PathGetDirectoryName(string path) 143 | { 144 | if (string.IsNullOrEmpty(path)) 145 | { 146 | return null; 147 | } 148 | if (path == "/") 149 | { 150 | return "/"; 151 | } 152 | if (path.EndsWith("/")) 153 | { 154 | path = path.Substring(0, path.Length - 1); 155 | } 156 | var idx = path.LastIndexOf('/'); 157 | if (idx == 0) 158 | { 159 | return "/"; 160 | } 161 | if (idx < 0) 162 | { 163 | return ""; 164 | } 165 | return path.Substring(0, idx); 166 | } 167 | 168 | public virtual string PathGetExtension(string path) 169 | { 170 | var idx = path.LastIndexOf('.'); 171 | return idx >= 0 ? path.Substring(idx) : ""; 172 | } 173 | 174 | public virtual string PathGetFileName(string path) 175 | { 176 | var idx = path.LastIndexOf('/'); 177 | return idx >= 0 ? path.Substring(idx + 1) : path; 178 | } 179 | 180 | public virtual string PathGetFileNameWithoutExtension(string path) 181 | { 182 | var ext = PathGetExtension(path); 183 | var fn = PathGetFileName(path); 184 | return fn.Substring(0, fn.Length - ext.Length); 185 | } 186 | 187 | public virtual string PathGetTempPath() => "/tmp/"; 188 | 189 | public virtual void ProcessStartAndWaitForExit(ProcessStartInfo psi) 190 | { 191 | } 192 | 193 | private class MemoryStreamWithFlushCallback : MemoryStream 194 | { 195 | private readonly Action _flushCallback; 196 | 197 | internal MemoryStreamWithFlushCallback(Action flushCallback) 198 | { 199 | _flushCallback = flushCallback; 200 | } 201 | 202 | internal MemoryStreamWithFlushCallback(byte[] buffer, Action flushCallback) : this(flushCallback) 203 | { 204 | Write(buffer, 0, buffer.Length); 205 | base.Flush(); 206 | } 207 | 208 | public override void Flush() 209 | { 210 | base.Flush(); 211 | _flushCallback(this); 212 | } 213 | } 214 | } 215 | 216 | internal class WindowsFakeSystemOperations : FakeSystemOperations 217 | { 218 | public override bool IsWindows => true; 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/VBASync.Tests/Mocks/QuickSession.cs: -------------------------------------------------------------------------------- 1 | using VBASync.Model; 2 | 3 | namespace VBASync.Tests.Mocks 4 | { 5 | internal class QuickSession : ISession 6 | { 7 | public ActionType Action { get; set; } 8 | public bool AutoRun { get; set; } 9 | public string FilePath { get; set; } 10 | public string FolderPath { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/VBASync.Tests/Mocks/QuickSessionSettings.cs: -------------------------------------------------------------------------------- 1 | using VBASync.Model; 2 | 3 | namespace VBASync.Tests.Mocks 4 | { 5 | public class QuickSessionSettings : ISessionSettings 6 | { 7 | public bool AddNewDocumentsToFile { get; set; } 8 | public Hook AfterExtractHook { get; set; } 9 | public Hook BeforePublishHook { get; set; } 10 | public bool DeleteDocumentsFromFile { get; set; } 11 | public bool IgnoreEmpty { get; set; } 12 | public bool SearchRepositorySubdirectories { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/VBASync.Tests/Packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/VBASync.Tests/UnitTests/AppIniFileTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using VBASync.Model; 4 | 5 | namespace VBASync.Tests.UnitTests 6 | { 7 | [TestFixture] 8 | public class AppIniFileTests 9 | { 10 | [TestCase("Extract", ExpectedResult = ActionType.Extract)] 11 | [TestCase("EXTRACT", ExpectedResult = ActionType.Extract)] 12 | [TestCase("Publish", ExpectedResult = ActionType.Publish)] 13 | [TestCase("PUBLISH", ExpectedResult = ActionType.Publish)] 14 | public ActionType? AppIniParsesActionType(string value) 15 | { 16 | var content = "[General]" + Environment.NewLine 17 | + "ActionTypeTest=" + value + Environment.NewLine; 18 | var ini = new AppIniFile(); 19 | ini.ProcessString(content); 20 | return ini.GetActionType("General", "ActionTypeTest"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/VBASync.Tests/UnitTests/HookTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using VBASync.Model; 3 | using VBASync.Tests.Mocks; 4 | 5 | namespace VBASync.Tests.UnitTests 6 | { 7 | [TestFixture] 8 | public class HookTests 9 | { 10 | [Test] 11 | public void HookLinux() 12 | { 13 | var hook = new Hook(new FakeSystemOperations(), "./hook.sh \"{TargetDir}\""); 14 | var psi = hook.GetProcessStartInfo("/tmp/ExtractedVbaFolder/"); 15 | Assert.That(psi.FileName, Is.EqualTo("sh")); 16 | Assert.That(psi.Arguments, Is.EqualTo("-c \"./hook.sh \\\"/tmp/ExtractedVbaFolder/\\\"\"")); 17 | } 18 | 19 | [Test] 20 | public void HookWindows() 21 | { 22 | var hook = new Hook(new WindowsFakeSystemOperations(), "hook.bat \"{TargetDir}\""); 23 | var psi = hook.GetProcessStartInfo("C:\\Temp\\ExtractedVbaFolder\\"); 24 | Assert.That(psi.FileName, Is.EqualTo("cmd.exe").IgnoreCase); 25 | Assert.That(psi.Arguments, Is.EqualTo("/c hook.bat \"C:\\Temp\\ExtractedVbaFolder\\\"")); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/VBASync.Tests/UnitTests/IniFileTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using VBASync.Model; 4 | 5 | namespace VBASync.Tests.UnitTests 6 | { 7 | [TestFixture] 8 | public class IniFileTests 9 | { 10 | [Test] 11 | public void IniDefaultBoolIsNull() 12 | { 13 | var content = "[General]" + Environment.NewLine; 14 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.Null); 15 | } 16 | 17 | [Test] 18 | public void IniDefaultSubject() 19 | { 20 | var content = "BoolTest=0" + Environment.NewLine; 21 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.EqualTo(false)); 22 | } 23 | 24 | [Test] 25 | public void IniInvalidBoolIsNull() 26 | { 27 | var content = "[General]" + Environment.NewLine 28 | + "BoolTest=bubba" + Environment.NewLine; 29 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.Null); 30 | } 31 | 32 | [Test] 33 | public void IniOverride() 34 | { 35 | var content = "[General]" + Environment.NewLine 36 | + "BoolTest=0" + Environment.NewLine 37 | + "BoolTest=1" + Environment.NewLine; 38 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.EqualTo(true)); 39 | } 40 | 41 | [TestCase("0", ExpectedResult = false)] 42 | [TestCase("1", ExpectedResult = true)] 43 | [TestCase("False", ExpectedResult = false)] 44 | [TestCase("FALSE", ExpectedResult = false)] 45 | [TestCase("No", ExpectedResult = false)] 46 | [TestCase("NO", ExpectedResult = false)] 47 | [TestCase("True", ExpectedResult = true)] 48 | [TestCase("TRUE", ExpectedResult = true)] 49 | [TestCase("Yes", ExpectedResult = true)] 50 | [TestCase("YES", ExpectedResult = true)] 51 | public bool? IniParsesBool(string value) 52 | { 53 | var content = "[General]" + Environment.NewLine 54 | + "BoolTest=" + value + Environment.NewLine; 55 | return MakeIni(content).GetBool("General", "BoolTest"); 56 | } 57 | 58 | [Test] 59 | public void IniParsesGuid() 60 | { 61 | var content = "[General]" + Environment.NewLine 62 | + "GuidTest=b5b92f29-a4e3-4da0-8a93-608143212733" + Environment.NewLine; 63 | Assert.That(MakeIni(content).GetGuid("General", "GuidTest"), 64 | Is.EqualTo(new Guid("b5b92f29-a4e3-4da0-8a93-608143212733"))); 65 | } 66 | 67 | [TestCase(@"""-7""", ExpectedResult = -7)] 68 | [TestCase(@"""5""", ExpectedResult = 5)] 69 | [TestCase("-23", ExpectedResult = -23)] 70 | [TestCase("4", ExpectedResult = 4)] 71 | public int? IniParsesInt(string value) 72 | { 73 | var content = "[General]" + Environment.NewLine 74 | + "IntTest=" + value + Environment.NewLine; 75 | return MakeIni(content).GetInt("General", "IntTest"); 76 | } 77 | 78 | [TestCase(@" ""value with spaces inside "" ", ExpectedResult = "value with spaces inside ")] 79 | [TestCase("bubba", ExpectedResult = "bubba")] 80 | public string IniParsesString(string value) 81 | { 82 | var content = "[General]" + Environment.NewLine 83 | + "StringTest=" + value + Environment.NewLine; 84 | return MakeIni(content).GetString("General", "StringTest"); 85 | } 86 | 87 | [Test] 88 | public void IniParsesVersion() 89 | { 90 | var content = "[General]" + Environment.NewLine 91 | + "VersionTest=215397.2" + Environment.NewLine; 92 | Assert.That(MakeIni(content).GetVersion("General", "VersionTest"), 93 | Is.EqualTo(new Version(215397, 2))); 94 | } 95 | 96 | [Test] 97 | public void IniParsesSpacedBool() 98 | { 99 | var content = " [General] " + Environment.NewLine 100 | + " BoolTest = 1 " + Environment.NewLine; 101 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.EqualTo(true)); 102 | } 103 | 104 | [Test] 105 | public void IniSubjectIsCaseInsensitive() 106 | { 107 | var content = "[general]" + Environment.NewLine 108 | + "BoolTest=1" + Environment.NewLine; 109 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.EqualTo(true)); 110 | } 111 | 112 | [Test] 113 | public void IniValueIsCaseInsensitive() 114 | { 115 | var content = "[General]" + Environment.NewLine 116 | + "booltest=1" + Environment.NewLine; 117 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.EqualTo(true)); 118 | } 119 | 120 | [Test] 121 | public void IniWithNoNewLine() 122 | { 123 | Assert.That(MakeIni("BoolTest=1").GetBool("General", "BoolTest"), Is.EqualTo(true)); 124 | } 125 | 126 | [Test] 127 | public void IniWithoutEndingNewLine() 128 | { 129 | var content = "[General]" + Environment.NewLine 130 | + "BoolTest=1"; 131 | Assert.That(MakeIni(content).GetBool("General", "BoolTest"), Is.EqualTo(true)); 132 | } 133 | 134 | private IniFile MakeIni(string content) 135 | { 136 | var ini = new IniFile(); 137 | ini.ProcessString(content); 138 | return ini; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/VBASync.Tests/UnitTests/ModuleProcessingTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Text; 3 | using VBASync.Model; 4 | 5 | namespace VBASync.Tests.UnitTests 6 | { 7 | [TestFixture] 8 | public class ModuleProcessingTests 9 | { 10 | [Test] 11 | public void StubOutBasic() 12 | { 13 | var sb = new StringBuilder(); 14 | sb.Append("VERSION 1.0 CLASS\r\n"); 15 | sb.Append("BEGIN\r\n"); 16 | sb.Append(" MultiUse = -1 'True\r\n"); 17 | sb.Append("END\r\n"); 18 | sb.Append("Attribute VB_Name = \"ThisWorkbook\"\r\n"); 19 | sb.Append("Attribute VB_Base = \"0{00020819-0000-0000-C000-000000000046}\"\r\n"); 20 | sb.Append("Attribute VB_GlobalNameSpace = False\r\n"); 21 | sb.Append("Attribute VB_Creatable = False\r\n"); 22 | sb.Append("Attribute VB_PredeclaredId = True\r\n"); 23 | sb.Append("Attribute VB_Exposed = True\r\n"); 24 | sb.Append("Attribute VB_TemplateDerived = False\r\n"); 25 | sb.Append("Attribute VB_Customizable = True\r\n"); 26 | var expected = sb.ToString(); 27 | sb.Append("Option Explicit\r\n\r\n"); 28 | sb.Append("Private Sub Workbook_Open()\r\n"); 29 | sb.Append(" MsgBox \"Hello World!\"\r\n"); 30 | sb.Append("End Sub\r\n"); 31 | Assert.That(ModuleProcessing.StubOut(sb.ToString()), Is.EqualTo(expected)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/VBASync.Tests/UnitTests/StartupTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System; 3 | using VBASync.Model; 4 | using VBASync.Tests.Mocks; 5 | 6 | namespace VBASync.Tests.UnitTests 7 | { 8 | [TestFixture] 9 | public class StartupTests 10 | { 11 | [Test] 12 | public void StartupAllArgsExtract() 13 | { 14 | var startup = new Startup(MakeWindowsHook, s => ThrowUnexpectedIniRequest()); 15 | startup.ProcessArgs(new[] { "-x", "-f", @"C:\Path\To\File", "-d", @"C:\Path\To\Folder", 16 | "-r", "-a", "-i", "-h", @"MyAwesomeHook.bat ""{TargetDir}""" }); 17 | 18 | Assert.That(startup.Action, Is.EqualTo(ActionType.Extract)); 19 | Assert.That(startup.AddNewDocumentsToFile, Is.EqualTo(true)); 20 | Assert.That(startup.AfterExtractHook.Content, Is.EqualTo(@"MyAwesomeHook.bat ""{TargetDir}""")); 21 | Assert.That(startup.AutoRun, Is.EqualTo(true)); 22 | Assert.That(startup.FilePath, Is.EqualTo(@"C:\Path\To\File")); 23 | Assert.That(startup.FolderPath, Is.EqualTo(@"C:\Path\To\Folder")); 24 | Assert.That(startup.IgnoreEmpty, Is.EqualTo(true)); 25 | } 26 | 27 | [Test] 28 | public void StartupAllArgsPublish() 29 | { 30 | var startup = new Startup(MakeWindowsHook, s => ThrowUnexpectedIniRequest()); 31 | startup.ProcessArgs(new[] { "-p", "-f", @"C:\Path\To\File", "-d", @"C:\Path\To\Folder", 32 | "-r", "-a", "-i", "-h", @"MyAwesomeHook.bat ""{TargetDir}""" }); 33 | 34 | Assert.That(startup.Action, Is.EqualTo(ActionType.Publish)); 35 | Assert.That(startup.AddNewDocumentsToFile, Is.EqualTo(true)); 36 | Assert.That(startup.BeforePublishHook.Content, Is.EqualTo(@"MyAwesomeHook.bat ""{TargetDir}""")); 37 | Assert.That(startup.AutoRun, Is.EqualTo(true)); 38 | Assert.That(startup.FilePath, Is.EqualTo(@"C:\Path\To\File")); 39 | Assert.That(startup.FolderPath, Is.EqualTo(@"C:\Path\To\Folder")); 40 | Assert.That(startup.IgnoreEmpty, Is.EqualTo(true)); 41 | } 42 | 43 | [Test] 44 | public void StartupAutoExtract() 45 | { 46 | var startup = new Startup(MakeWindowsHook, s => ThrowUnexpectedIniRequest()); 47 | 48 | var generalIni = new AppIniFile(); 49 | generalIni.PumpValue("General", "Language", "en"); 50 | generalIni.PumpValue("General", "ActionType", "Extract"); 51 | generalIni.PumpValue("General", "FolderPath", @"C:\Path\To\LastFolder"); 52 | generalIni.PumpValue("General", "FilePath", @"C:\Path\To\LastFile"); 53 | generalIni.PumpValue("DiffTool", "Path", @"C:\Path\To\DiffTool"); 54 | startup.ProcessIni(generalIni, false); 55 | 56 | var sessionIni = new AppIniFile(); 57 | sessionIni.PumpValue("General", "ActionType", "Publish"); 58 | sessionIni.PumpValue("General", "FolderPath", @"C:\Path\To\RealFolder"); 59 | sessionIni.PumpValue("General", "FilePath", @"C:\Path\To\RealFile"); 60 | startup.ProcessIni(sessionIni, true); 61 | 62 | startup.ProcessArgs(new[] { "-r", "-x" }); 63 | 64 | Assert.That(startup.Language, Is.EqualTo("en")); 65 | Assert.That(startup.Action, Is.EqualTo(ActionType.Extract)); 66 | Assert.That(startup.FolderPath, Is.EqualTo(@"C:\Path\To\RealFolder")); 67 | Assert.That(startup.FilePath, Is.EqualTo(@"C:\Path\To\RealFile")); 68 | Assert.That(startup.DiffTool, Is.EqualTo(@"C:\Path\To\DiffTool")); 69 | Assert.That(startup.RecentFiles.Count, Is.EqualTo(0)); 70 | Assert.That(startup.AutoRun, Is.EqualTo(true)); 71 | } 72 | 73 | [Test] 74 | public void StartupAutoPublishDueIniOverride() 75 | { 76 | var startup = new Startup(MakeWindowsHook, AllowOnlyPublisherIniRequest); 77 | 78 | var generalIni = new AppIniFile(); 79 | generalIni.PumpValue("General", "Language", "en"); 80 | generalIni.PumpValue("General", "ActionType", "Extract"); 81 | generalIni.PumpValue("General", "FolderPath", @"C:\Path\To\LastFolder"); 82 | generalIni.PumpValue("General", "FilePath", @"C:\Path\To\LastFile"); 83 | generalIni.PumpValue("DiffTool", "Path", @"C:\Path\To\DiffTool"); 84 | startup.ProcessIni(generalIni, false); 85 | 86 | var sessionIni = new AppIniFile(); 87 | sessionIni.PumpValue("General", "ActionType", "Publish"); 88 | sessionIni.PumpValue("General", "FolderPath", @"C:\Path\To\RealFolder"); 89 | sessionIni.PumpValue("General", "FilePath", @"C:\Path\To\RealFile"); 90 | startup.ProcessIni(sessionIni, true); 91 | 92 | startup.ProcessArgs(new[] { "-r", "-x", "Publisher.ini" }); 93 | 94 | Assert.That(startup.Language, Is.EqualTo("en")); 95 | Assert.That(startup.Action, Is.EqualTo(ActionType.Publish)); 96 | Assert.That(startup.FolderPath, Is.EqualTo(@"C:\Path\To\RealFolder")); 97 | Assert.That(startup.FilePath, Is.EqualTo(@"C:\Path\To\RealFile")); 98 | Assert.That(startup.DiffTool, Is.EqualTo(@"C:\Path\To\DiffTool")); 99 | Assert.That(startup.RecentFiles.Count, Is.EqualTo(0)); 100 | Assert.That(startup.AutoRun, Is.EqualTo(true)); 101 | } 102 | 103 | [Test] 104 | public void StartupDefaultDiffToolParameters() 105 | { 106 | var startup = new Startup(); 107 | Assert.That(startup.DiffToolParameters, Is.EqualTo(@"""{OldFile}"" ""{NewFile}""")); 108 | } 109 | 110 | [Test] 111 | public void StartupInteractiveNoSessionIni() 112 | { 113 | var startup = new Startup(MakeWindowsHook, s => ThrowUnexpectedIniRequest()); 114 | 115 | var ini = new AppIniFile(); 116 | ini.PumpValue("General", "Language", "en"); 117 | ini.PumpValue("General", "ActionType", "Extract"); 118 | ini.PumpValue("General", "FolderPath", @"C:\Path\To\LastFolder"); 119 | ini.PumpValue("General", "FilePath", @"C:\Path\To\LastFile"); 120 | ini.PumpValue("DiffTool", "Path", @"C:\Path\To\DiffTool"); 121 | ini.PumpValue("RecentFiles", "1", @"C:\Path\To\RecentFile1"); 122 | ini.PumpValue("RecentFiles", "2", @"C:\Path\To\RecentFile2"); 123 | startup.ProcessIni(ini, false); 124 | 125 | startup.ProcessIni(new AppIniFile(), true); 126 | 127 | startup.ProcessArgs(new string[0]); 128 | 129 | Assert.That(startup.Language, Is.EqualTo("en")); 130 | Assert.That(startup.Action, Is.EqualTo(ActionType.Extract)); 131 | Assert.That(startup.FolderPath, Is.Null); 132 | Assert.That(startup.FilePath, Is.Null); 133 | Assert.That(startup.DiffTool, Is.EqualTo(@"C:\Path\To\DiffTool")); 134 | Assert.That(startup.RecentFiles.Count, Is.EqualTo(2)); 135 | Assert.That(startup.RecentFiles[0], Is.EqualTo(@"C:\Path\To\RecentFile1")); 136 | Assert.That(startup.RecentFiles[1], Is.EqualTo(@"C:\Path\To\RecentFile2")); 137 | Assert.That(startup.AutoRun, Is.EqualTo(false)); 138 | } 139 | 140 | [Test] 141 | public void StartupInteractiveWithSessionIni() 142 | { 143 | var startup = new Startup(MakeWindowsHook, s => ThrowUnexpectedIniRequest()); 144 | 145 | var generalIni = new AppIniFile(); 146 | generalIni.PumpValue("General", "Language", "en"); 147 | generalIni.PumpValue("General", "ActionType", "Extract"); 148 | generalIni.PumpValue("General", "FolderPath", @"C:\Path\To\LastFolder"); 149 | generalIni.PumpValue("General", "FilePath", @"C:\Path\To\LastFile"); 150 | generalIni.PumpValue("DiffTool", "Path", @"C:\Path\To\DiffTool"); 151 | startup.ProcessIni(generalIni, false); 152 | 153 | var sessionIni = new AppIniFile(); 154 | sessionIni.PumpValue("General", "ActionType", "Publish"); 155 | sessionIni.PumpValue("General", "FolderPath", @"C:\Path\To\RealFolder"); 156 | sessionIni.PumpValue("General", "FilePath", @"C:\Path\To\RealFile"); 157 | startup.ProcessIni(sessionIni, true); 158 | 159 | startup.ProcessArgs(new string[0]); 160 | 161 | Assert.That(startup.Language, Is.EqualTo("en")); 162 | Assert.That(startup.Action, Is.EqualTo(ActionType.Publish)); 163 | Assert.That(startup.FolderPath, Is.EqualTo(@"C:\Path\To\RealFolder")); 164 | Assert.That(startup.FilePath, Is.EqualTo(@"C:\Path\To\RealFile")); 165 | Assert.That(startup.DiffTool, Is.EqualTo(@"C:\Path\To\DiffTool")); 166 | Assert.That(startup.RecentFiles.Count, Is.EqualTo(0)); 167 | Assert.That(startup.AutoRun, Is.EqualTo(false)); 168 | } 169 | 170 | private AppIniFile AllowOnlyPublisherIniRequest(string path) 171 | { 172 | if (string.Equals(path, "Publisher.ini", StringComparison.InvariantCultureIgnoreCase)) 173 | { 174 | var ini = new AppIniFile(); 175 | ini.PumpValue("General", "ActionType", "Publish"); 176 | return ini; 177 | } 178 | return ThrowUnexpectedIniRequest(); 179 | } 180 | 181 | private Hook MakeWindowsHook(string content) => new Hook(new WindowsFakeSystemOperations(), content); 182 | 183 | private AppIniFile ThrowUnexpectedIniRequest() 184 | { 185 | throw new ApplicationException("Encountered an unexpected request for a .ini file!"); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/VBASync.Tests/VBASync.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {15432F02-5D62-47F7-96A7-372CBDA98D66} 7 | Library 8 | VBASync.Tests 9 | VBASync.Tests 10 | v4.5 11 | 512 12 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 13 | 15.0 14 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 15 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 16 | False 17 | UnitTest 18 | 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | True 48 | True 49 | PublishToXlsmIsRepeatableFiles.resx 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | {2b02695f-5e12-4cef-9541-ca134b2f0480} 65 | VBASync.WPF 66 | 67 | 68 | {69c03739-3447-441e-afb2-baf5f6c52cfe} 69 | VBASync 70 | 71 | 72 | 73 | 74 | ResXFileCodeGenerator 75 | PublishToXlsmIsRepeatableFiles.Designer.cs 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/VBASync.WPF/AboutWindow.xaml: -------------------------------------------------------------------------------- 1 |  17 | 18 | 19 | 29 |