Skip to content

Commit 05adaa9

Browse files
authored
Merge pull request #13 from InnoFang/dev
✨ 💥 New feature to support counting remote repositories
2 parents 03f75ad + 1695736 commit 05adaa9

File tree

13 files changed

+436
-100
lines changed

13 files changed

+436
-100
lines changed

.github/workflows/code-counter-CI.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ jobs:
88
runs-on: ubuntu-latest
99
strategy:
1010
matrix:
11-
python-version: [3.6, 3.7, 3.8, 3.9, 3.10.0]
11+
python-version: [3.7, 3.8, 3.9, 3.10.0]
1212

1313
steps:
1414
- uses: actions/checkout@v3
1515
- name: Set up Python ${{ matrix.python-version }}
1616
uses: actions/setup-python@v3
1717
with:
1818
python-version: ${{ matrix.python-version }}
19-
- name: Test with pytest
19+
- name: Install dependencies
20+
run: |
21+
python -m pip install --upgrade pip
22+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
23+
- name: Run unittest
2024
run: python -m unittest discover -s tests -p "test_*.py"

code_counter/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.0"
1+
__version__ = "1.1.0"

code_counter/__main__.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# coding:utf8
2+
# -*- coding: utf-8 -*-
33

44
import time
55
from code_counter.core.counter import CodeCounter
@@ -8,25 +8,35 @@
88

99

1010
def main():
11-
args = CodeCounterArgs()
11+
cocnt_args = CodeCounterArgs()
1212

1313
config = Config()
14-
if args.has_config_args():
15-
config.invoke(args.config())
14+
if cocnt_args.has_config_args():
15+
config.invoke(cocnt_args.config())
1616
return
1717

1818
code_counter = CodeCounter()
1919

20-
search_args = args.search()
21-
code_counter.setSearchArgs(search_args)
20+
if cocnt_args.has_search_args():
21+
args = cocnt_args.search()
22+
elif cocnt_args.has_remote_args():
23+
args = cocnt_args.remote()
24+
if cocnt_args.is_gitee_repo() and not config.access_tokens.gitee:
25+
config.request_gitee_access_token()
26+
elif cocnt_args.is_github_repo() and not config.access_tokens.github:
27+
config.request_github_access_token()
28+
else:
29+
raise Exception('wrong command')
30+
31+
code_counter.setArgs(args)
2232

2333
time_start = time.time()
2434
code_counter.search()
2535
time_end = time.time()
2636

2737
print('\n\tTotally cost {} s.'.format(time_end - time_start))
2838

29-
if search_args.graph:
39+
if args.graph:
3040
code_counter.visualize()
3141

3242

code_counter/conf/config.json

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,59 @@
11
{
22
"suffix": [
3+
"go",
4+
"lua",
35
"java",
46
"swift",
7+
"cu",
8+
"h",
9+
"pde",
510
"dart",
11+
"cs",
612
"rb",
7-
"cc",
8-
"go",
13+
"cuh",
914
"php",
10-
"sh",
11-
"lua",
12-
"m",
13-
"cu",
14-
"c",
15-
"vb",
15+
"lisp",
16+
"cpp",
17+
"rust",
18+
"R",
1619
"clj",
20+
"jl",
21+
"py",
1722
"js",
18-
"cuh",
23+
"vb",
1924
"hpp",
20-
"R",
21-
"cs",
22-
"kt",
23-
"ts",
25+
"sh",
26+
"cc",
27+
"m",
2428
"scala",
25-
"rust",
26-
"py",
27-
"cpp",
29+
"kt",
2830
"rs",
29-
"h",
30-
"jl",
31-
"pde",
32-
"lisp"
31+
"c",
32+
"ts"
3333
],
3434
"comment": [
35-
"//",
36-
":",
37-
"*",
38-
"*/",
35+
"/*",
3936
";",
40-
"#",
4137
"\"\"\"\"",
42-
"/*"
38+
"*/",
39+
":",
40+
"//",
41+
"#",
42+
"*"
4343
],
4444
"ignore": [
45-
"venv",
45+
".idea",
4646
"dist",
4747
"target",
48-
"build",
49-
".idea",
50-
"node_modules",
51-
".vscode",
5248
".git",
53-
"out"
54-
]
49+
"venv",
50+
".vscode",
51+
"node_modules",
52+
"out",
53+
"build"
54+
],
55+
"access_tokens": {
56+
"github": "",
57+
"gitee": ""
58+
}
5559
}

code_counter/conf/config.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import pkg_resources
66

77

