Skip to content

Commit 8871470

Browse files
committed
feat(Config & CLI): Add config file support and default values
- Add support for configuration file - Add debugging flag `-v` + - Replaced all mandatory CLI options with default values (root directory, IDEA home, Inspection profile name, ...)
1 parent b30598f commit 8871470

File tree

4 files changed

+148
-56
lines changed

4 files changed

+148
-56
lines changed

.ideainspect

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Levels to look for. Default: WARNING,ERROR
2+
levels: WARNING,ERROR,INFO
3+
4+
# Inspection result files to skip. For example "TodoComment" or "TodoComment.xml".
5+
skip: TodoComment,Annoatator
6+
7+
# Ignore issues affecting source files matching given regex. Example ".*/generated/.*".
8+
skipfile: .*/generated/.*,src/main/Foo.java
9+
10+
# Target directory to place the IDEA inspection XML result files. Default: target/inspection-results
11+
resultdir: target/inspection-results
12+
13+
# IDEA installation home directory. Default: IDEA_HOME environment variable or "idea".
14+
ideahome: /home/ben/devel/idea
15+
16+
# Limit IDEA inspection to this directory
17+
# dir: .
18+
19+
# Use this inspection profile file located ".idea/inspectionProfiles".
20+
# profile: Project_Default.xml
21+
22+
# IDEA project root directory containing the ".idea" directory
23+
# rootdir: .

CHANGES.asciidoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
= Changes
2+
3+
[cols="1,5", options="header"]
4+
|===
5+
| Version | Change
6+
| 1.2 | Add support for configuration file +
7+
Add debugging flag `-v` +
8+
Replaced all mandatory CLI options with default values (root directory, IDEA home, Inspection profile name, ...)
9+
| 1.1 | Support for ignoring issues affecting specific source files using a regular expression (Option `-sf`)
10+
| 1.0 | First release
11+
|===

README.asciidoc

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ NOTE: Due to a limitation of IDEA itself it is not possible to run more than one
7272

7373

7474
== Usage
75+
76+
=== Configuration file base usage
77+
78+
IDEA CLI Inspector supports configuration via a `.ideainspect` file in the project
79+
root directory. If you
80+
81+
=== Command line based usage
7582
For a full list of options please run `ideainspect.groovy -h`:
7683

7784
----
@@ -194,13 +201,7 @@ To fix this error simply assign every module a SDK other than "Project SDK".
194201
The source code is located under https://github.com/bentolor/idea-cli-inspector.
195202

196203

197-
== Changes
198-
[cols="1,5", options="header"]
199-
|===
200-
| Version | Change
201-
| 1.1 | Support for ignoring issues affecting specific source files using a regular expression (Option `-sf`)
202-
| 1.0 | First release
203-
|===
204+
204205

205206
== License
206207
Licensed under the Apache License, Version 2.0 (the "License");

ideainspect.groovy

Lines changed: 106 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,94 +24,113 @@
2424
*/
2525
import groovy.io.FileType
2626
import org.apache.commons.cli.Option
27+
import java.nio.file.Paths
2728

28-
println "= IntellIJ IDEA Code Analysis Wrapper - v1.1 - @bentolor"
29+
println "= IntellIJ IDEA Code Analysis Wrapper - v1.2 - @bentolor"
2930

3031
// Defaults
3132
def resultDir = "target/inspection-results"
3233
def acceptedLeves = ["[WARNING]", "[ERROR]"]
3334
def skipResults = []
3435
def skipIssueFilesRegex = []
3536
def ideaTimeout = 20 // broken - has no effect
36-
37+
verbose = false
3738

3839
//
3940
// --- Command line option parsing
4041
//
41-
OptionAccessor opt = parseCli()
42-
43-
if (!opt) System.exit(1) // will print usage automatically
44-
if (opt.help) { cliBuilder.usage(); System.exit(1); }
42+
def configOpts = parseConfigFile()
43+
def OptionAccessor cliOpts = parseCli((configOpts << args).flatten())
4544

