Skip to content

Commit 8690e7a

Browse files
committed
implementation to handle podspec
Signed-off-by: rpotter12 <rohitpotter12@gmail.com>
1 parent 72850bd commit 8690e7a

4 files changed

Lines changed: 313 additions & 4 deletions

File tree

src/packagedcode/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@
2525
from __future__ import absolute_import
2626
from __future__ import unicode_literals
2727

28-
from packagedcode import build
29-
from packagedcode import chef
30-
from packagedcode import models
3128
from packagedcode import about
3229
from packagedcode import bower
33-
from packagedcode import conda
30+
from packagedcode import build
3431
from packagedcode import cargo
32+
from packagedcode import chef
33+
from packagedcode import cocoapods
34+
from packagedcode import conda
3535
from packagedcode import freebsd
3636
from packagedcode import haxe
3737
from packagedcode import maven
38+
from packagedcode import models
3839
from packagedcode import npm
3940
from packagedcode import nuget
4041
from packagedcode import phpcomposer
@@ -62,6 +63,7 @@
6263
phpcomposer.PHPComposerPackage,
6364
haxe.HaxePackage,
6465
cargo.RustCargoCrate,
66+
cocoapods.CocoapodsPackage,
6567
models.MeteorPackage,
6668
bower.BowerPackage,
6769
freebsd.FreeBSDPackage,