8-
class SetEncoder(json.JSONEncoder):
8+
class ConfigEncoder(json.JSONEncoder):
99
def default(self, obj):
1010
if isinstance(obj, set):
1111
return list(obj)
12+
elif isinstance(obj, Config.AccessTokens):
13+
return obj.__dict__
1214
return json.JSONEncoder.default(self, obj)
1315

1416

@@ -22,13 +24,21 @@ def __call__(cls, *args, **kwargs):
2224

2325

2426
class Config(metaclass=SingletonMeta):
27+
class AccessTokens:
28+
def __init__(self, github='', gitee=''):
29+
self.github = github
30+
self.gitee = gitee
31+
2532
def __init__(self):
2633
conf = self.__load()
2734

2835
self.suffix = set(conf['suffix'])
2936
self.comment = set(conf['comment'])
3037
self.ignore = set(conf['ignore'])
3138

39+
self.access_tokens = self.AccessTokens(github=conf['access_tokens']['github'],
40+
gitee=conf['access_tokens']['gitee'])
41+
3242
def invoke(self, args):
3343
if args.restore:
3444
self.restore()
@@ -43,7 +53,7 @@ def invoke(self, args):
4353
self.show()
4454

4555
def show(self):
46-
print(json.dumps(self.__dict__, indent=4, cls=SetEncoder))
56+
print(json.dumps(self.__dict__, indent=4, cls=ConfigEncoder))
4757

4858
def __confirm(self, tips):
4959
check = input(tips)
@@ -97,7 +107,43 @@ def __load(self):
97107
def __update(self):
98108
filename = pkg_resources.resource_filename(__name__, 'config.json')
99109
with open(filename, 'w') as config:
100-
json.dump(self.__dict__, config, indent=4, cls=SetEncoder)
110+
json.dump(self.__dict__, config, indent=4, cls=ConfigEncoder)
111+
112+
def request_github_access_token(self):
113+
if not self.access_tokens.github:
114+
print("\nThe Github access token is empty. In this case, your usage times will be limited by Github. "
115+
"You can change this situation by setting access token. ")
116+
print("\nRequest a Github access token from https://github.com/settings/tokens/new .")
117+
print("`code-counter` require access to the public repositories .")
118+
print("Choose the `public_repo` in `Select scopes`, and click the `Generate token` to generate a token, "
119+
"then copy the access token and paste it here.")
120+
121+
access_token = input('\nthe Github access token: ')
122+
access_token = access_token.strip()
123+
if access_token:
124+
self.access_tokens.github = access_token
125+
self.__update()
126+
else:
127+
print('\nNo access token set!')
128+
print('======================\n')
129+
130+
def request_gitee_access_token(self):
131+
if not self.access_tokens.gitee:
132+
print("\nThe Gitee access token is empty. In this case, your usage times will be limited by Gitee. "
133+
"You can change this situation by setting access token. ")
134+
print("\nRequest a Gitee access token from https://gitee.com/profile/personal_access_tokens/new .")
135+
print("`code-counter` require access to the public repositories.")
136+
print("Only need to choose the `projects`, and click the `submit` to generate a token, "
137+
"then copy the access token and paste it here.")
138+
139+
access_token = input('\nthe Gitee access token: ')
140+
access_token = access_token.strip()
141+
if access_token:
142+
self.access_tokens.gitee = access_token
143+
self.__update()
144+
else:
145+
print('\nNo access token set!')
146+
print('======================\n')
101147

