Skip to content

Commit 23acd24

Browse files
committed
import and export all the named ranges in the workbook
1 parent 3161e08 commit 23acd24

File tree

8 files changed

+228
-58
lines changed

8 files changed

+228
-58
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
VbaDeveloper
22
============
33

4-
VbaDeveloper is an excel addin for easy version control of all your vba code.
5-
It will automatically export all your classes and modules to plain text, whenever you save your vba project. In this way changes can easily be committed using git or svn or any other source control system.
4+
VbaDeveloper is an excel addin for easy version control of all your vba code. If you write VBA code in excel, all your files are stored in binary format. You can commit those, but a version control system cannot do much more than that with them. Merging code from different branches, reverting commits (other than the last one), or viewing differences between two commits is very troublesome for binary files. The VbaDeveloper Addin aims to solve this problem.
65

7-
VbaDeveloper can also import the code again into your excel workbook. This is particularly useful after reverting an earlier commit or after merging branches. When you open an excel workbook it will ask if you want to import the code for that project.
86

9-
A code formatter for VBA is also included. It is implemented in VBA and can be directly run as a macro within the VBA Editor, so you can format your code as you write it. The most convenient way to run it is by opening the immediate window and then typing 'format'. This will format the active codepane.
7+
Features
8+
--------------
9+
10+
Whenever you save your vba project the addin will *automatically* export all your classes and modules to plain text. In this way your changes can easily be committed using git or svn or any other source control system. You only need to save your VBA project, no other manual steps are needed. It feels like you are working in plain text files.
11+
12+
VbaDeveloper can also import the code again into your excel workbook. This is particularly useful after reverting an earlier commit or after merging branches. Whenever you open an excel workbook it will ask if you want to import the code for that project.
13+
14+
A code formatter for VBA is also included. It is implemented in VBA and can be directly run as a macro within the VBA Editor, so you can format your code as you write it. The most convenient way to run it is by opening the immediate window and then typing ' application.run "format" '. This will format the active codepane.
15+
16+
Besides the vba code, the addin also imports and exports any named ranges. This makes it easy to track in your commit history how those have changed or you can use this feature to easily transport them from one workbook to another.
1017

1118
All functionality is also easily accessible via a menu. Look for the vbaDeveloper menu in the ribbon, under the addins section.
1219

1320
Building the addin
1421
-----------------------
1522

16-
This repository does not contain the addin itself, only the files needed to build it. In short it come downs to these steps:
23+
This repository does not contain the addin itself which is an excel addin in binary format, only the files needed to build it. In short it come downs to these steps:
1724

1825
- Manually import the Build module into a new excel workbook.
1926
- Add the required vba references.
2027
- Save the workbook as an excel add-in.
21-
- Close it, then open it again and import the other modules.
28+
- Close it, then open it again and let the Build module import the other modules.
2229

23-
Detailed instructions can be found in *src/vbaDeveloper.xlam/Build.bas*.
30+
Read the detailed instructions in *src/vbaDeveloper.xlam/Build.bas*.

src/vbaDeveloper.xlam/Build.bas

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,33 +46,65 @@ Public Sub testExport()
4646
End Sub
4747

4848