src/packagedcode/cocoapods.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# All rights reserved.
2+
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
3+
# The ScanCode software is licensed under the Apache License version 2.0.
4+
# Data generated with ScanCode require an acknowledgment.
5+
# ScanCode is a trademark of nexB Inc.
6+
#
7+
# You may not use this software except in compliance with the License.
8+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing, software distributed
10+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+
# specific language governing permissions and limitations under the License.
13+
#
14+
# When you publish or redistribute any data created with ScanCode or any ScanCode
15+
# derivative work, you must accompany this data with the following acknowledgment:
16+
#
17+
# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
18+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
19+
# ScanCode should be considered or used as legal advice. Consult an Attorney
20+
# for any legal advice.
21+
# ScanCode is a free software code scanning tool from nexB Inc. and others.
22+
# Visit https://github.com/nexB/scancode-toolkit/ for support and download.
23+
24+
from __future__ import absolute_import
25+
from __future__ import print_function
26+
from __future__ import unicode_literals
27+
28+
import logging
29+
import re
30+
31+
import attr
32+
from packageurl import PackageURL
33+
34+
from commoncode.fileutils import py2
35+
from commoncode import filetype
36+
from commoncode import fileutils
37+
from packagedcode import models
38+
from packagedcode import spec
39+
40+
41+
"""
42+
Handle cocoapods packages manifests for macOS and iOS
43+
including .podspec, Podfile and Podfile.lock files.
44+
See https://cocoapods.org
45+
"""
46+
47+
# TODO: implementation to get dependency data using gemsfileparser
48+
# Check: https://gitlab.com/balasankarc/gemfileparser
49+
# TODO: override the license detection to detect declared_license correctly.
50+
51+
52+
TRACE = False
53+
54+
logger = logging.getLogger(__name__)
55+
56+
if TRACE:
57+
import sys
58+
logging.basicConfig(stream=sys.stdout)
59+
logger.setLevel(logging.DEBUG)
60+
61+
62+
@attr.s()
63+
class CocoapodsPackage(models.Package):
64+
metafiles = ('*.podspec',)
65+
extensions = ('.podspec',)
66+
default_type = 'pods'
67+
default_primary_language = 'Objective-C'
68+
default_web_baseurl = 'https://cocoapods.org'
69+
default_download_baseurl = None
70+
default_api_baseurl = None
71+
72+
@classmethod
73+
def recognize(cls, location):
74+
yield parse(location)
75+
76+
def repository_homepage_url(self, baseurl=default_web_baseurl):
77+
return '{}/pods/{}'.format(baseurl, self.name)
78+
79+
def repository_download_url(self):
80+
return '{}/archive/{}.zip'.format(self.homepage_url, self.version)
81+
82+
83+
def is_podspec(location):
84+
"""
85+
Checks is the file is a podspec file or not.
86+
"""
87+
return (filetype.is_file(location) and location.endswith('.podspec'))
88+
89+
90+
def parse(location):
91+
"""
92+
Return a Package object from a .podspec file or None.
93+
"""
94+
if not is_podspec(location):
95+
return
96+
97+
podspec_data = spec.parse_spec(location)
98+
return build_package(podspec_data)
99+
100+
101+
def build_package(podspec_data):
102+
"""
103+
Return a Pacakge object from a package data mapping or None.
104+
"""
105+
name = podspec_data.get('name')
106+
version = podspec_data.get('version')
107+
declared_license = podspec_data.get('license')
108+
summary = podspec_data.get('summary')
109+
description = podspec_data.get('description')
110+
homepage_url = podspec_data.get('homepage_url')
111+
source = podspec_data.get('source')
112+
authors = podspec_data.get('author') or None
113+
114+
author_names = []
115+
author_email = []
116+
if authors:
117+
for split_author in authors:
118+
split_author = split_author.strip()
119+
author, email = parse_person(split_author)
120+
author_names.append(author)
121+
author_email.append(email)
122+
123+
parties = []
124+
if authors:
125+
parties.append(
126+
models.Party(
127+
type=models.party_person,
128+
name=', '.join(author_names),
129+
email=', '.join(author_email),
130+
role='author'
131+
)
132+
)
133+
134+
if len(summary) > len(description):
135+
description = summary
136+
137+
package = CocoapodsPackage(
138+
name=name,
139+
version=version,
140+
vcs_url=source,
141+
source_packages=list(source.split('\n')),
142+
description=description,
143+
declared_license=declared_license,
144+
homepage_url=homepage_url,
145+
parties=parties
146+
)
147+
return package
148+
149+
150+
person_parser = re.compile(
151+
r'^(?P<name>[a-zA-Z0-9\s]+)'
152+
r'=>'
153+
r'(?P<email>[\S+]+$)'
154+
).match
155+
156+
person_parser_only_name = re.compile(
157+
r'^(?P<name>[a-zA-Z0-9\s]+)'
158+
).match
159+
160+
161+
def parse_person(person):
162+
"""
163+
https://guides.cocoapods.org/syntax/podspec.html#authors
164+
Author can be in the form:
165+
s.author = 'Rohit Potter'
166+
or
167+
s.author = 'Rohit Potter=>rohit@gmail.com'
168+
Author check:
169+
>>> p = parse_person('Rohit Potter=>rohit@gmail.com')
170+
>>> assert p == ('Rohit Potter', 'rohit@gmail.com')
171+
>>> p = parse_person('Rohit Potter')
172+
>>> assert p == ('Rohit Potter', None)
173+
"""
174+
parsed = person_parser(person)
175+
if not parsed:
176+
parsed = person_parser_only_name(person)
177+
name = parsed.group('name')
178+
email = None
179+
else:
180+
name = parsed.group('name')
181+
email = parsed.group('email')
182+
183+
return name, email