102148
def restore(self):
103149
self.suffix = {"c", "cc", "clj", "cpp", "cs", "cu", "cuh", "dart", "go", "h", "hpp", "java", "jl", "js", "kt",

code_counter/core/args.py

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env python3
2-
# coding:utf8
2+
# -*- coding: utf-8 -*-
33

4+
import re
45
import sys
56
import argparse
67
from code_counter import __version__
@@ -10,9 +11,32 @@ def split_args(args):
1011
return list(args.split(','))
1112

1213

14+
def remote_repo_parse(repo):
15+
# check HTTPS link first
16+
res = re.search(r"^https://(github|gitee).com/([^/]+)/([^\.]+)\.git$", repo, re.I)
17+
# if got none, then check SSH link
18+
if not res:
19+
res = re.search(r"^git@(github|gitee).com:([^/]+)/([^\.]+)\.git$", repo, re.I)
20+
if not res:
21+
print('Remote repository link parse error! please enter a right HTTPS or SSH link:')
22+
print('\tcocnt remote', repo)
23+
exit(1)
24+
if res.group(1).lower() == 'github':
25+
# Github API
26+
return 'https://api.github.com/repos/{}/{}/contents/'.format(res.group(2), res.group(3))
27+
elif res.group(1).lower() == 'gitee':
28+
# Gitee API
29+
return 'https://gitee.com/api/v5/repos/{}/{}/contents/'.format(res.group(2), res.group(3))
30+
else:
31+
print('Remote repository link parse error! please enter a right HTTPS or SSH link:')
32+
print('\tcocnt remote', repo)
33+
exit(1)
34+
35+
1336
class CodeCounterArgs:
1437
__SEARCH__ = 'search'
1538
__CONFIG__ = 'config'
39+
__REMOTE__ = 'remote'
1640

1741
def __init__(self):
1842
parser = argparse.ArgumentParser(
@@ -21,7 +45,8 @@ def __init__(self):
2145
"that can help you easily count code and display detailed results.",
2246
usage="""cocnt <command> [<args>]
2347
These are common Code-Counter commands used in various situations:
24-
search Search code in the given path(s)
48+
search Search and count code lines for the given path(s)
49+
remote Search and count the remote repository
2550
config Configure Code-Counter
2651
""")
2752
parser.add_argument('--version', action='version',
@@ -38,16 +63,36 @@ def __init__(self):
3863
def has_search_args(self):
3964
return self.__SEARCH__ in self.__args
4065

66+
def has_remote_args(self):
67+
return self.__REMOTE__ in self.__args
68+
4169
def has_config_args(self):
4270
return self.__CONFIG__ in self.__args
4371

72+
def is_github_repo(self):
73+
if not self.has_remote_args():
74+
return False
75+
return self.__args[self.__REMOTE__].input_path.startswith('https://api.github.com/repos/')
76+
77+
def is_gitee_repo(self):
78+
if not self.has_remote_args():
79+
return False
80+
return self.__args[self.__REMOTE__].input_path.startswith('https://gitee.com/api/v5/repos/')
81+
4482
def search(self):
4583
if not self.has_search_args():
4684
return None
4785
if self.__args[self.__SEARCH__] == argparse.Namespace():
4886
self.__args[self.__SEARCH__] = self.__search()
4987
return self.__args[self.__SEARCH__]
5088

89+
def remote(self):
90+
if not self.has_remote_args():
91+
return None
92+
if self.__args[self.__REMOTE__] == argparse.Namespace():
93+
self.__args[self.__REMOTE__] = self.__remote()
94+
return self.__args[self.__REMOTE__]
95+
5196
def config(self):
5297
if not self.has_config_args():
5398
return None
@@ -57,10 +102,10 @@ def config(self):
57102

58103
def __search(self):
59104
parser = argparse.ArgumentParser(
60-
description="Search code in the given path(s)",
105+
description="Search and count code lines for the given path(s)",
61106
usage="cocnt search input_path [-h] [-v] [-g] "
62107
"[-o OUTPUT_PATH] [--suffix SUFFIX] [--comment COMMENT] [--ignore IGNORE]")
63-
parser.add_argument('input_path', type=split_args,
108+
parser.add_argument('input_path', metavar="paths", type=split_args,
64109
help="counting the code lines according to the given path(s)")
65110
parser.add_argument('-v', '--verbose', dest="verbose", action='store_true',
66111
help="show verbose information")
@@ -76,6 +121,27 @@ def __search(self):
76121
help="ignore some directories or files that you don't want to count")
77122
return parser.parse_args(sys.argv[2:])
78123

124+
def __remote(self):
125+
parser = argparse.ArgumentParser(
126+
description="Search and count the remote repository with a given Github or Gitee HTTP link",
127+
usage="cocnt remote <repository> [-h] [-v] [-g] "
128+
"[-o OUTPUT_PATH] [--suffix SUFFIX] [--comment COMMENT] [--ignore IGNORE]")
129+
parser.add_argument('input_path', metavar="repository", type=remote_repo_parse,
130+
help="search and count a remote repository")
131+
parser.add_argument('-v', '--verbose', dest="verbose", action='store_true',
132+
help="show verbose information")
133+
parser.add_argument('-g', '--graph', dest='graph', action='store_true',
134+
help="choose to whether to visualize the result")
135+
parser.add_argument('-o', '--output', dest='output_path',
136+
help="specify an output path if you want to store the result")
137+
parser.add_argument('--suffix', dest='suffix', type=split_args,
138+
help="what code files do you want to count")
139+
parser.add_argument('--comment', dest='comment', type=split_args,
140+
help="the comment symbol, which can be judged whether the current line is a comment")
141+
parser.add_argument('--ignore', dest='ignore', type=split_args,
142+
help="ignore some directories or files that you don't want to count")
143+
return parser.parse_args(sys.argv[2:])
144+
79145
def __config(self):
80146
parser = argparse.ArgumentParser(
81147
prog="code-counter",

0 commit comments

Comments
 (0)