-
Notifications
You must be signed in to change notification settings - Fork 5
/
javimp.py
200 lines (172 loc) · 9.23 KB
/
javimp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
#!/usr/bin/env python
# javimp.py
# find probable java import statements for a given list of
# classes specified by root name
import sys, os, fileinput, re, time
MISSING_3RD_PARTY_MODULES = []
try:
from lxml import html
except ImportError:
MISSING_3RD_PARTY_MODULES.append("lxml")
try:
import requests
except ImportError:
MISSING_3RD_PARTY_MODULES.append("requests")
try:
import pyperclip
except ImportError:
MISSING_3RD_PARTY_MODULES.append("pyperclip")
HELP_MESSAGE_STRING = """
DESCRIPTION:
This python script tries to match the arguments you give it to classes in a
database, and prints out the results it finds. You can update the list of
classes through web scraping by running the program with no arguments.
USAGE:
python %s [options] [args]
or
python %s [args] [options]
or
python %s [args] [options] [more args]
Please take note that aside from -a mode, only one mode can be in effect
at the time. Precedence is -i > -o > -c > None
OPTIONS:
-h: Displays this help message
-a: Show all mode; use in combination with any other option to add any
additional matches of a class as a commented import
-i: Insert mode; insert the complete import statements directly in one or
more .java source file(s)
Example: Given a source file containing these lines:
import Activity;
import Bundle;
The script would correctly replace this with:
import android.app.Activity;
import android.os.Bundle;
-c: Clipboard mode; copies the correct import statements to your clipboard
-o: File output; Creates a file in your current working directory which
contains the correct import statements
NOTE: The included java_classes.list file should contain all of the standard
library classes and the classes for Android app development as of 2016/06/20.
It may be incomplete, contain erroneous information, or be otherwise unsuitable
for your purposes. This software comes with no warranty or guarantee. Use at
own risk.
Sincerely, Butterbeard Studios.
""" %(sys.argv[0],sys.argv[0],sys.argv[0])
FILELOCATION = os.path.dirname(os.path.abspath(__file__))
if __name__ == '__main__':
if "-h" in sys.argv:
print(HELP_MESSAGE_STRING)
sys.exit()
if (MISSING_3RD_PARTY_MODULES):
sys.stderr.write("Warning - 3rd party modules missing: " + ", ".join(MISSING_3RD_PARTY_MODULES) + "\n")
sys.stderr.write("Some of this program's functionality will not be available.\n\n")
includeAllMatches = "-a" in sys.argv
mode = "i" if "-i" in sys.argv else "o" if "-o" in sys.argv else "c" if "-c" in sys.argv else None
if mode == "c":
if "pyperclip" in MISSING_3RD_PARTY_MODULES:
print("Error - missing modules: pyperclip")
sys.exit()
pyperclip_str = "" # needed to accumulate string to paste to clipboard
if len(sys.argv) == 1:
# only python script supplied
# update source list
if not ("lxml" in MISSING_3RD_PARTY_MODULES or "requests" in MISSING_3RD_PARTY_MODULES):
print("Updating list of classes. This may take a little while...")
# backwards compatible way of not printing a newline at the end
sys.stdout.write("Fetching Java standard library classes...")
sys.stdout.flush()
page = requests.get("http://docs.oracle.com/javase/7/docs/api/allclasses-frame.html")
tree = html.fromstring(page.content)
allStdlibClasses = [a.replace(".html", "").replace("/", ".") for a in tree.xpath('//a[@target="classFrame"]/@href')]
sys.stdout.write("\nFetching Android API classes...")
sys.stdout.flush()
page = requests.get("https://developer.android.com/reference/classes.html")
tree = html.fromstring(page.content)
allAndroidClasses = [a.replace("https://developer.android.com/reference/", "").replace(".html", "").replace("/", ".") for a in tree.xpath('//td[@class="jd-linkcol"]/a/@href')]
allClasses = set(allStdlibClasses+allAndroidClasses)
with open(os.path.join(FILELOCATION, "java_classes.list"), "w") as classlist:
for a in allClasses:
classlist.write(a + "\n")
print("\nList of classes to search for updated.")
else:
print("Error - missing modules: " + ("lxml and requests" if "requests" in MISSING_3RD_PARTY_MODULES else "lxml") if "lxml" in MISSING_3RD_PARTY_MODULES else "requests")
else:
for i in range(1, len(sys.argv)):
if sys.argv[i].startswith("-"):
# option, we don't wanna deal with this
continue
found = False # used if includeAllMatches
if mode == "i":
# insert mode is special, so we handle it separately
for line in fileinput.input(sys.argv[i], inplace=True):
# A NOTE ON FILEINPUT:
#
# With the inplace option set to True, fileinput backs up
# the file, and sets sys.stdout to the original file for the
# duration of the loop. Any calls to sys.stdout.write or
# print will therefore be written on top of the original
# file.
found = False
if line.startswith("import"):
with open(os.path.join(FILELOCATION, "java_classes.list"), "r") as classlist:
for c in classlist:
if c.rstrip().endswith(".%s" %re.sub("import |;", "", line.rstrip())):
if found:
# multiple matches; add new matches as comments just in case
# note how found is set to True AFTER this, so if this is the
# first class found, it will still be False from earlier
sys.stdout.write("//")
sys.stdout.write("import %s;\n" %c.rstrip())
found = True
if not includeAllMatches:
break # fuck it, let's just hope it's the right one
if not found:
# 1) There was no match for the attempted import
# 2) The line didn't start with import, so we jumped straight here
# In either case, insert the line as it was
sys.stdout.write(line)
else:
import_str = ""
with open(sys.argv[i], "r") as f:
for line in f:
if line.startswith(" "):
break
if line.startswith("import"):
found = False
with open(os.path.join(FILELOCATION, "java_classes.list"), "r") as classlist:
for c in classlist:
if c.rstrip().endswith(".%s" %re.sub("import |;", "", line.rstrip())):
if found:
# already found, comment out subsequent matches
import_str += "//"
# we need to use os.linesep here instead of \n because of
# clipboard support
import_str += "import %s;%s" %(c.rstrip(), os.linesep)
found = True
if not includeAllMatches:
break
# So import_str now holds a single complete import statement[1]
# corresponding to the class specified in sys.argv[i], or is
# empty if no match was found[2]. All we need to figure out is
# how to output it.
#
# 1: ... plus an unknown number of subsequent import statements
# that are commented out.
# 2: Or if the specified class to import already had the full
# package specified.
if mode == "o":
# output to file
with open("import_statements.txt", "a") as o:
o.write(import_str)
elif mode == "c":
# we still need to iterate over all the other classes so we can
# copy it all to the clipboard in one go
pyperclip_str += import_str
else:
# write to stdout
sys.stdout.write(import_str)
if pyperclip_str:
# if not -c mode, this was never written to, so it's False
# if else, it's either empty because there were no matches,
# or it contains all the imports, and we can copy them to
# the clipboard
pyperclip.copy(pyperclip_str)