4645
// Levels
47-
if (opt.l) {
46+
if (cliOpts.l) {
4847
acceptedLeves.clear()
49-
opt.ls.each { level -> acceptedLeves << "[" + level + "]" }
48+
cliOpts.ls.each { level -> acceptedLeves << "[" + level + "]" }
5049
}
5150
// Skip result XML files
52-
if (opt.s) opt.ss.each { skipFile -> skipResults << skipFile.replace(".xml", "") }
51+
if (cliOpts.s) {
52+
cliOpts.ss.each { skipFile -> skipResults << skipFile.replace(".xml", "") }
53+
}
5354
// Skip issues affecting given file name regex
54-
if (opt.sf) opt.sfs.each { skipRegex -> skipIssueFilesRegex << skipRegex }
55+
if (cliOpts.sf) {
56+
cliOpts.sfs.each { skipRegex -> skipIssueFilesRegex << skipRegex }
57+
}
5558
// target directory
56-
if (opt.t) resultDir = opt.t
59+
if (cliOpts.t) {
60+
resultDir = cliOpts.t
61+
}
5762
// timeout
58-
// if (opt.to) ideaTimeout = opt.to.toInteger();
63+
// if (cliOpts.to) ideaTimeout = cliOpts.to.toInteger();
5964
// IDEA home
6065
def scriptExtension = (System.properties['os.name'].toLowerCase().contains('windows')) ? ".bat" : ".sh"
6166
def pathSep = File.separator
62-
def ideaPath = new File(opt.i + pathSep + "bin" + pathSep + "idea" + scriptExtension)
63-
assertPath(ideaPath, "IDEA Installation directory")
64-
// Root Diretory
65-
def rootDir = new File(opt.r)
66-
def dotIdeaDir = new File(opt.r + pathSep + ".idea")
67-
assertPath(dotIdeaDir, "IDEA project directory")
67+
def ideaHome = cliOpts.i ?: (System.getenv("IDEA_HOME") ?: "idea")
68+
def ideaPath = new File(ideaHome + pathSep + "bin" + pathSep + "idea" + scriptExtension)
69+
assertPath(ideaPath, "IDEA Installation directory",
70+
"Use a IDEA_HOME environment variable or the `ideahome` property in `.ideainspect` \n" +
71+
"or the `-i` command line option to point me to a valid IntelliJ installation")
72+
// Passed project root Directory or working directory
73+
def rootDir = cliOpts.r ? new File(cliOpts.r) : Paths.get(".").toAbsolutePath().normalize().toFile()
74+
def dotIdeaDir = new File(rootDir, ".idea")
75+
assertPath(dotIdeaDir, "IDEA project directory", "Please set the `rootdir` property to the location of your `.idea` project")
6876
// Inspection Profile
69-
def profilePath = new File(dotIdeaDir.path + pathSep + "inspectionProfiles" + pathSep + opt.p)
77+
def profileName = cliOpts.p ?: "Project_Default.xml"
78+
def profilePath = new File(dotIdeaDir.path + pathSep + "inspectionProfiles" + pathSep + profileName)
7079
assertPath(profilePath, "IDEA inspection profile file")
7180

7281
// Prepare result directory
7382
def resultPath = new File(resultDir);
74-
if (!resultPath.isAbsolute()) resultPath = new File(rootDir, resultDir);
75-
if (resultPath.exists() && !resultPath.deleteDir()) fail "Unable to remove result dir " + resultPath.absolutePath
76-
if (!resultPath.mkdirs()) fail "Unable to create result dir " + resultPath.absolutePath
77-
83+
if (!resultPath.absolute) {
84+
resultPath = new File(rootDir, resultDir)
85+
};
86+
if (resultPath.exists() && !resultPath.deleteDir()) {
87+
fail "Unable to remove result dir " + resultPath.absolutePath
88+
}
89+
if (!resultPath.mkdirs()) {
90+
fail "Unable to create result dir " + resultPath.absolutePath
91+
}
7892

7993
//
8094
// --- Actually running IDEA
8195
//
8296

8397
// ~/projects/dashboard.git/. ~/projects/dashboard.git/.idea/inspectionProfiles/bens_idea15_2015_11.xml /tmp/ -d server
8498
def ideaArgs = [ideaPath.path, "inspect", rootDir.absolutePath, profilePath.absolutePath, resultPath.absolutePath, "-v1"]
85-
if (opt.d) ideaArgs << "-d" << opt.d
99+
if (cliOpts.d) {
100+
ideaArgs << "-d" << cliOpts.d
101+
}
86102

87103
println "#"
88104
println "# Running IDEA IntelliJ Inspection"
89105
println "#"
90106
println "Executing: " + ideaArgs.join(" ")
91107

92-
def processBuilder=new ProcessBuilder(ideaArgs)
108+
def processBuilder = new ProcessBuilder(ideaArgs)
93109
processBuilder.redirectErrorStream(true)
94110
processBuilder.directory(rootDir) // <--
95111
def ideaProcess = processBuilder.start()
96112
ideaProcess.consumeProcessOutput(System.out, System.err)
97113
ideaProcess.waitForOrKill(1000 * 60 * ideaTimeout)
98114
def exitValue = ideaProcess.exitValue()
99-
if (exitValue != 0) fail("IDEA Process returned with an unexpected return code of $exitValue")
100-
115+
if (exitValue != 0) {
116+
fail("IDEA Process returned with an unexpected return code of $exitValue")
117+
}
101118

102119
//
103120
// --- Now lets look on the results
104121
//
105122
analyzeResult(resultPath, acceptedLeves, skipResults, skipIssueFilesRegex)
106123

107-
108124
//
109125
// --- Helper functions
110126
//
111127

112-
void assertPath(File profilePath, String description) {
128+
void assertPath(File profilePath, String description, String hint = null) {
113129
if (!profilePath.exists()) {
114130
println description + " " + profilePath.path + " not found!"
131+
if (hint) {
132+
println hint
133+
}
115134
System.exit(1)
116135
}
117136
}
@@ -123,43 +142,81 @@ void fail(String message) {
123142
System.exit(1)
124143
}
125144

145+
private parseConfigFile() {
146+
// Parse root dir with minimal CliBuilder
147+
def cliBuilder = new CliBuilder()
148+
cliBuilder.with {
149+
r argName: 'dir', longOpt: 'rootdir', args: 1, required: false,
150+
'IDEA project root directory containing the ".idea" directory'
151+
v argName: 'verbose', longOpt: 'verbose', args: 0, required: false,
152+
'Enable verbose logging & debugging'
153+
}
126154

127-
private OptionAccessor parseCli() {
128-
def cliBuilder = new CliBuilder(usage: 'groovy ideainspect.groovy\n -i <IDEA_HOME> -p <PROFILEXML> -r <PROJDIR>')
155+
def opt = cliBuilder.parse(args)
156+
verbose = verbose ?: (opt && opt.v)
157+
def rootDir = opt != null && opt.r ? opt.r : '.'
158+
159+
def configFile = new File(rootDir + '/.ideainspect')
160+
def configArgs = []
161+
if (configFile.exists()) {
162+
if (verbose) {
163+
println "Parsing " + configFile.absolutePath
164+
}
165+
configFile.eachLine { line ->
166+
def values = line.split(':')
167+
if (!line.startsWith('#') && values.length == 2) {
168+
configArgs.push('--' + values[0].trim())
169+
configArgs.push(values[1].trim())
170+
}
171+
}
172+
}
173+
if (verbose) {
174+
println configArgs
175+
}
176+
return configArgs
177+
}
178+
179+
private OptionAccessor parseCli(configArgs) {
180+
def cliBuilder = new CliBuilder(usage: 'groovy ideainspect.groovy\n -i <IDEA_HOME> -p <PROFILEXML> -r <PROJDIR>',
181+
stopAtNonOption: false)
129182
cliBuilder.with {
130183
h argName: 'help', longOpt: 'help', 'Show usage information and quit'
131184
l argName: 'level', longOpt: 'levels', args: Option.UNLIMITED_VALUES, valueSeparator: ',',
132185
'Levels to look for. Default: WARNING,ERROR'
133186
s argName: 'file', longOpt: 'skip', args: Option.UNLIMITED_VALUES, valueSeparator: ',',
134187
'Analysis result files to skip. For example "TodoComment" or "TodoComment.xml".'
135188
sf argName: 'regex', longOpt: 'skipfile', args: Option.UNLIMITED_VALUES, valueSeparator: ',',
136-
'Ignore issues affecting source files matching given regex. Example ".*/generated/.*".'
189+
'Ignore issues affecting source files matching given regex. Example ".*/generated/.*".'
137190
t argName: 'dir', longOpt: 'resultdir', args: 1,
138191
'Target directory to place the IDEA inspection XML result files. Default: target/inspection-results'
139-
i argName: 'dir', longOpt: 'ideahome', args: 1, required: true,
140-
'IDEA installation home directory. Required'
192+
i argName: 'dir', longOpt: 'ideahome', args: 1,
193+
'IDEA installation home directory. Default: IDEA_HOME environment variable or "idea"'
141194
d argName: 'dir', longOpt: 'dir', args: 1, 'Limit IDEA inspection to this directory'
142-
p argName: 'file', longOpt: 'profile', args: 1, required: true,
143-
'Use this inspection profile file located ".idea/inspectionProfiles". \nExample: "myprofile.xml"'
144-
r argName: 'dir', longOpt: 'rootdir', args: 1, required: true,
145-
'IDEA project root directory containing the ".idea" directory'
195+
p argName: 'file', longOpt: 'profile', args: 1,
196+
'Use this inspection profile file located ".idea/inspectionProfiles". \nExample: "myprofile.xml". Default: "Project_Default.xml"'
197+
r argName: 'dir', longOpt: 'rootdir', args: 1,
198+
'IDEA project root directory containing the ".idea" directory. Default: Working directory'
199+
v argName: 'verbose', longOpt: 'verbose', args: 0,
200+
'Enable verbose logging & debugging'
146201
//to argName: 'minutes', longOpt: 'timeout', args: 1,
147202
// 'Timeout in Minutes to wait for IDEA to complete the inspection. Default:'
148203
}
149204

150-
def opt = cliBuilder.parse(args)
205+
def opt = cliBuilder.parse(configArgs)
151206

152-
if (!opt) System.exit(1); // will print usage automatically
207+
if (!opt) {
208+
System.exit(1)
209+
}; // will print usage automatically
153210
if (opt.help) {
154-
println ""
155-
println "This tools runs IntelliJ IDEA inspection via command line and"
156-
println "tries to parse the output. \n"
157-
println "Example usage:"
158-
println " ./ideainspect.groovy -i ~/devel/idea -r . -p myinspections.xml \\"
159-
println " -d src/main/java -s unused,Annotator,TodoComment.xml -l ERROR"
160-
println " "
161-
cliBuilder.usage();
162-
System.exit(1);
211+
println ""
212+
println "This tools runs IntelliJ IDEA inspection via command line and"
213+
println "tries to parse the output. \n"
214+
println "Example usage:"
215+
println " ./ideainspect.groovy -i ~/devel/idea -r . -p myinspections.xml \\"
216+
println " -d src/main/java -s unused,Annotator,TodoComment.xml -l ERROR"
217+
println " "
218+
cliBuilder.usage();
219+
System.exit(1);
163220
}
164221
opt
165222
}

0 commit comments

Comments
 (0)