cocoapods-graph 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +8 -0
- data/LICENSE +19 -0
- data/README.md +217 -0
- data/Rakefile +18 -0
- data/cocoapods-graph.gemspec +44 -0
- data/exe/cocoapods-graph +90 -0
- data/lib/cocoapods_graph/constants.rb +17 -0
- data/lib/cocoapods_graph/generator.rb +96 -0
- data/lib/cocoapods_graph/pod_class.rb +18 -0
- data/lib/cocoapods_graph/template.html +537 -0
- data/lib/cocoapods_graph/version.rb +3 -0
- data/lib/cocoapods_graph.rb +8 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6d00b5c1eced22d283334372175b45d6a166c71261dcada2731d9581a86facc9
|
4
|
+
data.tar.gz: 6c11214289c0328e13214292758d3757bcafa6f70367b9f84effa7b17e7b038b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1477ae702985ad56ff75ad3c134b41fdc85a216d0616399cdd4660b64613f84b588f30c0e56f0e2d1496bbee637f39687da166045d352ed927e02f46d2b205b9
|
7
|
+
data.tar.gz: edc4c0d543bd6ee201d84496041e0476c95b89a50db097a5746eb4efa2d5964f98fc734ddb2ad7467aa60f82cf32d9deec338b8fe47bf04cf12e54c8c872ab84
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [1.0.0] - 2024-12-15
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- 🎉 Initial release of CocoaPods Graph
|
12
|
+
- ✨ Interactive dependency wheel visualization using D3.js
|
13
|
+
- 🔍 Smart search functionality with auto-complete dropdown
|
14
|
+
- 📊 Real-time statistics dashboard (total packages, direct dependencies, total dependencies)
|
15
|
+
- 📱 Responsive design that works on desktop and mobile
|
16
|
+
- 🎨 Modern UI/UX with gradient backgrounds and smooth animations
|
17
|
+
- 🌐 Self-contained HTML output that works offline
|
18
|
+
- 📄 JSON export for programmatic access
|
19
|
+
- 🖥️ Console output for quick dependency inspection
|
20
|
+
- 💎 Ruby gem distribution for easy installation
|
21
|
+
- 📖 Comprehensive documentation and examples
|
22
|
+
|
23
|
+
### Technical Features
|
24
|
+
- **Command-line interface** with multiple output options
|
25
|
+
- **Modular Ruby architecture** with clean separation of concerns
|
26
|
+
- **Professional HTML template** with embedded CSS and JavaScript
|
27
|
+
- **D3.js integration** for interactive visualizations
|
28
|
+
- **Search and highlight** functionality for exploring large dependency graphs
|
29
|
+
- **Color-coded visualization** for easy package identification
|
30
|
+
- **Hover effects and animations** for better user experience
|
31
|
+
|
32
|
+
### Supported Formats
|
33
|
+
- HTML: Interactive dependency wheel with search functionality
|
34
|
+
- JSON: Structured data for programmatic use
|
35
|
+
- Console: Quick text-based dependency listing
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2025 Erick Jung
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
# 📦 CocoaPods Graph
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/cocoapods-graph)
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
5
|
+
|
6
|
+
Generate beautiful, interactive dependency graphs for your CocoaPods projects. Transform your `Podfile.lock` into stunning visualizations that help you understand complex pod relationships.
|
7
|
+
|
8
|
+

|
9
|
+
|
10
|
+
## ✨ Features
|
11
|
+
|
12
|
+
- **🎨 Interactive Dependency Wheel**: Beautiful D3.js visualization with hover effects and smooth animations
|
13
|
+
- **🔍 Smart Search**: Find and highlight specific packages instantly with auto-complete dropdown
|
14
|
+
- **📊 Detailed Statistics**: Real-time stats showing total packages, direct dependencies, and relationships
|
15
|
+
- **📱 Responsive Design**: Works perfectly on desktop and mobile devices
|
16
|
+
- **🎯 Modern UI/UX**: Clean, professional interface with gradient backgrounds and smooth transitions
|
17
|
+
- **⚡ Fast Performance**: Optimized for projects with hundreds of dependencies
|
18
|
+
- **🌐 Self-contained**: Generated HTML files work offline with no external dependencies
|
19
|
+
|
20
|
+
## 🚀 Installation
|
21
|
+
|
22
|
+
### Install as a Gem (Recommended)
|
23
|
+
|
24
|
+
```bash
|
25
|
+
gem install cocoapods-graph
|
26
|
+
```
|
27
|
+
|
28
|
+
### Install from Source
|
29
|
+
|
30
|
+
```bash
|
31
|
+
git clone https://github.com/erickjung/cocoapods-graph.git
|
32
|
+
cd cocoapods-graph
|
33
|
+
bundle install
|
34
|
+
gem build cocoapods-graph.gemspec
|
35
|
+
gem install cocoapods-graph-*.gem
|
36
|
+
```
|
37
|
+
|
38
|
+
## 📖 Usage
|
39
|
+
|
40
|
+
### Basic Usage
|
41
|
+
|
42
|
+
```bash
|
43
|
+
# Generate interactive HTML report
|
44
|
+
cocoapods-graph -f Podfile.lock --html
|
45
|
+
|
46
|
+
# Generate JSON data
|
47
|
+
cocoapods-graph -f Podfile.lock --json
|
48
|
+
|
49
|
+
# Print dependencies to console
|
50
|
+
cocoapods-graph -f Podfile.lock --show
|
51
|
+
|
52
|
+
# Generate both HTML and JSON
|
53
|
+
cocoapods-graph -f Podfile.lock --html --json
|
54
|
+
```
|
55
|
+
|
56
|
+
### Command Line Options
|
57
|
+
|
58
|
+
| Option | Description |
|
59
|
+
|--------|-------------|
|
60
|
+
| `-f, --file FILE` | Specify path to Podfile.lock file |
|
61
|
+
| `--html` | Generate interactive HTML dependency wheel |
|
62
|
+
| `--json` | Generate JSON data file |
|
63
|
+
| `--show` | Print dependencies to console |
|
64
|
+
| `-h, --help` | Show help message |
|
65
|
+
| `-v, --version` | Show version |
|
66
|
+
|
67
|
+
### Examples
|
68
|
+
|
69
|
+
```bash
|
70
|
+
# In your iOS project directory
|
71
|
+
cocoapods-graph -f Podfile.lock --html
|
72
|
+
# Creates: Podfile.lock.html
|
73
|
+
|
74
|
+
# Specify custom file path
|
75
|
+
cocoapods-graph -f MyProject/Podfile.lock --html --json
|
76
|
+
# Creates: MyProject/Podfile.lock.html and MyProject/Podfile.lock.json
|
77
|
+
|
78
|
+
# Quick dependency check
|
79
|
+
cocoapods-graph -f Podfile.lock --show
|
80
|
+
```
|
81
|
+
|
82
|
+
## 🎨 Features Showcase
|
83
|
+
|
84
|
+
### Interactive Dependency Wheel
|
85
|
+
|
86
|
+

