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 ParseSpec
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 if the file is actually a podspec file
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_obj = ParseSpec ()
98+ podspec_data = podspec_obj .parse_spec (location )
99+ return build_package (podspec_data )
100+
101+
102+ def build_package (podspec_data ):
103+ """
104+ Return a Package object from a package data mapping or None.
105+ """
106+ name = podspec_data .get ('name' )
107+ version = podspec_data .get ('version' )
108+ declared_license = podspec_data .get ('license' )
109+ summary = podspec_data .get ('summary' )
110+ description = podspec_data .get ('description' )
111+ homepage_url = podspec_data .get ('homepage_url' )
112+ source = podspec_data .get ('source' )
113+ authors = podspec_data .get ('author' ) or None
114+
115+ author_names = []
116+ author_email = []
117+ if authors :
118+ for split_author in authors :
119+ split_author = split_author .strip ()
120+ author , email = parse_person (split_author )
121+ author_names .append (author )
122+ author_email .append (email )
123+
124+ parties = []
125+ if authors :
126+ parties .append (
127+ models .Party (
128+ type = models .party_person ,
129+ name = ', ' .join (author_names ),
130+ email = ', ' .join (author_email ),
131+ role = 'author'
132+ )
133+ )
134+
135+ if len (summary ) > len (description ):
136+ description = summary
137+
138+ package = CocoapodsPackage (
139+ name = name ,
140+ version = version ,
141+ vcs_url = source ,
142+ source_packages = list (source .split ('\n ' )),
143+ description = description ,
144+ declared_license = declared_license ,
145+ homepage_url = homepage_url ,
146+ parties = parties
147+ )
148+ return package
149+
150+
151+ person_parser = re .compile (
152+ r'^(?P<name>[\w\s(),-_.,]+)'
153+ r'=>'
154+ r'(?P<email>[\S+]+$)'
155+ ).match
156+
157+ person_parser_only_name = re .compile (
158+ r'^(?P<name>[\w\s(),-_.,]+)'
159+ ).match
160+
161+
162+ def parse_person (person ):
163+ """
164+ Return name and email from person string.
165+
166+ https://guides.cocoapods.org/syntax/podspec.html#authors
167+ Author can be in the form:
168+ s.author = 'Rohit Potter'
169+ or
170+ s.author = 'Rohit Potter=>rohit@gmail.com'
171+ Author check:
172+ >>> p = parse_person('Rohit Potter=>rohit@gmail.com')
173+ >>> assert p == ('Rohit Potter', 'rohit@gmail.com')
174+ >>> p = parse_person('Rohit Potter')
175+ >>> assert p == ('Rohit Potter', None)
176+ """
177+ parsed = person_parser (person )
178+ if not parsed :
179+ parsed = person_parser_only_name (person )
180+ name = parsed .group ('name' )
181+ email = None
182+ else :
183+ name = parsed .group ('name' )
184+ email = parsed .group ('email' )
185+
186+ return name , email
0 commit comments