Skip to content

Commit 31b9d58

Browse files
authored
Merge pull request #2075 from rpotter12/gemspec-parser-new
Packagedcode to handle Ruby(.gemspec) and Cocoapods(.podspec)
2 parents bce3557 + cf05a90 commit 31b9d58

33 files changed

Lines changed: 2007 additions & 440 deletions

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ def read(*names, **kwargs):
188188
'xmltodict >= 0.11.0',
189189
'javaproperties >= 0.5',
190190
'toml >= 0.10.0',
191+
'gemfileparser >= 0.7.0',
191192
'pkginfo >= 1.5.0.1',
192193
'dparse >= 0.4.1',
193194

src/packagedcode/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@
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 conda
34+
from packagedcode import cocoapods
3535
from packagedcode import freebsd
3636
from packagedcode import golang
3737
from packagedcode import haxe
3838
from packagedcode import maven
39+
from packagedcode import models
3940
from packagedcode import npm
4041
from packagedcode import nuget
4142
from packagedcode import opam
@@ -64,6 +65,7 @@
6465
phpcomposer.PHPComposerPackage,
6566
haxe.HaxePackage,
6667
cargo.RustCargoCrate,
68+
cocoapods.CocoapodsPackage,
6769
opam.OpamPackage,
6870
models.MeteorPackage,
6971
bower.BowerPackage,

src/packagedcode/cocoapods.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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.spec 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: override the license detection to detect declared_license correctly.
48+
49+
50+
TRACE = False
51+
52+
logger = logging.getLogger(__name__)
53+
54+
if TRACE:
55+
import sys
56+
logging.basicConfig(stream=sys.stdout)
57+
logger.setLevel(logging.DEBUG)
58+
59+
60+
@attr.s()
61+
class CocoapodsPackage(models.Package):
62+
metafiles = ('*.podspec',)
63+
extensions = ('.podspec',)
64+
default_type = 'pods'
65+
default_primary_language = 'Objective-C'
66+
default_web_baseurl = 'https://cocoapods.org'
67+
default_download_baseurl = None
68+
default_api_baseurl = None
69+
70+
@classmethod
71+
def recognize(cls, location):
72+
yield parse(location)
73+
74+
def repository_homepage_url(self, baseurl=default_web_baseurl):
75+
return '{}/pods/{}'.format(baseurl, self.name)
76+
77+
def repository_download_url(self):
78+
return '{}/archive/{}.zip'.format(self.homepage_url, self.version)
79+
80+
81+
def is_podspec(location):
82+
"""
83+
Checks if the file is actually a podspec file
84+
"""
85+
return (filetype.is_file(location) and location.endswith('.podspec'))
86+
87+
88+
def parse(location):
89+
"""
90+
Return a Package object from a .podspec file or None.
91+
"""
92+
if not is_podspec(location):
93+
return
94+
95+
podspec_object = Spec()
96+
podspec_data = podspec_object.parse_spec(location)
97+
return build_package(podspec_data)
98+
99+
100+
def build_package(podspec_data):
101+
"""
102+
Return a Package object from a package data mapping or None.
103+
"""
104+
name = podspec_data.get('name')
105+
version = podspec_data.get('version')
106+
declared_license = podspec_data.get('license')
107+
summary = podspec_data.get('summary')
108+
description = podspec_data.get('description')
109+
homepage_url = podspec_data.get('homepage_url')
110+
source = podspec_data.get('source')
111+
authors = podspec_data.get('author') or []
112+
113+
author_names = []
114+
author_email = []
115+
if authors:
116+
for split_author in authors:
117+
split_author = split_author.strip()
118+
author, email = parse_person(split_author)
119+
author_names.append(author)
120+
author_email.append(email)
121+
122+
parties = list(party_mapper(author_names, author_email))
123+
124+
package = CocoapodsPackage(
125+
name=name,
126+
version=version,
127+
vcs_url=source,
128+
source_packages=list(source.split('\n')),
129+
description=description,
130+
declared_license=declared_license,
131+
homepage_url=homepage_url,
132+
parties=parties
133+
)
134+
135+
return package
136+
137+
138+
def party_mapper(author, email):
139+
"""
140+
Yields a Party object with author and email.
141+
"""
142+
for person in author:
143+
yield models.Party(
144+
type=models.party_person,
145+
name=person,
146+
role='author')
147+
148+
for person in email:
149+
yield models.Party(
150+
type=models.party_person,
151+
email=person,
152+
role='email')
153+
154+
155+
person_parser = re.compile(
156+
r'^(?P<name>[\w\s(),-_.,]+)'
157+
r'=>'
158+
r'(?P<email>[\S+]+$)'
159+
).match
160+
161+
person_parser_only_name = re.compile(
162+
r'^(?P<name>[\w\s(),-_.,]+)'
163+
).match
164+
165+
166+
def parse_person(person):
167+
"""
168+
Return name and email from person string.
169+
170+
https://guides.cocoapods.org/syntax/podspec.html#authors
171+
Author can be in the form:
172+
s.author = 'Rohit Potter'
173+
or
174+
s.author = 'Rohit Potter=>rohit@gmail.com'
175+
Author check:
176+
>>> p = parse_person('Rohit Potter=>rohit@gmail.com')
177+
>>> assert p == ('Rohit Potter', 'rohit@gmail.com')
178+
>>> p = parse_person('Rohit Potter')
179+
>>> assert p == ('Rohit Potter', None)
180+
"""
181+
parsed = person_parser(person)
182+
if not parsed:
183+
parsed = person_parser_only_name(person)
184+
name = parsed.group('name')
185+
email = None
186+
else:
187+
name = parsed.group('name')
188+
email = parsed.group('email')
189+
190+
return name, email

0 commit comments

Comments
 (0)