src/packagedcode/spec.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# All rights reserved.
2+
# http://nexb.com and https://github.com/nexB/scancode-toolkit/
3+
# The ScanCode software is licensed under the Apache License version 2.0.
4+
# Data generated with ScanCode require an acknowledgment.
5+
# ScanCode is a trademark of nexB Inc.
6+
#
7+
# You may not use this software except in compliance with the License.
8+
# You may obtain a copy of the License at: http://apache.org/licenses/LICENSE-2.0
9+
# Unless required by applicable law or agreed to in writing, software distributed
10+
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
11+
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
12+
# specific language governing permissions and limitations under the License.
13+
#
14+
# When you publish or redistribute any data created with ScanCode or any ScanCode
15+
# derivative work, you must accompany this data with the following acknowledgment:
16+
#
17+
# Generated with ScanCode and provided on an "AS IS" BASIS, WITHOUT WARRANTIES
18+
# OR CONDITIONS OF ANY KIND, either express or implied. No content created from
19+
# ScanCode should be considered or used as legal advice. Consult an Attorney
20+
# for any legal advice.
21+
# ScanCode is a free software code scanning tool from nexB Inc. and others.
22+
# Visit https://github.com/nexB/scancode-toolkit/ for support and download.
23+
24+
25+
import csv
26+
import glob
27+
import io
28+
import os
29+
import re
30+
31+
32+
"""
33+
Handle Cocoapods(.podspec) and Ruby(.gemspec) files.
34+
"""
35+
36+
37+
def get_stripped_data(line):
38+
"""
39+
Return data after removing unnecessary special character and space.
40+
"""
41+
if '#' in line:
42+
line = line[:line.index('#')]
43+
stripped_data = line.strip()
44+
stripped_data = stripped_data.replace("'", '')
45+
stripped_data = stripped_data.replace('"', '')
46+
stripped_data = stripped_data.replace('{', '')
47+
stripped_data = stripped_data.replace('}', '')
48+
stripped_data = stripped_data.strip()
49+
return stripped_data
50+
51+
52+
def get_description(location):
53+
"""
54+
https://guides.cocoapods.org/syntax/podspec.html#description
55+
description is in the form:
56+
spec.description = <<-DESC
57+
Computes the meaning of life.
58+
Features:
59+
1. Is self aware
60+
...
61+
42. Likes candies.
62+
DESC
63+
Return description from podspec.
64+
"""
65+
with io.open(location, encoding='utf-8', closefd=True) as data:
66+
lines = data.readlines()
67+
description = ''
68+
for i, content in enumerate(lines):
69+
if '.description' in content:
70+
for cont in lines[i+1:]:
71+
if 'DESC' in cont:
72+
break
73+
description += ' '.join([description, cont.strip()])
74+
break
75+
description.strip()
76+
return description
77+
78+
79+
def parse_spec(location):
80+
"""
81+
Return dictionary contains podspec or gemspec file data.
82+
"""
83+
with io.open(location, encoding='utf-8', closefd=True) as data:
84+
lines = data.readlines()
85+
86+
spec_data = {}
87+
88+
for line in lines:
89+
if '.name' in line:
90+
name = re.sub(r'/*.*name.*?=', '', line)
91+
spec_data['name'] = get_stripped_data(name)
92+
elif '.version' in line and '.version.' not in line:
93+
version = re.sub(r'/*.*version.*?=', '', line)
94+
spec_data['version'] = get_stripped_data(version)
95+
elif '.license' in line:
96+
license_type = re.sub(r'/*.*license.*?=', '', line)
97+
spec_data['license'] = get_stripped_data(license_type)
98+
elif '.source' in line and '.source_files' not in line:
99+
source = re.sub(r'/*.*source.*?>', '', line)
100+
stripped_source = re.sub(r',.*', '', source)
101+
spec_data['source'] = get_stripped_data(stripped_source)
102+
elif '.author' in line:
103+
authors = re.sub(r'/*.*author.*?=', '', line)
104+
stripped_authors = get_stripped_data(authors)
105+
stripped_authors = stripped_authors.replace(' => ', "=>")
106+
stripped_authors = stripped_authors.strip()
107+
stripped_authors = stripped_authors.split(',')
108+
spec_data['author'] = stripped_authors
109+
elif '.summary' in line:
110+
summary = re.sub(r'/*.*summary.*?=', '', line)
111+
spec_data['summary'] = get_stripped_data(summary)
112+
elif '.description' in line:
113+
spec_data['description'] = get_description(location)
114+
elif '.homepage' in line:
115+
homepage_url = re.sub(r'/*.*homepage.*?=', '', line)
116+
spec_data['homepage_url'] = get_stripped_data(homepage_url)
117+
118+
return spec_data

tests/packagedcode/data/plugin/help.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ Package: nuget
200200
extensions: .nupkg
201201
filetypes: zip archive, microsoft ooxml
202202

203+
--------------------------------------------
204+
Package: pods
205+
class: packagedcode.cocoapods:CocoapodsPackage
206+
metafiles: *.podspec
207+
extensions: .podspec
208+
203209
--------------------------------------------
204210
Package: pypi
205211
class: packagedcode.pypi:PythonPackage

0 commit comments

Comments
 (0)