|
87
|
+
*Example dependency visualization from the WordPress iOS app, showing the interactive dependency wheel with search functionality and statistics dashboard.*
|
88
|
+
|
89
|
+
📊 **[Try the live interactive demo!](docs/Wordpress-Report.html)** - Open the full WordPress iOS app dependency visualization
|
90
|
+
|
91
|
+
- **Hover effects**: Move your mouse over any package to see its relationships
|
92
|
+
- **Visual highlighting**: Related dependencies are highlighted while others fade
|
93
|
+
- **Color coding**: Each package gets a unique color for easy identification
|
94
|
+
- **Smooth animations**: Elegant transitions make exploration enjoyable
|
95
|
+
|
96
|
+
### Smart Search Functionality
|
97
|
+
- **Live search**: Results appear as you type
|
98
|
+
- **Auto-complete**: Dropdown shows matching packages with dependency counts
|
99
|
+
- **One-click selection**: Click any result to highlight it on the wheel
|
100
|
+
- **Keyboard navigation**: Use Enter/Escape for quick actions
|
101
|
+
|
102
|
+
### Professional Statistics Dashboard
|
103
|
+
- **Total Packages**: Count of all pods in your project
|
104
|
+
- **Direct Dependencies**: Number of pods your app directly depends on
|
105
|
+
- **Total Dependencies**: Sum of all dependency relationships
|
106
|
+
- **Real-time updates**: Stats update as you explore the graph
|
107
|
+
|
108
|
+
## 📁 Output Files
|
109
|
+
|
110
|
+
### HTML Report (`Podfile.lock.html`)
|
111
|
+
- Self-contained interactive visualization
|
112
|
+
- Modern, responsive design
|
113
|
+
- Search and highlight functionality
|
114
|
+
- Professional statistics dashboard
|
115
|
+
- Offline-ready (no internet required)
|
116
|
+
|
117
|
+
### JSON Data (`Podfile.lock.json`)
|
118
|
+
- Structured dependency data
|
119
|
+
- Perfect for CI/CD integration
|
120
|
+
- Easy to parse programmatically
|
121
|
+
- Compatible with other tools
|
122
|
+
|
123
|
+
## 🛠️ Development
|
124
|
+
|
125
|
+
### Setup Development Environment
|
126
|
+
|
127
|
+
```bash
|
128
|
+
git clone https://github.com/erickjung/cocoapods-graph.git
|
129
|
+
cd cocoapods-graph
|
130
|
+
bundle install
|
131
|
+
```
|
132
|
+
|
133
|
+
### Run Tests
|
134
|
+
|
135
|
+
```bash
|
136
|
+
bundle exec rake
|
137
|
+
```
|
138
|
+
|
139
|
+
### Build Gem Locally
|
140
|
+
|
141
|
+
```bash
|
142
|
+
gem build cocoapods-graph.gemspec
|
143
|
+
gem install cocoapods-graph-*.gem
|
144
|
+
```
|
145
|
+
|
146
|
+
## 🏗️ Project Structure
|
147
|
+
|
148
|
+
```
|
149
|
+
cocoapods-graph/
|
150
|
+
├── lib/
|
151
|
+
│ ├── cocoapods_graph.rb # Main module
|
152
|
+
│ └── cocoapods_graph/
|
153
|
+
│ ├── version.rb # Version constant
|
154
|
+
│ ├── pod_class.rb # Pod data structure
|
155
|
+
│ ├── generator.rb # Core parsing logic
|
156
|
+
│ └── template.html # HTML template
|
157
|
+
├── exe/
|
158
|
+
│ └── cocoapods-graph # Executable binary
|
159
|
+
├── cocoapods-graph.gemspec # Gem specification
|
160
|
+
└── README.md # This file
|
161
|
+
```
|
162
|
+
|
163
|
+
## 🤝 Contributing
|
164
|
+
|
165
|
+
We welcome contributions! Here's how you can help:
|
166
|
+
|
167
|
+
1. **Fork the repository**
|
168
|
+
2. **Create a feature branch**: `git checkout -b my-new-feature`
|
169
|
+
3. **Make your changes** and add tests if applicable
|
170
|
+
4. **Commit your changes**: `git commit -am 'Add some feature'`
|
171
|
+
5. **Push to the branch**: `git push origin my-new-feature`
|
172
|
+
6. **Submit a pull request**
|
173
|
+
|
174
|
+
### Ideas for Contributions
|
175
|
+
- Add more visualization types (tree view, network graph)
|
176
|
+
- Implement filtering and grouping options
|
177
|
+
- Add export to other formats (SVG, PNG, PDF)
|
178
|
+
- Create VS Code / Xcode extensions
|
179
|
+
- Add support for other dependency managers
|
180
|
+
|
181
|
+
## 📝 Changelog
|
182
|
+
|
183
|
+
### v1.0.0 (2024-XX-XX)
|
184
|
+
- 🎉 Initial release
|
185
|
+
- ✨ Interactive dependency wheel visualization
|
186
|
+
- 🔍 Smart search functionality
|
187
|
+
- 📊 Statistics dashboard
|
188
|
+
- 📱 Responsive design
|
189
|
+
- 🌐 Self-contained HTML output
|
190
|
+
|
191
|
+
## 📄 License
|
192
|
+
|
193
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
194
|
+
|
195
|
+
## 👨💻 Author
|
196
|
+
|
197
|
+
**Erick Jung** - [E7G](https://www.erickjung.com)
|
198
|
+
- Email: talk@erickjung.com
|
199
|
+
- GitHub: [@erickjung](https://github.com/erickjung)
|
200
|
+
|
201
|
+
## 🙏 Acknowledgments
|
202
|
+
|
203
|
+
- Built with [D3.js](https://d3js.org/) for beautiful visualizations
|
204
|
+
- Inspired by the need for better dependency analysis tools
|
205
|
+
- Thanks to the CocoaPods team for creating an amazing dependency manager
|
206
|
+
|
207
|
+
## ⭐ Support
|
208
|
+
|
209
|
+
If you find this tool helpful, please give it a star on GitHub! ⭐
|
210
|
+
|
211
|
+
Found a bug or have a feature request? [Open an issue](https://github.com/erickjung/cocoapods-graph/issues)
|
212
|
+
|
213
|
+
---
|
214
|
+
|
215
|
+
<div align="center">
|
216
|
+
<sub>Built with ❤️ by <a href="https://www.erickjung.com">E7G</a></sub>
|
217
|
+
</div>
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
desc 'Run tests'
|
7
|
+
task default: :spec
|
8
|
+
|
9
|
+
desc 'Install gem locally'
|
10
|
+
task :install_local do
|
11
|
+
sh 'gem build cocoapods-graph.gemspec'
|
12
|
+
sh 'gem install cocoapods-graph-*.gem'
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Uninstall gem'
|
16
|
+
task :uninstall do
|
17
|
+
sh 'gem uninstall cocoapods-graph'
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative "lib/cocoapods_graph/version"
|
2
|
+
require_relative "lib/cocoapods_graph/constants"
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = CocoaPodsGraph::PROJECT_NAME
|
6
|
+
spec.version = CocoaPodsGraph::VERSION
|
7
|
+
spec.authors = [CocoaPodsGraph::PROJECT_AUTHOR]
|
8
|
+
spec.email = [CocoaPodsGraph::PROJECT_EMAIL]
|
9
|
+
|
10
|
+
spec.summary = CocoaPodsGraph::PROJECT_SUMMARY
|
11
|
+
spec.description = "A Ruby gem that parses Podfile.lock files and generates beautiful, interactive dependency wheel visualizations using D3.js. Perfect for understanding complex pod relationships in your iOS projects."
|
12
|
+
spec.homepage = CocoaPodsGraph::PROJECT_HOMEPAGE
|
13
|
+
spec.license = CocoaPodsGraph::PROJECT_LICENSE
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = CocoaPodsGraph::PROJECT_SOURCE_CODE_URI
|
19
|
+
spec.metadata["changelog_uri"] = CocoaPodsGraph::PROJECT_CHANGELOG_URI
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
23
|
+
Dir.glob("lib/**/*") + Dir.glob("exe/*") + %w[
|
24
|
+
cocoapods-graph.gemspec
|
25
|
+
README.md
|
26
|
+
LICENSE
|
27
|
+
CHANGELOG.md
|
28
|
+
Gemfile
|
29
|
+
Rakefile
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
spec.bindir = "exe"
|
34
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
35
|
+
spec.require_paths = ["lib"]
|
36
|
+
|
37
|
+
# Runtime dependencies
|
38
|
+
spec.add_dependency "json", "~> 2.0"
|
39
|
+
|
40
|
+
# Development dependencies
|
41
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
42
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
43
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
44
|
+
end
|
data/exe/cocoapods-graph
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require_relative '../lib/cocoapods_graph'
|
6
|
+
|
7
|
+
module CocoaPodsGraph
|
8
|
+
class CLI
|
9
|
+
VERSION = CocoaPodsGraph::VERSION
|
10
|
+
|
11
|
+
def self.run
|
12
|
+
options = {}
|
13
|
+
|
14
|
+
parser = OptionParser.new do |parser|
|
15
|
+
parser.banner = 'Usage: cocoapods-graph [options]'
|
16
|
+
parser.on('-f', '--file FILE', 'Specify Podfile.lock file path') { |f| options[:file] = f }
|
17
|
+
parser.on('--show', 'Print dependencies on console') { options[:show] = true }
|
18
|
+
parser.on('--json', 'Save dependencies to JSON file') { options[:json] = true }
|
19
|
+
parser.on('--html', 'Save dependencies to interactive HTML wheel graph') { options[:html] = true }
|
20
|
+
parser.on('-v', '--version', 'Show version') do
|
21
|
+
puts VERSION
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
parser.on('-h', '--help', 'Show this help message') do
|
25
|
+
puts parser
|
26
|
+
puts "\nExamples:"
|
27
|
+
puts " cocoapods-graph -f Podfile.lock --html # Generate interactive HTML report"
|
28
|
+
puts " cocoapods-graph -f Podfile.lock --json # Generate JSON output"
|
29
|
+
puts " cocoapods-graph -f Podfile.lock --show # Print to console"
|
30
|
+
puts " cocoapods-graph -f Podfile.lock --html --json # Generate both HTML and JSON"
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
parser.parse!
|
37
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
38
|
+
puts "Error: #{e.message}"
|
39
|
+
puts parser
|
40
|
+
exit(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
if ARGV.empty? && options.empty?
|
44
|
+
puts "#{PROJECT_NAME} - #{VERSION} (#{PROJECT_DESCRIPTION})"
|
45
|
+
puts "by #{PROJECT_AUTHOR_WITH_EMAIL}"
|
46
|
+
puts 'Type: cocoapods-graph -h to see more information'
|
47
|
+
exit(1)
|
48
|
+
end
|
49
|
+
|
50
|
+
if options[:file] && !options[:file].empty?
|
51
|
+
unless options[:show] || options[:json] || options[:html]
|
52
|
+
puts 'You must select an output option (--show | --json | --html)'
|
53
|
+
exit(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Add .lock extension if not present
|
57
|
+
options[:file] += '.lock' unless options[:file].include?('.lock')
|
58
|
+
|
59
|
+
unless File.exist?(options[:file])
|
60
|
+
puts "Error: File '#{options[:file]}' not found"
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
result = CocoaPodsGraph::Generator.parse_lock_file(options[:file])
|
65
|
+
|
66
|
+
if options[:show]
|
67
|
+
puts 'Printing dependencies...'
|
68
|
+
result.each(&:print_object)
|
69
|
+
end
|
70
|
+
|
71
|
+
if options[:json]
|
72
|
+
puts 'Saving JSON file...'
|
73
|
+
CocoaPodsGraph::Generator.save_json_file(result, options[:file])
|
74
|
+
end
|
75
|
+
|
76
|
+
if options[:html]
|
77
|
+
puts 'Saving HTML file...'
|
78
|
+
CocoaPodsGraph::Generator.save_html_wheel_file(result, options[:file])
|
79
|
+
end
|
80
|
+
|
81
|
+
puts 'Done!'
|
82
|
+
else
|
83
|
+
puts 'Error: Please specify a Podfile.lock file with -f option'
|
84
|
+
exit(1)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
CocoaPodsGraph::CLI.run if __FILE__ == $PROGRAM_NAME
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CocoaPodsGraph
|
4
|
+
# Project metadata constants - single source of truth
|
5
|
+
PROJECT_NAME = 'cocoapods-graph'
|
6
|
+
PROJECT_DESCRIPTION = 'Generate interactive dependency graphs for CocoaPods projects'
|
7
|
+
PROJECT_AUTHOR = 'Erick Jung'
|
8
|
+
PROJECT_EMAIL = 'talk@erickjung.com'
|
9
|
+
PROJECT_HOMEPAGE = 'https://github.com/erickjung/cocoapods-graph'
|
10
|
+
PROJECT_SOURCE_CODE_URI = 'https://github.com/erickjung/cocoapods-graph'
|
11
|
+
PROJECT_CHANGELOG_URI = 'https://github.com/erickjung/cocoapods-graph/blob/main/CHANGELOG.md'
|
12
|
+
PROJECT_LICENSE = 'MIT'
|
13
|
+
|
14
|
+
# Derived constants for convenience
|
15
|
+
PROJECT_AUTHOR_WITH_EMAIL = "#{PROJECT_AUTHOR} (#{PROJECT_EMAIL})"
|
16
|
+
PROJECT_SUMMARY = 'Interactive dependency wheel visualizations for CocoaPods projects'
|
17
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module CocoaPodsGraph
|
5
|
+
class Generator
|
6
|
+
|
7
|
+
def self.parse_lock_file(file_name)
|
8
|
+
def self.parse_lock_pods_line(line)
|
9
|
+
begin
|
10
|
+
name = line[line.index('-') + 1...line.index('(')].strip
|
11
|
+
rescue
|
12
|
+
name = line[line.index('-') + 1..-1].strip
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
version = line[line.index('(') + 1...line.index(')')].strip
|
17
|
+
rescue
|
18
|
+
version = ''
|
19
|
+
end
|
20
|
+
|
21
|
+
PodClass.new(name, version, [])
|
22
|
+
end
|
23
|
+
|
24
|
+
content_file = File.readlines(file_name).map { |x| x.chomp.delete('"') }
|
25
|
+
|
26
|
+
result_list = []
|
27
|
+
pod = PodClass.new('', '', [])
|
28
|
+
|
29
|
+
content_file.each do |line|
|
30
|
+
if line.start_with?(' -')
|
31
|
+
result_list << pod if pod.name.length > 0
|
32
|
+
pod = parse_lock_pods_line(line)
|
33
|
+
elsif line.start_with?(' -')
|
34
|
+
pod.dependencies << parse_lock_pods_line(line)
|
35
|
+
elsif line.start_with?('DEPENDENCIES')
|
36
|
+
result_list << pod if pod.name.length > 0
|
37
|
+
break
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
result_list
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.generate_json(result_list)
|
45
|
+
def self.generate_json_deps(result_list)
|
46
|
+
data = '{'
|
47
|
+
result_list.each do |result|
|
48
|
+
data += '"%s":"1",' % [result.name]
|
49
|
+
end
|
50
|
+
data = data[0...-1]
|
51
|
+
data + '}'
|
52
|
+
end
|
53
|
+
|
54
|
+
json = '{"packages": ['
|
55
|
+
deps = generate_json_deps(result_list)
|
56
|
+
json += '{"name":"app","require":%s},' % deps
|
57
|
+
|
58
|
+
result_list.each do |pod|
|
59
|
+
if pod.dependencies.length > 0
|
60
|
+
pod_deps = generate_json_deps(pod.dependencies)
|
61
|
+
json += '{"name":"%s","require":%s},' % [pod.name, pod_deps]
|
62
|
+
else
|
63
|
+
json += '{"name":"%s","require":{}},' % pod.name
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
json[0...-1] + ']}'
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.save_json_file(data, file_name)
|
71
|
+
File.open(file_name + '.json', 'w') do |outfile|
|
72
|
+
outfile.write(generate_json(data))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.save_html_wheel_file(data, file_name)
|
77
|
+
template_path = File.join(__dir__, 'template.html')
|
78
|
+
p_template = File.read(template_path)
|
79
|
+
p_data_json = "'%s'" % generate_json(data)
|
80
|
+
p_data_width = '960'
|
81
|
+
p_data_margin = '200'
|
82
|
+
p_data_padding = '.02'
|
83
|
+
p_generation_date = Time.now.strftime('%B %d, %Y at %I:%M %p')
|
84
|
+
|
85
|
+
html_out = p_template.gsub('P_DATA_JSON', p_data_json)
|
86
|
+
.gsub('P_DATA_WIDTH', p_data_width)
|
87
|
+
.gsub('P_DATA_MARGIN', p_data_margin)
|
88
|
+
.gsub('P_DATA_PADDING', p_data_padding)
|
89
|
+
.gsub('P_GENERATION_DATE', p_generation_date)
|
90
|
+
|
91
|
+
File.open(file_name + '.html', 'w') do |outfile|
|
92
|
+
outfile.write(html_out)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CocoaPodsGraph
|
2
|
+
class PodClass
|
3
|
+
attr_accessor :name, :version, :dependencies
|
4
|
+
|
5
|
+
def initialize(name, version, dependencies)
|
6
|
+
@name = name
|
7
|
+
@version = version
|
8
|
+
@dependencies = dependencies
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_object
|
12
|
+
puts "#{@name} #{@version}"
|
13
|
+
@dependencies.each do |dep|
|
14
|
+
puts " #{dep.name} #{dep.version}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,537 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>CocoaPods Dependencies Graph</title>
|
7
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
8
|
+
<style>
|
9
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
10
|
+
|
11
|
+
body {
|
12
|
+
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
13
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
14
|
+
min-height: 100vh;
|
15
|
+
color: #333;
|
16
|
+
line-height: 1.6;
|
17
|
+
}
|
18
|
+
|
19
|
+
.header {
|
20
|
+
text-align: center;
|
21
|
+
padding: 2rem 1rem 1rem;
|
22
|
+
color: white;
|
23
|
+
}
|
24
|
+
|
25
|
+
.header h1 {
|
26
|
+
font-size: 2.5rem;
|
27
|
+
font-weight: 700;
|
28
|
+
margin-bottom: 0.5rem;
|
29
|
+
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
30
|
+
}
|
31
|
+
|
32
|
+
.header p {
|
33
|
+
font-size: 1.1rem;
|
34
|
+
opacity: 0.9;
|
35
|
+
font-weight: 300;
|
36
|
+
}
|
37
|
+
|
38
|
+
.main-container {
|
39
|
+
max-width: 1200px;
|
40
|
+
margin: 0 auto;
|
41
|
+
padding: 0 1rem;
|
42
|
+
}
|
43
|
+
|
44
|
+
.chart-container {
|
45
|
+
background: white;
|
46
|
+
border-radius: 16px;
|
47
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
48
|
+
padding: 2rem;
|
49
|
+
margin-bottom: 2rem;
|
50
|
+
position: relative;
|
51
|
+
overflow: hidden;
|
52
|
+
}
|
53
|
+
|
54
|
+
.chart-container::before {
|
55
|
+
content: "";
|
56
|
+
position: absolute;
|
57
|
+
top: 0;
|
58
|
+
left: 0;
|
59
|
+
right: 0;
|
60
|
+
height: 4px;
|
61
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
62
|
+
}
|
63
|
+
|
64
|
+
.chart-header {
|
65
|
+
text-align: center;
|
66
|
+
margin-bottom: 2rem;
|
67
|
+
}
|
68
|
+
|
69
|
+
.chart-title {
|
70
|
+
font-size: 1.5rem;
|
71
|
+
font-weight: 600;
|
72
|
+
color: #2d3748;
|
73
|
+
margin-bottom: 0.5rem;
|
74
|
+
}
|
75
|
+
|
76
|
+
.chart-subtitle {
|
77
|
+
color: #718096;
|
78
|
+
font-size: 0.95rem;
|
79
|
+
}
|
80
|
+
|
81
|
+
#chart_placeholder {
|
82
|
+
display: flex;
|
83
|
+
justify-content: center;
|
84
|
+
align-items: center;
|
85
|
+
min-height: 500px;
|
86
|
+
position: relative;
|
87
|
+
}
|
88
|
+
|
89
|
+
.dependencyWheel {
|
90
|
+
font: 11px "Inter", sans-serif;
|
91
|
+
filter: drop-shadow(0 4px 8px rgba(0,0,0,0.1));
|
92
|
+
}
|
93
|
+
|
94
|
+
.group text {
|
95
|
+
font-weight: 500;
|
96
|
+
font-size: 11px;
|
97
|
+
}
|
98
|
+
|
99
|
+
.chord {
|
100
|
+
transition: opacity 0.3s ease;
|
101
|
+
}
|
102
|
+
|
103
|
+
.group path {
|
104
|
+
transition: all 0.3s ease;
|
105
|
+
}
|
106
|
+
|
107
|
+
.group:hover path {
|
108
|
+
transform: scale(1.02);
|
109
|
+
filter: brightness(1.1);
|
110
|
+
}
|
111
|
+
|
112
|
+
.instructions {
|
113
|
+
background: #f7fafc;
|
114
|
+
border-radius: 12px;
|
115
|
+
padding: 1.5rem;
|
116
|
+
margin-bottom: 2rem;
|
117
|
+
border-left: 4px solid #667eea;
|
118
|
+
}
|
119
|
+
|
120
|
+
.instructions h3 {
|
121
|
+
color: #2d3748;
|
122
|
+
font-size: 1.1rem;
|
123
|
+
font-weight: 600;
|
124
|
+
margin-bottom: 0.5rem;
|
125
|
+
display: flex;
|
126
|
+
align-items: center;
|
127
|
+
gap: 0.5rem;
|
128
|
+
}
|
129
|
+
|
130
|
+
.instructions ul {
|
131
|
+
color: #4a5568;
|
132
|
+
font-size: 0.9rem;
|
133
|
+
list-style: none;
|
134
|
+
padding-left: 0;
|
135
|
+
}
|
136
|
+
|
137
|
+
.instructions li {
|
138
|
+
margin-bottom: 0.3rem;
|
139
|
+
padding-left: 1.2rem;
|
140
|
+
position: relative;
|
141
|
+
}
|
142
|
+
|
143
|
+
.instructions li::before {
|
144
|
+
content: "•";
|
145
|
+
color: #667eea;
|
146
|
+
font-weight: bold;
|
147
|
+
position: absolute;
|
148
|
+
left: 0;
|
149
|
+
}
|
150
|
+
|
151
|
+
.search-container {
|
152
|
+
background: #f8fafc;
|
153
|
+
border-radius: 8px;
|
154
|
+
padding: 1rem;
|
155
|
+
margin-bottom: 1.5rem;
|
156
|
+
border: 1px solid #e2e8f0;
|
157
|
+
}
|
158
|
+
|
159
|
+
.search-header {
|
160
|
+
display: flex;
|
161
|
+
align-items: center;
|
162
|
+
gap: 0.5rem;
|
163
|
+
margin-bottom: 0.75rem;
|
164
|
+
color: #2d3748;
|
165
|
+
font-weight: 600;
|
166
|
+
font-size: 1rem;
|
167
|
+
}
|
168
|
+
|
169
|
+
.search-input-container {
|
170
|
+
position: relative;
|
171
|
+
margin-bottom: 0.75rem;
|
172
|
+
}
|
173
|
+
|
174
|
+
.search-input {
|
175
|
+
width: 100%;
|
176
|
+
padding: 0.75rem 1rem 0.75rem 2.5rem;
|
177
|
+
border: 2px solid #e2e8f0;
|
178
|
+
border-radius: 8px;
|
179
|
+
font-size: 1rem;
|
180
|
+
font-family: inherit;
|
181
|
+
transition: all 0.3s ease;
|
182
|
+
}
|
183
|
+
|
184
|
+
.search-input:focus {
|
185
|
+
outline: none;
|
186
|
+
border-color: #667eea;
|
187
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
188
|
+
}
|
189
|
+
|
190
|
+
.search-icon {
|
191
|
+
position: absolute;
|
192
|
+
left: 0.75rem;
|
193
|
+
top: 50%;
|
194
|
+
transform: translateY(-50%);
|
195
|
+
color: #718096;
|
196
|
+
font-size: 1.1rem;
|
197
|
+
}
|
198
|
+
|
199
|
+
.search-results {
|
200
|
+
max-height: 200px;
|
201
|
+
overflow-y: auto;
|
202
|
+
border: 1px solid #e2e8f0;
|
203
|
+
border-radius: 6px;
|
204
|
+
background: white;
|
205
|
+
position: absolute;
|
206
|
+
width: 100%;
|
207
|
+
z-index: 10;
|
208
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
209
|
+
display: none;
|
210
|
+
}
|
211
|
+
|
212
|
+
.search-result-item {
|
213
|
+
padding: 0.75rem 1rem;
|
214
|
+
cursor: pointer;
|
215
|
+
border-bottom: 1px solid #f7fafc;
|
216
|
+
transition: background-color 0.2s ease;
|
217
|
+
display: flex;
|
218
|
+
justify-content: space-between;
|
219
|
+
align-items: center;
|
220
|
+
}
|
221
|
+
|
222
|
+
.search-result-item:hover {
|
223
|
+
background-color: #f7fafc;
|
224
|
+
}
|
225
|
+
|
226
|
+
.search-result-item:last-child {
|
227
|
+
border-bottom: none;
|
228
|
+
}
|
229
|
+
|
230
|
+
.search-result-name {
|
231
|
+
font-weight: 500;
|
232
|
+
color: #2d3748;
|
233
|
+
}
|
234
|
+
|
235
|
+
.search-result-version {
|
236
|
+
font-size: 0.85rem;
|
237
|
+
color: #718096;
|
238
|
+
font-family: monospace;
|
239
|
+
}
|
240
|
+
|
241
|
+
.stats {
|
242
|
+
display: grid;
|
243
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
244
|
+
gap: 1rem;
|
245
|
+
margin-bottom: 2rem;
|
246
|
+
}
|
247
|
+
|
248
|
+
.stat-card {
|
249
|
+
background: white;
|
250
|
+
border-radius: 12px;
|
251
|
+
padding: 1.5rem;
|
252
|
+
text-align: center;
|
253
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
254
|
+
border: 1px solid #e2e8f0;
|
255
|
+
}
|
256
|
+
|
257
|
+
.stat-number {
|
258
|
+
font-size: 2rem;
|
259
|
+
font-weight: 700;
|
260
|
+
color: #667eea;
|
261
|
+
margin-bottom: 0.25rem;
|
262
|
+
}
|
263
|
+
|
264
|
+
.stat-label {
|
265
|
+
color: #718096;
|
266
|
+
font-size: 0.9rem;
|
267
|
+
font-weight: 500;
|
268
|
+
}
|
269
|
+
|
270
|
+
.footer {
|
271
|
+
text-align: center;
|
272
|
+
padding: 2rem 1rem;
|
273
|
+
color: rgba(255,255,255,0.8);
|
274
|
+
font-size: 0.85rem;
|
275
|
+
}
|
276
|
+
|
277
|
+
.loading {
|
278
|
+
display: flex;
|
279
|
+
align-items: center;
|
280
|
+
justify-content: center;
|
281
|
+
min-height: 300px;
|
282
|
+
color: #718096;
|
283
|
+
font-size: 1.1rem;
|
284
|
+
}
|
285
|
+
|
286
|
+
.spinner {
|
287
|
+
width: 40px;
|
288
|
+
height: 40px;
|
289
|
+
border: 3px solid #e2e8f0;
|
290
|
+
border-top: 3px solid #667eea;
|
291
|
+
border-radius: 50%;
|
292
|
+
animation: spin 1s linear infinite;
|
293
|
+
margin-right: 1rem;
|
294
|
+
}
|
295
|
+
|
296
|
+
@keyframes spin {
|
297
|
+
0% { transform: rotate(0deg); }
|
298
|
+
100% { transform: rotate(360deg); }
|
299
|
+
}
|
300
|
+
|
301
|
+
@media (max-width: 768px) {
|
302
|
+
.header h1 { font-size: 2rem; }
|
303
|
+
.chart-container { padding: 1rem; margin: 0 0.5rem 1rem; }
|
304
|
+
.stats { grid-template-columns: 1fr 1fr; gap: 0.75rem; }
|
305
|
+
}
|
306
|
+
</style>
|
307
|
+
</head>
|
308
|
+
<body>
|
309
|
+
<div class="header">
|
310
|
+
<h1>📦 CocoaPods Dependencies</h1>
|
311
|
+
<p>Interactive dependency visualization for your iOS project</p>
|
312
|
+
</div>
|
313
|
+
|
314
|
+
<div class="main-container">
|
315
|
+
<div class="stats" id="stats"></div>
|
316
|
+
|
317
|
+
<div class="chart-container">
|
318
|
+
<div class="chart-header">
|
319
|
+
<h2 class="chart-title">Dependencies Wheel</h2>
|
320
|
+
<p class="chart-subtitle">Hover over packages to explore relationships</p>
|
321
|
+
</div>
|
322
|
+
|
323
|
+
<div class="search-container">
|
324
|
+
<div class="search-header">
|
325
|
+
🔍 Search Packages
|
326
|
+
</div>
|
327
|
+
<div class="search-input-container">
|
328
|
+
<span class="search-icon">🔍</span>
|
329
|
+
<input
|
330
|
+
type="text"
|
331
|
+
id="packageSearch"
|
332
|
+
class="search-input"
|
333
|
+
placeholder="Type package name to search and highlight..."
|
334
|
+
autocomplete="off"
|
335
|
+
/>
|
336
|
+
<div id="searchResults" class="search-results"></div>
|
337
|
+
</div>
|
338
|
+
</div>
|
339
|
+
|
340
|
+
<div id="chart_placeholder">
|
341
|
+
<div class="loading">
|
342
|
+
<div class="spinner"></div>
|
343
|
+
Loading dependency graph...
|
344
|
+
</div>
|
345
|
+
</div>
|
346
|
+
</div>
|
347
|
+
|
348
|
+
<div class="instructions">
|
349
|
+
<h3>🎯 How to use this visualization</h3>
|
350
|
+
<ul>
|
351
|
+
<li>Hover over any package name or arc to highlight its dependencies</li>
|
352
|
+
<li>The curves (chords) show dependency relationships between packages</li>
|
353
|
+
<li>Thicker arcs represent packages with more dependencies</li>
|
354
|
+
<li>Colors help distinguish different packages and their relationships</li>
|
355
|
+
</ul>
|
356
|
+
</div>
|
357
|
+
</div>
|
358
|
+
|
359
|
+
<div class="footer">
|
360
|
+
Generated by CocoaPods Graph • Made with ❤️ by <a href="https://www.erickjung.com" target="_blank" style="color: rgba(255,255,255,0.9); text-decoration: none;">E7G</a><br>
|
361
|
+
<small>Generated on P_GENERATION_DATE</small>
|
362
|
+
</div>
|
363
|
+
|
364
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
|
365
|
+
<script>
|
366
|
+
// Parse data and show stats
|
367
|
+
const data = JSON.parse(P_DATA_JSON);
|
368
|
+
const totalPackages = data.packages.length;
|
369
|
+
const appDependencies = Object.keys(data.packages[0].require).length;
|
370
|
+
const totalDependencies = data.packages.reduce((sum, pkg) => sum + Object.keys(pkg.require).length, 0);
|
371
|
+
|
372
|
+
document.getElementById("stats").innerHTML = `
|
373
|
+
<div class="stat-card">
|
374
|
+
<div class="stat-number">${totalPackages}</div>
|
375
|
+
<div class="stat-label">Total Packages</div>
|
376
|
+
</div>
|
377
|
+
<div class="stat-card">
|
378
|
+
<div class="stat-number">${appDependencies}</div>
|
379
|
+
<div class="stat-label">Direct Dependencies</div>
|
380
|
+
</div>
|
381
|
+
<div class="stat-card">
|
382
|
+
<div class="stat-number">${totalDependencies}</div>
|
383
|
+
<div class="stat-label">Total Dependencies</div>
|
384
|
+
</div>
|
385
|
+
`;
|
386
|
+
|
387
|
+
// D3 dependency wheel code
|
388
|
+
d3.chart=d3.chart||{},d3.chart.dependencyWheel=function(e){var t=window,r=document,n=r.documentElement,a=r.getElementsByTagName("body")[0],c=(t.innerWidth||n.clientWidth||a.clientWidth,t.innerHeight||n.clientHeight||a.clientHeight,200),i=.03,o=960;function u(e){e.each(function(e){var t=e.matrix,r=e.packageNames,n=o/2-c,a=d3.chord().padAngle(i).sortSubgroups(d3.descending),u=d3.select(this).selectAll("svg").data([e]).enter().append("svg:svg").attr("width",o).attr("height",o).attr("class","dependencyWheel").append("g").attr("transform","translate("+o/2+","+o/2+")"),d=d3.arc().innerRadius(n).outerRadius(n+20),l=function(e){return 0===e.index?"#ccc":"hsl("+parseInt((r[e.index][0].charCodeAt()-97)/26*360,10)+",90%,70%)"},s=function(e){return function(t,r){u.selectAll(".chord").filter(function(e){return e.source.index!=r&&e.target.index!=r}).transition().style("opacity",e);var n=[];u.selectAll(".chord").filter(function(e){e.source.index==r&&n.push(e.target.index),e.target.index==r&&n.push(e.source.index)}),n.push(r);var a=n.length;u.selectAll(".group").filter(function(e){for(var t=0;t<a;t++)if(n[t]==e.index)return!1;return!0}).transition().style("opacity",e)}},g=a(t),p=g.groups[0],h=-(p.endAngle-p.startAngle)/2*(180/Math.PI),f=u.selectAll("g.group").data(g.groups).enter().append("svg:g").attr("class","group").attr("transform",function(e){return"rotate("+h+")"});f.append("svg:path").style("fill",l).style("stroke",l).attr("d",d).style("cursor","pointer").on("mouseover",s(.1)).on("mouseout",s(1)),f.append("svg:text").each(function(e){e.angle=(e.startAngle+e.endAngle)/2}).attr("dy",".35em").attr("text-anchor",function(e){return e.angle>Math.PI?"end":null}).attr("transform",function(e){return"rotate("+(180*e.angle/Math.PI-90)+")translate("+(n+26)+")"+(e.angle>Math.PI?"rotate(180)":"")}).style("cursor","pointer").text(function(e){return r[e.index]}).on("mouseover",s(.1)).on("mouseout",s(1)),u.selectAll("path.chord").data(g).enter().append("svg:path").attr("class","chord").style("stroke",function(e){return d3.rgb(l(e.source)).darker()}).style("fill",function(e){return l(e.source)}).attr("d",d3.ribbon().radius(n)).attr("transform",function(e){return"rotate("+h+")"}).style("opacity",1)})}return u.width=function(e){return arguments.length?(o=e,u):o},u.margin=function(e){return arguments.length?(c=e,u):c},u.padding=function(e){return arguments.length?(i=e,u):i},u};
|
389
|
+
var buildMatrixFromDependencies=function(e){var t=e.packages,r={},n={},a=[],c=0,i={};return t.forEach(function(e){if(e.replace)for(replaced in e.replace)i[replaced]=e.name}),t.forEach(function(e){for(packageName in e.require)packageName in i&&(e.require[i[packageName]]=e.require[packageName],delete e.require[packageName])}),t.forEach(function(e){packageName=e.name,packageName in r||(n[c]=packageName,r[packageName]=c++)}),t.forEach(function(e){var t=r[e.name],n=a[t];if(!n){n=a[t]=[];for(var i=-1;++i<c;)n[i]=0}for(packageName in e.require)n[r[packageName]]++}),a.forEach(function(e,t){for(var r=.001,n=-1;++n<c;){var a=(n+t)%c;1==e[a]&&(e[a]+=r,r+=.001)}}),{matrix:a,packageNames:n}};
|
390
|
+
|
391
|
+
// Clear loading and render chart
|
392
|
+
d3.select("#chart_placeholder").selectAll("*").remove();
|
393
|
+
d3.select("#chart_placeholder").datum(buildMatrixFromDependencies(data)).call(d3.chart.dependencyWheel().width(P_DATA_WIDTH).margin(P_DATA_MARGIN).padding(P_DATA_PADDING));
|
394
|
+
|
395
|
+
// Search functionality
|
396
|
+
let currentHighlightedIndex = -1;
|
397
|
+
const packageNames = data.packages.map(pkg => pkg.name);
|
398
|
+
|
399
|
+
const searchInput = document.getElementById("packageSearch");
|
400
|
+
const searchResults = document.getElementById("searchResults");
|
401
|
+
|
402
|
+
function highlightPackage(packageName, temporary = false) {
|
403
|
+
const packageIndex = packageNames.indexOf(packageName);
|
404
|
+
if (packageIndex === -1) return;
|
405
|
+
|
406
|
+
// Find the corresponding group element and trigger mouseover
|
407
|
+
const svg = d3.select("#chart_placeholder svg");
|
408
|
+
const groups = svg.selectAll(".group");
|
409
|
+
|
410
|
+
groups.each(function(d, i) {
|
411
|
+
if (i === packageIndex) {
|
412
|
+
// Reset all to normal first
|
413
|
+
resetHighlight();
|
414
|
+
|
415
|
+
// Highlight this package
|
416
|
+
currentHighlightedIndex = packageIndex;
|
417
|
+
d3.select(this).select("path").style("stroke-width", "3px").style("filter", "brightness(1.2)");
|
418
|
+
d3.select(this).select("text").style("font-weight", "bold").style("font-size", "13px");
|
419
|
+
|
420
|
+
// Fade non-related elements
|
421
|
+
svg.selectAll(".chord")
|
422
|
+
.filter(function(chord) {
|
423
|
+
return chord.source.index !== packageIndex && chord.target.index !== packageIndex;
|
424
|
+
})
|
425
|
+
.transition().style("opacity", 0.1);
|
426
|
+
|
427
|
+
svg.selectAll(".group")
|
428
|
+
.filter(function(group, idx) {
|
429
|
+
// Find related packages
|
430
|
+
let related = [packageIndex];
|
431
|
+
svg.selectAll(".chord").each(function(chord) {
|
432
|
+
if (chord.source.index === packageIndex) related.push(chord.target.index);
|
433
|
+
if (chord.target.index === packageIndex) related.push(chord.source.index);
|
434
|
+
});
|
435
|
+
return related.indexOf(idx) === -1;
|
436
|
+
})
|
437
|
+
.transition().style("opacity", 0.3);
|
438
|
+
}
|
439
|
+
});
|
440
|
+
}
|
441
|
+
|
442
|
+
function resetHighlight() {
|
443
|
+
const svg = d3.select("#chart_placeholder svg");
|
444
|
+
svg.selectAll(".chord").transition().style("opacity", 1);
|
445
|
+
svg.selectAll(".group").transition().style("opacity", 1);
|
446
|
+
svg.selectAll(".group path").style("stroke-width", "1px").style("filter", "none");
|
447
|
+
svg.selectAll(".group text").style("font-weight", "500").style("font-size", "11px");
|
448
|
+
currentHighlightedIndex = -1;
|
449
|
+
}
|
450
|
+
|
451
|
+
function performSearch(query) {
|
452
|
+
if (!query.trim()) {
|
453
|
+
searchResults.style.display = "none";
|
454
|
+
resetHighlight();
|
455
|
+
return;
|
456
|
+
}
|
457
|
+
|
458
|
+
const filtered = data.packages.filter(pkg =>
|
459
|
+
pkg.name.toLowerCase().includes(query.toLowerCase())
|
460
|
+
).slice(0, 10); // Limit to 10 results
|
461
|
+
|
462
|
+
if (filtered.length === 0) {
|
463
|
+
searchResults.style.display = "none";
|
464
|
+
resetHighlight();
|
465
|
+
return;
|
466
|
+
}
|
467
|
+
|
468
|
+
searchResults.innerHTML = filtered.map(pkg => `
|
469
|
+
<div class="search-result-item" data-package="${pkg.name}">
|
470
|
+
<span class="search-result-name">${pkg.name}</span>
|
471
|
+
<span class="search-result-version">${Object.keys(pkg.require).length} deps</span>
|
472
|
+
</div>
|
473
|
+
`).join("");
|
474
|
+
|
475
|
+
searchResults.style.display = "block";
|
476
|
+
|
477
|
+
// Add click handlers to results
|
478
|
+
searchResults.querySelectorAll(".search-result-item").forEach(item => {
|
479
|
+
item.addEventListener("click", function() {
|
480
|
+
const packageName = this.dataset.package;
|
481
|
+
searchInput.value = packageName;
|
482
|
+
searchResults.style.display = "none";
|
483
|
+
highlightPackage(packageName);
|
484
|
+
});
|
485
|
+
|
486
|
+
item.addEventListener("mouseenter", function() {
|
487
|
+
const packageName = this.dataset.package;
|
488
|
+
highlightPackage(packageName, true);
|
489
|
+
});
|
490
|
+
|
491
|
+
item.addEventListener("mouseleave", function() {
|
492
|
+
if (currentHighlightedIndex === -1) {
|
493
|
+
resetHighlight();
|
494
|
+
}
|
495
|
+
});
|
496
|
+
});
|
497
|
+
|
498
|
+
// Auto-highlight first result if exact match
|
499
|
+
if (filtered.length === 1 || filtered[0].name.toLowerCase() === query.toLowerCase()) {
|
500
|
+
highlightPackage(filtered[0].name, true);
|
501
|
+
}
|
502
|
+
}
|
503
|
+
|
504
|
+
// Search input events
|
505
|
+
searchInput.addEventListener("input", function() {
|
506
|
+
performSearch(this.value);
|
507
|
+
});
|
508
|
+
|
509
|
+
searchInput.addEventListener("keydown", function(e) {
|
510
|
+
if (e.key === "Enter") {
|
511
|
+
const results = searchResults.querySelectorAll(".search-result-item");
|
512
|
+
if (results.length > 0) {
|
513
|
+
results[0].click();
|
514
|
+
}
|
515
|
+
} else if (e.key === "Escape") {
|
516
|
+
this.value = "";
|
517
|
+
searchResults.style.display = "none";
|
518
|
+
resetHighlight();
|
519
|
+
}
|
520
|
+
});
|
521
|
+
|
522
|
+
// Click outside to close search results
|
523
|
+
document.addEventListener("click", function(e) {
|
524
|
+
if (!e.target.closest(".search-input-container")) {
|
525
|
+
searchResults.style.display = "none";
|
526
|
+
}
|
527
|
+
});
|
528
|
+
|
529
|
+
// Double-click on chart to reset
|
530
|
+
document.getElementById("chart_placeholder").addEventListener("dblclick", function() {
|
531
|
+
searchInput.value = "";
|
532
|
+
searchResults.style.display = "none";
|
533
|
+
resetHighlight();
|
534
|
+
});
|
535
|
+
</script>
|
536
|
+
</body>
|
537
|
+
</html>
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cocoapods-graph
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Erick Jung
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: A Ruby gem that parses Podfile.lock files and generates beautiful, interactive
|
70
|
+
dependency wheel visualizations using D3.js. Perfect for understanding complex pod
|
71
|
+
relationships in your iOS projects.
|
72
|
+
email:
|
73
|
+
- talk@erickjung.com
|
74
|
+
executables:
|
75
|
+
- cocoapods-graph
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- CHANGELOG.md
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- cocoapods-graph.gemspec
|
85
|
+
- exe/cocoapods-graph
|
86
|
+
- lib/cocoapods_graph.rb
|
87
|
+
- lib/cocoapods_graph/constants.rb
|
88
|
+
- lib/cocoapods_graph/generator.rb
|
89
|
+
- lib/cocoapods_graph/pod_class.rb
|
90
|
+
- lib/cocoapods_graph/template.html
|
91
|
+
- lib/cocoapods_graph/version.rb
|
92
|
+
homepage: https://github.com/erickjung/cocoapods-graph
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata:
|
96
|
+
allowed_push_host: https://rubygems.org
|
97
|
+
homepage_uri: https://github.com/erickjung/cocoapods-graph
|
98
|
+
source_code_uri: https://github.com/erickjung/cocoapods-graph
|
99
|
+
changelog_uri: https://github.com/erickjung/cocoapods-graph/blob/main/CHANGELOG.md
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: 2.6.0
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubygems_version: 3.4.19
|
116
|
+
signing_key:
|
117
|
+
specification_version: 4
|
118
|
+
summary: Interactive dependency wheel visualizations for CocoaPods projects
|
119
|
+
test_files: []
|