49+
' Returns the directory where code is exported to or imported from.
50+
' When createIfNotExists:=True, the directory will be created if it does not exist yet.
51+
' This is desired when we get the directory for exporting.
52+
' When createIfNotExists:=False and the directory does not exist, an empty String is returned.
53+
' This is desired when we get the directory for importing.
54+
'
55+
' Directory names always end with a '\', unless an empty string is returned.
56+
' Usually called with: fullWorkbookPath = wb.FullName or fullWorkbookPath = vbProject.fileName
57+
' if the workbook is new and has never been saved,
58+
' vbProject.fileName will throw an error while wb.FullName will return a name without slashes.
59+
Public Function getSourceDir(fullWorkbookPath As String, createIfNotExists As Boolean) As String
60+
' First check if the fullWorkbookPath contains a \.
61+
If Not InStr(fullWorkbookPath, "\") > 0 Then
62+
'In this case it is a new workbook, we skip it
63+
Exit Function
64+
End If
65+
66+
Dim fso As New Scripting.FileSystemObject
67+
Dim projDir As String
68+
projDir = fso.GetParentFolderName(fullWorkbookPath) & "\"
69+
Dim srcDir As String
70+
srcDir = projDir & "src\"
71+
Dim exportDir As String
72+
exportDir = srcDir & fso.GetFileName(fullWorkbookPath) & "\"
73+
74+
If createIfNotExists Then
75+
If Not fso.FolderExists(srcDir) Then
76+
fso.CreateFolder srcDir
77+
Debug.Print "Created Folder " & srcDir
78+
End If
79+
If Not fso.FolderExists(exportDir) Then
80+
fso.CreateFolder exportDir
81+
Debug.Print "Created Folder " & exportDir
82+
End If
83+
Else
84+
If Not fso.FolderExists(exportDir) Then
85+
Debug.Print "Folder does not exist: " & exportDir
86+
exportDir = ""
87+
End If
88+
End If
89+
getSourceDir = exportDir
90+
End Function
91+
4992

5093
' Usually called after the given workbook is saved
5194
Public Sub exportVbaCode(vbaProject As VBProject)
52-
'locate and create the export directory if necessary
5395
Dim vbProjectFileName As String
96+
On Error Resume Next
97+
'this can throw if the workbook has never been saved.
5498
vbProjectFileName = vbaProject.fileName
99+
On Error GoTo 0
55100
If vbProjectFileName = "" Then
56101
'In this case it is a new workbook, we skip it
102+
Debug.Print "No file name for project " & vbaProject.name & ", skipping"
57103
Exit Sub
58104
End If
59105

60-
Dim fso As New Scripting.FileSystemObject
61-
Dim projDir As String
62-
projDir = fso.GetParentFolderName(vbProjectFileName)
63-
Dim proj_root As String
64-
proj_root = projDir & "\src\"
65106
Dim export_path As String
66-
export_path = proj_root & fso.GetFileName(vbProjectFileName)
67-
68-
If Not fso.FolderExists(proj_root) Then
69-
fso.CreateFolder proj_root
70-
Debug.Print "Created Folder " & proj_root
71-
End If
72-
If Not fso.FolderExists(export_path) Then
73-
fso.CreateFolder export_path
74-
Debug.Print "Created Folder " & export_path
75-
End If
107+
export_path = getSourceDir(vbProjectFileName, createIfNotExists:=True)
76108

77109
Debug.Print "exporting to " & export_path
78110
'export all components
@@ -102,7 +134,7 @@ Private Function hasCodeToExport(component As VBComponent) As Boolean
102134
hasCodeToExport = True
103135
If component.codeModule.CountOfLines <= 2 Then
104136
Dim firstLine As String
105-
firstLine = Trim(component.codeModule.Lines(1, 1))
137+
firstLine = Trim(component.codeModule.lines(1, 1))
106138
'Debug.Print firstLine
107139
hasCodeToExport = Not (firstLine = "" Or firstLine = "Option Explicit")
108140
End If
@@ -126,14 +158,13 @@ Private Sub exportLines(exportPath As String, component As VBComponent)
126158
Dim fso As New Scripting.FileSystemObject
127159
Dim outStream As TextStream
128160
Set outStream = fso.CreateTextFile(fileName, True, False)
129-
outStream.Write (component.codeModule.Lines(1, component.codeModule.CountOfLines))
161+
outStream.Write (component.codeModule.lines(1, component.codeModule.CountOfLines))
130162
outStream.Close
131163
End Sub
132164

133165

134166
' Usually called after the given workbook is opened
135167
Public Sub importVbaCode(vbaProject As VBProject)
136-
'find project files
137168
Dim vbProjectFileName As String
138169
On Error Resume Next
139170
'this can throw if the workbook has never been saved.
@@ -145,20 +176,11 @@ Public Sub importVbaCode(vbaProject As VBProject)
145176
Exit Sub
146177
End If
147178

148-
Dim fso As New Scripting.FileSystemObject
149-
Dim projDir As String
150-
projDir = fso.GetParentFolderName(vbProjectFileName)
151-
Dim proj_root As String
152-
proj_root = projDir & "\src\"
153179
Dim export_path As String
154-
export_path = proj_root & fso.GetFileName(vbProjectFileName)
155-
156-
If Not fso.FolderExists(proj_root) Then
157-
Debug.Print "Could not find folder " & proj_root
158-
Exit Sub
159-
End If
160-
If Not fso.FolderExists(export_path) Then
161-
Debug.Print "Could not find folder " & export_path
180+
export_path = getSourceDir(vbProjectFileName, createIfNotExists:=False)
181+
If export_path = "" Then
182+
'The source directory does not exist, code has never been exported for this vbaProject.
183+
Debug.Print "No import directory for project " & vbaProject.name & ", skipping"
162184
Exit Sub
163185
End If
164186

@@ -167,6 +189,7 @@ Public Sub importVbaCode(vbaProject As VBProject)
167189
Set sheetsToImport = New Dictionary
168190
Set vbaProjectToImport = vbaProject
169191

192+
Dim fso As New Scripting.FileSystemObject
170193
Dim projContents As Folder
171194
Set projContents = fso.GetFolder(export_path)
172195
Dim file As Object
@@ -189,6 +212,7 @@ Public Sub importVbaCode(vbaProject As VBProject)
189212
Debug.Print "almost finished importing code for " & vbaProject.name
190213
End Sub
191214

215+
192216
Private Sub checkHowToImport(file As Object)
193217
Dim fileName As String
194218
fileName = file.name
@@ -220,6 +244,7 @@ Private Sub checkHowToImport(file As Object)
220244
End If
221245
End Sub
222246

247+
223248
' Only removes the vba component if it exists
224249
Private Sub removeComponent(vbaProject As VBProject, componentName As String)
225250
If componentExists(vbaProject, componentName) Then
@@ -230,9 +255,10 @@ Private Sub removeComponent(vbaProject As VBProject, componentName As String)
230255
End If
231256
End Sub
232257

258+
233259
Public Sub importComponents()
234260
If componentsToImport Is Nothing Then
235-
Debug.Print "Failed to import! 'Dictionary 'componentsToImport' was not initialized."
261+
Debug.Print "Failed to import! Dictionary 'componentsToImport' was not initialized."
236262
Exit Sub
237263
End If
238264
Dim componentName As String
@@ -249,12 +275,13 @@ Public Sub importComponents()
249275
Next
250276

251277
Debug.Print "Finished importing code for " & vbaProjectToImport.name
252-
'We're done, clear globals explicitly to free memory
278+
'We're done, clear globals explicitly to free memory.
253279
Set componentsToImport = Nothing
254280
Set vbaProjectToImport = Nothing
255281
End Sub
256282

257-
' Assumes any component with same name has already been removed
283+
284+
' Assumes any component with same name has already been removed.
258285
Private Sub importComponent(vbaProject As VBProject, filePath As String)
259286
Debug.Print "Importing component from " & filePath
260287
vbaProject.VBComponents.Import filePath
@@ -266,7 +293,7 @@ Private Sub importLines(vbaProject As VBProject, file As Object)
266293
componentName = left(file.name, InStr(file.name, ".") - 1)
267294
Dim c As VBComponent
268295
If Not componentExists(vbaProject, componentName) Then
269-
'Create a sheet to import this code into. We cannot set the ws.codeName property which is read-only,
296+
' Create a sheet to import this code into. We cannot set the ws.codeName property which is read-only,
270297
' instead we set its vbComponent.name which leads to the same result.
271298
Dim addedSheetCodeName As String
272299
addedSheetCodeName = addSheetToWorkbook(componentName, vbaProject.fileName)
@@ -276,7 +303,7 @@ Private Sub importLines(vbaProject As VBProject, file As Object)
276303
Set c = vbaProject.VBComponents(componentName)
277304
Debug.Print "Importing lines from " & componentName & " into component " & c.name
278305

279-
' At this point compilation errors may cause a crash, so we ignore those
306+
' At this point compilation errors may cause a crash, so we ignore those.
280307
On Error Resume Next
281308
c.codeModule.DeleteLines 1, c.codeModule.CountOfLines
282309
c.codeModule.AddFromFile file.Path
@@ -310,6 +337,7 @@ Public Function openWorkbook(ByVal filePath As String) As Workbook
310337
Set openWorkbook = wb
311338
End Function
312339

340+
313341
' Returns the CodeName of the added sheet or an empty String if the workbook could not be opened.
314342
Public Function addSheetToWorkbook(sheetName As String, workbookFilePath As String) As String
315343
Dim wb As Workbook

src/vbaDeveloper.xlam/ErrorHandling.bas

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
Attribute VB_Name = "ErrorHandling"
22
Option Explicit
33

4-
Public Sub RaiseError(errNumber As Integer, Optional errSource As String = "", Optional errMessage As String = "")
4+
Public Sub RaiseError(errNumber As Integer, Optional errSource As String = "", Optional errDescription As String = "")
55
If errSource = "" Then 'set default values
66
errSource = Err.Source
7-
errMessage = Err.Description
7+
errDescription = Err.Description
88
End If
9-
Err.Raise vbObjectError + errNumber, errSource, errMessage
9+
Err.Raise vbObjectError + errNumber, errSource, errDescription
1010
End Sub
1111

12+
1213
Public Sub handleError(Optional errLocation As String = "")
1314
Dim errorMessage As String
1415
errorMessage = "Error in " & errLocation & ", [" & Err.Source & "] : error number " & Err.Number & vbNewLine & Err.Description

src/vbaDeveloper.xlam/Formatter.bas

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ Public Sub formatCode(codePane As codeModule)
165165
Dim lineNr As Integer
166166
For lineNr = 1 To lineCount
167167
Dim line As String
168-
line = Trim(codePane.Lines(lineNr, 1))
168+
line = Trim(codePane.lines(lineNr, 1))
169169
If Not line = "" Then
170170
If isEqual(ONEWORD_ELSE, line) _
171171
Or lineStartsWith(BEG_END_ELSEIF, line) _
@@ -205,7 +205,7 @@ Public Sub removeIndentation(codePane As codeModule)
205205
Dim lineNr As Integer
206206
For lineNr = 1 To lineCount
207207
Dim line As String
208-
line = codePane.Lines(lineNr, 1)
208+
line = codePane.lines(lineNr, 1)
209209
line = Trim(line)
210210
Call codePane.ReplaceLine(lineNr, line)
211211
Next
@@ -249,6 +249,7 @@ Private Function lineStartsWith(begin As String, strToCheck As String) As Boolea
249249
End Function
250250

251251

252-
Private Function isLabel(line As String) As Boolean
253-
isLabel = (right(line, 1) = ":")
252+
Public Function isLabel(line As String) As Boolean
253+
'it must end with a colon: and may not contain a space.
254+
isLabel = (right(line, 1) = ":") And (InStr(line, " ") < 1)
254255
End Function

src/vbaDeveloper.xlam/Menu.bas

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Attribute VB_Name = "Menu"
22
Option Explicit
33

4-
Private Const MENU_TITLE = "vbaDeveloper"
4+
Private Const MENU_TITLE = "VbaDeveloper"
55

66
Public Sub createMenu()
77
Dim rootMenu As CommandBarPopup
@@ -49,6 +49,7 @@ nextProject:
4949
On Error GoTo 0 'reset the error handling
5050
End Sub
5151

52+
5253
Private Function addMenuItem(menu As CommandBarPopup, ByVal onAction As String, ByVal caption As String) As CommandBarButton
5354
Dim menuItem As CommandBarButton
5455
Set menuItem = menu.Controls.Add(Type:=msoControlButton)
@@ -66,6 +67,7 @@ Private Function addSubmenu(menu As CommandBarPopup, ByVal position As Integer,
6667
Set addSubmenu = subMenu
6768
End Function
6869

70+
6971
Private Sub addMenuSeparator(menuItem As CommandBarPopup)
7072
menuItem.BeginGroup = True
7173
End Sub
@@ -89,6 +91,9 @@ Public Sub exportVbProject(ByVal projectName As String)
8991
Dim project As VBProject
9092
Set project = Application.VBE.VBProjects(projectName)
9193
Build.exportVbaCode project
94+
Dim wb As Workbook
95+
Set wb = Build.openWorkbook(project.fileName)
96+
NamedRanges.exportNamedRanges wb
9297
MsgBox "Finished exporting code for: " & project.name
9398

9499
On Error GoTo 0
@@ -97,12 +102,16 @@ exportVbProject_Error:
97102
ErrorHandling.handleError "Menu.exportVbProject"
98103
End Sub
99104

105+
100106
Public Sub importVbProject(ByVal projectName As String)
101107
On Error GoTo importVbProject_Error
102108

103109
Dim project As VBProject
104110
Set project = Application.VBE.VBProjects(projectName)
105111
Build.importVbaCode project
112+
Dim wb As Workbook
113+
Set wb = Build.openWorkbook(project.fileName)
114+
NamedRanges.importNamedRanges wb
106115
MsgBox "Finished importing code for: " & project.name
107116

108117
On Error GoTo 0
@@ -111,6 +120,7 @@ importVbProject_Error:
111120
ErrorHandling.handleError "Menu.importVbProject"
112121
End Sub
113122

123+
114124
Public Sub formatVbProject(ByVal projectName As String)
115125
On Error GoTo formatVbProject_Error
116126

@@ -119,7 +129,7 @@ Public Sub formatVbProject(ByVal projectName As String)
119129
Formatter.formatProject project
120130
MsgBox "Finished formatting code for: " & project.name & vbNewLine _
121131
& vbNewLine _
122-
& "Did you know you can also format your code, while writing it, by typing 'format' in the immediate window?"
132+
& "Did you know you can also format your code, while writing it, by typing 'application.Run ""format""' in the immediate window?"
123133

124134
On Error GoTo 0
125135
Exit Sub

0 commit comments

Comments
 (0)